enhance(undo): undo/redo is page-scoped now

pull/11293/head
rcmerci 2024-04-11 14:24:28 +08:00
parent 37d6196000
commit 53970880d3
7 changed files with 145 additions and 89 deletions

View File

@ -675,15 +675,15 @@
(transit/write transit-w (rtc-core/get-block-update-log (uuid block-uuid))))
(undo
[_this repo]
[_this repo page-block-uuid-str]
(when-let [conn (worker-state/get-datascript-conn repo)]
(undo-redo/undo repo conn))
(undo-redo/undo repo (uuid page-block-uuid-str) conn))
nil)
(redo
[_this repo]
[_this repo page-block-uuid-str]
(when-let [conn (worker-state/get-datascript-conn repo)]
(undo-redo/redo repo conn)))
(undo-redo/redo repo (uuid page-block-uuid-str) conn)))
(keep-alive
[_this]

View File

@ -40,21 +40,34 @@
(defn undo!
[e]
(when-let [repo (state/get-current-repo)]
(when (db-transact/request-finished?)
(util/stop e)
(p/do!
(state/set-state! [:editor/last-replace-ref-content-tx repo] nil)
(editor/save-current-block!)
(state/clear-editor-action!)
(state/set-block-op-type! nil)
(let [^js worker @state/*db-worker]
(.undo worker repo))))))
;; TODO:
;; 1. :block/name will be non-unique, switch to other way to get current-page-uuid
;; 2. (state/get-current-page) return wrong result when editing in right-sidebar
(when-let [current-page-uuid-str (some->> (state/get-current-page)
(vector :block/name)
db/entity
:block/uuid
str)]
(when (db-transact/request-finished?)
(util/stop e)
(p/do!
(state/set-state! [:editor/last-replace-ref-content-tx repo] nil)
(editor/save-current-block!)
(state/clear-editor-action!)
(state/set-block-op-type! nil)
(let [^js worker @state/*db-worker]
(.undo worker repo current-page-uuid-str)))))))
(defn redo!
[e]
(when-let [repo (state/get-current-repo)]
(when (db-transact/request-finished?)
(util/stop e)
(state/clear-editor-action!)
(let [^js worker @state/*db-worker]
(.redo worker repo)))))
(when-let [current-page-uuid-str (some->> (state/get-current-page)
(vector :block/name)
db/entity
:block/uuid
str)]
(when (db-transact/request-finished?)
(util/stop e)
(state/clear-editor-action!)
(let [^js worker @state/*db-worker]
(.redo worker repo current-page-uuid-str))))))

View File

@ -4,11 +4,11 @@
[logseq.common.config :as common-config]
[frontend.schema-register :include-macros true :as sr]))
(sr/defkeyword :undo/repo->undo-stack
"{repo [first-op, second-op, ...]}")
(sr/defkeyword :undo/repo->pege-block-uuid->undo-ops
"{repo {<page-block-uuid> [op1 op2 ...]}}")
(sr/defkeyword :undo/repo->undo-stack
"{repo [first-op, second-op, ...]}")
(sr/defkeyword :undo/repo->pege-block-uuid->redo-ops
"{repo {<page-block-uuid> [op1 op2 ...]}}")
(defonce *state (atom {:worker/object nil
@ -24,8 +24,8 @@
:rtc/downloading-graph? false
:undo/repo->undo-stack (atom {})
:undo/repo->redo-stack (atom {})}))
:undo/repo->pege-block-uuid->undo-ops (atom {})
:undo/repo->pege-block-uuid->redo-ops (atom {})}))
(defonce *rtc-ws-url (atom nil))

View File

@ -139,10 +139,18 @@ when undo this op, this original entity-map will be transacted back into db")
(def ^:private apply-conj-vec (partial apply (fnil conj [])))
(comment
(def ^:private op-count-hard-limit 3000)
(def ^:private op-count-limit 2000))
(defn- push-undo-ops
[repo ops]
(assert (undo-ops-validator ops) ops)
(swap! (:undo/repo->undo-stack @worker-state/*state) update repo apply-conj-vec ops))
[repo page-block-uuid ops]
(assert (and (undo-ops-validator ops)
(uuid? page-block-uuid))
{:ops ops :page-block-uuid page-block-uuid})
(swap! (:undo/repo->pege-block-uuid->undo-ops @worker-state/*state)
update-in [repo page-block-uuid]
apply-conj-vec ops))
(defn- pop-ops-helper
[stack]
@ -164,32 +172,38 @@ when undo this op, this original entity-map will be transacted back into db")
[ops (subvec (vec stack) 0 i)]))
(defn- pop-undo-ops
[repo]
(let [repo->undo-stack (:undo/repo->undo-stack @worker-state/*state)
undo-stack (@repo->undo-stack repo)
[repo page-block-uuid]
(assert (uuid? page-block-uuid) page-block-uuid)
(let [repo->pege-block-uuid->undo-ops (:undo/repo->pege-block-uuid->undo-ops @worker-state/*state)
undo-stack (get-in @repo->pege-block-uuid->undo-ops [repo page-block-uuid])
[ops undo-stack*] (pop-ops-helper undo-stack)]
(swap! repo->undo-stack assoc repo undo-stack*)
(swap! repo->pege-block-uuid->undo-ops assoc-in [repo page-block-uuid] undo-stack*)
ops))
(defn- empty-undo-stack?
[repo]
(empty? (@(:undo/repo->undo-stack @worker-state/*state) repo)))
[repo page-block-uuid]
(empty? (get-in @(:undo/repo->pege-block-uuid->undo-ops @worker-state/*state) [repo page-block-uuid])))
(defn- empty-redo-stack?
[repo]
(empty? (@(:undo/repo->redo-stack @worker-state/*state) repo)))
[repo page-block-uuid]
(empty? (get-in @(:undo/repo->pege-block-uuid->redo-ops @worker-state/*state) [repo page-block-uuid])))
(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))
[repo page-block-uuid ops]
(assert (and (undo-ops-validator ops)
(uuid? page-block-uuid))
{:ops ops :page-block-uuid page-block-uuid})
(swap! (:undo/repo->pege-block-uuid->redo-ops @worker-state/*state)
update-in [repo page-block-uuid]
apply-conj-vec ops))
(defn- pop-redo-ops
[repo]
(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*)
[repo page-block-uuid]
(assert (uuid? page-block-uuid) page-block-uuid)
(let [repo->pege-block-uuid->redo-ops (:undo/repo->pege-block-uuid->redo-ops @worker-state/*state)
undo-stack (get-in @repo->pege-block-uuid->redo-ops [repo page-block-uuid])
[ops undo-stack*] (pop-ops-helper undo-stack)]
(swap! repo->pege-block-uuid->redo-ops assoc-in [repo page-block-uuid] undo-stack*)
ops))
(defn- normal-block?
@ -277,8 +291,8 @@ when undo this op, this original entity-map will be transacted back into db")
(conj [:push-undo-redo])))))))
(defn undo
[repo conn]
(if-let [ops (not-empty (pop-undo-ops repo))]
[repo page-block-uuid conn]
(if-let [ops (not-empty (pop-undo-ops repo page-block-uuid))]
(let [redo-ops-to-push (transient [])]
(batch-tx/with-batch-tx-mode conn
(doseq [op ops]
@ -288,16 +302,16 @@ when undo this op, this original entity-map will be transacted back into db")
(some-> *undo-redo-info-for-test* (reset! {:op op :tx (second r)}))
(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)))
(push-redo-ops repo page-block-uuid (cons boundary rev-ops)))
nil)
(when (empty-undo-stack? repo)
(when (empty-undo-stack? repo page-block-uuid)
(prn "No further undo information")
::empty-undo-stack)))
(defn redo
[repo conn]
(if-let [ops (not-empty (pop-redo-ops repo))]
[repo page-block-uuid conn]
(if-let [ops (not-empty (pop-redo-ops repo page-block-uuid))]
(let [undo-ops-to-push (transient [])]
(batch-tx/with-batch-tx-mode conn
(doseq [op ops]
@ -307,10 +321,10 @@ when undo this op, this original entity-map will be transacted back into db")
(some-> *undo-redo-info-for-test* (reset! {:op op :tx (second r)}))
(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)))
(push-undo-ops repo page-block-uuid (cons boundary rev-ops)))
nil)
(when (empty-redo-stack? repo)
(when (empty-redo-stack? repo page-block-uuid)
(prn "No further redo information")
::empty-redo-stack)))
@ -368,11 +382,21 @@ when undo this op, this original entity-map will be transacted back into db")
{:block-uuid (:block/uuid entity-after)
:block-origin-content (:block/content entity-before)}]]))))))
(defn- find-page-block-uuid
[db-before db-after same-entity-datoms-coll]
(some
(fn [entity-datoms]
(when-let [e (ffirst entity-datoms)]
(or (some-> (d/entity db-before e) :block/page :block/uuid)
(some-> (d/entity db-after e) :block/page :block/uuid))))
same-entity-datoms-coll))
(defn- generate-undo-ops
[repo db-before db-after same-entity-datoms-coll id->attr->datom gen-boundary-op?]
(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 (if gen-boundary-op? (cons boundary ops) ops)))))
(when-let [page-block-uuid (find-page-block-uuid db-before db-after same-entity-datoms-coll)]
(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 page-block-uuid (if gen-boundary-op? (cons boundary ops) ops))))))
(defmethod db-listener/listen-db-changes :gen-undo-ops
[_ {:keys [_tx-data tx-meta db-before db-after
@ -385,8 +409,8 @@ when undo this op, this original entity-map will be transacted back into db")
(defn clear-undo-redo-stack
[]
(reset! (:undo/repo->undo-stack @worker-state/*state) {})
(reset! (:undo/repo->redo-stack @worker-state/*state) {}))
(reset! (:undo/repo->pege-block-uuid->redo-ops @worker-state/*state) {})
(reset! (:undo/repo->pege-block-uuid->undo-ops @worker-state/*state) {}))
(comment

View File

@ -5,26 +5,41 @@
(defn gen-available-block-uuid
[db]
(gen/elements
(->> (d/q '[:find ?block-uuid
:where
[?block :block/parent]
[?block :block/left]
[?block :block/uuid ?block-uuid]]
db)
(apply concat))))
[db & {:keys [page-uuid]}]
(let [query (cond-> '[:find ?block-uuid]
page-uuid
(concat '[:in $ ?page-uuid])
true
(concat '[:where
[?block :block/parent]
[?block :block/left]
[?block :block/uuid ?block-uuid]])
page-uuid
(concat '[[?block :block/page ?page]
[?page :block/uuid ?page-uuid]]))]
(gen/elements
(->> (if page-uuid
(d/q query db page-uuid)
(d/q query db))
(apply concat)))))
(defn gen-available-parent-left-pair
"generate [<parent-uuid> <left-uuid>]"
[db]
(gen/elements
(d/q '[:find ?parent-uuid ?left-uuid
:where
[?b :block/uuid]
[?b :block/parent ?parent]
[?b :block/left ?left]
[?parent :block/uuid ?parent-uuid]
[?left :block/uuid ?left-uuid]]
db)))
[db & {:keys [page-uuid]}]
(let [query (cond-> '[:find ?parent-uuid ?left-uuid]
page-uuid
(concat '[:in $ ?page-uuid])
true
(concat '[:where
[?b :block/uuid]
[?b :block/parent ?parent]
[?b :block/left ?left]
[?parent :block/uuid ?parent-uuid]
[?left :block/uuid ?left-uuid]])
page-uuid
(concat '[[?b :block/page ?page]
[?page :block/uuid ?page-uuid]]))]
(gen/elements
(if page-uuid
(d/q query db page-uuid)
(d/q query db)))))

View File

@ -205,8 +205,8 @@ This can be called in synchronous contexts as no async fns should be invoked"
{:re-render? false :verbose false :refresh? true})))
(defn initial-test-page-and-blocks
[]
(let [page-uuid (random-uuid)
[& {:keys [page-uuid]}]
(let [page-uuid (or page-uuid (random-uuid))
first-block-uuid (random-uuid)
second-block-uuid (random-uuid)
page-id [:block/uuid page-uuid]]

View File

@ -7,7 +7,9 @@
[frontend.test.helper :as test-helper]
[frontend.worker.undo-redo :as undo-redo]))
(def ^:private init-data (test-helper/initial-test-page-and-blocks))
(def ^:private page-uuid (random-uuid))
(def ^:private init-data (test-helper/initial-test-page-and-blocks {:page-uuid page-uuid}))
(defn- start-and-destroy-db
[f]
(test-helper/db-based-start-and-destroy-db
@ -20,11 +22,13 @@
(defn- gen-block-uuid
[db & {:keys [non-exist-frequency] :or {non-exist-frequency 1}}]
(gen/frequency [[9 (t.gen/gen-available-block-uuid db)] [non-exist-frequency gen-non-exist-block-uuid]]))
(gen/frequency [[9 (t.gen/gen-available-block-uuid db {:page-uuid page-uuid})]
[non-exist-frequency gen-non-exist-block-uuid]]))
(defn- gen-parent-left-pair
[db]
(gen/frequency [[9 (t.gen/gen-available-parent-left-pair db)] [1 (gen/vector gen-non-exist-block-uuid 2)]]))
(gen/frequency [[9 (t.gen/gen-available-parent-left-pair db {:page-uuid page-uuid})]
[1 (gen/vector gen-non-exist-block-uuid 2)]]))
(defn- gen-move-block-op
[db]
@ -43,7 +47,7 @@
(defn- gen-remove-block-op
[db]
(gen/let [block-uuid (gen-block-uuid db {:non-exist-frequency 80})
(gen/let [block-uuid (gen-block-uuid db {:non-exist-frequency 90})
[parent left] (gen-parent-left-pair db)
content gen/string-alphanumeric]
[:frontend.worker.undo-redo/remove-block
@ -116,7 +120,7 @@
[conn]
(binding [undo-redo/*undo-redo-info-for-test* (atom nil)]
(loop [i 0]
(let [r (undo-redo/undo test-helper/test-db-name-db-version conn)
(let [r (undo-redo/undo test-helper/test-db-name-db-version page-uuid conn)
current-db @conn]
(check-block-count @undo-redo/*undo-redo-info-for-test* current-db)
(if (not= :frontend.worker.undo-redo/empty-undo-stack r)
@ -124,7 +128,7 @@
(prn :undo-count i))))
(loop []
(let [r (undo-redo/redo test-helper/test-db-name-db-version conn)
(let [r (undo-redo/redo test-helper/test-db-name-db-version page-uuid conn)
current-db @conn]
(check-block-count @undo-redo/*undo-redo-info-for-test* current-db)
(when (not= :frontend.worker.undo-redo/empty-redo-stack r)
@ -132,12 +136,12 @@
(deftest undo-redo-gen-test
(let [conn (db/get-db false)
all-remove-ops (gen/generate (gen/vector (gen-op @conn {:remove-block-op 1000}) 20))]
(#'undo-redo/push-undo-ops test-helper/test-db-name-db-version all-remove-ops)
all-remove-ops (gen/generate (gen/vector (gen-op @conn {:remove-block-op 1000}) 100))]
(#'undo-redo/push-undo-ops test-helper/test-db-name-db-version page-uuid all-remove-ops)
(prn :block-count-before-init (count (get-db-block-set @conn)))
(loop [i 0]
(when (not= :frontend.worker.undo-redo/empty-undo-stack
(undo-redo/undo test-helper/test-db-name-db-version conn))
(undo-redo/undo test-helper/test-db-name-db-version page-uuid conn))
(recur (inc i))))
(prn :block-count (count (get-db-block-set @conn)))
(undo-redo/clear-undo-redo-stack)
@ -145,7 +149,7 @@
(let [origin-graph-block-set (get-db-block-set @conn)
ops (gen/generate (gen/vector (gen-op @conn {:move-block-op 1000 :boundary-op 500}) 300))]
(prn :ops (count ops))
(#'undo-redo/push-undo-ops test-helper/test-db-name-db-version ops)
(#'undo-redo/push-undo-ops test-helper/test-db-name-db-version page-uuid ops)
(undo-all-then-redo-all conn)
(undo-all-then-redo-all conn)
@ -157,7 +161,7 @@
(let [origin-graph-block-set (get-db-block-set @conn)
ops (gen/generate (gen/vector (gen-op @conn) 1000))]
(prn :ops (count ops))
(#'undo-redo/push-undo-ops test-helper/test-db-name-db-version ops)
(#'undo-redo/push-undo-ops test-helper/test-db-name-db-version page-uuid ops)
(undo-all-then-redo-all conn)
(undo-all-then-redo-all conn)