Skip to content

Commit 8443a54

Browse files
authored
Merge pull request #80 from ClayPulse/add-app-state-snapshot
Add app state snapshot during import & export
2 parents 096f898 + 6f3130a commit 8443a54

File tree

24 files changed

+395
-96
lines changed

24 files changed

+395
-96
lines changed

.changeset/forty-cows-smell.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pulse-editor/shared-utils": patch
3+
"@pulse-editor/react-api": patch
4+
---
5+
6+
Add app state snapshot during import & export

.changeset/pre.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"early-pumas-listen",
3030
"few-wasps-beam",
3131
"fluffy-poems-cover",
32+
"forty-cows-smell",
3233
"free-ears-swim",
3334
"fruity-goats-look",
3435
"full-beans-stop",

npm-packages/react-api/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# @pulse-editor/react-api
22

3+
## 0.1.1-alpha.46
4+
5+
### Patch Changes
6+
7+
- Add app state snapshot during import & export
8+
- Updated dependencies
9+
- @pulse-editor/shared-utils@0.1.1-alpha.46
10+
311
## 0.1.1-alpha.45
412

513
### Patch Changes

npm-packages/react-api/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pulse-editor/react-api",
3-
"version": "0.1.1-alpha.45",
3+
"version": "0.1.1-alpha.46",
44
"main": "dist/main.js",
55
"files": [
66
"dist"
@@ -38,7 +38,7 @@
3838
"typescript-eslint": "^8.30.1"
3939
},
4040
"peerDependencies": {
41-
"@pulse-editor/shared-utils": "0.1.1-alpha.45",
41+
"@pulse-editor/shared-utils": "0.1.1-alpha.46",
4242
"react": "^19.0.0",
4343
"react-dom": "^19.0.0"
4444
}

npm-packages/react-api/src/hooks/editor/use-loading.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import { IMCMessage, IMCMessageTypeEnum } from "@pulse-editor/shared-utils";
12
import { useEffect, useState } from "react";
23
import useIMC from "../../lib/use-imc";
3-
import { IMCMessage, IMCMessageTypeEnum } from "@pulse-editor/shared-utils";
44

55
export default function useLoading() {
66
const receiverHandlerMap = new Map<
77
IMCMessageTypeEnum,
8-
(senderWindow: Window, message: IMCMessage) => Promise<void>
8+
(senderWindow: Window, message: IMCMessage) => Promise<any>
99
>();
1010

1111
const { imc, isReady } = useIMC(receiverHandlerMap);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useContext, useEffect, useState } from "react";
2+
import { SnapshotContext } from "../../providers/snapshot-provider";
3+
4+
export default function useSnapShotState<T>(
5+
key: string,
6+
initialValue?: T,
7+
onRestore?: (value: T) => void
8+
) {
9+
const snapshotContext = useContext(SnapshotContext);
10+
11+
if (!snapshotContext) {
12+
throw new Error("useSnapShotState must be used within a SnapshotProvider");
13+
}
14+
15+
const { states, setStates } = snapshotContext;
16+
17+
// Initialize state with the value from context or the initial value
18+
const [state, setState] = useState<T>(
19+
states[key] !== undefined ? states[key] : initialValue
20+
);
21+
22+
// Update context whenever state changes
23+
const setSnapshotState: React.Dispatch<React.SetStateAction<T>> = (value) => {
24+
setState((prev) => {
25+
const newValue =
26+
typeof value === "function" ? (value as (prev: T) => T)(prev) : value;
27+
28+
// Defer the setStates call to next microtask, outside render phase
29+
Promise.resolve().then(() => {
30+
setStates((prevStates) => ({
31+
...prevStates,
32+
[key]: newValue,
33+
}));
34+
});
35+
36+
return newValue;
37+
});
38+
};
39+
40+
// Set the initial value in context if not already set
41+
useEffect(() => {
42+
// Only set if the key does not exist in the context
43+
if (states[key] === undefined && initialValue !== undefined) {
44+
setStates((prevStates) => ({
45+
...prevStates,
46+
[key]: initialValue,
47+
}));
48+
}
49+
}, []);
50+
51+
// Restore state from context when key or states change
52+
useEffect(() => {
53+
console.log("Restoring state for key:", key, states[key]);
54+
55+
if (states[key] !== undefined && states[key] !== state) {
56+
setState(states[key]);
57+
if (onRestore) {
58+
onRestore(states[key]);
59+
}
60+
}
61+
}, [states[key]]);
62+
63+
return [state, setSnapshotState] as const;
64+
}

