Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/odd-hounds-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@pulse-editor/shared-utils": patch
"@pulse-editor/react-api": patch
---

Fix snapshot restore triggers at every state changes
1 change: 1 addition & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"large-moose-tap",
"lazy-zebras-mate",
"mighty-ghosts-crash",
"odd-hounds-enjoy",
"petite-memes-fix",
"polite-lines-dance",
"polite-rules-switch",
Expand Down
8 changes: 8 additions & 0 deletions npm-packages/react-api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @pulse-editor/react-api

## 0.1.1-alpha.47

### Patch Changes

- Fix snapshot restore triggers at every state changes
- Updated dependencies
- @pulse-editor/shared-utils@0.1.1-alpha.47

## 0.1.1-alpha.46

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions npm-packages/react-api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pulse-editor/react-api",
"version": "0.1.1-alpha.46",
"version": "0.1.1-alpha.47",
"main": "dist/main.js",
"files": [
"dist"
Expand Down Expand Up @@ -38,7 +38,7 @@
"typescript-eslint": "^8.30.1"
},
"peerDependencies": {
"@pulse-editor/shared-utils": "0.1.1-alpha.46",
"@pulse-editor/shared-utils": "0.1.1-alpha.47",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
Expand Down
37 changes: 20 additions & 17 deletions npm-packages/react-api/src/hooks/editor/use-snapshot-state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react";
import { useContext, useEffect, useRef, useState } from "react";
import { SnapshotContext } from "../../providers/snapshot-provider";

export default function useSnapShotState<T>(
Expand All @@ -7,25 +7,23 @@ export default function useSnapShotState<T>(
onRestore?: (value: T) => void
) {
const snapshotContext = useContext(SnapshotContext);

if (!snapshotContext) {
throw new Error("useSnapShotState must be used within a SnapshotProvider");
}

const { states, setStates } = snapshotContext;

// Initialize state with the value from context or the initial value
const [state, setState] = useState<T>(
states[key] !== undefined ? states[key] : initialValue
);

// Update context whenever state changes
const isLocalUpdate = useRef(false);

const setSnapshotState: React.Dispatch<React.SetStateAction<T>> = (value) => {
setState((prev) => {
const newValue =
typeof value === "function" ? (value as (prev: T) => T)(prev) : value;

// Defer the setStates call to next microtask, outside render phase
isLocalUpdate.current = true; // mark as local
Promise.resolve().then(() => {
setStates((prevStates) => ({
...prevStates,
Expand All @@ -37,26 +35,31 @@ export default function useSnapShotState<T>(
});
};

// Set the initial value in context if not already set
// Initialize context with initial value
useEffect(() => {
// Only set if the key does not exist in the context
if (states[key] === undefined && initialValue !== undefined) {
setStates((prevStates) => ({
...prevStates,
setStates((prev) => ({
...prev,
[key]: initialValue,
}));
}
}, []);

// Restore state from context when key or states change
// Only restore when external changes occur
useEffect(() => {
console.log("Restoring state for key:", key, states[key]);
const contextValue = states[key];
if (contextValue === undefined) return;

if (isLocalUpdate.current) {
// skip this run because we caused it ourselves
isLocalUpdate.current = false;
return;
}

if (states[key] !== undefined && states[key] !== state) {
setState(states[key]);
if (onRestore) {
onRestore(states[key]);
}
if (contextValue !== state) {
console.log("Restoring state for key:", key, contextValue);
setState(contextValue);
onRestore?.(contextValue);
}
}, [states[key]]);

Expand Down
6 changes: 6 additions & 0 deletions npm-packages/shared-utils/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @pulse-editor/shared-utils

## 0.1.1-alpha.47

### Patch Changes

- Fix snapshot restore triggers at every state changes

## 0.1.1-alpha.46

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion npm-packages/shared-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pulse-editor/shared-utils",
"version": "0.1.1-alpha.46",
"version": "0.1.1-alpha.47",
"main": "dist/main.js",
"files": [
"dist"
Expand Down
2 changes: 2 additions & 0 deletions web/components/marketplace/app/app-gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ export default function AppGallery() {
<div className="flex flex-col gap-y-2">
<div className="flex flex-col items-center">
<Select
label="Filter apps"
size="sm"
className="w-fit min-w-48"
items={selectLabels}
startContent={
Expand Down
2 changes: 2 additions & 0 deletions web/components/marketplace/workflow/workflow-gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export default function WorkflowGallery() {
<div className="flex flex-col gap-y-2">
<div className="flex flex-col items-center">
<Select
label="Filter workflows"
size="sm"
className="w-fit min-w-48"
items={selectLabels}
startContent={
Expand Down
9 changes: 7 additions & 2 deletions web/lib/hooks/use-canvas-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export default function useCanvasWorkflow(
ReactFlowNode<AppNodeData> | undefined
>(initialWorkflowContent?.defaultEntryPoint);

const [isRestored, setIsRestored] = useState(false);

// Update entry points
useEffect(() => {
debouncedGetEntryPoint();
Expand All @@ -47,14 +49,17 @@ export default function useCanvasWorkflow(
useEffect(() => {
async function restore() {
if (!imcContext) return;
else if (isRestored) return;
else if (!initialWorkflowContent) return;
setIsRestored(true);

if (initialWorkflowContent?.snapshotStates) {
if (initialWorkflowContent.snapshotStates) {
await restoreAppsSnapshotStates(initialWorkflowContent);
}
}

restore();
}, [initialWorkflowContent, imcContext]);
}, [initialWorkflowContent, imcContext, isRestored]);

async function startWorkflow() {
// DAG traversal using Kahn's algorithm (topological sort)
Expand Down