From fe7faf0d0dc56d235aa6dd16fa4a97bcdef14501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 13 Jan 2021 15:11:47 +0100 Subject: [PATCH] :sparkles: Rework changes detection --- common/app/common/pages/changes.cljc | 32 +- common/app/common/pages/spec.cljc | 5 + .../app/main/data/workspace/libraries.cljs | 9 +- .../data/workspace/libraries_helpers.cljs | 397 +++++++++++------- frontend/src/app/main/store.cljs | 8 +- 5 files changed, 296 insertions(+), 155 deletions(-) diff --git a/common/app/common/pages/changes.cljc b/common/app/common/pages/changes.cljc index fbbfcdeab..c88eee553 100644 --- a/common/app/common/pages/changes.cljc +++ b/common/app/common/pages/changes.cljc @@ -77,8 +77,10 @@ (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))) + (-> + (update-in [:objects parent-id :touched] + cph/set-touched-group :shapes-group) + (d/dissoc-in [:objects parent-id :remote-synced?])))) data)))] (if page-id (d/update-in-when data [:pages-index page-id] update-fn) @@ -110,7 +112,9 @@ (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) + (-> + (update-in [parent-id :touched] cph/set-touched-group :shapes-group) + (d/dissoc-in [parent-id :remote-synced?])) (contains? objects frame-id) (update-in [frame-id :shapes] (fn [s] (filterv #(not= % id) s))) @@ -185,7 +189,9 @@ (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))) + (-> + (update :touched cph/set-touched-group :shapes-group) + (dissoc :remote-synced?)))) (remove-from-old-parent [cpindex objects shape-id] (let [prev-parent-id (get cpindex shape-id)] @@ -210,8 +216,10 @@ (and (:shape-ref obj) (= (:type obj) :group) (not ignore-touched)) - (update-in [pid :touched] - cph/set-touched-group :shapes-group)))))))) + (-> + (update-in [pid :touched] + cph/set-touched-group :shapes-group) + (d/dissoc-in [pid :remote-synced?]))))))))) (update-parent-id [objects id] (update objects id assoc :parent-id parent-id)) @@ -392,7 +400,9 @@ ;; touched). For the moment we disable geometry touched ;; except width and height that seems to work well. (or (not= group :geometry-group) (#{:width :height} attr))) - (update :touched cph/set-touched-group group) + (-> + (update :touched cph/set-touched-group group) + (dissoc :remote-synced?)) (nil? val) (dissoc attr) @@ -408,6 +418,14 @@ (dissoc shape :touched) (assoc shape :touched touched)))) +(defmethod process-operation :set-remote-synced + [shape op] + (let [remote-synced? (:remote-synced? op) + shape-ref (:shape-ref shape)] + (if (or (nil? shape-ref) (not remote-synced?)) + (dissoc shape :remote-synced?) + (assoc shape :remote-synced? true)))) + (defmethod process-operation :default [_ op] (ex/raise :type :not-implemented diff --git a/common/app/common/pages/spec.cljc b/common/app/common/pages/spec.cljc index cbb2d8e8d..25dc321f0 100644 --- a/common/app/common/pages/spec.cljc +++ b/common/app/common/pages/spec.cljc @@ -427,6 +427,8 @@ (s/def :internal.operations.set/val any?) (s/def :internal.operations.set/touched (s/nilable (s/every keyword? :kind set?))) +(s/def :internal.operations.set/remote-synced? + (s/nilable boolean?)) (defmethod operation-spec :set [_] (s/keys :req-un [:internal.operations.set/attr @@ -435,6 +437,9 @@ (defmethod operation-spec :set-touched [_] (s/keys :req-un [:internal.operations.set/touched])) +(defmethod operation-spec :set-remote-synced [_] + (s/keys :req-un [:internal.operations.set/remote-synced?])) + (defmulti change-spec :type) (s/def :internal.changes.set-option/option any?) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 872043e82..f54b66284 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -384,7 +384,8 @@ (geom/move $ delta) (assoc $ :frame-id frame-id) (assoc $ :parent-id - (or (:parent-id $) (:frame-id $)))) + (or (:parent-id $) (:frame-id $))) + (dissoc $ :touched)) (nil? (:shape-ref original-shape)) (assoc :shape-ref (:id original-shape)) @@ -448,6 +449,9 @@ {:type :set :attr :component-root? :val nil} + {:type :set + :attr :remote-synced? + :val nil} {:type :set :attr :shape-ref :val nil} @@ -469,6 +473,9 @@ {:type :set :attr :component-root? :val (:component-root? obj)} + {:type :set + :attr :remote-synced? + :val (:remote-synced? obj)} {:type :set :attr :shape-ref :val (:shape-ref obj)} diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index a6fcddfe1..cc6746481 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -10,6 +10,7 @@ (ns app.main.data.workspace.libraries-helpers (:require [cljs.spec.alpha :as s] + [clojure.set :as set] [app.common.spec :as us] [app.common.data :as d] [app.common.geom.point :as gpt] @@ -48,6 +49,7 @@ (declare remove-shape) (declare move-shape) (declare change-touched) +(declare change-remote-synced) (declare update-attrs) (declare reposition-shape) @@ -122,7 +124,7 @@ (defn generate-sync-file "Generate changes to synchronize all shapes in all pages of the current file, - with the given asset of the given library." + that use assets of the given type in the given library." [asset-type library-id state] (s/assert #{:colors :components :typographies} asset-type) (s/assert ::us/uuid library-id) @@ -154,8 +156,8 @@ [rchanges uchanges]))))) (defn generate-sync-library - "Generate changes to synchronize all shapes inside components of the current - file library, that use the given type of asset of the given library." + "Generate changes to synchronize all shapes in all components of the current + file library, that use assets of the given type in the given library." [asset-type library-id state] (log/info :msg "Sync local components with library" @@ -185,8 +187,8 @@ [rchanges uchanges]))))) (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." + "Generate changes to synchronize all shapes in a particular container (a page + or a component) that use assets of the given type in the given library." [asset-type library-id state container] (if (cp/page? container) @@ -250,9 +252,9 @@ (= library-id (:typography-ref-file %))))))))) (defmulti generate-sync-shape - "Generate changes to synchronize one shape, that use the given type - of asset of the given library." - (fn [type _ _ _ _] type)) + "Generate changes to synchronize one shape with all assets of the given type + that is using, in the given library." + (fn [type library-id state container shape] type)) (defmethod generate-sync-shape :components [_ library-id state container shape] @@ -353,34 +355,129 @@ node))] (generate-sync-text-shape shape container update-node))) - -;; ---- Component synchronization helpers ---- - (defn- get-assets [library-id asset-type state] (if (= library-id (:current-file-id state)) (get-in state [:workspace-data asset-type]) (get-in state [:workspace-libraries library-id :data asset-type]))) + +;; ---- Component synchronization helpers ---- + +;; Three sources of component synchronization: +;; +;; - NORMAL SYNC: when a component is updated, any shape that use it, +;; must be synchronized. All attributes that have changed in the +;; component and whose attr group has not been "touched" in the dest +;; shape are copied. +;; +;; generate-sync-shape-direct (reset = false) +;; +;; - FORCED SYNC: when the "reset" command is applied to some shape, +;; all attributes that have changed in the component are copied, and +;; the "touched" flags are cleared. +;; +;; generate-sync-shape-direct (reset = true) +;; +;; - INVERSE SYNC: when the "update component" command is used in some +;; shape, all the attributes that have changed in the shape are copied +;; into the linked component. The "touched" flags are also cleared in +;; the origin shape. +;; +;; generate-sync-shape-inverse +;; +;; The initial shape is always a group (a root instance), so all the +;; children are recursively synced, too. A root instance is a group shape +;; that has the "component-id" attribute and also "component-root?" is true. +;; +;; The children lists of the instance and the component shapes are compared +;; side-by-side. Any new, deleted or moved child modifies (and "touches") +;; the parent shape. +;; +;; When a shape inside a component is in turn an instance of another +;; component, the synchronization is more complex: +;; +;; [Page] +;; Instance-2 #--> Component-2 (#--> = root instance) +;; IShape-2-1 --> Shape-2-1 (@--> = nested instance) +;; Subinstance-2-2 @--> Component-1 ( --> = shape ref) +;; IShape-2-2-1 --> Shape-1-1 +;; +;; [Component-1] +;; Component-1 +;; Shape-1-1 +;; +;; [Component-2] +;; Component-2 +;; Shape-2-1 +;; Subcomponent-2-2 @--> Component-1 +;; Shape-2-2-1 --> Shape-1-1 +;; +;; * A SUBINSTANCE ACTUALLY HAS TWO MASTERS. For example IShape-2-2-1 +;; depends on Shape-2-2-1 (in the "near" component) but also on +;; Shape-1-1-1 (in the "remote" component). The "shape-ref" attribute +;; always refer to the remote shape, and it's guaranteed that it's +;; always a final shape, not an instance. The relationship between the +;; shape and the near shape is that both point to the same remote. +;; +;; * THE INITIAL VALUE of IShape-2-2-1 comes from the near component +;; Shape-2-2-1 (although the shape-ref attribute points to the direct +;; component Shape-1-1). The touched flags of IShape-2-2-1 start +;; cleared at first, and activate on any attribute change onwards. +;; +;; * IN A NORMAL SYNC, the sync process starts in the root instance and +;; continues recursively with the children of the root instance and +;; the component. Therefore, IShape-2-2-1 is synced with Shape-2-2-1. +;; +;; * IN A FORCED SYNC, IF THE INITIAL SHAPE IS THE ROOT INSTANCE, the +;; order is the same, and IShape-2-2-1 is reset from Shape-2-2-1 and +;; marked as not touched. +;; +;; * IF THE INITIAL SHAPE IS THE SUBINSTANCE, the sync is done against +;; the remote component. Therefore, IShape-2-2-1 is synched with +;; Shape-1-1. Then the "touched" flags are reset, and the +;; "remote-synced?" flag is set (it will be set until the shape is +;; touched again or it's synced forced normal or inverse with the +;; near component). +;; +;; * IN AN INVERSE SYNC, IF THE INITIAL SHAPE IS THE ROOT INSTANCE, the +;; order is the same as in the normal sync. Therefore, IShape-2-2-1 +;; values are copied into Shape-2-2-1, and then its touched flags are +;; cleared. Then, the "touched" flags THAT ARE TRUE are copied to +;; Shape-2-2-1. This may cause that Shape-2-2-1 is now touched respect +;; to Shape-1-1, and so, some attributes are not copied in a subsequent +;; normal sync. Or, if "remote-synced?" flag is set in IShape-2-2-1, +;; all touched flags are cleared in Shape-2-2-1 and "remote-synced?" +;; is removed. +;; +;; * IN AN INVERSE SYNC INITIATED IN THE SUBINSTANCE, the update is done +;; to the remote component. E.g. IShape-2-2-1 attributes are copied into +;; Shape-1-1, and then touched cleared and "remote-synced?" flag set. +;; +;; #### WARNING: there are two conditions that are invisible to user: +;; - When the near shape (Shape-2-2-1) is touched respect the remote +;; one (Shape-1-1), there is no asterisk displayed anywhere. +;; - When the instance shape (IShape-2-2-1) is synced with the remote +;; shape (remote-synced? = true), the user will see that this shape +;; is different than the one in the near component (Shape-2-2-1) +;; but it's not touched. + (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 - changed, and whose group has not been touched in the instance shape will - 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." + instance, and all its children, from the given component." [container shape-id local-library libraries reset?] (log/debug :msg "Sync shape direct" :shape (str shape-id) :reset? reset?) - (let [shape-inst (cp/get-shape container shape-id) - component (cp/get-component (:component-id shape-inst) - (:component-file shape-inst) - local-library - libraries) - shape-master (cp/get-shape component (:shape-ref shape-inst)) + (let [shape-inst (cp/get-shape container shape-id) + component (cp/get-component (:component-id shape-inst) + (:component-file shape-inst) + local-library + libraries) + shape-master (cp/get-shape component (:shape-ref shape-inst)) - root-inst shape-inst - root-master (cp/get-component-root component)] + initial-root? (:component-root? shape-inst) + + root-inst shape-inst + root-master (cp/get-component-root component)] (generate-sync-shape-direct-recursive container shape-inst @@ -388,33 +485,41 @@ shape-master root-inst root-master - {:omit-touched? (not reset?) - :reset-touched? reset? - :copy-touched? false}))) + reset? + initial-root?))) (defn- generate-sync-shape-direct-recursive - [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" + [container shape-inst component shape-master root-inst root-master reset? initial-root?] + (log/debug :msg "Sync shape direct recursive" :shape (str (:name shape-inst)) - :component (:name component) - :options options) + :component (:name component)) - (let [[rchanges uchanges] + (let [omit-touched? (not reset?) + clear-remote-synced? (and initial-root? reset?) + set-remote-synced? (and (not initial-root?) reset?) + + [rchanges uchanges] (concat-changes (update-attrs shape-inst shape-master root-inst root-master container - options) - (change-touched shape-inst - shape-master - container - options)) + omit-touched?) + (concat-changes + (if reset? + (change-touched shape-inst + shape-master + container + {:reset-touched? true}) + empty-changes) + (concat-changes + (if clear-remote-synced? + (change-remote-synced shape-inst container nil) + empty-changes) + (if set-remote-synced? + (change-remote-synced shape-inst container true) + empty-changes)))) children-inst (mapv #(cp/get-shape container %) (:shapes shape-inst)) @@ -432,18 +537,12 @@ container root-inst root-master - omit-touched?)) + omit-touched? + set-remote-synced?)) both (fn [child-inst child-master] (let [sub-root? (and (:component-id shape-inst) - (not (:component-root? shape-inst))) - - options (if-not sub-root? - options - {:omit-touched? true - :reset-touched? false - :copy-touched? false})] - + (not (:component-root? shape-inst)))] (generate-sync-shape-direct-recursive container child-inst component @@ -454,7 +553,8 @@ (if sub-root? shape-master root-master) - options))) + reset? + initial-root?))) moved (fn [shape-inst shape-master] (move-shape @@ -478,24 +578,21 @@ (defn- generate-sync-shape-inverse "Generate changes to update the component a shape is linked to, from - the values in the shape and all its children. - All atributes of each instance shape that have changed, will be copied - to the component shape. Also clears the 'touched' flags in the source - shapes. - And if the component shapes are, in turn, instances of a second component, - their 'touched' flags will be set accordingly." + the values in the shape and all its children." [page-id shape-id local-library libraries] (log/debug :msg "Sync shape inverse" :shape (str shape-id)) - (let [container (cp/get-container page-id :page local-library) - shape-inst (cp/get-shape container shape-id) - component (cp/get-component (:component-id shape-inst) - (:component-file shape-inst) - local-library - libraries) - shape-master (cp/get-shape component (:shape-ref shape-inst)) + (let [container (cp/get-container page-id :page local-library) + shape-inst (cp/get-shape container shape-id) + component (cp/get-component (:component-id shape-inst) + (:component-file shape-inst) + local-library + libraries) + shape-master (cp/get-shape component (:shape-ref shape-inst)) - root-inst shape-inst - root-master (cp/get-component-root component)] + initial-root? (:component-root? shape-inst) + + root-inst shape-inst + root-master (cp/get-component-root component)] (generate-sync-shape-inverse-recursive container shape-inst @@ -503,22 +600,19 @@ shape-master root-inst root-master - {:reset-touched? false - :set-touched? true - :copy-touched? false}))) + initial-root?))) (defn- generate-sync-shape-inverse-recursive - [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}}] + [container shape-inst component shape-master root-inst root-master initial-root?] (log/trace :msg "Sync shape inverse recursive" :shape (str (:name shape-inst)) - :component (:name component) - :options options) + :component (:name component)) - (let [component-container (cp/make-container component :component) + (let [component-container (cp/make-container component :component) + + omit-touched? false + set-remote-synced? (not initial-root?) + clear-remote-synced? initial-root? [rchanges uchanges] (concat-changes @@ -527,15 +621,24 @@ root-master root-inst component-container - options) + omit-touched?) (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))) + (change-touched shape-inst + shape-master + container + {:reset-touched? true}) + (concat-changes + (change-touched shape-master + shape-inst + component-container + {:copy-touched? true}) + (concat-changes + (if clear-remote-synced? + (change-remote-synced shape-inst container nil) + empty-changes) + (if set-remote-synced? + (change-remote-synced shape-inst container true) + empty-changes))))) children-inst (mapv #(cp/get-shape container %) (:shapes shape-inst)) @@ -556,13 +659,7 @@ both (fn [child-inst child-master] (let [sub-root? (and (:component-id shape-inst) - (not (:component-root? shape-inst))) - - options (if-not sub-root? - options - {:reset-touched? false - :set-touched? false - :copy-touched? true})] + (not (:component-root? shape-inst)))] (generate-sync-shape-inverse-recursive container child-inst @@ -574,7 +671,7 @@ (if sub-root? shape-master root-master) - options))) + initial-root?))) moved (fn [shape-inst shape-master] (move-shape @@ -660,14 +757,15 @@ (concat-changes (moved-cb child-inst' child-master)))))))))))) (defn- add-shape-to-instance - [component-shape component container root-instance root-master omit-touched?] + [component-shape component container root-instance root-master omit-touched? set-remote-synced?] (log/info :msg (str "ADD [P] " (:name component-shape))) (let [component-parent-shape (cp/get-shape component (:parent-id component-shape)) parent-shape (d/seek #(cp/is-master-of component-parent-shape %) (cp/get-object-with-children (:id root-instance) (:objects container))) all-parents (vec (cons (:id parent-shape) - (cp/get-parents parent-shape (:objects container)))) + (cp/get-parents (:id parent-shape) + (:objects container)))) update-new-shape (fn [new-shape original-shape] (let [new-shape (reposition-shape new-shape @@ -678,7 +776,10 @@ (assoc :frame-id (:frame-id parent-shape)) (nil? (:shape-ref original-shape)) - (assoc :shape-ref (:id original-shape))))) + (assoc :shape-ref (:id original-shape)) + + set-remote-synced? + (assoc :remote-synced? true)))) update-original-shape (fn [original-shape new-shape] original-shape) @@ -732,7 +833,8 @@ (cp/get-object-with-children (:id root-master) (:objects component))) all-parents (vec (cons (:id component-parent-shape) - (cp/get-parents component-parent-shape (:objects component)))) + (cp/get-parents (:id component-parent-shape) + (:objects component)))) update-new-shape (fn [new-shape original-shape] (reposition-shape new-shape @@ -873,10 +975,8 @@ [rchanges uchanges]))) (defn- change-touched - [dest-shape orig-shape container - {:keys [reset-touched? copy-touched?] - :as options :or {reset-touched? false - copy-touched? false}}] + [dest-shape origin-shape container + {:keys [reset-touched? copy-touched?] :as options}] (if (or (nil? (:shape-ref dest-shape)) (not (or reset-touched? copy-touched?))) empty-changes @@ -890,10 +990,15 @@ :operations [{:type :set-touched :touched - (cond reset-touched? - nil - copy-touched? - (:touched orig-shape))}]} $ + (cond + reset-touched? + nil + copy-touched? + (if (:remote-synced? origin-shape) + nil + (set/union + (:touched dest-shape) + (:touched origin-shape))))}]} $ (if (cp/page? container) (assoc $ :page-id (:id container)) (assoc $ :component-id (:id container))))] @@ -908,6 +1013,34 @@ (assoc $ :component-id (:id container))))]] [rchanges uchanges])))) +(defn- change-remote-synced + [shape container remote-synced?] + (if (nil? (:shape-ref shape)) + empty-changes + (do + (log/info :msg (str "CHANGE-REMOTE-SYNCED? " + (if (cp/page? container) "[P] " "[C] ") + (:name shape)) + :remote-synced? remote-synced?) + (let [rchanges [(as-> {:type :mod-obj + :id (:id shape) + :operations + [{:type :set-remote-synced + :remote-synced? remote-synced?}]} $ + (if (cp/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))] + + uchanges [(as-> {:type :mod-obj + :id (:id shape) + :operations + [{:type :set-remote-synced + :remote-synced? (:remote-synced? shape)}]} $ + (if (cp/page? container) + (assoc $ :page-id (:id container)) + (assoc $ :component-id (:id container))))]] + [rchanges uchanges])))) + (defn- set-touched-shapes-group [shape container] (if-not (:shape-ref shape) @@ -938,22 +1071,12 @@ [rchanges uchanges])))) (defn- update-attrs - "The main function that implements the sync algorithm. Copy + "The main function that implements the attribute sync algorithm. Copy attributes that have changed in the origin shape to the dest shape. + If omit-touched? is true, attributes whose group has been touched - in the destination shape will be ignored. - 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. - 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 - reset-touched? false - set-touched? false - copy-touched? false}}] + in the destination shape will not be copied." + [dest-shape origin-shape dest-root origin-root container omit-touched?] (log/info :msg (str "SYNC " (:name origin-shape) @@ -975,27 +1098,8 @@ (let [attr (first attrs)] (if (nil? attr) - (let [roperations (cond - reset-touched? - (conj roperations - {:type :set-touched - :touched nil}) - copy-touched? - (conj roperations - {:type :set-touched - :touched (:touched origin-shape)}) - :else - roperations) - - uoperations (cond - (or reset-touched? copy-touched?) - (conj uoperations - {:type :set-touched - :touched (:touched dest-shape)}) - :else - uoperations) - - all-parents (or (cp/get-parents dest-shape (:objects container)) []) + (let [all-parents (vec (or (cp/get-parents (:id dest-shape) + (:objects container)) [])) rchanges [(as-> {:type :mod-obj :id (:id dest-shape) @@ -1019,19 +1123,22 @@ (if (cp/page? container) (assoc $ :page-id (:id container)) (assoc $ :component-id (:id container))))]] - [rchanges uchanges]) + (if (seq roperations) + [rchanges uchanges] + empty-changes)) (let [roperation {:type :set :attr attr :val (get origin-shape attr) - :ignore-touched (not set-touched?)} + :ignore-touched true} uoperation {:type :set :attr attr :val (get dest-shape attr) - :ignore-touched (not set-touched?)} + :ignore-touched true} attr-group (get cp/component-sync-attrs attr)] - (if (and (touched attr-group) omit-touched?) + (if (or (= (get origin-shape attr) (get dest-shape attr)) + (and (touched attr-group) omit-touched?)) (recur (next attrs) roperations uoperations) diff --git a/frontend/src/app/main/store.cljs b/frontend/src/app/main/store.cljs index abcb6fbb3..18a30c99e 100644 --- a/frontend/src/app/main/store.cljs +++ b/frontend/src/app/main/store.cljs @@ -94,10 +94,14 @@ {:length 20 :type :right}) (show-component shape objects)) - (when (and show-touched (seq (:touched shape))) - (println (str (str/repeat " " level) + (when show-touched + (when (seq (:touched shape)) + (println (str (str/repeat " " level) " " (str (:touched shape))))) + (when (:remote-synced? shape) + (println (str (str/repeat " " level) + " (remote-synced)")))) (when (:shapes shape) (dorun (for [shape-id (:shapes shape)] (show-shape shape-id (inc level) objects))))))