diff --git a/src/main/frontend/worker/rtc/client.cljs b/src/main/frontend/worker/rtc/client.cljs index e88952ff30..9464fd41f4 100644 --- a/src/main/frontend/worker/rtc/client.cljs +++ b/src/main/frontend/worker/rtc/client.cljs @@ -233,6 +233,30 @@ (string/ends-with? ns ".property")) (= :db.cardinality/one (:db/cardinality (db-schema a)))))) a-coll))) +(defmethod local-block-ops->remote-ops-aux :add-op + [_ & {:keys [db block add-op parent-uuid block-order *remote-ops *depend-on-block-uuid-set]}] + (let [block-uuid (:block/uuid block) + pos (->pos parent-uuid block-order) + av-coll (->> (:av-coll (last add-op)) + (remove-redundant-av db) + (remove-non-exist-ref-av db)) + [schema-av-coll other-av-coll] (group-by-schema-attrs av-coll) + update-schema-op (schema-av-coll->update-schema-op db block-uuid (:db/ident block) schema-av-coll) + depend-on-block-uuids (keep (fn [[_a v]] (when (uuid? v) v)) other-av-coll) + card-one-attrs (seq (av-coll->card-one-attrs (d/schema db) other-av-coll))] + (when (seq other-av-coll) + (swap! *remote-ops conj + [:add (cond-> {:block-uuid block-uuid + :pos pos + :av-coll other-av-coll} + (:db/ident block) (assoc :db/ident (:db/ident block)) + card-one-attrs (assoc :card-one-attrs card-one-attrs))])) + (when update-schema-op + (swap! *remote-ops conj update-schema-op)) + (swap! *depend-on-block-uuid-set (partial apply conj) depend-on-block-uuids) + (when parent-uuid + (swap! *depend-on-block-uuid-set conj parent-uuid)))) + (defmethod local-block-ops->remote-ops-aux :update-op [_ & {:keys [db block update-op block-order parent-uuid *remote-ops *depend-on-block-uuid-set]}] (let [block-uuid (:block/uuid block) @@ -281,9 +305,9 @@ [db block-ops] (let [*depend-on-block-uuid-set (atom #{}) *remote-ops (atom []) - {move-op :move remove-op :remove update-op :update update-page-op :update-page remove-page-op :remove-page} + {move-op :move remove-op :remove update-op :update add-op :add update-page-op :update-page remove-page-op :remove-page} block-ops] - (when-let [block-uuid (some (comp :block-uuid last) [move-op update-op update-page-op])] + (when-let [block-uuid (some (comp :block-uuid last) [move-op update-op add-op update-page-op])] (when-let [block (d/entity db [:block/uuid block-uuid])] (let [parent-uuid (some-> block :block/parent :block/uuid)] ;; remote-move-op @@ -294,6 +318,16 @@ :block-uuid block-uuid :*remote-ops *remote-ops :*depend-on-block-uuid-set *depend-on-block-uuid-set)) + ;; remote-add-op + (when add-op + (local-block-ops->remote-ops-aux :add-op + :db db + :block block + :add-op add-op + :parent-uuid parent-uuid + :block-order (:block/order block) + :*remote-ops *remote-ops + :*depend-on-block-uuid-set *depend-on-block-uuid-set)) ;; remote-update-op (when update-op (local-block-ops->remote-ops-aux :update-op @@ -394,6 +428,10 @@ (some->> (get-in block-uuid->remote-ops [block-uuid :move]) (vector :move))) sorted-uuids) + add-ops (keep + (fn [[_ remote-ops]] + (some->> (:add remote-ops) (vector :add))) + block-uuid->remote-ops) update-schema-ops (keep (fn [[_ remote-ops]] (some->> (:update-schema remote-ops) (vector :update-schema))) @@ -415,7 +453,7 @@ (fn [[_ remote-ops]] (some->> (:remove-page remote-ops) (vector :remove-page))) block-uuid->remote-ops)] - (concat update-schema-ops update-page-ops remove-ops sorted-move-ops update-ops remove-page-ops))) + (concat add-ops update-schema-ops update-page-ops remove-ops sorted-move-ops update-ops remove-page-ops))) (defn- rollback [repo block-ops-map-coll update-kv-value-ops-map-coll rename-db-ident-ops-map-coll] @@ -470,6 +508,13 @@ (recur rest-remote-ops (conj result [op-type (assoc op-value :av-coll av-coll*)]))) + :add + (let [av-coll* (c.m/ t1 t2) - (merge-update-ops update-op2 update-op1) - (let [{av-coll1 :av-coll block-uuid :block-uuid} (last update-op1) - {av-coll2 :av-coll} (last update-op2)] - [:update t2 + (merge-update-ops op2 op1) + (let [{av-coll1 :av-coll block-uuid :block-uuid} (last op1) + {av-coll2 :av-coll} (last op2) + result-op-type (if (or (= :add op-type1) (= :add op-type2)) :add :update)] + [result-op-type t2 {:block-uuid block-uuid :av-coll (concat av-coll1 av-coll2)}])))) @@ -152,22 +162,34 @@ [_ move-op-t _move-op-value :as move-op] :move [_ update-op-t _update-op-value :as update-op] :update [_ update-page-op-t _update-page-op-value :as update-page-op] :update-page - [_ remove-page-op-t _remove-page-op-value :as remove-page-op] :remove-page} + [_ remove-page-op-t _remove-page-op-value :as remove-page-op] :remove-page + [_ add-op-t _add-op-value :as add-op] :add} (into {} (filter (fn [[_op-type op]] (some-> op (not= :retract))) current-block-op-map))] (case op-type - :move + :add (if (>= remove-op-t op-t) current-block-op-map (cond-> (assoc current-block-op-map :remove :retract) - (or (nil? move-op) (> op-t move-op-t)) (assoc :move op-to-add))) + (or (nil? add-op) (> op-t add-op-t)) (assoc :add op-to-add))) + :move + (if (>= remove-op-t op-t) current-block-op-map + (if add-op + (let [[_ add-t add-val] add-op + new-t (max add-t op-t)] + (assoc current-block-op-map :add [:add new-t add-val])) + (cond-> (assoc current-block-op-map :remove :retract) + (or (nil? move-op) (> op-t move-op-t)) (assoc :move op-to-add)))) :update (if (>= remove-op-t op-t) current-block-op-map - (assoc current-block-op-map - :remove :retract - :update (if update-op (merge-update-ops update-op op-to-add) op-to-add))) + (if add-op + (assoc current-block-op-map :add (merge-update-ops add-op op-to-add)) + (assoc current-block-op-map + :remove :retract + :update (if update-op (merge-update-ops update-op op-to-add) op-to-add)))) :remove - (if (or (>= move-op-t op-t) (>= update-op-t op-t)) current-block-op-map - (cond-> (assoc current-block-op-map :move :retract :update :retract) - (or (nil? remove-op) (> op-t remove-op-t)) (assoc :remove op-to-add))) + (if (or (>= move-op-t op-t) (>= update-op-t op-t) (and add-op (>= add-op-t op-t))) + current-block-op-map + (cond-> (assoc current-block-op-map :move :retract :update :retract :add :retract) + (or (nil? remove-op) (> op-t remove-op-t)) (assoc :remove op-to-add))) :update-page (if (>= remove-page-op-t op-t) current-block-op-map (cond-> (assoc current-block-op-map :remove-page :retract) @@ -182,7 +204,7 @@ (let [sorted-ops (sort-by second ops) block-uuids (map (fn [[_op-type _t value]] (:block-uuid value)) sorted-ops) ents (d/pull-many client-ops-db '[*] (map (fn [block-uuid] [:block/uuid block-uuid]) block-uuids)) - op-types [:move :update :remove :update-page :remove-page] + op-types [:add :move :update :remove :update-page :remove-page] init-block-uuid->op-type->op (into {} (map (fn [ent] diff --git a/src/main/frontend/worker/rtc/gen_client_op.cljs b/src/main/frontend/worker/rtc/gen_client_op.cljs index 77a68d276e..a410d26840 100644 --- a/src/main/frontend/worker/rtc/gen_client_op.cljs +++ b/src/main/frontend/worker/rtc/gen_client_op.cljs @@ -134,6 +134,7 @@ add?->block-order->t :block/order} a->add?->v->t [retract-block-uuid t1] (some-> add?->block-uuid->t (get false) first) + [add-block-uuid t-uuid] (some-> add?->block-uuid->t (get true) first) [retract-block-name _] (some-> add?->block-name->t (get false) first) [add-block-name t2] (some-> add?->block-name->t latest-add?->v->t (get-first-vt true)) [add-block-title t3] (some-> add?->block-title->t latest-add?->v->t (get-first-vt true)) @@ -152,6 +153,14 @@ retract-block-uuid [[:remove t1 {:block-uuid retract-block-uuid}]] + add-block-uuid + (let [av-coll (update-op-av-coll db-before db-after a->add?->v->t*) + add-op [:add (or t-uuid t4 t5) {:block-uuid block-uuid :av-coll av-coll}]] + (if (or add-block-name + (and (ldb/page? entity) add-block-title)) + [[:update-page (or t2 t3) {:block-uuid block-uuid}] add-op] + [add-op])) + :else (let [ops (cond-> [] (or add-block-parent add-block-order) diff --git a/src/test/frontend/worker/rtc/client_op_test.cljs b/src/test/frontend/worker/rtc/client_op_test.cljs index 385f0497e1..86708f1d88 100644 --- a/src/test/frontend/worker/rtc/client_op_test.cljs +++ b/src/test/frontend/worker/rtc/client_op_test.cljs @@ -186,3 +186,64 @@ (is (some? (:move result-op))) (is (= 3 (second (:move result-op)))) (is (nil? (:remove result-op)))))))))) + +(deftest add-op-merge-test + (testing "Merge :add and :move" + (let [conn (d/create-conn client-op/schema-in-db) + block-uuid (random-uuid) + add-op [:add 1 {:block-uuid block-uuid :av-coll []}] + move-op [:move 2 {:block-uuid block-uuid}]] + (with-redefs [worker-state/get-client-ops-conn (constantly conn)] + (client-op/add-ops! "repo" [add-op]) + (client-op/add-ops! "repo" [move-op]) + (let [ops (client-op/get&remove-all-block-ops "repo") + result-op (first ops)] + (is (= 1 (count ops))) + (is (some? (:add result-op))) + (is (= 2 (second (:add result-op)))) + (is (nil? (:move result-op))))))) + + (testing "Merge :add and :update" + (let [conn (d/create-conn client-op/schema-in-db) + block-uuid (random-uuid) + add-op [:add 1 {:block-uuid block-uuid :av-coll [[:block/title "A" 1 true]]}] + update-op [:update 2 {:block-uuid block-uuid :av-coll [[:block/title "B" 2 true]]}]] + (with-redefs [worker-state/get-client-ops-conn (constantly conn)] + (client-op/add-ops! "repo" [add-op]) + (client-op/add-ops! "repo" [update-op]) + (let [ops (client-op/get&remove-all-block-ops "repo") + result-op (first ops)] + (is (= 1 (count ops))) + (is (some? (:add result-op))) + (is (= 2 (second (:add result-op)))) + (let [av-coll (:av-coll (last (:add result-op)))] + (is (= [[:block/title "A" 1 true] [:block/title "B" 2 true]] av-coll))) + (is (nil? (:update result-op))))))) + + (testing "Merge :add and :remove (Newer Remove)" + (let [conn (d/create-conn client-op/schema-in-db) + block-uuid (random-uuid) + add-op [:add 1 {:block-uuid block-uuid :av-coll []}] + remove-op [:remove 2 {:block-uuid block-uuid}]] + (with-redefs [worker-state/get-client-ops-conn (constantly conn)] + (client-op/add-ops! "repo" [add-op]) + (client-op/add-ops! "repo" [remove-op]) + (let [ops (client-op/get&remove-all-block-ops "repo") + result-op (first ops)] + (is (= 1 (count ops))) + (is (some? (:remove result-op))) + (is (nil? (:add result-op))))))) + + (testing "Merge :remove and :add (Newer Add)" + (let [conn (d/create-conn client-op/schema-in-db) + block-uuid (random-uuid) + remove-op [:remove 1 {:block-uuid block-uuid}] + add-op [:add 2 {:block-uuid block-uuid :av-coll []}]] + (with-redefs [worker-state/get-client-ops-conn (constantly conn)] + (client-op/add-ops! "repo" [remove-op]) + (client-op/add-ops! "repo" [add-op]) + (let [ops (client-op/get&remove-all-block-ops "repo") + result-op (first ops)] + (is (= 1 (count ops))) + (is (some? (:add result-op))) + (is (nil? (:remove result-op)))))))) diff --git a/src/test/frontend/worker/rtc/client_test.cljs b/src/test/frontend/worker/rtc/client_test.cljs index 30e9f9ef31..d21ec004e0 100644 --- a/src/test/frontend/worker/rtc/client_test.cljs +++ b/src/test/frontend/worker/rtc/client_test.cljs @@ -104,4 +104,30 @@ [:block/title (ldb/write-transit-str "xxx") 1 true] [:block/type (ldb/write-transit-str "property") 1 true] [:db/cardinality (ldb/write-transit-str :db.cardinality/one) 1 true] - [:db/index (ldb/write-transit-str true) 1 true]]}]}))))))) + [:db/index (ldb/write-transit-str true) 1 true]]}]})))))) + + (testing "user.class/zzz creation (add op)" + (let [block-uuid (random-uuid) + db (d/db-with empty-db [{:block/uuid block-uuid, + :block/updated-at 1720017595873, + :block/created-at 1720017595872, + :db/ident :user.class/zzz, + :block/type "class", + :block/name "zzz", + :block/title "zzz"}])] + (is (= {:add + {:block-uuid block-uuid + :db/ident :user.class/zzz + :pos [nil nil] + :av-coll + [[:block/name "[\"~#'\",\"zzz\"]" 1 true] + [:block/title "[\"~#'\",\"zzz\"]" 1 true] + [:block/type "[\"~#'\",\"class\"]" 1 true]]}} + (:remote-ops + (#'subject/local-block-ops->remote-ops + db + {:add [:add 1 {:block-uuid block-uuid + :av-coll + [[:block/name (ldb/write-transit-str "zzz") 1 true] + [:block/title (ldb/write-transit-str "zzz") 1 true] + [:block/type (ldb/write-transit-str "class") 1 true]]}]}))))))) diff --git a/src/test/frontend/worker/rtc/gen_client_op_test.cljs b/src/test/frontend/worker/rtc/gen_client_op_test.cljs index 4419fc13d1..2a36e9c781 100644 --- a/src/test/frontend/worker/rtc/gen_client_op_test.cljs +++ b/src/test/frontend/worker/rtc/gen_client_op_test.cljs @@ -24,7 +24,7 @@ id->same-entity-datoms (group-by first datom-vec-coll)] (update-vals id->same-entity-datoms #'subject/entity-datoms=>a->add?->v->t))) -(deftest entity-datoms=>ops-test +(deftest ^:large-vars/cleanup-todo entity-datoms=>ops-test (testing "remove whiteboard page-block" (let [conn (db-test/create-conn) block-uuid (random-uuid) @@ -43,6 +43,31 @@ (is (= [[:remove-page {:block-uuid block-uuid}]] (map (fn [[op-type _t op-value]] [op-type op-value]) r))))) + (testing "create page block" + (let [conn (db-test/create-conn) + block-uuid (random-uuid) + tx-data [[:db/add 1000000 :block/uuid block-uuid] + [:db/add 1000000 :block/name "page-name"] + [:db/add 1000000 :block/title "Page Title"] + [:db/add 1000000 :block/created-at 1716882111476] + [:db/add 1000000 :block/updated-at 1716882111476]] + {:keys [db-before db-after tx-data]} (d/transact! conn tx-data) + ops (#'subject/entity-datoms=>ops db-before db-after + (tx-data=>e->a->add?->v->t tx-data) + nil + (map vec tx-data))] + (is (= [[:update-page {:block-uuid block-uuid}] + [:add {:block-uuid block-uuid + :av-coll + (set [[:block/updated-at "[\"~#'\",1716882111476]"] + [:block/created-at "[\"~#'\",1716882111476]"] + [:block/title "[\"~#'\",\"Page Title\"]"]])}]] + (map (fn [[op-type _t op-value]] + [op-type (cond-> op-value + (:av-coll op-value) + (assoc :av-coll (set (map #(take 2 %) (:av-coll op-value)))))]) + ops))))) + (testing "update-schema op" (let [conn (db-test/create-conn) tx-data [[:db/add 1000000 :db/index true] @@ -64,20 +89,19 @@ #{:logseq.property/ignored-attr-x} (map vec tx-data))] (is (= - [[:move {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"}] - [:update-page {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"}] - [:update {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305" - :av-coll - [[:db/index "[\"~#'\",true]"] - [:logseq.property/type "[\"~#'\",\"~:number\"]"] - [:db/valueType "[\"~#'\",\"~:db.type/ref\"]"] - [:block/updated-at "[\"~#'\",1716882111476]"] - [:block/created-at "[\"~#'\",1716882111476]"] - [:block/tags #uuid "00000002-1038-7670-4800-000000000000"] - [:block/title "[\"~#'\",\"qqq\"]"] - [:db/cardinality "[\"~#'\",\"~:db.cardinality/one\"]"] + [[:update-page {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"}] + [:add {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305" + :av-coll + [[:db/index "[\"~#'\",true]"] + [:logseq.property/type "[\"~#'\",\"~:number\"]"] + [:db/valueType "[\"~#'\",\"~:db.type/ref\"]"] + [:block/updated-at "[\"~#'\",1716882111476]"] + [:block/created-at "[\"~#'\",1716882111476]"] + [:block/tags #uuid "00000002-1038-7670-4800-000000000000"] + [:block/title "[\"~#'\",\"qqq\"]"] + [:db/cardinality "[\"~#'\",\"~:db.cardinality/one\"]"] ;; [:db/ident "[\"~#'\",\"~:user.property/qqq\"]"] - ]}]] + ]}]] (map (fn [[op-type _t op-value]] [op-type (cond-> op-value (:av-coll op-value) @@ -101,16 +125,16 @@ (map vec tx-data))] (is (= [[:update-page {:block-uuid #uuid "66856a29-6eb3-4122-af97-8580a853c6a6"}] - [:update {:block-uuid #uuid "66856a29-6eb3-4122-af97-8580a853c6a6", - :av-coll - (set - [[:block/updated-at "[\"~#'\",1720019497643]"] - [:block/created-at "[\"~#'\",1720019497643]"] - [:block/tags #uuid "00000002-5389-0208-3000-000000000000"] - [:block/title "[\"~#'\",\"zzz\"]"] - [:logseq.property.class/extends #uuid "00000002-2737-8382-7000-000000000000"] + [:add {:block-uuid #uuid "66856a29-6eb3-4122-af97-8580a853c6a6", + :av-coll + (set + [[:block/updated-at "[\"~#'\",1720019497643]"] + [:block/created-at "[\"~#'\",1720019497643]"] + [:block/tags #uuid "00000002-5389-0208-3000-000000000000"] + [:block/title "[\"~#'\",\"zzz\"]"] + [:logseq.property.class/extends #uuid "00000002-2737-8382-7000-000000000000"] ;;1. shouldn't have :db/ident, :db/ident is special, will be handled later - ])}]] + ])}]] (map (fn [[op-type _t op-value]] [op-type (cond-> op-value (:av-coll op-value) @@ -129,7 +153,7 @@ (testing "add page" (worker-page/create! conn "TEST-PAGE" {:uuid page-uuid}) (is (some? (d/pull @conn '[*] [:block/uuid page-uuid]))) - (is (= {page-uuid #{:update-page :update}} + (is (= {page-uuid #{:add :update-page}} (ops-coll=>block-uuid->op-types (client-op/get&remove-all-block-ops repo))))) (testing "add blocks to this page" (let [target-entity (d/entity @conn [:block/uuid page-uuid])] @@ -142,8 +166,8 @@ target-entity {:sibling? false :keep-uuid? true})) (is (= - {block-uuid1 #{:move :update} - block-uuid2 #{:move :update}} + {block-uuid1 #{:add} + block-uuid2 #{:add}} (ops-coll=>block-uuid->op-types (client-op/get&remove-all-block-ops repo)))))) (testing "delete a block" @@ -164,10 +188,8 @@ :block/tags :block/title :db/cardinality}] #_{:clj-kondo/ignore [:unresolved-symbol :invalid-arity]} (is (->> (me/find (subject/generate-rtc-ops-from-property-entities [ent]) - ([:move _ {:block-uuid ?block-uuid}] - [:update-page _ {:block-uuid ?block-uuid}] - [:update _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}]) - !av-coll-attrs) + ([:update-page . _ ...] [:add _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}]) + !av-coll-attrs) set (set/difference av-coll-attrs) empty?)))) @@ -180,9 +202,8 @@ :block/tags :block/title}] #_{:clj-kondo/ignore [:unresolved-symbol :invalid-arity]} (is (->> (me/find (subject/generate-rtc-ops-from-class-entities [ent]) - ([:update-page _ {:block-uuid ?block-uuid}] - [:update _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}]) - !av-coll-attrs) + ([:update-page . _ ...] [:add _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}]) + !av-coll-attrs) set (set/difference av-coll-attrs) empty?))))