From b080d40e064560174a5caffb2d1a457b44344c8d Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 22 Feb 2023 03:32:30 +0100 Subject: [PATCH 1/3] fix(website): prioritize changes done in editor over hash change --- .../src/components/editor/LoadedEditor.tsx | 60 +++++++++++-------- .../components/editor/useSandboxServices.ts | 7 ++- .../src/components/hooks/useHashState.ts | 4 +- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index 9bfda6a1c339..35da6d22ac29 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -49,6 +49,9 @@ export const LoadedEditor: React.FC = ({ }) => { const { colorMode } = useColorMode(); const [_, setDecorations] = useState([]); + const [lastCode, setLastCode] = useState(() => code); + const [lastTSConfig, setLastTSConfig] = useState(() => tsconfig); + const [lastEslintrc, setLastEslintrc] = useState(() => eslintrc); const codeActions = useRef(new Map()).current; const [tabs] = useState>(() => { @@ -228,16 +231,19 @@ export const LoadedEditor: React.FC = ({ ), tabs.eslintrc.onDidChangeContent( debounce(() => { + setLastEslintrc(tabs.eslintrc.getValue()); onChange({ eslintrc: tabs.eslintrc.getValue() }); }, 500), ), tabs.tsconfig.onDidChangeContent( debounce(() => { + setLastTSConfig(tabs.tsconfig.getValue()); onChange({ tsconfig: tabs.tsconfig.getValue() }); }, 500), ), tabs.code.onDidChangeContent( debounce(() => { + setLastCode(tabs.code.getValue()); onChange({ code: tabs.code.getValue() }); }, 500), ), @@ -301,7 +307,7 @@ export const LoadedEditor: React.FC = ({ }); useEffect(() => { - if (code !== tabs.code.getValue()) { + if (code !== lastCode && code !== tabs.code.getValue()) { tabs.code.applyEdits([ { range: tabs.code.getFullModelRange(), @@ -309,10 +315,12 @@ export const LoadedEditor: React.FC = ({ }, ]); } + // We do not want to update the code when lastCode changes #6336 + // eslint-disable-next-line react-hooks/exhaustive-deps }, [code, tabs.code]); useEffect(() => { - if (tsconfig !== tabs.tsconfig.getValue()) { + if (tsconfig !== lastTSConfig && tsconfig !== tabs.tsconfig.getValue()) { tabs.tsconfig.applyEdits([ { range: tabs.tsconfig.getFullModelRange(), @@ -320,10 +328,12 @@ export const LoadedEditor: React.FC = ({ }, ]); } + // We do not want to update the code when lastTSConfig changes #6336 + // eslint-disable-next-line react-hooks/exhaustive-deps }, [tabs.tsconfig, tsconfig]); useEffect(() => { - if (eslintrc !== tabs.eslintrc.getValue()) { + if (eslintrc !== lastEslintrc && eslintrc !== tabs.eslintrc.getValue()) { tabs.eslintrc.applyEdits([ { range: tabs.eslintrc.getFullModelRange(), @@ -331,6 +341,8 @@ export const LoadedEditor: React.FC = ({ }, ]); } + // We do not want to update the code when lastEslintrc changes #6336 + // eslint-disable-next-line react-hooks/exhaustive-deps }, [eslintrc, tabs.eslintrc]); useEffect(() => { @@ -340,29 +352,27 @@ export const LoadedEditor: React.FC = ({ }, [colorMode, sandboxInstance]); useEffect(() => { - if (sandboxInstance.editor.getModel() === tabs.code) { - setDecorations(prevDecorations => - sandboxInstance.editor.deltaDecorations( - prevDecorations, - decoration && showAST - ? [ - { - range: new sandboxInstance.monaco.Range( - decoration.start.line, - decoration.start.column + 1, - decoration.end.line, - decoration.end.column + 1, - ), - options: { - inlineClassName: 'myLineDecoration', - stickiness: 1, - }, + setDecorations(prevDecorations => + tabs.code.deltaDecorations( + prevDecorations, + decoration && showAST + ? [ + { + range: new sandboxInstance.monaco.Range( + decoration.start.line, + decoration.start.column + 1, + decoration.end.line, + decoration.end.column + 1, + ), + options: { + inlineClassName: 'myLineDecoration', + stickiness: 1, }, - ] - : [], - ), - ); - } + }, + ] + : [], + ), + ); }, [decoration, sandboxInstance, showAST, tabs.code]); return null; diff --git a/packages/website/src/components/editor/useSandboxServices.ts b/packages/website/src/components/editor/useSandboxServices.ts index 20378ca79ffe..a6f4cbb7dfff 100644 --- a/packages/website/src/components/editor/useSandboxServices.ts +++ b/packages/website/src/components/editor/useSandboxServices.ts @@ -1,5 +1,4 @@ import { useColorMode } from '@docusaurus/theme-common'; -import { createCompilerOptions } from '@site/src/components/editor/config'; import type Monaco from 'monaco-editor'; import { useEffect, useState } from 'react'; @@ -9,8 +8,10 @@ import type { } from '../../vendor/sandbox'; import { WebLinter } from '../linter/WebLinter'; import type { RuleDetails } from '../types'; +import { createCompilerOptions } from './config'; import { editorEmbedId } from './EditorEmbed'; import { sandboxSingleton } from './loadSandbox'; +import type { CommonEditorProps } from './types'; export interface SandboxServicesProps { readonly jsx?: boolean; @@ -30,7 +31,7 @@ export interface SandboxServices { } export const useSandboxServices = ( - props: SandboxServicesProps, + props: CommonEditorProps & SandboxServicesProps, ): Error | SandboxServices | undefined => { const { onLoaded } = props; const [services, setServices] = useState(); @@ -52,7 +53,7 @@ export const useSandboxServices = ( const compilerOptions = createCompilerOptions(props.jsx); const sandboxConfig: Partial = { - text: '', + text: props.code, monacoSettings: { minimap: { enabled: false }, fontSize: 13, diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index 215386428915..c7cc5fb074d1 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -218,9 +218,9 @@ function useHashState( }; useEffect(() => { - window.addEventListener('hashchange', onHashChange); + window.addEventListener('popstate', onHashChange); return (): void => { - window.removeEventListener('hashchange', onHashChange); + window.removeEventListener('popstate', onHashChange); }; }, []); From 3bd4243c50cf664c98d8ca70c753d939c7ad6aa5 Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 23 Feb 2023 00:52:25 +0100 Subject: [PATCH 2/3] fix(website): do not applyEdits when editor is focused --- .../src/components/editor/LoadedEditor.tsx | 63 +++++++------------ .../src/components/hooks/useResizeObserver.ts | 25 ++++++++ 2 files changed, 46 insertions(+), 42 deletions(-) create mode 100644 packages/website/src/components/hooks/useResizeObserver.ts diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index 35da6d22ac29..b3066871c81b 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -8,6 +8,7 @@ import { parseTSConfig, tryParseEslintModule, } from '../config/utils'; +import { useResizeObserver } from '../hooks/useResizeObserver'; import { debounce } from '../lib/debounce'; import type { LintCodeAction } from '../linter/utils'; import { parseLintResults, parseMarkers } from '../linter/utils'; @@ -49,9 +50,10 @@ export const LoadedEditor: React.FC = ({ }) => { const { colorMode } = useColorMode(); const [_, setDecorations] = useState([]); - const [lastCode, setLastCode] = useState(() => code); - const [lastTSConfig, setLastTSConfig] = useState(() => tsconfig); - const [lastEslintrc, setLastEslintrc] = useState(() => eslintrc); + const container = useRef( + sandboxInstance.editor.getContainerDomNode?.() || + sandboxInstance.editor.getDomNode(), + ); const codeActions = useRef(new Map()).current; const [tabs] = useState>(() => { @@ -231,19 +233,16 @@ export const LoadedEditor: React.FC = ({ ), tabs.eslintrc.onDidChangeContent( debounce(() => { - setLastEslintrc(tabs.eslintrc.getValue()); onChange({ eslintrc: tabs.eslintrc.getValue() }); }, 500), ), tabs.tsconfig.onDidChangeContent( debounce(() => { - setLastTSConfig(tabs.tsconfig.getValue()); onChange({ tsconfig: tabs.tsconfig.getValue() }); }, 500), ), tabs.code.onDidChangeContent( debounce(() => { - setLastCode(tabs.code.getValue()); onChange({ code: tabs.code.getValue() }); }, 500), ), @@ -279,35 +278,15 @@ export const LoadedEditor: React.FC = ({ return debounce(() => sandboxInstance.editor.layout(), 1); }, [sandboxInstance]); - useEffect(() => { + useResizeObserver(container, () => { resize(); - }, [resize, showAST]); - - const domNode = sandboxInstance.editor.getContainerDomNode(); - const resizeObserver = useMemo(() => { - return new ResizeObserver(() => { - resize(); - }); - }, [resize]); - - useEffect(() => { - if (domNode) { - resizeObserver.observe(domNode); - - return (): void => resizeObserver.unobserve(domNode); - } - return (): void => {}; - }, [domNode, resizeObserver]); - - useEffect(() => { - window.addEventListener('resize', resize); - return (): void => { - window.removeEventListener('resize', resize); - }; }); useEffect(() => { - if (code !== lastCode && code !== tabs.code.getValue()) { + if ( + !sandboxInstance.editor.hasTextFocus() && + code !== tabs.code.getValue() + ) { tabs.code.applyEdits([ { range: tabs.code.getFullModelRange(), @@ -315,12 +294,13 @@ export const LoadedEditor: React.FC = ({ }, ]); } - // We do not want to update the code when lastCode changes #6336 - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [code, tabs.code]); + }, [sandboxInstance, code, tabs.code]); useEffect(() => { - if (tsconfig !== lastTSConfig && tsconfig !== tabs.tsconfig.getValue()) { + if ( + !sandboxInstance.editor.hasTextFocus() && + tsconfig !== tabs.tsconfig.getValue() + ) { tabs.tsconfig.applyEdits([ { range: tabs.tsconfig.getFullModelRange(), @@ -328,12 +308,13 @@ export const LoadedEditor: React.FC = ({ }, ]); } - // We do not want to update the code when lastTSConfig changes #6336 - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tabs.tsconfig, tsconfig]); + }, [sandboxInstance, tabs.tsconfig, tsconfig]); useEffect(() => { - if (eslintrc !== lastEslintrc && eslintrc !== tabs.eslintrc.getValue()) { + if ( + !sandboxInstance.editor.hasTextFocus() && + eslintrc !== tabs.eslintrc.getValue() + ) { tabs.eslintrc.applyEdits([ { range: tabs.eslintrc.getFullModelRange(), @@ -341,9 +322,7 @@ export const LoadedEditor: React.FC = ({ }, ]); } - // We do not want to update the code when lastEslintrc changes #6336 - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [eslintrc, tabs.eslintrc]); + }, [sandboxInstance, eslintrc, tabs.eslintrc]); useEffect(() => { sandboxInstance.monaco.editor.setTheme( diff --git a/packages/website/src/components/hooks/useResizeObserver.ts b/packages/website/src/components/hooks/useResizeObserver.ts new file mode 100644 index 000000000000..1fb1cffcb563 --- /dev/null +++ b/packages/website/src/components/hooks/useResizeObserver.ts @@ -0,0 +1,25 @@ +import { type RefObject, useEffect, useRef } from 'react'; + +const useResizeObserver = ( + element: RefObject, + callback: () => void, +): void => { + const observer = useRef(); + + useEffect(() => { + const current = element.current; + observer.current = new ResizeObserver(callback); + + if (current) { + observer.current.observe(current); + } + + return () => { + if (observer.current && current) { + observer.current.unobserve(current); + } + }; + }, [callback, element]); +}; + +export { useResizeObserver }; From 79b0fa0420f2b204c249010db3cc011bee0938bf Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 23 Feb 2023 00:58:43 +0100 Subject: [PATCH 3/3] fix(website): simplify useResizeObserver --- .../src/components/editor/LoadedEditor.tsx | 8 +++--- .../src/components/hooks/useResizeObserver.ts | 26 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index b3066871c81b..07255bfe6b9a 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -50,10 +50,6 @@ export const LoadedEditor: React.FC = ({ }) => { const { colorMode } = useColorMode(); const [_, setDecorations] = useState([]); - const container = useRef( - sandboxInstance.editor.getContainerDomNode?.() || - sandboxInstance.editor.getDomNode(), - ); const codeActions = useRef(new Map()).current; const [tabs] = useState>(() => { @@ -278,6 +274,10 @@ export const LoadedEditor: React.FC = ({ return debounce(() => sandboxInstance.editor.layout(), 1); }, [sandboxInstance]); + const container = + sandboxInstance.editor.getContainerDomNode?.() ?? + sandboxInstance.editor.getDomNode(); + useResizeObserver(container, () => { resize(); }); diff --git a/packages/website/src/components/hooks/useResizeObserver.ts b/packages/website/src/components/hooks/useResizeObserver.ts index 1fb1cffcb563..2760776298e9 100644 --- a/packages/website/src/components/hooks/useResizeObserver.ts +++ b/packages/website/src/components/hooks/useResizeObserver.ts @@ -1,25 +1,25 @@ -import { type RefObject, useEffect, useRef } from 'react'; +import { useEffect, useMemo } from 'react'; const useResizeObserver = ( - element: RefObject, + element: HTMLElement | null, callback: () => void, ): void => { - const observer = useRef(); + const resizeObserver = useMemo(() => { + return new ResizeObserver(() => { + callback(); + }); + }, [callback]); useEffect(() => { - const current = element.current; - observer.current = new ResizeObserver(callback); - - if (current) { - observer.current.observe(current); + if (element) { + resizeObserver.observe(element); } - - return () => { - if (observer.current && current) { - observer.current.unobserve(current); + return (): void => { + if (element) { + resizeObserver.unobserve(element); } }; - }, [callback, element]); + }, [element, resizeObserver]); }; export { useResizeObserver };