♻️ Split app.common.logic.libraries by concern

The namespace held 3,300 lines: component sync, instance creation and
detach, swap and the variant switch's attribute carry, duplication,
and the sync of library colors and typographies. Each now has its own
namespace:

- app.common.logic.libraries (cll): component sync, reset, update
  main, attribute and placement sync, and the sync of a file with its
  libraries;
- app.common.logic.instances (cli): create, instantiate and restore a
  component, detach a copy;
- app.common.logic.swap (clsw): swap a copy head, carry overrides
  across a variant switch;
- app.common.logic.duplicate (cld): duplicate components and shapes;
- app.common.logic.library-assets (clla): sync of library colors and
  typographies, which the asset multimethods delegate to.

Each new namespace may require libraries, never the reverse; the
helpers they share became public there (attr-operations,
reposition-shape, orient-like, swap-content-root-id, ...). Callers use
the new aliases. The functions move as they were.

No behavior change: the golden masters and the suites are unchanged.

Benchmark (lower quantile, ms; baseline / now / ratio):
  deepest-edit-through-three-levels    35.2 /  37.3 / 1.06
  edit-through-a-chain-of-dependents   68.9 /  69.7 / 1.01
  library-update-into-copies           83.1 /  79.8 / 0.96
  main-edit-into-10-copies              1.8 /   1.6 / 0.92
  main-edit-into-100-copies            15.4 /  15.4 / 1.00
  main-edit-into-1000-copies          153.1 / 156.0 / 1.02
  variant-switch-into-copies           32.1 /  27.0 / 0.84

AI-assisted-by: claude-opus-5-5
This commit is contained in:
alonso.torres 2026-10-14 01:30:30 +02:00
parent d684fd16d1
commit c6b6572ae4
26 changed files with 1724 additions and 1599 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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)))))))

View File

@ -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))))

File diff suppressed because it is too large Load Diff

View File

@ -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)))

View File

@ -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]))

View File

@ -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?

View File

@ -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?

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)]

View File

@ -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

View File

@ -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)

View File

@ -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)))

View File

@ -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 {:page-id (:main-instance-page (get-in library [:data :components component-id]))
:ids [(:id main-instance)]})
@ -533,7 +535,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)
@ -580,7 +582,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
@ -624,7 +626,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))))))
@ -666,7 +668,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))]

View File

@ -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) #{})

View File

@ -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

View File

@ -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)