npm-packages/react-api/src/lib/use-imc.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { InterModuleCommunication } from "@pulse-editor/shared-utils";
21
import {
32
IMCMessageTypeEnum,
3+
InterModuleCommunication,
44
ReceiverHandlerMap,
55
} from "@pulse-editor/shared-utils";
66
import { useEffect, useState } from "react";
@@ -35,9 +35,8 @@ export default function useIMC(handlerMap: ReceiverHandlerMap) {
3535
await newImc.initOtherWindow(targetWindow);
3636
setImc(newImc);
3737

38-
newImc.sendMessage(IMCMessageTypeEnum.AppReady).then(() => {
39-
setIsReady(true);
40-
});
38+
await newImc.sendMessage(IMCMessageTypeEnum.AppReady);
39+
setIsReady(true);
4140
}
4241

4342
initIMC();

npm-packages/react-api/src/main.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import useAgentTools from "./hooks/agent/use-agent-tools";
22
import useAgents from "./hooks/agent/use-agents";
3-
import useRegisterAction from "./hooks/editor/use-register-action";
43
import useFileView from "./hooks/editor/use-file-view";
54
import useLoading from "./hooks/editor/use-loading";
65
import useNotification from "./hooks/editor/use-notification";
6+
import useRegisterAction from "./hooks/editor/use-register-action";
77
import useTheme from "./hooks/editor/use-theme";
88
import useToolbar from "./hooks/editor/use-toolbar";
99

@@ -14,20 +14,24 @@ import useSTT from "./hooks/ai-modality/use-stt";
1414
import useTTS from "./hooks/ai-modality/use-tts";
1515
import useVideoGen from "./hooks/ai-modality/use-video-gen";
1616
import usePulseEnv from "./hooks/editor/use-env";
17+
import useSnapshotState from "./hooks/editor/use-snapshot-state";
1718
import useTerminal from "./hooks/terminal/use-terminal";
19+
import SnapshotProvider from "./providers/snapshot-provider";
1820

1921
export {
22+
SnapshotProvider,
2023
useAgentTools,
2124
useAgents,
22-
useRegisterAction,
2325
useFileView,
2426
useImageGen,
2527
useLLM,
2628
useLoading,
2729
useNotification,
2830
useOCR,
2931
usePulseEnv,
32+
useRegisterAction,
3033
useSTT,
34+
useSnapshotState,
3135
useTTS,
3236
useTerminal,
3337
useTheme,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { IMCMessage, IMCMessageTypeEnum } from "@pulse-editor/shared-utils";
2+
import React, {
3+
createContext,
4+
Dispatch,
5+
ReactNode,
6+
SetStateAction,
7+
useEffect,
8+
useState,
9+
} from "react";
10+
import useIMC from "../lib/use-imc";
11+
12+
export const SnapshotContext = createContext<SnapshotContextType | undefined>(
13+
undefined
14+
);
15+
16+
export type SnapshotContextType = {
17+
states: { [key: string]: any };
18+
setStates: Dispatch<SetStateAction<{ [key: string]: any }>>;
19+
};
20+
21+
export default function SnapshotProvider({
22+
children,
23+
}: {
24+
children: ReactNode;
25+
}) {
26+
const receiverHandlerMap = new Map<
27+
IMCMessageTypeEnum,
28+
(senderWindow: Window, message: IMCMessage) => Promise<any>
29+
>([
30+
[
31+
IMCMessageTypeEnum.EditorAppStateSnapshotRestore,
32+
async (senderWindow: Window, message: IMCMessage) => {
33+
const { states } = message.payload;
34+
35+
// Update all states in the context
36+
setStates((prev) => ({ ...states }));
37+
},
38+
],
39+
[
40+
IMCMessageTypeEnum.EditorAppStateSnapshotSave,
41+
async (senderWindow: Window, message: IMCMessage) => {
42+
// Return current states in the context
43+
return { states };
44+
},
45+
],
46+
]);
47+
48+
const { imc, isReady } = useIMC(receiverHandlerMap);
49+
50+
const [states, setStates] = useState<{ [key: string]: any }>({});
51+
52+
useEffect(() => {
53+
if (isReady) {
54+
imc?.updateReceiverHandlerMap(receiverHandlerMap);
55+
}
56+
}, [isReady, states]);
57+
58+
return (
59+
<SnapshotContext.Provider value={{ states, setStates }}>
60+
{children}
61+
</SnapshotContext.Provider>
62+
);
63+
}

npm-packages/shared-utils/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @pulse-editor/shared-utils
22

3+
## 0.1.1-alpha.46
4+
5+
### Patch Changes
6+
7+
- Add app state snapshot during import & export
8+
39
## 0.1.1-alpha.45
410

511
### Patch Changes

0 commit comments

Comments
 (0)