refactor(undo): update ns frontend.worker.undo-redo (wip)

This commit is contained in:
rcmerci
2024-04-03 17:59:13 +08:00
parent 6339d76db0
commit a7c8451ce4
5 changed files with 162 additions and 30 deletions

View File

@@ -1,10 +1,13 @@
(ns frontend.worker.undo-redo
"undo/redo related fns and op-schema"
(:require [frontend.worker.db-listener :as db-listener]
[datascript.core :as d]
[malli.util :as mu]
[malli.core :as m]))
(:require [datascript.core :as d]
[frontend.worker.db-listener :as db-listener]
[frontend.worker.state :as worker-state]
[logseq.common.config :as common-config]
[logseq.outliner.core :as outliner-core]
[logseq.outliner.transaction :as outliner-tx]
[malli.core :as m]
[malli.util :as mu]))
(def undo-op-schema
(mu/closed-schema
@@ -40,10 +43,10 @@
[:map
[:block-uuid :uuid]
[:block-origin-content {:optional true} :string]
;; TODO: add more attrs
;; TODO: add more attrs
]]]]))
(def undo-op-validator (m/validator undo-op-schema))
(def undo-ops-validator (m/validator [:sequential undo-op-schema]))
(defn reverse-op
[db op]
@@ -83,7 +86,122 @@
(cond-> {:block-uuid block-uuid}
block-origin-content (assoc :block-origin-content block-origin-content))]))))
(def entity-map-pull-pattern
(def ^:private apply-conj-vec (partial apply (fnil conj [])))
(defn- push-undo-ops
[repo ops]
(swap! (:undo/repo->undo-stack @worker-state/*state) update repo apply-conj-vec ops))
(defn- pop-undo-op
[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)))
(defn- push-redo-ops
[repo ops]
(swap! (:undo/repo->redo-stack @worker-state/*state) update repo apply-conj-vec ops))
(defn- pop-redo-op
[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)))
(defmulti 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]
(when-let [left-entity (d/entity @conn [:block/uuid (:block/left block-entity-map)])]
(let [sibling? (not= (:block/left block-entity-map) (:block/parent block-entity-map))]
(outliner-tx/transact!
{:gen-undo-op? false
:outliner-op :insert-blocks
:transact-opts {:repo repo
:conn conn}}
(outliner-core/insert-blocks! repo conn
[(cond-> {:block/uuid block-uuid
:block/content (:block/content block-entity-map)
:block/created-at (:block/created-at block-entity-map)
:block/updated-at (:block/updated-at block-entity-map)
:block/format :markdown}
(seq (:block/tags block-entity-map))
(assoc :block/tags (mapv (partial vector :block/uuid)
(:block/tags block-entity-map))))]
left-entity {:sibling? sibling? :keep-uuid? true}))
:push-undo-redo
))))
(defmethod reverse-apply-op :insert-block
[op conn repo]
(let [[_ {:keys [block-uuid]}] op]
(when-let [block-entity (d/entity @conn [:block/uuid block-uuid])]
(when (empty? (seq (:block/_parent block-entity))) ;if have children, skip
(outliner-tx/transact!
{:gen-undo-op? false
:outliner-op :delete-blocks
:transact-opts {:repo repo
:conn conn}}
(outliner-core/delete-blocks! repo conn
(common-config/get-date-formatter (worker-state/get-config repo))
[block-entity]
{:children? false}))
:push-undo-redo))))
(defmethod reverse-apply-op :move-block
[op conn repo]
(let [[_ {:keys [block-uuid block-origin-left block-origin-parent]}] op]
(when-let [block-entity (d/entity @conn [:block/uuid block-uuid])]
(when-let [left-entity (d/entity @conn [:block/uuid block-origin-left])]
(let [sibling? (not= block-origin-left block-origin-parent)]
(outliner-tx/transact!
{:gen-undo-op? false
:outliner-op :move-blocks
:transact-opts {:repo repo
:conn conn}}
(outliner-core/move-blocks! repo conn [block-entity] left-entity sibling?))
:push-undo-redo)))))
(defmethod reverse-apply-op :update-block
[op conn repo]
(let [[_ {:keys [block-uuid block-origin-content]}] op]
(when-let [block-entity (d/entity @conn [:block/uuid block-uuid])]
(let [new-block (assoc block-entity :block/content block-origin-content)]
(outliner-tx/transact!
{:gen-undo-op? false
:outliner-op :save-block
:transact-opts {:repo repo
:conn conn}}
(outliner-core/save-block! repo conn
(common-config/get-date-formatter (worker-state/get-config repo))
new-block))
:push-undo-redo))))
(defn undo
[repo]
(when-let [op (pop-undo-op 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])))))
(defn redo
[repo]
(when-let [op (pop-redo-op 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])))))
;;; listen db changes and push undo-ops
(def ^:private entity-map-pull-pattern
[:block/uuid
{:block/left [:block/uuid]}
{:block/parent [:block/uuid]}
@@ -102,16 +220,14 @@
(update m :block/tags (partial mapv :block/uuid))
m)))
(defn normal-block?
(defn- normal-block?
[entity]
(and (:block/parent entity)
(:block/left entity)))
(defn entity-datoms=>op
(defn- entity-datoms=>ops
[db-before db-after id->attr->datom entity-datoms]
{:post [(or (nil? %)
(undo-op-validator %))]}
(when-let [e (ffirst entity-datoms)]
(let [attr->datom (id->attr->datom e)]
(when (seq attr->datom)
@@ -124,31 +240,37 @@
(cond
(and (not add1?) block-uuid
(normal-block? entity-before))
[:remove-block
{:block-uuid (:block/uuid entity-before)
:block-entity-map (->block-entity-map db-before e)}]
[[:remove-block
{:block-uuid (:block/uuid entity-before)
:block-entity-map (->block-entity-map db-before e)}]]
(and add1? block-uuid
(normal-block? entity-after))
[:insert-block {:block-uuid (:block/uuid entity-after)}]
[[:insert-block {:block-uuid (:block/uuid entity-after)}]]
(and (or add3? add4?)
(normal-block? entity-after))
[:move-block
{:block-uuid (:block/uuid entity-after)
:block-origin-left (:block/uuid (:block/left entity-before))
:block-origin-parent (:block/uuid (:block/parent entity-before))}]
(cond-> [[:move-block
{:block-uuid (:block/uuid entity-after)
:block-origin-left (:block/uuid (:block/left entity-before))
:block-origin-parent (:block/uuid (:block/parent entity-before))}]]
(and add2? block-content)
(conj [:update-block
{:block-uuid (:block/uuid entity-after)
:block-origin-content (:block/content entity-before)}]))
(and add2? block-content
(normal-block? entity-after))
[:update-block
{:block-uuid (:block/uuid entity-after)
:block-origin-content (:block/content entity-before)}]))))))
[[:update-block
{:block-uuid (:block/uuid entity-after)
:block-origin-content (:block/content entity-before)}]]))))))
(defn generate-undo-ops
[_repo db-before db-after same-entity-datoms-coll id->attr->datom]
(let [ops (keep (partial entity-datoms=>op db-before db-after id->attr->datom) same-entity-datoms-coll)]
(prn ::debug-undo-ops ops)))
(defn- generate-undo-ops
[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)]
(assert (undo-ops-validator ops) ops)
(when (seq ops)
(push-undo-ops repo ops))))
(defmethod db-listener/listen-db-changes :gen-undo-ops
@@ -156,3 +278,5 @@
repo id->attr->datom same-entity-datoms-coll]}]
(when (:gen-undo-op? tx-meta true)
(generate-undo-ops repo db-before db-after same-entity-datoms-coll id->attr->datom)))
;;; listen db changes and push undo-ops (ends)