From fe8fa811df7e14e3d6311c2970425f7005360000 Mon Sep 17 00:00:00 2001
From: lawvs <18554747+lawvs@users.noreply.github.com>
Date: Fri, 12 Apr 2024 21:41:36 +0800
Subject: [PATCH] feat: can undo and redo changes
---
src/components/config-panel.tsx | 41 +++++++++++++++++-
src/state.ts | 74 ++++++++++++++++++++++++++++++++-
src/y-type.ts | 4 +-
3 files changed, 115 insertions(+), 4 deletions(-)
diff --git a/src/components/config-panel.tsx b/src/components/config-panel.tsx
index 0f9b5c9..6443fff 100644
--- a/src/components/config-panel.tsx
+++ b/src/components/config-panel.tsx
@@ -1,9 +1,11 @@
-import { useConfig, useYDoc } from "../state";
+import { Redo, Undo } from "lucide-react";
+import { useConfig, useUndoManager, useYDoc } from "../state";
import { fileToYDoc } from "../utils";
import { ConnectButton } from "./connect-button";
import { ExportButton } from "./export-button";
import { FullScreenDropZone } from "./full-screen-drop-zone";
import { LoadButton } from "./load-button";
+import { Button } from "./ui/button";
import { Label } from "./ui/label";
import { Switch } from "./ui/switch";
import { useToast } from "./ui/use-toast";
@@ -12,6 +14,8 @@ export function ConfigPanel() {
const [yDoc, setYDoc] = useYDoc();
const { toast } = useToast();
const [config, setConfig] = useConfig();
+ const { undoManager, canRedo, canUndo, undoStackSize, redoStackSize } =
+ useUndoManager();
return (
@@ -77,6 +81,41 @@ export function ConfigPanel() {
+ {config.editable && (
+
+
+
+
+ )}
+
{
+ // The UndoManager can only track shared types that are created
+ // See https://discuss.yjs.dev/t/global-document-undo-manager/2555
+ const keys = Array.from(doc.share.keys());
+ if (!keys.length) return;
+ const scope = keys.map((key) => doc.get(key));
+ undoManager.addToScope(scope);
+ // undoManager.addTrackedOrigin(origin);
+ });
+ doc.on("beforeTransaction", (transaction) => {
+ // Try to track all origins
+ // Workaround for https://github.com/yjs/yjs/issues/624
+ transaction.origin = TRACK_ALL_ORIGINS;
+ });
+ return undoManager;
+}
+
+const defaultYDoc = new Y.Doc();
+const defaultUndoManager = createUndoManager(defaultYDoc);
+
+const undoManagerAtom = atom(defaultUndoManager);
+
+const yDocAtom = atom(defaultYDoc, (get, set, newDoc: Y.Doc) => {
+ get(undoManagerAtom).destroy();
+ const undoManager = createUndoManager(newDoc);
+ set(undoManagerAtom, undoManager);
+ get(yDocAtom).destroy();
+ set(yDocAtom, newDoc);
+});
export const useYDoc = () => {
return useAtom(yDocAtom);
};
+export const useUndoManager = () => {
+ const undoManager = useAtomValue(undoManagerAtom);
+ const [state, setState] = useState({
+ canUndo: undoManager.canUndo(),
+ canRedo: undoManager.canRedo(),
+ undoStackSize: undoManager.undoStack.length,
+ redoStackSize: undoManager.redoStack.length,
+ });
+
+ useEffect(() => {
+ const callback = () => {
+ setState({
+ canUndo: undoManager.canUndo(),
+ canRedo: undoManager.canRedo(),
+ undoStackSize: undoManager.undoStack.length,
+ redoStackSize: undoManager.redoStack.length,
+ });
+ };
+ callback();
+
+ undoManager.on("stack-item-added", callback);
+ undoManager.on("stack-item-popped", callback);
+ return () => {
+ undoManager.off("stack-item-added", callback);
+ undoManager.off("stack-item-popped", callback);
+ };
+ }, [state, undoManager]);
+
+ return {
+ undoManager,
+ ...state,
+ };
+};
+
export type Config = {
parseYDoc: boolean;
showDelta: boolean;
diff --git a/src/y-type.ts b/src/y-type.ts
index 026217f..fcf1da8 100644
--- a/src/y-type.ts
+++ b/src/y-type.ts
@@ -19,7 +19,9 @@ export function guessType(abstractType: Y.AbstractType) {
return Y.Map;
}
if (abstractType._length > 0) {
- return Y.Array;
+ // TODO distinguish between Y.Text and Y.Array
+ return Y.Text;
+ // return Y.Array;
}
return Y.AbstractType;
}