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
This commit is contained in:
committed by
GitHub
parent
f163a77fe5
commit
c712f22667
@ -538,6 +538,31 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
return outputNode;
|
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() {
|
function focusOnHotkeys() {
|
||||||
const currContainerRef = props.containerRef.current;
|
const currContainerRef = props.containerRef.current;
|
||||||
if (currContainerRef) {
|
if (currContainerRef) {
|
||||||
@ -577,36 +602,44 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
|
|
||||||
// TODO: DRY this and the update function
|
// TODO: DRY this and the update function
|
||||||
function initializeEditableRegion(
|
function initializeEditableRegion(
|
||||||
stickiness: number,
|
range: IRange,
|
||||||
target: editor.ITextModel,
|
modelContext: {
|
||||||
range: IRange
|
monaco: typeof monacoEditor;
|
||||||
|
model: editor.ITextModel;
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
|
const { monaco, model } = modelContext;
|
||||||
const lineDecoration = {
|
const lineDecoration = {
|
||||||
range,
|
range,
|
||||||
options: {
|
options: {
|
||||||
isWholeLine: true,
|
isWholeLine: true,
|
||||||
linesDecorationsClassName: 'myEditableLineDecoration',
|
linesDecorationsClassName: 'myEditableLineDecoration',
|
||||||
stickiness
|
stickiness:
|
||||||
|
monaco.editor.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return target.deltaDecorations([], [lineDecoration]);
|
return model.deltaDecorations([], [lineDecoration]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateEditableRegion(
|
function updateEditableRegion(
|
||||||
stickiness: number,
|
|
||||||
target: editor.ITextModel,
|
|
||||||
range: IRange,
|
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 = {
|
const lineDecoration = {
|
||||||
range,
|
range,
|
||||||
options: {
|
options: {
|
||||||
isWholeLine: true,
|
...oldOptions,
|
||||||
linesDecorationsClassName: 'myEditableLineDecoration',
|
...options
|
||||||
stickiness
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return target.deltaDecorations(oldIds, [lineDecoration]);
|
model?.deltaDecorations([insideEditDecId], [lineDecoration]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDescriptionZoneTop() {
|
function getDescriptionZoneTop() {
|
||||||
@ -693,11 +726,10 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
editableRegion[1] - 1
|
editableRegion[1] - 1
|
||||||
]);
|
]);
|
||||||
|
|
||||||
dataRef.current.insideEditDecId = initializeEditableRegion(
|
dataRef.current.insideEditDecId = initializeEditableRegion(editableRange, {
|
||||||
monaco.editor.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
monaco,
|
||||||
model,
|
model
|
||||||
editableRange
|
})[0];
|
||||||
)[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWidgetsToRegions(editor: editor.IStandaloneCodeEditor) {
|
function addWidgetsToRegions(editor: editor.IStandaloneCodeEditor) {
|
||||||
@ -757,18 +789,13 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
function addContentChangeListener() {
|
function addContentChangeListener() {
|
||||||
const { model } = dataRef.current;
|
const { model } = dataRef.current;
|
||||||
const monaco = monacoRef.current;
|
const monaco = monacoRef.current;
|
||||||
if (!model || !monaco) return;
|
if (!monaco) return;
|
||||||
|
|
||||||
model.onDidChangeContent(() => {
|
model?.onDidChangeContent(() => {
|
||||||
const redecorateEditableRegion = () => {
|
const redecorateEditableRegion = () => {
|
||||||
const coveringRange = getLinesCoveringEditableRegion();
|
const coveringRange = getLinesCoveringEditableRegion();
|
||||||
if (coveringRange) {
|
if (coveringRange) {
|
||||||
dataRef.current.insideEditDecId = updateEditableRegion(
|
updateEditableRegion(coveringRange, { model });
|
||||||
monaco.editor.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
|
||||||
model,
|
|
||||||
coveringRange,
|
|
||||||
[dataRef.current.insideEditDecId]
|
|
||||||
)[0];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -861,40 +888,17 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
updateDescriptionZone();
|
updateDescriptionZone();
|
||||||
updateOutputZone();
|
updateOutputZone();
|
||||||
showEditableRegion(editor);
|
showEditableRegion(editor);
|
||||||
}
|
|
||||||
// resetting test output
|
// Since the outputNode is only reset when the step is restarted, users
|
||||||
// TODO: DRY this - createOutputNode doesn't also need to set this up.
|
// that want to try different solutions will need to do that.
|
||||||
const testButton = document.getElementById('test-button');
|
resetOutputNode();
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [props.challengeFiles, props.isResetting]);
|
}, [props.challengeFiles, props.isResetting]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { output } = props;
|
const { output } = props;
|
||||||
|
const { model, insideEditDecId } = dataRef.current;
|
||||||
const editableRegion = getEditableRegionFromRedux();
|
const editableRegion = getEditableRegionFromRedux();
|
||||||
if (editableRegion.length === 2) {
|
if (editableRegion.length === 2) {
|
||||||
const testOutput = document.getElementById('test-output');
|
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
|
const range = model?.getDecorationRange(insideEditDecId);
|
||||||
// the DOM
|
if (range) {
|
||||||
const editableRegionDecorators = document.getElementsByClassName(
|
updateEditableRegion(
|
||||||
'myEditableLineDecoration'
|
range,
|
||||||
);
|
{ model },
|
||||||
if (editableRegionDecorators.length > 0) {
|
{
|
||||||
for (const i of editableRegionDecorators) {
|
linesDecorationsClassName: 'myEditableLineDecoration tests-passed'
|
||||||
i.classList.add('tests-passed');
|
}
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (testOutput && testStatus) {
|
if (testOutput && testStatus) {
|
||||||
testOutput.innerHTML = '';
|
testOutput.innerHTML = '';
|
||||||
testStatus.innerHTML = '✅ Step completed.';
|
testStatus.innerHTML = '✅ Step completed.';
|
||||||
|
Reference in New Issue
Block a user