From 859c3125946db8dd6bb1ac63f47d1606b86fb35b Mon Sep 17 00:00:00 2001 From: Felix Habib Date: Tue, 8 Oct 2024 15:22:39 +1100 Subject: [PATCH] Save editor hidden status to playroom url --- cypress/e2e/urlHandling.cy.ts | 30 ++++++++++++++++++++++++++ src/Playroom/CodeEditor/CodeEditor.tsx | 25 ++++++++++++++++----- src/Playroom/Playroom.tsx | 1 + src/StoreContext/StoreContext.tsx | 9 ++++++++ src/index.d.ts | 1 + src/utils/usePreviewUrl.ts | 3 ++- utils/index.d.ts | 3 +++ utils/index.js | 26 +++++++++++++++++----- 8 files changed, 87 insertions(+), 11 deletions(-) diff --git a/cypress/e2e/urlHandling.cy.ts b/cypress/e2e/urlHandling.cy.ts index 59feb967..a1986313 100644 --- a/cypress/e2e/urlHandling.cy.ts +++ b/cypress/e2e/urlHandling.cy.ts @@ -30,6 +30,20 @@ describe('URL handling', () => { cy.title().should('eq', 'Test | Playroom'); }); + + it('editor hidden', () => { + cy.visit('http://localhost:9000/'); + + cy.get('textarea').should('not.be.focused'); + + // Todo - write a test that checks the CodeMirror element is not visible + /* + The CodeMirror element is not visible, but it is in the DOM + This test fails because the element doesn't meet Cypress's requirements for being hidden + https://docs.cypress.io/guides/core-concepts/interacting-with-elements#An-element-is-considered-hidden-if + */ + // cy.get('.CodeMirror-code').should('be.hidden'); + }); }); describe('where paramType is search', () => { @@ -62,5 +76,21 @@ describe('URL handling', () => { cy.title().should('eq', 'Test | Playroom'); }); + + it('editor hidden', () => { + cy.visit( + 'http://localhost:9001/?code=N4IgpgJglgLg9gJwBJQhMA7EAuGCCuYAvkA' + ); + + cy.get('textarea').should('not.be.focused'); + + // Todo - write a test that checks the CodeMirror element is not visible + /* + The CodeMirror element is not visible, but it is in the DOM + This test fails because the element doesn't meet Cypress's requirements for being hidden + https://docs.cypress.io/guides/core-concepts/interacting-with-elements#An-element-is-considered-hidden-if + */ + // cy.get('.CodeMirror-code').should('be.hidden'); + }); }); }); diff --git a/src/Playroom/CodeEditor/CodeEditor.tsx b/src/Playroom/CodeEditor/CodeEditor.tsx index 3c869741..07eefd1b 100644 --- a/src/Playroom/CodeEditor/CodeEditor.tsx +++ b/src/Playroom/CodeEditor/CodeEditor.tsx @@ -6,8 +6,8 @@ import 'codemirror/addon/dialog/dialog.css'; import 'codemirror/theme/neo.css'; import { - StoreContext, type CursorPosition, + StoreContext, } from '../../StoreContext/StoreContext'; import { formatCode as format, isMac } from '../../utils/formatting'; import { validateCode } from '../../utils/compileJsx'; @@ -69,11 +69,18 @@ interface Hint { interface Props { code: string; onChange: (code: string) => void; + editorHidden: boolean; previewCode?: string; hints?: Record; } -export const CodeEditor = ({ code, onChange, previewCode, hints }: Props) => { +export const CodeEditor = ({ + code, + editorHidden, + onChange, + previewCode, + hints, +}: Props) => { const editorInstanceRef = useRef(null); const insertionPointRef = useRef | null>( null @@ -168,17 +175,23 @@ export const CodeEditor = ({ code, onChange, previewCode, hints }: Props) => { ) { return; } - editorInstanceRef.current.setValue(code); validateCodeInEditor(editorInstanceRef.current, code); } }, [code, previewCode]); + const mounted = useRef(false); + useEffect(() => { + if (!mounted.current) { + mounted.current = true; + return; + } + if (editorInstanceRef.current && !editorInstanceRef.current.hasFocus()) { setCursorPosition(cursorPosition); } - }, [cursorPosition, setCursorPosition]); + }, [cursorPosition, previewCode, setCursorPosition]); useEffect(() => { if (editorInstanceRef.current) { @@ -212,7 +225,9 @@ export const CodeEditor = ({ code, onChange, previewCode, hints }: Props) => { editorDidMount={(editorInstance) => { editorInstanceRef.current = editorInstance; validateCodeInEditor(editorInstance, code); - setCursorPosition(cursorPosition); + if (!editorHidden) { + setCursorPosition(cursorPosition); + } }} onChange={(editorInstance, data, newCode) => { if (editorInstance.hasFocus() && !previewCode) { diff --git a/src/Playroom/Playroom.tsx b/src/Playroom/Playroom.tsx index 00bfa858..0357fa7d 100644 --- a/src/Playroom/Playroom.tsx +++ b/src/Playroom/Playroom.tsx @@ -117,6 +117,7 @@ export default ({ components, themes, widths, snippets }: PlayroomProps) => {
dispatch({ type: 'updateCode', payload: { code: newCode } }) } diff --git a/src/StoreContext/StoreContext.tsx b/src/StoreContext/StoreContext.tsx index d9d345d8..d40aa405 100644 --- a/src/StoreContext/StoreContext.tsx +++ b/src/StoreContext/StoreContext.tsx @@ -60,6 +60,7 @@ interface DebounceUpdateUrl { themes?: string[]; widths?: number[]; title?: string; + editorHidden?: boolean; } export interface CursorPosition { @@ -478,6 +479,7 @@ export const StoreProvider = ({ let themesFromQuery: State['visibleThemes']; let widthsFromQuery: State['visibleWidths']; let titleFromQuery: State['title']; + let editorHiddenFromQuery: State['editorHidden']; const paramsCode = params.get('code'); if (paramsCode) { @@ -486,11 +488,13 @@ export const StoreProvider = ({ themes: parsedThemes, widths: parsedWidths, title: parsedTitle, + editorHidden: parsedEditorHidden, } = JSON.parse( lzString.decompressFromEncodedURIComponent(String(paramsCode)) ?? '' ); codeFromQuery = parsedCode; + editorHiddenFromQuery = parsedEditorHidden; themesFromQuery = parsedThemes; widthsFromQuery = parsedWidths; titleFromQuery = parsedTitle; @@ -527,6 +531,8 @@ export const StoreProvider = ({ ? convertAndStoreSizeAsPercentage('width', storedWidth) : storedWidth) || defaultEditorSize; + const editorHidden = editorHiddenFromQuery === true; + const visibleWidths = widthsFromQuery || storedVisibleWidths || @@ -547,6 +553,7 @@ export const StoreProvider = ({ ...(editorPosition ? { editorPosition } : {}), ...(editorHeight ? { editorHeight } : {}), ...(editorWidth ? { editorWidth } : {}), + ...(editorHidden ? { editorHidden } : {}), ...(visibleThemes ? { visibleThemes } : {}), ...(visibleWidths ? { visibleWidths } : {}), ...(colorScheme ? { colorScheme } : {}), @@ -582,12 +589,14 @@ export const StoreProvider = ({ themes: state.visibleThemes, widths: state.visibleWidths, title: state.title, + editorHidden: state.editorHidden, }); }, [ state.code, state.visibleThemes, state.visibleWidths, state.title, + state.editorHidden, debouncedCodeUpdate, ]); diff --git a/src/index.d.ts b/src/index.d.ts index 3c32b53d..a81bbdc6 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -2,6 +2,7 @@ interface PlayroomConfig { components: string; outputPath: string; title?: string; + editorHidden?: boolean; themes?: string; widths?: number[]; snippets?: Snippet[]; diff --git a/src/utils/usePreviewUrl.ts b/src/utils/usePreviewUrl.ts index c06f1b88..44c0c8cc 100644 --- a/src/utils/usePreviewUrl.ts +++ b/src/utils/usePreviewUrl.ts @@ -9,7 +9,7 @@ const baseUrl = window.location.href .split('index.html')[0]; export default (theme: string) => { - const [{ code, title }] = useContext(StoreContext); + const [{ code, title, editorHidden }] = useContext(StoreContext); const isThemed = theme !== '__PLAYROOM__NO_THEME__'; @@ -19,5 +19,6 @@ export default (theme: string) => { theme: isThemed ? theme : undefined, paramType: playroomConfig.paramType, title, + editorHidden, }); }; diff --git a/utils/index.d.ts b/utils/index.d.ts index 3be841be..f28b8fef 100644 --- a/utils/index.d.ts +++ b/utils/index.d.ts @@ -14,6 +14,7 @@ interface CompressParamsOptions { widths?: number[]; theme?: string; title?: string; + editorHidden?: boolean; } export const compressParams: (options: CompressParamsOptions) => string; @@ -24,6 +25,7 @@ interface CreateUrlOptions { widths?: number[]; paramType?: ParamType; title?: string; + editorHidden?: boolean; } export const createUrl: (options: CreateUrlOptions) => string; @@ -34,6 +36,7 @@ interface CreatePreviewUrlOptions { theme?: string; paramType?: ParamType; title?: string; + editorHidden?: boolean; } export const createPreviewUrl: (options: CreatePreviewUrlOptions) => string; diff --git a/utils/index.js b/utils/index.js index 01f57ba7..e14f4b33 100644 --- a/utils/index.js +++ b/utils/index.js @@ -1,12 +1,20 @@ const lzString = require('lz-string'); -const compressParams = ({ code, themes, widths, theme, title }) => { +const compressParams = ({ + code, + themes, + widths, + theme, + title, + editorHidden, +}) => { const data = JSON.stringify({ ...(code ? { code } : {}), ...(themes ? { themes } : {}), ...(widths ? { widths } : {}), ...(theme ? { theme } : {}), ...(title ? { title } : {}), + ...(editorHidden ? { editorHidden } : {}), }); return lzString.compressToEncodedURIComponent(data); @@ -18,12 +26,19 @@ const createUrl = ({ themes, widths, title, + editorHidden, paramType = 'hash', }) => { let path = ''; - if (code || themes || widths || title) { - const compressedData = compressParams({ code, themes, widths, title }); + if (code || themes || widths || title || editorHidden) { + const compressedData = compressParams({ + code, + themes, + widths, + title, + editorHidden, + }); path = `${paramType === 'hash' ? '#' : ''}?code=${compressedData}`; } @@ -42,12 +57,13 @@ const createPreviewUrl = ({ code, theme, title, + editorHidden, paramType = 'hash', }) => { let path = ''; - if (code || theme || title) { - const compressedData = compressParams({ code, theme, title }); + if (code || theme || title || editorHidden) { + const compressedData = compressParams({ code, theme, title, editorHidden }); path = `/preview/${paramType === 'hash' ? '#' : ''}?code=${compressedData}`; }