From 6db1a907c806a2998623875183c0d0ef78353e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 19 Nov 2020 15:35:34 +0100 Subject: [PATCH] :sparkles: Use touched flags when adding/deleting/moving shapes --- common/app/common/pages.cljc | 81 ++++- common/app/common/pages_helpers.cljc | 10 + frontend/src/app/main/data/workspace.cljs | 9 +- .../app/main/data/workspace/libraries.cljs | 4 +- .../data/workspace/libraries_helpers.cljs | 339 ++++++++++-------- 5 files changed, 270 insertions(+), 173 deletions(-) diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 1d4e76fb31..0188a1eab4 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -340,6 +340,7 @@ :rx :radius-group :ry :radius-group :masked-group? :mask-group}) + ;; shapes-group is handled differently (s/def ::minimal-shape (s/keys :req-un [::type ::name] @@ -468,30 +469,52 @@ (s/def :internal.changes.add-obj/obj ::shape) +(defn- valid-container-id-frame? + [o] + (or (and (contains? o :page-id) + (not (contains? o :component-id)) + (some? (:frame-id o))) + (and (contains? o :component-id) + (not (contains? o :page-id)) + (nil? (:frame-id o))))) + +(defn- valid-container-id? + [o] + (or (and (contains? o :page-id) + (not (contains? o :component-id))) + (and (contains? o :component-id) + (not (contains? o :page-id))))) + (defmethod change-spec :add-obj [_] - (s/keys :req-un [::id (or ::page-id ::component-id) - :internal.changes.add-obj/obj] - :opt-un [::parent-id ::frame-id])) + (s/and (s/keys :req-un [::id :internal.changes.add-obj/obj] + :opt-un [::page-id ::component-id ::parent-id ::frame-id]) + valid-container-id-frame?)) (s/def ::operation (s/multi-spec operation-spec :type)) (s/def ::operations (s/coll-of ::operation)) (defmethod change-spec :mod-obj [_] - (s/keys :req-un [::id (or ::page-id ::component-id) ::operations])) + (s/and (s/keys :req-un [::id ::operations] + :opt-un [::page-id ::component-id]) + valid-container-id?)) (defmethod change-spec :del-obj [_] - (s/keys :req-un [::id (or ::page-id ::component-id)])) + (s/and (s/keys :req-un [::id] + :opt-un [::page-id ::component-id]) + valid-container-id?)) (s/def :internal.changes.reg-objects/shapes (s/coll-of uuid? :kind vector?)) (defmethod change-spec :reg-objects [_] - (s/keys :req-un [(or ::page-id ::component-id) - :internal.changes.reg-objects/shapes])) + (s/and (s/keys :req-un [:internal.changes.reg-objects/shapes] + :opt-un [::page-id ::component-id]) + valid-container-id?)) (defmethod change-spec :mov-objects [_] - (s/keys :req-un [(or ::page-id ::component-id) ::parent-id :internal.shape/shapes] - :opt-un [::index])) + (s/and (s/keys :req-un [::parent-id :internal.shape/shapes] + :opt-un [::page-id ::component-id ::index]) + valid-container-id?)) (defmethod change-spec :add-page [_] (s/or :empty (s/keys :req-un [::id ::name]) @@ -710,7 +733,8 @@ (assoc data :options (d/dissoc-in (:options data) path))))))) (defmethod process-change :add-obj - [data {:keys [id obj page-id component-id frame-id parent-id index] :as change}] + [data {:keys [id obj page-id component-id frame-id parent-id + index ignore-touched] :as change}] (let [update-fn (fn [data] (let [parent-id (or parent-id frame-id) objects (:objects data)] @@ -726,7 +750,13 @@ (cond (some #{id} shapes) shapes (nil? index) (conj shapes id) - :else (cph/insert-at-index shapes index [id])))))))))] + :else (cph/insert-at-index shapes index [id]))))) + (cond-> + (and (:shape-ref (get-in data [:objects parent-id])) + (not= parent-id frame-id) + (not ignore-touched)) + (update-in [:objects parent-id :touched] + cph/set-touched-group :shapes-group))))))] (if page-id (d/update-in-when data [:pages-index page-id] update-fn) (d/update-in-when data [:components component-id] update-fn)))) @@ -742,7 +772,7 @@ (d/update-in-when data [:components component-id :objects] update-fn)))) (defmethod process-change :del-obj - [data {:keys [page-id component-id id] :as change}] + [data {:keys [page-id component-id id ignore-touched] :as change}] (letfn [(delete-object [objects] (if-let [target (get objects id)] (let [parent-id (cph/get-parent id objects) @@ -754,6 +784,9 @@ (= :group (:type parent))) (update-in [parent-id :shapes] (fn [s] (filterv #(not= % id) s))) + (and (:shape-ref parent) (not ignore-touched)) + (update-in [parent-id :touched] cph/set-touched-group :shapes-group) + (contains? objects frame-id) (update-in [frame-id :shapes] (fn [s] (filterv #(not= % id) s))) @@ -813,7 +846,7 @@ (d/update-in-when data [:components component-id :objects] reg-objects)))) (defmethod process-change :mov-objects - [data {:keys [parent-id shapes index page-id component-id] :as change}] + [data {:keys [parent-id shapes index page-id component-id ignore-touched] :as change}] (letfn [(is-valid-move? [objects shape-id] (let [invalid-targets (cph/calculate-invalid-targets shape-id objects)] (and (not (invalid-targets parent-id)) @@ -840,6 +873,14 @@ (strip-id [coll id] (filterv #(not= % id) coll)) + (add-to-parent [parent index shapes] + (cond-> parent + true + (update :shapes check-insert-items parent index shapes) + + (and (:shape-ref parent) (= (:type parent) :group) (not ignore-touched)) + (update :touched cph/set-touched-group :shapes-group))) + (remove-from-old-parent [cpindex objects shape-id] (let [prev-parent-id (get cpindex shape-id)] ;; Do nothing if the parent id of the shape is the same as @@ -856,7 +897,15 @@ (recur pid (:parent-id obj) (dissoc objects pid)) - (update-in objects [pid :shapes] strip-id sid))))))) + (cond-> objects + true + (update-in [pid :shapes] strip-id sid) + + (and (:shape-ref obj) + (= (:type obj) :group) + (not ignore-touched)) + (update-in [pid :touched] + cph/set-touched-group :shapes-group)))))))) (update-parent-id [objects id] (update objects id assoc :parent-id parent-id)) @@ -888,7 +937,7 @@ (if valid? (as-> objects $ - (update-in $ [parent-id :shapes] check-insert-items parent index shapes) + (update $ parent-id #(add-to-parent % index shapes)) (reduce update-parent-id $ shapes) (reduce (partial remove-from-old-parent cpindex) $ shapes) (reduce (partial update-frame-ids frm-id) $ (get-in $ [parent-id :shapes]))) @@ -1016,7 +1065,7 @@ (cond-> shape (and shape-ref group (not ignore) (not= val (get shape attr))) - (update :touched #(conj (or % #{}) group)) + (update :touched cph/set-touched-group group) (nil? val) (dissoc attr) diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index ecc0dd463f..8c2f1e5619 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -49,6 +49,7 @@ (defn page? [container] + (assert (some? (:type container))) (= (:type container) :page)) (defn component? @@ -297,3 +298,12 @@ (d/seek #(gsh/has-point? % position)) :id) uuid/zero))) + +(defn set-touched-group + [touched group] + (conj (or touched #{}) group)) + +(defn touched-group? + [shape group] + ((or (:touched shape) #{}) group)) + diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index f75a89a672..fbb8c9e3a7 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -711,6 +711,7 @@ (reduce (fn [res id] (let [children (cph/get-children id objects) parents (cph/get-parents id objects) + parent (get objects (first parents)) add-change (fn [id] (let [item (get objects id)] {:type :add-obj @@ -726,7 +727,13 @@ (map add-change children) [{:type :reg-objects :page-id page-id - :shapes (vec parents)}]))) + :shapes (vec parents)}] + (when (some? parent) + [{:type :mod-obj + :page-id page-id + :id (:id parent) + :operations [{:type :set-touched + :touched (:touched parent)}]}])))) [] ids) (map #(array-map diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 77d9879705..3eb8f60db7 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -398,13 +398,15 @@ :page-id page-id :frame-id (:frame-id obj) :parent-id (:parent-id obj) + :ignore-touched true :obj obj}) new-shapes) uchanges (map (fn [obj] {:type :del-obj :id (:id obj) - :page-id page-id}) + :page-id page-id + :ignore-touched true}) new-shapes)] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 3a6241d4be..bd6608a3b5 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -48,9 +48,7 @@ (declare add-shape-to-master) (declare remove-shape) (declare move-shape) -(declare remove-component-and-ref) -(declare remove-ref) -(declare reset-touched) +(declare change-touched) (declare update-attrs) (declare calc-new-pos) @@ -375,13 +373,18 @@ root-master {:omit-touched? (not reset?) :reset-touched? reset? - :set-touched? false}))) + :copy-touched? false}))) (defn- generate-sync-shape-direct-recursive - [container shape-inst component shape-master root-inst root-master options] - (log/trace :msg "Sync shape direct" + [container shape-inst component shape-master root-inst root-master + {:keys [omit-touched? reset-touched? copy-touched?] + :as options :or {omit-touched? false + reset-touched? false + copy-touched? false}}] + (log/trace :msg "Sync shape direct recursive" :shape (str (:name shape-inst)) - :component (:name component)) + :component (:name component) + :options options) (let [root-inst (if (:component-id shape-inst) shape-inst @@ -391,12 +394,17 @@ root-master) [rchanges uchanges] - (update-attrs shape-inst - shape-master - root-inst - root-master - container - options) + (concat-changes + (update-attrs shape-inst + shape-master + root-inst + root-master + container + options) + (change-touched shape-inst + shape-master + container + options)) children-inst (mapv #(cph/get-shape container %) (:shapes shape-inst)) @@ -405,21 +413,22 @@ only-inst (fn [shape-inst] (remove-shape shape-inst - container)) + container + omit-touched?)) only-master (fn [shape-master] (add-shape-to-instance shape-master component container root-inst - root-master)) + root-master + omit-touched?)) both (fn [shape-inst shape-master] (let [options (if-not (:component-id shape-inst) options {:omit-touched? false :reset-touched? false - :set-touched? false :copy-touched? true})] (generate-sync-shape-direct-recursive container @@ -435,7 +444,8 @@ shape-inst (d/index-of children-inst shape-inst) (d/index-of children-master shape-master) - container)) + container + omit-touched?)) [child-rchanges child-uchanges] (compare-children children-inst @@ -476,15 +486,20 @@ shape-master root-inst root-master - {:omit-touched? false - :reset-touched? false - :set-touched? true}))) + {:reset-touched? false + :set-touched? true + :copy-touched? false}))) (defn- generate-sync-shape-inverse-recursive - [container shape-inst component shape-master root-inst root-master options] - (log/trace :msg "Sync shape inverse" + [container shape-inst component shape-master root-inst root-master + {:keys [reset-touched? set-touched? copy-touched?] + :as options :or {reset-touched? false + set-touched? false + copy-touched? false}}] + (log/trace :msg "Sync shape inverse recursive" :shape (str (:name shape-inst)) - :component (:name component)) + :component (:name component) + :options options) (let [root-inst (if (:component-id shape-inst) shape-inst @@ -503,9 +518,14 @@ root-inst component-container options) - (if (:set-touched? options) - (reset-touched shape-inst container) - empty-changes)) + (concat-changes + (change-touched shape-master + shape-inst + component-container + options) + (if (:set-touched? options) + (change-touched shape-inst nil container {:reset-touched? true}) + empty-changes))) children-inst (mapv #(cph/get-shape container %) (:shapes shape-inst)) @@ -521,13 +541,13 @@ only-master (fn [shape-master] (remove-shape shape-master - component-container)) - + component-container + false)) + both (fn [shape-inst shape-master] (let [options (if-not (:component-id shape-inst) options - {:omit-touched? false - :reset-touched? false + {:reset-touched? false :set-touched? false :copy-touched? true})] @@ -544,7 +564,8 @@ shape-master (d/index-of children-master shape-master) (d/index-of children-inst shape-inst) - component-container)) + component-container + false)) [child-rchanges child-uchanges] (compare-children children-inst @@ -560,6 +581,7 @@ ; ---- Operation generation helpers ---- + (defn- compare-children [children-inst children-master only-inst-cb only-master-cb both-cb moved-cb inverse?] (loop [children-inst (seq (or children-inst [])) @@ -626,14 +648,14 @@ (d/concat uchanges1 uchanges2)]) (defn- add-shape-to-instance - [component-shape component page root-instance root-master] + [component-shape component container root-instance root-master omit-touched?] (log/info :msg (str "ADD [P] " (:name component-shape))) (let [component-parent-shape (cph/get-shape component (:parent-id component-shape)) parent-shape (d/seek #(cph/is-master-of component-parent-shape %) (cph/get-object-with-children (:id root-instance) - (:objects page))) + (:objects container))) all-parents (vec (cons (:id parent-shape) - (cph/get-parents parent-shape (:objects page)))) + (cph/get-parents parent-shape (:objects container)))) update-new-shape (fn [new-shape original-shape] (let [new-pos (calc-new-pos new-shape @@ -665,29 +687,43 @@ [new-shape new-shapes _] (cph/clone-object component-shape (:id parent-shape) - (get page :objects) + (get container :objects) update-new-shape update-original-shape) rchanges (d/concat (mapv (fn [shape'] - {:type :add-obj - :id (:id shape') - :page-id (:id page) - :parent-id (:parent-id shape') - :obj shape'}) + (as-> {:type :add-obj + :id (:id shape') + :parent-id (:parent-id shape') + :ignore-touched true + :obj shape'} $ + (cond-> $ + (:frame-id shape') + (assoc :frame-id (:frame-id shape'))) + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))) new-shapes) - [{:type :reg-objects - :page-id (:id page) - :shapes all-parents}]) + [(as-> {:type :reg-objects + :shapes all-parents} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))]) - uchanges (mapv (fn [shape'] - {:type :del-obj - :id (:id shape') - :page-id (:id page)}) - new-shapes)] + uchanges (d/concat + (mapv (fn [shape'] + (as-> {:type :del-obj + :id (:id shape') + :ignore-touched true} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))) + new-shapes))] - [rchanges uchanges])) + (if (and (cph/touched-group? parent-shape :shapes-group) omit-touched?) + empty-changes + [rchanges uchanges]))) (defn- add-shape-to-master [shape component page root-instance root-master] @@ -716,7 +752,7 @@ [new-shape new-shapes updated-shapes] (cph/clone-object shape - (:shape-ref parent-shape) + (:id component-parent-shape) (get page :objects) update-new-shape update-original-shape) @@ -727,6 +763,7 @@ :id (:id shape') :component-id (:id component) :parent-id (:parent-id shape') + :ignore-touched true :obj shape'}) new-shapes) [{:type :reg-objects @@ -753,29 +790,40 @@ :val (:touched shape')}]}) updated-shapes)) - uchanges (mapv (fn [shape'] - {:type :del-obj - :id (:id shape') - :page-id (:id page)}) - new-shapes)] + uchanges (d/concat + (mapv (fn [shape'] + {:type :del-obj + :id (:id shape') + :page-id (:id page) + :ignore-touched true}) + new-shapes))] [rchanges uchanges])) (defn- remove-shape - [shape container] + [shape container omit-touched?] (log/info :msg (str "REMOVE-SHAPE " (if (cph/page? container) "[P] " "[C] ") (:name shape))) (let [objects (get container :objects) parents (cph/get-parents (:id shape) objects) + parent (first parents) children (cph/get-children (:id shape) objects) + rchanges [(as-> {:type :del-obj + :id (:id shape) + :ignore-touched true} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))] + add-change (fn [id] (let [shape' (get objects id)] (as-> {:type :add-obj :id id :index (cph/position-on-parent id objects) :parent-id (:parent-id shape') + :ignore-touched true :obj shape'} $ (cond-> $ (:frame-id shape') @@ -784,12 +832,6 @@ (assoc $ :page-id (:id container)) (assoc $ :component-id (:id container)))))) - rchanges [(as-> {:type :del-obj - :id (:id shape)} $ - (if (cph/page? container) - (assoc $ :page-id (:id container)) - (assoc $ :component-id (:id container))))] - uchanges (d/concat [(add-change (:id shape))] (map add-change children) @@ -798,10 +840,13 @@ (if (cph/page? container) (assoc $ :page-id (:id container)) (assoc $ :component-id (:id container))))])] - [rchanges uchanges])) + + (if (and (cph/touched-group? parent :shapes-group) omit-touched?) + empty-changes + [rchanges uchanges]))) (defn- move-shape - [shape index-before index-after container] + [shape index-before index-after container omit-touched?] (log/info :msg (str "MOVE " (if (cph/page? container) "[P] " "[C] ") (:name shape) @@ -809,111 +854,93 @@ index-before " -> " index-after)) - (let [rchanges [(as-> {:type :mov-objects + (let [parent (cph/get-shape container (:parent-id shape)) + + rchanges [(as-> {:type :mov-objects :parent-id (:parent-id shape) :shapes [(:id shape)] - :index index-after} $ + :index index-after + :ignore-touched true} $ (if (cph/page? container) (assoc $ :page-id (:id container)) (assoc $ :component-id (:id container))))] uchanges [(as-> {:type :mov-objects :parent-id (:parent-id shape) :shapes [(:id shape)] - :index index-before} $ + :index index-before + :ignore-touched true} $ (if (cph/page? container) (assoc $ :page-id (:id container)) (assoc $ :component-id (:id container))))]] - [rchanges uchanges])) -(defn- remove-component-and-ref - [shape container] - (log/info :msg (str "REMOVE-COMPONENT-AND-REF " - (if (cph/page? container) "[P] " "[C] ") - (:name shape))) - [[(as-> {:type :mod-obj - :id (:id shape) - :operations [{:type :set - :attr :component-root? - :val nil} - {:type :set - :attr :component-id - :val nil} - {:type :set - :attr :component-file - :val nil} - {:type :set - :attr :shape-ref - :val nil} - {:type :set-touched - :touched nil}]} $ - (if (cph/page? container) - (assoc $ :page-id (:id container)) - (assoc $ :component-id (:id container))))] - [(as-> {:type :mod-obj - :id (:id shape) - :operations [{:type :set - :attr :component-root? - :val (:component-root? shape)} - {:type :set - :attr :component-id - :val (:component-id shape)} - {:type :set - :attr :component-file - :val (:component-file shape)} - {:type :set - :attr :shape-ref - :val (:shape-ref shape)} - {:type :set-touched - :touched (:touched shape)}]} $ - (if (cph/page? container) - (assoc $ :page-id (:id container)) - (assoc $ :component-id (:id container))))]]) + (if (and (cph/touched-group? parent :shapes-group) omit-touched?) + empty-changes + [rchanges uchanges]))) -(defn- remove-ref - [shape container] - (log/info :msg (str "REMOVE-REF " - (if (cph/page? container) "[P] " "[C] ") - (:name shape))) - [[(as-> {:type :mod-obj - :id (:id shape) - :operations [{:type :set - :attr :shape-ref - :val nil} - {:type :set-touched - :touched nil}]} $ - (if (cph/page? container) - (assoc $ :page-id (:id container)) - (assoc $ :component-id (:id container))))] - [(as-> {:type :mod-obj - :id (:id shape) - :operations [{:type :set - :attr :shape-ref - :val (:shape-ref shape)} - {:type :set-touched - :touched (:touched shape)}]} $ - (if (cph/page? container) - (assoc $ :page-id (:id container)) - (assoc $ :component-id (:id container))))]]) +(defn- change-touched + [dest-shape orig-shape container + {:keys [reset-touched? copy-touched?] + :as options :or {reset-touched? false + copy-touched? false}}] + (if (or (nil? (:shape-ref dest-shape)) + (not (or reset-touched? copy-touched?))) + empty-changes + (do + (log/info :msg (str "CHANGE-TOUCHED " + (if (cph/page? container) "[P] " "[C] ") + (:name dest-shape)) + :options options) + (let [rchanges [(as-> {:type :mod-obj + :id (:id dest-shape) + :operations + [{:type :set-touched + :touched + (cond reset-touched? + nil + copy-touched? + (:touched orig-shape))}]} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))] -(defn- reset-touched + uchanges [(as-> {:type :mod-obj + :id (:id dest-shape) + :operations + [{:type :set-touched + :touched (:touched dest-shape)}]} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))]] + [rchanges uchanges])))) + +(defn- set-touched-shapes-group [shape container] - (log/info :msg (str "RESET-TOUCHED " - (if (cph/page? container) "[P] " "[C] ") - (:name shape))) - [[(as-> {:type :mod-obj - :id (:id shape) - :operations [{:type :set-touched - :touched nil}]} $ - (if (cph/page? container) - (assoc $ :page-id (:id container)) - (assoc $ :component-id (:id container))))] - [(as-> {:type :mod-obj - :id (:id shape) - :operations [{:type :set-touched - :touched (:touched shape)}]} $ - (if (cph/page? container) - (assoc $ :page-id (:id container)) - (assoc $ :component-id (:id container))))]]) + (if-not (:shape-ref shape) + empty-changes + (do + (log/info :msg (str "SET-TOUCHED-SHAPES-GROUP " + (if (cph/page? container) "[P] " "[C] ") + (:name shape))) + (let [rchanges [(as-> {:type :mod-obj + :id (:id shape) + :operations + [{:type :set-touched + :touched (cph/set-touched-group + (:touched shape) + :shapes-group)}]} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))] + + uchanges [(as-> {:type :mod-obj + :id (:id shape) + :operations + [{:type :set-touched + :touched (:touched shape)}]} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))]] + [rchanges uchanges])))) (defn- update-attrs "The main function that implements the sync algorithm. Copy @@ -923,7 +950,9 @@ If reset-touched? is true, the 'touched' flags will be cleared in the dest shape. If set-touched? is true, the corresponding 'touched' flags will be - set in dest shape if they are different than their current values." + set in dest shape if they are different than their current values. + If copy-touched? is true, the value of 'touched' flags in the + origin shape will be copied as is to the dest shape." [dest-shape origin-shape dest-root origin-root container {:keys [omit-touched? reset-touched? set-touched? copy-touched?] :as options :or {omit-touched? false