✨ Migrate swap slots to override groups and remove them

The migration 0033 stores every swap of a file as an override group.
A swap made in a copy is paired with the child of the near main that
holds the same position, as sync paired it; any other slot is dropped.
All pairings are found on the data as it was, so swaps nested in swaps
pair against the content of the outer one, and libraries pair the same
whether they are migrated or not. A head that refers to its own slot
only inherited it, and drops it even when its near main cannot be
reached. Any other slot that matches no child, or whose near main is in
a library that is not loaded, keeps the slot as its ref, so validation
reports it. An import, which migrates its files before it links their
libraries, leaves those slots and converts them once the libraries are
linked. A migrated head that sits elsewhere than the child it replaces
would place it gets its geometry marked as touched, so the first sync
does not move it. Migrated and new files carry the feature
components/swap-group, so a client that still writes slots cannot open
them.

Validation checks the new form: :misplaced-slot reports any slot,
:misplaced-swap-group the group anywhere but on a nested copy head in a
copy, :ref-not-in-near-main a nested copy head whose ref is not a child
of the near main of its parent, and :duplicate-ref two children of a
copy that refer to the same main shape. Each has a repair.

Nothing but the migrations reads slots any more: the slot branches go
from sync, swap, switch, reset, detach, paste, duplicate and move. The
helpers that read and write a slot on a shape move to files/swap-slots,
and the ones that follow refs to files/comp-processors. The graph
projection drops the FillsSwapSlot edge, the components debugger shows
the component a swapped head takes its content from, and the plugin API
documents componentRefShape for a swapped copy. The swap tests are
named after what they check.

Benchmark (lower quantile, ms; baseline / now / ratio):
  deepest-edit-through-three-levels    35.2 /  34.6 / 0.98
  edit-through-a-chain-of-dependents   68.9 /  63.5 / 0.92
  library-update-into-copies           83.1 /  76.4 / 0.92
  main-edit-into-10-copies              1.8 /   1.6 / 0.87
  main-edit-into-100-copies            15.4 /  15.1 / 0.98
  main-edit-into-1000-copies          153.1 / 148.8 / 0.97
  variant-switch-into-copies           32.1 /  26.4 / 0.82

AI-assisted-by: claude-opus-5-5
This commit is contained in:
alonso.torres 2026-10-13 00:01:33 +02:00
parent 122f64db3d
commit 03312bfef4
34 changed files with 1387 additions and 1016 deletions

View File

@ -88,7 +88,7 @@
- Formats graph column values.
- Quotes reserved graph labels such as `Group` and `Boolean`.
- Defines container tables and shape tables.
- Defines `IsChildOf`, `IsInstanceOf`, `RefersTo`, and `FillsSwapSlot`.
- Defines `IsChildOf`, `IsInstanceOf`, and `RefersTo`.
### `app.graph.schema.contract`
@ -175,10 +175,8 @@
- `link-shape-refs` creates `RefersTo` edges from `shape-ref`.
- Ladybug limits multi-label relationship `MERGE` statements.
- The transform emits one statement for each shape-table pair.
- `link-swap-slots` creates `FillsSwapSlot` edges.
- Swap slot IDs come from `swap-slot-<uuid>` entries in `touched`.
- The transform removes swap slot entries from `touched` after edge creation.
- The transform order matters because it reads and then changes `touched`.
- A swapped copy head carries `swap-group` in `touched`; its `RefersTo` edge points at the main child it replaces, and its `IsInstanceOf` edge at the component it takes its content from.
- The transforms read disjoint columns, so their order does not matter.
### `app.graph.meta`

View File

@ -22,7 +22,7 @@
- Validation starts at root plus orphan shapes, then validates component records. `validate-file!` raises `:validation :referential-integrity` with collected details.
- `repair-file` does not mutate data directly; it reduces validation errors into redo changes using `changes-builder`. Callers must apply or persist those changes.
- `repair-file-until-valid` stops on: no errors; unchanged `:data` after apply (backend page/`PointerMap` `=` is pointer identity); stable error fingerprint `(frequencies (map #(dissoc % :shape) errors))` vs previous pass (check fingerprint before `repair-file`/`process-changes`); or `max-iterations`. Each `repair-error` builds from the original `file-data`, so N `:set`-based errors on one shape take N passes.
- `:missing-slot` fires only for a REAL swap: a copy sub-head whose `shape-ref` is no longer a child of the near main parent. A pure positional mismatch (ref still a sibling elsewhere) is a reorder — valid, realigned by the async component sync; do not "repair" it by assigning swap slots (a slot freezes the child out of normal sync). `fix-missing-swap-slots` (migration 0019) follows the same membership rule.
- `:ref-not-in-near-main`: a nested copy head inside a copy must refer to a child of the near main of its parent (the content ref of the parent, so a swapped parent counts by its own component). A pure positional mismatch (ref still a sibling elsewhere) is a reorder — valid, realigned by the async component sync. Its repair pairs the head with the child that stands for the same deepest main child, else the free child at its index (adding `:swap-group` when the components differ), else detaches it. `:misplaced-swap-group`: `:swap-group` anywhere but a nested copy head inside a copy. `:misplaced-slot`: any swap slot (files are migrated to `:swap-group` before validation, migration 0033).
## Migrations

View File

