diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index a0325d45..da72db33 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,13 +1,12 @@ import { InternalEditor } from "../internal/components/InternalEditor.js"; -import React from "react"; import "./index.css"; import { DocumentProvider } from "@yext/pages/util"; -import { useEffect, useState, useCallback, useRef } from "react"; +import React, { useEffect, useState, useCallback, useRef } from "react"; import { LoadingScreen } from "../internal/puck/components/LoadingScreen.tsx"; import { Toaster } from "../internal/puck/ui/Toaster.tsx"; import { getLocalStorageKey } from "../internal/utils/localStorageHelper.ts"; import { TemplateMetadata } from "../internal/types/templateMetadata.ts"; -import { type History, type Data, type Config } from "@measured/puck"; +import { type History, type Config } from "@measured/puck"; import { useReceiveMessage, useSendMessageToParent, @@ -41,7 +40,6 @@ export interface EditorProps { } export const Editor = ({ document, puckConfigs }: EditorProps) => { - const [puckData, setPuckData] = useState(); const [puckInitialHistory, setPuckInitialHistory] = useState({ histories: [], @@ -137,10 +135,21 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => { isFirstRender.current = false; // toggle flag after first render/mounting return; } - loadPuckDataUsingHistory(); // do something after state has updated + loadPuckInitialHistory(); // do something after state has updated }, [templateMetadata, saveState, visualConfigurationData]); - const loadPuckDataUsingHistory = useCallback(() => { + /** + * Determines the initialHistory to send to Puck. It is based on a combination + * of localStorage and saveState (from the DB). + * + * When in dev mode, only use localStorage if it exists. + * When NOT dev mode: + * 1. if no saveState, clear localStorage and use nothing + * 2. if saveState, find matching hash in localStorage: + * - if match, use localStorage with index set to saveState hash + * - if no match, use saveState directly and clear localStorage + */ + const loadPuckInitialHistory = useCallback(() => { if ( !visualConfigurationDataFetched || !saveStateFetched || @@ -169,12 +178,17 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => { histories: localHistories, index: localHistoryIndex, }); - setPuckData(localHistories[localHistoryIndex].data.data); return; } - // Otherwise start from the data saved to Content - setPuckData(visualConfigurationData); + // Otherwise start fresh from Content + setPuckInitialHistory({ + histories: visualConfigurationData + ? [{ id: "root", data: { data: visualConfigurationData } }] + : [], + index: visualConfigurationData ? 0 : -1, + }); + return; } @@ -188,14 +202,16 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => { templateMetadata.entityId ); - setPuckData(visualConfigurationData); + setPuckInitialHistory({ + histories: visualConfigurationData + ? [{ id: "root", data: { data: visualConfigurationData } }] + : [], + index: visualConfigurationData ? 0 : -1, + }); + return; } - // The history stored has both "ui" and "data" keys, but PuckData - // is only concerned with the "data" portion. - setPuckData(jsonFromEscapedJsonString(saveState.history).data); - // Check localStorage for existing Puck history const localHistoryArray = window.localStorage.getItem( getLocalStorageKey( @@ -209,6 +225,15 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => { // No localStorage, start from saveState if (!localHistoryArray) { + setPuckInitialHistory({ + histories: visualConfigurationData + ? [ + { id: "root", data: { data: visualConfigurationData } }, + jsonFromEscapedJsonString(saveState.history).data, + ] + : [jsonFromEscapedJsonString(saveState.history).data], + index: visualConfigurationData ? 1 : 0, + }); return; } @@ -233,12 +258,7 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => { templateMetadata.layoutId, templateMetadata.entityId ); - }, [ - setPuckInitialHistory, - setPuckData, - clearLocalStorage, - getLocalStorageKey, - ]); + }, [setPuckInitialHistory, clearLocalStorage, getLocalStorageKey]); const { sendToParent: iFrameLoaded } = useSendMessageToParent( "iFrameLoaded", @@ -329,7 +349,6 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => { ); const isLoading = - !puckData || !puckConfig || !templateMetadata || !document || @@ -339,7 +358,6 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => { const progress: number = (100 * // @ts-expect-error adding bools is fine (!!puckConfig + - !!puckData + !!templateMetadata + !!document + saveStateFetched + @@ -352,7 +370,6 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => { { saveSaveState={saveSaveState} saveVisualConfigData={saveVisualConfigData} sendDevSaveStateData={sendDevSaveStateData} + visualConfigurationData={visualConfigurationData} /> ) : ( diff --git a/src/internal/components/InternalEditor.tsx b/src/internal/components/InternalEditor.tsx index 6b1301e3..e20b9831 100644 --- a/src/internal/components/InternalEditor.tsx +++ b/src/internal/components/InternalEditor.tsx @@ -10,7 +10,6 @@ import { PuckInitialHistory } from "../../components/Editor.tsx"; interface InternalEditorProps { puckConfig: Config; - puckData: any; // json object puckInitialHistory: PuckInitialHistory; isLoading: boolean; clearHistory: ( @@ -25,12 +24,12 @@ interface InternalEditorProps { saveSaveState: (data: any) => void; saveVisualConfigData: (data: any) => void; sendDevSaveStateData: (data: any) => void; + visualConfigurationData: any; } // Render Puck editor export const InternalEditor = ({ puckConfig, - puckData, puckInitialHistory, isLoading, clearHistory, @@ -39,6 +38,7 @@ export const InternalEditor = ({ saveSaveState, saveVisualConfigData, sendDevSaveStateData, + visualConfigurationData, }: InternalEditorProps) => { const [canEdit, setCanEdit] = useState(false); const historyIndex = useRef(-1); @@ -85,7 +85,7 @@ export const InternalEditor = ({ } } }, - [templateMetadata, getLocalStorageKey] + [templateMetadata, getLocalStorageKey, saveState] ); const handleClearLocalChanges = () => { @@ -96,6 +96,7 @@ export const InternalEditor = ({ templateMetadata.layoutId, templateMetadata.entityId ); + historyIndex.current = -1; }; const handleSave = async (data: Data) => { @@ -114,12 +115,26 @@ export const InternalEditor = ({ } }; + /** + * Explanation for Puck `data` and `initialHistory`: + * Let's say there are two changes, one is "committed" to Content called C, and one is the saveState WIP called W. + * Puck data = [W] + * initialHistories = [C, W] index at W + * Ideally we can undo such that C is what shows at index -1, but because data starts at W we end up with W -> C -> W. + * If we start data at C, then initial render shows C, but we want to render W. + * To overcome this, we limit the undo history to index 0 in Header.tsx. + */ return ( ) ?? { root: {}, content: [], zones: {} } + (puckInitialHistory.histories[puckInitialHistory.index].data + .data as Partial) ?? { + root: {}, + content: [], + zones: {}, + } } initialHistory={puckInitialHistory} onChange={change} @@ -130,6 +145,7 @@ export const InternalEditor = ({ handleClearLocalChanges, handleHistoryChange, appState.data, + visualConfigurationData, handleSave, templateMetadata.isDevMode ); diff --git a/src/internal/puck/components/Header.tsx b/src/internal/puck/components/Header.tsx index 1a47995a..ec070dc9 100644 --- a/src/internal/puck/components/Header.tsx +++ b/src/internal/puck/components/Header.tsx @@ -33,22 +33,15 @@ const handleClick = (slug: string) => { export const customHeader = ( handleClearLocalChanges: () => void, handleHistoryChange: (histories: History[], index: number) => void, - data: Data, + currentPuckData: Data, // the current state of Puck data + initialPuckData: Data, // the initial state of Puck data before any local changes handleSaveData: (data: Data) => Promise, isDevMode: boolean ) => { const entityDocument = useDocument(); const { - history: { - back, - forward, - histories, - index, - hasFuture, - hasPast, - setHistories, - setHistoryIndex, - }, + dispatch: puckDispatch, + history: { back, forward, histories, index, hasFuture, setHistories }, } = usePuck(); useEffect(() => { @@ -63,7 +56,12 @@ export const customHeader = (
- @@ -128,7 +136,7 @@ const ClearLocalChangesButton = ({ Cancel - +