From 4df2ea8060a2d0f8194ca5e4c5c6874139321c0f Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Sat, 29 Jun 2024 22:35:33 -0500 Subject: [PATCH] refactor: Finalize mount/unmount/update --- src/components/code-editor/index.jsx | 56 ++++++++++++++----- src/components/controllers/repl/index.jsx | 18 ++---- src/components/controllers/tutorial/index.jsx | 21 ++++--- 3 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/components/code-editor/index.jsx b/src/components/code-editor/index.jsx index 8a051def2..68403c0ba 100644 --- a/src/components/code-editor/index.jsx +++ b/src/components/code-editor/index.jsx @@ -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'; @@ -26,21 +26,39 @@ const highlightStyle = HighlightStyle.define([ { tag: tags.invalid, class: 'cm-invalid' } ]); +/** + * @param {object} props + * @param {string} props.editorCode + * @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.editorCode } + }); + return; + } const theme = EditorView.theme({}, { dark: true }); const state = EditorState.create({ - doc: props.value, + doc: props.editorCode, extensions: [ lineNumbers(), highlightActiveLine(), @@ -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()); } }) ] @@ -65,16 +84,23 @@ export default function CodeEditor(props) { state, parent: editorParent.current }); - - setEditor(editor.current); - }, [props.baseExampleSlug]); + }, [props.editorCode]); useEffect(() => ( () => { - editor.current.destroy(); - setEditor(null); + if (editor.current) editor.current.destroy(); } ), []); return
; } + +/** @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; +} diff --git a/src/components/controllers/repl/index.jsx b/src/components/controllers/repl/index.jsx index 34bde5f80..4c7a7743f 100644 --- a/src/components/controllers/repl/index.jsx +++ b/src/components/controllers/repl/index.jsx @@ -1,12 +1,8 @@ -<<<<<<< HEAD -import { useState, useEffect } from 'preact/hooks'; -import { useLocation, useRoute } from 'preact-iso'; -======= import { useState } from 'preact/hooks'; ->>>>>>> 85f6ec02 (refactor: Swap effect for input handler) +import { useLocation, useRoute } from 'preact-iso'; import { Splitter } from '../../splitter'; -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'; @@ -25,12 +21,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'), @@ -136,9 +131,8 @@ export function Repl({ code }) { > diff --git a/src/components/controllers/tutorial/index.jsx b/src/components/controllers/tutorial/index.jsx index b06579d90..26d416a04 100644 --- a/src/components/controllers/tutorial/index.jsx +++ b/src/components/controllers/tutorial/index.jsx @@ -58,8 +58,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; /** @@ -175,15 +174,15 @@ export function Tutorial({ html, meta }) { } > -
- -
- +
+ +
+ } >