Skip to content

Commit

Permalink
fix: update Puck after clearing local changes and fix undo state (#18)
Browse files Browse the repository at this point in the history
[SUMO-6143](https://yexttest.atlassian.net/browse/SUMO-6143?atlOrigin=eyJpIjoiZGI5NTk1ZmUzOTE2NGZlNGEzYTdmN2UwZjc2N2EzYzEiLCJwIjoiaiJ9)

This fixes the Clear Local Changes button to:
1. Close the modal on confirmation
2. Reset the Puck data state without a page refresh

Additionally, this fixes the undo button to properly undo to the
original state before there were any changes instead of getting stuck at
the first change.

---------

Co-authored-by: Brian Baugher <123590980+brian-baugher@users.noreply.github.com>
Co-authored-by: Matt Kilpatrick <mkilpatrick@yext.com>
  • Loading branch information
3 people authored Aug 15, 2024
1 parent b2eaf28 commit 3b33631
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 43 deletions.
64 changes: 41 additions & 23 deletions src/components/Editor.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -41,7 +40,6 @@ export interface EditorProps {
}

export const Editor = ({ document, puckConfigs }: EditorProps) => {
const [puckData, setPuckData] = useState<Data>();
const [puckInitialHistory, setPuckInitialHistory] =
useState<PuckInitialHistory>({
histories: [],
Expand Down Expand Up @@ -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 ||
Expand Down Expand Up @@ -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;
}

Expand All @@ -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(
Expand All @@ -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;
}

Expand All @@ -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",
Expand Down Expand Up @@ -329,7 +349,6 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => {
);

const isLoading =
!puckData ||
!puckConfig ||
!templateMetadata ||
!document ||
Expand All @@ -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 +
Expand All @@ -352,7 +370,6 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => {
<DocumentProvider value={document}>
<InternalEditor
puckConfig={puckConfig}
puckData={puckData}
isLoading={isLoading}
puckInitialHistory={puckInitialHistory}
clearHistory={
Expand All @@ -363,6 +380,7 @@ export const Editor = ({ document, puckConfigs }: EditorProps) => {
saveSaveState={saveSaveState}
saveVisualConfigData={saveVisualConfigData}
sendDevSaveStateData={sendDevSaveStateData}
visualConfigurationData={visualConfigurationData}
/>
</DocumentProvider>
) : (
Expand Down
24 changes: 20 additions & 4 deletions src/internal/components/InternalEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { PuckInitialHistory } from "../../components/Editor.tsx";

interface InternalEditorProps {
puckConfig: Config;
puckData: any; // json object
puckInitialHistory: PuckInitialHistory;
isLoading: boolean;
clearHistory: (
Expand All @@ -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,
Expand All @@ -39,6 +38,7 @@ export const InternalEditor = ({
saveSaveState,
saveVisualConfigData,
sendDevSaveStateData,
visualConfigurationData,
}: InternalEditorProps) => {
const [canEdit, setCanEdit] = useState<boolean>(false);
const historyIndex = useRef<number>(-1);
Expand Down Expand Up @@ -85,7 +85,7 @@ export const InternalEditor = ({
}
}
},
[templateMetadata, getLocalStorageKey]
[templateMetadata, getLocalStorageKey, saveState]
);

const handleClearLocalChanges = () => {
Expand All @@ -96,6 +96,7 @@ export const InternalEditor = ({
templateMetadata.layoutId,
templateMetadata.entityId
);
historyIndex.current = -1;
};

const handleSave = async (data: Data) => {
Expand All @@ -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 (
<EntityFieldProvider>
<Puck
config={puckConfig}
data={
(puckData as Partial<Data>) ?? { root: {}, content: [], zones: {} }
(puckInitialHistory.histories[puckInitialHistory.index].data
.data as Partial<Data>) ?? {
root: {},
content: [],
zones: {},
}
}
initialHistory={puckInitialHistory}
onChange={change}
Expand All @@ -130,6 +145,7 @@ export const InternalEditor = ({
handleClearLocalChanges,
handleHistoryChange,
appState.data,
visualConfigurationData,
handleSave,
templateMetadata.isDevMode
);
Expand Down
40 changes: 24 additions & 16 deletions src/internal/puck/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>,
isDevMode: boolean
) => {
const entityDocument = useDocument<any>();
const {
history: {
back,
forward,
histories,
index,
hasFuture,
hasPast,
setHistories,
setHistoryIndex,
},
dispatch: puckDispatch,
history: { back, forward, histories, index, hasFuture, setHistories },
} = usePuck();

useEffect(() => {
Expand All @@ -63,7 +56,12 @@ export const customHeader = (
</div>
<div className="header-center"></div>
<div className="actions">
<Button variant="ghost" size="icon" disabled={!hasPast} onClick={back}>
<Button
variant="ghost"
size="icon"
disabled={index === 0} // prevent going to -1 because it would loop data back to the saveState
onClick={back}
>
<RotateCcw className="sm-icon" />
</Button>
<Button
Expand All @@ -79,7 +77,10 @@ export const customHeader = (
onClearLocalChanges={() => {
handleClearLocalChanges();
setHistories([]);
setHistoryIndex(-1);
puckDispatch({
type: "setData",
data: initialPuckData,
});
}}
/>
<Button
Expand All @@ -93,7 +94,7 @@ export const customHeader = (
variant="secondary"
disabled={histories.length === 0}
onClick={async () => {
await handleSaveData(data);
await handleSaveData(currentPuckData);
handleClearLocalChanges();
}}
>
Expand All @@ -114,8 +115,15 @@ const ClearLocalChangesButton = ({
disabled,
onClearLocalChanges,
}: ClearLocalChangesButtonProps) => {
const [dialogOpen, setDialogOpen] = React.useState<boolean>(false);

const handleClearLocalChanges = () => {
onClearLocalChanges();
setDialogOpen(false);
};

return (
<AlertDialog>
<AlertDialog open={dialogOpen} onOpenChange={setDialogOpen}>
<AlertDialogTrigger disabled={disabled} asChild>
<Button variant="outline">Clear Local Changes</Button>
</AlertDialogTrigger>
Expand All @@ -128,7 +136,7 @@ const ClearLocalChangesButton = ({
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button onClick={onClearLocalChanges}>Confirm</Button>
<Button onClick={handleClearLocalChanges}>Confirm</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Expand Down

0 comments on commit 3b33631

Please sign in to comment.