fix(client): keep hints while user edits code (#44085)

* fix: only reset hints and test button on restart

Previously it would be triggered by any change to the editor contents

* refactor: move output node reset into own function

* refactor: reorganise init/update editable region args

* fix: control when line decorations update

* refactor: clean up updateEditableRegion
pull/44078/head
Oliver Eyton-Williams 2021-11-01 17:43:42 +01:00 committed by GitHub
parent f163a77fe5
commit c712f22667
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 67 additions and 62 deletions

View File

@ -538,6 +538,31 @@ const Editor = (props: EditorProps): JSX.Element => {
return outputNode;
}
function resetOutputNode() {
const { model, insideEditDecId } = dataRef.current;
const testButton = document.getElementById('test-button');
if (testButton) {
testButton.innerHTML = 'Check Your Code (Ctrl + Enter)';
testButton.onclick = () => {
props.executeChallenge();
};
}
const testStatus = document.getElementById('test-status');
if (testStatus) {
testStatus.innerHTML = '';
}
const testOutput = document.getElementById('test-output');
if (testOutput) {
testOutput.innerHTML = '';
}
// Resetting margin decorations
const range = model?.getDecorationRange(insideEditDecId);
if (range) {
updateEditableRegion(range, { model });
}
}
function focusOnHotkeys() {
const currContainerRef = props.containerRef.current;
if (currContainerRef) {
@ -577,36 +602,44 @@ const Editor = (props: EditorProps): JSX.Element => {
// TODO: DRY this and the update function
function initializeEditableRegion(
stickiness: number,
target: editor.ITextModel,
range: IRange
range: IRange,
modelContext: {
monaco: typeof monacoEditor;
model: editor.ITextModel;
}
) {
const { monaco, model } = modelContext;
const lineDecoration = {
range,
options: {
isWholeLine: true,
linesDecorationsClassName: 'myEditableLineDecoration',
stickiness
stickiness:
monaco.editor.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges
}
};
return target.deltaDecorations([], [lineDecoration]);
return model.deltaDecorations([], [lineDecoration]);
}
function updateEditableRegion(
stickiness: number,
target: editor.ITextModel,
range: IRange,
oldIds: string[] = []
modelContext: {
model?: editor.ITextModel;
},
options: editor.IModelDecorationOptions = {}
) {
const { model } = modelContext;
const { insideEditDecId } = dataRef.current;
const oldOptions = model?.getDecorationOptions(insideEditDecId);
const lineDecoration = {
range,
options: {
isWholeLine: true,
linesDecorationsClassName: 'myEditableLineDecoration',
stickiness
...oldOptions,
...options
}
};
return target.deltaDecorations(oldIds, [lineDecoration]);
model?.deltaDecorations([insideEditDecId], [lineDecoration]);
}
function getDescriptionZoneTop() {
@ -693,11 +726,10 @@ const Editor = (props: EditorProps): JSX.Element => {
editableRegion[1] - 1
]);
dataRef.current.insideEditDecId = initializeEditableRegion(
monaco.editor.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
model,
editableRange
)[0];
dataRef.current.insideEditDecId = initializeEditableRegion(editableRange, {
monaco,
model
})[0];
}
function addWidgetsToRegions(editor: editor.IStandaloneCodeEditor) {
@ -757,18 +789,13 @@ const Editor = (props: EditorProps): JSX.Element => {
function addContentChangeListener() {
const { model } = dataRef.current;
const monaco = monacoRef.current;
if (!model || !monaco) return;
if (!monaco) return;
model.onDidChangeContent(() => {
model?.onDidChangeContent(() => {
const redecorateEditableRegion = () => {
const coveringRange = getLinesCoveringEditableRegion();
if (coveringRange) {
dataRef.current.insideEditDecId = updateEditableRegion(
monaco.editor.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
model,
coveringRange,
[dataRef.current.insideEditDecId]
)[0];
updateEditableRegion(coveringRange, { model });
}
};
@ -861,40 +888,17 @@ const Editor = (props: EditorProps): JSX.Element => {
updateDescriptionZone();
updateOutputZone();
showEditableRegion(editor);
}
// resetting test output
// TODO: DRY this - createOutputNode doesn't also need to set this up.
const testButton = document.getElementById('test-button');
if (testButton) {
testButton.innerHTML = 'Check Your Code (Ctrl + Enter)';
testButton.onclick = () => {
props.executeChallenge();
};
}
const testStatus = document.getElementById('test-status');
if (testStatus) {
testStatus.innerHTML = '';
}
const testOutput = document.getElementById('test-output');
if (testOutput) {
testOutput.innerHTML = '';
}
// resetting margin decorations
// TODO: this should be done via the decorator api, not by manipulating
// the DOM
const editableRegionDecorators = document.getElementsByClassName(
'myEditableLineDecoration'
);
if (editableRegionDecorators.length > 0) {
for (const i of editableRegionDecorators) {
i.classList.remove('tests-passed');
}
// Since the outputNode is only reset when the step is restarted, users
// that want to try different solutions will need to do that.
resetOutputNode();
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.challengeFiles, props.isResetting]);
useEffect(() => {
const { output } = props;
const { model, insideEditDecId } = dataRef.current;
const editableRegion = getEditableRegionFromRedux();
if (editableRegion.length === 2) {
const testOutput = document.getElementById('test-output');
@ -910,16 +914,17 @@ const Editor = (props: EditorProps): JSX.Element => {
};
}
// TODO: this should be done via the decorator api, not by manipulating
// the DOM
const editableRegionDecorators = document.getElementsByClassName(
'myEditableLineDecoration'
);
if (editableRegionDecorators.length > 0) {
for (const i of editableRegionDecorators) {
i.classList.add('tests-passed');
}
const range = model?.getDecorationRange(insideEditDecId);
if (range) {
updateEditableRegion(
range,
{ model },
{
linesDecorationsClassName: 'myEditableLineDecoration tests-passed'
}
);
}
if (testOutput && testStatus) {
testOutput.innerHTML = '';
testStatus.innerHTML = '✅ Step completed.';