diff --git a/.serena/memories/common/component-data-model.md b/.serena/memories/common/component-data-model.md index 9b7a9bb322..dab325dec7 100644 --- a/.serena/memories/common/component-data-model.md +++ b/.serena/memories/common/component-data-model.md @@ -40,7 +40,7 @@ Masters are not normally touched through `set-shape-attr`, but touched flags can `make-component-instance` in `app.common.types.container` produces a clean component copy through `update-new-shape`, dissociating attrs such as `:touched`, `:variant-id`, and `:variant-name` on cloned shapes. -`duplicate-component` in `app.common.logic.libraries` creates a new component master by cloning existing component shapes, setting component metadata, and applying a position delta. It does not have the same clean-copy semantics as `make-component-instance`, so inherited attrs on the source can matter. +`duplicate-component` in `app.common.logic.duplicate` creates a new component master by cloning existing component shapes, setting component metadata, and applying a position delta. It does not have the same clean-copy semantics as `make-component-instance`, so inherited attrs on the source can matter. When a bug depends on touched state, identify which cloning path produced the shape before changing sync logic. diff --git a/.serena/memories/common/component-debugging-recipes.md b/.serena/memories/common/component-debugging-recipes.md index 6c1164732c..c4d6ce98d1 100644 --- a/.serena/memories/common/component-debugging-recipes.md +++ b/.serena/memories/common/component-debugging-recipes.md @@ -34,9 +34,9 @@ To inspect operations within the latest `:mod-obj`: To capture what `update-attrs-on-switch` saw during a real UI swap, patch it temporarily in `cljs_repl`: ```clojure -(def orig (deref #'app.common.logic.libraries/update-attrs-on-switch)) +(def orig (deref #'app.common.logic.swap/update-attrs-on-switch)) (def trace-buf (atom [])) -(set! app.common.logic.libraries/update-attrs-on-switch +(set! app.common.logic.swap/update-attrs-on-switch (fn [& args] (swap! trace-buf conj (let [[_ curr prev _ _ origin _] args] @@ -45,7 +45,7 @@ To capture what `update-attrs-on-switch` saw during a real UI swap, patch it tem :origin-ref (select-keys origin [:id :name :x :y :width :height :selrect])})) (apply orig args))) ;; trigger UI action, then inspect @trace-buf -(set! app.common.logic.libraries/update-attrs-on-switch orig) +(set! app.common.logic.swap/update-attrs-on-switch orig) ``` Runtime patching is faster than adding temporary source instrumentation and avoids recompilation cleanup. Restore the var or reload the frontend when finished. diff --git a/.serena/memories/common/component-swap-pipeline.md b/.serena/memories/common/component-swap-pipeline.md index 56c3f95a31..d2fabb22cd 100644 --- a/.serena/memories/common/component-swap-pipeline.md +++ b/.serena/memories/common/component-swap-pipeline.md @@ -12,9 +12,9 @@ Frontend entry points under `frontend/src/app/main/data/workspace/`: For a single swap with `keep-touched? = true`: -1. `cll/generate-component-swap` in `common/src/app/common/logic/libraries.cljc` builds the base changes: remove old shape and instantiate the target component in its place through `generate-new-shape-for-swap`, `generate-instantiate-component`, and `make-component-instance`. +1. `clsw/generate-component-swap` in `common/src/app/common/logic/swap.cljc` builds the base changes: remove old shape and instantiate the target component in its place through `generate-new-shape-for-swap`, `generate-instantiate-component`, and `make-component-instance`. 2. `clv/generate-keep-touched` in `common/src/app/common/logic/variants.cljc` walks pre-swap children, augments each with chain-derived touched flags through `add-touched-from-ref-chain`, finds the equivalent target shape through `find-shape-ref-child-of`, then calls `update-attrs-on-switch`. -3. `update-attrs-on-switch` in `app.common.logic.libraries` decides which touched attrs from the previous shape should be copied onto the freshly instantiated target shape. +3. `update-attrs-on-switch` in `app.common.logic.swap` decides which touched attrs from the previous shape should be copied onto the freshly instantiated target shape. ## update-attrs-on-switch hazards diff --git a/.serena/memories/common/core.md b/.serena/memories/common/core.md index 9fadef36a4..13f79e41db 100644 --- a/.serena/memories/common/core.md +++ b/.serena/memories/common/core.md @@ -7,7 +7,7 @@ - `app.common.data` and `app.common.data.macros`: generic data helpers and performance macros that do not depend on Penpot domain entities. - `app.common.types.*`: shared shape/file/page/component/token data types, schemas, predicates, and entity-local operations. `app.common.types.organization` contains organization schemas, `apply-organization`, and fail-closed organization/team permission rules (`allowed?`, `can-send-invitations?`). - `app.common.files.*`: file-level operations, shape tree helpers, change application, migrations, validation, and undo/redo-related logic. -- `app.common.logic.*`: higher-level workflows/algorithms over files, shapes, components, variants, libraries, tokens, etc. +- `app.common.logic.*`: higher-level workflows/algorithms over files, shapes, components, variants, libraries, tokens, etc. Component logic: `libraries` (`cll`, sync, reset, update main, attribute and placement sync), `instances` (`cli`, create, instantiate, restore, detach), `swap` (`clsw`, swap and the switch's override carry), `duplicate` (`cld`), `library-assets` (`clla`, color and typography sync); `variants` (`clv`) drives the switch. Each may require `libraries`, never the reverse. - `app.common.geom.*`: geometry helpers and transformations. - `app.common.schema` / `app.common.schema.*`: Malli abstraction layer. - `app.common.math`, `app.common.time`, `app.common.uuid`, `app.common.json`, etc.: cross-runtime utilities. diff --git a/.serena/memories/frontend/composable-component-tests.md b/.serena/memories/frontend/composable-component-tests.md index 0bf5ccd5cf..fe289cd34a 100644 --- a/.serena/memories/frontend/composable-component-tests.md +++ b/.serena/memories/frontend/composable-component-tests.md @@ -195,7 +195,7 @@ Two pure runners (no store, JVM + CLJS): (`op-ready`: the token shows on the shape) that must hold before it awaits the pending work. - `n/paste-to-root target`: the clipboard's copy (shape-refs of nested heads advanced, the skipped ref chain's touched merged) and paste at the root; the interpreter pastes `thp/simulate-copy-shape`'s data. - The node, that helper and `copy-selected` all advance through `cll/advance-copied-objects`. A duplicate inside a copy is + The node, that helper and `copy-selected` all advance through `cld/advance-copied-objects` (`app.common.logic.duplicate`). A duplicate inside a copy is refused by `ctk/allow-duplicate?`, and an alt-duplication with no move makes the next edit join its undo group (it expects the drag's move), so neither models a move out of a main. - `n/undo-before-sync` (frontend-only) emits an edit and `dwu/undo` in one step, so the undo diff --git a/common/src/app/common/logic/duplicate.cljc b/common/src/app/common/logic/duplicate.cljc new file mode 100644 index 0000000000..5494bef24b --- /dev/null +++ b/common/src/app/common/logic/duplicate.cljc @@ -0,0 +1,594 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns app.common.logic.duplicate + "Duplicate components and the shapes that hold mains and copies, with the + flows and guides of their frames." + (:require + [app.common.data :as d] + [app.common.files.changes-builder :as pcb] + [app.common.files.helpers :as cfh] + [app.common.files.variant :as cfv] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.logic.instances :as cli] + [app.common.logic.libraries :as cll] + [app.common.logic.variant-properties :as clvp] + [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.common.types.page :as ctp] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape-tree :as ctst] + [app.common.types.shape.interactions :as ctsi] + [app.common.types.shape.layout :as ctsl] + [app.common.types.variant :as ctv] + [app.common.uuid :as uuid] + [clojure.set :as set])) + +(defn- duplicate-component + "Clone the root shape of the component and all children. Generate new + ids from all of them. Optionally set the component-file if the file where the + new component will reside is different than the origin one." + [component new-component-id new-component-file library-data force-id delta variant-id] + (let [main-instance-page (ctf/get-component-page library-data component) + main-instance-shape (ctf/get-component-root library-data component) + delta (or delta (gpt/point (+ (:width main-instance-shape) 50) 0)) + + ids-map (volatile! {}) + inverted-ids-map (volatile! {}) + nested-main-heads (volatile! #{}) + + update-original-shape + (fn [original-shape new-shape] + ; Save some ids for later + (vswap! ids-map assoc (:id original-shape) (:id new-shape)) + (vswap! inverted-ids-map assoc (:id new-shape) (:id original-shape)) + (when (and (ctk/main-instance? original-shape) + (not= (:component-id original-shape) (:id component))) + (vswap! nested-main-heads conj (:id original-shape))) + original-shape) + + update-new-shape + (fn [new-shape _] + (cond-> new-shape + ;; Link the new main to the new component, and re-root it + ;; to the destination file when duplicating across files. + ;; Only the outer main matches `(:id component)`, so + ;; nested main-instances are not touched here. + (= (:component-id new-shape) (:id component)) + (assoc :component-id new-component-id) + + (and (= (:component-id new-shape) (:id component)) + (some? new-component-file) + (not= new-component-file (:component-file new-shape))) + (assoc :component-file new-component-file) + + ; If it is the instance root, add it the variant-id + (and (ctk/instance-root? new-shape) (some? variant-id)) + (assoc :variant-id variant-id) + + :always + (gsh/move delta))) + + [new-instance-shape new-instance-shapes _] + (ctst/clone-shape main-instance-shape + (:parent-id main-instance-shape) + (:objects main-instance-page) + :update-new-shape update-new-shape + :update-original-shape update-original-shape + :force-id force-id) + remap-frame + (fn [shape] + ; Remap all frame-ids internal to the component to the new shapes + (update shape :frame-id + #(get @ids-map % (:frame-id shape)))) + + convert-nested-main + (fn [shape] + ; If there is some nested main instance, convert it into a copy of + ; main nested in the original component. + (let [origin-shape-id (get @inverted-ids-map (:id shape)) + objects (:objects main-instance-page) + parent-ids (cfh/get-parent-ids-seq-with-self objects origin-shape-id)] + (cond-> shape + (@nested-main-heads origin-shape-id) + (dissoc :main-instance) + + (some @nested-main-heads parent-ids) + (assoc :shape-ref origin-shape-id)))) + + xf-shape (comp (map remap-frame) + (map convert-nested-main)) + + new-instance-shapes (into [] xf-shape new-instance-shapes)] + + [new-instance-shape new-instance-shapes])) + +(defn generate-duplicate-component + "Create a new component copied from the one with the given id." + [changes library component-id new-component-id & {:keys [new-component-file new-shape-id apply-changes-local-library? delta new-variant-id page-id]}] + (let [component (ctkl/get-component (:data library) component-id) + new-name (:name component) + + main-instance-page (ctf/get-component-page (:data library) component) + ;; Since variants, we can duplicate a component into another page + target-page-id (or page-id (:id main-instance-page)) + + [new-main-instance-shape new-main-instance-shapes] + (duplicate-component component new-component-id new-component-file (:data library) new-shape-id delta new-variant-id)] + + [new-main-instance-shape + (-> changes + (pcb/with-page-id target-page-id) + (pcb/with-objects (:objects main-instance-page)) + (pcb/add-objects new-main-instance-shapes {:ignore-touched true}) + (pcb/add-component new-component-id + (:path component) + new-name + [] + (:id new-main-instance-shape) + target-page-id + (:annotation component) + (or new-variant-id (:variant-id component)) + (:variant-properties component) + {:apply-changes-local-library? apply-changes-local-library?}) + ;; Update grid layout if the new main instance is inside + (pcb/update-shapes + [(:frame-id new-main-instance-shape)] + (fn [shape objects] + (cond-> shape + (ctsl/grid-layout? shape) + (ctsl/assign-cells objects))) + {:with-objects? true}))])) + +(defn generate-duplicate-flows + [changes shapes page ids-map] + (let [flows (get page :flows) + unames (volatile! (cfh/get-used-names (vals flows))) + has-flow? (partial ctp/get-frame-flow flows)] + + (reduce (fn [changes frame-id] + (let [name (cfh/generate-unique-name "Flow" @unames :immediate-suffix? true) + frame-id (get ids-map frame-id) + flow-id (uuid/next) + new-flow {:id flow-id + :name name + :starting-frame frame-id}] + + (vswap! unames conj name) + (-> changes + (pcb/with-page page) + (pcb/set-flow flow-id new-flow)))) + + changes + (->> shapes + (filter cfh/frame-shape?) + (map :id) + (filter has-flow?))))) + +(defn generate-duplicate-guides + [changes shapes page ids-map delta] + (let [guides (get page :guides) + frames (filter cfh/frame-shape? shapes)] + + ;; FIXME: this can be implemented efficiently just indexing guides + ;; by frame-id instead of iterate over all guides all the time + + (reduce (fn [changes frame] + (let [new-id (get ids-map (:id frame)) + new-frame (gsh/move frame delta)] + + (reduce-kv (fn [changes _ guide] + (if (= (:id frame) (:frame-id guide)) + (let [guide-id (uuid/next) + position (if (= (:axis guide) :x) + (+ (:position guide) (- (:x new-frame) (:x frame))) + (+ (:position guide) (- (:y new-frame) (:y frame)))) + guide {:id guide-id + :frame-id new-id + :position position + :axis (:axis guide)}] + (pcb/set-guide changes guide-id guide)) + changes)) + changes + guides))) + (pcb/with-page changes page) + frames))) + +(defn- duplicate-variant + [changes library component base-pos parent page-id into-new-variant? new-component-file] + (let [component-page (ctpl/get-page (:data library) (:main-instance-page component)) + objects (:objects component-page) + component-shape (get objects (:main-instance-id component)) + orig-pos (gpt/point (:x component-shape) (:y component-shape)) + delta (gpt/subtract base-pos orig-pos) + new-component-id (uuid/next) + [shape changes] (generate-duplicate-component changes + library + (:id component) + new-component-id + {:apply-changes-local-library? true + :delta delta + :new-variant-id (if into-new-variant? nil (:id parent)) + :page-id page-id + :new-component-file new-component-file}) + value (when into-new-variant? + (str ctv/value-prefix + (-> (cfv/extract-properties-values (:data library) objects (:id parent)) + last + :value + count + inc)))] + + [shape + (cond-> changes + into-new-variant? + (clvp/generate-make-shapes-variant [shape] parent) + + ;; If it has the same parent, update the value of the last property + (and into-new-variant? (= (:variant-id component) (:id parent))) + (clvp/generate-update-property-value new-component-id (-> component :variant-properties count dec) value) + + :always + (pcb/change-parent (:id parent) [shape] 0))])) + + +(defn generate-duplicate-component-change + [changes objects page main parent-id frame-id delta libraries library-data ids-map & {:keys [new-component-file]}] + (let [main-id (:id main) + component-id (:component-id main) + ;; Source library file id (where the component was originally + ;; defined). Renamed from `file-id` to make the contrast with + ;; `new-component-file` explicit when duplicating across files. + source-file-id (:component-file main) + component (ctf/get-component libraries source-file-id component-id) + pos (as-> (gsh/move main delta) $ + (gpt/point (:x $) (:y $))) + + parent (get objects parent-id) + + + ;; When we duplicate a variant alone, we will instanciate it + ;; When we duplicate a variant along with its variant-container, we will duplicate it + in-variant-container? (contains? ids-map (:variant-id main)) + + restore-component + #(let [{:keys [shape changes]} + (cli/prepare-restore-component changes + library-data + component-id + page + pos + main-id + parent-id + frame-id)] + [shape changes]) + + + [_shape changes] + (cond + (nil? component) + (restore-component) + + (and (ctk/is-variant? main) in-variant-container?) + (duplicate-variant changes + (get libraries source-file-id) + component + pos + parent + (:id page) + false + new-component-file) + + (ctk/is-variant-container? parent) + (duplicate-variant changes + (get libraries source-file-id) + component + pos + parent + (:id page) + true + new-component-file) + :else + (cli/generate-instantiate-component changes + objects + source-file-id + component-id + pos + page + libraries + main-id + parent-id + frame-id + ids-map + {}))] + changes)) + +(defn generate-duplicate-shape-change + ([changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id] + (generate-duplicate-shape-change changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id (:frame-id obj) (:parent-id obj) false false nil)) + ([changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id variant-props] + (generate-duplicate-shape-change changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id (:frame-id obj) (:parent-id obj) false false variant-props)) + + ([changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id frame-id parent-id duplicating-component? child? variant-props] + (cond + (nil? obj) + changes + + (ctf/is-main-of-known-component? obj libraries) + (generate-duplicate-component-change changes objects page obj parent-id frame-id delta libraries library-data ids-map {:new-component-file file-id}) + + :else + (let [frame? (cfh/frame-shape? obj) + group? (cfh/group-shape? obj) + bool? (cfh/bool-shape? obj) + new-id (ids-map (:id obj)) + parent-id (or parent-id frame-id) + parent (get objects parent-id) + name (:name obj) + + is-component-root? (or (:saved-component-root obj) + ;; Backward compatibility + (:saved-component-root? obj) + (ctk/instance-root? obj)) + duplicating-component? (or duplicating-component? (ctk/instance-head? obj)) + is-component-main? (ctk/main-instance? obj) + + into-component? (and duplicating-component? + (ctn/in-any-component? objects parent)) + + level-delta (if (some? level-delta) + level-delta + (ctn/get-nesting-level-delta objects obj parent)) + + ;; a swapped head that leaves every copy level + rehead? (and (ctk/swapped? obj) + (pos? level-delta) + (>= level-delta + (->> (ctn/get-parent-copy-heads objects obj) + (remove #(= (:id %) (:id obj))) + (count)))) + new-shape-ref (if rehead? + (cll/swap-content-root-id libraries obj) + (ctf/advance-shape-ref nil page libraries obj level-delta {:include-deleted? true})) + + regenerate-component + (fn [changes shape] + (let [[_ changes] (cli/generate-add-component-changes changes shape objects file-id (:id page) variant-props)] + changes)) + + new-obj + (-> obj + (assoc :id new-id + :name name + :parent-id parent-id + :frame-id frame-id) + + (dissoc :shapes + :use-for-thumbnail) + + (cond-> (not is-component-root?) + (dissoc :main-instance)) + + (cond-> into-component? + (dissoc :component-root)) + + (cond-> (and (ctk/instance-head? obj) + (not into-component?)) + (assoc :component-root true)) + + (cond-> (or frame? group? bool?) + (assoc :shapes [])) + + (cond-> (and (some? new-shape-ref) + (not= new-shape-ref (:shape-ref obj))) + (assoc :shape-ref new-shape-ref)) + + (cond-> rehead? + (ctk/remove-swap-group)) + + (gsh/move delta) + (d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)) + + (cond-> (ctsl/grid-layout? obj) + (ctsl/remap-grid-cells ids-map)) + + (cond-> (ctk/is-variant-container? parent) + (assoc :variant-id parent-id)) + + (cond-> (and (ctk/is-variant? obj) (not (ctk/is-variant-container? parent))) + (-> (assoc :name (ctv/variant-name-to-name obj)) + (dissoc :variant-id)))) + + new-obj (cond-> new-obj + (not duplicating-component?) + (ctk/detach-shape)) + + ;; We want the first added object to touch it's parent, but not subsequent children + changes (-> (pcb/add-object changes new-obj {:ignore-touched (and duplicating-component? child?)}) + (pcb/amend-last-change #(assoc % :old-id (:id obj))) + (cond-> (ctsl/grid-layout? objects (:parent-id obj)) + (-> (pcb/update-shapes [(:parent-id obj)] ctsl/assign-cells {:with-objects? true}) + (pcb/reorder-grid-children [(:parent-id obj)])))) + + changes (cond-> changes + (and is-component-root? is-component-main?) + (regenerate-component new-obj)) + + ;; This is needed for the recursive call to find the new object as parent + page' (ctst/add-shape (:id new-obj) + new-obj + {:objects objects} + (:frame-id new-obj) + (:parent-id new-obj) + nil + true)] + + (reduce (fn [changes child] + (generate-duplicate-shape-change changes + (:objects page') + page + unames + update-unames! + ids + ids-map + child + delta + ;; the children of a swapped head refer into its own component + (if (ctk/swapped? obj) 0 level-delta) + libraries + library-data + file-id + (if frame? new-id frame-id) + new-id + duplicating-component? + true + variant-props)) + changes + (map (d/getf objects) (:shapes obj))))))) + +(defn advance-copied-objects + "The clipboard's `objects` (`selected` and their children, copied from + `page` of `file`) with the copies nested in other copies advanced: each + copy head that the copy takes out of `n` copy levels, and its children, + get the shape ref `n` levels up the ref chain and the touched groups of + the chain they skip, so they keep the values the skipped mains gave them." + [file page libraries objects selected] + (let [container (ctn/make-container page :page) + advance-shape (fn [level-delta objects shape] + (let [new-shape-ref (ctf/advance-shape-ref file page libraries shape level-delta + {:include-deleted? true})] + (cond-> objects + (and (some? new-shape-ref) (not= new-shape-ref (:shape-ref shape))) + (-> (assoc-in [(:id shape) :shape-ref] new-shape-ref) + (assoc-in [(:id shape) :touched] + (set/union + (or (:touched shape) #{}) + (cli/inherited-touched + shape + (ctf/get-touched-from-ref-chain-until-target-ref + container libraries shape new-shape-ref)))))))) + detach-copy (fn [objects shape] + (reduce #(update %1 %2 ctk/detach-shape) + objects + (cfh/get-children-ids-with-self objects (:id shape)))) + advance-copy (fn [objects shape] + (if (and (ctk/instance-head? shape) (not (ctk/main-instance? shape))) + (let [level-delta (ctn/get-nesting-level-delta (:objects page) shape (get-in page [:objects uuid/zero]))] + (cond + (not (pos? level-delta)) + objects + + ;; the copy leaves every copy level + (ctk/swapped? shape) + (if-let [root-id (cll/swap-content-root-id libraries shape)] + (update objects (:id shape) cli/rehead-swapped root-id) + (detach-copy objects shape)) + + :else + (let [unchanged-ids (cli/swapped-descendant-ids objects (:id shape))] + (reduce (partial advance-shape level-delta) + objects + (->> (cfh/get-children-with-self objects (:id shape)) + (remove #(contains? unchanged-ids (:id %)))))))) + objects))] + (reduce advance-copy + objects + (mapcat #(ctn/get-child-heads objects %) selected)))) + +(defn generate-duplicate-changes + "Prepare objects to duplicate: generate new id, give them unique names, + move to the desired position, and recalculate parents and frames as needed." + [changes all-objects page ids delta libraries library-data file-id & {:keys [variant-props alt-duplication?]}] + (let [shapes (map (d/getf all-objects) ids) + unames (volatile! (cfh/get-used-names (:objects page))) + update-unames! (fn [new-name] (vswap! unames conj new-name)) + all-ids (reduce #(into %1 (cons %2 (cfh/get-children-ids all-objects %2))) (d/ordered-set) ids) + + ;; We need ids-map for remapping the grid layout. But when duplicating the guides + ;; we calculate a new one because the components will have created new shapes. + ids-map (into {} (map #(vector % (uuid/next))) all-ids) + + + ;; If there is an alt-duplication we change to root + ;; For variants so the copy is made as a child of root + ;; This is because inside a variant-container can't be a copy + ;; For other shape this way the layout won't be changed when duplicated + ;; and if you move outside the layout will not change + shapes (map (fn [shape] + (cond-> shape + alt-duplication? + (assoc :parent-id uuid/zero :frame-id uuid/zero))) + shapes) + + + changes (-> changes + (pcb/with-page page) + (pcb/with-objects all-objects) + (pcb/with-library-data library-data)) + + changes + (->> shapes + (reduce #(generate-duplicate-shape-change %1 + all-objects + page + unames + update-unames! + ids + ids-map + %2 + delta + nil + libraries + library-data + file-id + variant-props) + changes)) + + ;; We need to check the changes to get the ids-map + ids-map + (into {} + (comp + (filter #(= :add-obj (:type %))) + (map #(vector (:old-id %) (-> % :obj :id)))) + (:redo-changes changes))] + + (-> changes + (generate-duplicate-flows shapes page ids-map) + (generate-duplicate-guides shapes page ids-map delta)))) + +(defn generate-duplicate-changes-update-indices + "Updates the changes to correctly set the indexes of the duplicated objects, + depending on the index of the original object respect their parent." + [changes objects ids] + (let [;; index-map is a map that goes from parent-id => vector([id index-in-parent]) + index-map (reduce (fn [index-map id] + (let [parent-id (get-in objects [id :parent-id]) + parent-index (cfh/get-position-on-parent objects id)] + (update index-map parent-id (fnil conj []) [id parent-index]))) + {} + ids) + + inc-indices + (fn [[offset result] [id index]] + [(inc offset) (conj result [id (+ index offset)])]) + + fix-indices + (fn [_ entry] + (->> entry + (sort-by second) + (reduce inc-indices [1 []]) + (second) + (into {}))) + + objects-indices (->> index-map (d/mapm fix-indices) (vals) (reduce merge))] + + (pcb/amend-changes + changes + (fn [change] + (assoc change :index (get objects-indices (:old-id change))))))) diff --git a/common/src/app/common/logic/instances.cljc b/common/src/app/common/logic/instances.cljc new file mode 100644 index 0000000000..e62fce510a --- /dev/null +++ b/common/src/app/common/logic/instances.cljc @@ -0,0 +1,421 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns app.common.logic.instances + "Components and their instances: create a component from shapes, instantiate + it, restore a deleted one, and detach a copy from its component (the copy + heads below it go up a nesting level)." + (:require + [app.common.data :as d] + [app.common.files.changes-builder :as pcb] + [app.common.files.helpers :as cfh] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.logging :as log] + [app.common.logic.libraries :as cll] + [app.common.logic.variant-properties :as clvp] + [app.common.path-names :as cpn] + [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape.layout :as ctsl] + [app.common.uuid :as uuid] + [clojure.set :as set])) + +(defn generate-instantiate-component + "Generate changes to create a new instance from a component." + ([changes objects file-id component-id position page libraries] + (generate-instantiate-component changes objects file-id component-id position page libraries nil nil nil {})) + ([changes objects file-id component-id position page libraries old-id parent-id frame-id params] + (generate-instantiate-component changes objects file-id component-id position page libraries old-id parent-id frame-id {} params)) + ([changes objects file-id component-id position page libraries old-id parent-id frame-id ids-map + {:keys [force-frame?] + :or {force-frame? false}}] + + (let [component (ctf/get-component libraries file-id component-id) + library (get libraries file-id) + parent (when parent-id (get objects parent-id)) + + ;; When we are intanciating a variant, it can't be on a variant-container + parent (when parent + (if (and (ctk/is-variant? component) + (ctk/is-variant-container? parent)) + (get objects (:parent-id parent)) + parent)) + parent-id (d/nilv (:id parent) parent-id) + frame-id (d/nilv (:frame-id parent) frame-id) + + [new-shape new-shapes] + (ctn/make-component-instance page + component + (:data library) + position + (cond-> {} + (contains? ids-map old-id) + (assoc :force-id (get ids-map old-id)) + + force-frame? + (assoc :force-frame-id frame-id))) + + first-shape + (cond-> (first new-shapes) + (not (nil? parent-id)) + (assoc :parent-id parent-id) + (and (not (nil? parent)) (= :frame (:type parent))) + (assoc :frame-id (:id parent)) + (and (not (nil? parent)) (not= :frame (:type parent))) + (assoc :frame-id (:frame-id parent)) + (and (not (nil? parent)) (ctn/in-any-component? objects parent)) + (dissoc :component-root) + (and (nil? parent) (not (nil? frame-id))) + (assoc :frame-id frame-id)) + + ;; on copy/paste old id is used later to reorder the paster layers + changes + (cond-> (pcb/add-object changes first-shape {:ignore-touched true}) + (some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id))) + + duplicated-parent? + (->> ids-map vals (some #(= % (:parent-id first-shape)))) + + changes + (if (and (ctsl/grid-layout? objects (:parent-id first-shape)) (not duplicated-parent?)) + (let [target-cell (-> position meta :cell) + + [row column] + (when (some? target-cell) + [(:row target-cell) (:column target-cell)])] + (-> changes + (pcb/update-shapes + [(:parent-id first-shape)] + (fn [shape objects] + (-> shape + (ctsl/assign-cells objects) + (cond-> (and (some? row) (some? column)) + (-> (ctsl/push-into-cell [(:id first-shape)] row column) + (ctsl/assign-cells objects))))) + {:with-objects? true}) + (pcb/reorder-grid-children [(:parent-id first-shape)]))) + changes) + + changes + (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) + changes + (rest new-shapes))] + + [new-shape changes]))) + +(declare generate-detach-recursive) +(declare generate-advance-nesting-level) +(declare generate-detach-immediate) +(declare generate-rehead-swapped) + +(defn generate-detach-instance + "Generate changes to remove the links between a shape and all its children + with a component." + [changes container libraries shape-id] + (let [shape (ctn/get-shape container shape-id)] + (cll/shape-log :debug shape-id container + :msg "Detach instance" :shape-id shape-id :container (:id container)) + (generate-detach-recursive changes container libraries shape-id true (true? (:component-root shape))))) + +(defn- generate-detach-recursive + [changes container libraries shape-id first component-root?] + (let [shape (ctn/get-shape container shape-id)] + (cll/shape-log :trace shape-id container + :msg " Processing" :shape-id shape-id) + (if (and (ctk/instance-head? shape) (not first)) + ; Subinstances are not detached + (cond-> changes + component-root? + ; If the initial shape was component-root, first level subinstances are converted in top instances + (pcb/update-shapes [shape-id] #(do (log/trace :msg " -> promote to root") + (assoc % :component-root true))) + + (ctk/swapped? shape) + ; A swapped head left with no copy around it is a copy of its own component + (generate-rehead-swapped container libraries shape) + + (not (ctk/swapped? shape)) + ; Near shape-ref of shape and children need to be advanced one level + (generate-advance-nesting-level nil container libraries (:id shape))) + + ;; Otherwise, detach the shape and all children + (let [children-ids (:shapes shape)] + (log/trace :msg " -> detach") + (reduce #(generate-detach-recursive %1 container libraries %2 false component-root?) + (pcb/update-shapes changes [(:id shape)] ctk/detach-shape) + children-ids))))) + +(defn rehead-swapped + "Make a swapped head a plain copy of its own component, for when it leaves + every copy level." + [shape root-id] + (-> shape + (assoc :shape-ref root-id) + (ctk/remove-swap-group))) + +(defn- generate-rehead-swapped + [changes container libraries shape] + (if-let [root-id (cll/swap-content-root-id libraries shape)] + (pcb/update-shapes changes [(:id shape)] #(rehead-swapped % root-id)) + (generate-detach-immediate changes container (:id shape) (atom #{})))) + +(defn swapped-descendant-ids + "The ids of the shapes below a swapped head in the subtree of the shape. + They refer into the head's own component, so they do not change when the + head changes its nesting level." + [objects shape-id] + (into #{} + (comp (filter ctk/swapped?) + (mapcat #(cfh/get-children-ids objects (:id %)))) + (cfh/get-children-with-self objects shape-id))) + +(defn inherited-touched + "The touched groups a copy shape takes from the main shape it skips when it + goes up a nesting level. A swapped head takes only the placement ones, + since its content does not come from that main." + [shape ref-touched] + (if (ctk/swapped? shape) + (set/intersection ref-touched cll/placement-groups) + ref-touched)) + +(defn- generate-advance-nesting-level + [changes file container libraries shape-id] + (log/trace :msg " -> advance-nesting-level") + (let [detached-ids (atom #{}) + unchanged-ids (swapped-descendant-ids (:objects container) shape-id) + children (->> (cfh/get-children-with-self (:objects container) shape-id) ;; TODO: this function should be refactored to be a recursive tree traversal. + (remove #(contains? unchanged-ids (:id %)))) + skip-near (fn [changes shape] ;; this way we could shake the tree more easily when detaching shapes + (cll/shape-log :trace (:id shape) container ;; and perhaps even allow to recover nested instances that have been + :msg " * advancing" :shape-id (:id shape)) ;; swapped and so we can access the main instance again. + (if (contains? @detached-ids (:id shape)) + (do (log/trace :msg " (detached)") + changes) + (let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})] + (cond-> changes + (some? (:shape-ref ref-shape)) + (pcb/update-shapes [(:id shape)] #(do (log/trace :msg (str " (advanced to " (:shape-ref ref-shape) ")")) + (assoc % :shape-ref (:shape-ref ref-shape)))) + + ;; When advancing level, the touched groups of the ref-shape must be + ;; merged into the current shape, because they refer to the new + ;; referenced shape. + (some? ref-shape) + (pcb/update-shapes + [(:id shape)] + #(do (log/trace :msg " (merge touched)") + (log/trace :msg (str " (ref-shape: " (:id ref-shape) ")")) + (log/trace :msg (str " (ref touched: " (:touched ref-shape) ")")) + (assoc % :touched + (set/union (:touched shape) + (inherited-touched shape (or (:touched ref-shape) #{})))))) + + ;; If we can't get the ref-shape (e.g. it's in an external library not linked), + ;: we can't do a suitable advance. So it's better to detach the shape and all its + ;; children (and add to detached-ids so they are not processed again). + (nil? ref-shape) + (generate-detach-immediate container (:id shape) detached-ids)))))] + + (reduce skip-near changes children))) + +(defn- generate-detach-immediate + [changes container shape-id detached-ids] + (let [shape-and-children (cfh/get-children-ids-with-self (:objects container) shape-id)] + (log/trace :msg " (cannot advance; detach shape and children)") + (swap! detached-ids #(into % shape-and-children)) + (pcb/update-shapes changes shape-and-children ctk/detach-shape))) + +(defn prepare-restore-component + ([changes library-data component-id current-page] + (let [component (ctkl/get-deleted-component library-data component-id) + page (or (ctf/get-component-page library-data component) + (when (some #(= (:id current-page) %) (:pages library-data)) ;; If the page doesn't belong to the library, it's not valid + current-page) + (ctpl/get-last-page library-data))] + (prepare-restore-component changes library-data component-id page nil nil nil nil))) + + ([changes library-data component-id page position old-id parent-id frame-id] + (let [library-data (or (pcb/get-library-data changes) library-data) + component (ctkl/get-deleted-component library-data component-id) + objects (or (pcb/get-objects changes) (:objects page)) + parent (get objects parent-id) + main-inst (get-in component [:objects (:main-instance-id component)]) + inside-component? (some? (ctn/get-instance-root (:objects page) parent)) + shapes (cfh/get-children-with-self (:objects component) (:main-instance-id component)) + + is-variant? (ctk/is-variant? component) + + orig-pos (gpt/point (:x main-inst) (:y main-inst)) + delta (if position + (gpt/subtract position orig-pos) + (gpt/point 0 0)) + minusdelta (gpt/point (- (:x delta)) (- (:y delta))) + + moved-shapes (map #(gsh/move % delta) shapes) + + first-shape (cond-> (first moved-shapes) + (not (nil? parent-id)) + (assoc :parent-id parent-id) + (not (nil? frame-id)) + (assoc :frame-id frame-id) + (and (nil? frame-id) parent (= :frame (:type parent))) + (assoc :frame-id parent-id) + (and (nil? frame-id) parent (not= :frame (:type parent))) + (assoc :frame-id (:frame-id parent)) + inside-component? + (dissoc :component-root) + (not inside-component?) + (assoc :component-root true)) + + restoring-into-parent (get objects (:parent-id first-shape)) + + changes (-> changes + (pcb/with-page page) + (pcb/with-objects (:objects page)) + (pcb/with-library-data library-data)) + changes (cond-> (pcb/add-object changes first-shape {:ignore-touched true}) + (some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id))) ; on copy/paste old id is used later to reorder the paster layers + changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) + changes + (rest moved-shapes)) + changes (cond-> changes + ;; Transform variant info into name when restoring into a parent that is not a variant-container, + ;; or when restoring into a variant-container that doesn't exists anymore + (and is-variant? + (or (and parent (not (ctk/is-variant-container? parent))) + (nil? restoring-into-parent))) + (clvp/generate-make-shapes-no-variant [first-shape]) + ;; Add variant info and rename when restoring into a variant-container + (ctk/is-variant-container? restoring-into-parent) + (clvp/generate-make-shapes-variant [first-shape] restoring-into-parent))] + {:changes (pcb/restore-component changes component-id (:id page) minusdelta) + :shape (first moved-shapes)}))) + +(defn generate-add-component-changes + [changes root objects file-id page-id variant-props] + (let [name (:name root) + variant-id (when (ctk/is-variant? root) (:parent-id root)) + props (when (ctk/is-variant? root) (get variant-props (:component-id root))) + + [path name] (cpn/split-group-name name) + + [root-shape updated-shapes] + (ctn/convert-shape-in-component root objects file-id) + + changes (-> changes + (pcb/add-component (:id root-shape) + path + name + updated-shapes + (:id root) + page-id + nil + variant-id + props))] + [root-shape changes])) + +(defn generate-add-component + "If there is exactly one id, and it's a frame, and not already a component, + use it as root. Otherwise, create a frame that contains all ids. Then, make a + component with it, and link all shapes to their corresponding one in the component." + [changes shapes objects page-id file-id prepare-create-board] + + (let [changes (pcb/with-page-id changes page-id) + shapes-count (count shapes) + first-shape (first shapes) + + from-singe-frame? + (and (= 1 shapes-count) + (cfh/frame-shape? first-shape)) + + [root changes old-root-ids] + (if (and (= shapes-count 1) + (cfh/frame-shape? first-shape) + (not (ctk/instance-head? first-shape))) + [first-shape + (-> changes + (pcb/with-page-id page-id) + (pcb/with-objects objects)) + (:shapes first-shape)] + + (let [root-name (if (= 1 shapes-count) + (:name first-shape) + "Component 1") + + shape-ids (into (d/ordered-set) (map :id) shapes) + + [root changes] + (prepare-create-board changes + (uuid/next) + (:parent-id first-shape) + objects + shape-ids + nil + root-name + true)] + + [root changes shape-ids])) + + changes + (cond-> changes + (not from-singe-frame?) + (pcb/update-shapes + (:shapes root) + (fn [shape] + (assoc shape :constraints-h :scale :constraints-v :scale)))) + + objects' (assoc objects (:id root) root) + + [root-shape changes] (generate-add-component-changes changes root objects' file-id page-id nil) + + changes (pcb/update-shapes changes + old-root-ids + #(dissoc % :component-root) + [:component-root])] + + [root (:id root-shape) changes])) + +(defn generate-restore-component + "Restore a deleted component, with the given id, in the given file library." + [changes library-data component-id library-id current-page objects] + (let [{:keys [changes shape]} (prepare-restore-component changes library-data component-id current-page) + parent-id (:parent-id shape) + + insert-before? + (and (ctsl/flex-layout? objects parent-id) + (not (ctsl/reverse? objects parent-id))) + + objects + (-> objects + (assoc (:id shape) shape) + (cond-> (and (some? parent-id) insert-before?) + (update-in [parent-id :shapes] #(d/concat-vec [(:id shape)] %))) + (cond-> (and (some? parent-id) (not insert-before?)) + (update-in [parent-id :shapes] conj (:id shape)))) + + ;; Adds a resize-parents operation so the groups are updated. We add all the new objects + new-objects-ids (->> changes :redo-changes (filter #(= (:type %) :add-obj)) (mapv :id)) + changes (-> changes + (pcb/with-objects objects) + (pcb/resize-parents new-objects-ids) + ;; Fix the order of the children inside the parent + (cond-> (ctsl/any-layout? objects parent-id) + (pcb/reorder-children parent-id (get-in objects [parent-id :shapes]))))] + (assoc changes :file-id library-id))) + +(defn generate-detach-component + "Generate changes for remove all references to components in the shape, + with the given id and all its children, at the current page." + [changes id file page-id libraries] + (let [container (ctn/get-container file :page page-id)] + (-> changes + (pcb/with-container container) + (pcb/with-objects (:objects container)) + (generate-detach-instance container libraries id)))) diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index aedeffe27f..d1eb582a3b 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -5,41 +5,37 @@ ;; Copyright (c) KALEIDOS SUBSIDIARY SL (ns app.common.logic.libraries + "Component sync: the changes that bring copies up to date with their mains + (sync and reset) and mains up to date with a copy (update main), and the + sync of a file with the libraries it uses (`generate-sync-file-changes`). + Creating, detaching, swapping and duplicating components live in + `app.common.logic.instances`, `.swap` and `.duplicate`; colors and + typographies in `app.common.logic.library-assets`." #?(:cljs (:require-macros [app.common.logic.libraries :refer [shape-log container-log]])) (:require [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] - [app.common.files.variant :as cfv] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.common :as gco] [app.common.logging :as log] - [app.common.logic.shapes :as cls] - [app.common.logic.variant-properties :as clvp] + [app.common.logic.library-assets :as clla] [app.common.math :as mth] [app.common.path-names :as cpn] [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] - [app.common.types.library :as ctl] [app.common.types.modifiers :as ctm] - [app.common.types.page :as ctp] - [app.common.types.pages-list :as ctpl] - [app.common.types.path.segment :as segment] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] - [app.common.types.shape.interactions :as ctsi] [app.common.types.shape.layout :as ctsl] [app.common.types.text :as txt] [app.common.types.token :as cto] [app.common.types.typography :as cty] - [app.common.types.variant :as ctv] - [app.common.uuid :as uuid] [clojure.set :as set])) ;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default @@ -66,7 +62,7 @@ (conj flex-child-attrs :constraints-h :constraints-v :fixed-scroll)) ;; Touched groups of the placement attributes and of the position -(def ^:private placement-groups +(def placement-groups (into #{:geometry-group} (map #(ctk/resolve-sync-group :rect %)) placement-attrs)) (defn enabled-shape? @@ -95,7 +91,6 @@ (declare generate-sync-container) (declare generate-sync-shape) -(declare generate-sync-text-shape) (declare uses-assets?) (declare generate-sync-shape-direct) @@ -131,251 +126,7 @@ (let [uuid-str (str uuid)] (subs uuid-str (- (count uuid-str) 6)))) -;; ---- Components and instances creation ---- - -(defn- duplicate-component - "Clone the root shape of the component and all children. Generate new - ids from all of them. Optionally set the component-file if the file where the - new component will reside is different than the origin one." - [component new-component-id new-component-file library-data force-id delta variant-id] - (let [main-instance-page (ctf/get-component-page library-data component) - main-instance-shape (ctf/get-component-root library-data component) - delta (or delta (gpt/point (+ (:width main-instance-shape) 50) 0)) - - ids-map (volatile! {}) - inverted-ids-map (volatile! {}) - nested-main-heads (volatile! #{}) - - update-original-shape - (fn [original-shape new-shape] - ; Save some ids for later - (vswap! ids-map assoc (:id original-shape) (:id new-shape)) - (vswap! inverted-ids-map assoc (:id new-shape) (:id original-shape)) - (when (and (ctk/main-instance? original-shape) - (not= (:component-id original-shape) (:id component))) - (vswap! nested-main-heads conj (:id original-shape))) - original-shape) - - update-new-shape - (fn [new-shape _] - (cond-> new-shape - ;; Link the new main to the new component, and re-root it - ;; to the destination file when duplicating across files. - ;; Only the outer main matches `(:id component)`, so - ;; nested main-instances are not touched here. - (= (:component-id new-shape) (:id component)) - (assoc :component-id new-component-id) - - (and (= (:component-id new-shape) (:id component)) - (some? new-component-file) - (not= new-component-file (:component-file new-shape))) - (assoc :component-file new-component-file) - - ; If it is the instance root, add it the variant-id - (and (ctk/instance-root? new-shape) (some? variant-id)) - (assoc :variant-id variant-id) - - :always - (gsh/move delta))) - - [new-instance-shape new-instance-shapes _] - (ctst/clone-shape main-instance-shape - (:parent-id main-instance-shape) - (:objects main-instance-page) - :update-new-shape update-new-shape - :update-original-shape update-original-shape - :force-id force-id) - remap-frame - (fn [shape] - ; Remap all frame-ids internal to the component to the new shapes - (update shape :frame-id - #(get @ids-map % (:frame-id shape)))) - - convert-nested-main - (fn [shape] - ; If there is some nested main instance, convert it into a copy of - ; main nested in the original component. - (let [origin-shape-id (get @inverted-ids-map (:id shape)) - objects (:objects main-instance-page) - parent-ids (cfh/get-parent-ids-seq-with-self objects origin-shape-id)] - (cond-> shape - (@nested-main-heads origin-shape-id) - (dissoc :main-instance) - - (some @nested-main-heads parent-ids) - (assoc :shape-ref origin-shape-id)))) - - xf-shape (comp (map remap-frame) - (map convert-nested-main)) - - new-instance-shapes (into [] xf-shape new-instance-shapes)] - - [new-instance-shape new-instance-shapes])) - -(defn generate-duplicate-component - "Create a new component copied from the one with the given id." - [changes library component-id new-component-id & {:keys [new-component-file new-shape-id apply-changes-local-library? delta new-variant-id page-id]}] - (let [component (ctkl/get-component (:data library) component-id) - new-name (:name component) - - main-instance-page (ctf/get-component-page (:data library) component) - ;; Since variants, we can duplicate a component into another page - target-page-id (or page-id (:id main-instance-page)) - - [new-main-instance-shape new-main-instance-shapes] - (duplicate-component component new-component-id new-component-file (:data library) new-shape-id delta new-variant-id)] - - [new-main-instance-shape - (-> changes - (pcb/with-page-id target-page-id) - (pcb/with-objects (:objects main-instance-page)) - (pcb/add-objects new-main-instance-shapes {:ignore-touched true}) - (pcb/add-component new-component-id - (:path component) - new-name - [] - (:id new-main-instance-shape) - target-page-id - (:annotation component) - (or new-variant-id (:variant-id component)) - (:variant-properties component) - {:apply-changes-local-library? apply-changes-local-library?}) - ;; Update grid layout if the new main instance is inside - (pcb/update-shapes - [(:frame-id new-main-instance-shape)] - (fn [shape objects] - (cond-> shape - (ctsl/grid-layout? shape) - (ctsl/assign-cells objects))) - {:with-objects? true}))])) - - -(defn generate-instantiate-component - "Generate changes to create a new instance from a component." - ([changes objects file-id component-id position page libraries] - (generate-instantiate-component changes objects file-id component-id position page libraries nil nil nil {})) - ([changes objects file-id component-id position page libraries old-id parent-id frame-id params] - (generate-instantiate-component changes objects file-id component-id position page libraries old-id parent-id frame-id {} params)) - ([changes objects file-id component-id position page libraries old-id parent-id frame-id ids-map - {:keys [force-frame?] - :or {force-frame? false}}] - - (let [component (ctf/get-component libraries file-id component-id) - library (get libraries file-id) - parent (when parent-id (get objects parent-id)) - - ;; When we are intanciating a variant, it can't be on a variant-container - parent (when parent - (if (and (ctk/is-variant? component) - (ctk/is-variant-container? parent)) - (get objects (:parent-id parent)) - parent)) - parent-id (d/nilv (:id parent) parent-id) - frame-id (d/nilv (:frame-id parent) frame-id) - - [new-shape new-shapes] - (ctn/make-component-instance page - component - (:data library) - position - (cond-> {} - (contains? ids-map old-id) - (assoc :force-id (get ids-map old-id)) - - force-frame? - (assoc :force-frame-id frame-id))) - - first-shape - (cond-> (first new-shapes) - (not (nil? parent-id)) - (assoc :parent-id parent-id) - (and (not (nil? parent)) (= :frame (:type parent))) - (assoc :frame-id (:id parent)) - (and (not (nil? parent)) (not= :frame (:type parent))) - (assoc :frame-id (:frame-id parent)) - (and (not (nil? parent)) (ctn/in-any-component? objects parent)) - (dissoc :component-root) - (and (nil? parent) (not (nil? frame-id))) - (assoc :frame-id frame-id)) - - ;; on copy/paste old id is used later to reorder the paster layers - changes - (cond-> (pcb/add-object changes first-shape {:ignore-touched true}) - (some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id))) - - duplicated-parent? - (->> ids-map vals (some #(= % (:parent-id first-shape)))) - - changes - (if (and (ctsl/grid-layout? objects (:parent-id first-shape)) (not duplicated-parent?)) - (let [target-cell (-> position meta :cell) - - [row column] - (when (some? target-cell) - [(:row target-cell) (:column target-cell)])] - (-> changes - (pcb/update-shapes - [(:parent-id first-shape)] - (fn [shape objects] - (-> shape - (ctsl/assign-cells objects) - (cond-> (and (some? row) (some? column)) - (-> (ctsl/push-into-cell [(:id first-shape)] row column) - (ctsl/assign-cells objects))))) - {:with-objects? true}) - (pcb/reorder-grid-children [(:parent-id first-shape)]))) - changes) - - changes - (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) - changes - (rest new-shapes))] - - [new-shape changes]))) - -(declare generate-detach-recursive) -(declare generate-advance-nesting-level) -(declare generate-detach-immediate) -(declare generate-rehead-swapped) - -(defn generate-detach-instance - "Generate changes to remove the links between a shape and all its children - with a component." - [changes container libraries shape-id] - (let [shape (ctn/get-shape container shape-id)] - (shape-log :debug shape-id container - :msg "Detach instance" :shape-id shape-id :container (:id container)) - (generate-detach-recursive changes container libraries shape-id true (true? (:component-root shape))))) - -(defn- generate-detach-recursive - [changes container libraries shape-id first component-root?] - (let [shape (ctn/get-shape container shape-id)] - (shape-log :trace shape-id container - :msg " Processing" :shape-id shape-id) - (if (and (ctk/instance-head? shape) (not first)) - ; Subinstances are not detached - (cond-> changes - component-root? - ; If the initial shape was component-root, first level subinstances are converted in top instances - (pcb/update-shapes [shape-id] #(do (log/trace :msg " -> promote to root") - (assoc % :component-root true))) - - (ctk/swapped? shape) - ; A swapped head left with no copy around it is a copy of its own component - (generate-rehead-swapped container libraries shape) - - (not (ctk/swapped? shape)) - ; Near shape-ref of shape and children need to be advanced one level - (generate-advance-nesting-level nil container libraries (:id shape))) - - ;; Otherwise, detach the shape and all children - (let [children-ids (:shapes shape)] - (log/trace :msg " -> detach") - (reduce #(generate-detach-recursive %1 container libraries %2 false component-root?) - (pcb/update-shapes changes [(:id shape)] ctk/detach-shape) - children-ids))))) - -(defn- swap-content-root-id +(defn swap-content-root-id "The id of the main root a swapped head takes its content from." [libraries shape] (:main-instance-id (ctf/get-component libraries @@ -383,152 +134,6 @@ (:component-id shape) :include-deleted? true))) -(defn- rehead-swapped - "Make a swapped head a plain copy of its own component, for when it leaves - every copy level." - [shape root-id] - (-> shape - (assoc :shape-ref root-id) - (ctk/remove-swap-group))) - -(defn- generate-rehead-swapped - [changes container libraries shape] - (if-let [root-id (swap-content-root-id libraries shape)] - (pcb/update-shapes changes [(:id shape)] #(rehead-swapped % root-id)) - (generate-detach-immediate changes container (:id shape) (atom #{})))) - -(defn- swapped-descendant-ids - "The ids of the shapes below a swapped head in the subtree of the shape. - They refer into the head's own component, so they do not change when the - head changes its nesting level." - [objects shape-id] - (into #{} - (comp (filter ctk/swapped?) - (mapcat #(cfh/get-children-ids objects (:id %)))) - (cfh/get-children-with-self objects shape-id))) - -(defn- inherited-touched - "The touched groups a copy shape takes from the main shape it skips when it - goes up a nesting level. A swapped head takes only the placement ones, - since its content does not come from that main." - [shape ref-touched] - (if (ctk/swapped? shape) - (set/intersection ref-touched placement-groups) - ref-touched)) - -(defn- generate-advance-nesting-level - [changes file container libraries shape-id] - (log/trace :msg " -> advance-nesting-level") - (let [detached-ids (atom #{}) - unchanged-ids (swapped-descendant-ids (:objects container) shape-id) - children (->> (cfh/get-children-with-self (:objects container) shape-id) ;; TODO: this function should be refactored to be a recursive tree traversal. - (remove #(contains? unchanged-ids (:id %)))) - skip-near (fn [changes shape] ;; this way we could shake the tree more easily when detaching shapes - (shape-log :trace (:id shape) container ;; and perhaps even allow to recover nested instances that have been - :msg " * advancing" :shape-id (:id shape)) ;; swapped and so we can access the main instance again. - (if (contains? @detached-ids (:id shape)) - (do (log/trace :msg " (detached)") - changes) - (let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})] - (cond-> changes - (some? (:shape-ref ref-shape)) - (pcb/update-shapes [(:id shape)] #(do (log/trace :msg (str " (advanced to " (:shape-ref ref-shape) ")")) - (assoc % :shape-ref (:shape-ref ref-shape)))) - - ;; When advancing level, the touched groups of the ref-shape must be - ;; merged into the current shape, because they refer to the new - ;; referenced shape. - (some? ref-shape) - (pcb/update-shapes - [(:id shape)] - #(do (log/trace :msg " (merge touched)") - (log/trace :msg (str " (ref-shape: " (:id ref-shape) ")")) - (log/trace :msg (str " (ref touched: " (:touched ref-shape) ")")) - (assoc % :touched - (set/union (:touched shape) - (inherited-touched shape (or (:touched ref-shape) #{})))))) - - ;; If we can't get the ref-shape (e.g. it's in an external library not linked), - ;: we can't do a suitable advance. So it's better to detach the shape and all its - ;; children (and add to detached-ids so they are not processed again). - (nil? ref-shape) - (generate-detach-immediate container (:id shape) detached-ids)))))] - - (reduce skip-near changes children))) - -(defn- generate-detach-immediate - [changes container shape-id detached-ids] - (let [shape-and-children (cfh/get-children-ids-with-self (:objects container) shape-id)] - (log/trace :msg " (cannot advance; detach shape and children)") - (swap! detached-ids #(into % shape-and-children)) - (pcb/update-shapes changes shape-and-children ctk/detach-shape))) - -(defn prepare-restore-component - ([changes library-data component-id current-page] - (let [component (ctkl/get-deleted-component library-data component-id) - page (or (ctf/get-component-page library-data component) - (when (some #(= (:id current-page) %) (:pages library-data)) ;; If the page doesn't belong to the library, it's not valid - current-page) - (ctpl/get-last-page library-data))] - (prepare-restore-component changes library-data component-id page nil nil nil nil))) - - ([changes library-data component-id page position old-id parent-id frame-id] - (let [library-data (or (pcb/get-library-data changes) library-data) - component (ctkl/get-deleted-component library-data component-id) - objects (or (pcb/get-objects changes) (:objects page)) - parent (get objects parent-id) - main-inst (get-in component [:objects (:main-instance-id component)]) - inside-component? (some? (ctn/get-instance-root (:objects page) parent)) - shapes (cfh/get-children-with-self (:objects component) (:main-instance-id component)) - - is-variant? (ctk/is-variant? component) - - orig-pos (gpt/point (:x main-inst) (:y main-inst)) - delta (if position - (gpt/subtract position orig-pos) - (gpt/point 0 0)) - minusdelta (gpt/point (- (:x delta)) (- (:y delta))) - - moved-shapes (map #(gsh/move % delta) shapes) - - first-shape (cond-> (first moved-shapes) - (not (nil? parent-id)) - (assoc :parent-id parent-id) - (not (nil? frame-id)) - (assoc :frame-id frame-id) - (and (nil? frame-id) parent (= :frame (:type parent))) - (assoc :frame-id parent-id) - (and (nil? frame-id) parent (not= :frame (:type parent))) - (assoc :frame-id (:frame-id parent)) - inside-component? - (dissoc :component-root) - (not inside-component?) - (assoc :component-root true)) - - restoring-into-parent (get objects (:parent-id first-shape)) - - changes (-> changes - (pcb/with-page page) - (pcb/with-objects (:objects page)) - (pcb/with-library-data library-data)) - changes (cond-> (pcb/add-object changes first-shape {:ignore-touched true}) - (some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id))) ; on copy/paste old id is used later to reorder the paster layers - changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) - changes - (rest moved-shapes)) - changes (cond-> changes - ;; Transform variant info into name when restoring into a parent that is not a variant-container, - ;; or when restoring into a variant-container that doesn't exists anymore - (and is-variant? - (or (and parent (not (ctk/is-variant-container? parent))) - (nil? restoring-into-parent))) - (clvp/generate-make-shapes-no-variant [first-shape]) - ;; Add variant info and rename when restoring into a variant-container - (ctk/is-variant-container? restoring-into-parent) - (clvp/generate-make-shapes-variant [first-shape] restoring-into-parent))] - {:changes (pcb/restore-component changes component-id (:id page) minusdelta) - :shape (first moved-shapes)}))) - ;; ---- General library synchronization functions ---- (defn generate-sync-file @@ -640,71 +245,12 @@ (defmethod generate-sync-shape :colors [_ changes library-id _ shape libraries _] (shape-log :debug (:id shape) nil :msg "Sync colors of shape" :shape (:name shape)) - - ;; Synchronize a shape that uses some colors of the library. The value of the - ;; color in the library is copied to the shape. It is a derived write, so it - ;; touches no override of a copy. - (let [library-colors (get-in libraries [library-id :data :colors])] - (pcb/update-shapes changes - [(:id shape)] - #(ctl/sync-colors % library-id library-colors) - {:ignore-touched true}))) + (clla/generate-sync-colors changes library-id shape libraries)) (defmethod generate-sync-shape :typographies [_ changes library-id container shape libraries _] (shape-log :debug (:id shape) nil :msg "Sync typographies of shape" :shape (:name shape)) - - ;; Synchronize a shape that uses some typographies of the library. The text - ;; styles of the typography are copied to the shape. It is a derived write, - ;; so it touches no override of a copy. - (let [typographies (get-in libraries [library-id :data :typographies]) - update-node (fn [node] - (if-let [typography (get typographies (:typography-ref-id node))] - (merge node (select-keys typography txt/typography-fields)) - (dissoc node :typography-ref-id - :typography-ref-file)))] - (generate-sync-text-shape changes shape container update-node))) - -(defn- generate-sync-text-shape - [changes shape container update-node] - (let [old-content (:content shape) - new-content (txt/transform-nodes update-node old-content) - - redo-change - (make-change - container - {:type :mod-obj - :id (:id shape) - :operations [{:type :set - :attr :content - :val new-content - :ignore-touched true} - {:type :set - :attr :position-data - :val nil}]}) - - undo-change - (make-change - container - {:type :mod-obj - :id (:id shape) - :operations [{:type :set - :attr :content - :val old-content - :ignore-touched true} - {:type :set - :attr :position-data - :val nil}]}) - - changes' (-> changes - (update :redo-changes conj redo-change) - (update :undo-changes conj undo-change))] - - (if (= new-content old-content) - changes - (-> changes' - (pcb/apply-changes-local))))) - + (clla/generate-sync-typographies changes library-id container shape libraries)) ;; ---- Component synchronization helpers ---- @@ -1690,9 +1236,11 @@ :ignore-touched true}]})) (pcb/apply-changes-local))))) -(defn- generate-update-tokens +(defn generate-update-tokens + "Copy into `dest-shape` the tokens `origin-shape` applies to the attributes + it syncs: with `omit-touched?`, only those whose group is not in `touched`; + with `valid-attrs`, only those attributes (all when nil)." [changes container dest-shape origin-shape touched omit-touched? valid-attrs] - ;; valid-attrs is a set of attrs to consider on the update. If it is nil, it will consider all the attrs (let [attrs (->> (keys ctk/sync-attrs) ;; We don't update the flex-child attrs @@ -1720,7 +1268,9 @@ (seq applied-tokens) (update-tokens container dest-shape origin-shape applied-tokens)))) -(defn- add-update-attr-changes +(defn add-update-attr-changes + "Add to `changes` the operations of `attr-operations` on `dest-shape`, and + the update of its parents." [changes dest-shape container roperations uoperations] (let [all-parents (cfh/get-parent-ids (:objects container) (:id dest-shape))] @@ -1746,7 +1296,7 @@ (pcb/apply-changes-local)))) -(defn- text-content-with-overrides +(defn text-content-with-overrides "The content of a text that takes the overrides `groups` (text override groups, see `txt/text-overrides`) from `own-content` and everything else from `source-content`." @@ -1796,7 +1346,7 @@ {:ignore-touched true}) changes)) -(defn- attr-operations +(defn attr-operations "The redo and undo `:set` operations, as `[roperations uoperations]`, that copy attributes into `dest-shape`. For each of `updatable-attrs`, `attr-ops` gives the `[attr value]` pairs to set, in order: none to @@ -1924,365 +1474,6 @@ :always (generate-update-tokens container dest-shape origin-shape touched omit-touched? nil)))) -(defn- set-path-new-values - [current-shape prev-shape transform] - (let [new-content (segment/transform-content - (:content current-shape) - (gmt/transform-in (gpt/point 0 0) transform)) - new-points (-> (segment/content->selrect new-content) - (grc/rect->points)) - points-center (gco/points->center new-points) - new-selrect (gsh/calculate-selrect new-points points-center) - shape (assoc current-shape - :content new-content - :points new-points - :selrect new-selrect) - - prev-center (segment/content-center (:content prev-shape)) - delta (gpt/subtract points-center (first new-points)) - new-pos (gpt/subtract prev-center delta)] - (gsh/absolute-move shape new-pos))) - -(defn- switch-path-change-value - [prev-shape ; The shape before the switch - current-shape ; The shape after the switch (a clean copy) - ref-shape ; The referenced shape on the main component - ; before the switch - attr] - (let [old-width (-> ref-shape :selrect :width) - new-width (-> prev-shape :selrect :width) - - old-height (-> ref-shape :selrect :height) - new-height (-> prev-shape :selrect :height) - - transform (-> (gpt/point (/ new-width old-width) - (/ new-height old-height)) - (gmt/scale-matrix)) - - shape (set-path-new-values current-shape prev-shape transform)] - (get shape attr))) - - -(defn- switch-fixed-layout-geom-change-value - [prev-shape ; The shape before the switch - current-shape ; The shape after the switch (a clean copy) - origin-shape ; The original shape - attr] - ;; When there is a layout with fixed h or v sizing, we need - ;; to keep the width/height (and recalculate selrect and points) - (let [prev-width (-> prev-shape :selrect :width) - current-width (-> current-shape :selrect :width) - origin-width (-> origin-shape :selrect :width) - - prev-height (-> prev-shape :selrect :height) - current-height (-> current-shape :selrect :height) - origin-height (-> origin-shape :selrect :height) - - x (-> current-shape :selrect :x) - y (-> current-shape :selrect :y) - - - h-sizing (:layout-item-h-sizing prev-shape) - v-sizing (:layout-item-v-sizing prev-shape) - - final-width (if (= :fix h-sizing) - current-width - (if (= origin-width current-width) - prev-width ;; same-size: preserve override - current-width)) ;; different-size: use new component's - - final-height (if (= :fix v-sizing) - current-height - (if (= origin-height current-height) - prev-height ;; same-size: preserve override - current-height)) ;; different-size: use new component's - - selrect (assoc (:selrect current-shape) - :width final-width - :height final-height - :x x - :y y - :x1 x - :y1 y - :x2 (+ x final-width) - :y2 (+ y final-height))] - - (case attr - :width - final-width - - :height - final-height - - :selrect - selrect - - :points - (-> selrect - (grc/rect->points) - (gsh/transform-points - (grc/rect->center selrect) - (or (:transform current-shape) (gmt/matrix))))))) - - -(defn- switch-geom-change-value - [prev-shape current-shape attr] - ;; Composite geometry stores absolute coordinates. When preserving a size - ;; override across variants, keep the target variant's position and only carry - ;; the previous dimensions; otherwise :x/:y can disagree with :selrect/:points. - (let [prev-selrect (:selrect prev-shape) - current-selrect (:selrect current-shape) - final-width (:width prev-selrect) - final-height (:height prev-selrect) - x (:x current-selrect) - y (:y current-selrect) - selrect (assoc current-selrect - :width final-width - :height final-height - :x x - :y y - :x1 x - :y1 y - :x2 (+ x final-width) - :y2 (+ y final-height))] - (case attr - :selrect - selrect - - :points - (-> selrect - (grc/rect->points) - (gsh/transform-points - (grc/rect->center selrect) - (or (:transform current-shape) (gmt/matrix))))))) - - -(defn- equal-geometry? - "Returns true when the value of `attr` in `shape` is considered equal - to the corresponding value in `origin-shape`, ignoring positional - displacement (x/y). - For :selrect we compare width/height only; - for :points we normalise each vector so the first point is the - origin before comparing. - For :content on path shapes we compare the bounding-box width/height - of the path segments, again ignoring absolute position. - - Comparisons use `mth/close?` (and `gpt/close?` for points) rather than - exact `=` because `previous-shape` here may carry sub-pixel drift from - interactive transform modifiers (e.g. an alt-drag duplicate of a - variant whose children are component instances). Without tolerance - this guard would miss equivalent geometries and let the `:else` branch - in `update-attrs-on-switch` carry stale `:selrect`/`:points` from the - pre-switch shape onto the freshly instantiated target." - [shape origin-shape attr] - (or (and (= attr :selrect) - (mth/close? (-> shape :selrect :width) (-> origin-shape :selrect :width)) - (mth/close? (-> shape :selrect :height) (-> origin-shape :selrect :height))) - (and (= attr :points) - (let [normalize-pts (fn [pts] - (when (seq pts) - (let [f (first pts)] - (mapv #(gpt/subtract % f) pts)))) - a (normalize-pts (get shape :points)) - b (normalize-pts (get origin-shape :points))] - (and (= (count a) (count b)) - (every? identity (map gpt/close? a b))))) - (and (= attr :content) - (cfh/path-shape? shape) - (let [ca (:content shape) - cb (:content origin-shape)] - (and (some? ca) (some? cb) - (let [selrect-a (segment/content->selrect ca) - selrect-b (segment/content->selrect cb)] - (and (some? selrect-a) (some? selrect-b) - (mth/close? (:width selrect-a) (:width selrect-b)) - (mth/close? (:height selrect-a) (:height selrect-b))))))))) - -(defn- switch-attr-operations - "The `[attr value]` pairs that carry `attr` over a variant switch, from - `previous-shape`, the copy shape before the switch placed like - `current-shape`, into `current-shape`, its clean copy of the new - variant. Only an override (a group in `touched`) is carried, and only - where `origin-ref-shape`, the main shape before the switch, and the new - copy agree on the attribute (decision (a) of the component contract); - geometry carries the override's size at the new position." - [current-shape previous-shape origin-ref-shape touched text-auto? attr] - (let [sync-group - (ctk/resolve-sync-group (:type previous-shape) attr) - - skip? - (or - ;; For auto text, avoid copying geometry-driven attrs on switch. - (and text-auto? - (contains? #{:points :selrect :width :height :position-data} attr)) - - ;; If the attribute is not valid for the destiny, don't copy it - (not (cts/is-allowed-switch-keep-attr? attr (:type current-shape))) - - ;; If the values are already equal, don't copy them - (= (get previous-shape attr) (get current-shape attr)) - - ;; If :selrect/:points values are already equal ignoring displacement, - ;; don't copy them - (equal-geometry? previous-shape origin-ref-shape attr) - - ;; If the attr is not touched, don't copy it - (not (touched sync-group)) - - ;; If both variants (origin and destiny) don't have the same value - ;; for that attribute, don't copy it. - ;; Exceptions: :points :selrect and :content can be different - ;; - ;; Sample: - ;; 1. We have a variant with C1 (bg red) and C2 (bg blue). - ;; 2. We make a copy of C1 called Copy. - ;; 3. We set Copy’s bg to green (so it it has an override on the bg). - ;; 4. We switch Copy to use C2 as base. - ;; 5. The bg of Copy now is blue (we ignore the override) - (and - (not (contains? #{:points :selrect :content} attr)) - (not= (get origin-ref-shape attr) (get current-shape attr))) - - ;; The :content attr cant't be copied to elements of different type - (and (= attr :content) (not= (:type previous-shape) (:type current-shape)))) - - ;; On texts, both text (the actual letters) - ;; and attrs (bold, font, etc) are in the same attr :content. - ;; If only one of them is touched, we want to address this case and - ;; only update the untouched one - text-change? - (and (not skip?) - (cfh/text-shape? current-shape) - (cfh/text-shape? previous-shape) - (= :content attr) - (touched sync-group)) - - path-change? - (and (= :path (:type current-shape)) - (contains? #{:points :selrect :content} attr)) - - ;; position-data is a special case because can be affected by :geometry-group and :content-group - ;; so, if the position-data changes but the geometry is touched we need to reset the position-data - ;; so it's calculated again - reset-pos-data? - (and (not skip?) - (cfh/text-shape? previous-shape) - (or (= attr :position-data) (= attr :selrect)) - (not= (:position-data previous-shape) (:position-data current-shape)) - (touched :geometry-group)) - - skip? - (or skip? - ;; If we are going to reset the position data, skip the selrect attr - (and reset-pos-data? (= attr :selrect)) - ;; Avoid copying composite geometry attrs (:selrect/:points) when the - ;; variant dimensions differ but neither sizing is :fix. Without this, - ;; :width/:height are correctly skipped by the check above - ;; but :selrect/:points would still carry the old override dimensions, - ;; leaving the shape in an inconsistent state. When :fix sizing is - ;; present, switch-fixed-layout-geom-change-value handles the composite - ;; attrs and must NOT be bypassed. Path shapes are also handled - ;; separately via switch-path-change-value. - (and (contains? #{:selrect :points} attr) - (not path-change?) - (not (or (= :fix (:layout-item-h-sizing previous-shape)) - (= :fix (:layout-item-v-sizing previous-shape)))) - (or (not= (get origin-ref-shape :width) (get current-shape :width)) - (not= (get origin-ref-shape :height) (get current-shape :height))))) - - attr-val - (when-not skip? - (cond - ;; If position data changes and the geometry group is touched - ;; we need to put to nil so we can regenerate it - reset-pos-data? - nil - - ;; Each text override that the old and the new main agree on is - ;; carried over the new main's content - text-change? - (text-content-with-overrides (:content previous-shape) - (:content current-shape) - (set/difference (txt/text-overrides touched) - (txt/override-groups (:content origin-ref-shape) - (:content current-shape)))) - - path-change? - (switch-path-change-value previous-shape - current-shape - origin-ref-shape - attr) - - (and (or (= :fix (:layout-item-h-sizing previous-shape)) - (= :fix (:layout-item-v-sizing previous-shape))) - (contains? #{:points :selrect :width :height} attr)) - (switch-fixed-layout-geom-change-value previous-shape current-shape origin-ref-shape attr) - - (and (contains? #{:points :selrect} attr) - (not path-change?)) - (switch-geom-change-value previous-shape current-shape attr) - - :else - (get previous-shape attr)))] - - ;; If the final attr-value is the actual value, skip - (if (or skip? (= attr-val (get current-shape attr))) - [] - (cond-> [] - ;; On a text-change, we want to force a position-data reset - ;; so it's calculated again - text-change? - (conj [:position-data nil]) - - ;; On a rotation operation we need to keep also the transformation matrixes - (= attr :rotation) - (conj [:transform (:transform previous-shape)] - [:transform-inverse (:transform-inverse previous-shape)]) - - :always - (conj [attr attr-val]))))) - -(defn update-attrs-on-switch - "Copy attributes that have changed in the shape previous to the switch - to the current shape (post switch). Used only on variants switch" - ;; NOTE: This function have similitudes but is very different to - ;; update-attrs: - ;; In components (update-attrs), the source shape is "clean", and the destination - ;; shape may have touched elements that shouldn't be overwritten. - ;; In variants (update-attrs-on-switch), the destination shape is "clean", - ;; and it's the source shape that may have touched elements, and we only want - ;; to copy those touched elements. - [changes current-shape previous-shape current-root prev-root origin-ref-shape container] - (let [;; We need to sync only the position relative to the origin of the component. - ;; (see update-attrs for a full explanation) - previous-shape (reposition-shape previous-shape prev-root current-root) - touched (get previous-shape :touched #{}) - text-auto? (and (cfh/text-shape? current-shape) - (contains? #{:auto-height :auto-width} (:grow-type current-shape))) - - [roperations uoperations] - (attr-operations current-shape - #(switch-attr-operations current-shape previous-shape origin-ref-shape - touched text-auto? %)) - - updated-attrs (into #{} (comp (filter #(= :set (:type %))) - (map :attr)) - roperations) - updated-sync-groups (into #{} - (keep #(ctk/resolve-sync-group (:type previous-shape) %)) - updated-attrs) - new-content (some #(when (= (:attr %) :content) (:val %)) roperations) - new-touched (set/union (or (:touched current-shape) #{}) - updated-sync-groups - (when (and (cfh/text-shape? current-shape) (some? new-content)) - (txt/override-groups (:content current-shape) new-content))) - roperations (into [{:type :set-touched :touched new-touched}] roperations) - uoperations (into (list {:type :set-touched :touched (:touched current-shape)}) uoperations)] - (cond-> changes - (> (count roperations) 1) - (-> (add-update-attr-changes current-shape container roperations uoperations) - (generate-update-tokens container current-shape previous-shape touched false updated-attrs))))) - (defn- propagate-attrs "Helper that puts the origin attributes (attrs) into dest but only if not touched the group or if omit-touched? flag is true" @@ -2483,7 +1674,7 @@ {:ignore-touched true}))] (pcb/concat-changes changes new-changes))) -(defn- reposition-shape +(defn reposition-shape "Expresses the shape (belonging to the origin-root instance) in the frame of the dest-root instance, making the geometry of both instances directly comparable — and copyable. @@ -2534,129 +1725,7 @@ (assoc change :page-id (:id container)) (assoc change :component-id (:id container)))) -(defn generate-add-component-changes - [changes root objects file-id page-id variant-props] - (let [name (:name root) - variant-id (when (ctk/is-variant? root) (:parent-id root)) - props (when (ctk/is-variant? root) (get variant-props (:component-id root))) - - [path name] (cpn/split-group-name name) - - [root-shape updated-shapes] - (ctn/convert-shape-in-component root objects file-id) - - changes (-> changes - (pcb/add-component (:id root-shape) - path - name - updated-shapes - (:id root) - page-id - nil - variant-id - props))] - [root-shape changes])) - -(defn generate-add-component - "If there is exactly one id, and it's a frame, and not already a component, - use it as root. Otherwise, create a frame that contains all ids. Then, make a - component with it, and link all shapes to their corresponding one in the component." - [changes shapes objects page-id file-id prepare-create-board] - - (let [changes (pcb/with-page-id changes page-id) - shapes-count (count shapes) - first-shape (first shapes) - - from-singe-frame? - (and (= 1 shapes-count) - (cfh/frame-shape? first-shape)) - - [root changes old-root-ids] - (if (and (= shapes-count 1) - (cfh/frame-shape? first-shape) - (not (ctk/instance-head? first-shape))) - [first-shape - (-> changes - (pcb/with-page-id page-id) - (pcb/with-objects objects)) - (:shapes first-shape)] - - (let [root-name (if (= 1 shapes-count) - (:name first-shape) - "Component 1") - - shape-ids (into (d/ordered-set) (map :id) shapes) - - [root changes] - (prepare-create-board changes - (uuid/next) - (:parent-id first-shape) - objects - shape-ids - nil - root-name - true)] - - [root changes shape-ids])) - - changes - (cond-> changes - (not from-singe-frame?) - (pcb/update-shapes - (:shapes root) - (fn [shape] - (assoc shape :constraints-h :scale :constraints-v :scale)))) - - objects' (assoc objects (:id root) root) - - [root-shape changes] (generate-add-component-changes changes root objects' file-id page-id nil) - - changes (pcb/update-shapes changes - old-root-ids - #(dissoc % :component-root) - [:component-root])] - - [root (:id root-shape) changes])) - -(defn generate-restore-component - "Restore a deleted component, with the given id, in the given file library." - [changes library-data component-id library-id current-page objects] - (let [{:keys [changes shape]} (prepare-restore-component changes library-data component-id current-page) - parent-id (:parent-id shape) - - insert-before? - (and (ctsl/flex-layout? objects parent-id) - (not (ctsl/reverse? objects parent-id))) - - objects - (-> objects - (assoc (:id shape) shape) - (cond-> (and (some? parent-id) insert-before?) - (update-in [parent-id :shapes] #(d/concat-vec [(:id shape)] %))) - (cond-> (and (some? parent-id) (not insert-before?)) - (update-in [parent-id :shapes] conj (:id shape)))) - - ;; Adds a resize-parents operation so the groups are updated. We add all the new objects - new-objects-ids (->> changes :redo-changes (filter #(= (:type %) :add-obj)) (mapv :id)) - changes (-> changes - (pcb/with-objects objects) - (pcb/resize-parents new-objects-ids) - ;; Fix the order of the children inside the parent - (cond-> (ctsl/any-layout? objects parent-id) - (pcb/reorder-children parent-id (get-in objects [parent-id :shapes]))))] - (assoc changes :file-id library-id))) - -(defn generate-detach-component - "Generate changes for remove all references to components in the shape, - with the given id and all its children, at the current page." - [changes id file page-id libraries] - (let [container (ctn/get-container file :page page-id)] - (-> changes - (pcb/with-container container) - (pcb/with-objects (:objects container)) - (generate-detach-instance container libraries id)))) - -(defn- orient-like +(defn orient-like "A function that turns each shape of the subtree of `head` around `center`, so that `head` takes the flips and then the rotation of `target`; nil when they already agree." @@ -2678,103 +1747,6 @@ rotate? (as-> shape (gsh/transform-shape shape (ctm/rotation-modifiers shape center angle)))))))) -(defn- generate-keep-orientation - "Give the new head of a swap, and its subtree, the flips and the rotation - of the shape it replaces, around the head's own center. They come from - its parent, as when the parent is rotated, and are not overrides." - [changes new-shape shape] - (if-let [orient (orient-like new-shape shape (gsh/shape->center new-shape))] - (pcb/update-shapes changes - (cfh/get-children-ids-with-self (pcb/get-objects changes) (:id new-shape)) - orient - {:ignore-touched true}) - changes)) - -(defn- generate-swap-in-copy - "A swap inside a copy keeps the position: the new head takes the place of - the main shape the old one referred to." - [changes new-shape shape file page libraries] - (let [file* {:id (:id file) :data file} - objects (pcb/get-objects changes) - position (ctf/find-ref-shape file* page libraries shape - :include-deleted? true - :with-context? true)] - (pcb/update-shapes changes - (cfh/get-children-ids-with-self objects (:id new-shape)) - (ctf/take-position file* libraries objects new-shape (:shape-ref shape) position) - {:ignore-touched true}))) - -(defn generate-new-shape-for-swap - [changes shape file page libraries id-new-component index target-cell keep-props-values] - (let [objects (:objects page) - position (gpt/point (:x shape) (:y shape)) - changes (-> changes - (pcb/with-objects objects)) - position (-> position (with-meta {:cell target-cell})) - parent (get objects (:parent-id shape)) - inside-copy? (ctk/in-component-copy? parent) - - [new-shape changes] - ;; When we make a swap of an item, there can be copies which refer to that item, - ;; so we want to assign the item id to the new instanciated copy, to mantain that reference - (generate-instantiate-component changes - objects - (:id file) - id-new-component - position - page - libraries - (:id shape) - (:parent-id shape) - (:frame-id shape) - {(:id shape) (:id shape)} ;; keep the id of the original shape - {:force-frame? true}) - - changes - (cond-> changes - inside-copy? - (generate-swap-in-copy new-shape shape file page libraries)) - - new-shape - (get (pcb/get-objects changes) (:id new-shape)) - - changes - (generate-keep-orientation changes new-shape shape)] - - [new-shape (-> changes - ;; Restore the properties - (pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values)) - - ;; We need to set the same index as the original shape - (pcb/change-parent (:parent-id shape) [new-shape] index {:allow-altering-copies true - :ignore-touched true}) - (change-touched new-shape - shape - (ctn/make-container page :page) - {}))])) - -(defn generate-component-swap - [changes objects shape file page libraries id-new-component - index target-cell keep-props-values ignore-swapped?] - (let [;; When we keep the touched properties, we can't delete the - ;; swapped children (we will keep them too) - ignore-swapped-fn - (if ignore-swapped? - #(ctk/swapped? (get objects %)) - (constantly false)) - - ;; In the cases where the swapped shape was the first element of the masked group it would make the group to loose the - ;; mask property as part of the sanitization check on generate-delete-shapes, passing "ignore-mask" to prevent this - [all-parents changes] - (-> changes - (cls/generate-delete-shapes - file page objects (d/ordered-set (:id shape)) - {:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true :ignore-flows-for #{(:id shape)}})) - [new-shape changes] - (-> changes - (generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))] - [new-shape all-parents changes])) - (defn- maybe-sync [c enabled? done? f] (if (and enabled? (not (done? c))) @@ -2803,450 +1775,3 @@ (pcb/with-container container) (pcb/with-objects (:objects container)) (generate-sync-shape-direct file-full libraries container id true))) - -(defn generate-duplicate-flows - [changes shapes page ids-map] - (let [flows (get page :flows) - unames (volatile! (cfh/get-used-names (vals flows))) - has-flow? (partial ctp/get-frame-flow flows)] - - (reduce (fn [changes frame-id] - (let [name (cfh/generate-unique-name "Flow" @unames :immediate-suffix? true) - frame-id (get ids-map frame-id) - flow-id (uuid/next) - new-flow {:id flow-id - :name name - :starting-frame frame-id}] - - (vswap! unames conj name) - (-> changes - (pcb/with-page page) - (pcb/set-flow flow-id new-flow)))) - - changes - (->> shapes - (filter cfh/frame-shape?) - (map :id) - (filter has-flow?))))) - -(defn generate-duplicate-guides - [changes shapes page ids-map delta] - (let [guides (get page :guides) - frames (filter cfh/frame-shape? shapes)] - - ;; FIXME: this can be implemented efficiently just indexing guides - ;; by frame-id instead of iterate over all guides all the time - - (reduce (fn [changes frame] - (let [new-id (get ids-map (:id frame)) - new-frame (gsh/move frame delta)] - - (reduce-kv (fn [changes _ guide] - (if (= (:id frame) (:frame-id guide)) - (let [guide-id (uuid/next) - position (if (= (:axis guide) :x) - (+ (:position guide) (- (:x new-frame) (:x frame))) - (+ (:position guide) (- (:y new-frame) (:y frame)))) - guide {:id guide-id - :frame-id new-id - :position position - :axis (:axis guide)}] - (pcb/set-guide changes guide-id guide)) - changes)) - changes - guides))) - (pcb/with-page changes page) - frames))) - -(defn- duplicate-variant - [changes library component base-pos parent page-id into-new-variant? new-component-file] - (let [component-page (ctpl/get-page (:data library) (:main-instance-page component)) - objects (:objects component-page) - component-shape (get objects (:main-instance-id component)) - orig-pos (gpt/point (:x component-shape) (:y component-shape)) - delta (gpt/subtract base-pos orig-pos) - new-component-id (uuid/next) - [shape changes] (generate-duplicate-component changes - library - (:id component) - new-component-id - {:apply-changes-local-library? true - :delta delta - :new-variant-id (if into-new-variant? nil (:id parent)) - :page-id page-id - :new-component-file new-component-file}) - value (when into-new-variant? - (str ctv/value-prefix - (-> (cfv/extract-properties-values (:data library) objects (:id parent)) - last - :value - count - inc)))] - - [shape - (cond-> changes - into-new-variant? - (clvp/generate-make-shapes-variant [shape] parent) - - ;; If it has the same parent, update the value of the last property - (and into-new-variant? (= (:variant-id component) (:id parent))) - (clvp/generate-update-property-value new-component-id (-> component :variant-properties count dec) value) - - :always - (pcb/change-parent (:id parent) [shape] 0))])) - - -(defn generate-duplicate-component-change - [changes objects page main parent-id frame-id delta libraries library-data ids-map & {:keys [new-component-file]}] - (let [main-id (:id main) - component-id (:component-id main) - ;; Source library file id (where the component was originally - ;; defined). Renamed from `file-id` to make the contrast with - ;; `new-component-file` explicit when duplicating across files. - source-file-id (:component-file main) - component (ctf/get-component libraries source-file-id component-id) - pos (as-> (gsh/move main delta) $ - (gpt/point (:x $) (:y $))) - - parent (get objects parent-id) - - - ;; When we duplicate a variant alone, we will instanciate it - ;; When we duplicate a variant along with its variant-container, we will duplicate it - in-variant-container? (contains? ids-map (:variant-id main)) - - restore-component - #(let [{:keys [shape changes]} - (prepare-restore-component changes - library-data - component-id - page - pos - main-id - parent-id - frame-id)] - [shape changes]) - - - [_shape changes] - (cond - (nil? component) - (restore-component) - - (and (ctk/is-variant? main) in-variant-container?) - (duplicate-variant changes - (get libraries source-file-id) - component - pos - parent - (:id page) - false - new-component-file) - - (ctk/is-variant-container? parent) - (duplicate-variant changes - (get libraries source-file-id) - component - pos - parent - (:id page) - true - new-component-file) - :else - (generate-instantiate-component changes - objects - source-file-id - component-id - pos - page - libraries - main-id - parent-id - frame-id - ids-map - {}))] - changes)) - -(defn generate-duplicate-shape-change - ([changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id] - (generate-duplicate-shape-change changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id (:frame-id obj) (:parent-id obj) false false nil)) - ([changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id variant-props] - (generate-duplicate-shape-change changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id (:frame-id obj) (:parent-id obj) false false variant-props)) - - ([changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id frame-id parent-id duplicating-component? child? variant-props] - (cond - (nil? obj) - changes - - (ctf/is-main-of-known-component? obj libraries) - (generate-duplicate-component-change changes objects page obj parent-id frame-id delta libraries library-data ids-map {:new-component-file file-id}) - - :else - (let [frame? (cfh/frame-shape? obj) - group? (cfh/group-shape? obj) - bool? (cfh/bool-shape? obj) - new-id (ids-map (:id obj)) - parent-id (or parent-id frame-id) - parent (get objects parent-id) - name (:name obj) - - is-component-root? (or (:saved-component-root obj) - ;; Backward compatibility - (:saved-component-root? obj) - (ctk/instance-root? obj)) - duplicating-component? (or duplicating-component? (ctk/instance-head? obj)) - is-component-main? (ctk/main-instance? obj) - - into-component? (and duplicating-component? - (ctn/in-any-component? objects parent)) - - level-delta (if (some? level-delta) - level-delta - (ctn/get-nesting-level-delta objects obj parent)) - - ;; a swapped head that leaves every copy level - rehead? (and (ctk/swapped? obj) - (pos? level-delta) - (>= level-delta - (->> (ctn/get-parent-copy-heads objects obj) - (remove #(= (:id %) (:id obj))) - (count)))) - new-shape-ref (if rehead? - (swap-content-root-id libraries obj) - (ctf/advance-shape-ref nil page libraries obj level-delta {:include-deleted? true})) - - regenerate-component - (fn [changes shape] - (let [[_ changes] (generate-add-component-changes changes shape objects file-id (:id page) variant-props)] - changes)) - - new-obj - (-> obj - (assoc :id new-id - :name name - :parent-id parent-id - :frame-id frame-id) - - (dissoc :shapes - :use-for-thumbnail) - - (cond-> (not is-component-root?) - (dissoc :main-instance)) - - (cond-> into-component? - (dissoc :component-root)) - - (cond-> (and (ctk/instance-head? obj) - (not into-component?)) - (assoc :component-root true)) - - (cond-> (or frame? group? bool?) - (assoc :shapes [])) - - (cond-> (and (some? new-shape-ref) - (not= new-shape-ref (:shape-ref obj))) - (assoc :shape-ref new-shape-ref)) - - (cond-> rehead? - (ctk/remove-swap-group)) - - (gsh/move delta) - (d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)) - - (cond-> (ctsl/grid-layout? obj) - (ctsl/remap-grid-cells ids-map)) - - (cond-> (ctk/is-variant-container? parent) - (assoc :variant-id parent-id)) - - (cond-> (and (ctk/is-variant? obj) (not (ctk/is-variant-container? parent))) - (-> (assoc :name (ctv/variant-name-to-name obj)) - (dissoc :variant-id)))) - - new-obj (cond-> new-obj - (not duplicating-component?) - (ctk/detach-shape)) - - ;; We want the first added object to touch it's parent, but not subsequent children - changes (-> (pcb/add-object changes new-obj {:ignore-touched (and duplicating-component? child?)}) - (pcb/amend-last-change #(assoc % :old-id (:id obj))) - (cond-> (ctsl/grid-layout? objects (:parent-id obj)) - (-> (pcb/update-shapes [(:parent-id obj)] ctsl/assign-cells {:with-objects? true}) - (pcb/reorder-grid-children [(:parent-id obj)])))) - - changes (cond-> changes - (and is-component-root? is-component-main?) - (regenerate-component new-obj)) - - ;; This is needed for the recursive call to find the new object as parent - page' (ctst/add-shape (:id new-obj) - new-obj - {:objects objects} - (:frame-id new-obj) - (:parent-id new-obj) - nil - true)] - - (reduce (fn [changes child] - (generate-duplicate-shape-change changes - (:objects page') - page - unames - update-unames! - ids - ids-map - child - delta - ;; the children of a swapped head refer into its own component - (if (ctk/swapped? obj) 0 level-delta) - libraries - library-data - file-id - (if frame? new-id frame-id) - new-id - duplicating-component? - true - variant-props)) - changes - (map (d/getf objects) (:shapes obj))))))) - -(defn advance-copied-objects - "The clipboard's `objects` (`selected` and their children, copied from - `page` of `file`) with the copies nested in other copies advanced: each - copy head that the copy takes out of `n` copy levels, and its children, - get the shape ref `n` levels up the ref chain and the touched groups of - the chain they skip, so they keep the values the skipped mains gave them." - [file page libraries objects selected] - (let [container (ctn/make-container page :page) - advance-shape (fn [level-delta objects shape] - (let [new-shape-ref (ctf/advance-shape-ref file page libraries shape level-delta - {:include-deleted? true})] - (cond-> objects - (and (some? new-shape-ref) (not= new-shape-ref (:shape-ref shape))) - (-> (assoc-in [(:id shape) :shape-ref] new-shape-ref) - (assoc-in [(:id shape) :touched] - (set/union - (or (:touched shape) #{}) - (inherited-touched - shape - (ctf/get-touched-from-ref-chain-until-target-ref - container libraries shape new-shape-ref)))))))) - detach-copy (fn [objects shape] - (reduce #(update %1 %2 ctk/detach-shape) - objects - (cfh/get-children-ids-with-self objects (:id shape)))) - advance-copy (fn [objects shape] - (if (and (ctk/instance-head? shape) (not (ctk/main-instance? shape))) - (let [level-delta (ctn/get-nesting-level-delta (:objects page) shape (get-in page [:objects uuid/zero]))] - (cond - (not (pos? level-delta)) - objects - - ;; the copy leaves every copy level - (ctk/swapped? shape) - (if-let [root-id (swap-content-root-id libraries shape)] - (update objects (:id shape) rehead-swapped root-id) - (detach-copy objects shape)) - - :else - (let [unchanged-ids (swapped-descendant-ids objects (:id shape))] - (reduce (partial advance-shape level-delta) - objects - (->> (cfh/get-children-with-self objects (:id shape)) - (remove #(contains? unchanged-ids (:id %)))))))) - objects))] - (reduce advance-copy - objects - (mapcat #(ctn/get-child-heads objects %) selected)))) - -(defn generate-duplicate-changes - "Prepare objects to duplicate: generate new id, give them unique names, - move to the desired position, and recalculate parents and frames as needed." - [changes all-objects page ids delta libraries library-data file-id & {:keys [variant-props alt-duplication?]}] - (let [shapes (map (d/getf all-objects) ids) - unames (volatile! (cfh/get-used-names (:objects page))) - update-unames! (fn [new-name] (vswap! unames conj new-name)) - all-ids (reduce #(into %1 (cons %2 (cfh/get-children-ids all-objects %2))) (d/ordered-set) ids) - - ;; We need ids-map for remapping the grid layout. But when duplicating the guides - ;; we calculate a new one because the components will have created new shapes. - ids-map (into {} (map #(vector % (uuid/next))) all-ids) - - - ;; If there is an alt-duplication we change to root - ;; For variants so the copy is made as a child of root - ;; This is because inside a variant-container can't be a copy - ;; For other shape this way the layout won't be changed when duplicated - ;; and if you move outside the layout will not change - shapes (map (fn [shape] - (cond-> shape - alt-duplication? - (assoc :parent-id uuid/zero :frame-id uuid/zero))) - shapes) - - - changes (-> changes - (pcb/with-page page) - (pcb/with-objects all-objects) - (pcb/with-library-data library-data)) - - changes - (->> shapes - (reduce #(generate-duplicate-shape-change %1 - all-objects - page - unames - update-unames! - ids - ids-map - %2 - delta - nil - libraries - library-data - file-id - variant-props) - changes)) - - ;; We need to check the changes to get the ids-map - ids-map - (into {} - (comp - (filter #(= :add-obj (:type %))) - (map #(vector (:old-id %) (-> % :obj :id)))) - (:redo-changes changes))] - - (-> changes - (generate-duplicate-flows shapes page ids-map) - (generate-duplicate-guides shapes page ids-map delta)))) - -(defn generate-duplicate-changes-update-indices - "Updates the changes to correctly set the indexes of the duplicated objects, - depending on the index of the original object respect their parent." - [changes objects ids] - (let [;; index-map is a map that goes from parent-id => vector([id index-in-parent]) - index-map (reduce (fn [index-map id] - (let [parent-id (get-in objects [id :parent-id]) - parent-index (cfh/get-position-on-parent objects id)] - (update index-map parent-id (fnil conj []) [id parent-index]))) - {} - ids) - - inc-indices - (fn [[offset result] [id index]] - [(inc offset) (conj result [id (+ index offset)])]) - - fix-indices - (fn [_ entry] - (->> entry - (sort-by second) - (reduce inc-indices [1 []]) - (second) - (into {}))) - - objects-indices (->> index-map (d/mapm fix-indices) (vals) (reduce merge))] - - (pcb/amend-changes - changes - (fn [change] - (assoc change :index (get objects-indices (:old-id change))))))) diff --git a/common/src/app/common/logic/library_assets.cljc b/common/src/app/common/logic/library_assets.cljc new file mode 100644 index 0000000000..d4dbc1bfd7 --- /dev/null +++ b/common/src/app/common/logic/library_assets.cljc @@ -0,0 +1,84 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns app.common.logic.library-assets + "Sync of the shapes that use the colors and typographies of a library. The + values come from the library; they are derived writes, so they set no + override on a copy." + (:require + [app.common.files.changes-builder :as pcb] + [app.common.files.helpers :as cfh] + [app.common.types.library :as ctl] + [app.common.types.text :as txt])) + +(defn- make-change + [container change] + (if (cfh/page? container) + (assoc change :page-id (:id container)) + (assoc change :component-id (:id container)))) + +(defn generate-sync-colors + "Copy into `shape` the value of each color of the library `library-id` that + it uses." + [changes library-id shape libraries] + (let [library-colors (get-in libraries [library-id :data :colors])] + (pcb/update-shapes changes + [(:id shape)] + #(ctl/sync-colors % library-id library-colors) + {:ignore-touched true}))) + +(defn- generate-sync-text-shape + [changes shape container update-node] + (let [old-content (:content shape) + new-content (txt/transform-nodes update-node old-content) + + redo-change + (make-change + container + {:type :mod-obj + :id (:id shape) + :operations [{:type :set + :attr :content + :val new-content + :ignore-touched true} + {:type :set + :attr :position-data + :val nil}]}) + + undo-change + (make-change + container + {:type :mod-obj + :id (:id shape) + :operations [{:type :set + :attr :content + :val old-content + :ignore-touched true} + {:type :set + :attr :position-data + :val nil}]}) + + changes' (-> changes + (update :redo-changes conj redo-change) + (update :undo-changes conj undo-change))] + + (if (= new-content old-content) + changes + (-> changes' + (pcb/apply-changes-local))))) + +(defn generate-sync-typographies + "Copy into the text `shape` the text styles of each typography of the + library `library-id` that its runs use; a run whose typography is no + longer in the library loses its reference." + [changes library-id container shape libraries] + (let [typographies (get-in libraries [library-id :data :typographies]) + update-node (fn [node] + (if-let [typography (get typographies (:typography-ref-id node))] + (merge node (select-keys typography txt/typography-fields)) + (dissoc node :typography-ref-id + :typography-ref-file)))] + (generate-sync-text-shape changes shape container update-node))) diff --git a/common/src/app/common/logic/swap.cljc b/common/src/app/common/logic/swap.cljc new file mode 100644 index 0000000000..684180f559 --- /dev/null +++ b/common/src/app/common/logic/swap.cljc @@ -0,0 +1,485 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns app.common.logic.swap + "Component swap: replace a copy head with a copy of another component, and + carry the overrides of a copy across a variant switch." + (:require + [app.common.data :as d] + [app.common.files.changes-builder :as pcb] + [app.common.files.helpers :as cfh] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] + [app.common.geom.rect :as grc] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.common :as gco] + [app.common.logic.instances :as cli] + [app.common.logic.libraries :as cll] + [app.common.logic.shapes :as cls] + [app.common.math :as mth] + [app.common.types.component :as ctk] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.common.types.path.segment :as segment] + [app.common.types.shape :as cts] + [app.common.types.text :as txt] + [clojure.set :as set])) + +(defn- set-path-new-values + [current-shape prev-shape transform] + (let [new-content (segment/transform-content + (:content current-shape) + (gmt/transform-in (gpt/point 0 0) transform)) + new-points (-> (segment/content->selrect new-content) + (grc/rect->points)) + points-center (gco/points->center new-points) + new-selrect (gsh/calculate-selrect new-points points-center) + shape (assoc current-shape + :content new-content + :points new-points + :selrect new-selrect) + + prev-center (segment/content-center (:content prev-shape)) + delta (gpt/subtract points-center (first new-points)) + new-pos (gpt/subtract prev-center delta)] + (gsh/absolute-move shape new-pos))) + +(defn- switch-path-change-value + [prev-shape ; The shape before the switch + current-shape ; The shape after the switch (a clean copy) + ref-shape ; The referenced shape on the main component + ; before the switch + attr] + (let [old-width (-> ref-shape :selrect :width) + new-width (-> prev-shape :selrect :width) + + old-height (-> ref-shape :selrect :height) + new-height (-> prev-shape :selrect :height) + + transform (-> (gpt/point (/ new-width old-width) + (/ new-height old-height)) + (gmt/scale-matrix)) + + shape (set-path-new-values current-shape prev-shape transform)] + (get shape attr))) + + +(defn- switch-fixed-layout-geom-change-value + [prev-shape ; The shape before the switch + current-shape ; The shape after the switch (a clean copy) + origin-shape ; The original shape + attr] + ;; When there is a layout with fixed h or v sizing, we need + ;; to keep the width/height (and recalculate selrect and points) + (let [prev-width (-> prev-shape :selrect :width) + current-width (-> current-shape :selrect :width) + origin-width (-> origin-shape :selrect :width) + + prev-height (-> prev-shape :selrect :height) + current-height (-> current-shape :selrect :height) + origin-height (-> origin-shape :selrect :height) + + x (-> current-shape :selrect :x) + y (-> current-shape :selrect :y) + + + h-sizing (:layout-item-h-sizing prev-shape) + v-sizing (:layout-item-v-sizing prev-shape) + + final-width (if (= :fix h-sizing) + current-width + (if (= origin-width current-width) + prev-width ;; same-size: preserve override + current-width)) ;; different-size: use new component's + + final-height (if (= :fix v-sizing) + current-height + (if (= origin-height current-height) + prev-height ;; same-size: preserve override + current-height)) ;; different-size: use new component's + + selrect (assoc (:selrect current-shape) + :width final-width + :height final-height + :x x + :y y + :x1 x + :y1 y + :x2 (+ x final-width) + :y2 (+ y final-height))] + + (case attr + :width + final-width + + :height + final-height + + :selrect + selrect + + :points + (-> selrect + (grc/rect->points) + (gsh/transform-points + (grc/rect->center selrect) + (or (:transform current-shape) (gmt/matrix))))))) + + +(defn- switch-geom-change-value + [prev-shape current-shape attr] + ;; Composite geometry stores absolute coordinates. When preserving a size + ;; override across variants, keep the target variant's position and only carry + ;; the previous dimensions; otherwise :x/:y can disagree with :selrect/:points. + (let [prev-selrect (:selrect prev-shape) + current-selrect (:selrect current-shape) + final-width (:width prev-selrect) + final-height (:height prev-selrect) + x (:x current-selrect) + y (:y current-selrect) + selrect (assoc current-selrect + :width final-width + :height final-height + :x x + :y y + :x1 x + :y1 y + :x2 (+ x final-width) + :y2 (+ y final-height))] + (case attr + :selrect + selrect + + :points + (-> selrect + (grc/rect->points) + (gsh/transform-points + (grc/rect->center selrect) + (or (:transform current-shape) (gmt/matrix))))))) + + +(defn- equal-geometry? + "Returns true when the value of `attr` in `shape` is considered equal + to the corresponding value in `origin-shape`, ignoring positional + displacement (x/y). + For :selrect we compare width/height only; + for :points we normalise each vector so the first point is the + origin before comparing. + For :content on path shapes we compare the bounding-box width/height + of the path segments, again ignoring absolute position. + + Comparisons use `mth/close?` (and `gpt/close?` for points) rather than + exact `=` because `previous-shape` here may carry sub-pixel drift from + interactive transform modifiers (e.g. an alt-drag duplicate of a + variant whose children are component instances). Without tolerance + this guard would miss equivalent geometries and let the `:else` branch + in `update-attrs-on-switch` carry stale `:selrect`/`:points` from the + pre-switch shape onto the freshly instantiated target." + [shape origin-shape attr] + (or (and (= attr :selrect) + (mth/close? (-> shape :selrect :width) (-> origin-shape :selrect :width)) + (mth/close? (-> shape :selrect :height) (-> origin-shape :selrect :height))) + (and (= attr :points) + (let [normalize-pts (fn [pts] + (when (seq pts) + (let [f (first pts)] + (mapv #(gpt/subtract % f) pts)))) + a (normalize-pts (get shape :points)) + b (normalize-pts (get origin-shape :points))] + (and (= (count a) (count b)) + (every? identity (map gpt/close? a b))))) + (and (= attr :content) + (cfh/path-shape? shape) + (let [ca (:content shape) + cb (:content origin-shape)] + (and (some? ca) (some? cb) + (let [selrect-a (segment/content->selrect ca) + selrect-b (segment/content->selrect cb)] + (and (some? selrect-a) (some? selrect-b) + (mth/close? (:width selrect-a) (:width selrect-b)) + (mth/close? (:height selrect-a) (:height selrect-b))))))))) + +(defn- switch-attr-operations + "The `[attr value]` pairs that carry `attr` over a variant switch, from + `previous-shape`, the copy shape before the switch placed like + `current-shape`, into `current-shape`, its clean copy of the new + variant. Only an override (a group in `touched`) is carried, and only + where `origin-ref-shape`, the main shape before the switch, and the new + copy agree on the attribute (decision (a) of the component contract); + geometry carries the override's size at the new position." + [current-shape previous-shape origin-ref-shape touched text-auto? attr] + (let [sync-group + (ctk/resolve-sync-group (:type previous-shape) attr) + + skip? + (or + ;; For auto text, avoid copying geometry-driven attrs on switch. + (and text-auto? + (contains? #{:points :selrect :width :height :position-data} attr)) + + ;; If the attribute is not valid for the destiny, don't copy it + (not (cts/is-allowed-switch-keep-attr? attr (:type current-shape))) + + ;; If the values are already equal, don't copy them + (= (get previous-shape attr) (get current-shape attr)) + + ;; If :selrect/:points values are already equal ignoring displacement, + ;; don't copy them + (equal-geometry? previous-shape origin-ref-shape attr) + + ;; If the attr is not touched, don't copy it + (not (touched sync-group)) + + ;; If both variants (origin and destiny) don't have the same value + ;; for that attribute, don't copy it. + ;; Exceptions: :points :selrect and :content can be different + ;; + ;; Sample: + ;; 1. We have a variant with C1 (bg red) and C2 (bg blue). + ;; 2. We make a copy of C1 called Copy. + ;; 3. We set Copy’s bg to green (so it it has an override on the bg). + ;; 4. We switch Copy to use C2 as base. + ;; 5. The bg of Copy now is blue (we ignore the override) + (and + (not (contains? #{:points :selrect :content} attr)) + (not= (get origin-ref-shape attr) (get current-shape attr))) + + ;; The :content attr cant't be copied to elements of different type + (and (= attr :content) (not= (:type previous-shape) (:type current-shape)))) + + ;; On texts, both text (the actual letters) + ;; and attrs (bold, font, etc) are in the same attr :content. + ;; If only one of them is touched, we want to address this case and + ;; only update the untouched one + text-change? + (and (not skip?) + (cfh/text-shape? current-shape) + (cfh/text-shape? previous-shape) + (= :content attr) + (touched sync-group)) + + path-change? + (and (= :path (:type current-shape)) + (contains? #{:points :selrect :content} attr)) + + ;; position-data is a special case because can be affected by :geometry-group and :content-group + ;; so, if the position-data changes but the geometry is touched we need to reset the position-data + ;; so it's calculated again + reset-pos-data? + (and (not skip?) + (cfh/text-shape? previous-shape) + (or (= attr :position-data) (= attr :selrect)) + (not= (:position-data previous-shape) (:position-data current-shape)) + (touched :geometry-group)) + + skip? + (or skip? + ;; If we are going to reset the position data, skip the selrect attr + (and reset-pos-data? (= attr :selrect)) + ;; Avoid copying composite geometry attrs (:selrect/:points) when the + ;; variant dimensions differ but neither sizing is :fix. Without this, + ;; :width/:height are correctly skipped by the check above + ;; but :selrect/:points would still carry the old override dimensions, + ;; leaving the shape in an inconsistent state. When :fix sizing is + ;; present, switch-fixed-layout-geom-change-value handles the composite + ;; attrs and must NOT be bypassed. Path shapes are also handled + ;; separately via switch-path-change-value. + (and (contains? #{:selrect :points} attr) + (not path-change?) + (not (or (= :fix (:layout-item-h-sizing previous-shape)) + (= :fix (:layout-item-v-sizing previous-shape)))) + (or (not= (get origin-ref-shape :width) (get current-shape :width)) + (not= (get origin-ref-shape :height) (get current-shape :height))))) + + attr-val + (when-not skip? + (cond + ;; If position data changes and the geometry group is touched + ;; we need to put to nil so we can regenerate it + reset-pos-data? + nil + + ;; Each text override that the old and the new main agree on is + ;; carried over the new main's content + text-change? + (cll/text-content-with-overrides (:content previous-shape) + (:content current-shape) + (set/difference (txt/text-overrides touched) + (txt/override-groups (:content origin-ref-shape) + (:content current-shape)))) + + path-change? + (switch-path-change-value previous-shape + current-shape + origin-ref-shape + attr) + + (and (or (= :fix (:layout-item-h-sizing previous-shape)) + (= :fix (:layout-item-v-sizing previous-shape))) + (contains? #{:points :selrect :width :height} attr)) + (switch-fixed-layout-geom-change-value previous-shape current-shape origin-ref-shape attr) + + (and (contains? #{:points :selrect} attr) + (not path-change?)) + (switch-geom-change-value previous-shape current-shape attr) + + :else + (get previous-shape attr)))] + + ;; If the final attr-value is the actual value, skip + (if (or skip? (= attr-val (get current-shape attr))) + [] + (cond-> [] + ;; On a text-change, we want to force a position-data reset + ;; so it's calculated again + text-change? + (conj [:position-data nil]) + + ;; On a rotation operation we need to keep also the transformation matrixes + (= attr :rotation) + (conj [:transform (:transform previous-shape)] + [:transform-inverse (:transform-inverse previous-shape)]) + + :always + (conj [attr attr-val]))))) + +(defn update-attrs-on-switch + "Copy attributes that have changed in the shape previous to the switch + to the current shape (post switch). Used only on variants switch" + ;; NOTE: This function have similitudes but is very different to + ;; cll/update-attrs: + ;; In components (cll/update-attrs), the source shape is "clean", and the destination + ;; shape may have touched elements that shouldn't be overwritten. + ;; In variants (update-attrs-on-switch), the destination shape is "clean", + ;; and it's the source shape that may have touched elements, and we only want + ;; to copy those touched elements. + [changes current-shape previous-shape current-root prev-root origin-ref-shape container] + (let [;; We need to sync only the position relative to the origin of the component. + ;; (see cll/update-attrs for a full explanation) + previous-shape (cll/reposition-shape previous-shape prev-root current-root) + touched (get previous-shape :touched #{}) + text-auto? (and (cfh/text-shape? current-shape) + (contains? #{:auto-height :auto-width} (:grow-type current-shape))) + + [roperations uoperations] + (cll/attr-operations current-shape + #(switch-attr-operations current-shape previous-shape origin-ref-shape + touched text-auto? %)) + + updated-attrs (into #{} (comp (filter #(= :set (:type %))) + (map :attr)) + roperations) + updated-sync-groups (into #{} + (keep #(ctk/resolve-sync-group (:type previous-shape) %)) + updated-attrs) + new-content (some #(when (= (:attr %) :content) (:val %)) roperations) + new-touched (set/union (or (:touched current-shape) #{}) + updated-sync-groups + (when (and (cfh/text-shape? current-shape) (some? new-content)) + (txt/override-groups (:content current-shape) new-content))) + roperations (into [{:type :set-touched :touched new-touched}] roperations) + uoperations (into (list {:type :set-touched :touched (:touched current-shape)}) uoperations)] + (cond-> changes + (> (count roperations) 1) + (-> (cll/add-update-attr-changes current-shape container roperations uoperations) + (cll/generate-update-tokens container current-shape previous-shape touched false updated-attrs))))) + +(defn- generate-keep-orientation + "Give the new head of a swap, and its subtree, the flips and the rotation + of the shape it replaces, around the head's own center. They come from + its parent, as when the parent is rotated, and are not overrides." + [changes new-shape shape] + (if-let [orient (cll/orient-like new-shape shape (gsh/shape->center new-shape))] + (pcb/update-shapes changes + (cfh/get-children-ids-with-self (pcb/get-objects changes) (:id new-shape)) + orient + {:ignore-touched true}) + changes)) + +(defn- generate-swap-in-copy + "A swap inside a copy keeps the position: the new head takes the place of + the main shape the old one referred to." + [changes new-shape shape file page libraries] + (let [file* {:id (:id file) :data file} + objects (pcb/get-objects changes) + position (ctf/find-ref-shape file* page libraries shape + :include-deleted? true + :with-context? true)] + (pcb/update-shapes changes + (cfh/get-children-ids-with-self objects (:id new-shape)) + (ctf/take-position file* libraries objects new-shape (:shape-ref shape) position) + {:ignore-touched true}))) + +(defn generate-new-shape-for-swap + [changes shape file page libraries id-new-component index target-cell keep-props-values] + (let [objects (:objects page) + position (gpt/point (:x shape) (:y shape)) + changes (-> changes + (pcb/with-objects objects)) + position (-> position (with-meta {:cell target-cell})) + parent (get objects (:parent-id shape)) + inside-copy? (ctk/in-component-copy? parent) + + [new-shape changes] + ;; When we make a swap of an item, there can be copies which refer to that item, + ;; so we want to assign the item id to the new instanciated copy, to mantain that reference + (cli/generate-instantiate-component changes + objects + (:id file) + id-new-component + position + page + libraries + (:id shape) + (:parent-id shape) + (:frame-id shape) + {(:id shape) (:id shape)} ;; keep the id of the original shape + {:force-frame? true}) + + changes + (cond-> changes + inside-copy? + (generate-swap-in-copy new-shape shape file page libraries)) + + new-shape + (get (pcb/get-objects changes) (:id new-shape)) + + changes + (generate-keep-orientation changes new-shape shape)] + + [new-shape (-> changes + ;; Restore the properties + (pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values)) + + ;; We need to set the same index as the original shape + (pcb/change-parent (:parent-id shape) [new-shape] index {:allow-altering-copies true + :ignore-touched true}) + (cll/change-touched new-shape + shape + (ctn/make-container page :page) + {}))])) + +(defn generate-component-swap + [changes objects shape file page libraries id-new-component + index target-cell keep-props-values ignore-swapped?] + (let [;; When we keep the touched properties, we can't delete the + ;; swapped children (we will keep them too) + ignore-swapped-fn + (if ignore-swapped? + #(ctk/swapped? (get objects %)) + (constantly false)) + + ;; In the cases where the swapped shape was the first element of the masked group it would make the group to loose the + ;; mask property as part of the sanitization check on generate-delete-shapes, passing "ignore-mask" to prevent this + [all-parents changes] + (-> changes + (cls/generate-delete-shapes + file page objects (d/ordered-set (:id shape)) + {:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true :ignore-flows-for #{(:id shape)}})) + [new-shape changes] + (-> changes + (generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))] + [new-shape all-parents changes])) diff --git a/common/src/app/common/logic/variants.cljc b/common/src/app/common/logic/variants.cljc index 8ed0d25843..18a63034ce 100644 --- a/common/src/app/common/logic/variants.cljc +++ b/common/src/app/common/logic/variants.cljc @@ -4,8 +4,10 @@ [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] [app.common.files.variant :as cfv] + [app.common.logic.duplicate :as cld] [app.common.logic.libraries :as cll] [app.common.logic.shapes :as cls] + [app.common.logic.swap :as clsw] [app.common.logic.variant-properties :as clvp] [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] @@ -28,7 +30,7 @@ inc)) [new-shape changes] (-> changes - (cll/generate-duplicate-component + (cld/generate-duplicate-component {:data data} component-id new-component-id @@ -249,7 +251,7 @@ ldata page libraries) ;; If the orig-child-touched wasn't swapped, copy ;; the touched attributes into it - (cll/update-attrs-on-switch + (clsw/update-attrs-on-switch changes related-shape-in-new orig-child-touched new-shape original-shape orig-ref-shape container)) changes)] @@ -283,8 +285,8 @@ keep-props (select-keys shape ctk/swap-keep-attrs) [new-shape all-parents changes] - (cll/generate-component-swap changes objects shape ldata page libraries component-id - index target-cell keep-props keep-touched?) + (clsw/generate-component-swap changes objects shape ldata page libraries component-id + index target-cell keep-props keep-touched?) [changes parents-of-swapped] (if keep-touched? diff --git a/common/src/app/common/test_helpers/components.cljc b/common/src/app/common/test_helpers/components.cljc index 23716e91d1..55964ef422 100644 --- a/common/src/app/common/test_helpers/components.cljc +++ b/common/src/app/common/test_helpers/components.cljc @@ -13,7 +13,7 @@ [app.common.files.helpers :as cfh] [app.common.files.swap-slots :as cfss] [app.common.geom.point :as gpt] - [app.common.logic.libraries :as cll] + [app.common.logic.swap :as clsw] [app.common.path-names :as cpn] [app.common.test-helpers.files :as thf] [app.common.test-helpers.ids-map :as thi] @@ -195,7 +195,7 @@ [new_shape _ changes] (-> (pcb/empty-changes nil (:id page)) - (cll/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values false)) + (clsw/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values false)) file' (cond-> (thf/apply-changes file changes) as-slot? diff --git a/common/src/app/common/test_helpers/composable/comp/nodes.cljc b/common/src/app/common/test_helpers/composable/comp/nodes.cljc index 1e4ff88851..88052c21c1 100644 --- a/common/src/app/common/test_helpers/composable/comp/nodes.cljc +++ b/common/src/app/common/test_helpers/composable/comp/nodes.cljc @@ -17,6 +17,8 @@ [app.common.files.tokens :as cfo] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.logic.duplicate :as cld] + [app.common.logic.instances :as cli] [app.common.logic.libraries :as cll] [app.common.logic.shapes :as cls] [app.common.logic.variant-properties :as clvp] @@ -804,7 +806,7 @@ (let [the-file (tm/file situation) shape-id (tm/target-shape-id situation target) page (thf/current-page the-file) - changes (cll/generate-detach-component (pcb/empty-changes) + changes (cli/generate-detach-component (pcb/empty-changes) shape-id (:data the-file) (:id page) @@ -935,7 +937,7 @@ component-id (deleted-component-id situation deletion) component (get-in data [:components component-id]) page (get-in data [:pages-index (:main-instance-page component)]) - changes (cll/generate-restore-component (pcb/empty-changes) + changes (cli/generate-restore-component (pcb/empty-changes) data component-id (:id the-file) @@ -996,14 +998,14 @@ (throw (ex-info "The shape cannot be duplicated (inside a copy)" {:type ::duplicate-refused :target target}))) changes (-> (pcb/empty-changes nil) - (cll/generate-duplicate-changes objects + (cld/generate-duplicate-changes objects page #{shape-id} (gpt/point 0 0) (libraries situation) (:data the-file) (:id the-file)) - (cll/generate-duplicate-changes-update-indices objects #{shape-id})) + (cld/generate-duplicate-changes-update-indices objects #{shape-id})) file' (thf/apply-changes the-file changes :validate? false)] (-> situation (tm/with-file file') @@ -1035,7 +1037,7 @@ (defn- copied-objects "The subtree of `id` as the clipboard copies it." [file page libraries id] - (cll/advance-copied-objects file + (cld/advance-copied-objects file page libraries (into {} (map (juxt :id identity)) @@ -1060,7 +1062,7 @@ copied (-> (copied-objects the-file page libs id) (update id assoc :parent-id uuid/zero :frame-id uuid/zero)) changes (-> (pcb/empty-changes nil (:id page)) - (cll/generate-duplicate-changes (merge (:objects page) copied) + (cld/generate-duplicate-changes (merge (:objects page) copied) page #{id} (gpt/point 0 0) diff --git a/common/src/app/common/test_helpers/compositions.cljc b/common/src/app/common/test_helpers/compositions.cljc index 73427778e4..6012a24186 100644 --- a/common/src/app/common/test_helpers/compositions.cljc +++ b/common/src/app/common/test_helpers/compositions.cljc @@ -10,8 +10,10 @@ [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] + [app.common.logic.duplicate :as cld] [app.common.logic.libraries :as cll] [app.common.logic.shapes :as cls] + [app.common.logic.swap :as clsw] [app.common.logic.variants :as clv] [app.common.test-helpers.components :as thc] [app.common.test-helpers.files :as thf] @@ -427,18 +429,18 @@ orig-shapes (when keep-touched? (cfh/get-children-with-self (:objects page) (:id shape))) [new-shape _all-parents changes] - (cll/generate-component-swap (pcb/empty-changes) - (:objects page) - shape - (:data library) - page - libraries - (-> (thc/get-component library component-tag) - :id) - 0 - nil - {} - (true? keep-touched?)) + (clsw/generate-component-swap (pcb/empty-changes) + (:objects page) + shape + (:data library) + page + libraries + (-> (thc/get-component library component-tag) + :id) + 0 + nil + {} + (true? keep-touched?)) [changes _] (if keep-touched? (clv/generate-keep-touched changes new-shape shape orig-shapes page libraries (:data file)) @@ -569,14 +571,14 @@ shape (ths/get-shape file shape-tag :page-label page-label) changes (-> (pcb/empty-changes nil) - (cll/generate-duplicate-changes (:objects page) ;; objects + (cld/generate-duplicate-changes (:objects page) ;; objects page ;; page #{(:id shape)} ;; ids (gpt/point 0 0) ;; delta {(:id file) file} ;; libraries (:data file) ;; library-data (:id file)) ;; file-id - (cll/generate-duplicate-changes-update-indices (:objects page) ;; objects + (cld/generate-duplicate-changes-update-indices (:objects page) ;; objects #{(:id shape)})) file' (thf/apply-changes file changes :validate? (not propagate-fn))] (if propagate-fn diff --git a/common/test/common_tests/logic/chained_propagation_test.cljc b/common/test/common_tests/logic/chained_propagation_test.cljc index a7c0f538f3..7be6a15cad 100644 --- a/common/test/common_tests/logic/chained_propagation_test.cljc +++ b/common/test/common_tests/logic/chained_propagation_test.cljc @@ -7,7 +7,7 @@ (ns common-tests.logic.chained-propagation-test (:require [app.common.files.changes-builder :as pcb] - [app.common.logic.libraries :as cll] + [app.common.logic.instances :as cli] [app.common.logic.shapes :as cls] [app.common.test-helpers.components :as thc] [app.common.test-helpers.compositions :as tho] @@ -114,7 +114,7 @@ (restore-comp-5 [file] (let [page (thf/current-page file)] (thf/apply-changes file - (cll/generate-restore-component (pcb/empty-changes) + (cli/generate-restore-component (pcb/empty-changes) (:data file) (thi/id :comp-5) (:id file) diff --git a/common/test/common_tests/logic/comp_creation_test.cljc b/common/test/common_tests/logic/comp_creation_test.cljc index 0447226b77..78264c0683 100644 --- a/common/test/common_tests/logic/comp_creation_test.cljc +++ b/common/test/common_tests/logic/comp_creation_test.cljc @@ -10,6 +10,8 @@ [app.common.files.changes-builder :as pcb] [app.common.files.shapes-helpers :as cfsh] [app.common.geom.point :as gpt] + [app.common.logic.duplicate :as cld] + [app.common.logic.instances :as cli] [app.common.logic.libraries :as cll] [app.common.logic.shapes :as cls] [app.common.test-helpers.components :as thc] @@ -37,7 +39,7 @@ ;; ==== Action [_ component-id changes] - (cll/generate-add-component (pcb/empty-changes) + (cli/generate-add-component (pcb/empty-changes) [frame1] (:objects page) (:id page) @@ -69,7 +71,7 @@ ;; ==== Action [_ component-id changes] - (cll/generate-add-component (pcb/empty-changes) + (cli/generate-add-component (pcb/empty-changes) [shape1] (:objects page) (:id page) @@ -104,7 +106,7 @@ ;; ==== Action [_ component-id changes] - (cll/generate-add-component (pcb/empty-changes) + (cli/generate-add-component (pcb/empty-changes) [shape1 shape2] (:objects page) (:id page) @@ -142,7 +144,7 @@ ;; ==== Action [_ component-id changes] - (cll/generate-add-component (pcb/empty-changes) + (cli/generate-add-component (pcb/empty-changes) [frame1 frame2] (:objects page) (:id page) @@ -180,7 +182,7 @@ ;; ==== Action [_ component-id changes] - (cll/generate-add-component (pcb/empty-changes) + (cli/generate-add-component (pcb/empty-changes) [frame1] (:objects page) (:id page) @@ -219,7 +221,7 @@ ;; ==== Action [_ component2-id changes] - (cll/generate-add-component (pcb/empty-changes) + (cli/generate-add-component (pcb/empty-changes) [copy1-root] (:objects page) (:id page) @@ -276,7 +278,7 @@ ;; ==== Action [_ changes] - (cll/generate-duplicate-component (pcb/empty-changes) + (cld/generate-duplicate-component (pcb/empty-changes) file (:id component) (uuid/next) @@ -324,7 +326,7 @@ ;; ==== Action [new-shape _] - (cll/generate-duplicate-component (pcb/empty-changes) + (cld/generate-duplicate-component (pcb/empty-changes) file (:id component) (uuid/next) @@ -352,7 +354,7 @@ ;; ==== Action [new-shape _] - (cll/generate-duplicate-component (pcb/empty-changes) + (cld/generate-duplicate-component (pcb/empty-changes) file (:id component) (uuid/next) @@ -426,7 +428,7 @@ file-deleted (thf/apply-changes file changes) page-deleted (thf/current-page file-deleted) - changes (cll/generate-restore-component (pcb/empty-changes) + changes (cli/generate-restore-component (pcb/empty-changes) (:data file-deleted) (thi/id :component1) (:id file-deleted) @@ -468,7 +470,7 @@ {}) file (-> (thf/apply-changes file changes) (ctf/update-file-data #(ctpl/delete-page % (:id page2)))) - changes (cll/generate-restore-component (pcb/empty-changes) + changes (cli/generate-restore-component (pcb/empty-changes) (:data file) (thi/id :component1) (:id file) @@ -518,7 +520,7 @@ ;; ==== Action [new-shape changes] - (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) ;; This may not be moved to generate + (cli/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) ;; This may not be moved to generate (pcb/with-objects (:objects page))) ;; because in some cases the objects (:objects page) ;; not the same as those on the page (:id file) @@ -561,7 +563,7 @@ ;; ==== Action [new-shape changes] - (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) + (cli/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) (pcb/with-objects (:objects page))) (:objects page) (:id library) @@ -606,7 +608,7 @@ ;; ==== Action [new-shape changes] - (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) + (cli/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) (pcb/with-objects (:objects page))) (:objects page) (:id file) @@ -652,7 +654,7 @@ ;; ==== Action [new-shape changes] - (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) + (cli/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) (pcb/with-objects (:objects page))) (:objects page) (:id library) @@ -694,7 +696,7 @@ copy1-root (ths/get-shape file :copy1-root) ;; ==== Action - changes (cll/generate-detach-component (pcb/empty-changes) + changes (cli/generate-detach-component (pcb/empty-changes) (:id copy1-root) (:data file) (:id page) diff --git a/common/test/common_tests/logic/comp_detach_with_nested_test.cljc b/common/test/common_tests/logic/comp_detach_with_nested_test.cljc index ca2729a787..74e76775c9 100644 --- a/common/test/common_tests/logic/comp_detach_with_nested_test.cljc +++ b/common/test/common_tests/logic/comp_detach_with_nested_test.cljc @@ -7,7 +7,7 @@ (ns common-tests.logic.comp-detach-with-nested-test (:require [app.common.files.changes-builder :as pcb] - [app.common.logic.libraries :as cll] + [app.common.logic.instances :as cli] [app.common.test-helpers.components :as thc] [app.common.test-helpers.compositions :as tho] [app.common.test-helpers.files :as thf] @@ -74,7 +74,7 @@ page (thf/current-page file) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -116,7 +116,7 @@ page (thf/current-page file) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -158,7 +158,7 @@ page (thf/current-page file) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -196,7 +196,7 @@ page (thf/current-page file) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -241,7 +241,7 @@ page (thf/current-page file) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -286,7 +286,7 @@ copy-nested2-ellipse (ths/get-shape file :copy-nested2-ellipse) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -330,7 +330,7 @@ copy-nested2-ellipse (ths/get-shape file :copy-nested2-ellipse) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -375,7 +375,7 @@ copy-nested2-rectangle (ths/get-shape file :copy-nested2-rectangle) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -420,7 +420,7 @@ copy-nested2-rectangle (ths/get-shape file :copy-nested2-rectangle) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -458,7 +458,7 @@ page (thf/current-page file) ;; ==== Action - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page diff --git a/common/test/common_tests/logic/comp_move_swapped_copies_test.cljc b/common/test/common_tests/logic/comp_move_swapped_copies_test.cljc index bda7baf1b8..621925d4f0 100644 --- a/common/test/common_tests/logic/comp_move_swapped_copies_test.cljc +++ b/common/test/common_tests/logic/comp_move_swapped_copies_test.cljc @@ -8,7 +8,8 @@ (:require [app.common.files.changes-builder :as pcb] [app.common.geom.point :as gpt] - [app.common.logic.libraries :as cll] + [app.common.logic.duplicate :as cld] + [app.common.logic.instances :as cli] [app.common.logic.shapes :as cls] [app.common.test-helpers.components :as thc] [app.common.test-helpers.compositions :as tho] @@ -217,14 +218,14 @@ ;; ==== Action changes (-> (pcb/empty-changes nil) - (cll/generate-duplicate-changes (:objects page) ;; objects + (cld/generate-duplicate-changes (:objects page) ;; objects page ;; page #{(:id blue1)} ;; ids (gpt/point 0 0) ;; delta {(:id file) file} ;; libraries (:data file) ;; library-data (:id file)) ;; file-id - (cll/generate-duplicate-changes-update-indices (:objects page) ;; objects + (cld/generate-duplicate-changes-update-indices (:objects page) ;; objects #{(:id blue1)})) ;; ids @@ -267,14 +268,14 @@ ;; Duplicate yellow changes' (-> (pcb/empty-changes nil) - (cll/generate-duplicate-changes (:objects page') ;; objects + (cld/generate-duplicate-changes (:objects page') ;; objects page' ;; page #{(:id yellow')} ;; ids (gpt/point 0 0) ;; delta {(:id file') file'} ;; libraries (:data file') ;; library-data (:id file')) ;; file-id - (cll/generate-duplicate-changes-update-indices (:objects page') ;; objects + (cld/generate-duplicate-changes-update-indices (:objects page') ;; objects #{(:id yellow')})) ;; ids file'' (thf/apply-changes file' changes') @@ -489,14 +490,14 @@ ;; ==== Action changes (-> (pcb/empty-changes nil) - (cll/generate-duplicate-changes (:objects page) ;; objects + (cld/generate-duplicate-changes (:objects page) ;; objects page ;; page #{(:id green-copy)} ;; ids (gpt/point 0 0) ;; delta {(:id file) file} ;; libraries (:data file) ;; library-data (:id file)) ;; file-id - (cll/generate-duplicate-changes-update-indices (:objects page) ;; objects + (cld/generate-duplicate-changes-update-indices (:objects page) ;; objects #{(:id green-copy)})) ;; ids @@ -549,7 +550,7 @@ blue2 (ths/get-shape file :blue2) ;; ==== Action - changes (cll/generate-detach-component (pcb/empty-changes) + changes (cli/generate-detach-component (pcb/empty-changes) (:id green-copy) (:data file) (:id page) diff --git a/common/test/common_tests/logic/swap_group_test.cljc b/common/test/common_tests/logic/swap_group_test.cljc index 08c0d050c4..684a9e1b6e 100644 --- a/common/test/common_tests/logic/swap_group_test.cljc +++ b/common/test/common_tests/logic/swap_group_test.cljc @@ -14,8 +14,11 @@ [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.logic.duplicate :as cld] + [app.common.logic.instances :as cli] [app.common.logic.libraries :as cll] [app.common.logic.shapes :as cls] + [app.common.logic.swap :as clsw] [app.common.logic.variants :as clv] [app.common.math :as mth] [app.common.test-helpers.components :as thc] @@ -220,7 +223,7 @@ (defn- detach [file label] (let [page (thf/current-page file) - changes (cll/generate-detach-instance (-> (pcb/empty-changes nil) + changes (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page @@ -234,7 +237,7 @@ (let [page (thf/current-page file) objects (:objects page) ids (cfh/get-children-ids-with-self objects (thi/id label))] - (cll/advance-copied-objects file + (cld/advance-copied-objects file page {(:id file) file} (select-keys objects ids) @@ -541,9 +544,9 @@ libraries {(:id file) file} orig (cfh/get-children-with-self (:objects page) (:id shape)) [new-shape _ changes] - (cll/generate-component-swap (pcb/empty-changes nil (:id page)) - (:objects page) shape (:data file) page libraries - (:id (thc/get-component file :c02)) 0 nil {} true) + (clsw/generate-component-swap (pcb/empty-changes nil (:id page)) + (:objects page) shape (:data file) page libraries + (:id (thc/get-component file :c02)) 0 nil {} true) [changes _] (clv/generate-keep-touched changes new-shape shape orig page libraries (:data file)) file' (thf/apply-changes file changes) file'' (thf/apply-undo-changes file' changes)] diff --git a/common/test/common_tests/logic/text_override_operations_test.cljc b/common/test/common_tests/logic/text_override_operations_test.cljc index aeac7e96d1..d6aaa53e04 100644 --- a/common/test/common_tests/logic/text_override_operations_test.cljc +++ b/common/test/common_tests/logic/text_override_operations_test.cljc @@ -12,7 +12,7 @@ notation of `app.common.test-helpers.texts`." (:require [app.common.files.changes-builder :as pcb] - [app.common.logic.libraries :as cll] + [app.common.logic.instances :as cli] [app.common.test-helpers.components :as thc] [app.common.test-helpers.compositions :as tho] [app.common.test-helpers.files :as thf] @@ -50,7 +50,7 @@ {:children-labels [:copy-label :copy-label-text]})) page (thf/current-page file) file' (thf/apply-changes file - (cll/generate-detach-instance (-> (pcb/empty-changes nil) + (cli/generate-detach-instance (-> (pcb/empty-changes nil) (pcb/with-page page) (pcb/with-objects (:objects page))) page diff --git a/common/test/common_tests/logic/variants_test.cljc b/common/test/common_tests/logic/variants_test.cljc index c4cfc1b95a..f168eca602 100644 --- a/common/test/common_tests/logic/variants_test.cljc +++ b/common/test/common_tests/logic/variants_test.cljc @@ -8,7 +8,8 @@ (:require [app.common.files.changes-builder :as pcb] [app.common.geom.point :as gpt] - [app.common.logic.libraries :as cll] + [app.common.logic.duplicate :as cld] + [app.common.logic.instances :as cli] [app.common.logic.shapes :as cls] [app.common.logic.variant-properties :as clvp] [app.common.test-helpers.components :as thc] @@ -253,7 +254,7 @@ (pcb/with-page-id (:id page)) (pcb/with-library-data (:data file)) (pcb/with-objects (:objects page)) - (cll/generate-duplicate-changes objects ;; objects + (cld/generate-duplicate-changes objects ;; objects page ;; page #{(:id variant-container)} ;; ids (gpt/point 0 0) ;; delta @@ -319,7 +320,7 @@ ;; Instantiate at a position inside the variant container, without ;; an explicit parent, so the destiny frame is chosen by position [new-shape changes] - (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) + (cli/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) (pcb/with-objects (:objects page))) (:objects page) (:id file) diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index 73e6625eaf..95a60c5d32 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -17,7 +17,7 @@ [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.grid-layout :as gslg] - [app.common.logic.libraries :as cll] + [app.common.logic.duplicate :as cld] [app.common.logic.shapes :as cls] [app.common.schema :as sm] [app.common.transit :as t] @@ -156,7 +156,7 @@ libraries (:files state) ;; FIXME page (dsh/lookup-page state)] - (update data :objects #(cll/advance-copied-objects file page libraries % selected)))) + (update data :objects #(cld/advance-copied-objects file page libraries % selected)))) (on-copy-error [error] (js/console.error "clipboard blocked:" error) @@ -939,7 +939,7 @@ (gslg/get-drop-cell frame-id all-objects position)) changes (-> (pcb/empty-changes it) - (cll/generate-duplicate-changes all-objects page selected delta + (cld/generate-duplicate-changes all-objects page selected delta libraries ldata file-id {:variant-props variant-props}) (pcb/amend-changes (partial process-rchange media-idx)) (pcb/amend-changes (partial change-add-obj-index objects selected index))) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index ea434212a5..45eaac35ca 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -14,6 +14,8 @@ [app.common.files.shapes-helpers :as cfsh] [app.common.geom.point :as gpt] [app.common.logging :as log] + [app.common.logic.duplicate :as cld] + [app.common.logic.instances :as cli] [app.common.logic.libraries :as cll] [app.common.logic.shapes :as cls] [app.common.logic.tokens :as clo] @@ -354,7 +356,7 @@ parents (into #{} (map :parent-id) shapes)] (when-not (empty? shapes) (let [[root component-id changes] - (cll/generate-add-component (pcb/empty-changes it) shapes objects page-id file-id + (cli/generate-add-component (pcb/empty-changes it) shapes objects page-id file-id cfsh/prepare-create-artboard-from-selection)] (when id-ref (reset! id-ref component-id)) @@ -474,7 +476,7 @@ [main-instance changes] (-> (pcb/empty-changes it nil) - (cll/generate-duplicate-component library component-id new-component-id))] + (cld/generate-duplicate-component library component-id new-component-id))] (rx/of (ptk/data-event :layout/update {:ids [(:id main-instance)]}) (dch/commit-changes changes))))))) @@ -532,7 +534,7 @@ objects (:objects comp-page) changes (-> (pcb/empty-changes it) - (cll/generate-restore-component ldata component-id library-id comp-page objects)) + (cli/generate-restore-component ldata component-id library-id comp-page objects)) page-id (->> changes :redo-changes (keep :page-id) first) @@ -579,7 +581,7 @@ current-file-id (:current-file-id state) [new-shape changes] - (cll/generate-instantiate-component changes + (cli/generate-instantiate-component changes objects file-id component-id @@ -623,7 +625,7 @@ libraries (dsh/lookup-libraries state) changes (-> (pcb/empty-changes it) - (cll/generate-detach-component id fdata page-id libraries))] + (cli/generate-detach-component id fdata page-id libraries))] (rx/of (dch/commit-changes changes)))))) @@ -665,7 +667,7 @@ changes (when can-detach? (reduce (fn [changes id] - (cll/generate-detach-component changes id fdata page-id libraries)) + (cli/generate-detach-component changes id fdata page-id libraries)) (pcb/empty-changes it) selected))] diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index aaab6e6802..063e85cb04 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -14,7 +14,7 @@ [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] - [app.common.logic.libraries :as cll] + [app.common.logic.duplicate :as cld] [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.uuid :as uuid] @@ -488,8 +488,8 @@ library-data (dsh/lookup-file-data state file-id) changes (-> (pcb/empty-changes it) - (cll/generate-duplicate-changes objects page ids delta libraries library-data file-id {:alt-duplication? alt-duplication?}) - (cll/generate-duplicate-changes-update-indices objects ids)) + (cld/generate-duplicate-changes objects page ids delta libraries library-data file-id {:alt-duplication? alt-duplication?}) + (cld/generate-duplicate-changes-update-indices objects ids)) tags (or (:tags changes) #{}) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index df614dfe7a..2b61791457 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -14,7 +14,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.flex-layout :as flex] [app.common.geom.shapes.grid-layout :as grid] - [app.common.logic.libraries :as cll] + [app.common.logic.duplicate :as cld] [app.common.types.color :as clr] [app.common.types.component :as ctc] [app.common.types.modifiers :as ctm] @@ -451,8 +451,8 @@ changes (-> (pcb/empty-changes it) - (cll/generate-duplicate-changes objects page selected (gpt/point 0 0) libraries library-data file-id) - (cll/generate-duplicate-changes-update-indices objects selected)) + (cld/generate-duplicate-changes objects page selected (gpt/point 0 0) libraries library-data file-id) + (cld/generate-duplicate-changes-update-indices objects selected)) ;; Creates a map with shape-id => duplicated-shape-id ids-map @@ -923,8 +923,8 @@ ;; Generate duplication changes for all shapes at once changes (-> (pcb/empty-changes it) - (cll/generate-duplicate-changes objects page all-shapes (gpt/point 0 0) libraries library-data file-id) - (cll/generate-duplicate-changes-update-indices objects all-shapes)) + (cld/generate-duplicate-changes objects page all-shapes (gpt/point 0 0) libraries library-data file-id) + (cld/generate-duplicate-changes-update-indices objects all-shapes)) ;; Build ids-map: old-shape-id -> new-shape-id ids-map diff --git a/frontend/test/frontend_tests/helpers/pages.cljs b/frontend/test/frontend_tests/helpers/pages.cljs index 90e1bfd5a2..b7ec409253 100644 --- a/frontend/test/frontend_tests/helpers/pages.cljs +++ b/frontend/test/frontend_tests/helpers/pages.cljs @@ -13,7 +13,8 @@ [app.common.files.shapes-helpers :as cfsh] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.logic.libraries :as cll] + [app.common.logic.duplicate :as cld] + [app.common.logic.instances :as cli] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] @@ -137,7 +138,7 @@ shapes (dwg/shapes-for-grouping objects shape-ids) [group component-id changes] - (cll/generate-add-component (pcb/empty-changes nil) + (cli/generate-add-component (pcb/empty-changes nil) shapes (:objects page) (:id page) @@ -160,7 +161,7 @@ (pcb/with-objects objects)) [new-shape changes] - (cll/generate-instantiate-component changes + (cli/generate-instantiate-component changes objects file-id component-id @@ -226,7 +227,7 @@ ;; When copying an instance that is nested inside another one, we need to ;; advance the shape refs to one or more levels of remote mains. (advance-copies [data] - (update data :objects #(cll/advance-copied-objects file page libraries % selected)))] + (update data :objects #(cld/advance-copied-objects file page libraries % selected)))] (let [file-id (:id file) frame-id (cfh/common-parent-frame objects selected)