Skip to content

Commit 1de6aaf

Browse files
sync reset
1 parent 75fbd53 commit 1de6aaf

File tree

3 files changed

+95
-9
lines changed

3 files changed

+95
-9
lines changed

editor/grida-canvas/action.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,29 @@ import type { GoogleWebFontList } from "@grida/fonts/google";
88

99
export type Action =
1010
| InternalAction
11+
| DocumentResetAction
1112
| EditorCameraAction
1213
| EditorAction
1314
| EditorClipAction;
1415

1516
export type InternalAction = __InternalWebfontListLoadAction;
1617

18+
/**
19+
* Document Reset Action
20+
*
21+
* Special marker action emitted when the entire document state is replaced via `reset()`.
22+
* This action is NOT handled by the global actions reducer - it only marks that a full
23+
* state replacement occurred. Subscribers can check for this action to distinguish
24+
* between full resets and incremental changes.
25+
*/
26+
export interface DocumentResetAction {
27+
type: "document/reset";
28+
/**
29+
* Unique identifier for this reset operation (auto-generated timestamp if not provided)
30+
*/
31+
document_key: string;
32+
}
33+
1734
export type EditorAction =
1835
| EditorConfigAction
1936
| EditorNudgeGestureStateAction

editor/grida-canvas/editor.i.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,6 +2508,19 @@ export namespace editor.api {
25082508
export interface IDocumentStoreActions {
25092509
undo(): void;
25102510
redo(): void;
2511+
/**
2512+
* Reset the entire document state
2513+
*
2514+
* Completely replaces the editor state, bypassing the reducer.
2515+
* - Preserves ONLY the camera transform (everything else is replaced)
2516+
* - Clears undo/redo history
2517+
* - Emits a "document/reset" action for subscribers
2518+
* - Auto-generates a timestamp key if not provided
2519+
*
2520+
* @param state - The new complete editor state
2521+
* @param key - Optional unique identifier (auto-generated if omitted)
2522+
* @param force - If true, bypass locked check
2523+
*/
25112524
reset(
25122525
state: editor.state.IEditorState,
25132526
key?: string,

editor/grida-canvas/editor.ts

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -545,23 +545,65 @@ class EditorDocumentStore
545545
}
546546

547547
// #region IDocumentEditorActions implementation
548+
/**
549+
* Reset the entire document state
550+
*
551+
* This is a special operation that bypasses the reducer and directly replaces
552+
* the entire state. Unlike `dispatch()`, it does not generate patches.
553+
*
554+
* **Characteristics:**
555+
* - Completely replaces the state (no Immer produce)
556+
* - Preserves ONLY the current camera transform (everything else is replaced)
557+
* - Clears undo/redo history
558+
* - Resets transaction ID to 0
559+
* - Emits a "document/reset" action so subscribers can detect the reset
560+
*
561+
* **Use cases:**
562+
* - Loading a document from a file
563+
* - Importing content from external sources
564+
* - Resetting to a completely new state
565+
*
566+
* @param state - The new complete editor state to set
567+
* @param key - Optional unique identifier for this reset operation.
568+
* If not provided, a timestamp is auto-generated.
569+
* @param force - If true, bypass the locked check. Use with caution.
570+
*
571+
* @returns The new transaction ID (always 0 after reset)
572+
*
573+
* @example
574+
* ```ts
575+
* // Load a document from file
576+
* const fileData = await fetch('/example.grida').then(r => r.json());
577+
* editor.commands.reset(
578+
* editor.state.init({
579+
* editable: true,
580+
* document: fileData.document
581+
* }),
582+
* '/example.grida'
583+
* );
584+
* ```
585+
*/
548586
public reset(
549587
state: editor.state.IEditorState,
550588
key: string | undefined = undefined,
551589
force: boolean = false
552590
): number {
553591
if (this._locked && !force) return this._tid;
554592

555-
const __prev_state = this.mstate;
556-
const __prev_transform = __prev_state.transform;
557-
this.mstate = produce(state, (draft) => {
558-
if (key) draft.document_key = key;
559-
// preserve the transform state
560-
draft.transform = __prev_transform;
561-
});
593+
const document_key = key ?? Date.now().toString();
594+
const prev_transform = this.mstate.transform;
595+
596+
// Explicit full reset: Use the provided state (typically from editor.state.init())
597+
// and only preserve the current camera transform
598+
this.mstate = {
599+
...state,
600+
document_key, // Set reset identifier
601+
transform: prev_transform, // Preserve camera transform
602+
};
603+
562604
this.historyManager.clear();
563605
this._tid = 0;
564-
this.emit(undefined, []);
606+
this.emit({ type: "document/reset", document_key }, []);
565607
return this._tid;
566608
}
567609

@@ -2383,12 +2425,26 @@ export class Editor
23832425
// subscribe
23842426
this.doc.subscribeWithSelector(
23852427
(state) => state.document,
2386-
(_, document, _prev, _action, patches) => {
2428+
(_, document, _prev, action, patches) => {
23872429
// FIXME: Unstable
23882430
// the current patch based sync is not stable, it WILL fail to direct sync when deleting a node, etc.
23892431
// this is not fully tested, and the direct sync fallback should kept as-is until we fully investicate this.
23902432

23912433
if (!this._m_wasm_canvas_scene) return;
2434+
2435+
// Full sync on document reset
2436+
if (action?.type === "document/reset") {
2437+
syncDocument(
2438+
this._m_wasm_canvas_scene,
2439+
document,
2440+
this.doc.state.scene_id
2441+
);
2442+
// Perform initial actions after reset
2443+
this.camera.fit("*");
2444+
return;
2445+
}
2446+
2447+
// Patch-based sync for normal changes
23922448
if (!patches || patches.length === 0) return;
23932449

23942450
const documentPatches = patches.filter(

0 commit comments

Comments
 (0)