diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 4d28d8eb4..cf023be1e 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -30,18 +30,30 @@ [changes save-undo?] (assoc changes :save-undo? save-undo?)) -(defn with-page [changes page] +(defn with-page + [changes page] (vary-meta changes assoc ::page page - ::page-id (:id page) - ::objects (:objects page))) + ::page-id (:id page))) -(defn with-objects [changes objects] +(defn with-container + [changes container] + (if (cph/page? container) + (vary-meta changes assoc ::page-id (:id container)) + (vary-meta changes assoc ::component-id (:id container)))) + +(defn with-objects + [changes objects] (let [file-data (-> (cp/make-file-data (uuid/next) uuid/zero) (assoc-in [:pages-index uuid/zero :objects] objects))] (vary-meta changes assoc ::file-data file-data ::applied-changes-count 0))) +(defn with-library-data + [changes data] + (vary-meta changes assoc + ::library-data data)) + (defn amend-last-change "Modify the last redo-changes added with an update function." [changes f] @@ -53,10 +65,23 @@ [changes f] (update changes :redo-changes #(mapv f %))) +(defn concat-changes + [changes1 changes2] + {:redo-changes (d/concat-vec (:redo-changes changes1) (:redo-changes changes2)) + :undo-changes (d/concat-vec (:undo-changes changes1) (:undo-changes changes2)) + :origin (:origin changes1)}) + +; TODO: remove this when not needed (defn- assert-page-id [changes] (assert (contains? (meta changes) ::page-id) "Give a page-id or call (with-page) before using this function")) +(defn- assert-container-id + [changes] + (assert (or (contains? (meta changes) ::page-id) + (contains? (meta changes) ::component-id)) + "Give a page-id or call (with-container) before using this function")) + (defn- assert-page [changes] (assert (contains? (meta changes) ::page) "Call (with-page) before using this function")) @@ -65,6 +90,10 @@ [changes] (assert (contains? (meta changes) ::file-data) "Call (with-objects) before using this function")) +(defn- assert-library + [changes] + (assert (contains? (meta changes) ::library-data) "Call (with-library-data) before using this function")) + (defn- apply-changes-local [changes] (if-let [file-data (::file-data (meta changes))] @@ -155,9 +184,9 @@ ;; Shape tree changes -(defn add-obj +(defn add-object ([changes obj] - (add-obj changes obj nil)) + (add-object changes obj nil)) ([changes obj {:keys [index ignore-touched] :or {index ::undefined ignore-touched false}}] (assert-page-id changes) @@ -225,9 +254,11 @@ (update-shapes changes ids update-fn nil)) ([changes ids update-fn {:keys [attrs ignore-geometry?] :or {attrs nil ignore-geometry? false}}] - (assert-page-id changes) + (assert-container-id changes) (assert-objects changes) - (let [objects (get-in (meta changes) [::file-data :pages-index uuid/zero :objects]) + (let [page-id (::page-id (meta changes)) + component-id (::component-id (meta changes)) + objects (get-in (meta changes) [::file-data :pages-index uuid/zero :objects]) generate-operation (fn [operations attr old new ignore-geometry?] @@ -255,9 +286,14 @@ (seq uops) (conj {:type :set-touched :touched (:touched old-obj)})) - change {:type :mod-obj - :page-id (::page-id (meta changes)) - :id id}] + change (cond-> {:type :mod-obj + :id id} + + (some? page-id) + (assoc :page-id page-id) + + (some? component-id) + (assoc :component-id component-id))] (cond-> changes (seq rops) @@ -374,3 +410,190 @@ (-> (reduce resize-parent changes all-parents) (apply-changes-local)))) +;; Library changes + +(defn add-recent-color + [changes color] + (-> changes + (update :redo-changes conj {:type :add-recent-color :color color}) + (apply-changes-local))) + +(defn add-color + [changes color] + (-> changes + (update :redo-changes conj {:type :add-color :color color}) + (update :undo-changes conj {:type :del-color :id (:id color)}) + (apply-changes-local))) + +(defn update-color + [changes color] + (assert-library changes) + (let [library-data (::library-data (meta changes)) + prev-color (get-in library-data [:colors (:id color)])] + (-> changes + (update :redo-changes conj {:type :mod-color :color color}) + (update :undo-changes conj {:type :mod-color :color prev-color}) + (apply-changes-local)))) + +(defn delete-color + [changes color-id] + (assert-library changes) + (let [library-data (::library-data (meta changes)) + prev-color (get-in library-data [:colors color-id])] + (-> changes + (update :redo-changes conj {:type :del-color :id color-id}) + (update :undo-changes conj {:type :add-color :color prev-color}) + (apply-changes-local)))) + +(defn add-media + [changes object] + (-> changes + (update :redo-changes conj {:type :add-media :object object}) + (update :undo-changes conj {:type :del-media :id (:id object)}) + (apply-changes-local))) + +(defn update-media + [changes object] + (assert-library changes) + (let [library-data (::library-data (meta changes)) + prev-object (get-in library-data [:media (:id object)])] + (-> changes + (update :redo-changes conj {:type :mod-media :object object}) + (update :undo-changes conj {:type :mod-media :object prev-object}) + (apply-changes-local)))) + +(defn delete-media + [changes id] + (assert-library changes) + (let [library-data (::library-data (meta changes)) + prev-object (get-in library-data [:media id])] + (-> changes + (update :redo-changes conj {:type :del-media :id id}) + (update :undo-changes conj {:type :add-media :object prev-object}) + (apply-changes-local)))) + +(defn add-typography + [changes typography] + (-> changes + (update :redo-changes conj {:type :add-typography :typography typography}) + (update :undo-changes conj {:type :del-typography :id (:id typography)}) + (apply-changes-local))) + +(defn update-typography + [changes typography] + (assert-library changes) + (let [library-data (::library-data (meta changes)) + prev-typography (get-in library-data [:typographies (:id typography)])] + (-> changes + (update :redo-changes conj {:type :mod-typography :typography typography}) + (update :undo-changes conj {:type :mod-typography :typography prev-typography}) + (apply-changes-local)))) + +(defn delete-typography + [changes typography-id] + (assert-library changes) + (let [library-data (::library-data (meta changes)) + prev-typography (get-in library-data [:typographies typography-id])] + (-> changes + (update :redo-changes conj {:type :del-typography :id typography-id}) + (update :undo-changes conj {:type :add-typography :typography prev-typography}) + (apply-changes-local)))) + +(defn add-component + [changes id path name new-shapes updated-shapes] + (assert-page-id changes) + (assert-objects changes) + (let [page-id (::page-id (meta changes)) + objects (-> changes meta ::file-data (get-in [:pages-index uuid/zero :objects]))] + + (-> changes + (update :redo-changes + (fn [redo-changes] + (-> redo-changes + (conj {:type :add-component + :id id + :path path + :name name + :shapes new-shapes}) + (into (map (fn [updated-shape] + {:type :mod-obj + :page-id page-id + :id (:id updated-shape) + :operations [{:type :set + :attr :component-id + :val (:component-id updated-shape)} + {:type :set + :attr :component-file + :val (:component-file updated-shape)} + {:type :set + :attr :component-root? + :val (:component-root? updated-shape)} + {:type :set + :attr :shape-ref + :val (:shape-ref updated-shape)} + {:type :set + :attr :touched + :val (:touched updated-shape)}]}) + updated-shapes))))) + (update :undo-changes + (fn [undo-changes] + (-> undo-changes + (conj {:type :del-component + :id id}) + (into (map (fn [updated-shape] + (let [original-shape (get objects (:id updated-shape))] + {:type :mod-obj + :page-id page-id + :id (:id updated-shape) + :operations [{:type :set + :attr :component-id + :val (:component-id original-shape)} + {:type :set + :attr :component-file + :val (:component-file original-shape)} + {:type :set + :attr :component-root? + :val (:component-root? original-shape)} + {:type :set + :attr :shape-ref + :val (:shape-ref original-shape)} + {:type :set + :attr :touched + :val (:touched original-shape)}]})) + updated-shapes))))) + (apply-changes-local)))) + +(defn update-component + [changes id update-fn] + (assert-library changes) + (let [library-data (::library-data (meta changes)) + prev-component (get-in library-data [:components id]) + new-component (update-fn prev-component)] + (if new-component + (-> changes + (update :redo-changes conj {:type :mod-component + :id id + :name (:name new-component) + :path (:path new-component) + :objects (:objects new-component)}) + (update :undo-changes conj {:type :mod-component + :id id + :name (:name prev-component) + :path (:path prev-component) + :objects (:objects prev-component)})) + changes))) + +(defn delete-component + [changes id] + (assert-library changes) + (let [library-data (::library-data (meta changes)) + prev-component (get-in library-data [:components id])] + (-> changes + (update :redo-changes conj {:type :del-component + :id id}) + (update :undo-changes conj {:type :add-component + :id id + :name (:name prev-component) + :path (:path prev-component) + :shapes (vals (:objects prev-component))})))) + diff --git a/frontend/package.json b/frontend/package.json index 17bbe38d2..5edc48cfd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ ], "scripts": { "compile-test": "clojure -M:dev:shadow-cljs compile test --config-merge '{:autorun false}'", + "lint": "clj-kondo --parallel --lint src/", "lint-scss": "yarn run prettier -c resources/styles", "run-test": "node target/tests.js", "test": "yarn run compile-test && yarn run run-test", diff --git a/frontend/src/app/main/data/workspace/bool.cljs b/frontend/src/app/main/data/workspace/bool.cljs index 1e2e4b5ee..2c316d294 100644 --- a/frontend/src/app/main/data/workspace/bool.cljs +++ b/frontend/src/app/main/data/workspace/bool.cljs @@ -98,7 +98,7 @@ shape-id (:id boolean-data) changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) - (pcb/add-obj boolean-data {:index index}) + (pcb/add-object boolean-data {:index index}) (pcb/change-parent shape-id shapes))] (rx/of (dch/commit-changes changes) (dwc/select-shapes (d/ordered-set shape-id))))))))) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index 270086962..67fe9cb10 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -102,10 +102,6 @@ (us/assert ::spec.change/changes redo-changes) (us/assert ::spec.change/changes undo-changes) - ;; (prn "====== commit-changes ======" path) - ;; (cljs.pprint/pprint redo-changes) - ;; (cljs.pprint/pprint undo-changes) - (update-in state path cp/process-changes redo-changes false) (catch :default e diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 55fc8adf7..98d6dc0c3 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -325,7 +325,7 @@ selected) changes (-> (pcb/empty-changes it page-id) - (pcb/add-obj shape))] + (pcb/add-object shape))] (rx/concat (rx/of (dch/commit-changes changes) diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index f09384d06..52aa5e009 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -88,7 +88,7 @@ changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) - (pcb/add-obj group) + (pcb/add-object group) (pcb/change-parent (:id group) shapes) (pcb/remove-objects ids-to-delete))] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 68b034754..9f2839757 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -8,9 +8,9 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as geom] [app.common.logging :as log] [app.common.pages :as cp] + [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.spec.change :as spec.change] @@ -34,7 +34,7 @@ [potok.core :as ptk])) ;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default -(log/set-level! :warn) +(log/set-level! :debug) (defn- log-changes [changes file] @@ -57,7 +57,7 @@ prefix (if (:component-id change) "[C] " "[P] ") extract (cond-> {:type (:type change) - :change change} + :raw-change change} shape (assoc :shape (str prefix (:name shape))) (:operations change) @@ -100,25 +100,20 @@ ptk/WatchEvent (watch [it _ _] - (let [rchg {:type :add-color - :color color} - uchg {:type :del-color - :id id}] + (let [changes (-> (pcb/empty-changes it) + (pcb/add-color color))] (rx/of #(assoc-in % [:workspace-local :color-for-rename] id) - (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it}))))))) + (dch/commit-changes changes))))))) + (defn add-recent-color [color] (us/assert ::spec.color/recent-color color) (ptk/reify ::add-recent-color ptk/WatchEvent (watch [it _ _] - (let [rchg {:type :add-recent-color - :color color}] - (rx/of (dch/commit-changes {:redo-changes [rchg] - :undo-changes [] - :origin it})))))) + (let [changes (-> (pcb/empty-changes it) + (pcb/add-recent-color color))] + (rx/of (dch/commit-changes changes)))))) (def clear-color-for-rename (ptk/reify ::clear-color-for-rename @@ -127,23 +122,20 @@ (assoc-in state [:workspace-local :color-for-rename] nil)))) (defn update-color - [{:keys [id] :as color} file-id] + [color file-id] (us/assert ::spec.color/color color) (us/assert ::us/uuid file-id) (ptk/reify ::update-color ptk/WatchEvent (watch [it state _] - (let [[path name] (cph/parse-path-name (:name color)) - color (assoc color :path path :name name) - prev (get-in state [:workspace-data :colors id]) - rchg {:type :mod-color - :color color} - uchg {:type :mod-color - :color prev}] + (let [data (get state :workspace-data) + [path name] (cph/parse-path-name (:name color)) + color (assoc color :path path :name name) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/update-color color))] (rx/of (dwu/start-undo-transaction) - (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it}) + (dch/commit-changes changes) (sync-file (:current-file-id state) file-id) (dwu/commit-undo-transaction)))))) @@ -153,29 +145,22 @@ (ptk/reify ::delete-color ptk/WatchEvent (watch [it state _] - (let [prev (get-in state [:workspace-data :colors id]) - rchg {:type :del-color - :id id} - uchg {:type :add-color - :color prev}] - (rx/of (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it})))))) + (let [data (get state :workspace-data) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/delete-color id))] + (rx/of (dch/commit-changes changes)))))) (defn add-media - [{:keys [id] :as media}] + [media] (us/assert ::spec.file/media-object media) (ptk/reify ::add-media ptk/WatchEvent (watch [it _ _] - (let [obj (select-keys media [:id :name :width :height :mtype]) - rchg {:type :add-media - :object obj} - uchg {:type :del-media - :id id}] - (rx/of (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it})))))) + (let [obj (select-keys media [:id :name :width :height :mtype]) + changes (-> (pcb/empty-changes it) + (pcb/add-media obj))] + (rx/of (dch/commit-changes changes)))))) (defn rename-media [id new-name] @@ -184,22 +169,14 @@ (ptk/reify ::rename-media ptk/WatchEvent (watch [it state _] - (let [object (get-in state [:workspace-data :media id]) + (let [data (get state :workspace-data) [path name] (cph/parse-path-name new-name) - - rchanges [{:type :mod-media - :object {:id id - :name name - :path path}}] - - uchanges [{:type :mod-media - :object {:id id - :name (:name object) - :path (:path object)}}]] - - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it})))))) + object (get-in data [:media id]) + new-object (assoc object :path path :name name) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/update-media new-object))] + (rx/of (dch/commit-changes changes)))))) (defn delete-media [{:keys [id] :as params}] @@ -207,14 +184,11 @@ (ptk/reify ::delete-media ptk/WatchEvent (watch [it state _] - (let [prev (get-in state [:workspace-data :media id]) - rchg {:type :del-media - :id id} - uchg {:type :add-media - :object prev}] - (rx/of (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it})))))) + (let [data (get state :workspace-data) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/delete-media id))] + (rx/of (dch/commit-changes changes)))))) (defn add-typography ([typography] (add-typography typography true)) @@ -227,13 +201,9 @@ ptk/WatchEvent (watch [it _ _] - (let [rchg {:type :add-typography - :typography typography} - uchg {:type :del-typography - :id (:id typography)}] - (rx/of (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it}) + (let [changes (-> (pcb/empty-changes it) + (pcb/add-typography typography))] + (rx/of (dch/commit-changes changes) #(cond-> % edit? (assoc-in [:workspace-global :rename-typography] (:id typography)))))))))) @@ -245,15 +215,12 @@ (ptk/reify ::update-typography ptk/WatchEvent (watch [it state _] - (let [prev (get-in state [:workspace-data :typographies (:id typography)]) - rchg {:type :mod-typography - :typography typography} - uchg {:type :mod-typography - :typography prev}] + (let [data (get state :workspace-data) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/update-typography typography))] (rx/of (dwu/start-undo-transaction) - (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it}) + (dch/commit-changes changes) (sync-file (:current-file-id state) file-id) (dwu/commit-undo-transaction)))))) @@ -263,15 +230,11 @@ (ptk/reify ::delete-typography ptk/WatchEvent (watch [it state _] - (let [prev (get-in state [:workspace-data :typographies id]) - rchg {:type :del-typography - :id id} - uchg {:type :add-typography - :typography prev}] - (rx/of (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it})))))) - + (let [data (get state :workspace-data) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/delete-typography id))] + (rx/of (dch/commit-changes changes)))))) (defn- add-component2 "This is the second step of the component creation." @@ -287,12 +250,10 @@ objects (wsh/lookup-page-objects state page-id) shapes (dwg/shapes-for-grouping objects selected)] (when-not (empty? shapes) - (let [[group rchanges uchanges] + (let [[group changes] (dwlh/generate-add-component it shapes objects page-id file-id)] - (when-not (empty? rchanges) - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it}) + (when-not (empty? (:redo-changes changes)) + (rx/of (dch/commit-changes changes) (dwc/select-shapes (d/ordered-set (:id group))))))))))) (defn add-component @@ -317,31 +278,27 @@ (ptk/reify ::rename-component ptk/WatchEvent (watch [it state _] - ;; NOTE: we need to ensure the component exists, because there - ;; are small posibilities of race conditions with component - ;; deletion. - (when-let [component (get-in state [:workspace-data :components id])] - (let [[path name] (cph/parse-path-name new-name) - objects (get component :objects) - ;; Give the same name to the root shape - new-objects (assoc-in objects - [(:id component) :name] - name) + (let [data (get state :workspace-data) + [path name] (cph/parse-path-name new-name) - rchanges [{:type :mod-component - :id id - :name name - :path path - :objects new-objects}] + update-fn + (fn [component] + ;; NOTE: we need to ensure the component exists, + ;; because there are small posibilities of race + ;; conditions with component deletion. + (when component + (-> component + (assoc :path path) + (assoc :name name) + (update :objects + ;; Give the same name to the root shape + #(assoc-in % [id :name] name))))) - uchanges [{:type :mod-component - :id id - :name (:name component) - :path (:path component) - :objects objects}]] - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it}))))))) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/update-component id update-fn))] + + (rx/of (dch/commit-changes changes)))))) (defn duplicate-component "Create a new component copied from the one with the given id." @@ -358,18 +315,15 @@ [new-shape new-shapes _updated-shapes] (dwlh/duplicate-component component) - rchanges [{:type :add-component - :id (:id new-shape) - :name new-name - :path (:path component) - :shapes new-shapes}] + changes (-> (pcb/empty-changes it nil) ;; no objects are changed + (pcb/with-objects nil) ;; in the current page + (pcb/add-component (:id new-shape) + (:path component) + new-name + new-shapes + []))] - uchanges [{:type :del-component - :id (:id new-shape)}]] - - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it})))))) + (rx/of (dch/commit-changes changes)))))) (defn delete-component "Delete the component with the given id, from the current file library." @@ -378,20 +332,12 @@ (ptk/reify ::delete-component ptk/WatchEvent (watch [it state _] - (let [component (get-in state [:workspace-data :components id]) + (let [data (get state :workspace-data) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/delete-component id))] - rchanges [{:type :del-component - :id id}] - - uchanges [{:type :add-component - :id id - :name (:name component) - :path (:path component) - :shapes (vals (:objects component))}]] - - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it})))))) + (rx/of (dch/commit-changes changes)))))) (defn instantiate-component "Create a new shape in the current page, from the component with the given id @@ -450,26 +396,11 @@ (get component :objects) update-new-shape) - rchanges (mapv (fn [obj] - {:type :add-obj - :id (:id obj) - :page-id page-id - :frame-id (:frame-id obj) - :parent-id (:parent-id obj) - :ignore-touched true - :obj obj}) - new-shapes) + changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) + (pcb/empty-changes it page-id) + new-shapes)] - uchanges (mapv (fn [obj] - {:type :del-obj - :id (:id obj) - :page-id page-id - :ignore-touched true}) - new-shapes)] - - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it}) + (rx/of (dch/commit-changes changes) (dwc/select-shapes (d/ordered-set (:id new-shape)))))))) (defn detach-component @@ -484,12 +415,12 @@ page-id (get state :current-page-id) container (cph/get-container file :page page-id) - [rchanges uchanges] - (dwlh/generate-detach-instance container id)] + changes (-> (pcb/empty-changes it) + (pcb/with-container container) + (pcb/with-objects (:objects container)) + (dwlh/generate-detach-instance container id))] - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it})))))) + (rx/of (dch/commit-changes changes)))))) (def detach-selected-components (ptk/reify ::detach-selected-components @@ -503,17 +434,15 @@ (wsh/lookup-selected) (cph/clean-loops objects)) - [rchanges uchanges] - (reduce (fn [changes id] - (dwlh/concat-changes - changes - (dwlh/generate-detach-instance container id))) - dwlh/empty-changes - selected)] + changes (reduce + (fn [changes id] + (dwlh/generate-detach-instance changes container id)) + (-> (pcb/empty-changes it) + (pcb/with-container container) + (pcb/with-objects objects)) + selected)] - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it})))))) + (rx/of (dch/commit-changes changes)))))) (defn nav-to-component-file [file-id] @@ -559,15 +488,16 @@ page-id (:current-page-id state) container (cph/get-container file :page page-id) - [rchanges uchanges] - (dwlh/generate-sync-shape-direct libraries container id true)] + changes + (-> (pcb/empty-changes it) + (pcb/with-container container) + (pcb/with-objects (:objects container)) + (dwlh/generate-sync-shape-direct libraries container id true))] (log/debug :msg "RESET-COMPONENT finished" :js/rchanges (log-changes - rchanges - file)) - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it})))))) + (:redo-changes changes) + file)) + (rx/of (dch/commit-changes changes)))))) (defn update-component "Modify the component linked to the shape with the given id, in the @@ -592,8 +522,10 @@ container (cph/get-container local-file :page page-id) shape (cph/get-shape container id) - [rchanges uchanges] - (dwlh/generate-sync-shape-inverse libraries container id) + changes + (-> (pcb/empty-changes it) + (pcb/with-container container) + (dwlh/generate-sync-shape-inverse libraries container id)) file-id (:component-file shape) file (dwlh/get-file state file-id) @@ -602,35 +534,33 @@ (filter :local-change?) (map #(dissoc % :local-change?))) - local-rchanges (into [] xf-filter rchanges) - local-uchanges (into [] xf-filter uchanges) + local-changes (-> changes + (update :redo-changes #(into [] xf-filter %)) + (update :undo-changes #(into [] xf-filter %))) xf-remove (comp (remove :local-change?) (map #(dissoc % :local-change?))) - rchanges (into [] xf-remove rchanges) - uchanges (into [] xf-remove uchanges)] + nonlocal-changes (-> changes + (update :redo-changes #(into [] xf-remove %)) + (update :undo-changes #(into [] xf-remove %)))] (log/debug :msg "UPDATE-COMPONENT finished" - :js/local-rchanges (log-changes - local-rchanges + :js/local-changes (log-changes + (:redo-changes local-changes) file) - :js/rchanges (log-changes - rchanges - file)) + :js/nonlocal-changes (log-changes + (:redo-changes nonlocal-changes) + file)) (rx/of - (when (seq local-rchanges) - (dch/commit-changes {:redo-changes local-rchanges - :undo-changes local-uchanges - :origin it - :file-id (:id local-file)})) - (when (seq rchanges) - (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it - :file-id file-id}))))))) + (when (seq (:redo-changes local-changes)) + (dch/commit-changes (assoc local-changes + :file-id (:id local-file)))) + (when (seq (:redo-changes nonlocal-changes)) + (dch/commit-changes (assoc nonlocal-changes + :file-id file-id)))))))) (defn update-component-sync [shape-id file-id] @@ -679,33 +609,27 @@ :file (dwlh/pretty-file file-id state) :library (dwlh/pretty-file library-id state)) (let [file (dwlh/get-file state file-id) - library-changes [(dwlh/generate-sync-library file-id :components library-id state) - (dwlh/generate-sync-library file-id :colors library-id state) - (dwlh/generate-sync-library file-id :typographies library-id state)] - file-changes [(dwlh/generate-sync-file file-id :components library-id state) - (dwlh/generate-sync-file file-id :colors library-id state) - (dwlh/generate-sync-file file-id :typographies library-id state)] - xf-fcat (comp (remove nil?) (map first) (mapcat identity)) - rchanges (d/concat-vec - (sequence xf-fcat library-changes) - (sequence xf-fcat file-changes)) + changes (-> (pcb/empty-changes it)) + library-changes (-> changes + (dwlh/generate-sync-library file-id :components library-id state) + (dwlh/generate-sync-library file-id :colors library-id state) + (dwlh/generate-sync-library file-id :typographies library-id state)) + file-changes (-> library-changes + (dwlh/generate-sync-file file-id :components library-id state) + (dwlh/generate-sync-file file-id :colors library-id state) + (dwlh/generate-sync-file file-id :typographies library-id state)) - xf-scat (comp (remove nil?) (map second) (mapcat identity)) - uchanges (d/concat-vec - (sequence xf-scat library-changes) - (sequence xf-scat file-changes))] + changes (pcb/concat-changes library-changes file-changes)] (log/debug :msg "SYNC-FILE finished" :js/rchanges (log-changes - rchanges + (:redo-changes changes) file)) (rx/concat (rx/of (dm/hide-tag :sync-dialog)) - (when rchanges - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it - :file-id file-id}))) + (when (seq (:redo-changes changes)) + (rx/of (dch/commit-changes (assoc changes ;; TODO a ver qué pasa con esto + :file-id file-id)))) (when (not= file-id library-id) ;; When we have just updated the library file, give some time for the ;; update to finish, before marking this file as synced. @@ -717,7 +641,7 @@ (rp/mutation :update-sync {:file-id file-id :library-id library-id}))) - (when (some? library-changes) + (when (seq (:redo-changes library-changes)) (rx/of (sync-file-2nd-stage file-id library-id)))))))) (defn sync-file-2nd-stage @@ -738,19 +662,15 @@ (log/info :msg "SYNC-FILE (2nd stage)" :file (dwlh/pretty-file file-id state) :library (dwlh/pretty-file library-id state)) - (let [file (dwlh/get-file state file-id) - [rchanges1 uchanges1] (dwlh/generate-sync-file file-id :components library-id state) - [rchanges2 uchanges2] (dwlh/generate-sync-library file-id :components library-id state) - rchanges (d/concat-vec rchanges1 rchanges2) - uchanges (d/concat-vec uchanges1 uchanges2)] - (when rchanges + (let [file (dwlh/get-file state file-id) + changes (-> (pcb/empty-changes it) + (dwlh/generate-sync-file file-id :components library-id state) + (dwlh/generate-sync-library file-id :components library-id state))] + (when (seq (:redo-changes changes)) (log/debug :msg "SYNC-FILE (2nd stage) finished" :js/rchanges (log-changes - rchanges + (:redo-changes changes) file)) - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it - :file-id file-id}))))))) + (rx/of (dch/commit-changes (assoc changes :file-id file-id)))))))) (def ignore-sync (ptk/reify ::ignore-sync diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 4c5b20b79..ac8127744 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -11,6 +11,7 @@ [app.common.geom.shapes :as geom] [app.common.logging :as log] [app.common.pages :as cp] + [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.text :as txt] @@ -21,7 +22,7 @@ ;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default (log/set-level! :warn) -(defonce empty-changes [[] []]) +;; (defonce empty-changes [[] []]) (defonce color-sync-attrs [[:fill-color-ref-id :fill-color-ref-file :color :fill-color] @@ -53,13 +54,6 @@ (declare reposition-shape) (declare make-change) -(defn concat-changes - [& rest] - (letfn [(concat-changes' [[rchanges1 uchanges1] [rchanges2 uchanges2]] - [(d/concat-vec rchanges1 rchanges2) - (d/concat-vec uchanges1 uchanges2)])] - (transduce (remove nil?) (completing concat-changes') empty-changes rest))) - (defn get-local-file [state] (get state :workspace-data)) @@ -134,77 +128,27 @@ [it shapes objects page-id file-id] (if (and (= (count shapes) 1) (:component-id (first shapes))) - empty-changes - (let [name (if (= 1 (count shapes)) (:name (first shapes)) "Component-1") - [group rchanges uchanges] + (pcb/empty-changes it) + (let [name (if (= 1 (count shapes)) (:name (first shapes)) "Component-1") + [path name] (cph/parse-path-name name) + + [group changes] (if (and (= (count shapes) 1) (= (:type (first shapes)) :group)) - [(first shapes) [] []] - (let [[group changes] (dwg/prepare-create-group it objects page-id shapes name true)] - [group (:redo-changes changes) (:undo-changes changes)])) - - ;; Asserts for documentation purposes - _ (us/assert vector? rchanges) - _ (us/assert vector? uchanges) + [(first shapes) (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects))] + (dwg/prepare-create-group it objects page-id shapes name true)) [new-shape new-shapes updated-shapes] (make-component-shape group objects file-id) - rchanges (conj rchanges - {:type :add-component - :id (:id new-shape) - :name name - :shapes new-shapes}) - - rchanges (into rchanges - (map (fn [updated-shape] - {:type :mod-obj - :page-id page-id - :id (:id updated-shape) - :operations [{:type :set - :attr :component-id - :val (:component-id updated-shape)} - {:type :set - :attr :component-file - :val (:component-file updated-shape)} - {:type :set - :attr :component-root? - :val (:component-root? updated-shape)} - {:type :set - :attr :shape-ref - :val (:shape-ref updated-shape)} - {:type :set - :attr :touched - :val (:touched updated-shape)}]}) - updated-shapes)) - - uchanges (conj uchanges - {:type :del-component - :id (:id new-shape)}) - - uchanges (into uchanges - (map (fn [updated-shape] - (let [original-shape (get objects (:id updated-shape))] - {:type :mod-obj - :page-id page-id - :id (:id updated-shape) - :operations [{:type :set - :attr :component-id - :val (:component-id original-shape)} - {:type :set - :attr :component-file - :val (:component-file original-shape)} - {:type :set - :attr :component-root? - :val (:component-root? original-shape)} - {:type :set - :attr :shape-ref - :val (:shape-ref original-shape)} - {:type :set - :attr :touched - :val (:touched original-shape)}]})) - updated-shapes))] - [group rchanges uchanges]))) + changes (-> changes + (pcb/add-component (:id new-shape) + path + name + new-shapes + updated-shapes))] + [group changes]))) (defn duplicate-component "Clone the root shape of the component and all children. Generate new @@ -219,68 +163,29 @@ (defn generate-detach-instance "Generate changes to remove the links between a shape and all its children with a component." - [container shape-id] + [changes container shape-id] (log/debug :msg "Detach instance" :shape-id shape-id :container (:id container)) - (let [shapes (cph/get-children-with-self (:objects container) shape-id) - rchanges (mapv (fn [obj] - (make-change - container - {:type :mod-obj - :id (:id obj) - :operations [{:type :set - :attr :component-id - :val nil} - {:type :set - :attr :component-file - :val nil} - {:type :set - :attr :component-root? - :val nil} - {:type :set - :attr :remote-synced? - :val nil} - {:type :set - :attr :shape-ref - :val nil} - {:type :set - :attr :touched - :val nil}]})) - shapes) + (let [shapes (->> (cph/get-children-with-self (:objects container) shape-id) + (map :id)) - uchanges (mapv (fn [obj] - (make-change - container - {:type :mod-obj - :id (:id obj) - :operations [{:type :set - :attr :component-id - :val (:component-id obj)} - {:type :set - :attr :component-file - :val (:component-file obj)} - {: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)} - {:type :set - :attr :touched - :val (:touched obj)}]})) - shapes)] - - [rchanges uchanges])) + update-fn + (fn [shape] + (assoc shape + :component-id nil + :component-file nil + :component-root? nil + :remote-synced? nil + :shape-ref nil + :touched nil))] + (pcb/update-shapes changes shapes update-fn))) ;; ---- General library synchronization functions ---- (defn generate-sync-file "Generate changes to synchronize all shapes in all pages of the given file, that use assets of the given type in the given library." - [file-id asset-type library-id state] + [changes file-id asset-type library-id state] (s/assert #{:colors :components :typographies} asset-type) (s/assert ::us/uuid file-id) (s/assert ::us/uuid library-id) @@ -292,24 +197,21 @@ (let [file (get-file state file-id)] (loop [pages (vals (get file :pages-index)) - rchanges [] - uchanges []] + changes changes] (if-let [page (first pages)] - (let [[page-rchanges page-uchanges] - (generate-sync-container asset-type - library-id - state - (cph/make-container page :page))] - (recur (next pages) - (into rchanges page-rchanges) - (into uchanges page-uchanges))) - [rchanges uchanges])))) + (recur (next pages) + (generate-sync-container changes + asset-type + library-id + state + (cph/make-container page :page))) + changes)))) (defn generate-sync-library "Generate changes to synchronize all shapes in all components of the local library of the given file, that use assets of the given type in the given library." - [file-id asset-type library-id state] + [changes file-id asset-type library-id state] (log/info :msg "Sync local components with library" :asset-type asset-type @@ -318,23 +220,20 @@ (let [file (get-file state file-id)] (loop [local-components (vals (get file :components)) - rchanges [] - uchanges []] + changes changes] (if-let [local-component (first local-components)] - (let [[comp-rchanges comp-uchanges] - (generate-sync-container asset-type - library-id - state - (cph/make-container local-component :component))] - (recur (next local-components) - (into rchanges comp-rchanges) - (into uchanges comp-uchanges))) - [rchanges uchanges])))) + (recur (next local-components) + (generate-sync-container changes + asset-type + library-id + state + (cph/make-container local-component :component))) + changes)))) (defn- generate-sync-container "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] + [changes asset-type library-id state container] (if (cph/page? container) (log/debug :msg "Sync page in local file" :page-id (:id container)) @@ -344,19 +243,16 @@ linked-shapes (->> (vals (:objects container)) (filter has-asset-reference?))] (loop [shapes (seq linked-shapes) - rchanges [] - uchanges []] + changes changes] (if-let [shape (first shapes)] - (let [[shape-rchanges shape-uchanges] - (generate-sync-shape asset-type - library-id - state - container - shape)] - (recur (next shapes) - (into rchanges shape-rchanges) - (into uchanges shape-uchanges))) - [rchanges uchanges])))) + (recur (next shapes) + (generate-sync-shape asset-type + changes + library-id + state + container + shape)) + changes)))) (defn- has-asset-reference-fn "Gets a function that checks if a shape uses some asset of the given type @@ -400,39 +296,39 @@ (defmulti generate-sync-shape "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)) + (fn [type _changes _library-id _state _container _shape] type)) (defmethod generate-sync-shape :components - [_ _ state container shape] + [_ changes _library-id state container shape] (let [shape-id (:id shape) libraries (get-libraries state)] - (generate-sync-shape-direct libraries container shape-id false))) + (generate-sync-shape-direct changes libraries container shape-id false))) (defn- generate-sync-text-shape - [shape container update-node] + [changes shape container update-node] (let [old-content (:content shape) new-content (txt/transform-nodes update-node old-content) - rchanges [(make-change - container - {:type :mod-obj - :id (:id shape) - :operations [{:type :set - :attr :content - :val new-content}]})] - uchanges [(make-change - container - {:type :mod-obj - :id (:id shape) - :operations [{:type :set - :attr :content - :val old-content}]})]] - + changes' (-> changes + (update :redo-changes conj (make-change + container + {:type :mod-obj + :id (:id shape) + :operations [{:type :set + :attr :content + :val new-content}]})) + (update :undo-changes conj (make-change + container + {:type :mod-obj + :id (:id shape) + :operations [{:type :set + :attr :content + :val old-content}]})))] (if (= new-content old-content) - empty-changes - [rchanges uchanges]))) + changes + changes'))) (defmethod generate-sync-shape :colors - [_ library-id state container shape] + [_ changes library-id state container shape] (log/debug :msg "Sync colors of shape" :shape (:name shape)) ;; Synchronize a shape that uses some colors of the library. The value of the @@ -448,25 +344,25 @@ (assoc node :fill-color-ref-id nil :fill-color-ref-file nil)))] - (generate-sync-text-shape shape container update-node)) - (loop [attrs (seq color-sync-attrs) + (generate-sync-text-shape changes shape container update-node)) + (loop [attrs (seq color-sync-attrs) roperations [] uoperations []] (let [[attr-ref-id attr-ref-file color-attr attr] (first attrs)] (if (nil? attr) (if (empty? roperations) - empty-changes - (let [rchanges [(make-change - container - {:type :mod-obj - :id (:id shape) - :operations roperations})] - uchanges [(make-change - container - {:type :mod-obj - :id (:id shape) - :operations uoperations})]] - [rchanges uchanges])) + changes + (-> changes + (update :redo-changes (make-change + container + {:type :mod-obj + :id (:id shape) + :operations roperations})) + (update :undo-changes (make-change + container + {:type :mod-obj + :id (:id shape) + :operations uoperations})))) (if-not (contains? shape attr-ref-id) (recur (next attrs) roperations @@ -505,7 +401,7 @@ (into uoperations uoperations')))))))))) (defmethod generate-sync-shape :typographies - [_ library-id state container shape] + [_ changes library-id state container shape] (log/debug :msg "Sync typographies of shape" :shape (:name shape)) ;; Synchronize a shape that uses some typographies of the library. The attributes @@ -516,7 +412,7 @@ (merge node (dissoc typography :name :id)) (dissoc node :typography-ref-id :typography-ref-file)))] - (generate-sync-text-shape shape container update-node))) + (generate-sync-text-shape changes shape container update-node))) (defn- get-assets [library-id asset-type state] @@ -628,7 +524,7 @@ (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." - [libraries container shape-id reset?] + [changes libraries container shape-id 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 libraries @@ -642,7 +538,8 @@ root-main (cph/get-component-root component)] (if component - (generate-sync-shape-direct-recursive container + (generate-sync-shape-direct-recursive changes + container shape-inst component shape-main @@ -652,58 +549,62 @@ initial-root?) ; If the component is not found, because the master component has been ; deleted or the library unlinked, detach the instance. - (generate-detach-instance shape-id container)))) + (generate-detach-instance changes shape-id container)))) (defn- generate-sync-shape-direct-recursive - [container shape-inst component shape-main root-inst root-main reset? initial-root?] + [changes container shape-inst component shape-main root-inst root-main reset? initial-root?] (log/debug :msg "Sync shape direct recursive" :shape (str (:name shape-inst)) :component (:name component)) (if (nil? shape-main) ;; This should not occur, but protect against it in any case - (generate-detach-instance (:id shape-inst) container) + (generate-detach-instance changes (:id shape-inst) container) (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-main - root-inst - root-main - container - omit-touched?) - (when reset? - (change-touched shape-inst - shape-main - container - {:reset-touched? true})) - (when clear-remote-synced? - (change-remote-synced shape-inst container nil)) + changes (cond-> changes + :always + (update-attrs shape-inst + shape-main + root-inst + root-main + container + omit-touched?) - (when set-remote-synced? - (change-remote-synced shape-inst container true))) + reset? + (change-touched shape-inst + shape-main + container + {:reset-touched? true}) + + clear-remote-synced? + (change-remote-synced shape-inst container nil) + + set-remote-synced? + (change-remote-synced shape-inst container true)) children-inst (mapv #(cph/get-shape container %) (:shapes shape-inst)) children-main (mapv #(cph/get-shape component %) (:shapes shape-main)) - only-inst (fn [child-inst] + only-inst (fn [changes child-inst] (when-not (and omit-touched? (contains? (:touched shape-inst) :shapes-group)) - (remove-shape child-inst + (remove-shape changes + child-inst container omit-touched?))) - only-main (fn [child-main] + only-main (fn [changes child-main] (when-not (and omit-touched? (contains? (:touched shape-inst) :shapes-group)) - (add-shape-to-instance child-main + (add-shape-to-instance changes + child-main (d/index-of children-main child-main) component @@ -713,8 +614,9 @@ omit-touched? set-remote-synced?))) - both (fn [child-inst child-main] - (generate-sync-shape-direct-recursive container + both (fn [changes child-inst child-main] + (generate-sync-shape-direct-recursive changes + container child-inst component child-main @@ -723,30 +625,28 @@ reset? initial-root?)) - moved (fn [child-inst child-main] + moved (fn [changes child-inst child-main] (move-shape + changes child-inst (d/index-of children-inst child-inst) (d/index-of children-main child-main) container - omit-touched?)) + omit-touched?))] - [child-rchanges child-uchanges] - (compare-children children-inst - children-main - only-inst - only-main - both - moved - false)] - - [(d/concat-vec rchanges child-rchanges) - (d/concat-vec uchanges child-uchanges)]))) + (compare-children changes + children-inst + children-main + only-inst + only-main + both + moved + false)))) (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." - [libraries container shape-id] + [changes libraries container shape-id] (log/debug :msg "Sync shape inverse" :shape (str shape-id)) (let [shape-inst (cph/get-shape container shape-id) component (cph/get-component libraries @@ -760,59 +660,62 @@ root-main (cph/get-component-root component)] (if component - (generate-sync-shape-inverse-recursive container + (generate-sync-shape-inverse-recursive changes + container shape-inst component shape-main root-inst root-main initial-root?) - empty-changes))) + changes))) (defn- generate-sync-shape-inverse-recursive - [container shape-inst component shape-main root-inst root-main initial-root?] + [changes container shape-inst component shape-main root-inst root-main initial-root?] (log/trace :msg "Sync shape inverse recursive" :shape (str (:name shape-inst)) :component (:name component)) (if (nil? shape-main) ;; This should not occur, but protect against it in any case - empty-changes + changes (let [component-container (cph/make-container component :component) omit-touched? false set-remote-synced? (not initial-root?) clear-remote-synced? initial-root? - [rchanges uchanges] - (concat-changes - (update-attrs shape-main - shape-inst - root-main - root-inst - component-container - omit-touched?) - (change-touched shape-inst - shape-main - container - {:reset-touched? true}) - (change-touched shape-main - shape-inst - component-container - {:copy-touched? true}) - (when clear-remote-synced? - (change-remote-synced shape-inst container nil)) + changes (cond-> changes + :always + (-> (update-attrs shape-main + shape-inst + root-main + root-inst + component-container + omit-touched?) + (change-touched shape-inst + shape-main + container + {:reset-touched? true}) + (change-touched shape-main + shape-inst + component-container + {:copy-touched? true})) - (when set-remote-synced? - (change-remote-synced shape-inst container true))) + clear-remote-synced? + (change-remote-synced shape-inst container nil) + + set-remote-synced? + (change-remote-synced shape-inst container true)) children-inst (mapv #(cph/get-shape container %) (:shapes shape-inst)) children-main (mapv #(cph/get-shape component %) (:shapes shape-main)) - only-inst (fn [child-inst] - (add-shape-to-main child-inst + only-inst (fn [changes child-inst] + (add-shape-to-main changes + child-inst (d/index-of children-inst child-inst) component @@ -820,13 +723,15 @@ root-inst root-main)) - only-main (fn [child-main] - (remove-shape child-main + only-main (fn [changes child-main] + (remove-shape changes + child-main component-container false)) - both (fn [child-inst child-main] - (generate-sync-shape-inverse-recursive container + both (fn [changes child-inst child-main] + (generate-sync-shape-inverse-recursive changes + container child-inst component child-main @@ -834,16 +739,18 @@ root-main initial-root?)) - moved (fn [child-inst child-main] + moved (fn [changes child-inst child-main] (move-shape + changes child-main (d/index-of children-main child-main) (d/index-of children-inst child-inst) component-container false)) - [child-rchanges child-uchanges] - (compare-children children-inst + changes + (compare-children changes + children-inst children-main only-inst only-main @@ -857,22 +764,20 @@ check-local (fn [change] (cond-> change (= (:id change) (:id shape-inst)) - (assoc :local-change? true))) + (assoc :local-change? true)))] - rchanges (mapv check-local rchanges) - uchanges (mapv check-local uchanges)] - - [(d/concat-vec rchanges child-rchanges) - (d/concat-vec uchanges child-uchanges)]))) + (-> changes + (update :redo-changes (partial mapv check-local)) + (update :undo-changes (partial mapv check-local)))))) ; ---- Operation generation helpers ---- (defn- compare-children - [children-inst children-main only-inst-cb only-main-cb both-cb moved-cb inverse?] + [changes children-inst children-main only-inst-cb only-main-cb both-cb moved-cb inverse?] (loop [children-inst (seq (or children-inst [])) children-main (seq (or children-main [])) - changes [[] []]] + changes changes] (let [child-inst (first children-inst) child-main (first children-main)] (cond @@ -880,16 +785,16 @@ changes (nil? child-inst) - (transduce (map only-main-cb) concat-changes changes children-main) + (reduce only-main-cb changes children-main) (nil? child-main) - (transduce (map only-inst-cb) concat-changes changes children-inst) + (reduce only-inst-cb changes children-inst) :else (if (cph/is-main-of? child-main child-inst) (recur (next children-inst) (next children-main) - (concat-changes changes (both-cb child-inst child-main))) + (both-cb changes child-inst child-main)) (let [child-inst' (d/seek #(cph/is-main-of? child-main %) children-inst) child-main' (d/seek #(cph/is-main-of? % child-inst) children-main)] @@ -897,28 +802,28 @@ (nil? child-inst') (recur children-inst (next children-main) - (concat-changes changes (only-main-cb child-main))) + (only-main-cb changes child-main)) (nil? child-main') (recur (next children-inst) children-main - (concat-changes changes (only-inst-cb child-inst))) + (only-inst-cb changes child-inst)) :else (if inverse? (recur (next children-inst) (remove #(= (:id %) (:id child-main')) children-main) - (concat-changes changes - (both-cb child-inst' child-main) - (moved-cb child-inst child-main'))) + (-> changes + (both-cb child-inst' child-main) + (moved-cb child-inst child-main'))) (recur (remove #(= (:id %) (:id child-inst')) children-inst) (next children-main) - (concat-changes changes - (both-cb child-inst child-main') - (moved-cb child-inst' child-main))))))))))) + (-> changes + (both-cb child-inst child-main') + (moved-cb child-inst' child-main))))))))))) (defn- add-shape-to-instance - [component-shape index component container root-instance root-main omit-touched? set-remote-synced?] + [changes component-shape index component container root-instance root-main omit-touched? set-remote-synced?] (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-main-of? component-parent-shape %) @@ -952,39 +857,41 @@ update-new-shape update-original-shape) - rchanges (d/concat-vec - (map (fn [shape'] - (make-change - container - (as-> {:type :add-obj - :id (:id shape') - :parent-id (:parent-id shape') - :index index - :ignore-touched true - :obj shape'} $ - (cond-> $ - (:frame-id shape') - (assoc :frame-id (:frame-id shape')))))) - new-shapes) - [(make-change - container - {:type :reg-objects - :shapes all-parents})]) + add-obj-change (fn [changes shape'] + (update changes :redo-changes conj + (make-change + container + (as-> {:type :add-obj + :id (:id shape') + :parent-id (:parent-id shape') + :index index + :ignore-touched true + :obj shape'} $ + (cond-> $ + (:frame-id shape') + (assoc :frame-id (:frame-id shape'))))))) - uchanges (mapv (fn [shape'] - (make-change - container - {:type :del-obj - :id (:id shape') - :ignore-touched true})) - new-shapes)] + del-obj-change (fn [changes shape'] + (update changes :undo-changes conj + (make-change + container + {:type :del-obj + :id (:id shape') + :ignore-touched true}))) + + changes' (reduce add-obj-change changes new-shapes) + changes' (update changes' :redo-changes conj (make-change + container + {:type :reg-objects + :shapes all-parents})) + changes' (reduce del-obj-change changes' new-shapes)] (if (and (cph/touched-group? parent-shape :shapes-group) omit-touched?) - empty-changes - [rchanges uchanges]))) + changes + changes'))) (defn- add-shape-to-main - [shape index component page root-instance root-main] + [changes shape index component page root-instance root-main] (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-main-of? % parent-shape) @@ -1012,51 +919,55 @@ update-new-shape update-original-shape) - rchanges (d/concat-vec - (map (fn [shape'] - {:type :add-obj - :id (:id shape') - :component-id (:id component) - :parent-id (:parent-id shape') - :index index - :ignore-touched true - :obj shape'}) - new-shapes) - [{:type :reg-objects - :component-id (:id component) - :shapes all-parents}] - (map (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)) + add-obj-change (fn [changes shape'] + (update changes :redo-changes conj + {:type :add-obj + :id (:id shape') + :component-id (:id component) + :parent-id (:parent-id shape') + :index index + :ignore-touched true + :obj shape'})) - uchanges (mapv (fn [shape'] - {:type :del-obj - :id (:id shape') - :page-id (:id page) - :ignore-touched true}) - new-shapes)] + mod-obj-change (fn [changes shape'] + (update changes :redo-changes conj + {: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')}]})) - [rchanges uchanges])) + del-obj-change (fn [changes shape'] + (update changes :undo-changes conj + {:type :del-obj + :id (:id shape') + :page-id (:id page) + :ignore-touched true})) + + changes' (reduce add-obj-change changes new-shapes) + changes' (update changes' :redo-changes conj {:type :reg-objects + :component-id (:id component) + :shapes all-parents}) + changes' (reduce mod-obj-change changes' updated-shapes) + changes' (reduce del-obj-change changes' new-shapes)] + + changes')) (defn- remove-shape - [shape container omit-touched?] + [changes shape container omit-touched?] (log/info :msg (str "REMOVE-SHAPE " (if (cph/page? container) "[P] " "[C] ") (:name shape))) @@ -1065,44 +976,43 @@ parent (first parents) children (cph/get-children-ids objects (:id shape)) - rchanges [(make-change - container - {:type :del-obj - :id (:id shape) - :ignore-touched true}) - (make-change - container - {:type :reg-objects - :shapes (vec parents)})] + add-undo-change (fn [changes id] + (let [shape' (get objects id)] + (update changes :undo-changes conj + (make-change + container + (as-> {:type :add-obj + :id id + :index (cph/get-position-on-parent objects id) + :parent-id (:parent-id shape') + :ignore-touched true + :obj shape'} $ + (cond-> $ + (:frame-id shape') + (assoc :frame-id (:frame-id shape')))))))) - add-change (fn [id] - (let [shape' (get objects id)] - (make-change - container - (as-> {:type :add-obj - :id id - :index (cph/get-position-on-parent objects id) - :parent-id (:parent-id shape') - :ignore-touched true - :obj shape'} $ - (cond-> $ - (:frame-id shape') - (assoc :frame-id (:frame-id shape'))))))) + changes' (-> changes + (update :redo-changes conj (make-change + container + {:type :del-obj + :id (:id shape) + :ignore-touched true})) + (update :redo-changes conj (make-change + container + {:type :reg-objects + :shapes (vec parents)})) + (add-undo-change (:id shape))) - uchanges (d/concat-vec - [(add-change (:id shape))] - (map add-change children) - [(make-change - container - {:type :reg-objects - :shapes (vec parents)})])] + changes' (reduce add-undo-change + changes' + (map :id children))] (if (and (cph/touched-group? parent :shapes-group) omit-touched?) - empty-changes - [rchanges uchanges]))) + changes + changes'))) (defn- move-shape - [shape index-before index-after container omit-touched?] + [changes shape index-before index-after container omit-touched?] (log/info :msg (str "MOVE " (if (cph/page? container) "[P] " "[C] ") (:name shape) @@ -1112,31 +1022,32 @@ index-after)) (let [parent (cph/get-shape container (:parent-id shape)) - rchanges [(make-change - container - {:type :mov-objects - :parent-id (:parent-id shape) - :shapes [(:id shape)] - :index index-after - :ignore-touched true})] - uchanges [(make-change - container - {:type :mov-objects - :parent-id (:parent-id shape) - :shapes [(:id shape)] - :index index-before - :ignore-touched true})]] + changes' (-> changes + (update :redo-changes conj (make-change + container + {:type :mov-objects + :parent-id (:parent-id shape) + :shapes [(:id shape)] + :index index-after + :ignore-touched true})) + (update :undo-changes conj (make-change + container + {:type :mov-objects + :parent-id (:parent-id shape) + :shapes [(:id shape)] + :index index-before + :ignore-touched true})))] (if (and (cph/touched-group? parent :shapes-group) omit-touched?) - empty-changes - [rchanges uchanges]))) + changes + changes'))) (defn- change-touched - [dest-shape origin-shape container + [changes 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 + changes (do (log/info :msg (str "CHANGE-TOUCHED " (if (cph/page? container) "[P] " "[C] ") @@ -1150,50 +1061,48 @@ nil (set/union (:touched dest-shape) - (:touched origin-shape)))) + (:touched origin-shape))))] - rchanges [(make-change - container - {:type :mod-obj - :id (:id dest-shape) - :operations - [{:type :set-touched - :touched new-touched}]})] - - uchanges [(make-change - container - {:type :mod-obj - :id (:id dest-shape) - :operations - [{:type :set-touched - :touched (:touched dest-shape)}]})]] - [rchanges uchanges])))) + (-> changes + (update :redo-changes conj (make-change + container + {:type :mod-obj + :id (:id dest-shape) + :operations + [{:type :set-touched + :touched new-touched}]})) + (update :undo-changes conj (make-change + container + {:type :mod-obj + :id (:id dest-shape) + :operations + [{:type :set-touched + :touched (:touched dest-shape)}]}))))))) (defn- change-remote-synced - [shape container remote-synced?] + [changes shape container remote-synced?] (if (nil? (:shape-ref shape)) - empty-changes + changes (do (log/info :msg (str "CHANGE-REMOTE-SYNCED? " (if (cph/page? container) "[P] " "[C] ") (:name shape)) :remote-synced? remote-synced?) - (let [rchanges [(make-change - container - {:type :mod-obj - :id (:id shape) - :operations - [{:type :set-remote-synced - :remote-synced? remote-synced?}]})] - - uchanges [(make-change - container - {:type :mod-obj - :id (:id shape) - :operations - [{:type :set-remote-synced - :remote-synced? (:remote-synced? shape)}]})]] - [rchanges uchanges])))) + (-> changes + (update :redo-changes conj (make-change + container + {:type :mod-obj + :id (:id shape) + :operations + [{:type :set-remote-synced + :remote-synced? remote-synced?}]})) + (update :undo-changes conj (make-change + container + {:type :mod-obj + :id (:id shape) + :operations + [{:type :set-remote-synced + :remote-synced? (:remote-synced? shape)}]})))))) (defn- update-attrs "The main function that implements the attribute sync algorithm. Copy @@ -1201,7 +1110,7 @@ If omit-touched? is true, attributes whose group has been touched in the destination shape will not be copied." - [dest-shape origin-shape dest-root origin-root container omit-touched?] + [changes dest-shape origin-shape dest-root origin-root container omit-touched?] (log/info :msg (str "SYNC " (:name origin-shape) @@ -1225,30 +1134,29 @@ (let [attr (first attrs)] (if (nil? attr) - (let [all-parents (cph/get-parent-ids (:objects container) - (:id dest-shape)) - rchanges [(make-change - container - {:type :mod-obj - :id (:id dest-shape) - :operations roperations}) - (make-change - container - {:type :reg-objects - :shapes all-parents})] - uchanges [(make-change - container - {:type :mod-obj - :id (:id dest-shape) - :operations uoperations}) - (make-change - container - {:type :reg-objects - :shapes all-parents})]] - (if (seq roperations) - [rchanges uchanges] - empty-changes)) - + (if (empty? roperations) + changes + (let [all-parents (cph/get-parent-ids (:objects container) + (:id dest-shape))] + (-> changes + (update :redo-changes conj (make-change + container + {:type :mod-obj + :id (:id dest-shape) + :operations roperations})) + (update :redo-changes conj (make-change + container + {:type :reg-objects + :shapes all-parents})) + (update :undo-changes conj (make-change + container + {:type :mod-obj + :id (:id dest-shape) + :operations uoperations})) + (update :undo-changes conj (make-change + container + {:type :reg-objects + :shapes all-parents}))))) (let [roperation {:type :set :attr attr :val (get origin-shape attr) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 3d5ea5f36..fa02d1390 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -314,7 +314,7 @@ (geom/move delta) (d/update-when :interactions #(cti/remap-interactions % ids-map objects))) - changes (-> (pcb/add-obj changes new-frame) + changes (-> (pcb/add-object changes new-frame) (pcb/amend-last-change #(assoc % :old-id (:id obj)))) changes (reduce (fn [changes child] @@ -349,8 +349,8 @@ (geom/move delta) (d/update-when :interactions #(cti/remap-interactions % ids-map objects))) - changes (pcb/add-obj changes new-obj {:ignore-touched true}) - changes (-> (pcb/add-obj changes new-obj {:ignore-touched true}) + changes (pcb/add-object changes new-obj {:ignore-touched true}) + changes (-> (pcb/add-object changes new-obj {:ignore-touched true}) (pcb/amend-last-change #(assoc % :old-id (:id obj))))] (reduce (fn [changes child] diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 538dfaddb..a11af4465 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -399,7 +399,7 @@ new-shape (dwc/make-new-shape shape objects selected) changes (-> changes (pcb/with-objects objects) - (pcb/add-obj new-shape) + (pcb/add-object new-shape) (pcb/change-parent parent-id [new-shape] index)) unames (conj unames (:name new-shape)) @@ -480,7 +480,7 @@ ;; Creates the root shape new-shape (dwc/make-new-shape root-shape objects selected) changes (-> (pcb/empty-changes it page-id) - (pcb/add-obj new-shape)) + (pcb/add-object new-shape)) root-attrs (-> (:attrs svg-data) (usvg/format-styles)) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index c1a4e00a0..28895ba49 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -395,12 +395,13 @@ :shortcut (sc/get-tooltip :create-component) :on-click do-add-component}] (when has-component? - [:& menu-entry {:title (tr "workspace.shape.menu.detach-instances-in-bulk") - :shortcut (sc/get-tooltip :detach-component) - :on-click do-detach-component-in-bulk}] - (when (not single?) - [:& menu-entry {:title (tr "workspace.shape.menu.update-components-in-bulk") - :on-click do-update-in-bulk}]))]) + [:* + [:& menu-entry {:title (tr "workspace.shape.menu.detach-instances-in-bulk") + :shortcut (sc/get-tooltip :detach-component) + :on-click do-detach-component-in-bulk}] + (when (not single?) + [:& menu-entry {:title (tr "workspace.shape.menu.update-components-in-bulk") + :on-click do-update-in-bulk}])])]) (when is-component? ;; WARNING: this menu is the same as the context menu at the sidebar. diff --git a/frontend/test/app/test_helpers/pages.cljs b/frontend/test/app/test_helpers/pages.cljs index 13aa606bc..324d969a2 100644 --- a/frontend/test/app/test_helpers/pages.cljs +++ b/frontend/test/app/test_helpers/pages.cljs @@ -101,7 +101,7 @@ objects (wsh/lookup-page-objects state (:id page)) shapes (dwg/shapes-for-grouping objects ids) - [group rchanges uchanges] + [group changes] (dwlh/generate-add-component nil shapes (:objects page) @@ -110,5 +110,5 @@ (swap! idmap assoc label (:id group)) (update state :workspace-data - cp/process-changes rchanges))) + cp/process-changes (:redo-changes changes))))