@ -11,6 +11,7 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
[app.common.files.comp-processors :as cfcp]
[app.common.files.helpers :as cfh]
[app.common.files.migrations :as fmg]
[app.common.files.tokens :as cfo]
@ -787,6 +788,25 @@
;; all of them, not only the applied
(vary-meta dissoc ::fmg/migrated))))
(defn process-imported-file
"Process a file read from a binfile. The swap slots whose near main is in a
library not linked yet are left for `convert-deferred-swaps!`."
[cfg {:keys [id] :as file}]
(let [file (binding [cfcp/*defer-unreachable-swaps* true]
(process-file cfg file))]
(when (::cfcp/deferred-swaps (meta (:data file)))
(vswap! *state* update :deferred-swaps (fnil conj #{}) id))
file))
(defn convert-deferred-swaps!
"Convert the swap slots the import left in its files, once the libraries
of the files are linked."
[cfg]
(doseq [file-id (:deferred-swaps @*state*)]
(let [file (get-file cfg file-id :realize? true :migrate? false)
libs (get-resolved-file-libraries cfg file)]
(update-file! cfg (update file :data cfcp/convert-swap-slots libs)))))
(defn- encode-file
[cfg {:keys [id features] :as file}]
(let [file (if (and (contains? features "fdata/objects-map")

View File

@ -479,6 +479,7 @@
(read-section options))))
[:v1/metadata :v1/files :v1/rels :v1/sobjects])
(bfc/convert-deferred-swaps! cfg)
(bfm/apply-pending-migrations! cfg)
;; Knowing that the ids of the created files are in index,
@ -558,7 +559,7 @@
(assoc :project-id project-id)
(dissoc :thumbnails)
(bfc/check-components-v2))
file (bfc/process-file system file)]
file (bfc/process-imported-file system file)]
;; All features that are enabled and requires explicit migration are
;; added to the state for a posterior migration step.

View File

@ -1029,7 +1029,7 @@
(dissoc :options)
(dissoc :tokens-source)
(bfc/check-components-v2))
file (bfc/process-file cfg file)
file (bfc/process-imported-file cfg file)
file (ctf/check-file file)]
(bfm/register-pending-migrations! cfg file)
@ -1273,6 +1273,7 @@
(let [resolution (resolve-and-link-libraries cfg files-info)]
(bfc/convert-deferred-swaps! cfg)
(bfm/apply-pending-migrations! cfg)
{:file-ids file-ids
:resolution resolution})))

View File

@ -70,61 +70,14 @@
"MERGE (s)-[:RefersTo]->(t) "
"RETURN count(*);")))))
(def ^:private swap-slot-prefix "swap-slot-")
(def ^:private slot-uuid-expr
;; Ladybug `substring` is 1-indexed; 36 = RFC 4122 UUID text length.
(str "substring(touched_key, " (inc (count swap-slot-prefix)) ", 36)"))
(defn- link-swap-slots!
"`FillsSwapSlot` from a swapped-in shape to the slot it replaces.
Penpot records a component sub-shape swap as a `swap-slot-<uuid>` entry in
the *replacing* shape's `touched` set, where `<uuid>` names the replaced
slot shape in the main instance. The entries are then stripped from
`touched`, as `app.common.types.component/normal-touched-groups` does, so a
reader of `touched` sees design edits rather than swap bookkeeping.
Stripping makes this the one transform that writes a column another
transform could read. Anything reading `touched` has to run before it."
[^Connection conn]
(let [linked
(reduce
(fn [total statement] (+ total (run-scalar! conn statement)))
0
(shape-pair-statements
(fn [from to]
(str "MATCH (s:" (nodes/match-label from) ") "
"WHERE size(s.touched) > 0 "
"UNWIND s.touched AS touched_key "
"WITH s, touched_key "
"WHERE STARTS_WITH(touched_key, '" swap-slot-prefix "') "
"WITH s, CAST(" slot-uuid-expr ", 'UUID') AS slot_id "
"MATCH (t:" (nodes/match-label to) ") "
"WHERE t.id = slot_id AND s.id <> t.id "
"MERGE (s)-[r:FillsSwapSlot {slot_id: slot_id}]->(t) "
"RETURN count(r);"))))]
;; Strip unconditionally: an entry may name a slot that was garbage
;; collected, so "no edge created" does not mean "nothing to strip".
(doseq [table nodes/shape-tables]
(ladybug/exec-on-connection!
conn
[(str "MATCH (s:" (nodes/match-label table) ") "
"WHERE size(s.touched) > 0 "
"SET s.touched = list_filter(s.touched, x -> "
"NOT STARTS_WITH(x, '" swap-slot-prefix "'));")]))
linked))
(def registry
"Every transform this backend applies.
`:id` names the transform in the ingest report and the log. `:rel` names
the relationship it produces. The three registered here read disjoint
columns, so the vector order is not load-bearing. The one ordering
constraint that exists is stated on `link-swap-slots!`."
[{:id "link-component-instances" :rel :IsInstanceOf :run link-component-instances!}
{:id "link-shape-refs" :rel :RefersTo :run link-shape-refs!}
{:id "link-swap-slots" :rel :FillsSwapSlot :run link-swap-slots!}])
the relationship it produces. The ones registered here read disjoint
columns, so the vector order is not load-bearing."
[{:id "link-component-instances" :rel :IsInstanceOf :run link-component-instances!}
{:id "link-shape-refs" :rel :RefersTo :run link-shape-refs!}])
(defn apply-transforms!
"Apply every registered transform to an already loaded graph.

View File

@ -29,7 +29,7 @@
[clojure.string :as str]))
(def schema-version
"penpot-graph-slice-4")
"penpot-graph-slice-5")
(def ^:private document-projection
{:source ctf/schema:file
@ -329,15 +329,9 @@
[]
(shape-to-shape-rel-ddl "RefersTo" nil))
(defn fills-swap-slot-ddl
"Swapped-in shape → the slot shape it replaces."
[]
(shape-to-shape-rel-ddl "FillsSwapSlot" ["`slot_id` UUID"]))
(defn ddl-statements
[]
(-> (mapv create-node-table-ddl node-types)
(conj (is-child-of-ddl))
(conj (is-instance-of-ddl))
(conj (refers-to-ddl))
(conj (fills-swap-slot-ddl))))
(conj (refers-to-ddl))))

View File

@ -15,8 +15,17 @@
[app.common.files.changes-builder :as pcb]
[app.common.files.tokens :as cfo]
[app.common.files.validate :as cfv]
[app.common.files.comp-processors :as cfcp]
[app.common.files.migrations :as fmg]
[app.common.files.swap-slots :as cfss]
[app.common.pprint :as pp]
[app.common.test-helpers.components :as cthc]
[app.common.test-helpers.compositions :as ctho]
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi]
[app.common.thumbnails :as thc]
[app.common.time :as ct]
[app.common.types.component :as ctk]
[app.common.types.shape :as cts]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
@ -3030,3 +3039,67 @@
(t/testing "a file with components/v2 is left as it is"
(let [file (file #{"components/v2"} {:shape-ref (uuid/custom 2 2)} nil)]
(t/is (= file (bfc/check-components-v2 file)))))))
(defn- swap-slot-library
"A library, in the form files older than the feature components/swap-group
have, whose :page main holds a copy of :card with its star swapped for
:heart."
[]
(-> (cthf/sample-file :library)
(ctho/add-simple-component :star :star-main :star-rect)
(ctho/add-simple-component :heart :heart-main :heart-rect)
(ctho/add-simple-component :badge :badge-main :badge-rect)
(ctho/add-frame :card-main)
(cthc/instantiate-component :star :card-star :parent-label :card-main)
(cthc/make-component :card :card-main)
(ctho/add-frame :page-main)
(cthc/instantiate-component :card :inner-copy
:parent-label :page-main
:children-labels [:inner-star])
(cthc/make-component :page :page-main)
(ctho/swap-component-in-shape :inner-star :heart :as-slot? true)))
(t/deftest import-converts-deferred-swap-slots-once-libraries-are-linked
(cthi/reset-idmap!)
(let [profile (th/create-profile* 1)
project-id (:default-project-id profile)
old-migs (disj fmg/available-migrations "0034-convert-swap-slots-to-swap-groups")
library (swap-slot-library)
;; a file with a copy of :page whose star is swapped for :badge
file (-> (cthf/sample-file :file1)
(cthc/instantiate-component :page :outer-copy
:library library
:children-labels [:outer-inner :outer-star])
(ctho/swap-component-in-shape :outer-star :badge
:library library
:as-slot? true)
(assoc :project-id project-id)
(assoc :migrations old-migs)
(update :features disj "components/swap-group"))
page-id (-> file :data :pages first)]
(db/tx-run! th/*system*
(fn [cfg]
(binding [bfc/*state* (volatile! {:index {}})]
(bfc/insert-file! cfg (-> library
(assoc :project-id project-id)
(update :data cfcp/convert-swap-slots {})))
;; the library is not linked yet: the swap is left as it is
(let [imported (bfc/process-imported-file cfg file)]
(bfc/insert-file! cfg imported)
(t/is (= #{(:id file)} (:deferred-swaps @bfc/*state*)))
(t/is (contains? (:features imported) "components/swap-group"))
(t/is (some? (-> imported :data :pages-index (get page-id) :objects
(get (cthi/id :outer-star)) cfss/get-swap-slot))))
(db/insert! cfg :file-library-rel {:file-id (:id file)
:library-file-id (:id library)})
(bfc/convert-deferred-swaps! cfg)
(let [head (-> (bfc/get-file cfg (:id file) :migrate? false :realize? true)
:data :pages-index (get page-id) :objects
(get (cthi/id :outer-star)))]
(t/is (= (cthi/id :inner-star) (:shape-ref head)))
(t/is (ctk/swapped? head))
(t/is (nil? (cfss/get-swap-slot head)))))))))

View File

@ -48,6 +48,7 @@
"fdata/shape-data-type"
"fdata/path-data"
"components/v2"
"components/swap-group"
"styles/v2"
"layout/grid"
"plugins/runtime"
@ -67,6 +68,7 @@
"styles/v2"
"layout/grid"
"components/v2"
"components/swap-group"
"plugins/runtime"
"design-tokens/v1"
"tokens/numeric-input"
@ -96,7 +98,8 @@
;; or modifying a file or creating or modifying a team
(def no-team-inheritable-features
#{"fdata/path-data"
"fdata/shape-data-type"})
"fdata/shape-data-type"
"components/swap-group"})
;; This is a set of features that does not require an explicit
;; migration like components/v2 or the migration is not mandatory to
@ -107,6 +110,7 @@
"design-tokens/v1"
"fdata/shape-data-type"
"fdata/path-data"
"components/swap-group"
"tokens/numeric-input"
"variants/v1"}
(into frontend-only-features)

View File

@ -7,12 +7,112 @@
(ns app.common.files.comp-processors
"Repair, migration or transformation utilities for components."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.files.swap-slots :as cfss]
[app.common.geom.point :as gpt]
[app.common.logging :as log]
[app.common.types.component :as ctk]
[app.common.types.file :as ctf]))
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.shape.layout :as ctsl]))
(log/set-level! :warn)
;; ---- Swap slots
(defn find-swap-slot
"The deepest main child of the position the shape takes: its swap slot,
or the first one along its ref chain, or the last shape of the chain
before a main root."
([shape container file libraries]
(find-swap-slot shape container file libraries #{}))
([shape container file libraries viewed-ids]
(if (contains? viewed-ids (:id shape)) ;; prevent cycles
nil
(if-let [swap-slot (cfss/get-swap-slot shape)]
swap-slot
(let [ref-shape (ctf/find-ref-shape file
container
libraries
shape
:include-deleted? true
:with-context? true)
shape-meta (meta ref-shape)
ref-file (:file shape-meta)
ref-container (:container shape-meta)]
(when ref-shape
(if-let [swap-slot (cfss/get-swap-slot ref-shape)]
swap-slot
(if (ctk/main-instance? ref-shape)
(:id shape)
(find-swap-slot ref-shape ref-container ref-file libraries (conj viewed-ids (:id shape)))))))))))
(defn find-near-match
"Locate the shape that occupies the same position in the near main component.
This will be the ref-shape except if the shape is a copy subhead that has been
swapped. In this case, the near match will be the ref-shape that was before
the swap."
[file container libraries shape & {:keys [include-deleted? with-context?] :or {include-deleted? false with-context? false}}]
(let [parent-shape (ctn/get-shape container (:parent-id shape))
parent-ref-shape (when parent-shape
(ctf/find-ref-shape file container libraries parent-shape :include-deleted? include-deleted? :with-context? true))
ref-container (when parent-ref-shape
(:container (meta parent-ref-shape)))
shape-index (when parent-shape
(d/index-of (:shapes parent-shape) (:id shape)))
near-match-id (when (and parent-ref-shape shape-index)
(get (:shapes parent-ref-shape) shape-index))
near-match (when near-match-id
(cond-> (ctn/get-shape ref-container near-match-id)
with-context?
(with-meta (meta parent-ref-shape))))]
near-match))
(defn- find-next-related-swap-shape-id
"Go up from the chain of references shapes that will eventually lead to the shape
with swap-slot-id as id. Returns the next shape on the chain"
[parent swap-slot-id libraries]
(let [container (ctf/get-component-container-from-head parent libraries)
objects (:objects container)
children (cfh/get-children objects (:id parent))
original-shape-id (->> children
(filter #(= swap-slot-id (:id %)))
first
:id)]
(if original-shape-id
;; Return the children which id is the swap-slot-id
original-shape-id
;; No children with swap-slot-id as id, go up
(let [referenced-shape (ctf/find-ref-shape nil container libraries parent)
;; Recursive call that will get the id of the next shape on
;; the chain that ends on a shape with swap-slot-id as id
next-shape-id (when referenced-shape
(find-next-related-swap-shape-id referenced-shape swap-slot-id libraries))]
;; Return the children which shape-ref points to the next-shape-id
(->> children
(filter #(= next-shape-id (:shape-ref %)))
first
:id)))))
(defn find-ref-id-for-swapped
"When a shape has a swap slot, find the original ref-id that the shape had
before the swap"
[shape container libraries]
(let [swap-slot (cfss/get-swap-slot shape)
objects (:objects container)
parent (get objects (:parent-id shape))
parent-head (ctn/get-head-shape objects parent)
parent-ref (ctf/find-ref-shape nil container libraries parent-head)]
(when (and swap-slot parent-ref)
(find-next-related-swap-shape-id parent-ref swap-slot libraries))))
;; ---- Repairs
(defn remove-unneeded-objects-in-components
"Some components have an :objects attribute, despite not being deleted. This removes it.
It also adds an empty :objects if it's deleted and does not have it."
@ -77,11 +177,11 @@
shape container
#(ctf/find-ref-shape file container libraries % :include-deleted? true))
near-match (when swapped?
(ctf/find-near-match file container libraries shape :include-deleted? true :with-context? false))]
(find-near-match file container libraries shape :include-deleted? true :with-context? false))]
(if (and (some? near-match)
(not= (:shape-ref shape) (:id near-match))
(nil? (ctk/get-swap-slot shape)))
(let [updated-shape (ctk/set-swap-slot shape (:id near-match))]
(nil? (cfss/get-swap-slot shape)))
(let [updated-shape (cfss/set-swap-slot shape (:id near-match))]
(log/warn :hint "adding missing swap slot to shape"
:shape-id (str (:id shape))
:shape-name (:name shape)
@ -108,7 +208,9 @@
(ctf/update-all-shapes
file-data
(fn [shape]
(if (and (ctk/subcopy-head? shape) (nil? (ctk/get-swap-slot shape)))
(if (and (ctk/subcopy-head? shape)
(nil? (cfss/get-swap-slot shape))
(not (ctk/swapped? shape)))
(let [container (:container (meta shape))
file {:id (:id file-data) :data file-data}
ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true :with-context? true})]
@ -153,3 +255,148 @@
(> iteration 20)) ;; safety bound
next-data
(recur next-data (inc iteration)))))))
(def ^:dynamic *defer-unreachable-swaps*
"When true, `convert-swap-slots` leaves as they are the swap slots whose
near main it cannot reach, for a later pass with more libraries."
false)
(defn- find-swap-position
"Among the children of `parent-ref`, the near main of the parent of a
swapped head, the one that stands for the position `origin`: the deepest
main child of that position, as a swap slot holds it."
[parent-ref libraries origin]
(let [container (:container (meta parent-ref))
file (:file (meta parent-ref))]
(d/seek #(or (= (:id %) origin)
(= origin (find-swap-slot % container file libraries)))
(keep #(ctn/get-shape container %) (:shapes parent-ref)))))
(defn find-replaced-shape
"The child of the near main of the parent of the copy head `shape` whose
position the head takes: the one that stands for the deepest main child
the head refers to, or else the free one at the same index."
[file container libraries shape]
(let [parent (ctn/get-shape container (:parent-id shape))
parent-ref (when parent
(ctf/find-content-ref-shape file container libraries parent
:include-deleted? true
:with-context? true))]
(when parent-ref
(or (find-swap-position parent-ref libraries (:shape-ref shape))
(let [taken (into #{} (keep #(:shape-ref (ctn/get-shape container %))) (:shapes parent))
index (d/index-of (:shapes parent) (:id shape))
near-id (when index (get (:shapes parent-ref) index))]
(when (and (some? near-id) (not (contains? taken near-id)))
(ctn/get-shape (:container (meta parent-ref)) near-id)))))))
(defn- swap-made-in-copy?
"Whether a copy head with the swap slot `slot` was swapped in its copy,
and not only inherited the slot from its main: it refers to the root of
its own component, not to a child of its near main. When the near main
cannot be reached, a head that refers to its slot inherited it, since a
swap made in the copy refers to the root of another component; otherwise
its own component tells, and when that cannot be reached either, the swap
counts as made in the copy, so that it is kept."
[shape slot parent-ref file libraries]
(cond
(some? parent-ref)
(not-any? #(= % (:shape-ref shape)) (:shapes parent-ref))
(= slot (:shape-ref shape))
false
:else
(let [component (ctf/resolve-component shape file libraries :include-deleted? true)]
(or (nil? component)
(= (:shape-ref shape) (:main-instance-id component))))))
(defn- away-from-position?
"Whether the swapped head sits elsewhere than the main child it replaces
would place it. Sync did not move a head with a swap slot, so the main may
have moved that child since the swap. A position a layout owns does not
count."
[file container libraries shape position]
(let [objects (:objects container)
;; the head sync places it against: the top copy, or a swapped head
;; around it, which syncs from its own component
root (d/seek #(or (ctk/instance-root? %)
(some? (cfss/get-swap-slot %))
(ctk/swapped? %))
(map #(get objects %) (cfh/get-parent-ids objects (:id shape))))
root-main (when root
(ctf/find-content-ref-shape file container libraries root :include-deleted? true))
point (fn [shape]
(gpt/point (dm/get-in shape [:selrect :x])
(dm/get-in shape [:selrect :y])))]
(and (some? root-main)
(not (ctk/touched-group? shape :geometry-group))
(or (not (ctsl/any-layout-immediate-child? objects shape))
(ctsl/position-absolute? shape))
(not (gpt/close? (point shape)
(gpt/add (point position)
(gpt/subtract (point root) (point root-main))))))))
(defn convert-swap-slots
"Store the swaps of the file as override groups. Files older than the
feature components/swap-group mark a swapped copy head with a swap slot
(a `swap-slot-<id>` touched group) and point its :shape-ref at the root of
its own component. Now the head keeps the :shape-ref of the near main child
it replaces and carries the touched group :swap-group.
A swap made in a copy is paired with the child of the near main that holds
the same position, as sync paired it. Its geometry is marked as touched when
it sits elsewhere than that child would place it, since sync now moves it
with that child. One with no such child, or whose near
main cannot be reached, refers to its slot, so validation reports it. Any
other slot (a swap in a main, or a slot a copy inherited) is dropped.
Every pairing is found on the data as it was. With
`*defer-unreachable-swaps*`, a slot whose near main cannot be reached is
left, and the data carries `::deferred-swaps` in its metadata."
[file-data libraries]
(let [file {:id (:id file-data) :data file-data}
deferred? (volatile! false)
convert-shape
(fn [container shape slot]
(let [parent (ctn/get-shape container (:parent-id shape))
shape' (cfss/remove-swap-slot shape)]
(if-not (and (ctk/instance-head? shape)
(ctk/in-component-copy? parent))
shape'
(let [parent-ref (ctf/find-content-ref-shape file container libraries parent
:include-deleted? true
:with-context? true)]
(cond
(not (swap-made-in-copy? shape slot parent-ref file libraries))
shape'
(and (nil? parent-ref) *defer-unreachable-swaps*)
(do (vreset! deferred? true)
shape)
:else
(let [position (when parent-ref
(find-swap-position parent-ref libraries slot))]
(cond-> (-> shape'
(assoc :shape-ref (or (:id position) slot))
(update :touched (fnil conj #{}) :swap-group))
;; keep it where the user sees it
(and (some? position)
(away-from-position? file container libraries shape position))
(update :touched conj :geometry-group))))))))
convert-container
(fn [container]
(let [converted (into []
(keep (fn [shape]
(when-let [slot (cfss/get-swap-slot shape)]
(convert-shape container shape slot))))
(vals (:objects container)))]
(reduce #(assoc-in %1 [:objects (:id %2)] %2) container converted)))
file-data (ctf/update-containers file-data convert-container)]
(cond-> file-data
@deferred?
(vary-meta assoc ::deferred-swaps true))))

View File

@ -13,6 +13,7 @@
[app.common.files.comp-processors :as cfcp]
[app.common.files.defaults :as cfd]
[app.common.files.helpers :as cfh]
[app.common.files.swap-slots :as cfss]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
@ -938,12 +939,12 @@
libraries {component-file {:id component-file :data data}}
ref-shape (ctf/find-ref-shape file page libraries shape {:include-deleted? true :with-context? true})
ref-parent (get (:objects (:container (meta ref-shape))) (:parent-id ref-shape))
shape-swap-slot (ctk/get-swap-slot shape)
ref-swap-slot (ctk/get-swap-slot ref-shape)]
shape-swap-slot (cfss/get-swap-slot shape)
ref-swap-slot (cfss/get-swap-slot ref-shape)]
(if (and (some? shape-swap-slot)
(= shape-swap-slot ref-swap-slot)
(ctk/main-instance? ref-parent))
(ctk/remove-swap-slot shape)
(cfss/remove-swap-slot shape)
shape)))
(update-page [page]
@ -954,10 +955,10 @@
(defmethod migrate-data "legacy-48"
[data _]
(letfn [(fix-shape [shape]
(let [swap-slot (ctk/get-swap-slot shape)]
(let [swap-slot (cfss/get-swap-slot shape)]
(if (and (some? swap-slot)
(not (ctk/subcopy-head? shape)))
(ctk/remove-swap-slot shape)
(cfss/remove-swap-slot shape)
shape)))
(update-page [page]
@ -1493,11 +1494,11 @@
(defmethod migrate-data "0010-fix-swap-slots-pointing-non-existent-shapes"
[data _]
(letfn [(fix-shape [page shape]
(if (ctk/get-swap-slot shape)
(if (cfss/get-swap-slot shape)
(let [libs (some-> (:libs data) deref)
ref-id (when libs (ctf/find-ref-id-for-swapped shape page libs))]
ref-id (when libs (cfcp/find-ref-id-for-swapped shape page libs))]
(if (nil? ref-id)
(ctk/remove-swap-slot shape)
(cfss/remove-swap-slot shape)
shape))
shape))
@ -1509,7 +1510,7 @@
(defmethod migrate-data "0011-fix-invalid-text-touched-flags"
[data _]
(letfn [(fix-shape [shape]
(let [touched-groups (ctk/normal-touched-groups shape)
(let [touched-groups (cfss/normal-touched-groups shape)
content-touched? (touched-groups :content-group)
text-touched? (or (touched-groups :text-content-text)
(touched-groups :text-content-attribute)
@ -2296,6 +2297,14 @@
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0034-convert-swap-slots-to-swap-groups"
[data _]
(some-> cfeat/*new* (swap! conj "components/swap-group"))
(let [libraries (if (:libs data)
(deref (:libs data))
{})]
(cfcp/convert-swap-slots data libraries)))
(def available-migrations
(into (d/ordered-set)
["legacy-2"
@ -2386,4 +2395,5 @@
"0030-remove-stroke-per-side-attr"
"0031-migrate-stroke-width-token-attr"
"0032-fix-children-not-in-parent"
"0033-normalize-main-instance-names"]))
"0033-normalize-main-instance-names"
"0034-convert-swap-slots-to-swap-groups"]))

View File

@ -10,7 +10,9 @@
[app.common.data.macros :as dm]
[app.common.files.changes :as cfc]
[app.common.files.changes-builder :as pcb]
[app.common.files.comp-processors :as cfcp]
[app.common.files.helpers :as cfh]
[app.common.files.swap-slots :as cfss]
[app.common.files.tokens :as cfo]
[app.common.logging :as log]
[app.common.path-names :as cpn]
@ -591,80 +593,74 @@
(fn [shape]
;; Remove the swap slot
(log/debug :hint " -> remove swap-slot")
(ctk/remove-swap-slot shape))]
(cfss/remove-swap-slot shape))]
(log/debug :hint "repairing shape :misplaced-slot" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/update-shapes [(:id shape)] repair-shape))))
(defmethod repair-error :duplicate-slot
(defmethod repair-error :misplaced-swap-group
[_ {:keys [shape page-id] :as error} file-data _]
(let [page (ctpl/get-page file-data page-id)
childs (map #(get (:objects page) %) (:shapes shape))
child-with-duplicate (let [result (reduce (fn [[seen duplicates] item]
(let [swap-slot (ctk/get-swap-slot item)]
(if (contains? seen swap-slot)
[seen (conj duplicates item)]
[(conj seen swap-slot) duplicates])))
[#{} []]
childs)]
(second result))
repair-shape
(fn [shape]
;; Remove the swap slot
(log/debug :hint " -> remove swap-slot" :child-id (:id shape))
(ctk/remove-swap-slot shape))]
(log/debug :hint "repairing shape :duplicated-slot" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/update-shapes (map :id child-with-duplicate) repair-shape))))
(defmethod repair-error :component-duplicate-slot
[_ {:keys [shape] :as error} file-data _]
(let [main-shape (get-in shape [:objects (:main-instance-id shape)])
childs (map #(get (:objects shape) %) (:shapes main-shape))
childs-with-duplicate (let [result (reduce (fn [[seen duplicates] item]
(let [swap-slot (ctk/get-swap-slot item)]
(if (contains? seen swap-slot)
[seen (conj duplicates item)]
[(conj seen swap-slot) duplicates])))
[#{} []]
childs)]
(second result))
duplicated-ids (set (mapv :id childs-with-duplicate))
repair-component
(fn [component]
(let [objects (reduce-kv (fn [acc k v]
(if (contains? duplicated-ids k)
(assoc acc k (ctk/remove-swap-slot v))
(assoc acc k v)))
{}
(:objects component))]
(assoc component :objects objects)))]
(log/debug :hint "repairing component :component-duplicated-slot" :id (:id shape) :name (:name shape))
(-> (pcb/empty-changes nil)
(pcb/with-library-data file-data)
(pcb/update-component (:id shape) repair-component))))
(defmethod repair-error :missing-slot
[_ {:keys [shape page-id args] :as error} file-data _]
(let [repair-shape
(fn [shape]
;; Set the desired swap slot
(let [slot (:swap-slot args)]
(if (some? slot)
(do (log/debug :hint (str " -> set swap-slot to " slot))
(ctk/set-swap-slot shape slot))
shape)))]
(log/debug :hint " -> remove :swap-group")
(ctk/remove-swap-group shape))]
(log/debug :hint "repairing shape :missing-slot" :id (:id shape) :name (:name shape) :page-id page-id)
(log/debug :hint "repairing shape :misplaced-swap-group" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/update-shapes [(:id shape)] repair-shape))))
(defmethod repair-error :ref-not-in-near-main
[_ {:keys [shape page-id] :as error} file-data libraries]
(let [page (ctpl/get-page file-data page-id)
replaced (cfcp/find-replaced-shape {:id (:id file-data) :data file-data}
(ctn/make-container page :page)
libraries
shape)
reassign-shape
(fn [shape]
(log/debug :hint " -> reassign shape-ref to" :shape-ref (:id replaced))
(cond-> (assoc shape :shape-ref (:id replaced))
(or (not= (:component-id shape) (:component-id replaced))
(not= (:component-file shape) (:component-file replaced)))
(update :touched (fnil conj #{}) :swap-group)))]
(log/debug :hint "repairing shape :ref-not-in-near-main" :id (:id shape) :name (:name shape) :page-id page-id)
(if (some? replaced)
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/update-shapes [(:id shape)] reassign-shape))
;; no position to take: the copy keeps its content, detached
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/update-shapes (cfh/get-children-ids-with-self (:objects page) (:id shape))
ctk/detach-shape)))))
(defmethod repair-error :duplicate-ref
[_ {:keys [shape page-id] :as error} file-data _]
(let [page (ctpl/get-page file-data page-id)
objects (:objects page)
;; sync pairs the first child that refers to a main shape; the
;; others have no position to take
extra (->> (:shapes shape)
(map #(get objects %))
(reduce (fn [[seen extra] child]
(let [ref (:shape-ref child)]
(if (and (some? ref) (contains? seen ref))
[seen (conj extra (:id child))]
[(cond-> seen (some? ref) (conj ref)) extra])))
[#{} []])
(second))
ids (mapcat #(cfh/get-children-ids-with-self objects %) extra)]
(log/debug :hint "repairing shape :duplicate-ref" :id (:id shape) :name (:name shape) :page-id page-id)
(log/debug :hint " -> delete shapes" :shape-ids ids)
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/remove-objects ids))))
(defmethod repair-error :main-instance-not-a-variant
[_ {:keys [shape page-id args]} file-data _]
(let [repair-shape

View File

@ -0,0 +1,49 @@
;; 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.files.swap-slots
"Files older than the feature components/swap-group mark a swapped copy
head with a swap slot: a touched group `swap-slot-<id>`, where the id is
the deepest main child of the position the head takes. Validation reports
them, and the migrations read and repair them until
`app.common.files.comp-processors/convert-swap-slots` turns them into
override groups."
(:require
[app.common.data :as d]
[app.common.types.component :as ctk]
[cuerdas.core :as str]))
(defn swap-slot-group?
[group]
(str/starts-with? (name group) "swap-slot-"))
(defn build-swap-slot-group
"Convert a swap slot into a :touched group."
[swap-slot]
(when swap-slot
(keyword (str "swap-slot-" swap-slot))))
(defn normal-touched-groups
"The touched groups of the shape that are not swap slots."
[shape]
(into #{} (remove swap-slot-group?) (:touched shape)))
(defn get-swap-slot
"If the shape has a :touched group in the form :swap-slot-<uuid>, get the id."
[shape]
(when-let [group (d/seek swap-slot-group? (:touched shape))]
(parse-uuid (subs (name group) 10)))) ;; 10 is the length of "swap-slot-"
(defn set-swap-slot
"Add a touched group with a form :swap-slot-<uuid>."
[shape swap-slot]
(cond-> shape
(some? swap-slot)
(update :touched ctk/set-touched-group (build-swap-slot-group swap-slot))))
(defn remove-swap-slot
[shape]
(update shape :touched #(into #{} (remove swap-slot-group?) %)))

View File

@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cfh]
[app.common.files.swap-slots :as cfss]
[app.common.files.variant :as cfv]
[app.common.path-names :as cpn]
[app.common.schema :as sm]
@ -34,11 +35,9 @@
:child-not-found
:frame-not-found
:invalid-frame
:component-duplicate-slot
:component-not-main
:component-main-external
:component-not-found
:duplicate-slot
:invalid-main-instance-id
:invalid-main-instance-page
:invalid-main-instance-name
@ -64,7 +63,9 @@
:instance-head-not-frame
:invalid-text-touched
:misplaced-slot
:missing-slot
:misplaced-swap-group
:ref-not-in-near-main
:duplicate-ref
:shape-ref-cycle
:main-instance-not-a-variant
:main-instance-invalid-variant-id
@ -411,8 +412,7 @@
"Validate that if the copy has not been swapped, the component-id and component-file are
the same as in the referenced shape in the near main."
[shape file page libraries]
(when (and (nil? (ctk/get-swap-slot shape))
(not (ctk/swapped? shape)))
(when-not (ctk/swapped? shape)
(when-let [ref-shape (find-ref-shape* file page libraries shape)]
(when (or (not= (:component-id shape) (:component-id ref-shape))
(not= (:component-file shape) (:component-file ref-shape)))
@ -422,61 +422,60 @@
:component-id (:component-id ref-shape)
:component-file (:component-file ref-shape))))))
(defn- check-empty-swap-slot
"Validate that this shape does not have any swap slot."
(defn- check-no-swap-slot
"Validate that this shape has no swap slot, the form older files stored
swaps in."
[shape file page]
(when (some? (ctk/get-swap-slot shape))
(when (some? (cfss/get-swap-slot shape))
(report-error :misplaced-slot
"This shape should not have swap slot"
shape file page)))
(defn- has-duplicate-swap-slot?
"Returns true if any two children of `shape` share the same swap slot.
Uses a single pass with early exit on the first duplicate, avoiding
the intermediate `frequencies` map allocation."
[shape container]
(let [objects (:objects container)
result (reduce (fn [seen child-id]
(let [slot (ctk/get-swap-slot (get objects child-id))]
(if (nil? slot)
seen
(if (contains? seen slot)
(reduced true)
(conj seen slot)))))
#{}
(:shapes shape))]
(true? result)))
(defn- check-duplicate-swap-slot
"Validate that the children of this shape does not have duplicated slots."
(defn- check-not-swapped
"Validate that this shape is not marked as swapped: only a nested copy
head inside a copy can be."
[shape file page]
(when (has-duplicate-swap-slot? shape page)
(report-error :duplicate-slot
"This shape has children with the same swap slot"
(when (ctk/swapped? shape)
(report-error :misplaced-swap-group
"Only a nested copy head inside a copy can be swapped"
shape file page)))
(defn- check-required-swap-slot
"Validate that the shape has a swap slot if it's a subinstance head that has been
swapped (see `ctf/swapped-subhead?`)."
(defn- check-ref-in-near-main
"Validate that a nested copy head refers to a child of the near main of
its parent: the shape whose position it takes."
[shape file page libraries]
;; Guard first: if the shape already has a swap slot the invariant is satisfied
;; and we can avoid the ref-shape lookups entirely.
(when (and (nil? (ctk/get-swap-slot shape))
(ctf/swapped-subhead? shape page #(if (ctk/swapped? %)
(ctf/find-content-ref-shape file page libraries %
:include-deleted? true)
(find-ref-shape* file page libraries %))))
(let [near-match (ctf/find-near-match file page libraries shape :include-deleted? true :with-context? false)]
(when (some? near-match)
(report-error :missing-slot
"Shape has been swapped, should have swap slot"
shape file page
:swap-slot (or (ctk/get-swap-slot near-match) (:id near-match)))))))
(when (ctf/swapped-subhead? shape page #(ctf/find-content-ref-shape file page libraries %
:include-deleted? true))
(report-error :ref-not-in-near-main
(str/ffmt "Referenced shape % is not a child of the near main of the parent" (:shape-ref shape))
shape file page)))
(defn- duplicate-ref?
"Whether two children of the shape refer to the same main shape."
[shape page]
(let [objects (:objects page)]
(true? (reduce (fn [seen child-id]
(let [ref (:shape-ref (get objects child-id))]
(cond
(nil? ref) seen
(contains? seen ref) (reduced true)
:else (conj seen ref))))
#{}
(:shapes shape)))))
(defn- check-no-duplicate-ref
"Validate that no two children of a copy shape take the position of the
same main shape."
[shape file page]
(when (duplicate-ref? shape page)
(report-error :duplicate-ref
"Two children of this copy refer to the same main shape"
shape file page)))
(defn- check-valid-touched
"Validate that the text touched flags are coherent."
[shape file page]
(let [touched-groups (ctk/normal-touched-groups shape)
(let [touched-groups (or (:touched shape) #{})
content-touched? (touched-groups :content-group)
text-touched? (or (touched-groups :text-content-text)
(touched-groups :text-content-attribute)
@ -498,8 +497,8 @@
(check-component-main-head shape file page libraries)
(check-component-root shape file page)
(check-component-not-ref shape file page)
(check-empty-swap-slot shape file page)
(check-duplicate-swap-slot shape file page)
(check-no-swap-slot shape file page)
(check-not-swapped shape file page)
(run! #(check-shape % file page libraries :context :main-top) (:shapes shape)))
(defn- check-shape-main-root-nested
@ -511,7 +510,8 @@
(check-component-main-head shape file page libraries)
(check-component-not-root shape file page)
(check-component-not-ref shape file page)
(check-empty-swap-slot shape file page)
(check-no-swap-slot shape file page)
(check-not-swapped shape file page)
(run! #(check-shape % file page libraries :context :main-nested) (:shapes shape)))
(defn- check-shape-copy-root-top
@ -527,9 +527,10 @@
(check-component-root shape file page)
(check-component-ref shape file page libraries)
(check-ref-is-head shape file page libraries)
(check-empty-swap-slot shape file page)
(check-duplicate-swap-slot shape file page)
(check-no-swap-slot shape file page)
(check-not-swapped shape file page)
(check-valid-touched shape file page)
(check-no-duplicate-ref shape file page)
(run! #(check-shape % file page libraries :context :copy-top :library-exists library-exists) (:shapes shape))))
(defn- check-shape-copy-root-nested
@ -542,12 +543,14 @@
(check-component-not-root shape file page)
(check-valid-touched shape file page)
(check-ref-component-id shape file page libraries)
(check-required-swap-slot shape file page libraries)
(check-no-swap-slot shape file page)
(check-ref-in-near-main shape file page libraries)
;; We can have situations where the nested copy and the ancestor copy come from different libraries and some of them have been dettached
;; so we only validate the shape-ref if the ancestor is from a valid library
(when library-exists
(check-component-ref shape file page libraries)
(check-ref-is-head shape file page libraries))
(check-no-duplicate-ref shape file page)
(run! #(check-shape % file page libraries :context :copy-nested) (:shapes shape)))
(defn- check-shape-main-not-root
@ -556,7 +559,8 @@
(check-component-not-main-not-head shape file page)
(check-component-not-root shape file page)
(check-component-not-ref shape file page)
(check-empty-swap-slot shape file page)
(check-no-swap-slot shape file page)
(check-not-swapped shape file page)
(run! #(check-shape % file page libraries :context :main-any) (:shapes shape)))
(defn- check-shape-copy-not-root
@ -566,8 +570,10 @@
(check-component-not-root shape file page)
(check-component-ref shape file page libraries)
(check-ref-is-not-head shape file page libraries)
(check-empty-swap-slot shape file page)
(check-no-swap-slot shape file page)
(check-not-swapped shape file page)
(check-valid-touched shape file page)
(check-no-duplicate-ref shape file page)
(run! #(check-shape % file page libraries :context :copy-any) (:shapes shape)))
(defn- check-shape-not-component
@ -577,7 +583,8 @@
(check-component-not-main-not-head shape file page)
(check-component-not-root shape file page)
(check-component-not-ref shape file page)
(check-empty-swap-slot shape file page)
(check-no-swap-slot shape file page)
(check-not-swapped shape file page)
(run! #(check-shape % file page libraries :context :not-component) (:shapes shape)))
(defn- check-variant-container
@ -757,14 +764,6 @@
shape file page)
(check-shape-not-component shape file page libraries)))))))))
(defn check-component-duplicate-swap-slot
[component file]
(let [shape (get-in component [:objects (:main-instance-id component)])]
(when (has-duplicate-swap-slot? shape component)
(report-error :component-duplicate-slot
"This deleted component has children with the same swap slot"
component file nil))))
(defn check-ref-cycles
[component file]
(let [cycles-ids (-> (reduce-kv (fn [acc id shape]
@ -828,7 +827,6 @@
(check-main-inside-main component file)
(check-not-objects component file))
(when (:deleted component)
(check-component-duplicate-swap-slot component file)
(check-ref-cycles component file))
(when (ctk/is-variant? component)

View File

@ -357,19 +357,12 @@
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> promote to root")
(assoc % :component-root true)))
(some? (ctk/get-swap-slot shape))
; First level subinstances of a detached component can't have swap-slot
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> remove swap-slot")
(ctk/remove-swap-slot %)))
(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)
(and (nil? (ctk/get-swap-slot shape))
(not (ctk/swapped? shape)))
(not (ctk/swapped? shape))
; Near shape-ref of shape and children need to be advanced one level
; (except if the head is already swapped)
(generate-advance-nesting-level nil container libraries (:id shape)))
;; Otherwise, detach the shape and all children
@ -439,9 +432,9 @@
(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 normal touched groups (not swap slots) of the
;; ref-shape must be merged into the current shape, because they refer to
;; the new referenced 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)]
@ -450,15 +443,7 @@
(log/trace :msg (str " (ref touched: " (:touched ref-shape) ")"))
(assoc % :touched
(set/union (:touched shape)
(inherited-touched shape (ctk/normal-touched-groups ref-shape))))))
;; Swap slot must also be copied if the current shape has not any,
;; except if this is the first level subcopy.
(and (some? (ctk/get-swap-slot ref-shape))
(nil? (ctk/get-swap-slot shape))
(not= (:id shape) shape-id))
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg (str " (got swap-slot " (ctk/get-swap-slot ref-shape) ")"))
(ctk/set-swap-slot % (ctk/get-swap-slot ref-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
@ -1012,9 +997,6 @@
children-inst
children-main
container
component-container
file
libraries
only-inst
only-main
both
@ -1190,9 +1172,6 @@
children-inst
children-main
container
component-container
file
libraries
only-inst
only-main
both
@ -1222,14 +1201,12 @@
a swapped child pairs that way too, and takes `swapped-cb` in a normal sync,
`both-cb` in a reset, and does not pair in an inverse sync, so the main
child it replaces is removed and the swapped child is added in its place."
[changes shape-inst children-inst children-main container-inst container-main file libraries only-inst-cb only-main-cb both-cb swapped-cb moved-cb inverse? reset?]
[changes shape-inst children-inst children-main container-inst only-inst-cb only-main-cb both-cb swapped-cb moved-cb inverse? reset?]
(shape-log :trace (:id shape-inst) container-inst :msg "Compare children")
(let [pair?
(fn [child-main child-inst]
(or (and (ctk/is-main-of? child-main child-inst)
(not (and inverse? (ctk/swapped? child-inst))))
(and (ctf/match-swap-slot? child-main child-inst container-inst container-main file libraries)
(not reset?))))
(and (ctk/is-main-of? child-main child-inst)
(not (and inverse? (ctk/swapped? child-inst)))))
synced-pair?
(fn [child-main child-inst]
@ -2560,26 +2537,13 @@
(pcb/update-shapes
[shape-copy-id]
(fn [shape-copy objects]
(let [component-page
(ctf/get-component-page main-container main-component)
component-swap-children
(->> shape-main
:shapes
(map #(get (:objects component-page) %))
(filter #(some? (ctk/get-swap-slot %)))
(group-by ctk/get-swap-slot))
ids-map
(let [ids-map
(into {}
(comp
(map #(get objects %))
(keep
(fn [copy-shape]
(let [main-shape
(if (some? (ctk/get-swap-slot copy-shape))
(first (get component-swap-children (ctk/get-swap-slot copy-shape)))
(ctf/get-ref-shape main-container main-component copy-shape))]
(let [main-shape (ctf/get-ref-shape main-container main-component copy-shape)]
[(:id main-shape) (:id copy-shape)]))))
(:shapes shape-copy))
@ -2904,9 +2868,7 @@
;; swapped children (we will keep them too)
ignore-swapped-fn
(if ignore-swapped?
#(let [child (get objects %)]
(or (some? (ctk/get-swap-slot child))
(ctk/swapped? child)))
#(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
@ -2941,37 +2903,14 @@
(maybe-sync sync-typographies? done? #(generate-sync-file % file-id :typographies asset-id library-id libraries current-file-id early-return?))))))
(defn generate-sync-head
[changes file-full libraries container id reset?]
(let [shape-inst (ctn/get-shape container id)
objects (:objects container)
parent (get objects (:parent-id shape-inst))
head (ctn/get-component-shape objects parent)
changes
(-> changes
(pcb/with-container container)
(pcb/with-objects (:objects container))
(generate-sync-shape-direct file-full libraries container (:id head) reset?))]
changes))
(defn generate-reset-component
"Reset the copy head to its near main. A swapped head turns back into the
component of the main shape it refers to, keeping its id."
[changes file-full libraries container id]
(let [objects (:objects container)
swap-slot (-> (ctn/get-shape container id)
(ctk/get-swap-slot))
changes
(-> changes
(pcb/with-container container)
(pcb/with-objects objects)
(generate-sync-shape-direct file-full libraries container id true))]
;; If the shape has been swapped, the reset also have to undo the swap. As the
;; shape has been reset and it's not touched now, by the sync above, if we
;; do another reset of the parent, the shape will be reset as it is in the
;; near main component.
(cond-> changes
(some? swap-slot)
(generate-sync-head file-full libraries container id true))))
(-> changes
(pcb/with-container container)
(pcb/with-objects (:objects container))
(generate-sync-shape-direct file-full libraries container id true)))
(defn generate-duplicate-flows
[changes shapes page ids-map]
@ -3138,11 +3077,11 @@
(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 true nil))
(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 true 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? remove-swap-slot? 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
@ -3165,8 +3104,6 @@
(ctk/instance-root? obj))
duplicating-component? (or duplicating-component? (ctk/instance-head? obj))
is-component-main? (ctk/main-instance? obj)
subinstance-head? (ctk/subinstance-head? obj)
instance-root? (ctk/instance-root? obj)
into-component? (and duplicating-component?
(ctn/in-any-component? objects parent))
@ -3198,11 +3135,6 @@
:parent-id parent-id
:frame-id frame-id)
(cond-> (and (not instance-root?)
subinstance-head?
remove-swap-slot?)
(ctk/remove-swap-slot))
(dissoc :shapes
:use-for-thumbnail)
@ -3282,11 +3214,6 @@
new-id
duplicating-component?
true
(and remove-swap-slot?
;; only remove swap slot of children when the current shape
;; is not a subinstance head nor a instance root
(not subinstance-head?)
(not instance-root?))
variant-props))
changes
(map (d/getf objects) (:shapes obj)))))))
@ -3318,9 +3245,7 @@
(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 (if (nil? (ctk/get-swap-slot shape))
(ctn/get-nesting-level-delta (:objects page) shape uuid/zero)
0)]
(let [level-delta (ctn/get-nesting-level-delta (:objects page) shape uuid/zero)]
(cond
(not (pos? level-delta))
objects

View File

@ -467,9 +467,6 @@
variant-shapes (filter ctk/is-variant? shapes)
component-main-parent
(ctn/find-component-main objects parent false)
groups-to-delete
(loop [current-id (first parents)
to-check (rest parents)
@ -573,14 +570,6 @@
(cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent))
(pcb/update-shapes ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))))
;; Remove the swap slots if it is moving to a different component
(pcb/update-shapes
child-heads-ids
(fn [shape]
(cond-> shape
(not= component-main-parent (ctn/find-component-main objects shape false))
(ctk/remove-swap-slot))))
;; Remove component-root property when moving a shape inside a component
(cond-> (ctn/get-instance-root objects parent)
(pcb/update-shapes children-ids #(dissoc % :component-root)))

View File

@ -73,9 +73,9 @@
"As part of the keep-touched process on a switch, given a child on the original
copy that was swapped (orig-swapped-child), and its related shape on the new copy
(related-shape-in-new), move the orig-swapped-child into the parent of
related-shape-in-new, fix its swap-slot if needed, and then delete
related-shape-in-new"
[changes related-shape-in-new orig-swapped-child ldata page swap-ref-id]
related-shape-in-new, make it take the place of related-shape-in-new, and
then delete related-shape-in-new"
[changes related-shape-in-new orig-swapped-child ldata page]
(let [;; Before to the swap, temporary move the previous
;; shape to the root panel to avoid problems when
;; the previous parent is deleted.
@ -85,7 +85,6 @@
(pcb/change-parent uuid/zero [orig-swapped-child] 0 {:allow-altering-copies true}))
objects (pcb/get-objects changes)
prev-swap-slot (ctk/get-swap-slot orig-swapped-child)
current-parent (get objects (:parent-id related-shape-in-new))
pos (d/index-of (:shapes current-parent) (:id related-shape-in-new))]
@ -94,27 +93,14 @@
;; Move the previous shape to the new parent
(pcb/change-parent (:parent-id related-shape-in-new) [orig-swapped-child] pos {:allow-altering-copies true})
;; We need to update the swap slot only when it pointed
;; to the swap-ref-id. Oterwise this is a swapped item
;; inside a nested copy, so we need to keep it.
(cond->
(= prev-swap-slot swap-ref-id)
(pcb/update-shapes
[(:id orig-swapped-child)]
#(-> %
(ctk/remove-swap-slot)
(ctk/set-swap-slot (:shape-ref related-shape-in-new)))))
;; A swap stored as override takes the place of the related shape,
;; and stays a swap while its component differs from that one
(cond->
(ctk/swapped? orig-swapped-child)
(pcb/update-shapes
[(:id orig-swapped-child)]
#(cond-> (assoc % :shape-ref (:shape-ref related-shape-in-new))
(and (= (:component-id %) (:component-id related-shape-in-new))
(= (:component-file %) (:component-file related-shape-in-new)))
(ctk/remove-swap-group))))
;; Take the place of the related shape; it stays a swap while its
;; component differs from that one
(pcb/update-shapes
[(:id orig-swapped-child)]
#(cond-> (assoc % :shape-ref (:shape-ref related-shape-in-new))
(and (= (:component-id %) (:component-id related-shape-in-new))
(= (:component-file %) (:component-file related-shape-in-new)))
(ctk/remove-swap-group)))
;; Delete new non-swapped item
(cls/generate-delete-shapes ldata page objects (d/ordered-set (:id related-shape-in-new)) {:allow-altering-copies true})
@ -131,7 +117,7 @@
;; Ignore first and last (base-parent and shape)
ancestors (when (and ancestors (<= 3 num-ancestors))
(subvec (vec ancestors) 1 (dec num-ancestors)))]
(some #(or (ctk/get-swap-slot %) (ctk/swapped? %)) ancestors)))
(some ctk/swapped? ancestors)))
(defn- find-shape-ref-child-of
"Get the shape referenced by the shape-ref of the near main of the shape,
@ -220,23 +206,14 @@
(let [;; The switched shape itself relates to the root of the new one
root? (= (:id orig-child-touched) (:id original-shape))
;; If the orig-child-touched was swapped, get its swap-slot
swap-slot (when-not root? (ctk/get-swap-slot orig-child-touched))
swapped? (and (not root?)
(or (some? swap-slot) (ctk/swapped? orig-child-touched)))
swapped? (and (not root?) (ctk/swapped? orig-child-touched))
;; orig-child-touched is in a copy. Get the referenced shape on the main component
;; If there is a swap slot, we will get the referenced shape in another way
orig-ref-shape (cond
root? orig-base-ref-shape
swap-slot nil
:else (find-shape-ref-child-of container libraries orig-child-touched (:id orig-base-ref-shape)))
orig-ref-shape (if root?
orig-base-ref-shape
(find-shape-ref-child-of container libraries orig-child-touched (:id orig-base-ref-shape)))
orig-ref-id (if swap-slot
;; If there is a swap slot, find the referenced shape id
(ctf/find-ref-id-for-swapped orig-child-touched container libraries)
;; If there is not a swap slot, get the id from the orig-ref-shape
(:id orig-ref-shape))
orig-ref-id (:id orig-ref-shape)
;; Get the shape path of the referenced main
shape-path (get o-ref-shapes-p-map orig-ref-id)
@ -254,7 +231,7 @@
(if swapped?
;; If the orig-child-touched was swapped, keep it
(keep-swapped-item changes related-shape-in-new orig-child-touched
ldata page orig-ref-id)
ldata page)
;; If the orig-child-touched wasn't swapped, copy
;; the touched attributes into it
(cll/update-attrs-on-switch

View File

@ -9,7 +9,9 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.comp-processors :as cfcp]
[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.path-names :as cpn]
@ -154,7 +156,7 @@
(let [page (thf/current-page file)
objects (:objects page)]
(when (ctn/in-any-component? objects (get objects (:parent-id shape)))
(ctf/find-swap-slot shape page file (assoc libraries (:id file) file)))))
(cfcp/find-swap-slot shape page file (assoc libraries (:id file) file)))))
(defn store-swap-as-slot
"Store the swap of the head `head-id` the way files older than the feature
@ -172,7 +174,7 @@
(ctk/remove-swap-group))
(some? slot)
(ctk/set-swap-slot slot))]
(cfss/set-swap-slot slot))]
(update file :data ctpl/update-page (:id page) #(ctst/set-shape % head'))))
(defn component-swap

View File

@ -445,16 +445,16 @@
[changes nil])
;; the old form does not validate any more
file' (cond-> (thf/apply-changes file changes :validate? (and (not propagate-fn) (not as-slot?)))
as-slot?
(-> (thc/store-swap-as-slot libraries (:id new-shape) slot)
(cond-> (not propagate-fn) (thf/validate-file!))))]
(thc/store-swap-as-slot libraries (:id new-shape) slot))]
(when new-shape-label
(thi/rm-id! (:id new-shape))
(thi/set-id! new-shape-label (:id new-shape)))
(if propagate-fn
(-> (propagate-fn file')
(thf/validate-file!))
(cond-> (propagate-fn file')
(not as-slot?) (thf/validate-file!))
file')))
(defn swap-component-in-shape

View File

@ -9,10 +9,10 @@
[app.common.data :as d]
[app.common.features :as ffeat]
[app.common.files.changes :as cfc]
[app.common.files.swap-slots :as cfss]
[app.common.files.validate :as cfv]
[app.common.pprint :refer [pprint]]
[app.common.test-helpers.ids-map :as thi]
[app.common.types.component :as ctk]
[app.common.types.file :as ctf]
[app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl]
@ -137,9 +137,9 @@
(defn- stringify-keys [m keys]
(let [kv (-> (select-keys m keys)
(assoc :swap-slot (when ((set keys) :swap-slot)
(ctk/get-swap-slot m)))
(cfss/get-swap-slot m)))
(assoc :swap-slot-label (when ((set keys) :swap-slot-label)
(when-let [slot (ctk/get-swap-slot m)]
(when-let [slot (cfss/get-swap-slot m)]
(thi/label slot))))
(d/without-nils))

View File

@ -6,14 +6,12 @@
(ns app.common.types.component
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[app.common.time :as-alias ct]
[app.common.types.page :as ctp]
[app.common.types.plugins :as ctpg]
[app.common.types.variant :as ctv]
[cuerdas.core :as str]))
[app.common.types.variant :as ctv]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA
@ -291,62 +289,6 @@
(swapped? shape)
(update :touched disj :swap-group)))
(defn build-swap-slot-group
"Convert a swap-slot into a :touched group"
[swap-slot]
(when swap-slot
(keyword (str "swap-slot-" swap-slot))))
(defn swap-slot?
[group]
(str/starts-with? (name group) "swap-slot-"))
(def ^:private xf:normal-touched
"Transducer that removes swap-slot touched groups."
(remove swap-slot?))
(defn normal-touched-groups
"Gets all touched groups that are not swap slots.
Returns an empty set immediately when `:touched` is nil or empty,
avoiding an unnecessary `into #{}` allocation for the common case."
[shape]
(let [touched (:touched shape)]
(if (empty? touched)
#{}
(into #{} xf:normal-touched touched))))
(defn group->swap-slot
[group]
(parse-uuid (subs (name group) 10))) ;; 10 is the length of "swap-slot-"
(defn get-swap-slot
"If the shape has a :touched group in the form :swap-slot-<uuid>, get the id."
[shape]
(let [group (d/seek swap-slot? (:touched shape))]
(when group
(group->swap-slot group))))
(defn set-swap-slot
"Add a touched group with a form :swap-slot-<uuid>."
[shape swap-slot]
(cond-> shape
(some? swap-slot)
(update :touched set-touched-group (build-swap-slot-group swap-slot))))
(defn match-swap-slot?
[shape-main shape-inst]
(let [slot-main (get-swap-slot shape-main)
slot-inst (get-swap-slot shape-inst)]
(when (some? slot-inst)
(or (= slot-main slot-inst)
(= (:id shape-main) slot-inst)))))
(defn remove-swap-slot
[shape]
(update shape :touched
(fn [touched]
(into #{} (remove #(str/starts-with? (name %) "swap-slot-") touched)))))
(defn get-deleted-component-root
[component]
(get-in component [:objects (:main-instance-id component)]))
@ -426,6 +368,4 @@
[group]
(ex/ignoring
(or (contains? all-touched-groups group)
(= group :swap-group)
(and (swap-slot? group)
(some? (group->swap-slot group))))))
(= group :swap-group))))

View File

@ -450,27 +450,6 @@
:include-deleted? include-deleted?
:with-context? with-context?)))
(defn find-near-match
"Locate the shape that occupies the same position in the near main component.
This will be the ref-shape except if the shape is a copy subhead that has been
swapped. In this case, the near match will be the ref-shape that was before
the swap."
[file container libraries shape & {:keys [include-deleted? with-context?] :or {include-deleted? false with-context? false}}]
(let [parent-shape (ctst/get-shape container (:parent-id shape))
parent-ref-shape (when parent-shape
(find-ref-shape file container libraries parent-shape :include-deleted? include-deleted? :with-context? true))
ref-container (when parent-ref-shape
(:container (meta parent-ref-shape)))
shape-index (when parent-shape
(d/index-of (:shapes parent-shape) (:id shape)))
near-match-id (when (and parent-ref-shape shape-index)
(get (:shapes parent-ref-shape) shape-index))
near-match (when near-match-id
(cond-> (ctst/get-shape ref-container near-match-id)
with-context?
(with-meta (meta parent-ref-shape))))]
near-match))
(defn swapped-subhead?
"Whether `shape` references outside its near main parent and needs a swap slot.
Same-parent positional differences are synchronized as reorders."
@ -557,80 +536,6 @@
(let [ref-component (find-ref-component file page libraries shape :include-deleted? true)]
(true? (= (:id component) (:id ref-component))))))
(defn find-swap-slot
([shape container file libraries]
(find-swap-slot shape container file libraries #{}))
([shape container file libraries viewed-ids]
(if (contains? viewed-ids (:id shape)) ;; prevent cycles
nil
(if-let [swap-slot (ctk/get-swap-slot shape)]
swap-slot
(let [ref-shape (find-ref-shape file
container
libraries
shape
:include-deleted? true
:with-context? true)
shape-meta (meta ref-shape)
ref-file (:file shape-meta)
ref-container (:container shape-meta)]
(if ref-shape
(if-let [swap-slot (ctk/get-swap-slot ref-shape)]
swap-slot
(if (ctk/main-instance? ref-shape)
(:id shape)
(find-swap-slot ref-shape ref-container ref-file libraries (conj viewed-ids (:id shape)))))
(:id shape))))))) ;; If cannot locate ref-shape (e.g. in an unlinked external library), use what we have
(defn match-swap-slot?
[shape-main shape-inst container-inst container-main file libraries]
(let [slot-main (find-swap-slot shape-main container-main file libraries)
slot-inst (find-swap-slot shape-inst container-inst file libraries)]
(when (some? slot-inst)
(or (= slot-main slot-inst)
(= (:id shape-main) slot-inst)))))
(defn- find-next-related-swap-shape-id
"Go up from the chain of references shapes that will eventually lead to the shape
with swap-slot-id as id. Returns the next shape on the chain"
[parent swap-slot-id libraries]
(let [container (get-component-container-from-head parent libraries)
objects (:objects container)
children (cfh/get-children objects (:id parent))
original-shape-id (->> children
(filter #(= swap-slot-id (:id %)))
first
:id)]
(if original-shape-id
;; Return the children which id is the swap-slot-id
original-shape-id
;; No children with swap-slot-id as id, go up
(let [referenced-shape (find-ref-shape nil container libraries parent)
;; Recursive call that will get the id of the next shape on
;; the chain that ends on a shape with swap-slot-id as id
next-shape-id (when referenced-shape
(find-next-related-swap-shape-id referenced-shape swap-slot-id libraries))]
;; Return the children which shape-ref points to the next-shape-id
(->> children
(filter #(= next-shape-id (:shape-ref %)))
first
:id)))))
(defn find-ref-id-for-swapped
"When a shape has been swapped, find the original ref-id that the shape had
before the swap"
[shape container libraries]
(let [swap-slot (ctk/get-swap-slot shape)
objects (:objects container)
parent (get objects (:parent-id shape))
parent-head (ctn/get-head-shape objects parent)
parent-ref (find-ref-shape nil container libraries parent-head)]
(when (and swap-slot parent-ref)
(find-next-related-swap-shape-id parent-ref swap-slot libraries))))
(defn get-component-shapes
"Retrieve all shapes of the component"
[file-data component]
@ -1317,10 +1222,10 @@
(defn get-touched-from-ref-chain-until-target-ref
"Returns a set with the :touched of all the items on the shape
ref chain until target-ref, including itself"
[container libraries shape target-ref & {:keys [remove-slots?] :or {remove-slots? true}}]
(let [chain (get-ref-chain-until-target-ref container libraries shape target-ref)
more-touched (cond->> chain
:always (map :touched)
:always (reduce set/union #{})
remove-slots? (remove ctk/swap-slot?))]
(set/union (or (:touched shape) #{}) (set more-touched))))
[container libraries shape target-ref]
(let [chain (get-ref-chain-until-target-ref container libraries shape target-ref)
more-touched (->> chain
(map :touched)
(remove nil?)
(apply set/union))]
(set/union (or (:touched shape) #{}) more-touched)))

View File

@ -8,12 +8,12 @@
(:require
[app.common.data :as d]
[app.common.files.comp-processors :as cfcp]
[app.common.files.swap-slots :as cfss]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[clojure.test :as t]))
@ -332,7 +332,7 @@
{(thi/id :copy2-nested-head)
{:touched
[nil
#{(ctk/build-swap-slot-group (str (thi/id :nested-head)))}]}}}}}}]
#{(cfss/build-swap-slot-group (str (thi/id :nested-head)))}]}}}}}}]
(t/is (= expected-diff diff))))
@ -412,7 +412,7 @@
{(thi/id :nested-subhead2)
{:touched
[nil
#{(ctk/build-swap-slot-group (str (thi/id :nested-head1)))}]}}}}}}]
#{(cfss/build-swap-slot-group (str (thi/id :nested-head1)))}]}}}}}}]
(t/is (= expected-diff diff))))
@ -469,7 +469,7 @@
{(thi/id :copy2-nested-head)
{:touched
[nil
#{(ctk/build-swap-slot-group (str (thi/id :nested2-head)))}]}}}}}}]
#{(cfss/build-swap-slot-group (str (thi/id :nested2-head)))}]}}}}}}]
(t/is (= expected-diff diff)))))
@ -862,3 +862,372 @@
(t/is (= expected-diff diff)))))
(t/deftest test-get-swap-slot
(let [s1 (ths/sample-shape :s1)
s2 (ths/sample-shape :s2 :touched #{:visibility-group})
s3 (ths/sample-shape :s3 :touched #{:swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f})
s4 (ths/sample-shape :s4 :touched #{:fill-group
:swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f})
s5 (ths/sample-shape :s5 :touched #{:swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f
:content-group
:geometry-group})
s6 (ths/sample-shape :s6 :touched #{:swap-slot-9cc181fa})]
(t/is (nil? (cfss/get-swap-slot s1)))
(t/is (nil? (cfss/get-swap-slot s2)))
(t/is (= (cfss/get-swap-slot s3) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
(t/is (= (cfss/get-swap-slot s4) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
(t/is (= (cfss/get-swap-slot s5) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
(t/is (nil? (cfss/get-swap-slot s6)))))
(t/deftest test-find-near-match
(t/testing "shapes not in a component have no near match"
(let [file
;; :frame1 [:name Frame1]
;; :child1 [:name Rect1]
(-> (thf/sample-file :file1)
(tho/add-frame-with-child :frame1 :shape1))
page (thf/current-page file)
frame1 (ths/get-shape file :frame1)
shape1 (ths/get-shape file :shape1)
near-match1 (cfcp/find-near-match file page {} frame1)
near-match2 (cfcp/find-near-match file page {} shape1)]
(t/is (nil? near-match1))
(t/is (nil? near-match2))))
(t/testing "shapes in a copy get the ref-shape"
(let [file
;; {:main-root} [:name Frame1] # [Component :component1]
;; :main-child1 [:name Rect1]
;; :main-child2 [:name Rect2]
;; :main-child3 [:name Rect3]
;;
;; :copy-root [:name Frame1] #--> [Component :component1] :main-root
;; <no-label> [:name Rect1] ---> :main-child1
;; <no-label> [:name Rect2] ---> :main-child2
;; <no-label> [:name Rect3] ---> :main-child3
(-> (thf/sample-file :file1)
(tho/add-component-with-many-children-and-copy :component1
:main-root [:main-child1 :main-child2 :main-child3]
:copy-root))
page (thf/current-page file)
main-root (ths/get-shape file :main-root)
main-child1 (ths/get-shape file :main-child1)
main-child2 (ths/get-shape file :main-child2)
main-child3 (ths/get-shape file :main-child3)
copy-root (ths/get-shape file :copy-root)
copy-child1 (ths/get-shape-by-id file (nth (:shapes copy-root) 0))
copy-child2 (ths/get-shape-by-id file (nth (:shapes copy-root) 1))
copy-child3 (ths/get-shape-by-id file (nth (:shapes copy-root) 2))
near-main-root (cfcp/find-near-match file page {} main-root)
near-main-child1 (cfcp/find-near-match file page {} main-child1)
near-main-child2 (cfcp/find-near-match file page {} main-child2)
near-main-child3 (cfcp/find-near-match file page {} main-child3)
near-copy-root (cfcp/find-near-match file page {} copy-root)
near-copy-child1 (cfcp/find-near-match file page {} copy-child1)
near-copy-child2 (cfcp/find-near-match file page {} copy-child2)
near-copy-child3 (cfcp/find-near-match file page {} copy-child3)]
(t/is (nil? near-main-root))
(t/is (nil? near-main-child1))
(t/is (nil? near-main-child2))
(t/is (nil? near-main-child3))
(t/is (nil? near-copy-root))
(t/is (= (:id near-copy-child1) (thi/id :main-child1)))
(t/is (= (:id near-copy-child2) (thi/id :main-child2)))
(t/is (= (:id near-copy-child3) (thi/id :main-child3)))))
(t/testing "shapes in nested not swapped copies get the ref-shape"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
;; :nested-child [:name Rect1] ---> :main1-child
;;
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame1] @--> [Component :component1] :nested-head
;; :copy2-nested-child [:name Rect1] ---> :nested-child
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head
:nested-head-params {:children-labels [:nested-child]})
(thc/instantiate-component :component2 :copy2
:children-labels [:copy2-nested-head :copy2-nested-child]))
page (thf/current-page file)
main1-root (ths/get-shape file :main1-root)
main1-child (ths/get-shape file :main1-child)
main2-root (ths/get-shape file :main2-root)
nested-head (ths/get-shape file :nested-head)
nested-child (ths/get-shape file :nested-child)
copy2 (ths/get-shape file :copy2)
copy2-nested-head (ths/get-shape file :copy2-nested-head)
copy2-nested-child (ths/get-shape file :copy2-nested-child)
near-main1-root (cfcp/find-near-match file page {} main1-root)
near-main1-child (cfcp/find-near-match file page {} main1-child)
near-main2-root (cfcp/find-near-match file page {} main2-root)
near-nested-head (cfcp/find-near-match file page {} nested-head)
near-nested-child (cfcp/find-near-match file page {} nested-child)
near-copy2 (cfcp/find-near-match file page {} copy2)
near-copy2-nested-head (cfcp/find-near-match file page {} copy2-nested-head)
near-copy2-nested-child (cfcp/find-near-match file page {} copy2-nested-child)]
(t/is (nil? near-main1-root))
(t/is (nil? near-main1-child))
(t/is (nil? near-main2-root))
(t/is (nil? near-nested-head))
(t/is (= (:id near-nested-child) (thi/id :main1-child)))
(t/is (nil? near-copy2))
(t/is (= (:id near-copy2-nested-head) (thi/id :nested-head)))
(t/is (= (:id near-copy2-nested-child) (thi/id :nested-child)))))
(t/testing "shapes in swapped copies get the swap slot"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
;; :nested-child [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :main3-root
;; {swap-slot :nested-head}
;; <no-label> [:name Rect3] ---> :main3-child
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head
:nested-head-params {:children-labels [:nested-child]})
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head])
(tho/add-simple-component :component3 :main3-root :main3-child
:root-params {:name "Frame3"}
:child-params {:name "Rect3"})
(tho/swap-component-in-first-child :copy2 :component3 :as-slot? true))
page (thf/current-page file)
main1-root (ths/get-shape file :main1-root)
main1-child (ths/get-shape file :main1-child)
main2-root (ths/get-shape file :main2-root)
nested-head (ths/get-shape file :nested-head)
nested-child (ths/get-shape file :nested-child)
copy2 (ths/get-shape file :copy2)
copy2-nested-head (ths/get-shape file :copy2-nested-head)
copy2-nested-child (ths/get-shape-by-id file (first (:shapes copy2-nested-head)))
near-main1-root (cfcp/find-near-match file page {} main1-root)
near-main1-child (cfcp/find-near-match file page {} main1-child)
near-main2-root (cfcp/find-near-match file page {} main2-root)
near-nested-head (cfcp/find-near-match file page {} nested-head)
near-nested-child (cfcp/find-near-match file page {} nested-child)
near-copy2 (cfcp/find-near-match file page {} copy2)
near-copy2-nested-head (cfcp/find-near-match file page {} copy2-nested-head)
near-copy2-nested-child (cfcp/find-near-match file page {} copy2-nested-child)]
(t/is (nil? near-main1-root))
(t/is (nil? near-main1-child))
(t/is (nil? near-main2-root))
(t/is (nil? near-nested-head))
(t/is (= (:id near-nested-child) (thi/id :main1-child)))
(t/is (nil? near-copy2))
(t/is (= (:id near-copy2-nested-head) (thi/id :nested-head)))
(t/is (= (:id near-copy2-nested-child) (thi/id :main3-child)))))
(t/testing "shapes in second level nested copies under swapped get the shape in the new main"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested2-head [:name Frame1] @--> [Component :component1] :main1-root
;; :nested2-child [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; {:main4-root} [:name Frame4] # [Component :component4]
;; :nested4-head [:name Frame3] @--> [Component :component1] :main3-root
;; :nested4-child [:name Rect3] ---> :main3-child
;;
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame4] @--> [Component :component4] :main4-root
;; {swap-slot :nested2-head}
;; <no-label> [:name Frame3] @--> :nested4-head
;; <no-label> [:name Rect3] ---> :nested4-child
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested2-head
:nested-head-params {:children-labels [:nested2-child]})
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head])
(tho/add-nested-component :component3 :main3-root :main3-child
:component4 :main4-root :nested4-head
:root1-params {:name "Frame3"}
:main1-child-params {:name "Rect3"}
:main2-root-params {:name "Frame4"}
:nested-head-params {:children-labels [:nested4-child]})
(tho/swap-component-in-first-child :copy2 :component4 :as-slot? true))
page (thf/current-page file)
main1-root (ths/get-shape file :main1-root)
main1-child (ths/get-shape file :main1-child)
main2-root (ths/get-shape file :main2-root)
nested2-head (ths/get-shape file :nested2-head)
nested2-child (ths/get-shape file :nested2-child)
main3-root (ths/get-shape file :main3-root)
main3-child (ths/get-shape file :main3-child)
main4-root (ths/get-shape file :main4-root)
nested4-head (ths/get-shape file :nested4-head)
nested4-child (ths/get-shape file :nested4-child)
copy2 (ths/get-shape file :copy2)
copy2-nested-head (ths/get-shape file :copy2-nested-head)
copy2-nested4-head (ths/get-shape-by-id file (first (:shapes copy2-nested-head)))
copy2-nested4-child (ths/get-shape-by-id file (first (:shapes copy2-nested4-head)))
near-main1-root (cfcp/find-near-match file page {} main1-root)
near-main1-child (cfcp/find-near-match file page {} main1-child)
near-main2-root (cfcp/find-near-match file page {} main2-root)
near-nested2-head (cfcp/find-near-match file page {} nested2-head)
near-nested2-child (cfcp/find-near-match file page {} nested2-child)
near-main3-root (cfcp/find-near-match file page {} main3-root)
near-main3-child (cfcp/find-near-match file page {} main3-child)
near-main4-root (cfcp/find-near-match file page {} main4-root)
near-nested4-head (cfcp/find-near-match file page {} nested4-head)
near-nested4-child (cfcp/find-near-match file page {} nested4-child)
near-copy2 (cfcp/find-near-match file page {} copy2)
near-copy2-nested-head (cfcp/find-near-match file page {} copy2-nested-head)
near-copy2-nested4-head (cfcp/find-near-match file page {} copy2-nested4-head)
near-copy2-nested4-child (cfcp/find-near-match file page {} copy2-nested4-child)]
(t/is (nil? near-main1-root))
(t/is (nil? near-main1-child))
(t/is (nil? near-main2-root))
(t/is (nil? near-nested2-head))
(t/is (= (:id near-nested2-child) (thi/id :main1-child)))
(t/is (nil? near-main3-root))
(t/is (nil? near-main3-child))
(t/is (nil? near-main4-root))
(t/is (nil? near-nested4-head))
(t/is (= (:id near-nested4-child) (thi/id :main3-child)))
(t/is (nil? near-copy2))
(t/is (= (:id near-copy2-nested-head) (thi/id :nested2-head)))
(t/is (= (:id near-copy2-nested4-head) (thi/id :nested4-head)))
(t/is (= (:id near-copy2-nested4-child) (thi/id :nested4-child)))))
(t/testing "component in external libraries still work well"
(let [library1
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested2-head [:name Frame1] @--> [Component :component1] :main1-root
;; :nested2-child [:name Rect1] ---> :main1-child
(-> (thf/sample-file :library1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested2-head
:nested-head-params {:children-labels [:nested2-child]}))
library2
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; {:main4-root} [:name Frame4] # [Component :component4]
;; :nested4-head [:name Frame3] @--> [Component :component1] :main3-root
;; :nested4-child [:name Rect3] ---> :main3-child
(-> (thf/sample-file :library2)
(tho/add-nested-component :component3 :main3-root :main3-child
:component4 :main4-root :nested4-head
:root1-params {:name "Frame3"}
:main1-child-params {:name "Rect3"}
:main2-root-params {:name "Frame4"}
:nested-head-params {:children-labels [:nested4-child]}))
file
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame4] @--> [Component :component4] :main4-root
;; {swap-slot :nested2-head}
;; <no-label> [:name Frame3] @--> :nested4-head
;; <no-label> [:name Rect3] ---> :nested4-child
(-> (thf/sample-file :file1)
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head]
:library library1)
(tho/swap-component-in-first-child :copy2 :component4 :library library2 :as-slot? true))
page-library1 (thf/current-page library1)
page-library2 (thf/current-page library2)
page-file (thf/current-page file)
libraries {(:id library1) library1
(:id library2) library2}
main1-root (ths/get-shape library1 :main1-root)
main1-child (ths/get-shape library1 :main1-child)
main2-root (ths/get-shape library1 :main2-root)
nested2-head (ths/get-shape library1 :nested2-head)
nested2-child (ths/get-shape library1 :nested2-child)
main3-root (ths/get-shape library2 :main3-root)
main3-child (ths/get-shape library2 :main3-child)
main4-root (ths/get-shape library2 :main4-root)
nested4-head (ths/get-shape library2 :nested4-head)
nested4-child (ths/get-shape library2 :nested4-child)
copy2 (ths/get-shape file :copy2)
copy2-nested-head (ths/get-shape file :copy2-nested-head)
copy2-nested4-head (ths/get-shape-by-id file (first (:shapes copy2-nested-head)))
copy2-nested4-child (ths/get-shape-by-id file (first (:shapes copy2-nested4-head)))
near-main1-root (cfcp/find-near-match file page-file libraries main1-root)
near-main1-child (cfcp/find-near-match file page-file libraries main1-child)
near-main2-root (cfcp/find-near-match file page-file libraries main2-root)
near-nested2-head (cfcp/find-near-match library1 page-library1 libraries nested2-head)
near-nested2-child (cfcp/find-near-match library1 page-library1 libraries nested2-child)
near-main3-root (cfcp/find-near-match file page-file libraries main3-root)
near-main3-child (cfcp/find-near-match file page-file libraries main3-child)
near-main4-root (cfcp/find-near-match file page-file libraries main4-root)
near-nested4-head (cfcp/find-near-match library2 page-library2 libraries nested4-head)
near-nested4-child (cfcp/find-near-match library2 page-library2 libraries nested4-child)
near-copy2 (cfcp/find-near-match file page-file libraries copy2)
near-copy2-nested-head (cfcp/find-near-match file page-file libraries copy2-nested-head)
near-copy2-nested4-head (cfcp/find-near-match file page-file libraries copy2-nested4-head)
near-copy2-nested4-child (cfcp/find-near-match file page-file libraries copy2-nested4-child)]
(t/is (some? main1-root))
(t/is (some? main1-child))
(t/is (some? main2-root))
(t/is (some? nested2-head))
(t/is (some? nested2-child))
(t/is (some? main3-root))
(t/is (some? main3-child))
(t/is (some? main4-root))
(t/is (some? nested4-head))
(t/is (some? nested4-child))
(t/is (some? copy2))
(t/is (some? copy2-nested-head))
(t/is (some? copy2-nested4-head))
(t/is (some? copy2-nested4-child))
(t/is (nil? near-main1-root))
(t/is (nil? near-main1-child))
(t/is (nil? near-main2-root))
(t/is (nil? near-nested2-head))
(t/is (= (:id near-nested2-child) (thi/id :main1-child)))
(t/is (nil? near-main3-root))
(t/is (nil? near-main3-child))
(t/is (nil? near-main4-root))
(t/is (nil? near-nested4-head))
(t/is (= (:id near-nested4-child) (thi/id :main3-child)))
(t/is (nil? near-copy2))
(t/is (= (:id near-copy2-nested-head) (thi/id :nested2-head)))
(t/is (= (:id near-copy2-nested4-head) (thi/id :nested4-head)))
(t/is (= (:id near-copy2-nested4-child) (thi/id :nested4-child))))))

View File

@ -0,0 +1,283 @@
;; 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 common-tests.files.convert-swap-slots-test
"The conversion of swap slots, the form files older than the feature
components/swap-group store swaps in, to override groups."
(:require
[app.common.features :as cfeat]
[app.common.files.changes-builder :as pcb]
[app.common.files.comp-processors :as cfcp]
[app.common.files.helpers :as cfh]
[app.common.files.migrations :as fmg]
[app.common.files.swap-slots :as cfss]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.logic.shapes :as cls]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(defn- add-components
[file]
;; {:star-main} [:name Frame1] # [Component :star]
;; :star-rect [:name Rect1]
;; {:heart-main} [:name Frame1] # [Component :heart]
;; :heart-rect [:name Rect1]
;; {:dot-main} [:name Frame1] # [Component :dot]
;; :dot-rect [:name Rect1]
;; {:badge-main} [:name Frame1] # [Component :badge]
;; :badge-dot [:name Frame1] @--> [Component :dot] :dot-main
;; {:card-main} [:name Frame1] # [Component :card]
;; :card-star [:name Frame1] @--> [Component :star] :star-main
(-> file
(tho/add-simple-component :star :star-main :star-rect
:child-params {:fills (ths/sample-fills-color :fill-color "#111111")})
(tho/add-simple-component :heart :heart-main :heart-rect
:child-params {:fills (ths/sample-fills-color :fill-color "#222222")})
(tho/add-simple-component :dot :dot-main :dot-rect)
(tho/add-frame :badge-main)
(thc/instantiate-component :dot :badge-dot :parent-label :badge-main)
(thc/make-component :badge :badge-main)
(tho/add-frame :card-main)
(thc/instantiate-component :star :card-star :parent-label :card-main)
(thc/make-component :card :card-main)))
(defn- setup
[]
;; :copy [:name Frame1] #--> [Component :card] :card-main
;; :copy-star [:name Frame1] @--> [Component :star] :card-star
(-> (thf/sample-file :file1)
(add-components)
(thc/instantiate-component :card :copy :children-labels [:copy-star])))
(defn- setup-two-levels
[file]
;; {:page-main} [:name Frame1] # [Component :page]
;; :inner-copy [:name Frame1] @--> [Component :card] :card-main
;; :inner-star [:name Frame1] @--> [Component :star] :card-star
;; :outer-copy [:name Frame1] #--> [Component :page] :page-main
;; :outer-inner [:name Frame1] @--> [Component :card] :inner-copy
;; :outer-star [:name Frame1] @--> [Component :star] :inner-star
(-> file
(tho/add-frame :page-main)
(thc/instantiate-component :card :inner-copy
:parent-label :page-main
:children-labels [:inner-star])
(thc/make-component :page :page-main)
(thc/instantiate-component :page :outer-copy
:children-labels [:outer-inner :outer-star])))
(defn- convert
([file] (convert file {}))
([file libraries]
(ctf/update-file-data file #(cfcp/convert-swap-slots % libraries))))
(defn- swap-state
"What the conversion changes on a shape."
[file label]
(let [shape (ths/get-shape file label)]
{:shape-ref (:shape-ref shape)
:touched (into #{} (map #(if (cfss/swap-slot-group? %) :swap-slot %)) (:touched shape))}))
(defn- first-child-id
[file label]
(first (:shapes (ths/get-shape file label))))
(t/deftest swap-in-a-copy-refers-to-the-replaced-child
(let [old (-> (setup)
(tho/swap-component-in-shape :copy-star :heart :as-slot? true))
file (-> old
(convert)
(thf/validate-file!))]
(t/is (= {:shape-ref (thi/id :heart-main) :touched #{:swap-slot}}
(swap-state old :copy-star)))
(t/is (= {:shape-ref (thi/id :card-star) :touched #{:swap-group}}
(swap-state file :copy-star)))
(t/is (= "#333333" (-> file
(tho/update-color :heart-rect "#333333")
(tho/propagate-component-changes :heart)
(thf/validate-file!)
(tho/bottom-fill-color :copy))))))
(t/deftest swap-in-a-main-drops-the-slot
(let [old (-> (setup)
(tho/swap-component-in-shape :card-star :heart
:as-slot? true
:propagate-fn #(tho/propagate-component-changes % :card)))
file (-> old
(convert)
(thf/validate-file!))]
(t/is (= #{:swap-slot} (:touched (swap-state old :card-star))))
(t/is (= {:shape-ref (thi/id :heart-main) :touched #{}}
(swap-state file :card-star)))))
(t/deftest slot-inherited-from-the-main-is-dropped
(let [old (-> (setup)
(tho/swap-component-in-shape :card-star :heart
:as-slot? true
:propagate-fn #(tho/propagate-component-changes % :card))
(thc/instantiate-component :card :copy2 :children-labels [:copy2-star]))
slot (cfss/get-swap-slot (ths/get-shape old :card-star))
old (ths/update-shape old :copy2-star :touched #{(cfss/build-swap-slot-group slot)})
file (-> old
(convert)
(thf/validate-file!))]
(t/is (= {:shape-ref (thi/id :card-star) :touched #{}}
(swap-state file :copy2-star)))))
(t/deftest swaps-nested-in-swaps-are-paired-on-the-old-data
(let [old (-> (setup)
(setup-two-levels)
(tho/swap-component-in-shape :outer-star :badge :as-slot? true))
_ (thi/set-id! :outer-dot (first-child-id old :outer-star))
file (-> old
(tho/swap-component-in-shape :outer-dot :heart :as-slot? true)
(convert)
(thf/validate-file!))]
(t/is (= {:shape-ref (thi/id :inner-star) :touched #{:swap-group}}
(swap-state file :outer-star)))
(t/is (= {:shape-ref (thi/id :badge-dot) :touched #{:swap-group}}
(swap-state file :outer-dot)))))
(defn- setup-library
[]
;; The library holds the components, and a swap in a copy inside its
;; :page main: :inner-star swapped for :heart. The file holds a copy of
;; :page, whose star is swapped for :badge.
(let [library (-> (thf/sample-file :library)
(add-components)
(setup-two-levels)
(tho/swap-component-in-shape :inner-star :heart
:as-slot? true
:propagate-fn #(tho/propagate-component-changes % :page)))
file (-> (thf/sample-file :file1)
(thc/instantiate-component :page :outer-copy
:library library
:children-labels [:outer-inner :outer-star])
(tho/swap-component-in-shape :outer-star :badge
:library library
:as-slot? true))]
[library file]))
(t/deftest pairing-reads-a-library-converted-or-not
(let [[library file] (setup-library)
converted (convert library)
expected {:shape-ref (thi/id :inner-star) :touched #{:swap-group}}]
(t/is (= expected (swap-state (convert file {(:id library) library}) :outer-star)))
(t/is (= expected (swap-state (convert file {(:id converted) converted}) :outer-star)))))
(t/deftest unreachable-near-main-refers-to-the-slot
(let [[_ file] (setup-library)
slot (cfss/get-swap-slot (ths/get-shape file :outer-star))
file' (convert file)]
(t/is (= {:shape-ref slot :touched #{:swap-group}}
(swap-state file' :outer-star)))))
(t/deftest unreachable-near-main-can-be-left-for-later
(let [[library file] (setup-library)
file' (binding [cfcp/*defer-unreachable-swaps* true]
(convert file))]
(t/is (= (swap-state file :outer-star) (swap-state file' :outer-star)))
(t/is (::cfcp/deferred-swaps (meta (:data file'))))
(t/is (= {:shape-ref (thi/id :inner-star) :touched #{:swap-group}}
(swap-state (convert file' {(:id library) library}) :outer-star)))))
(t/deftest slot-inherited-from-an-unreachable-main-is-dropped
;; the near main is in a library that is not loaded: the copy refers to
;; its slot, the child of that main that was swapped there
(let [library (-> (thf/sample-file :library)
(add-components)
(tho/swap-component-in-shape :card-star :heart
:as-slot? true
:propagate-fn #(tho/propagate-component-changes % :card)))
slot (cfss/get-swap-slot (ths/get-shape library :card-star))
file (-> (thf/sample-file :file1)
(thc/instantiate-component :card :copy
:library library
:children-labels [:copy-star])
(ths/update-shape :copy-star :touched #{(cfss/build-swap-slot-group slot)}))]
(t/is (= (thi/id :card-star) slot (:shape-ref (ths/get-shape file :copy-star))))
(t/is (= {:shape-ref (thi/id :card-star) :touched #{}}
(swap-state (convert file) :copy-star)))
(t/is (= {:shape-ref (thi/id :card-star) :touched #{}}
(swap-state (convert file {(:id library) library}) :copy-star)))))
(t/deftest slot-matching-no-child-is-kept-as-its-ref
(let [slot (uuid/next)
file (-> (setup)
(tho/swap-component-in-shape :copy-star :heart :as-slot? true)
(ths/update-shape :copy-star :touched #{(cfss/build-swap-slot-group slot)})
(convert))]
(t/is (= {:shape-ref slot :touched #{:swap-group}}
(swap-state file :copy-star)))))
(t/deftest migration-converts-and-adds-the-feature
(let [old (-> (setup)
(tho/swap-component-in-shape :copy-star :heart :as-slot? true)
(update :features disj "components/swap-group")
(assoc :migrations (disj fmg/available-migrations
"0034-convert-swap-slots-to-swap-groups")))
file (fmg/migrate-file old (delay {}))]
(t/is (contains? (:features file) "components/swap-group"))
(t/is (not (contains? (:features old) "components/swap-group")))
(t/is (contains? cfeat/default-features "components/swap-group"))
(t/is (= {:shape-ref (thi/id :card-star) :touched #{:swap-group}}
(swap-state file :copy-star)))))
(defn- move-shape
"Move the shape and its children by the given delta, with no sync."
[file label dx dy]
(let [page (thf/current-page file)
ids (set (cfh/get-children-ids-with-self (:objects page) (thi/id label)))
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
ids
#(gsh/move % (gpt/point dx dy))
(:objects page)
{})]
(thf/apply-changes file changes :validate? false)))
(defn- position
[file label]
(let [shape (ths/get-shape file label)]
[(get-in shape [:selrect :x]) (get-in shape [:selrect :y])]))
(t/deftest swap-away-from-its-position-keeps-it
;; the main moved the replaced child after the swap; the swapped head did
;; not follow, and it keeps its place
(let [file (-> (setup)
(tho/swap-component-in-shape :copy-star :heart :as-slot? true)
(move-shape :card-star 10 20)
(convert))
head (ths/get-shape file :copy-star)
file' (-> file
(tho/update-color :card-star "#555555")
(tho/propagate-component-changes :card)
(thf/validate-file!))]
(t/is (= {:shape-ref (thi/id :card-star) :touched #{:swap-group :geometry-group}}
(swap-state file :copy-star)))
(t/is (= [(get-in head [:selrect :x]) (get-in head [:selrect :y])]
(position file' :copy-star)))))
(t/deftest swap-in-its-position-follows-the-main
(let [file (-> (setup)
(tho/swap-component-in-shape :copy-star :heart :as-slot? true)
(convert)
(thf/validate-file!))
[x y] (position file :copy-star)
file' (-> file
(move-shape :card-star 10 20)
(tho/propagate-component-changes :card)
(thf/validate-file!))]
(t/is (= #{:swap-group} (:touched (ths/get-shape file :copy-star))))
(t/is (= [(+ x 10) (+ y 20)] (position file' :copy-star)))))

View File

@ -16,6 +16,7 @@
(:require
[app.common.files.changes-builder :as pcb]
[app.common.files.repair :as cfr]
[app.common.files.swap-slots :as cfss]
[app.common.files.validate :as cfv]
[app.common.logic.shapes :as cls]
[app.common.test-helpers.components :as thc]
@ -146,7 +147,7 @@
(defn- slot-group
[id]
#{(ctk/build-swap-slot-group id)})
#{(cfss/build-swap-slot-group id)})
;; ---- Main heads
@ -247,7 +248,7 @@
(t/deftest ref-shape-not-found
(let [[found file' errors] (repair (ths/update-shape (nested) :copy2-nested-head :shape-ref (uuid/next)))]
(t/is (= #{:ref-shape-not-found :missing-slot} found))
(t/is (= #{:ref-shape-not-found :ref-not-in-near-main} found))
(t/is (nil? errors))
;; no matching shape: the nested copy is detached
(t/is (nil? (:shape-ref (ths/get-shape file' :copy2-nested-head))))))
@ -381,35 +382,36 @@
(let [[found file' errors] (repair (ths/update-shape (simple) :copy-root :touched (slot-group (uuid/next))))]
(t/is (= #{:misplaced-slot} found))
(t/is (nil? errors))
(t/is (nil? (ctk/get-swap-slot (ths/get-shape file' :copy-root))))))
(t/is (nil? (cfss/get-swap-slot (ths/get-shape file' :copy-root))))))
(t/deftest duplicate-slot
(let [file (-> (two-nested)
(ths/update-shape :copy-a :touched (slot-group (thi/id :nested-a)))
(ths/update-shape :copy-b :touched (slot-group (thi/id :nested-a))))
[found file' errors] (repair file)]
(t/is (= #{:duplicate-slot} found))
(t/deftest misplaced-swap-group
(let [[found file' errors] (repair (ths/update-shape (simple) :copy-root :touched #{:swap-group}))]
(t/is (= #{:misplaced-swap-group} found))
(t/is (nil? errors))
(t/is (= (thi/id :nested-a) (ctk/get-swap-slot (ths/get-shape file' :copy-a))))
(t/is (nil? (ctk/get-swap-slot (ths/get-shape file' :copy-b))))))
(t/is (not (ctk/swapped? (ths/get-shape file' :copy-root))))))
(t/deftest component-duplicate-slot
(let [file (-> (two-nested)
(delete-main :main2-root)
(update-deleted-object :component2 :nested-a #(assoc % :touched (slot-group (thi/id :nested-a))))
(update-deleted-object :component2 :nested-b #(assoc % :touched (slot-group (thi/id :nested-a)))))
[found file' errors] (repair file)]
(t/is (= #{:component-duplicate-slot} found))
(t/is (nil? errors))
(t/is (= (thi/id :nested-a) (ctk/get-swap-slot (deleted-object file' :component2 :nested-a))))
(t/is (nil? (ctk/get-swap-slot (deleted-object file' :component2 :nested-b))))))
(t/deftest missing-slot
(t/deftest ref-not-in-near-main
;; a swapped copy that lost its position: it refers to the root of its own
;; component, as files older than components/swap-group did, with no slot
(let [file (-> (two-nested)
(tho/add-simple-component :component3 :main3-root :main3-child)
(tho/swap-component-in-shape :copy-a :component3 :new-shape-label :swapped :as-slot? true)
(ths/update-shape :swapped :touched nil))
[found file' errors] (repair file)]
(t/is (= #{:missing-slot} found))
[found file' errors] (repair file)
swapped (ths/get-shape file' :swapped)]
(t/is (= #{:ref-not-in-near-main} found))
(t/is (nil? errors))
(t/is (= (thi/id :nested-a) (ctk/get-swap-slot (ths/get-shape file' :swapped))))))
;; it takes the free position at its index, as a swap
(t/is (= (thi/id :nested-a) (:shape-ref swapped)))
(t/is (ctk/swapped? swapped))))
(t/deftest duplicate-ref
;; two children of the copy take the position of the same main shape
(let [file (ths/update-shape (two-nested) :copy-b :shape-ref (thi/id :nested-a))
[found file' errors] (repair file)
children (:shapes (ths/get-shape file' :copy2-root))]
(t/is (contains? found :duplicate-ref))
(t/is (nil? errors))
;; the first one is kept, the second one deleted with its children
(t/is (= [(thi/id :copy-a)] (filterv #{(thi/id :copy-a) (thi/id :copy-b)} children)))
(t/is (nil? (ths/get-shape file' :copy-b)))))

View File

@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns common-tests.logic.comp-remove-swap-slots-test
(ns common-tests.logic.comp-move-swapped-copies-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.geom.point :as gpt]
@ -53,7 +53,7 @@
(tho/add-frame :frame-b2)
(thc/make-component :b2 :frame-b2)))
(t/deftest test-remove-swap-slot-relocating-blue1-to-root
(t/deftest test-relocating-blue1-to-root
(let [;; ==== Setup
file (setup-file)
@ -82,7 +82,7 @@
(t/is (some? blue1'))
(t/is (not (ctk/swapped? blue1')))))
(t/deftest test-remove-swap-slot-relocating-blue1-to-b2
(t/deftest test-relocating-blue1-to-b2
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
@ -112,7 +112,7 @@
(t/is (some? blue1'))
(t/is (not (ctk/swapped? blue1')))))
(t/deftest test-remove-swap-slot-relocating-yellow-to-root
(t/deftest test-relocating-yellow-to-root
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
@ -155,7 +155,7 @@
(t/is (some? blue1''))
(t/is (not (ctk/swapped? blue1'')))))
(t/deftest test-remove-swap-slot-relocating-yellow-to-b2
(t/deftest test-relocating-yellow-to-b2
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
@ -208,7 +208,7 @@
(str/starts-with? (thi/label (:id %)) "<no-label")))
first))
(t/deftest test-remove-swap-slot-duplicating-blue1
(t/deftest test-duplicating-blue1
(let [;; ==== Setup
file (setup-file)
@ -245,7 +245,7 @@
(t/is (some? duplicated-blue1'))
(t/is (not (ctk/swapped? duplicated-blue1')))))
(t/deftest test-remove-swap-slot-duplicate-yellow
(t/deftest test-duplicate-yellow
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
@ -300,7 +300,7 @@
(t/is (some? duplicated-blue1''))
(t/is (not (ctk/swapped? duplicated-blue1'')))))
(t/deftest test-keep-swap-slot-relocating-blue1-before-yellow
(t/deftest test-relocating-blue1-before-yellow
(let [;; ==== Setup
file (setup-file)
@ -329,7 +329,7 @@
(t/is (some? blue1'))
(t/is (not (ctk/swapped? blue1')))))
(t/deftest test-keep-swap-slot-move-blue1-inside-and-outside-yellow
(t/deftest test-move-blue1-inside-and-outside-yellow
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
@ -373,7 +373,7 @@
(t/is (not (ctk/swapped? blue1'')))))
(t/deftest test-keep-swap-slot-relocate-blue1-inside-and-outside-yellow
(t/deftest test-relocate-blue1-inside-and-outside-yellow
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
@ -419,7 +419,7 @@
(t/is (not (ctk/swapped? blue1'')))))
(t/deftest test-remove-swap-slot-relocating-green-copy-to-root
(t/deftest test-relocating-green-copy-to-root
(let [;; ==== Setup
file (setup-file)
@ -449,7 +449,7 @@
(t/is (some? blue2'))
(t/is (ctk/swapped? blue2'))))
(t/deftest test-remove-swap-slot-relocating-green-copy-to-b2
(t/deftest test-relocating-green-copy-to-b2
(let [;; ==== Setup
file (setup-file)
@ -480,7 +480,7 @@
(t/is (some? blue2'))
(t/is (ctk/swapped? blue2'))))
(t/deftest test-remove-swap-slot-duplicating-green-copy
(t/deftest test-duplicating-green-copy
(let [;; ==== Setup
file (setup-file)
@ -522,7 +522,7 @@
(t/is (some? duplicated-blue1'))
(t/is (ctk/swapped? duplicated-blue1'))))
(t/deftest test-swap-outside-component-doesnt-have-swap-slot
(t/deftest test-swap-outside-component-is-not-marked
(let [;; ==== Setup
file (setup-file)
;; ==== Action
@ -540,7 +540,7 @@
(t/is (some? blue-copy1'))
(t/is (not (ctk/swapped? blue-copy1')))))
(t/deftest test-remove-swap-slot-detach
(t/deftest test-detach-ends-the-swap
(let [;; ==== Setup
file (setup-file)

View File

@ -460,3 +460,18 @@
(t/is (= (thi/id :heart-main) (:shape-ref head)))
(t/is (not (ctk/swapped? head)))
(t/is (empty? (:touched head)))))
(t/deftest swapped-head-takes-the-flex-item-attrs-of-the-replaced-child
(let [margin {:m1 7 :m2 7 :m3 7 :m4 7}
file (-> (thf/sample-file :file1)
(tho/add-simple-component :star :star-main :star-rect)
(tho/add-simple-component :heart :heart-main :heart-rect)
(tho/add-frame :card-main :layout :flex :layout-flex-dir :row)
(thc/instantiate-component :star :card-star :parent-label :card-main)
(thc/make-component :card :card-main)
(thc/instantiate-component :card :copy :children-labels [:copy-star])
(tho/swap-component-in-shape :copy-star :heart)
(ths/update-shape :card-star :layout-item-margin margin)
(propagate :card)
(thf/validate-file!))]
(t/is (= margin (:layout-item-margin (ths/get-shape file :copy-star))))))

View File

@ -25,6 +25,7 @@
[common-tests.files-migrations-0026-test]
[common-tests.files-migrations-test]
[common-tests.files.comp-processors-test]
[common-tests.files.convert-swap-slots-test]
[common-tests.files.helpers-test]
[common-tests.files.repair-components-test]
[common-tests.files.repair-test]
@ -63,7 +64,7 @@
[common-tests.logic.comp-detach-with-nested-test]
[common-tests.logic.comp-flex-interactions-test]
[common-tests.logic.comp-main-edit-breaks-copy-slots-test]
[common-tests.logic.comp-remove-swap-slots-test]
[common-tests.logic.comp-move-swapped-copies-test]
[common-tests.logic.comp-reset-test]
[common-tests.logic.comp-sync-test]
[common-tests.logic.comp-touched-test]
@ -145,6 +146,7 @@
'common-tests.files-migrations-0026-test
'common-tests.files-migrations-test
'common-tests.files.comp-processors-test
'common-tests.files.convert-swap-slots-test
'common-tests.files.helpers-test
'common-tests.files.repair-components-test
'common-tests.files.repair-test
@ -183,7 +185,7 @@
'common-tests.logic.comp-detach-with-nested-test
'common-tests.logic.comp-flex-interactions-test
'common-tests.logic.comp-main-edit-breaks-copy-slots-test
'common-tests.logic.comp-remove-swap-slots-test
'common-tests.logic.comp-move-swapped-copies-test
'common-tests.logic.comp-reset-test
'common-tests.logic.comp-sync-test
'common-tests.logic.comp-touched-test

View File

@ -6,13 +6,8 @@
(ns common-tests.types.components-test
(:require
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.types.component :as ctk]
[app.common.types.file :as ctf]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
@ -20,383 +15,11 @@
(t/deftest test-valid-touched-group
(t/is (ctk/valid-touched-group? :name-group))
(t/is (ctk/valid-touched-group? :geometry-group))
(t/is (ctk/valid-touched-group? :swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f))
(t/is (ctk/valid-touched-group? :swap-group))
(t/is (not (ctk/valid-touched-group? :this-is-not-a-group)))
(t/is (not (ctk/valid-touched-group? :swap-slot-)))
(t/is (not (ctk/valid-touched-group? :swap-slot-xxxxxx)))
(t/is (not (ctk/valid-touched-group? :swap-slot-9cc181fa-5eef-8084-8004)))
(t/is (not (ctk/valid-touched-group? :swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f)))
(t/is (not (ctk/valid-touched-group? nil))))
(t/deftest test-get-swap-slot
(let [s1 (ths/sample-shape :s1)
s2 (ths/sample-shape :s2 :touched #{:visibility-group})
s3 (ths/sample-shape :s3 :touched #{:swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f})
s4 (ths/sample-shape :s4 :touched #{:fill-group
:swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f})
s5 (ths/sample-shape :s5 :touched #{:swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f
:content-group
:geometry-group})
s6 (ths/sample-shape :s6 :touched #{:swap-slot-9cc181fa})]
(t/is (nil? (ctk/get-swap-slot s1)))
(t/is (nil? (ctk/get-swap-slot s2)))
(t/is (= (ctk/get-swap-slot s3) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
(t/is (= (ctk/get-swap-slot s4) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
(t/is (= (ctk/get-swap-slot s5) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
(t/is (nil? (ctk/get-swap-slot s6)))))
(t/deftest test-find-near-match
(t/testing "shapes not in a component have no near match"
(let [file
;; :frame1 [:name Frame1]
;; :child1 [:name Rect1]
(-> (thf/sample-file :file1)
(tho/add-frame-with-child :frame1 :shape1))
page (thf/current-page file)
frame1 (ths/get-shape file :frame1)
shape1 (ths/get-shape file :shape1)
near-match1 (ctf/find-near-match file page {} frame1)
near-match2 (ctf/find-near-match file page {} shape1)]
(t/is (nil? near-match1))
(t/is (nil? near-match2))))
(t/testing "shapes in a copy get the ref-shape"
(let [file
;; {:main-root} [:name Frame1] # [Component :component1]
;; :main-child1 [:name Rect1]
;; :main-child2 [:name Rect2]
;; :main-child3 [:name Rect3]
;;
;; :copy-root [:name Frame1] #--> [Component :component1] :main-root
;; <no-label> [:name Rect1] ---> :main-child1
;; <no-label> [:name Rect2] ---> :main-child2
;; <no-label> [:name Rect3] ---> :main-child3
(-> (thf/sample-file :file1)
(tho/add-component-with-many-children-and-copy :component1
:main-root [:main-child1 :main-child2 :main-child3]
:copy-root))
page (thf/current-page file)
main-root (ths/get-shape file :main-root)
main-child1 (ths/get-shape file :main-child1)
main-child2 (ths/get-shape file :main-child2)
main-child3 (ths/get-shape file :main-child3)
copy-root (ths/get-shape file :copy-root)
copy-child1 (ths/get-shape-by-id file (nth (:shapes copy-root) 0))
copy-child2 (ths/get-shape-by-id file (nth (:shapes copy-root) 1))
copy-child3 (ths/get-shape-by-id file (nth (:shapes copy-root) 2))
near-main-root (ctf/find-near-match file page {} main-root)
near-main-child1 (ctf/find-near-match file page {} main-child1)
near-main-child2 (ctf/find-near-match file page {} main-child2)
near-main-child3 (ctf/find-near-match file page {} main-child3)
near-copy-root (ctf/find-near-match file page {} copy-root)
near-copy-child1 (ctf/find-near-match file page {} copy-child1)
near-copy-child2 (ctf/find-near-match file page {} copy-child2)
near-copy-child3 (ctf/find-near-match file page {} copy-child3)]
(t/is (nil? near-main-root))
(t/is (nil? near-main-child1))
(t/is (nil? near-main-child2))
(t/is (nil? near-main-child3))
(t/is (nil? near-copy-root))
(t/is (= (:id near-copy-child1) (thi/id :main-child1)))
(t/is (= (:id near-copy-child2) (thi/id :main-child2)))
(t/is (= (:id near-copy-child3) (thi/id :main-child3)))))
(t/testing "shapes in nested not swapped copies get the ref-shape"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
;; :nested-child [:name Rect1] ---> :main1-child
;;
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame1] @--> [Component :component1] :nested-head
;; :copy2-nested-child [:name Rect1] ---> :nested-child
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head
:nested-head-params {:children-labels [:nested-child]})
(thc/instantiate-component :component2 :copy2
:children-labels [:copy2-nested-head :copy2-nested-child]))
page (thf/current-page file)
main1-root (ths/get-shape file :main1-root)
main1-child (ths/get-shape file :main1-child)
main2-root (ths/get-shape file :main2-root)
nested-head (ths/get-shape file :nested-head)
nested-child (ths/get-shape file :nested-child)
copy2 (ths/get-shape file :copy2)
copy2-nested-head (ths/get-shape file :copy2-nested-head)
copy2-nested-child (ths/get-shape file :copy2-nested-child)
near-main1-root (ctf/find-near-match file page {} main1-root)
near-main1-child (ctf/find-near-match file page {} main1-child)
near-main2-root (ctf/find-near-match file page {} main2-root)
near-nested-head (ctf/find-near-match file page {} nested-head)
near-nested-child (ctf/find-near-match file page {} nested-child)
near-copy2 (ctf/find-near-match file page {} copy2)
near-copy2-nested-head (ctf/find-near-match file page {} copy2-nested-head)
near-copy2-nested-child (ctf/find-near-match file page {} copy2-nested-child)]
(t/is (nil? near-main1-root))
(t/is (nil? near-main1-child))
(t/is (nil? near-main2-root))
(t/is (nil? near-nested-head))
(t/is (= (:id near-nested-child) (thi/id :main1-child)))
(t/is (nil? near-copy2))
(t/is (= (:id near-copy2-nested-head) (thi/id :nested-head)))
(t/is (= (:id near-copy2-nested-child) (thi/id :nested-child)))))
(t/testing "shapes in swapped copies get the swap slot"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
;; :nested-child [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :main3-root
;; {swap-slot :nested-head}
;; <no-label> [:name Rect3] ---> :main3-child
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head
:nested-head-params {:children-labels [:nested-child]})
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head])
(tho/add-simple-component :component3 :main3-root :main3-child
:root-params {:name "Frame3"}
:child-params {:name "Rect3"})
(tho/swap-component-in-first-child :copy2 :component3 :as-slot? true))
page (thf/current-page file)
main1-root (ths/get-shape file :main1-root)
main1-child (ths/get-shape file :main1-child)
main2-root (ths/get-shape file :main2-root)
nested-head (ths/get-shape file :nested-head)
nested-child (ths/get-shape file :nested-child)
copy2 (ths/get-shape file :copy2)
copy2-nested-head (ths/get-shape file :copy2-nested-head)
copy2-nested-child (ths/get-shape-by-id file (first (:shapes copy2-nested-head)))
near-main1-root (ctf/find-near-match file page {} main1-root)
near-main1-child (ctf/find-near-match file page {} main1-child)
near-main2-root (ctf/find-near-match file page {} main2-root)
near-nested-head (ctf/find-near-match file page {} nested-head)
near-nested-child (ctf/find-near-match file page {} nested-child)
near-copy2 (ctf/find-near-match file page {} copy2)
near-copy2-nested-head (ctf/find-near-match file page {} copy2-nested-head)
near-copy2-nested-child (ctf/find-near-match file page {} copy2-nested-child)]
(t/is (nil? near-main1-root))
(t/is (nil? near-main1-child))
(t/is (nil? near-main2-root))
(t/is (nil? near-nested-head))
(t/is (= (:id near-nested-child) (thi/id :main1-child)))
(t/is (nil? near-copy2))
(t/is (= (:id near-copy2-nested-head) (thi/id :nested-head)))
(t/is (= (:id near-copy2-nested-child) (thi/id :main3-child)))))
(t/testing "shapes in second level nested copies under swapped get the shape in the new main"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested2-head [:name Frame1] @--> [Component :component1] :main1-root
;; :nested2-child [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; {:main4-root} [:name Frame4] # [Component :component4]
;; :nested4-head [:name Frame3] @--> [Component :component1] :main3-root
;; :nested4-child [:name Rect3] ---> :main3-child
;;
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame4] @--> [Component :component4] :main4-root
;; {swap-slot :nested2-head}
;; <no-label> [:name Frame3] @--> :nested4-head
;; <no-label> [:name Rect3] ---> :nested4-child
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested2-head
:nested-head-params {:children-labels [:nested2-child]})
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head])
(tho/add-nested-component :component3 :main3-root :main3-child
:component4 :main4-root :nested4-head
:root1-params {:name "Frame3"}
:main1-child-params {:name "Rect3"}
:main2-root-params {:name "Frame4"}
:nested-head-params {:children-labels [:nested4-child]})
(tho/swap-component-in-first-child :copy2 :component4 :as-slot? true))
page (thf/current-page file)
main1-root (ths/get-shape file :main1-root)
main1-child (ths/get-shape file :main1-child)
main2-root (ths/get-shape file :main2-root)
nested2-head (ths/get-shape file :nested2-head)
nested2-child (ths/get-shape file :nested2-child)
main3-root (ths/get-shape file :main3-root)
main3-child (ths/get-shape file :main3-child)
main4-root (ths/get-shape file :main4-root)
nested4-head (ths/get-shape file :nested4-head)
nested4-child (ths/get-shape file :nested4-child)
copy2 (ths/get-shape file :copy2)
copy2-nested-head (ths/get-shape file :copy2-nested-head)
copy2-nested4-head (ths/get-shape-by-id file (first (:shapes copy2-nested-head)))
copy2-nested4-child (ths/get-shape-by-id file (first (:shapes copy2-nested4-head)))
near-main1-root (ctf/find-near-match file page {} main1-root)
near-main1-child (ctf/find-near-match file page {} main1-child)
near-main2-root (ctf/find-near-match file page {} main2-root)
near-nested2-head (ctf/find-near-match file page {} nested2-head)
near-nested2-child (ctf/find-near-match file page {} nested2-child)
near-main3-root (ctf/find-near-match file page {} main3-root)
near-main3-child (ctf/find-near-match file page {} main3-child)
near-main4-root (ctf/find-near-match file page {} main4-root)
near-nested4-head (ctf/find-near-match file page {} nested4-head)
near-nested4-child (ctf/find-near-match file page {} nested4-child)
near-copy2 (ctf/find-near-match file page {} copy2)
near-copy2-nested-head (ctf/find-near-match file page {} copy2-nested-head)
near-copy2-nested4-head (ctf/find-near-match file page {} copy2-nested4-head)
near-copy2-nested4-child (ctf/find-near-match file page {} copy2-nested4-child)]
(t/is (nil? near-main1-root))
(t/is (nil? near-main1-child))
(t/is (nil? near-main2-root))
(t/is (nil? near-nested2-head))
(t/is (= (:id near-nested2-child) (thi/id :main1-child)))
(t/is (nil? near-main3-root))
(t/is (nil? near-main3-child))
(t/is (nil? near-main4-root))
(t/is (nil? near-nested4-head))
(t/is (= (:id near-nested4-child) (thi/id :main3-child)))
(t/is (nil? near-copy2))
(t/is (= (:id near-copy2-nested-head) (thi/id :nested2-head)))
(t/is (= (:id near-copy2-nested4-head) (thi/id :nested4-head)))
(t/is (= (:id near-copy2-nested4-child) (thi/id :nested4-child)))))
(t/testing "component in external libraries still work well"
(let [library1
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested2-head [:name Frame1] @--> [Component :component1] :main1-root
;; :nested2-child [:name Rect1] ---> :main1-child
(-> (thf/sample-file :library1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested2-head
:nested-head-params {:children-labels [:nested2-child]}))
library2
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; {:main4-root} [:name Frame4] # [Component :component4]
;; :nested4-head [:name Frame3] @--> [Component :component1] :main3-root
;; :nested4-child [:name Rect3] ---> :main3-child
(-> (thf/sample-file :library2)
(tho/add-nested-component :component3 :main3-root :main3-child
:component4 :main4-root :nested4-head
:root1-params {:name "Frame3"}
:main1-child-params {:name "Rect3"}
:main2-root-params {:name "Frame4"}
:nested-head-params {:children-labels [:nested4-child]}))
file
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame4] @--> [Component :component4] :main4-root
;; {swap-slot :nested2-head}
;; <no-label> [:name Frame3] @--> :nested4-head
;; <no-label> [:name Rect3] ---> :nested4-child
(-> (thf/sample-file :file1)
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head]
:library library1)
(tho/swap-component-in-first-child :copy2 :component4 :library library2 :as-slot? true))
page-library1 (thf/current-page library1)
page-library2 (thf/current-page library2)
page-file (thf/current-page file)
libraries {(:id library1) library1
(:id library2) library2}
main1-root (ths/get-shape library1 :main1-root)
main1-child (ths/get-shape library1 :main1-child)
main2-root (ths/get-shape library1 :main2-root)
nested2-head (ths/get-shape library1 :nested2-head)
nested2-child (ths/get-shape library1 :nested2-child)
main3-root (ths/get-shape library2 :main3-root)
main3-child (ths/get-shape library2 :main3-child)
main4-root (ths/get-shape library2 :main4-root)
nested4-head (ths/get-shape library2 :nested4-head)
nested4-child (ths/get-shape library2 :nested4-child)
copy2 (ths/get-shape file :copy2)
copy2-nested-head (ths/get-shape file :copy2-nested-head)
copy2-nested4-head (ths/get-shape-by-id file (first (:shapes copy2-nested-head)))
copy2-nested4-child (ths/get-shape-by-id file (first (:shapes copy2-nested4-head)))
near-main1-root (ctf/find-near-match file page-file libraries main1-root)
near-main1-child (ctf/find-near-match file page-file libraries main1-child)
near-main2-root (ctf/find-near-match file page-file libraries main2-root)
near-nested2-head (ctf/find-near-match library1 page-library1 libraries nested2-head)
near-nested2-child (ctf/find-near-match library1 page-library1 libraries nested2-child)
near-main3-root (ctf/find-near-match file page-file libraries main3-root)
near-main3-child (ctf/find-near-match file page-file libraries main3-child)
near-main4-root (ctf/find-near-match file page-file libraries main4-root)
near-nested4-head (ctf/find-near-match library2 page-library2 libraries nested4-head)
near-nested4-child (ctf/find-near-match library2 page-library2 libraries nested4-child)
near-copy2 (ctf/find-near-match file page-file libraries copy2)
near-copy2-nested-head (ctf/find-near-match file page-file libraries copy2-nested-head)
near-copy2-nested4-head (ctf/find-near-match file page-file libraries copy2-nested4-head)
near-copy2-nested4-child (ctf/find-near-match file page-file libraries copy2-nested4-child)]
(t/is (some? main1-root))
(t/is (some? main1-child))
(t/is (some? main2-root))
(t/is (some? nested2-head))
(t/is (some? nested2-child))
(t/is (some? main3-root))
(t/is (some? main3-child))
(t/is (some? main4-root))
(t/is (some? nested4-head))
(t/is (some? nested4-child))
(t/is (some? copy2))
(t/is (some? copy2-nested-head))
(t/is (some? copy2-nested4-head))
(t/is (some? copy2-nested4-child))
(t/is (nil? near-main1-root))
(t/is (nil? near-main1-child))
(t/is (nil? near-main2-root))
(t/is (nil? near-nested2-head))
(t/is (= (:id near-nested2-child) (thi/id :main1-child)))
(t/is (nil? near-main3-root))
(t/is (nil? near-main3-child))
(t/is (nil? near-main4-root))
(t/is (nil? near-nested4-head))
(t/is (= (:id near-nested4-child) (thi/id :main3-child)))
(t/is (nil? near-copy2))
(t/is (= (:id near-copy2-nested-head) (thi/id :nested2-head)))
(t/is (= (:id near-copy2-nested4-head) (thi/id :nested4-head)))
(t/is (= (:id near-copy2-nested4-child) (thi/id :nested4-child))))))
(t/deftest variant-main-and-formula-rename
(let [container {:id (thi/new-id! :container)
:is-variant-container true}

View File

@ -11,6 +11,8 @@
components."
(:require
[app.common.data :as d]
[app.common.files.comp-processors :as cfcp]
[app.common.files.swap-slots :as cfss]
[app.common.files.validate :as cfv]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.composable.core :as tm]
@ -200,9 +202,9 @@
(defn- ref-id-for-swapped
[file]
(ctf/find-ref-id-for-swapped (ths/get-shape file :swapped)
(thf/current-page file)
{(:id file) file}))
(cfcp/find-ref-id-for-swapped (ths/get-shape file :swapped)
(thf/current-page file)
{(:id file) file}))
(t/deftest ref-id-for-swapped-goes-up-the-ref-chain
(let [file (-> (thf/sample-file :file)
@ -212,7 +214,7 @@
:c3 :main3 :nested-head2 :nested-subhead2
:copy3)
(swap-at-second-level))]
(t/is (= (thi/id :nested-head1) (ctk/get-swap-slot (ths/get-shape file :swapped))))
(t/is (= (thi/id :nested-head1) (cfss/get-swap-slot (ths/get-shape file :swapped))))
(t/is (= (thi/id :nested-subhead2) (ref-id-for-swapped file)))))
(t/deftest ref-id-for-swapped-with-mains-on-other-pages
@ -227,7 +229,7 @@
(thc/make-component :c3 :main3)
(thc/instantiate-component :c3 :copy3)
(swap-at-second-level))]
(t/is (= (thi/id :nested-head1) (ctk/get-swap-slot (ths/get-shape file :swapped))))
(t/is (= (thi/id :nested-head1) (cfss/get-swap-slot (ths/get-shape file :swapped))))
(tm/known-failure "F56" (thi/id :nested-subhead2) nil (ref-id-for-swapped file))))
;; ----- Deleted components
@ -329,9 +331,9 @@
(tho/add-simple-component-with-copy :component :main :main-child
:copy
:copy-root-params {:children-labels [:copy-child]}))
match (ctf/find-near-match file (thf/current-page file) {(:id file) file}
(ths/get-shape file :copy-child)
:with-context? true)]
match (cfcp/find-near-match file (thf/current-page file) {(:id file) file}
(ths/get-shape file :copy-child)
:with-context? true)]
(t/is (= (thi/id :main-child) (:id match)))
(t/is (= :page (:type (:container (meta match)))))
(t/is (= (:id (thf/current-page file)) (:id (:container (meta match)))))))

View File

@ -111,7 +111,7 @@
(ctk/instance-root? shape)
(ctk/main-instance? shape)
;; Swapped nested heads are not instance-roots, but still need a slot.
(some? (ctk/get-swap-slot shape)))))
(ctk/swapped? shape))))
(defn- box-thumbnail-slot?
"A box reserves a thumbnail slot when it is the root of a panel or the
@ -231,12 +231,9 @@
Includes deleted components so the chain can surface them."
[file page libraries objects shape]
(when-let [source (near-match-source-shape objects shape)]
(when-let [ref-shape (or (ctf/find-near-match file page libraries source
:with-context? true
:include-deleted? true)
(ctf/find-ref-shape file page libraries source
:with-context? true
:include-deleted? true))]
(when-let [ref-shape (ctf/find-ref-shape file page libraries source
:with-context? true
:include-deleted? true)]
(near-component-render-data (ref-shape->component-head ref-shape)
ref-shape))))
@ -279,27 +276,22 @@
:page-name (:name match-page)
:page-id (:id match-page)))))
(defn- resolve-swap-slot-main-data
"When the highlighted shape has a swap-slot, return its component main tree."
[file page libraries objects highlight-id]
(defn- resolve-swap-main-data
"When the highlighted shape is swapped, return the main tree of its own
component, where its content comes from."
[file _page libraries objects highlight-id]
(when-let [shape (get objects highlight-id)]
(when (ctk/get-swap-slot shape)
(when-let [component (ctf/find-ref-component file page libraries shape
:include-deleted? true)]
(let [head (ctn/get-head-shape objects shape)
component-file (ctf/find-component-file file libraries (:component-file head))
(when (ctk/swapped? shape)
(when-let [component (ctf/resolve-component shape file libraries :include-deleted? true)]
(let [component-file (ctf/find-component-file file libraries (:component-file shape))
file-data (:data component-file)
container (ctf/get-component-container file-data component)
root-shape (ctf/get-component-root file-data component)
component-page (ctf/get-component-page file-data component)
highlight-main-id
(or (ctf/find-ref-id-for-swapped shape page libraries)
(some-> (ctf/get-ref-shape file-data component shape) :id)
(ctk/get-swap-slot shape))]
component-page (ctf/get-component-page file-data component)]
(when root-shape
{:root-shape root-shape
:objects (:objects container)
:highlight-id highlight-main-id
:highlight-id (:id root-shape)
:file component-file
:deleted (boolean (:deleted component))
:file-name (resolve-file-name component-file libraries)
@ -403,14 +395,14 @@
[objects root-shape labels thumbnail-context highlight-id]
(shape-box-height objects root-shape preview-width labels thumbnail-context highlight-id true))
(defn- swap-slot-panel
(defn- swap-panel
"Build a swap-main panel for the highlight at `column-idx`.
The panel is drawn one column to the right (`(inc column-idx)`), so
arrows pair it with `highlight-panels` via `(dec column-idx)`."
[column-idx swap-row-y file page libraries objects highlight-id]
(when-let [{:keys [root-shape objects highlight-id file deleted
file-id page-id file-name page-name]}
(resolve-swap-slot-main-data file page libraries objects highlight-id)]
(resolve-swap-main-data file page libraries objects highlight-id)]
{:column-idx (inc column-idx)
:y swap-row-y
:root-shape root-shape
@ -776,7 +768,7 @@
[shape file-id page-id enabled?]
(let [shape-id (:id shape)
tag (shape-thumbnail-tag shape)
swapped? (some? (ctk/get-swap-slot shape))
swapped? (ctk/swapped? shape)
object-id (mf/with-memo [file-id page-id shape-id tag]
(thc/fmt-object-id file-id page-id shape-id tag))
thumb-ref (mf/with-memo [object-id]
@ -955,17 +947,17 @@
selection-swap-panel
(when (and root-shape selected-id swap-row-y)
(swap-slot-panel 0 swap-row-y
file page libraries objects selected-id))
(swap-panel 0 swap-row-y
file page libraries objects selected-id))
near-swap-panels
(when (and near-component-chain swap-row-y)
(keep-indexed
;; Near columns are 1-based (selection is column 0), matching
;; swap-slot-panel's (inc column-idx) placement and arrow pairing.
;; swap-panel's (inc column-idx) placement and arrow pairing.
(fn [idx {:keys [highlight-id file page objects]}]
(swap-slot-panel (inc idx) swap-row-y
file page libraries objects highlight-id))
(swap-panel (inc idx) swap-row-y
file page libraries objects highlight-id))
near-component-chain))
swap-panels*

View File

@ -3,7 +3,7 @@
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns frontend-tests.logic.comp-remove-swap-slots-test
(ns frontend-tests.logic.comp-move-swapped-copies-test
(:require
[app.common.files.validate :as cfv]
[app.common.test-helpers.components :as cthc]
@ -94,7 +94,7 @@
(str/starts-with? (cthi/label (:id %)) "<no-label")))
first))
(t/deftest test-remove-swap-slot-copy-paste-blue1-to-root
(t/deftest test-copy-paste-blue1-to-root
(t/async
done
(let [;; ==== Setup
@ -132,7 +132,7 @@
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-copy-paste-blue1-to-b1
(t/deftest test-copy-paste-blue1-to-b1
(t/async
done
(let [;; ==== Setup
@ -170,7 +170,7 @@
(t/is (some? copied-blue1'))
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-copy-paste-blue1-to-yellow
(t/deftest test-copy-paste-blue1-to-yellow
(t/async
done
(let [;; ==== Setup
@ -209,7 +209,7 @@
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-copy-paste-blue1-to-b2
(t/deftest test-copy-paste-blue1-to-b2
(t/async
done
(let [;; ==== Setup
@ -248,7 +248,7 @@
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-cut-paste-blue1-to-root
(t/deftest test-cut-paste-blue1-to-root
(t/async
done
(let [;; ==== Setup
@ -284,7 +284,7 @@
(t/deftest test-remove-swap-slot-cut-paste-blue1-to-yellow
(t/deftest test-cut-paste-blue1-to-yellow
(t/async
done
(let [;; ==== Setup
@ -321,7 +321,7 @@
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-cut-paste-blue1-to-b2
(t/deftest test-cut-paste-blue1-to-b2
(t/async
done
(let [;; ==== Setup
@ -358,7 +358,7 @@
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-copy-paste-yellow-to-root
(t/deftest test-copy-paste-yellow-to-root
(t/async
done
(let [;; ==== Setup
@ -396,7 +396,7 @@
(t/is (some? copied-blue1'))
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-copy-paste-yellow-to-b1
(t/deftest test-copy-paste-yellow-to-b1
(t/async
done
(let [;; ==== Setup
@ -436,7 +436,7 @@
(t/is (some? copied-blue1'))
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-copy-paste-yellow-to-b2
(t/deftest test-copy-paste-yellow-to-b2
(t/async
done
(let [;; ==== Setup
@ -475,7 +475,7 @@
(t/is (some? copied-blue1'))
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-cut-paste-yellow-to-root
(t/deftest test-cut-paste-yellow-to-root
(t/async
done
(let [;; ==== Setup
@ -511,7 +511,7 @@
(t/is (some? copied-blue1'))
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-cut-paste-yellow-to-b1
(t/deftest test-cut-paste-yellow-to-b1
(t/async
done
(let [;; ==== Setup
@ -549,7 +549,7 @@
(t/is (some? copied-blue1'))
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-remove-swap-slot-cut-paste-yellow-to-b2
(t/deftest test-cut-paste-yellow-to-b2
(t/async
done
(let [;; ==== Setup
@ -587,7 +587,7 @@
(t/is (some? copied-blue1'))
(t/is (not (ctk/swapped? copied-blue1')))))))))
(t/deftest test-keep-swap-slot-copy-paste-green-copy-to-root
(t/deftest test-copy-paste-green-copy-to-root
(t/async
done
(let [;; ==== Setup
@ -625,7 +625,7 @@
(t/is (some? copied-blue2'))
(t/is (ctk/swapped? copied-blue2'))))))))
(t/deftest test-keep-swap-slot-copy-paste-green-copy-to-b1
(t/deftest test-copy-paste-green-copy-to-b1
(t/async
done
(let [;; ==== Setup
@ -665,7 +665,7 @@
(t/is (some? copied-blue2'))
(t/is (ctk/swapped? copied-blue2'))))))))
(t/deftest test-keep-swap-slot-copy-paste-green-copy-to-b2
(t/deftest test-copy-paste-green-copy-to-b2
(t/async
done
(let [;; ==== Setup
@ -706,7 +706,7 @@
(t/is (ctk/swapped? copied-blue2'))))))))
(t/deftest test-keep-swap-slot-cut-paste-green-copy-to-root
(t/deftest test-cut-paste-green-copy-to-root
(t/async
done
(let [;; ==== Setup
@ -742,7 +742,7 @@
(t/is (some? copied-blue1'))
(t/is (ctk/swapped? copied-blue1'))))))))
(t/deftest test-keep-swap-slot-cut-paste-green-copy-to-b1
(t/deftest test-cut-paste-green-copy-to-b1
(t/async
done
(let [;; ==== Setup
@ -780,7 +780,7 @@
(t/is (some? copied-blue2'))
(t/is (ctk/swapped? copied-blue2'))))))))
(t/deftest test-keep-swap-slot-cut-paste-green-copy-to-b2
(t/deftest test-cut-paste-green-copy-to-b2
(t/async
done
(let [;; ==== Setup
@ -820,7 +820,7 @@
(t/deftest test-remove-swap-slot-copy-paste-swapped-main
(t/deftest test-copy-paste-swapped-main
(t/async
done
(let [;; ==== Setup

View File

@ -57,8 +57,8 @@
[frontend-tests.fonts-test]
[frontend-tests.helpers-shapes-test]
[frontend-tests.logic.bool-move-test]
[frontend-tests.logic.comp-remove-swap-slots-test]
[frontend-tests.logic.comp-watcher-test]
[frontend-tests.logic.comp-move-swapped-copies-test]
[frontend-tests.logic.components-and-tokens]
[frontend-tests.logic.copy-paste-typography-test]
[frontend-tests.logic.copying-and-duplicating-test]
@ -235,8 +235,8 @@
'frontend-tests.fonts-test
'frontend-tests.helpers-shapes-test
'frontend-tests.logic.bool-move-test
'frontend-tests.logic.comp-remove-swap-slots-test
'frontend-tests.logic.comp-watcher-test
'frontend-tests.logic.comp-move-swapped-copies-test
'frontend-tests.logic.components-and-tokens
'frontend-tests.logic.copy-paste-typography-test
'frontend-tests.logic.copying-and-duplicating-test

View File

@ -4271,7 +4271,8 @@ export interface ShapeBase extends PluginData {
/**
* @return Returns the equivalent shape in the component main instance. If the current shape is inside a
* main instance will return `null`;
* main instance will return `null`; For a copy that was swapped for another component, returns the shape
* of the main instance that it replaces.
*/
componentRefShape(): Shape | null;