Răsfoiți Sursa

enhance(undo): split undo-ops by ::boundary op

rcmerci 1 an în urmă
părinte
comite
ebcb6912ba
1 a modificat fișierele cu 55 adăugiri și 23 ștergeri
  1. 55 23
      src/main/frontend/worker/undo_redo.cljs

+ 55 - 23
src/main/frontend/worker/undo_redo.cljs

@@ -28,7 +28,9 @@ when undo this op, this original entity-map will be transacted back into db")
 (sr/defkeyword ::update-block
   "when a block is updated, generate a ::update-block undo-op.")
 
-(def undo-op-schema
+(def ^:private boundary [::boundary])
+
+(def ^:private undo-op-schema
   (mu/closed-schema
    [:multi {:dispatch first}
     [::boundary
@@ -65,7 +67,7 @@ when undo this op, this original entity-map will be transacted back into db")
        ;; TODO: add more attrs
        ]]]]))
 
-(def undo-ops-validator (m/validator [:sequential undo-op-schema]))
+(def ^:private undo-ops-validator (m/validator [:sequential undo-op-schema]))
 
 (def ^:private entity-map-pull-pattern
   [:block/uuid
@@ -85,7 +87,7 @@ when undo this op, this original entity-map will be transacted back into db")
       true                  (update :block/parent :block/uuid)
       (seq (:block/tags m)) (update :block/tags (partial mapv :block/uuid)))))
 
-(defn reverse-op
+(defn- reverse-op
   [db op]
   (let [block-uuid (:block-uuid (second op))]
     (case (first op)
@@ -120,26 +122,47 @@ when undo this op, this original entity-map will be transacted back into db")
   (assert (undo-ops-validator ops) ops)
   (swap! (:undo/repo->undo-stack @worker-state/*state) update repo apply-conj-vec ops))
 
-(defn- pop-undo-op
+(defn- pop-ops-helper
+  [stack]
+  (let [[ops i]
+        (loop [i (dec (count stack)) r []]
+          (let [peek-op (nth stack i nil)]
+            (cond
+              (neg? i)
+              [r 0]
+
+              (nil? peek-op)
+              [r i]
+
+              (= boundary peek-op)
+              [r i]
+
+              :else
+              (recur (dec i) (conj r peek-op)))))]
+    [ops (subvec stack 0 i)]))
+
+(defn- pop-undo-ops
   [repo]
-  (let [repo->undo-stack (:undo/repo->undo-stack @worker-state/*state)]
-    (when-let [peek-op (peek (@repo->undo-stack repo))]
-      (swap! repo->undo-stack update repo pop)
-      peek-op)))
+  (let [repo->undo-stack (:undo/repo->undo-stack @worker-state/*state)
+        undo-stack (@repo->undo-stack repo)
+        [ops undo-stack*] (pop-ops-helper undo-stack)]
+    (swap! repo->undo-stack assoc repo undo-stack*)
+    ops))
 
 (defn- push-redo-ops
   [repo ops]
   (assert (undo-ops-validator ops) ops)
   (swap! (:undo/repo->redo-stack @worker-state/*state) update repo apply-conj-vec ops))
 
-(defn- pop-redo-op
+(defn- pop-redo-ops
   [repo]
-  (let [repo->redo-stack (:undo/repo->redo-stack @worker-state/*state)]
-    (when-let [peek-op (peek (@repo->redo-stack repo))]
-      (swap! repo->redo-stack update repo pop)
-      peek-op)))
+  (let [repo->redo-stack (:undo/repo->redo-stack @worker-state/*state)
+        redo-stack (@repo->redo-stack repo)
+        [ops redo-stack*] (pop-ops-helper redo-stack)]
+    (swap! repo->redo-stack assoc repo redo-stack*)
+    ops))
 
-(defmulti reverse-apply-op (fn [op _conn _repo] (first op)))
+(defmulti ^:private reverse-apply-op (fn [op _conn _repo] (first op)))
 (defmethod reverse-apply-op ::remove-block
   [op conn repo]
   (let [[_ {:keys [block-uuid block-entity-map]}] op]
@@ -213,22 +236,31 @@ when undo this op, this original entity-map will be transacted back into db")
 
 (defn undo
   [repo]
-  (if-let [op (pop-undo-op repo)]
+  (if-let [ops (not-empty (pop-undo-ops repo))]
     (let [conn (worker-state/get-datascript-conn repo)
-          rev-op (reverse-op @conn op)]
-      (when (= :push-undo-redo (reverse-apply-op op conn repo))
-        (push-redo-ops repo [rev-op])))
+          redo-ops-to-push (transient [])]
+      (doseq [op ops]
+        (let [rev-op (reverse-op @conn op)]
+          (when (= :push-undo-redo (reverse-apply-op op conn repo))
+            (conj! redo-ops-to-push rev-op))))
+      (when-let [rev-ops (not-empty (persistent! redo-ops-to-push))]
+        (push-redo-ops repo (cons boundary rev-ops))))
     (prn "No further undo infomation")))
 
 (defn redo
   [repo]
-  (if-let [op (pop-redo-op repo)]
+  (if-let [ops (not-empty (pop-redo-ops repo))]
     (let [conn (worker-state/get-datascript-conn repo)
-          rev-op (reverse-op @conn op)]
-      (when (= :push-undo-redo (reverse-apply-op op conn repo))
-        (push-undo-ops repo [rev-op])))
+          undo-ops-to-push (transient [])]
+      (doseq [op ops]
+        (let [rev-op (reverse-op @conn op)]
+          (when (= :push-undo-redo (reverse-apply-op op conn repo))
+            (conj! undo-ops-to-push rev-op))))
+      (when-let [rev-ops (not-empty (persistent! undo-ops-to-push))]
+        (push-undo-ops repo (cons boundary rev-ops))))
     (prn "No further redo infomation")))
 
+
 ;;; listen db changes and push undo-ops
 
 (defn- normal-block?
@@ -279,7 +311,7 @@ when undo this op, this original entity-map will be transacted back into db")
   [repo db-before db-after same-entity-datoms-coll id->attr->datom]
   (let [ops (mapcat (partial entity-datoms=>ops db-before db-after id->attr->datom) same-entity-datoms-coll)]
     (when (seq ops)
-      (push-undo-ops repo ops))))
+      (push-undo-ops repo (cons boundary ops)))))
 
 (defmethod db-listener/listen-db-changes :gen-undo-ops
   [_ {:keys [_tx-data tx-meta db-before db-after