Skip to content

Commit b87d113

Browse files
committed
refactor: Finalize mount/unmount/update
1 parent 8ea7a4d commit b87d113

3 files changed

Lines changed: 54 additions & 31 deletions

File tree

src/components/code-editor/index.jsx

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { useState, useRef, useEffect } from 'preact/hooks';
1+
import { useRef, useEffect } from 'preact/hooks';
22
import { EditorView } from 'codemirror';
33
import { lineNumbers, keymap, highlightActiveLineGutter, highlightActiveLine } from '@codemirror/view';
4-
import { EditorState } from '@codemirror/state';
4+
import { EditorState, Transaction } from '@codemirror/state';
55
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
66
import { javascript } from '@codemirror/lang-javascript';
77
import { syntaxHighlighting, HighlightStyle, indentUnit, bracketMatching } from '@codemirror/language';
@@ -26,16 +26,34 @@ const highlightStyle = HighlightStyle.define([
2626
{ tag: tags.invalid, class: 'cm-invalid' }
2727
]);
2828

29+
/**
30+
* @param {object} props
31+
* @param {string} props.value
32+
* @param {(value: string) => void} props.onInput
33+
* @param {string} props.slug
34+
* @param {string} [props.class]
35+
*/
2936
export default function CodeEditor(props) {
3037
const editorParent = useRef(null);
38+
/** @type {{ current: EditorView | null }} */
3139
const editor = useRef(null);
32-
// eslint-disable-next-line no-unused-vars
33-
const [_, setEditor] = useState(null);
40+
41+
const routeHasChanged = useRef(false);
42+
43+
useEffect(() => {
44+
if (props.slug || !editor.current) routeHasChanged.current = true;
45+
}, [props.slug]);
3446

3547
useEffect(() => {
36-
console.log('editor code:\n', props.value);
37-
if (editor.current && !props.baseExampleSlug) return;
38-
if (editor.current) editor.current.destroy();
48+
if (routeHasChanged.current === false) return;
49+
routeHasChanged.current = false;
50+
51+
if (editor.current) {
52+
editor.current.dispatch({
53+
changes: { from: 0, to: editor.current.state.doc.length, insert: props.value }
54+
});
55+
return;
56+
}
3957

4058
const theme = EditorView.theme({}, { dark: true });
4159

@@ -54,8 +72,9 @@ export default function CodeEditor(props) {
5472
keymap.of([indentWithTab, ...defaultKeymap, ...historyKeymap]),
5573
[theme, syntaxHighlighting(highlightStyle, { fallback: true })],
5674
EditorView.updateListener.of(update => {
57-
if (update.docChanged) {
58-
if (props.onInput) props.onInput({ value: update.state.doc.toString() });
75+
// Ignores changes from swapping out the editor code programmatically
76+
if (isViewUpdateFromUserInput(update)) {
77+
props.onInput(update.state.doc.toString());
5978
}
6079
})
6180
]
@@ -65,16 +84,23 @@ export default function CodeEditor(props) {
6584
state,
6685
parent: editorParent.current
6786
});
68-
69-
setEditor(editor.current);
70-
}, [props.baseExampleSlug]);
87+
}, [props.value]);
7188

7289
useEffect(() => (
7390
() => {
74-
editor.current.destroy();
75-
setEditor(null);
91+
if (editor.current) editor.current.destroy();
7692
}
7793
), []);
7894

7995
return <div ref={editorParent} class={cx(style.codeEditor, props.class)} />;
8096
}
97+
98+
/** @param {import('@codemirror/view').ViewUpdate} viewUpdate */
99+
function isViewUpdateFromUserInput(viewUpdate) {
100+
if (viewUpdate.docChanged) {
101+
for (const transaction of viewUpdate.transactions) {
102+
if (transaction.annotation(Transaction.userEvent)) return true;
103+
}
104+
}
105+
return false;
106+
}

src/components/controllers/repl/index.jsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { useState, useEffect } from 'preact/hooks';
22
import { useLocation, useRoute } from 'preact-iso';
33
import { Splitter } from '../../splitter';
44
import { textToBase64 } from './query-encode.js';
5-
import { EXAMPLES, fetchExample } from './examples';
65
import { ErrorOverlay } from './error-overlay';
6+
import { EXAMPLES, fetchExample } from './examples';
77
import { useStoredValue } from '../../../lib/localstorage';
88
import { useResource } from '../../../lib/use-resource';
99
import { parseStackTrace } from './errors';
@@ -23,12 +23,11 @@ export function Repl({ code }) {
2323
const [error, setError] = useState(null);
2424
const [copied, setCopied] = useState(false);
2525

26-
// TODO: CodeMirror v5 cannot load in Node, and loading only the runner
27-
// causes some bad jumping/pop-in. For the moment, this is the best option
26+
// TODO: Needs some work for prerendering to not cause pop-in
2827
if (typeof window === 'undefined') return null;
2928

3029
/**
31-
* @type {{ Runner: import('../repl/runner').default, CodeEditor: import('../../code-editor').default }}
30+
* @type {{ Runner: import('./runner').default, CodeEditor: import('../../code-editor').default }}
3231
*/
3332
const { Runner, CodeEditor } = useResource(() => Promise.all([
3433
import('../../code-editor'),
@@ -143,8 +142,7 @@ export function Repl({ code }) {
143142
<CodeEditor
144143
class={style.code}
145144
value={editorCode}
146-
baseExampleSlug={exampleSlug}
147-
error={error}
145+
slug={query.example}
148146
onInput={onEditorInput}
149147
/>
150148
</Splitter>

src/components/controllers/tutorial/index.jsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ export function Tutorial({ html, meta }) {
5959
const hasCode = meta.code !== false;
6060
const showCode = showCodeOverride && hasCode;
6161

62-
// TODO: CodeMirror v5 cannot load in Node, and loading only the runner
63-
// causes some bad jumping/pop-in. For the moment, this is the best option
62+
// TODO: Needs some work for prerendering to not cause pop-in
6463
if (typeof window === 'undefined') return null;
6564

6665
/**
@@ -184,15 +183,15 @@ export function Tutorial({ html, meta }) {
184183
</>
185184
}
186185
>
187-
<div class={style.codeWindow}>
188-
<CodeEditor
189-
class={style.code}
190-
value={editorCode}
191-
error={error}
192-
onInput={setEditorCode}
193-
/>
194-
</div>
195-
</Splitter>
186+
<div class={style.codeWindow}>
187+
<CodeEditor
188+
class={style.code}
189+
value={editorCode}
190+
slug={path}
191+
onInput={setEditorCode}
192+
/>
193+
</div>
194+
</Splitter>
196195
}
197196
>
198197
<div class={style.tutorialWindow} ref={content}>

0 commit comments

Comments
 (0)