Skip to content

Commit

Permalink
refactor: Finalize mount/unmount/update
Browse files Browse the repository at this point in the history
  • Loading branch information
rschristian committed Jun 30, 2024
1 parent 8ea7a4d commit b87d113
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 31 deletions.
54 changes: 40 additions & 14 deletions src/components/code-editor/index.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useRef, useEffect } from 'preact/hooks';
import { useRef, useEffect } from 'preact/hooks';
import { EditorView } from 'codemirror';
import { lineNumbers, keymap, highlightActiveLineGutter, highlightActiveLine } from '@codemirror/view';
import { EditorState } from '@codemirror/state';
import { EditorState, Transaction } from '@codemirror/state';
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { syntaxHighlighting, HighlightStyle, indentUnit, bracketMatching } from '@codemirror/language';
Expand All @@ -26,16 +26,34 @@ const highlightStyle = HighlightStyle.define([
{ tag: tags.invalid, class: 'cm-invalid' }
]);

/**
* @param {object} props
* @param {string} props.value
* @param {(value: string) => void} props.onInput
* @param {string} props.slug
* @param {string} [props.class]
*/
export default function CodeEditor(props) {
const editorParent = useRef(null);
/** @type {{ current: EditorView | null }} */
const editor = useRef(null);
// eslint-disable-next-line no-unused-vars
const [_, setEditor] = useState(null);

const routeHasChanged = useRef(false);

useEffect(() => {
if (props.slug || !editor.current) routeHasChanged.current = true;
}, [props.slug]);

useEffect(() => {
console.log('editor code:\n', props.value);
if (editor.current && !props.baseExampleSlug) return;
if (editor.current) editor.current.destroy();
if (routeHasChanged.current === false) return;
routeHasChanged.current = false;

if (editor.current) {
editor.current.dispatch({
changes: { from: 0, to: editor.current.state.doc.length, insert: props.value }
});
return;
}

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

Expand All @@ -54,8 +72,9 @@ export default function CodeEditor(props) {
keymap.of([indentWithTab, ...defaultKeymap, ...historyKeymap]),
[theme, syntaxHighlighting(highlightStyle, { fallback: true })],
EditorView.updateListener.of(update => {
if (update.docChanged) {
if (props.onInput) props.onInput({ value: update.state.doc.toString() });
// Ignores changes from swapping out the editor code programmatically
if (isViewUpdateFromUserInput(update)) {
props.onInput(update.state.doc.toString());
}
})
]
Expand All @@ -65,16 +84,23 @@ export default function CodeEditor(props) {
state,
parent: editorParent.current
});

setEditor(editor.current);
}, [props.baseExampleSlug]);
}, [props.value]);

useEffect(() => (
() => {
editor.current.destroy();
setEditor(null);
if (editor.current) editor.current.destroy();
}
), []);

return <div ref={editorParent} class={cx(style.codeEditor, props.class)} />;
}

/** @param {import('@codemirror/view').ViewUpdate} viewUpdate */
function isViewUpdateFromUserInput(viewUpdate) {
if (viewUpdate.docChanged) {
for (const transaction of viewUpdate.transactions) {
if (transaction.annotation(Transaction.userEvent)) return true;
}
}
return false;
}
10 changes: 4 additions & 6 deletions src/components/controllers/repl/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useState, useEffect } from 'preact/hooks';
import { useLocation, useRoute } from 'preact-iso';
import { Splitter } from '../../splitter';
import { textToBase64 } from './query-encode.js';
import { EXAMPLES, fetchExample } from './examples';
import { ErrorOverlay } from './error-overlay';
import { EXAMPLES, fetchExample } from './examples';
import { useStoredValue } from '../../../lib/localstorage';
import { useResource } from '../../../lib/use-resource';
import { parseStackTrace } from './errors';
Expand All @@ -23,12 +23,11 @@ export function Repl({ code }) {
const [error, setError] = useState(null);
const [copied, setCopied] = useState(false);

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

/**
* @type {{ Runner: import('../repl/runner').default, CodeEditor: import('../../code-editor').default }}
* @type {{ Runner: import('./runner').default, CodeEditor: import('../../code-editor').default }}
*/
const { Runner, CodeEditor } = useResource(() => Promise.all([
import('../../code-editor'),
Expand Down Expand Up @@ -143,8 +142,7 @@ export function Repl({ code }) {
<CodeEditor
class={style.code}
value={editorCode}
baseExampleSlug={exampleSlug}
error={error}
slug={query.example}
onInput={onEditorInput}
/>
</Splitter>
Expand Down
21 changes: 10 additions & 11 deletions src/components/controllers/tutorial/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ export function Tutorial({ html, meta }) {
const hasCode = meta.code !== false;
const showCode = showCodeOverride && hasCode;

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

/**
Expand Down Expand Up @@ -184,15 +183,15 @@ export function Tutorial({ html, meta }) {
</>
}
>
<div class={style.codeWindow}>
<CodeEditor
class={style.code}
value={editorCode}
error={error}
onInput={setEditorCode}
/>
</div>
</Splitter>
<div class={style.codeWindow}>
<CodeEditor
class={style.code}
value={editorCode}
slug={path}
onInput={setEditorCode}
/>
</div>
</Splitter>
}
>
<div class={style.tutorialWindow} ref={content}>
Expand Down

0 comments on commit b87d113

Please sign in to comment.