diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index 05ee777612..c1e87dc26f 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -20,9 +20,10 @@ (update data :pages-index #(d/mapm f %))) (defn select-objects - "Get a list of all objects in a page that satisfy a condition" - [f page] - (filter f (vals (get page :objects)))) + "Get a list of all objects in a container (a page or a component) that + satisfy a condition" + [f container] + (filter f (vals (get container :objects)))) (defn update-object-list "Update multiple objects in a page at once" diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index a9e441b65c..21807df1f4 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -450,12 +450,14 @@ ptk/WatchEvent (watch [_ state stream] - (let [[rchanges1 uchanges1] (dwlh/generate-sync-file-components state file-id) - [rchanges2 uchanges2] (dwlh/generate-sync-library-components state file-id) + (let [[rchanges1 uchanges1] (dwlh/generate-sync-file :components file-id state) + [rchanges2 uchanges2] (dwlh/generate-sync-library :components file-id state) [rchanges3 uchanges3] (dwlh/generate-sync-file :colors file-id state) - [rchanges4 uchanges4] (dwlh/generate-sync-file :typographies file-id state) - rchanges (d/concat rchanges1 rchanges2 rchanges3 rchanges4) - uchanges (d/concat uchanges1 uchanges2 uchanges3 uchanges4)] + [rchanges4 uchanges4] (dwlh/generate-sync-library :colors file-id state) + [rchanges5 uchanges5] (dwlh/generate-sync-file :typographies file-id state) + [rchanges6 uchanges6] (dwlh/generate-sync-library :typographies file-id state) + rchanges (d/concat rchanges1 rchanges2 rchanges3 rchanges4 rchanges5 rchanges6) + uchanges (d/concat uchanges1 uchanges2 uchanges3 uchanges4 uchanges5 uchanges6)] (rx/concat (rx/of (dm/hide-tag :sync-dialog)) (when rchanges @@ -464,7 +466,7 @@ (rp/mutation :update-sync {:file-id (get-in state [:workspace-file :id]) :library-id file-id})) - (when (seq rchanges2) + (when (or (seq rchanges2) (seq rchanges4) (seq rchanges6)) (rx/of (sync-file-2nd-stage file-id)))))))) (defn sync-file-2nd-stage @@ -481,7 +483,7 @@ (ptk/reify ::sync-file-2nd-stage ptk/WatchEvent (watch [_ state stream] - (let [[rchanges uchanges] (dwlh/generate-sync-file-components state nil)] + (let [[rchanges uchanges] (dwlh/generate-sync-file :components nil state)] (when 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 ad4ac4d712..6c275cf95f 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -19,7 +19,7 @@ (defonce empty-changes [[] []]) -(declare generate-sync-page) +(declare generate-sync-container) (declare generate-sync-shape) (declare generate-sync-component-components) @@ -30,9 +30,11 @@ (declare update-attrs) (declare calc-new-pos) +;; ---- General library synchronization functions ---- + (defn generate-sync-file - "Generic method that given a type of asset will iterate through the file pages - and call synchronize" + "Generate changes to synchronize all shapes in all pages of the current file, + with the given asset of the given library." [asset-type library-id state] (s/assert #{:colors :components :typographies} asset-type) @@ -51,13 +53,47 @@ uchanges []] (if-let [page (first pages)] (let [[page-rchanges page-uchanges] - (generate-sync-page asset-type library-id library-items page)] + (generate-sync-container asset-type + library-id + library-items + page + (:id page) + nil)] (recur (next pages) (d/concat rchanges page-rchanges) (d/concat uchanges page-uchanges))) [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." + [asset-type library-id state] + (let [library-items + (if (nil? library-id) + (get-in state [:workspace-data asset-type]) + (get-in state [:workspace-libraries library-id :data asset-type]))] + (if (empty? library-items) + empty-changes + + (loop [local-components (seq (vals (get-in state [:workspace-data :components]))) + rchanges [] + uchanges []] + (if-let [local-component (first local-components)] + (let [[comp-rchanges comp-uchanges] + (generate-sync-container asset-type + library-id + library-items + local-component + nil + (:id local-component))] + (recur (next local-components) + (d/concat rchanges comp-rchanges) + (d/concat uchanges comp-uchanges))) + [rchanges uchanges]))))) + (defn has-asset-reference-fn + "Gets a function that checks if a shape uses some asset of the given type + in the given library." [asset-type library-id] (case asset-type :components @@ -83,35 +119,54 @@ #(and (some? (:typography-ref-id %)) (= library-id (:typography-ref-file %))))))))) -(defn generate-sync-page - [asset-type library-id library-items page] +(defn generate-sync-container + "Generate changes to synchronize all shapes in a particular container + (a page or a component)." + [asset-type library-id library-items container page-id component-id] (let [has-asset-reference? (has-asset-reference-fn asset-type library-id) - linked-shapes (cph/select-objects has-asset-reference? page)] + linked-shapes (cph/select-objects has-asset-reference? container)] (loop [shapes (seq linked-shapes) rchanges [] uchanges []] (if-let [shape (first shapes)] (let [[shape-rchanges shape-uchanges] - (generate-sync-shape asset-type library-id library-items page shape)] + (generate-sync-shape asset-type + library-id + library-items + (get container :objects) + page-id + component-id + shape)] (recur (next shapes) (d/concat rchanges shape-rchanges) (d/concat uchanges shape-uchanges))) [rchanges uchanges])))) -(defmulti generate-sync-shape (fn [type _ _ _ _ _] type)) +(defmulti generate-sync-shape (fn [type _ _ _ _ _ _ _] type)) (defmethod generate-sync-shape :components - [_ library-id library-items page shape] + [_ library-id library-items objects page-id component-id shape] + + ;; Synchronize a shape that is the root instance of a component, and all of its + ;; children. All attributes of the component shape that have changed, and whose + ;; group have not been touched in the linked shape, will be copied to the shape. + ;; Any shape that is linked to a no-longer existent component shape will be + ;; detached. (let [root-shape shape - objects (:objects page) components library-items - page-id nil - component-id (:id page) reset-touched? false] - (generate-sync-shape-and-children-components root-shape objects components page-id component-id reset-touched?))) + (generate-sync-shape-and-children-components root-shape + objects + components + page-id + component-id + reset-touched?))) (defmethod generate-sync-shape :colors - [_ library-id library-items page shape] + [_ library-id library-items _ page-id component-id shape] + + ;; Synchronize a shape that uses some colors of the library. The value of the + ;; color in the library is copied to the shape. (loop [attrs (seq cp/color-sync-attrs) roperations [] uoperations []] @@ -119,14 +174,16 @@ (if (nil? attr) (if (empty? roperations) empty-changes - (let [rchanges [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations roperations}] - uchanges [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations uoperations}]] + (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})]] [rchanges uchanges])) (let [attr-ref-id (keyword (str (name attr) "-ref-id"))] (if-not (contains? shape attr-ref-id) @@ -147,29 +204,35 @@ (conj uoperations uoperation))))))))) (defmethod generate-sync-shape :typographies - [_ library-id library-items page shape] + [_ library-id library-items _ page-id component-id shape] + + ;; Synchronize a shape that uses some typographies of the library. The attributes + ;; of the typography are copied to the shape." (let [update-node (fn [node] (if-let [typography (get library-items (:typography-ref-id node))] (merge node (d/without-keys typography [:name :id])) node)) old-content (:content shape) new-content (ut/map-node update-node old-content) - rchanges [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations [{:type :set - :attr :content - :val new-content}]}] - lchanges [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations [{:type :set - :attr :content - :val 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}]})]] (if (= new-content old-content) empty-changes [rchanges lchanges]))) + ;; ---- Create a new component ---- (defn make-component-shape @@ -199,118 +262,14 @@ (cph/clone-object shape nil objects update-new-shape update-original-shape))) -;; ---- Synchronize shapes with components - -(declare generate-sync-page-components) - -(defn generate-sync-file-components - "Generate changes to synchronize all shapes in current file that are linked - to some component in the given library. All attributes of the components - that have changed, and whose group have not been touched in the linked shape, - will be copied to the shape. Any shape that is linked to a no-longer - existent component will be detached." - [state library-id] - (let [components - (if (nil? library-id) - (get-in state [:workspace-data :components]) - (get-in state [:workspace-libraries library-id :data :components]))] - (if (nil? components) - [[] []] - (loop [pages (seq (vals (get-in state [:workspace-data :pages-index]))) - rchanges [] - uchanges []] - (let [page (first pages)] - (if (nil? page) - [rchanges uchanges] - (let [[page-rchanges page-uchanges] - (generate-sync-page-components page library-id components)] - (recur (next pages) - (d/concat rchanges page-rchanges) - (d/concat uchanges page-uchanges))))))))) - - -(defn generate-sync-page-components - "Generate changes to synchronize all shapes in a particular page. - Same considerations as above." - [page library-id components] - (let [objects (get page :objects) - linked-shapes (cph/select-objects #(and (some? (:component-id %)) - (= (:component-file %) library-id)) - page)] - (loop [shapes (seq linked-shapes) - rchanges [] - uchanges []] - (let [shape (first shapes)] - (if (nil? shape) - [rchanges uchanges] - (let [[shape-rchanges shape-uchanges] - (generate-sync-shape-and-children-components shape - objects - components - (:id page) - nil - false)] - (recur (next shapes) - (d/concat rchanges shape-rchanges) - (d/concat uchanges shape-uchanges)))))))) - - -(defn generate-sync-library-components - "Generate changes to synchronize all shapes inside components of the current - file library, that are linked to other component in the given library. - Same considerations as above." - [state library-id] - (let [components - (if (nil? library-id) - (get-in state [:workspace-data :components]) - (get-in state [:workspace-libraries library-id :data :components]))] - (if (nil? components) - empty-changes - (loop [local-components (seq (vals (get-in state [:workspace-data :components]))) - rchanges [] - uchanges []] - (let [local-component (first local-components)] - (if (nil? local-component) - [rchanges uchanges] - (let [[comp-rchanges comp-uchanges] - (generate-sync-component-components - local-component library-id components)] - (recur (next local-components) - (d/concat rchanges comp-rchanges) - (d/concat uchanges comp-uchanges))))))))) - - -(defn generate-sync-component-components - "Generate changes to synchronize all shapes in a particular component. - Same considerations as above." - [local-component library-id components] - (let [objects (get local-component :objects) - linked-shapes (filter #(and (some? (:component-id %)) - (= (:component-file %) library-id)) - (vals objects))] - (loop [shapes (seq linked-shapes) - rchanges [] - uchanges []] - (let [shape (first shapes)] - (if (nil? shape) - [rchanges uchanges] - (let [[shape-rchanges shape-uchanges] - (generate-sync-shape-and-children-components shape - objects - components - nil - (:id local-component) - false)] - (recur (next shapes) - (d/concat rchanges shape-rchanges) - (d/concat uchanges shape-uchanges)))))))) - +;; ---- Component synchronization helpers ---- (defn generate-sync-shape-and-children-components "Generate changes to synchronize one shape that is linked to a component, and all its children. If reset-touched? is false, same considerations as - above. If it's true, all attributes of the component that have changed - will be copied, and the 'touched' flags in the shapes will be cleared." + in generate-sync-shape :components. If it's true, all attributes of the + component that have changed will be copied, and the 'touched' flags in + the shapes will be cleared." [root-shape objects components page-id component-id reset-touched?] (let [all-shapes (cph/get-object-with-children (:id root-shape) objects) component (get components (:component-id root-shape))