mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
♻️ 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:
parent
ed7736916a
commit
7a2bd29a19
@ -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.
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
594
common/src/app/common/logic/duplicate.cljc
Normal file
594
common/src/app/common/logic/duplicate.cljc
Normal 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)))))))
|
||||
421
common/src/app/common/logic/instances.cljc
Normal file
421
common/src/app/common/logic/instances.cljc
Normal 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
84
common/src/app/common/logic/library_assets.cljc
Normal file
84
common/src/app/common/logic/library_assets.cljc
Normal 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)))
|
||||
485
common/src/app/common/logic/swap.cljc
Normal file
485
common/src/app/common/logic/swap.cljc
Normal 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]))
|
||||
@ -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?
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
@ -283,7 +285,7 @@
|
||||
|
||||
;; ==== Action
|
||||
[_ changes]
|
||||
(cll/generate-duplicate-component (pcb/empty-changes)
|
||||
(cld/generate-duplicate-component (pcb/empty-changes)
|
||||
file
|
||||
(:id component)
|
||||
(uuid/next)
|
||||
@ -331,7 +333,7 @@
|
||||
|
||||
;; ==== Action
|
||||
[new-shape _]
|
||||
(cll/generate-duplicate-component (pcb/empty-changes)
|
||||
(cld/generate-duplicate-component (pcb/empty-changes)
|
||||
file
|
||||
(:id component)
|
||||
(uuid/next)
|
||||
@ -359,7 +361,7 @@
|
||||
|
||||
;; ==== Action
|
||||
[new-shape _]
|
||||
(cll/generate-duplicate-component (pcb/empty-changes)
|
||||
(cld/generate-duplicate-component (pcb/empty-changes)
|
||||
file
|
||||
(:id component)
|
||||
(uuid/next)
|
||||
@ -433,7 +435,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)
|
||||
@ -475,7 +477,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)
|
||||
@ -525,7 +527,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)
|
||||
@ -568,7 +570,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)
|
||||
@ -613,7 +615,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)
|
||||
@ -659,7 +661,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)
|
||||
@ -701,7 +703,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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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]
|
||||
@ -73,7 +73,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."
|
||||
[file libraries page selected data]
|
||||
(update data :objects #(cll/advance-copied-objects file page libraries % selected)))
|
||||
(update data :objects #(cld/advance-copied-objects file page libraries % selected)))
|
||||
|
||||
(defn copy-selected
|
||||
[]
|
||||
@ -953,7 +953,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)))
|
||||
|
||||
@ -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))
|
||||
@ -470,7 +472,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)]})
|
||||
@ -529,7 +531,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)
|
||||
@ -576,7 +578,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
|
||||
@ -620,7 +622,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))))))
|
||||
|
||||
@ -662,7 +664,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))]
|
||||
|
||||
|
||||
@ -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) #{})
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
@ -138,7 +139,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)
|
||||
@ -161,7 +162,7 @@
|
||||
(pcb/with-objects objects))
|
||||
|
||||
[new-shape changes]
|
||||
(cll/generate-instantiate-component changes
|
||||
(cli/generate-instantiate-component changes
|
||||
objects
|
||||
file-id
|
||||
component-id
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user