From ebb7410e5b84681c1b032819488eaf8a66eefebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 11 Nov 2020 13:03:24 +0100 Subject: [PATCH 1/5] :sparkles: Synchronize add/delete/move shapes in componentes --- backend/src/app/services/mutations/files.clj | 3 +- common/app/common/pages.cljc | 79 +- common/app/common/pages_helpers.cljc | 46 +- .../app/main/data/workspace/libraries.cljs | 27 +- .../data/workspace/libraries_helpers.cljs | 890 +++++++++++------- 5 files changed, 640 insertions(+), 405 deletions(-) diff --git a/backend/src/app/services/mutations/files.clj b/backend/src/app/services/mutations/files.clj index 27da20bd5..38d971c0e 100644 --- a/backend/src/app/services/mutations/files.clj +++ b/backend/src/app/services/mutations/files.clj @@ -251,7 +251,8 @@ :add-media :mod-media :del-media :add-component :mod-component :del-component :add-typography :mod-typography :del-typography} (:type change)) - (and (= (:type change) :mod-obj) + (and (#{:add-obj :mod-obj :del-obj + :reg-objects :mov-objects} (:type change)) (some? (:component-id change))))) (declare update-file) diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 311d9e967..0d59551c8 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -435,6 +435,14 @@ :internal.file/recent-colors :internal.file/media])) +(s/def ::container-type #{:page :component}) + +(s/def ::container + (s/keys :req-un [::container-type + ::id + ::name + :internal.page/objects])) + (defmulti operation-spec :type) (s/def :internal.operations.set/attr keyword?) @@ -461,9 +469,9 @@ (s/def :internal.changes.add-obj/obj ::shape) (defmethod change-spec :add-obj [_] - (s/keys :req-un [::id ::page-id ::frame-id + (s/keys :req-un [::id (or ::page-id ::component-id) :internal.changes.add-obj/obj] - :opt-un [::parent-id])) + :opt-un [::parent-id ::frame-id])) (s/def ::operation (s/multi-spec operation-spec :type)) (s/def ::operations (s/coll-of ::operation)) @@ -472,16 +480,17 @@ (s/keys :req-un [::id (or ::page-id ::component-id) ::operations])) (defmethod change-spec :del-obj [_] - (s/keys :req-un [::id ::page-id])) + (s/keys :req-un [::id (or ::page-id ::component-id)])) (s/def :internal.changes.reg-objects/shapes (s/coll-of uuid? :kind vector?)) (defmethod change-spec :reg-objects [_] - (s/keys :req-un [::page-id :internal.changes.reg-objects/shapes])) + (s/keys :req-un [(or ::page-id ::component-id) + :internal.changes.reg-objects/shapes])) (defmethod change-spec :mov-objects [_] - (s/keys :req-un [::page-id ::parent-id :internal.shape/shapes] + (s/keys :req-un [(or ::page-id ::component-id) ::parent-id :internal.shape/shapes] :opt-un [::index])) (defmethod change-spec :add-page [_] @@ -701,26 +710,26 @@ (assoc data :options (d/dissoc-in (:options data) path))))))) (defmethod process-change :add-obj - [data {:keys [id obj page-id frame-id parent-id index] :as change}] - (d/update-in-when data [:pages-index page-id] - (fn [data] - (let [parent-id (or parent-id frame-id) - objects (:objects data)] - (when (and (contains? objects parent-id) - (contains? objects frame-id)) - (let [obj (assoc obj - :frame-id frame-id - :parent-id parent-id - :id id)] - (-> data - (update :objects assoc id obj) - (update-in [:objects parent-id :shapes] - (fn [shapes] - (let [shapes (or shapes [])] - (cond - (some #{id} shapes) shapes - (nil? index) (conj shapes id) - :else (cph/insert-at-index shapes index [id])))))))))))) + [data {:keys [id obj page-id component-id frame-id parent-id index] :as change}] + (let [update-fn (fn [data] + (let [parent-id (or parent-id frame-id) + objects (:objects data)] + (let [obj (assoc obj + :frame-id frame-id + :parent-id parent-id + :id id)] + (-> data + (update :objects assoc id obj) + (update-in [:objects parent-id :shapes] + (fn [shapes] + (let [shapes (or shapes [])] + (cond + (some #{id} shapes) shapes + (nil? index) (conj shapes id) + :else (cph/insert-at-index shapes index [id])))))))))] + (if page-id + (d/update-in-when data [:pages-index page-id] update-fn) + (d/update-in-when data [:components component-id] update-fn)))) (defmethod process-change :mod-obj [data {:keys [id page-id component-id operations] :as change}] @@ -733,8 +742,8 @@ (d/update-in-when data [:components component-id :objects] update-fn)))) (defmethod process-change :del-obj - [data {:keys [page-id id] :as change}] - (letfn [(delete-object [objects id] + [data {:keys [page-id component-id id] :as change}] + (letfn [(delete-object [objects] (if-let [target (get objects id)] (let [parent-id (cph/get-parent id objects) frame-id (:frame-id target) @@ -752,7 +761,9 @@ ; dependend objects (as-> $ (reduce delete-object $ (:shapes target))))) objects))] - (d/update-in-when data [:pages-index page-id :objects] delete-object id))) + (if page-id + (d/update-in-when data [:pages-index page-id :objects] delete-object) + (d/update-in-when data [:components component-id :objects] delete-object)))) (defn rotation-modifiers [center shape angle] @@ -765,7 +776,7 @@ ;; reg-objects operation "regenerates" the values for the parent groups (defmethod process-change :reg-objects - [data {:keys [page-id shapes]}] + [data {:keys [page-id component-id shapes]}] (letfn [(reg-objects [objects] (reduce #(update %1 %2 update-group %1) objects (sequence (comp @@ -797,10 +808,12 @@ (assoc-in [:modifiers :rotation] (:rotation group 0)) (geom/transform-shape))))] - (d/update-in-when data [:pages-index page-id :objects] reg-objects))) + (if page-id + (d/update-in-when data [:pages-index page-id :objects] reg-objects) + (d/update-in-when data [:components component-id :objects] reg-objects)))) (defmethod process-change :mov-objects - [data {:keys [parent-id shapes index page-id] :as change}] + [data {:keys [parent-id shapes index page-id component-id] :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)) @@ -881,7 +894,9 @@ (reduce (partial update-frame-ids frm-id) $ (get-in $ [parent-id :shapes]))) objects)))] - (d/update-in-when data [:pages-index page-id :objects] move-objects))) + (if page-id + (d/update-in-when data [:pages-index page-id :objects] move-objects) + (d/update-in-when data [:components component-id :objects] move-objects)))) (defmethod process-change :add-page [data {:keys [id name page]}] diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index e9722ec2d..9683e9369 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -42,11 +42,25 @@ objects) nil))) +(defn make-container + [page-or-component container-type] + (assoc page-or-component + :container-type container-type)) + +(defn is-page + [container] + (= (:container-type container) :page)) + +(defn is-component + [container] + (= (:container-type container) :component)) + (defn get-container - [page-id component-id local-file] - (if (some? page-id) - (get-in local-file [:pages-index page-id]) - (get-in local-file [:components component-id]))) + [container-id container-type local-file] + (-> (if (= container-type :page) + (get-in local-file [:pages-index container-id]) + (get-in local-file [:components container-id])) + (assoc :container-type container-type))) (defn get-shape [container shape-id] @@ -59,6 +73,12 @@ (get-in libraries [file-id :data]))] (get-in file [:components component-id]))) +(defn is-master-of + [shape-master shape-inst] + (and (:shape-ref shape-inst) + (or (= (:shape-ref shape-inst) (:id shape-master)) + (= (:shape-ref shape-inst) (:shape-ref shape-master))))) + (defn get-component-root [component] (get-in component [:objects (:id component)])) @@ -75,12 +95,12 @@ (defn get-children-objects "Retrieve all children objects recursively for a given object" [id objects] - (map #(get objects %) (get-children id objects))) + (mapv #(get objects %) (get-children id objects))) (defn get-object-with-children - "Retrieve a list with an object and all of its children" + "Retrieve a vector with an object and all of its children" [id objects] - (map #(get objects %) (cons id (get-children id objects)))) + (mapv #(get objects %) (cons id (get-children id objects)))) (defn is-shape-grouped "Checks if a shape is inside a group" @@ -210,17 +230,17 @@ :parent-id parent-id) (some? (:shapes object)) - (assoc :shapes (map :id new-direct-children))) + (assoc :shapes (mapv :id new-direct-children))) new-object (update-new-object new-object object) - new-objects (concat [new-object] new-children) + new-objects (d/concat [new-object] new-children) updated-object (update-original-object object new-object) updated-objects (if (identical? object updated-object) updated-children - (concat [updated-object] updated-children))] + (d/concat [updated-object] updated-children))] [new-object new-objects updated-objects]) @@ -232,9 +252,9 @@ (recur (next child-ids) - (concat new-direct-children [new-child]) - (concat new-children new-child-objects) - (concat updated-children updated-child-objects)))))))) + (d/concat new-direct-children [new-child]) + (d/concat new-children new-child-objects) + (d/concat updated-children updated-child-objects)))))))) (defn indexed-shapes diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 9bcf04238..77d987970 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -32,6 +32,7 @@ [cljs.spec.alpha :as s] [potok.core :as ptk])) +;; Change this to :info :debug or :trace to debug this module (log/set-level! :warn) (declare sync-file) @@ -493,16 +494,18 @@ (ptk/reify ::reset-component ptk/WatchEvent (watch [_ state stream] - ;; ===== Uncomment this to debug ===== (log/info :msg "RESET-COMPONENT of shape" :id (str id)) - (let [[rchanges uchanges] - (dwlh/generate-sync-shape-and-children-components (get state :current-page-id) - nil - id - (get state :workspace-data) - (get state :workspace-libraries) - true)] - ;; ===== Uncomment this to debug ===== + (let [local-file (get state :workspace-data) + libraries (get state :workspace-libraries) + container (cph/get-container (get state :current-page-id) + :page + local-file) + [rchanges uchanges] + (dwlh/generate-sync-shape-direct container + id + local-file + libraries + true)] (log/debug :msg "RESET-COMPONENT finished" :js/rchanges rchanges) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) @@ -516,7 +519,6 @@ (ptk/reify ::update-component ptk/WatchEvent (watch [_ state stream] - ;; ===== Uncomment this to debug ===== (log/info :msg "UPDATE-COMPONENT of shape" :id (str id)) (let [page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) @@ -529,7 +531,6 @@ (get state :workspace-data) (get state :workspace-libraries))] - ;; ===== Uncomment this to debug ===== (log/debug :msg "UPDATE-COMPONENT finished" :js/rchanges rchanges) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) @@ -552,7 +553,6 @@ ptk/WatchEvent (watch [_ state stream] - ;; ===== Uncomment this to debug ===== (log/info :msg "SYNC-FILE" :file (str (or file-id "local"))) (let [library-changes [(dwlh/generate-sync-library :components file-id state) (dwlh/generate-sync-library :colors file-id state) @@ -566,7 +566,6 @@ uchanges (d/concat [] (->> library-changes (remove nil?) (map second) (flatten)) (->> file-changes (remove nil?) (map second) (flatten)))] - ;; ===== Uncomment this to debug ===== (log/debug :msg "SYNC-FILE finished" :js/rchanges rchanges) (rx/concat (rx/of (dm/hide-tag :sync-dialog)) @@ -593,14 +592,12 @@ (ptk/reify ::sync-file-2nd-stage ptk/WatchEvent (watch [_ state stream] - ;; ===== Uncomment this to debug ===== (log/info :msg "SYNC-FILE (2nd stage)" :file (str (or file-id "local"))) (let [[rchanges1 uchanges1] (dwlh/generate-sync-file :components nil state) [rchanges2 uchanges2] (dwlh/generate-sync-library :components file-id state) rchanges (d/concat rchanges1 rchanges2) uchanges (d/concat uchanges1 uchanges2)] (when rchanges - ;; ===== Uncomment this to debug ===== (log/debug :msg "SYNC-FILE (2nd stage) finished" :js/rchanges rchanges) (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 929839973..42ff92479 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -18,6 +18,7 @@ [app.util.logging :as log] [app.util.text :as ut])) +;; Change this to :info :debug or :trace to debug this module (log/set-level! :warn) (defonce empty-changes [[] []]) @@ -36,14 +37,17 @@ (declare has-asset-reference-fn) (declare get-assets) -(declare generate-sync-shape-and-children-components) -(declare generate-sync-shape-and-children-normal) -(declare generate-sync-shape-and-children-nested) +(declare generate-sync-shape-direct) +(declare generate-sync-shape-direct-recursive) (declare generate-sync-shape-inverse) -(declare generate-sync-shape-inverse-normal) -(declare generate-sync-shape-inverse-nested) -(declare generate-sync-shape<-component) -(declare generate-sync-shape->component) +(declare generate-sync-shape-inverse-recursive) + +(declare compare-children) +(declare concat-changes) +(declare add-shape-to-instance) +(declare add-shape-to-master) +(declare remove-shape) +(declare move-shape) (declare remove-component-and-ref) (declare remove-ref) (declare reset-touched) @@ -133,9 +137,7 @@ (generate-sync-container asset-type library-id state - page - (:id page) - nil)] + (cph/make-container page :page))] (recur (next pages) (d/concat rchanges page-rchanges) (d/concat uchanges page-uchanges))) @@ -165,9 +167,8 @@ (generate-sync-container asset-type library-id state - local-component - nil - (:id local-component))] + (cph/make-container local-component + :component))] (recur (next local-components) (d/concat rchanges comp-rchanges) (d/concat uchanges comp-uchanges))) @@ -176,11 +177,11 @@ (defn- generate-sync-container "Generate changes to synchronize all shapes in a particular container (a page or a component) that are linked to the given library." - [asset-type library-id state container page-id component-id] + [asset-type library-id state container] - (if page-id - (log/debug :msg "Sync page in local file" :page-id page-id) - (log/debug :msg "Sync component in local library" :component-id component-id)) + (if (= (:container-type container) :page) + (log/debug :msg "Sync page in local file" :page-id (:id container)) + (log/debug :msg "Sync component in local library" :component-id (:id container))) (let [has-asset-reference? (has-asset-reference-fn asset-type library-id) linked-shapes (cph/select-objects has-asset-reference? container)] @@ -192,9 +193,7 @@ (generate-sync-shape asset-type library-id state - (get container :objects) - page-id - component-id + container shape)] (recur (next shapes) (d/concat rchanges shape-rchanges) @@ -241,16 +240,15 @@ (defmulti generate-sync-shape "Generate changes to synchronize one shape, that use the given type of asset of the given library." - (fn [type _ _ _ _ _ _ _] type)) + (fn [type _ _ _ _] type)) (defmethod generate-sync-shape :components - [_ library-id state objects page-id component-id shape] - (generate-sync-shape-and-children-components page-id - component-id - (:id shape) - (get state :workspace-data) - (get state :workspace-libraries) - false)) + [_ library-id state container shape] + (generate-sync-shape-direct container + (:id shape) + (get state :workspace-data) + (get state :workspace-libraries) + false)) (defn- generate-sync-text-shape [shape page-id component-id update-node] (let [old-content (:content shape) @@ -275,11 +273,13 @@ (defmethod generate-sync-shape :colors - [_ library-id state _ page-id component-id shape] + [_ library-id state container shape] ;; Synchronize a shape that uses some colors of the library. The value of the ;; color in the library is copied to the shape. - (let [colors (get-assets library-id :colors state)] + (let [page-id (when (cph/is-page container) (:id container)) + component-id (when (cph/is-component container) (:id container)) + colors (get-assets library-id :colors state)] (if (= :text (:type shape)) (let [update-node (fn [node] (if-let [color (get colors (:fill-color-ref-id node))] @@ -325,11 +325,13 @@ (conj uoperations uoperation)))))))))) (defmethod generate-sync-shape :typographies - [_ library-id state _ page-id component-id shape] + [_ library-id state container shape] ;; Synchronize a shape that uses some typographies of the library. The attributes ;; of the typography are copied to the shape." - (let [typographies (get-assets library-id :typographies state) + (let [page-id (when (cph/is-page container) (:id container)) + component-id (when (cph/is-component container) (:id container)) + typographies (get-assets library-id :typographies state) update-node (fn [node] (if-let [typography (get typographies (:typography-ref-id node))] (merge node (d/without-keys typography [:name :id])) @@ -345,7 +347,7 @@ (get-in state [:workspace-data asset-type]) (get-in state [:workspace-libraries library-id :data asset-type]))) -(defn generate-sync-shape-and-children-components +(defn generate-sync-shape-direct "Generate changes to synchronize one shape that the root of a component instance, and all its children, from the given component. If reset? is false, all atributes of each component shape that have @@ -353,121 +355,99 @@ be copied to this one. If reset? is true, all changed attributes will be copied and the 'touched' flags in the instance shape will be cleared." - [page-id component-id shape-id local-file libraries reset?] - (log/debug :msg "Sync shape and children" :shape (str shape-id) :reset? reset?) - (let [container (cph/get-container page-id component-id local-file) - shape (cph/get-shape container shape-id) - component (cph/get-component (:component-id shape) - (:component-file shape) - local-file - libraries) - root-shape shape - root-component (cph/get-component-root component)] + [container shape-id local-file libraries reset?] + (log/debug :msg "Sync shape direct" :shape (str shape-id) :reset? reset?) + (let [shape-inst (cph/get-shape container shape-id) + component (cph/get-component (:component-id shape-inst) + (:component-file shape-inst) + local-file + libraries) + shape-master (cph/get-shape component (:shape-ref shape-inst)) - (generate-sync-shape-and-children-normal page-id - component-id - container - shape - component - root-shape - root-component - reset?))) + root-inst shape-inst + root-master (cph/get-component-root component)] -(defn- generate-sync-shape-and-children-normal - [page-id component-id container shape component root-shape root-component reset?] - (log/trace :msg "Sync shape (normal)" - :shape (str (:name shape)) + (generate-sync-shape-direct-recursive container + shape-inst + component + shape-master + root-inst + root-master + {:omit-touched? (not reset?) + :reset-touched? reset? + :set-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" + :shape (str (:name shape-inst)) :component (:name component)) - (let [[rchanges uchanges] - (generate-sync-shape<-component shape - root-shape - root-component - component - page-id - component-id - reset?) - children-ids (get shape :shapes [])] - - (loop [children-ids (seq children-ids) - rchanges rchanges - uchanges uchanges] - (let [child-id (first children-ids)] - (if (nil? child-id) - [rchanges uchanges] - (let [child-shape (cph/get-shape container child-id) - - [child-rchanges child-uchanges] - (if (nil? (:component-id child-shape)) - (generate-sync-shape-and-children-normal page-id - component-id - container - child-shape - component - root-shape - root-component - reset?) - (generate-sync-shape-and-children-nested page-id - component-id - container - child-shape - component - root-shape - root-component - reset?))] - (recur (next children-ids) - (d/concat rchanges child-rchanges) - (d/concat uchanges child-uchanges)))))))) - -(defn- generate-sync-shape-and-children-nested - [page-id component-id container shape component root-shape root-component reset?] - (log/trace :msg "Sync shape (nested)" - :shape (str (:name shape)) - :component (:name component)) - (let [component-shape (d/seek #(= (:shape-ref %) - (:shape-ref shape)) - (vals (:objects component))) - root-shape (if (:component-id shape) - shape - root-shape) - root-component (if (:component-id shape) - component-shape - root-component) + (let [root-inst (if (:component-id shape-inst) + shape-inst + root-inst) + root-master (if (:component-id shape-inst) + shape-master + root-master) [rchanges uchanges] - (update-attrs shape - component-shape - root-shape - root-component - page-id - component-id - {:omit-touched? false - :reset-touched? false - :set-touched? false - :copy-touched? true}) + (update-attrs shape-inst + shape-master + root-inst + root-master + container + options) - children-ids (get shape :shapes [])] + children-inst (mapv #(cph/get-shape container %) + (:shapes shape-inst)) + children-master (mapv #(cph/get-shape component %) + (:shapes shape-master)) - (loop [children-ids (seq children-ids) - rchanges rchanges - uchanges uchanges] - (let [child-id (first children-ids)] - (if (nil? child-id) - [rchanges uchanges] - (let [child-shape (cph/get-shape container child-id) + only-inst (fn [shape-inst] + (remove-shape shape-inst + container)) - [child-rchanges child-uchanges] - (generate-sync-shape-and-children-nested page-id - component-id - container - child-shape - component - root-shape - root-component - reset?)] - (recur (next children-ids) - (d/concat rchanges child-rchanges) - (d/concat uchanges child-uchanges)))))))) + only-master (fn [shape-master] + (add-shape-to-instance shape-master + component + container + root-inst + root-master)) + + 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 + shape-inst + component + shape-master + root-inst + root-master + options))) + + moved (fn [shape-inst shape-master] + (move-shape + shape-inst + (d/index-of children-inst shape-inst) + (d/index-of children-master shape-master) + container)) + + [child-rchanges child-uchanges] + (compare-children children-inst + children-master + only-inst + only-master + both + moved + false)] + + [(d/concat rchanges child-rchanges) + (d/concat uchanges child-uchanges)])) (defn- generate-sync-shape-inverse "Generate changes to update the component a shape is linked to, from @@ -478,229 +458,448 @@ And if the component shapes are, in turn, instances of a second component, their 'touched' flags will be set accordingly." [page-id shape-id local-file libraries] - (log/debug :msg "Sync inverse shape and children" :shape (str shape-id)) - (let [page (cph/get-container page-id nil local-file) - shape (cph/get-shape page shape-id) - component (cph/get-component (:component-id shape) - (:component-file shape) + (log/debug :msg "Sync shape inverse" :shape (str shape-id)) + (let [container (cph/get-container page-id :page local-file) + shape-inst (cph/get-shape container shape-id) + component (cph/get-component (:component-id shape-inst) + (:component-file shape-inst) local-file libraries) - root-shape shape - root-component (cph/get-component-root component)] + shape-master (cph/get-shape component (:shape-ref shape-inst)) - (generate-sync-shape-inverse-normal page - shape - component - root-shape - root-component))) + root-inst shape-inst + root-master (cph/get-component-root component)] -(defn- generate-sync-shape-inverse-normal - [page shape component root-shape root-component] - (log/trace :msg "Sync shape inverse (normal)" - :shape (str (:name shape)) + (generate-sync-shape-inverse-recursive container + shape-inst + component + shape-master + root-inst + root-master + {:omit-touched? false + :reset-touched? false + :set-touched? true}))) + +(defn- generate-sync-shape-inverse-recursive + [container shape-inst component shape-master root-inst root-master options] + (log/trace :msg "Sync shape inverse" + :shape (str (:name shape-inst)) :component (:name component)) - (let [[rchanges uchanges] - (generate-sync-shape->component shape - root-shape - root-component - component - (:id page)) - children-ids (get shape :shapes [])] + (let [root-inst (if (:component-id shape-inst) + shape-inst + root-inst) + root-master (if (:component-id shape-inst) + shape-master + root-master) - (loop [children-ids (seq children-ids) - rchanges rchanges - uchanges uchanges] - (let [child-id (first children-ids)] - (if (nil? child-id) - [rchanges uchanges] - (let [child-shape (cph/get-shape page child-id) - - [child-rchanges child-uchanges] - (if (nil? (:component-id child-shape)) - (generate-sync-shape-inverse-normal page - child-shape - component - root-shape - root-component) - (generate-sync-shape-inverse-nested page - child-shape - component - root-shape - root-component))] - (recur (next children-ids) - (d/concat rchanges child-rchanges) - (d/concat uchanges child-uchanges)))))))) - -(defn- generate-sync-shape-inverse-nested - [page shape component root-shape root-component] - (log/trace :msg "Sync shape inverse (nested)" - :shape (str (:name shape)) - :component (:name component)) - (let [component-shape (d/seek #(= (:shape-ref %) - (:shape-ref shape)) - (vals (:objects component))) - root-shape (if (:component-id shape) - shape - root-shape) - root-component (if (:component-id shape) - component-shape - root-component) + component-container (cph/make-container component :component) [rchanges uchanges] - (update-attrs component-shape - shape - root-component - root-shape - nil - (:id component) - {:omit-touched? false - :reset-touched? false - :set-touched? false - :copy-touched? true}) + (concat-changes + (update-attrs shape-master + shape-inst + root-master + root-inst + component-container + options) + (if (:set-touched? options) + (reset-touched shape-inst container) + empty-changes)) - children-ids (get shape :shapes [])] + children-inst (mapv #(cph/get-shape container %) + (:shapes shape-inst)) + children-master (mapv #(cph/get-shape component %) + (:shapes shape-master)) - (loop [children-ids (seq children-ids) - rchanges rchanges - uchanges uchanges] - (let [child-id (first children-ids)] - (if (nil? child-id) - [rchanges uchanges] - (let [child-shape (cph/get-shape page child-id) + only-inst (fn [shape-inst] + (add-shape-to-master shape-inst + component + container + root-inst + root-master)) - [child-rchanges child-uchanges] - (generate-sync-shape-inverse-nested page - child-shape - component - root-shape - root-component)] - (recur (next children-ids) - (d/concat rchanges child-rchanges) - (d/concat uchanges child-uchanges)))))))) + only-master (fn [shape-master] + (remove-shape shape-master + component-container)) -(defn- generate-sync-shape<-component - "Generate changes to synchronize one shape that is linked to other shape - inside a component. Same considerations as above about reset-touched?" - [shape root-shape root-component component page-id component-id reset?] - (if (nil? component) - (remove-component-and-ref shape page-id component-id) - (let [component-shape (get (:objects component) (:shape-ref shape))] - (if (nil? component-shape) - (remove-ref shape page-id component-id) - (update-attrs shape - component-shape - root-shape - root-component - page-id - component-id - {:omit-touched? (not reset?) - :reset-touched? reset? - :set-touched? false}))))) + 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})] -(defn- generate-sync-shape->component - "Generate changes to synchronize one shape inside a component, with other - shape that is linked to it." - [shape root-shape root-component component page-id] - (if (nil? component) - empty-changes - (let [component-shape (get (:objects component) (:shape-ref shape))] - (if (nil? component-shape) - empty-changes - (let [[rchanges1 uchanges1] - (update-attrs component-shape - shape - root-component - root-shape - nil - (:id root-component) - {:omit-touched? false - :reset-touched? false - :set-touched? true}) - [rchanges2 uchanges2] - (reset-touched shape - page-id - nil)] - [(d/concat rchanges1 rchanges2) - (d/concat uchanges2 uchanges2)]))))) + (generate-sync-shape-inverse-recursive container + shape-inst + component + shape-master + root-inst + root-master + options))) + + moved (fn [shape-inst shape-master] + (move-shape + shape-master + (d/index-of children-master shape-master) + (d/index-of children-inst shape-inst) + component-container)) + + [child-rchanges child-uchanges] + (compare-children children-inst + children-master + only-inst + only-master + both + moved + true)] + + [(d/concat rchanges child-rchanges) + (d/concat uchanges child-uchanges)])) ; ---- Operation generation helpers ---- -(defn- remove-component-and-ref - [shape page-id component-id] - [[(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :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}]})] - [(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :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)}]})]]) +(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 [])) + children-master (seq (or children-master [])) + [rchanges uchanges] [[] []]] + (let [child-inst (first children-inst) + child-master (first children-master)] + (cond + (and (nil? child-inst) (nil? child-master)) + [rchanges uchanges] -(defn- -remove-ref - [shape page-id component-id] - [[(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :operations [{:type :set - :attr :shape-ref - :val nil} - {:type :set-touched - :touched nil}]})] - [(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :operations [{:type :set - :attr :shape-ref - :val (:shape-ref shape)} - {:type :set-touched - :touched (:touched shape)}]})]]) + (nil? child-inst) + (reduce (fn [changes child] + (concat-changes changes (only-master-cb child))) + [rchanges uchanges] + children-master) + + (nil? child-master) + (reduce (fn [changes child] + (concat-changes changes (only-inst-cb child))) + [rchanges uchanges] + children-inst) + + :else + (if (cph/is-master-of child-master child-inst) + (recur (next children-inst) + (next children-master) + (concat-changes [rchanges uchanges] + (both-cb child-inst child-master))) + + (let [child-inst' (d/seek #(cph/is-master-of child-master %) + children-inst) + child-master' (d/seek #(cph/is-master-of % child-inst) + children-master)] + (cond + (nil? child-inst') + (recur children-inst + (next children-master) + (concat-changes [rchanges uchanges] + (only-master-cb child-master))) + + (nil? child-master') + (recur (next children-inst) + children-master + (concat-changes [rchanges uchanges] + (only-inst-cb child-inst))) + + :else + (if inverse? + (recur (next children-inst) + (remove #(= (:id %) (:id child-master')) children-master) + (-> [rchanges uchanges] + (concat-changes (both-cb child-inst' child-master)) + (concat-changes (moved-cb child-inst child-master')))) + (recur (remove #(= (:id %) (:id child-inst')) children-inst) + (next children-master) + (-> [rchanges uchanges] + (concat-changes (both-cb child-inst child-master')) + (concat-changes (moved-cb child-inst' child-master)))))))))))) + +(defn concat-changes + [[rchanges1 uchanges1] [rchanges2 uchanges2]] + [(d/concat rchanges1 rchanges2) + (d/concat uchanges1 uchanges2)]) + +(defn- add-shape-to-instance + [component-shape component page root-instance root-master] + (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))) + all-parents (vec (cons (:id parent-shape) + (cph/get-parents parent-shape (:objects page)))) + + update-new-shape (fn [new-shape original-shape] + (let [new-pos (calc-new-pos new-shape + original-shape + root-instance + root-master)] + (cond-> new-shape + true + (assoc :shape-ref (:id original-shape) + :frame-id (:frame-id parent-shape) + :x (:x new-pos) + :y (:y new-pos)) + + (:component-id original-shape) + (assoc :component-id (:component-id original-shape)) + + (:component-file original-shape) + (assoc :component-file (:component-file original-shape)) + + (:component-root original-shape) + (assoc :component-root (:component-root original-shape)) + + (:touched original-shape) + (assoc :touched (:touched original-shape))))) + + update-original-shape (fn [original-shape new-shape] + original-shape) + + [new-shape new-shapes _] + (cph/clone-object component-shape + (:id parent-shape) + (get page :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'}) + new-shapes) + [{:type :reg-objects + :page-id (:id page) + :shapes all-parents}]) + + uchanges (mapv (fn [shape'] + {:type :del-obj + :id (:id shape') + :page-id (:id page)}) + new-shapes)] + + [rchanges uchanges])) + +(defn- add-shape-to-master + [shape component page root-instance root-master] + (log/info :msg (str "ADD [C] " (:name shape))) + (let [parent-shape (cph/get-shape page (:parent-id shape)) + component-parent-shape (d/seek #(cph/is-master-of % parent-shape) + (cph/get-object-with-children (:id root-master) + (:objects component))) + all-parents (vec (cons (:id component-parent-shape) + (cph/get-parents component-parent-shape (:objects component)))) + + update-new-shape (fn [new-shape original-shape] + (let [new-pos (calc-new-pos new-shape + original-shape + root-master + root-instance)] + (assoc new-shape + :x (:x new-pos) + :y (:y new-pos)))) + + update-original-shape (fn [original-shape new-shape] + (if-not (:shape-ref original-shape) + (assoc original-shape + :shape-ref (:id new-shape)) + original-shape)) + + [new-shape new-shapes updated-shapes] + (cph/clone-object shape + (:shape-ref parent-shape) + (get page :objects) + update-new-shape + update-original-shape) + + rchanges (d/concat + (mapv (fn [shape'] + {:type :add-obj + :id (:id shape') + :component-id (:id component) + :parent-id (:parent-id shape') + :obj shape'}) + new-shapes) + [{:type :reg-objects + :component-id (:id component) + :shapes all-parents}] + (mapv (fn [shape'] + {:type :mod-obj + :page-id (:id page) + :id (:id shape') + :operations [{:type :set + :attr :component-id + :val (:component-id shape')} + {:type :set + :attr :component-file + :val (:component-file shape')} + {:type :set + :attr :component-root? + :val (:component-root? shape')} + {:type :set + :attr :shape-ref + :val (:shape-ref shape')} + {:type :set + :attr :touched + :val (:touched shape')}]}) + updated-shapes)) + + uchanges (mapv (fn [shape'] + {:type :del-obj + :id (:id shape') + :page-id (:id page)}) + new-shapes)] + + [rchanges uchanges])) + +(defn- remove-shape + [shape container] + (let [page-id (when (cph/is-page container) (:id container)) + component-id (when (cph/is-component container) (:id container)) + + objects (get container :objects) + parents (cph/get-parents (:id shape) objects) + children (cph/get-children (:id shape) objects) + + add-change (fn [id] + (let [shape' (get objects id)] + (d/without-nils {:type :add-obj + :id id + :page-id page-id + :component-id component-id + :index (cph/position-on-parent id objects) + :frame-id (:frame-id shape') + :parent-id (:parent-id shape') + :obj shape'}))) + + rchanges [(d/without-nils {:type :del-obj + :page-id page-id + :component-id component-id + :id (:id shape)})] + + uchanges (d/concat + [(add-change (:id shape))] + (map add-change children) + [(d/without-nils {:type :reg-objects + :page-id page-id + :component-id component-id + :shapes (vec parents)})])] + [rchanges uchanges])) + +(defn- move-shape + [shape index-before index-after container] + (log/info :msg (str "MOVE " + (:name shape) + " " + index-before + " -> " + index-after)) + (let [page-id (when (cph/is-page container) (:id container)) + component-id (when (cph/is-component container) (:id container))] + (let [rchanges [(d/without-nils {:type :mov-objects + :parent-id (:parent-id shape) + :shapes [(:id shape)] + :index index-after + :page-id page-id + :component-id component-id})] + uchanges [(d/without-nils {:type :mov-objects + :parent-id (:parent-id shape) + :shapes [(:id shape)] + :index index-before + :page-id page-id + :component-id component-id})]] + [rchanges uchanges]))) + +(defn- remove-component-and-ref + [shape container] + (let [page-id (when (cph/is-page container) (:id container)) + component-id (when (cph/is-component container) (:id container))] + [[(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :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}]})] + [(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :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)}]})]])) + +(defn- remove-ref + [shape container] + (let [page-id (when (cph/is-page container) (:id container)) + component-id (when (cph/is-component container) (:id container))] + [[(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set + :attr :shape-ref + :val nil} + {:type :set-touched + :touched nil}]})] + [(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set + :attr :shape-ref + :val (:shape-ref shape)} + {:type :set-touched + :touched (:touched shape)}]})]])) (defn- reset-touched - [shape page-id component-id] - [[(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :operations [{:type :set-touched - :touched nil}]})] - [(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :operations [{:type :set-touched - :touched (:touched shape)}]})]]) + [shape container] + (let [page-id (when (cph/is-page container) (:id container)) + component-id (when (cph/is-component container) (:id container))] + [[(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set-touched + :touched nil}]})] + [(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set-touched + :touched (:touched shape)}]})]])) (defn- update-attrs "The main function that implements the sync algorithm. Copy @@ -711,7 +910,7 @@ 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." - [dest-shape origin-shape dest-root origin-root page-id component-id + [dest-shape origin-shape dest-root origin-root container {:keys [omit-touched? reset-touched? set-touched? copy-touched?] :as options :or {omit-touched? false reset-touched? false @@ -721,10 +920,13 @@ (log/info :msg (str "SYNC " (:name origin-shape) " -> " - (if page-id "[W] " "[C] ") + (if (cph/is-page container) "[P] " "[C] ") (:name dest-shape))) - (let [; The position attributes need a special sync algorith, because we do + (let [page-id (when (cph/is-page container) (:id container)) + component-id (when (cph/is-component container) (:id container)) + + ; The position attributes need a special sync algorith, because we do ; not synchronize the absolute position, but the position relative of ; the container shape of the component. new-pos (calc-new-pos dest-shape origin-shape dest-root origin-root) From 2582766a899876a1f99c88138643ca7c5842dc3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 18 Nov 2020 13:55:45 +0100 Subject: [PATCH 2/5] :recycle: Simplify container type management --- common/app/common/pages.cljc | 4 +- common/app/common/pages_helpers.cljc | 22 +- .../data/workspace/libraries_helpers.cljs | 317 +++++++++--------- 3 files changed, 172 insertions(+), 171 deletions(-) diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 0d59551c8..1d4e76fb3 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -435,10 +435,10 @@ :internal.file/recent-colors :internal.file/media])) -(s/def ::container-type #{:page :component}) +(s/def :internal.container/type #{:page :component}) (s/def ::container - (s/keys :req-un [::container-type + (s/keys :req-un [:internal.container/type ::id ::name :internal.page/objects])) diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index 9683e9369..ecc0dd463 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -43,24 +43,24 @@ nil))) (defn make-container - [page-or-component container-type] + [page-or-component type] (assoc page-or-component - :container-type container-type)) + :type type)) -(defn is-page +(defn page? [container] - (= (:container-type container) :page)) + (= (:type container) :page)) -(defn is-component +(defn component? [container] - (= (:container-type container) :component)) + (= (:type container) :component)) (defn get-container - [container-id container-type local-file] - (-> (if (= container-type :page) - (get-in local-file [:pages-index container-id]) - (get-in local-file [:components container-id])) - (assoc :container-type container-type))) + [id type local-file] + (-> (if (= type :page) + (get-in local-file [:pages-index id]) + (get-in local-file [:components id])) + (assoc :type type))) (defn get-shape [container shape-id] diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 42ff92479..46a70c2d5 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -179,7 +179,7 @@ (a page or a component) that are linked to the given library." [asset-type library-id state container] - (if (= (:container-type container) :page) + (if (cph/page? container) (log/debug :msg "Sync page in local file" :page-id (:id container)) (log/debug :msg "Sync component in local library" :component-id (:id container))) @@ -250,36 +250,36 @@ (get state :workspace-libraries) false)) -(defn- generate-sync-text-shape [shape page-id component-id update-node] +(defn- generate-sync-text-shape [shape container update-node] (let [old-content (:content shape) new-content (ut/map-node update-node old-content) - rchanges [(d/without-nils {:type :mod-obj - :page-id page-id - :component-id component-id - :id (:id shape) - :operations [{:type :set - :attr :content - :val new-content}]})] - lchanges [(d/without-nils {:type :mod-obj - :page-id page-id - :component-id component-id - :id (:id shape) - :operations [{:type :set - :attr :content - :val old-content}]})]] + rchanges [(as-> {:type :mod-obj + :id (:id shape) + :operations [{:type :set + :attr :content + :val new-content}]} $ + (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 + :attr :content + :val old-content}]} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))]] + (if (= new-content old-content) empty-changes - [rchanges lchanges]))) - + [rchanges uchanges]))) (defmethod generate-sync-shape :colors [_ library-id state container shape] ;; Synchronize a shape that uses some colors of the library. The value of the ;; color in the library is copied to the shape. - (let [page-id (when (cph/is-page container) (:id container)) - component-id (when (cph/is-component container) (:id container)) - colors (get-assets library-id :colors state)] + (let [colors (get-assets library-id :colors state)] (if (= :text (:type shape)) (let [update-node (fn [node] (if-let [color (get colors (:fill-color-ref-id node))] @@ -288,7 +288,7 @@ :fill-opacity (:opacity color) :fill-color-gradient (:gradient color)) node))] - (generate-sync-text-shape shape page-id component-id update-node)) + (generate-sync-text-shape shape container update-node)) (loop [attrs (seq color-sync-attrs) roperations [] uoperations []] @@ -296,16 +296,18 @@ (if (nil? attr) (if (empty? roperations) empty-changes - (let [rchanges [(d/without-nils {:type :mod-obj - :page-id page-id - :component-id component-id - :id (:id shape) - :operations roperations})] - uchanges [(d/without-nils {:type :mod-obj - :page-id page-id - :component-id component-id - :id (:id shape) - :operations uoperations})]] + (let [rchanges [(as-> {:type :mod-obj + :id (:id shape) + :operations roperations} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))] + uchanges [(as-> {:type :mod-obj + :id (:id shape) + :operations uoperations} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))]] [rchanges uchanges])) (if-not (contains? shape attr-ref-id) (recur (next attrs) @@ -329,14 +331,12 @@ ;; Synchronize a shape that uses some typographies of the library. The attributes ;; of the typography are copied to the shape." - (let [page-id (when (cph/is-page container) (:id container)) - component-id (when (cph/is-component container) (:id container)) - typographies (get-assets library-id :typographies state) + (let [typographies (get-assets library-id :typographies state) update-node (fn [node] (if-let [typography (get typographies (:typography-ref-id node))] (merge node (d/without-keys typography [:name :id])) node))] - (generate-sync-text-shape shape page-id component-id update-node))) + (generate-sync-text-shape shape container update-node))) ;; ---- Component synchronization helpers ---- @@ -764,36 +764,38 @@ (defn- remove-shape [shape container] - (let [page-id (when (cph/is-page container) (:id container)) - component-id (when (cph/is-component container) (:id container)) - - objects (get container :objects) + (let [objects (get container :objects) parents (cph/get-parents (:id shape) objects) children (cph/get-children (:id shape) objects) add-change (fn [id] (let [shape' (get objects id)] - (d/without-nils {:type :add-obj - :id id - :page-id page-id - :component-id component-id - :index (cph/position-on-parent id objects) - :frame-id (:frame-id shape') - :parent-id (:parent-id shape') - :obj shape'}))) + (as-> {:type :add-obj + :id id + :index (cph/position-on-parent id objects) + :parent-id (:parent-id shape') + :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)))))) - rchanges [(d/without-nils {:type :del-obj - :page-id page-id - :component-id component-id - :id (:id shape)})] + 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) - [(d/without-nils {:type :reg-objects - :page-id page-id - :component-id component-id - :shapes (vec parents)})])] + [(as-> {:type :reg-objects + :shapes (vec parents)} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))])] [rchanges uchanges])) (defn- move-shape @@ -804,102 +806,102 @@ index-before " -> " index-after)) - (let [page-id (when (cph/is-page container) (:id container)) - component-id (when (cph/is-component container) (:id container))] - (let [rchanges [(d/without-nils {:type :mov-objects - :parent-id (:parent-id shape) - :shapes [(:id shape)] - :index index-after - :page-id page-id - :component-id component-id})] - uchanges [(d/without-nils {:type :mov-objects - :parent-id (:parent-id shape) - :shapes [(:id shape)] - :index index-before - :page-id page-id - :component-id component-id})]] - [rchanges uchanges]))) + (let [rchanges [(as-> {:type :mov-objects + :parent-id (:parent-id shape) + :shapes [(:id shape)] + :index index-after} $ + (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} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))]] + [rchanges uchanges])) (defn- remove-component-and-ref [shape container] - (let [page-id (when (cph/is-page container) (:id container)) - component-id (when (cph/is-component container) (:id container))] - [[(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :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}]})] - [(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :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)}]})]])) + [[(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))))]]) (defn- remove-ref [shape container] - (let [page-id (when (cph/is-page container) (:id container)) - component-id (when (cph/is-component container) (:id container))] - [[(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :operations [{:type :set - :attr :shape-ref - :val nil} - {:type :set-touched - :touched nil}]})] - [(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :operations [{:type :set - :attr :shape-ref - :val (:shape-ref shape)} - {:type :set-touched - :touched (:touched 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- reset-touched [shape container] - (let [page-id (when (cph/is-page container) (:id container)) - component-id (when (cph/is-component container) (:id container))] - [[(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :operations [{:type :set-touched - :touched nil}]})] - [(d/without-nils {:type :mod-obj - :id (:id shape) - :page-id page-id - :component-id component-id - :operations [{:type :set-touched - :touched (:touched 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))))]]) (defn- update-attrs "The main function that implements the sync algorithm. Copy @@ -920,13 +922,10 @@ (log/info :msg (str "SYNC " (:name origin-shape) " -> " - (if (cph/is-page container) "[P] " "[C] ") + (if (cph/page? container) "[P] " "[C] ") (:name dest-shape))) - (let [page-id (when (cph/is-page container) (:id container)) - component-id (when (cph/is-component container) (:id container)) - - ; The position attributes need a special sync algorith, because we do + (let [; The position attributes need a special sync algorith, because we do ; not synchronize the absolute position, but the position relative of ; the container shape of the component. new-pos (calc-new-pos dest-shape origin-shape dest-root origin-root) @@ -966,16 +965,18 @@ :else uoperations) - rchanges [(d/without-nils {:type :mod-obj - :id (:id dest-shape) - :page-id page-id - :component-id component-id - :operations roperations})] - uchanges [(d/without-nils {:type :mod-obj - :id (:id dest-shape) - :page-id page-id - :component-id component-id - :operations uoperations})]] + rchanges [(as-> {:type :mod-obj + :id (:id dest-shape) + :operations roperations} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))] + uchanges [(as-> {:type :mod-obj + :id (:id dest-shape) + :operations uoperations} $ + (if (cph/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))]] [rchanges uchanges]) (if-not (contains? dest-shape attr) From 830d932eece395f4c21b517cf515d28ac3520779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 19 Nov 2020 11:59:20 +0100 Subject: [PATCH 3/5] :recycle: Add more debugging traces --- .../app/main/data/workspace/libraries_helpers.cljs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 46a70c2d5..3a6241d4b 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -560,7 +560,6 @@ ; ---- 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 [])) @@ -764,6 +763,9 @@ (defn- remove-shape [shape container] + (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) children (cph/get-children (:id shape) objects) @@ -801,6 +803,7 @@ (defn- move-shape [shape index-before index-after container] (log/info :msg (str "MOVE " + (if (cph/page? container) "[P] " "[C] ") (:name shape) " " index-before @@ -824,6 +827,9 @@ (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 @@ -865,6 +871,9 @@ (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 @@ -888,6 +897,9 @@ (defn- reset-touched [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 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 4/5] :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 1d4e76fb3..0188a1eab 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 ecc0dd463..8c2f1e561 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 f75a89a67..fbb8c9e3a 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 77d987970..3eb8f60db 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 3a6241d4b..bd6608a3b 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 From 02b3c6ac284782c532a146690585dcee0c6ed42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 25 Nov 2020 13:44:06 +0100 Subject: [PATCH 5/5] :bug: Ignore touched when duplicating shape --- frontend/src/app/main/data/workspace/selection.cljs | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index f2ef8af36..2d33d9e54 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -330,6 +330,7 @@ :old-id (:id obj) :frame-id frame-id :parent-id parent-id + :ignore-touched true :obj (dissoc reframed-obj :shapes)}] children-changes)))