🔧 Add more validations for components, to avoid some crashes (#7820)

* 🔧 Validate only after propagation in tests

* 💄 Enhance some component sync traces

* 🔧 Add fake uuid generator for debugging

* 🐛 Remove old feature of advancing references when reset changes

Since long time ago, we only allow to reset changes in the top copy
shape. In this case the near and the remote shapes are the same, so
the advance-ref has no effect.

* 🐛 Fix some bugs and add validations, repair and migrations

Also added several utilities to debug and to create scripts that
processes files

* 🐛 Fix misplaced parenthesis passing propagate-fn to wrong function

The :propagate-fn keyword argument was incorrectly placed inside the
ths/get-shape call instead of being passed to tho/reset-overrides.
This caused reset-overrides to never propagate component changes,
making the test not validate what it intended.

* 🐛 Accept and forward :include-deleted? in find-near-match

Callers were passing :include-deleted? true but the parameter was not
in the destructuring, so it was silently ignored and the function
always hardcoded true. This made the API misleading and would cause
incorrect behavior if called with :include-deleted? false.

* 💄 Use set/union alias instead of fully-qualified clojure.set/union

The namespace already requires [clojure.set :as set], so use the alias
for consistency.

* 🐛 Add tests for reset-overrides with and without propagate-fn

Add two focused tests to comp_reset_test to cover the propagate-fn
path in reset-overrides:

- test-reset-with-propagation-updates-copies: verifies that resetting
  an override on a nested copy inside a main and supplying propagate-fn
  causes the canonical color to appear in all downstream copies.

- test-reset-without-propagation-does-not-update-copies: regression
  guard for the misplaced-parenthesis bug; confirms that omitting
  propagate-fn leaves copies with the overridden value because the
  component sync never runs.

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
Andrés Moya 2026-04-15 08:42:42 +02:00 committed by GitHub
parent 909427d442
commit a3ea9fbecb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 1877 additions and 267 deletions

View File

@ -0,0 +1,115 @@
;; 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 INC
(ns app.common.files.comp-processors
"Repair, migration or transformation utilities for components."
(:require
[app.common.logging :as log]
[app.common.types.component :as ctk]
[app.common.types.file :as ctf]))
(log/set-level! :warn)
(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."
[file-data]
(ctf/update-components
file-data
(fn [component]
(if (:deleted component)
(if (nil? (:objects component))
(do
(log/warn :msg "Adding empty :objects to deleted component"
:component-id (:id component)
:component-name (:name component)
:file-id (:id file-data))
(assoc component :objects {}))
component)
(if (contains? component :objects)
(do
(log/warn :msg "Removing :objects from non-deleted component"
:component-id (:id component)
:component-name (:name component)
:file-id (:id file-data))
(dissoc component :objects))
component)))))
(defn fix-missing-swap-slots
"Locate shapes that have been swapped (i.e. their shape-ref does not point to the near match) but
they don't have a swap slot. In this case, add one pointing to the near match."
[file-data libraries]
(ctf/update-all-shapes
file-data
(fn [shape]
(if (ctk/subcopy-head? shape)
(let [container (:container (meta shape))
file {:id (:id file-data) :data file-data}
near-match (ctf/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))]
(log/warn :msg "Adding missing swap slot to shape"
:shape-id (:id shape)
:shape-name (:name shape)
:swap-slot (:id near-match)
:file-id (:id file)
:container-id (:id container)
:container-type (:type container))
{:result :update :updated-shape updated-shape})
{:result :keep}))
{:result :keep}))))
(defn sync-component-id-with-ref-shape
"Ensure that all copies heads have the same component id and file as the referenced shape.
There may be bugs that cause them to get out of sync."
[file-data libraries]
(letfn [(sync-one-iteration
[file-data libraries]
(ctf/update-all-shapes
file-data
(fn [shape]
(if (and (ctk/subcopy-head? shape) (nil? (ctk/get-swap-slot 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})]
(if (and (some? ref-shape)
(or (not= (:component-id shape) (:component-id ref-shape))
(not= (:component-file shape) (:component-file ref-shape))))
(let [shape' (cond-> shape
(some? (:component-id ref-shape))
(assoc :component-id (:component-id ref-shape))
(nil? (:component-id ref-shape))
(dissoc :component-id)
(some? (:component-file ref-shape))
(assoc :component-file (:component-file ref-shape))
(nil? (:component-file ref-shape))
(dissoc :component-file))]
(log/warn :msg "Syncing component id and file with ref shape"
:shape-id (:id shape)
:shape-name (:name shape)
:component-id (:component-id shape')
:component-file (:component-file shape')
:ref-shape-id (:id ref-shape)
:file-id (:id file)
:container-id (:id container)
:container-type (:type container))
{:result :update :updated-shape shape'})
{:result :keep}))
{:result :keep}))))]
;; If a copy inside a main is updated, we need to repeat the process for the change to be
;; propagated to all copies.
(loop [current-data file-data
iteration 0]
(let [next-data (sync-one-iteration current-data libraries)]
(if (or (= current-data next-data)
(> iteration 20)) ;; safety bound
next-data
(recur next-data (inc iteration)))))))

View File

@ -10,6 +10,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.features :as cfeat] [app.common.features :as cfeat]
[app.common.files.changes :as cpc] [app.common.files.changes :as cpc]
[app.common.files.comp-processors :as cfcp]
[app.common.files.defaults :as cfd] [app.common.files.defaults :as cfd]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
@ -1786,6 +1787,24 @@
(update :pages-index d/update-vals update-container) (update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container)))) (d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0018-remove-unneeded-objects-from-components"
[data _]
(cfcp/remove-unneeded-objects-in-components data))
(defmethod migrate-data "0019-fix-missing-swap-slots"
[data _]
(let [libraries (if (:libs data)
(deref (:libs data))
{})]
(cfcp/fix-missing-swap-slots data libraries)))
(defmethod migrate-data "0020-sync-component-id-with-near-main"
[data _]
(let [libraries (if (:libs data)
(deref (:libs data))
{})]
(cfcp/sync-component-id-with-ref-shape data libraries)))
(def available-migrations (def available-migrations
(into (d/ordered-set) (into (d/ordered-set)
["legacy-2" ["legacy-2"
@ -1860,4 +1879,7 @@
"0015-fix-text-attrs-blank-strings" "0015-fix-text-attrs-blank-strings"
"0015-clean-shadow-color" "0015-clean-shadow-color"
"0016-copy-fills-from-position-data-to-text-node" "0016-copy-fills-from-position-data-to-text-node"
"0017-fix-layout-flex-dir"])) "0017-fix-layout-flex-dir"
"0018-remove-unneeded-objects-from-components"
"0019-fix-missing-swap-slots"
"0020-sync-component-id-with-near-main"]))

View File

@ -334,6 +334,31 @@
(pcb/with-file-data file-data) (pcb/with-file-data file-data)
(pcb/update-shapes [(:id shape)] repair-shape)))) (pcb/update-shapes [(:id shape)] repair-shape))))
(defmethod repair-error :component-id-mismatch
[_ {:keys [shape page-id args] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Set the component-id and component-file to the ones of the near main
(log/debug :hint (str " -> set component-id to " (:component-id args)))
(log/debug :hint (str " -> set component-file to " (:component-file args)))
(cond-> shape
(some? (:component-id args))
(assoc :component-id (:component-id args))
(nil? (:component-id args))
(dissoc :component-id)
(some? (:component-file args))
(assoc :component-file (:component-file args))
(nil? (:component-file args))
(dissoc :component-file)))]
(log/dbg :hint "repairing shape :component-id-mismatch" :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-shape-is-head (defmethod repair-error :ref-shape-is-head
[_ {:keys [shape page-id args] :as error} file-data _] [_ {:keys [shape page-id args] :as error} file-data _]
(let [repair-shape (let [repair-shape
@ -501,7 +526,7 @@
(pcb/update-shapes [(:id shape)] repair-shape)))) (pcb/update-shapes [(:id shape)] repair-shape))))
(defmethod repair-error :component-nil-objects-not-allowed (defmethod repair-error :component-nil-objects-not-allowed
[_ {:keys [shape] :as error} file-data _] [_ {component :shape} file-data _] ; in this error the :shape argument is the component
(let [repair-component (let [repair-component
(fn [component] (fn [component]
;; Remove the objects key, or set it to {} if the component is deleted ;; Remove the objects key, or set it to {} if the component is deleted
@ -513,10 +538,26 @@
(log/debug :hint " -> remove :objects") (log/debug :hint " -> remove :objects")
(dissoc component :objects))))] (dissoc component :objects))))]
(log/dbg :hint "repairing component :component-nil-objects-not-allowed" :id (:id shape) :name (:name shape)) (log/dbg :hint "repairing component :component-nil-objects-not-allowed" :id (:id component) :name (:name component))
(-> (pcb/empty-changes nil) (-> (pcb/empty-changes nil)
(pcb/with-library-data file-data) (pcb/with-library-data file-data)
(pcb/update-component (:id shape) repair-component)))) (pcb/update-component (:id component) repair-component))))
(defmethod repair-error :non-deleted-component-cannot-have-objects
[_ {component :shape} file-data _] ; in this error the :shape argument is the component
(let [repair-component
(fn [component]
; Remove the :objects field
(if-not (:deleted component)
(do
(log/debug :hint " -> remove :objects")
(dissoc component :objects))
component))]
(log/dbg :hint "repairing component :non-deleted-component-cannot-have-objects" :id (:id component) :name (:name component))
(-> (pcb/empty-changes nil)
(pcb/with-library-data file-data)
(pcb/update-component (:id component) repair-component))))
(defmethod repair-error :invalid-text-touched (defmethod repair-error :invalid-text-touched
[_ {:keys [shape page-id] :as error} file-data _] [_ {:keys [shape page-id] :as error} file-data _]

View File

@ -51,6 +51,7 @@
:ref-shape-is-head :ref-shape-is-head
:ref-shape-is-not-head :ref-shape-is-not-head
:shape-ref-in-main :shape-ref-in-main
:component-id-mismatch
:root-main-not-allowed :root-main-not-allowed
:nested-main-not-allowed :nested-main-not-allowed
:root-copy-not-allowed :root-copy-not-allowed
@ -59,6 +60,7 @@
:not-head-copy-not-allowed :not-head-copy-not-allowed
:not-component-not-allowed :not-component-not-allowed
:component-nil-objects-not-allowed :component-nil-objects-not-allowed
:non-deleted-component-cannot-have-objects
:instance-head-not-frame :instance-head-not-frame
:invalid-text-touched :invalid-text-touched
:misplaced-slot :misplaced-slot
@ -326,6 +328,20 @@
:component-file (:component-file ref-shape) :component-file (:component-file ref-shape)
:component-id (:component-id ref-shape))))) :component-id (:component-id ref-shape)))))
(defn- check-ref-component-id
"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 (nil? (ctk/get-swap-slot shape))
(when-let [ref-shape (ctf/find-ref-shape file page libraries shape :include-deleted? true)]
(when (or (not= (:component-id shape) (:component-id ref-shape))
(not= (:component-file shape) (:component-file ref-shape)))
(report-error :component-id-mismatch
"Nested copy component-id and component-file must be the same as the near main"
shape file page
:component-id (:component-id ref-shape)
:component-file (:component-file ref-shape))))))
(defn- check-empty-swap-slot (defn- check-empty-swap-slot
"Validate that this shape does not have any swap slot." "Validate that this shape does not have any swap slot."
[shape file page] [shape file page]
@ -350,6 +366,19 @@
"This shape has children with the same swap slot" "This shape has children with the same swap slot"
shape file page))) shape file page)))
(defn- check-required-swap-slot
"Validate that the shape has swap-slot if it's a subinstance head and the ref shape is not the
matching shape by position in the near main."
[shape file page libraries]
(let [near-match (ctf/find-near-match file page libraries shape :include-deleted? true :with-context? false)]
(when (and (some? near-match)
(not= (:shape-ref shape) (:id near-match))
(nil? (ctk/get-swap-slot shape)))
(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))))))
(defn- check-valid-touched (defn- check-valid-touched
"Validate that the text touched flags are coherent." "Validate that the text touched flags are coherent."
[shape file page] [shape file page]
@ -418,6 +447,8 @@
(check-component-not-main-head shape file page libraries) (check-component-not-main-head shape file page libraries)
(check-component-not-root shape file page) (check-component-not-root shape file page)
(check-valid-touched 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)
;; We can have situations where the nested copy and the ancestor copy come from different libraries and some of them have been dettached ;; 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 ;; so we only validate the shape-ref if the ancestor is from a valid library
(when library-exists (when library-exists
@ -458,8 +489,7 @@
(defn- check-variant-container (defn- check-variant-container
"Shape is a variant container, so: "Shape is a variant container, so:
-all its children should be variants with variant-id equals to the shape-id -all its children should be variants with variant-id equals to the shape-id
-all the components should have the same properties -all the components should have the same properties"
"
[shape file page] [shape file page]
(let [shape-id (:id shape) (let [shape-id (:id shape)
shapes (:shapes shape) shapes (:shapes shape)
@ -648,6 +678,13 @@
"Component main not allowed inside other component" "Component main not allowed inside other component"
main-instance file component-page)))) main-instance file component-page))))
(defn- check-not-objects
[component file]
(when (d/not-empty? (:objects component))
(report-error :non-deleted-component-cannot-have-objects
"A non-deleted component cannot have shapes inside"
component file nil)))
(defn- check-component (defn- check-component
"Validate semantic coherence of a component. Report all errors found." "Validate semantic coherence of a component. Report all errors found."
[component file] [component file]
@ -656,7 +693,8 @@
"Objects list cannot be nil" "Objects list cannot be nil"
component file nil)) component file nil))
(when-not (:deleted component) (when-not (:deleted component)
(check-main-inside-main component file)) (check-main-inside-main component file)
(check-not-objects component file))
(when (:deleted component) (when (:deleted component)
(check-component-duplicate-swap-slot component file) (check-component-duplicate-swap-slot component file)
(check-ref-cycles component file)) (check-ref-cycles component file))
@ -674,8 +712,6 @@
;; PUBLIC API: VALIDATION FUNCTIONS ;; PUBLIC API: VALIDATION FUNCTIONS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare check-swap-slots)
(defn validate-file (defn validate-file
"Validate full referential integrity and semantic coherence on file data. "Validate full referential integrity and semantic coherence on file data.
@ -686,8 +722,6 @@
(doseq [page (filter :id (ctpl/pages-seq data))] (doseq [page (filter :id (ctpl/pages-seq data))]
(check-shape uuid/zero file page libraries) (check-shape uuid/zero file page libraries)
(when (str/includes? (:name file) "check-swap-slot")
(check-swap-slots uuid/zero file page libraries))
(->> (get-orphan-shapes page) (->> (get-orphan-shapes page)
(run! #(check-shape % file page libraries)))) (run! #(check-shape % file page libraries))))
@ -728,40 +762,3 @@
:hint "error on validating file referential integrity" :hint "error on validating file referential integrity"
:file-id (:id file) :file-id (:id file)
:details errors))) :details errors)))
(declare compare-slots)
;; Optional check to look for missing swap slots.
;; Search for copies that do not point the shape-ref to the near component but don't have swap slot
;; (looking for position relative to the parent, in the copy and the main).
;;
;; This check cannot be generally enabled, because files that have been migrated from components v1
;; may have copies with shapes that do not match by position, but have not been swapped. So we enable
;; it for specific files only. To activate the check, you need to add the string "check-swap-slot" to
;; the name of the file.
(defn- check-swap-slots
[shape-id file page libraries]
(let [shape (ctst/get-shape page shape-id)]
(if (and (ctk/instance-root? shape) (ctk/in-component-copy? shape))
(let [ref-shape (ctf/find-ref-shape file page libraries shape :include-deleted? true :with-context? true)
container (:container (meta ref-shape))]
(when (some? ref-shape)
(compare-slots shape ref-shape file page container)))
(doall (for [child-id (:shapes shape)]
(check-swap-slots child-id file page libraries))))))
(defn- compare-slots
[shape-copy shape-main file container-copy container-main]
(if (and (not= (:shape-ref shape-copy) (:id shape-main))
(nil? (ctk/get-swap-slot shape-copy)))
(report-error :missing-slot
"Shape has been swapped, should have swap slot"
shape-copy file container-copy
:swap-slot (or (ctk/get-swap-slot shape-main) (:id shape-main)))
(when (nil? (ctk/get-swap-slot shape-copy))
(let [children-id-pairs (d/zip-all (:shapes shape-copy) (:shapes shape-main))]
(doall (for [[child-copy-id child-main-id] children-id-pairs]
(let [child-copy (ctst/get-shape container-copy child-copy-id)
child-main (ctst/get-shape container-main child-main-id)]
(when (and (some? child-copy) (some? child-main))
(compare-slots child-copy child-main file container-copy container-main)))))))))

View File

@ -333,7 +333,7 @@
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> promote to root") (pcb/update-shapes [shape-id] #(do (log/trace :msg " -> promote to root")
(assoc % :component-root true))) (assoc % :component-root true)))
:always (some? (ctk/get-swap-slot shape))
; First level subinstances of a detached component can't have swap-slot ; First level subinstances of a detached component can't have swap-slot
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> remove swap-slot") (pcb/update-shapes [shape-id] #(do (log/trace :msg " -> remove swap-slot")
(ctk/remove-swap-slot %))) (ctk/remove-swap-slot %)))
@ -364,7 +364,7 @@
(let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})] (let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})]
(cond-> changes (cond-> changes
(some? (:shape-ref ref-shape)) (some? (:shape-ref ref-shape))
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg " (advanced)") (pcb/update-shapes [(:id shape)] #(do (log/trace :msg (str " (advanced to " (:shape-ref ref-shape) ")"))
(assoc % :shape-ref (:shape-ref ref-shape)))) (assoc % :shape-ref (:shape-ref ref-shape))))
;; When advancing level, the normal touched groups (not swap slots) of the ;; When advancing level, the normal touched groups (not swap slots) of the
@ -374,16 +374,18 @@
(pcb/update-shapes (pcb/update-shapes
[(:id shape)] [(:id shape)]
#(do (log/trace :msg " (merge touched)") #(do (log/trace :msg " (merge touched)")
(log/trace :msg (str " (ref-shape: " (:id ref-shape) ")"))
(log/trace :msg (str " (ref touched: " (:touched ref-shape) ")"))
(assoc % :touched (assoc % :touched
(clojure.set/union (:touched shape) (set/union (:touched shape)
(ctk/normal-touched-groups ref-shape))))) (ctk/normal-touched-groups ref-shape)))))
;; Swap slot must also be copied if the current shape has not any, ;; Swap slot must also be copied if the current shape has not any,
;; except if this is the first level subcopy. ;; except if this is the first level subcopy.
(and (some? (ctk/get-swap-slot ref-shape)) (and (some? (ctk/get-swap-slot ref-shape))
(nil? (ctk/get-swap-slot shape)) (nil? (ctk/get-swap-slot shape))
(not= (:id shape) shape-id)) (not= (:id shape) shape-id))
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg " (got swap-slot)") (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)))) (ctk/set-swap-slot % (ctk/get-swap-slot ref-shape))))
;; If we can't get the ref-shape (e.g. it's in an external library not linked), ;; If we can't get the ref-shape (e.g. it's in an external library not linked),
@ -771,14 +773,6 @@
;; is different than the one in the near component (Shape-2-2-1) ;; is different than the one in the near component (Shape-2-2-1)
;; but it's not touched. ;; but it's not touched.
(defn- redirect-shaperef ;;Set the :shape-ref of a shape pointing to the :id of its remote-shape
([container libraries shape]
(redirect-shaperef nil nil shape (ctf/find-remote-shape container libraries shape)))
([_ _ shape remote-shape]
(if (some? (:shape-ref shape))
(assoc shape :shape-ref (:id remote-shape))
shape)))
(defn generate-sync-shape-direct (defn generate-sync-shape-direct
"Generate changes to synchronize one shape that is the root of a component "Generate changes to synchronize one shape that is the root of a component
instance, and all its children, from the given component." instance, and all its children, from the given component."
@ -790,18 +784,12 @@
component (ctkl/get-component library (:component-id shape-inst) true)] component (ctkl/get-component library (:component-id shape-inst) true)]
(if (and (ctk/in-component-copy? shape-inst) (if (and (ctk/in-component-copy? shape-inst)
(or (ctf/direct-copy? shape-inst component container nil libraries) reset?)) ; In a normal sync, we don't want to sync remote mains, only direct/near (or (ctf/direct-copy? shape-inst component container nil libraries) reset?)) ; In a normal sync, we don't want to sync remote mains, only direct/near
(let [redirect-shaperef (partial redirect-shaperef container libraries) (let [shape-main (when component
shape-main (when component
(if reset? (if reset?
;; the reset is against the ref-shape, not against the original shape of the component ;; the reset is against the ref-shape, not against the original shape of the component
(ctf/find-ref-shape file container libraries shape-inst) (ctf/find-ref-shape file container libraries shape-inst)
(ctf/get-ref-shape library component shape-inst))) (ctf/get-ref-shape library component shape-inst)))
shape-inst (if reset?
(redirect-shaperef shape-inst shape-main)
shape-inst)
initial-root? (:component-root shape-inst) initial-root? (:component-root shape-inst)
root-inst shape-inst root-inst shape-inst
@ -819,8 +807,8 @@
root-inst root-inst
root-main root-main
reset? reset?
initial-root? initial-root?)
redirect-shaperef)
;; If the component is not found, because the master component has been ;; If the component is not found, because the master component has been
;; deleted or the library unlinked, do nothing. ;; deleted or the library unlinked, do nothing.
changes)) changes))
@ -844,7 +832,7 @@
nil)))))) nil))))))
(defn- generate-sync-shape-direct-recursive (defn- generate-sync-shape-direct-recursive
[changes container shape-inst component library file libraries shape-main root-inst root-main reset? initial-root? redirect-shaperef] [changes container shape-inst component library file libraries shape-main root-inst root-main reset? initial-root?]
(shape-log :debug (:id shape-inst) container (shape-log :debug (:id shape-inst) container
:msg "Sync shape direct recursive" :msg "Sync shape direct recursive"
:shape-inst (str (:name shape-inst) " " (pretty-uuid (:id shape-inst))) :shape-inst (str (:name shape-inst) " " (pretty-uuid (:id shape-inst)))
@ -891,9 +879,6 @@
children-inst (vec (ctn/get-direct-children container shape-inst)) children-inst (vec (ctn/get-direct-children container shape-inst))
children-main (vec (ctn/get-direct-children component-container shape-main)) children-main (vec (ctn/get-direct-children component-container shape-main))
children-inst (if reset?
(map #(redirect-shaperef %) children-inst) children-inst)
only-inst (fn [changes child-inst] only-inst (fn [changes child-inst]
(shape-log :trace (:id child-inst) container (shape-log :trace (:id child-inst) container
:msg "Only inst" :msg "Only inst"
@ -942,8 +927,7 @@
root-inst root-inst
root-main root-main
reset? reset?
initial-root? initial-root?))
redirect-shaperef))
swapped (fn [changes child-inst child-main] swapped (fn [changes child-inst child-main]
(shape-log :trace (:id child-inst) container (shape-log :trace (:id child-inst) container
@ -1008,16 +992,13 @@
the values in the shape and all its children." the values in the shape and all its children."
[changes file libraries container shape-id] [changes file libraries container shape-id]
(shape-log :debug shape-id container :msg "Sync shape inverse" :shape (str shape-id)) (shape-log :debug shape-id container :msg "Sync shape inverse" :shape (str shape-id))
(let [redirect-shaperef (partial redirect-shaperef container libraries) (let [shape-inst (ctn/get-shape container shape-id)
shape-inst (ctn/get-shape container shape-id)
library (dm/get-in libraries [(:component-file shape-inst) :data]) library (dm/get-in libraries [(:component-file shape-inst) :data])
component (ctkl/get-component library (:component-id shape-inst)) component (ctkl/get-component library (:component-id shape-inst))
shape-main (when component shape-main (when component
(ctf/find-remote-shape container libraries shape-inst)) (ctf/find-remote-shape container libraries shape-inst))
shape-inst (redirect-shaperef shape-inst shape-main)
initial-root? (:component-root shape-inst) initial-root? (:component-root shape-inst)
root-inst shape-inst root-inst shape-inst
@ -1038,12 +1019,11 @@
shape-main shape-main
root-inst root-inst
root-main root-main
initial-root? initial-root?)
redirect-shaperef)
changes))) changes)))
(defn- generate-sync-shape-inverse-recursive (defn- generate-sync-shape-inverse-recursive
[changes container shape-inst component library file libraries shape-main root-inst root-main initial-root? redirect-shaperef] [changes container shape-inst component library file libraries shape-main root-inst root-main initial-root?]
(shape-log :trace (:id shape-inst) container (shape-log :trace (:id shape-inst) container
:msg "Sync shape inverse recursive" :msg "Sync shape inverse recursive"
:shape (str (:name shape-inst)) :shape (str (:name shape-inst))
@ -1100,8 +1080,6 @@
children-main (mapv #(ctn/get-shape component-container %) children-main (mapv #(ctn/get-shape component-container %)
(:shapes shape-main)) (:shapes shape-main))
children-inst (map #(redirect-shaperef %) children-inst)
only-inst (fn [changes child-inst] only-inst (fn [changes child-inst]
(add-shape-to-main changes (add-shape-to-main changes
child-inst child-inst
@ -1130,8 +1108,7 @@
child-main child-main
root-inst root-inst
root-main root-main
initial-root? initial-root?))
redirect-shaperef))
swapped (fn [changes child-inst child-main] swapped (fn [changes child-inst child-main]
(shape-log :trace (:id child-inst) container (shape-log :trace (:id child-inst) container
@ -1773,6 +1750,23 @@
(pcb/update-shapes changes [(:id dest-shape)] ctk/unhead-shape {:ignore-touched true}) (pcb/update-shapes changes [(:id dest-shape)] ctk/unhead-shape {:ignore-touched true})
changes)) changes))
(defn- check-swapped-main
[changes dest-shape origin-shape]
;; Only for direct updates (from main to copy). Check if the main shape
;; has been swapped. If so, the new component-id and component-file must
;; be put into the copy.
(if (and (= (:shape-ref dest-shape) (:id origin-shape))
(ctk/instance-head? dest-shape)
(ctk/instance-head? origin-shape)
(or (not= (:component-id dest-shape) (:component-id origin-shape))
(not= (:component-file dest-shape) (:component-file origin-shape))))
(pcb/update-shapes changes [(:id dest-shape)]
#(assoc %
:component-id (:component-id origin-shape)
:component-file (:component-file origin-shape))
{:ignore-touched true})
changes))
(defn- update-attrs (defn- update-attrs
"The main function that implements the attribute sync algorithm. Copy "The main function that implements the attribute sync algorithm. Copy
attributes that have changed in the origin shape to the dest shape. attributes that have changed in the origin shape to the dest shape.
@ -1816,6 +1810,8 @@
:always :always
(check-detached-main dest-shape origin-shape) (check-detached-main dest-shape origin-shape)
:always :always
(check-swapped-main dest-shape origin-shape)
:always
(generate-update-tokens container dest-shape origin-shape touched omit-touched? nil)) (generate-update-tokens container dest-shape origin-shape touched omit-touched? nil))
(let [sync-group (let [sync-group

View File

@ -177,8 +177,11 @@
(thc/instantiate-component component-label copy-root-label copy-root-params))) (thc/instantiate-component component-label copy-root-label copy-root-params)))
(defn add-nested-component (defn add-nested-component
[file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label [file
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params]}] component1-label main1-root-label main1-child-label
component2-label main2-root-label nested-head-label
& {:keys [component1-params root1-params main1-child-params
component2-params main2-root-params nested-head-params]}]
;; Generated shape tree: ;; Generated shape tree:
;; {:main1-root-label} [:name Frame1] # [Component :component1-label] ;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
;; :main1-child-label [:name Rect1] ;; :main1-child-label [:name Rect1]
@ -204,8 +207,13 @@
component2-params))) component2-params)))
(defn add-nested-component-with-copy (defn add-nested-component-with-copy
[file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label copy2-root-label [file
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params copy2-root-params]}] component1-label main1-root-label main1-child-label
component2-label main2-root-label nested-head-label
copy2-root-label
& {:keys [component1-params root1-params main1-child-params
component2-params main2-root-params nested-head-params
copy2-root-params]}]
;; Generated shape tree: ;; Generated shape tree:
;; {:main1-root-label} [:name Frame1] # [Component :component1-label] ;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
;; :main1-child-label [:name Rect1] ;; :main1-child-label [:name Rect1]
@ -232,6 +240,102 @@
:nested-head-params nested-head-params) :nested-head-params nested-head-params)
(thc/instantiate-component component2-label copy2-root-label copy2-root-params))) (thc/instantiate-component component2-label copy2-root-label copy2-root-params)))
(defn add-two-levels-nested-component
[file
component1-label main1-root-label main1-child-label
component2-label main2-root-label nested-head1-label
component3-label main3-root-label nested-head2-label nested-subhead2-label
& {:keys [component1-params root1-params main1-child-params
component2-params main2-root-params nested-head1-params
component3-params main3-root-params nested-head2-params]}]
;; Generated shape tree:
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
;; :main1-child-label [:name Rect1]
;;
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
;; :nested-head1-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
;; <no-label> [:name Rect1] ---> :main1-child-label
;;
;; {:main3-root-label} [:name Frame3] # [Component :component3-label]
;; :nested-head2-label [:name Frame2] @--> [Component :component2-label] :main2-root-label
;; :nested-subhead2-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
;; <no-label> [:name Rect1] ---> :main1-child-label
(-> file
(add-simple-component component1-label
main1-root-label
main1-child-label
:component-params component1-params
:root-params root1-params
:child-params main1-child-params)
(add-frame main2-root-label (merge {:name "Frame2"}
main2-root-params))
(thc/instantiate-component component1-label
nested-head1-label
(assoc nested-head1-params
:parent-label main2-root-label))
(thc/make-component component2-label
main2-root-label
component2-params)
(add-frame main3-root-label (merge {:name "Frame3"}
main3-root-params))
(thc/instantiate-component component2-label
nested-head2-label
(assoc nested-head2-params
:parent-label main3-root-label
:children-labels [nested-subhead2-label]))
(thc/make-component component3-label
main3-root-label
component3-params)))
(defn add-two-levels-nested-component-with-copy
[file
component1-label main1-root-label main1-child-label
component2-label main2-root-label nested-head1-label
component3-label main3-root-label nested-head2-label nested-subhead2-label
copy2-root-label
& {:keys [component1-params root1-params main1-child-params
component2-params main2-root-params nested-head1-params
component3-params main3-root-params nested-head2-params
copy2-root-params]}]
;; Generated shape tree:
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
;; :main1-child-label [:name Rect1]
;;
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
;; :nested-head1-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
;; <no-label> [:name Rect1] ---> :main1-child-label
;;
;; {:main3-root-label} [:name Frame3] # [Component :component3-label]
;; :nested-head2-label [:name Frame2] @--> [Component :component2-label] :main2-root-label
;; :nested-subhead2-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
;; <no-label> [:name Rect1] ---> :main1-child-label
;;
;; :copy2-label [:name Frame3] #--> [Component :component3-label] :main3-root-label
;; <no-label> [:name Frame2] @--> [Component :component2-label] :nested-head2-label
;; <no-label> [:name Frame1] @--> [Component :component1-label] :nested-subhead2-label
;; <no-label> [:name Rect1] ---> <no-label>
(-> file
(add-two-levels-nested-component component1-label
main1-root-label
main1-child-label
component2-label
main2-root-label
nested-head1-label
component3-label
main3-root-label
nested-head2-label
nested-subhead2-label
:component1-params component1-params
:root1-params root1-params
:main1-child-params main1-child-params
:component2-params component2-params
:main2-root-params main2-root-params
:nested-head1-params nested-head1-params
:component3-params component3-params
:main3-root-params main3-root-params
:nested-head2-params nested-head2-params)
(thc/instantiate-component component3-label copy2-root-label copy2-root-params)))
;; ----- Getters ;; ----- Getters
(defn bottom-shape-by-id (defn bottom-shape-by-id
@ -274,15 +378,18 @@
file-id file-id
{file-id file} {file-id file}
file-id))] file-id))]
(thf/apply-changes file changes))) (thf/apply-changes file changes :validate? false)))
(defn swap-component (defn swap-component-
"Swap the specified shape by the component specified by component-tag" "Swap the specified shape by the component specified by component-tag"
[file shape component-tag & {:keys [page-label propagate-fn keep-touched? new-shape-label]}] [file shape component-tag & {:keys [page-label propagate-fn keep-touched? new-shape-label library]}]
(let [page (if page-label (let [page (if page-label
(thf/get-page file page-label) (thf/get-page file page-label)
(thf/current-page file)) (thf/current-page file))
libraries {(:id file) file} libraries (cond-> {(:id file) file}
(some? library)
(assoc (:id library) library))
library (or library file)
orig-shapes (when keep-touched? (cfh/get-children-with-self (:objects page) (:id shape))) orig-shapes (when keep-touched? (cfh/get-children-with-self (:objects page) (:id shape)))
@ -290,10 +397,10 @@
(cll/generate-component-swap (pcb/empty-changes) (cll/generate-component-swap (pcb/empty-changes)
(:objects page) (:objects page)
shape shape
(:data file) (:data library)
page page
libraries libraries
(-> (thc/get-component file component-tag) (-> (thc/get-component library component-tag)
:id) :id)
0 0
nil nil
@ -305,26 +412,36 @@
[changes nil]) [changes nil])
file' (thf/apply-changes file changes)] file' (thf/apply-changes file changes :validate? (not propagate-fn))]
(when new-shape-label (when new-shape-label
(thi/rm-id! (:id new-shape)) (thi/rm-id! (:id new-shape))
(thi/set-id! new-shape-label (:id new-shape))) (thi/set-id! new-shape-label (:id new-shape)))
(if propagate-fn (if propagate-fn
(propagate-fn file') (-> (propagate-fn file')
(thf/validate-file!))
file'))) file')))
(defn swap-component-in-shape [file shape-tag component-tag & {:keys [page-label propagate-fn]}] (defn swap-component-in-shape
(swap-component file (ths/get-shape file shape-tag :page-label page-label) component-tag :page-label page-label :propagate-fn propagate-fn)) [file shape-tag component-tag & {:keys [page-label propagate-fn keep-touched? new-shape-label library]}]
(swap-component- file (ths/get-shape file shape-tag :page-label page-label)
component-tag
:page-label page-label
:propagate-fn propagate-fn
:keep-touched? keep-touched?
:new-shape-label new-shape-label
:library library))
(defn swap-component-in-first-child [file shape-tag component-tag & {:keys [page-label propagate-fn]}] (defn swap-component-in-first-child
[file shape-tag component-tag & {:keys [page-label propagate-fn library]}]
(let [first-child-id (->> (ths/get-shape file shape-tag :page-label page-label) (let [first-child-id (->> (ths/get-shape file shape-tag :page-label page-label)
:shapes :shapes
first)] first)]
(swap-component file (swap-component- file
(ths/get-shape-by-id file first-child-id :page-label page-label) (ths/get-shape-by-id file first-child-id :page-label page-label)
component-tag component-tag
:page-label page-label :page-label page-label
:propagate-fn propagate-fn))) :propagate-fn propagate-fn
:library library)))
(defn update-color (defn update-color
"Update the first fill color for the shape identified by shape-tag" "Update the first fill color for the shape identified by shape-tag"
@ -339,9 +456,10 @@
(assoc shape :fills (ths/sample-fills-color :fill-color color))) (assoc shape :fills (ths/sample-fills-color :fill-color color)))
(:objects page) (:objects page)
{}) {})
file' (thf/apply-changes file changes)] file' (thf/apply-changes file changes :validate? (not propagate-fn))]
(if propagate-fn (if propagate-fn
(propagate-fn file') (-> (propagate-fn file')
(thf/validate-file!))
file'))) file')))
(defn update-bottom-color (defn update-bottom-color
@ -357,9 +475,10 @@
(assoc shape :fills (ths/sample-fills-color :fill-color color))) (assoc shape :fills (ths/sample-fills-color :fill-color color)))
(:objects page) (:objects page)
{}) {})
file' (thf/apply-changes file changes)] file' (thf/apply-changes file changes :validate? (not propagate-fn))]
(if propagate-fn (if propagate-fn
(propagate-fn file') (-> (propagate-fn file')
(thf/validate-file!))
file'))) file')))
(defn reset-overrides [file shape & {:keys [page-label propagate-fn]}] (defn reset-overrides [file shape & {:keys [page-label propagate-fn]}]
@ -374,9 +493,10 @@
{file-id file} {file-id file}
(ctn/make-container container :page) (ctn/make-container container :page)
(:id shape))) (:id shape)))
file' (thf/apply-changes file changes)] file' (thf/apply-changes file changes :validate? (not propagate-fn))]
(if propagate-fn (if propagate-fn
(propagate-fn file') (-> (propagate-fn file')
(thf/validate-file!))
file'))) file')))
(defn reset-overrides-in-first-child [file shape-tag & {:keys [page-label propagate-fn]}] (defn reset-overrides-in-first-child [file shape-tag & {:keys [page-label propagate-fn]}]
@ -398,9 +518,10 @@
#{(-> (ths/get-shape file shape-tag :page-label page-label) #{(-> (ths/get-shape file shape-tag :page-label page-label)
:id)} :id)}
{}) {})
file' (thf/apply-changes file changes)] file' (thf/apply-changes file changes :validate? (not propagate-fn))]
(if propagate-fn (if propagate-fn
(propagate-fn file') (-> (propagate-fn file')
(thf/validate-file!))
file'))) file')))
(defn duplicate-shape [file shape-tag & {:keys [page-label propagate-fn]}] (defn duplicate-shape [file shape-tag & {:keys [page-label propagate-fn]}]
@ -419,8 +540,9 @@
(:id file)) ;; file-id (:id file)) ;; file-id
(cll/generate-duplicate-changes-update-indices (:objects page) ;; objects (cll/generate-duplicate-changes-update-indices (:objects page) ;; objects
#{(:id shape)})) #{(:id shape)}))
file' (thf/apply-changes file changes)] file' (thf/apply-changes file changes :validate? (not propagate-fn))]
(if propagate-fn (if propagate-fn
(propagate-fn file') (-> (propagate-fn file')
(thf/validate-file!))
file'))) file')))

View File

@ -54,12 +54,14 @@
([file] (validate-file! file {})) ([file] (validate-file! file {}))
([file libraries] ([file libraries]
(cfv/validate-file-schema! file) (cfv/validate-file-schema! file)
(cfv/validate-file! file libraries))) (cfv/validate-file! file libraries)
file))
(defn apply-changes (defn apply-changes
[file changes] [file changes & {:keys [validate?] :or {validate? true}}]
(let [file' (ctf/update-file-data file #(cfc/process-changes % (:redo-changes changes) true))] (let [file' (ctf/update-file-data file #(cfc/process-changes % (:redo-changes changes) true))]
(validate-file! file') (when validate?
(validate-file! file'))
file')) file'))
(defn apply-undo-changes (defn apply-undo-changes

View File

@ -82,6 +82,18 @@
(:id page) (:id page)
#(ctst/set-shape % (ctn/set-shape-attr shape attr val))))))) #(ctst/set-shape % (ctn/set-shape-attr shape attr val)))))))
(defn update-shape-by-id
[file shape-id attr val & {:keys [page-label]}]
(let [page (if page-label
(thf/get-page file page-label)
(thf/current-page file))
shape (ctst/get-shape page shape-id)]
(update file :data
(fn [file-data]
(ctpl/update-page file-data
(:id page)
#(ctst/set-shape % (ctn/set-shape-attr shape attr val)))))))
(defn update-shape-text (defn update-shape-text
[file shape-label attr val & {:keys [page-label]}] [file shape-label attr val & {:keys [page-label]}]
(let [page (if page-label (let [page (if page-label

View File

@ -163,11 +163,15 @@
Note that design tokens also are involved, although they go by an alternate Note that design tokens also are involved, although they go by an alternate
route and thus they are not part of :sync-attrs. route and thus they are not part of :sync-attrs.
Also when detaching a nested copy it also needs to trigger a synchronization, Also when detaching a nested copy it also needs to trigger a synchronization,
even though :shape-ref is not a synced attribute per se" even though :shape-ref, :component-id or :component-file are not synced
attributes per se."
[attr] [attr]
(or (contains? sync-attrs attr) (or (contains? sync-attrs attr)
(= :shape-ref attr) (= :shape-ref attr)
(= :applied-tokens attr))) (= :applied-tokens attr)
(= :component-id attr)
(= :component-file attr)
(= :component-root attr)))
(defn instance-root? (defn instance-root?
"Check if this shape is the head of a top instance." "Check if this shape is the head of a top instance."

View File

@ -60,6 +60,9 @@
(some? objects) (some? objects)
(assoc :objects objects) (assoc :objects objects)
(nil? objects)
(dissoc :objects)
(some? modified-at) (some? modified-at)
(assoc :modified-at modified-at) (assoc :modified-at modified-at)

View File

@ -55,6 +55,10 @@
[page-or-component type] [page-or-component type]
(assoc page-or-component :type type)) (assoc page-or-component :type type))
(defn unmake-container
[container]
(dissoc container :type))
(defn page? (defn page?
[container] [container]
(= (:type container) :page)) (= (:type container) :page))

View File

@ -204,7 +204,8 @@
(defn update-file-data (defn update-file-data
[file f] [file f]
(update file :data f)) (when file
(update file :data f)))
(defn containers-seq (defn containers-seq
"Generate a sequence of all pages and all components, wrapped as containers" "Generate a sequence of all pages and all components, wrapped as containers"
@ -225,6 +226,85 @@
(ctpl/update-page file-data (:id container) f) (ctpl/update-page file-data (:id container) f)
(ctkl/update-component file-data (:id container) f))) (ctkl/update-component file-data (:id container) f)))
(defn update-pages
"Update all pages inside the file"
[file-data f]
(update file-data :pages-index d/update-vals
(fn [page]
(-> page
(ctn/make-container :page)
(f)
(ctn/unmake-container)))))
(defn update-components
"Update all components inside the file"
[file-data f]
(d/update-when file-data :components d/update-vals
(fn [component]
(-> component
(ctn/make-container :component)
(f)
(ctn/unmake-container)))))
(defn update-containers
"Update all pages and components inside the file"
[file-data f]
(-> file-data
(update-pages f)
(update-components f)))
(defn update-objects-tree
"Do a depth-first traversal of the shapes in a container, doing different kinds of updates.
The function f receives a shape with a context metadata with the container.
It must return a map with the following keys:
- :result -> :keep, :update or :remove
- :updated-shape -> the updated shape if result is :update"
[container f]
(letfn [(update-shape-recursive
[container shape-id]
(let [shape (ctst/get-shape container shape-id)]
(when (not shape)
(throw (ex-info "Shape not found" {:shape-id shape-id})))
(let [shape (with-meta shape {:container container})
{:keys [result updated-shape]} (f shape)
container'
(case result
:keep
container
:update
(ctst/set-shape container updated-shape)
:remove
(ctst/delete-shape container shape-id true)
(throw (ex-info "Invalid result from update function" {:result result})))]
(if (= result :remove)
container'
(reduce update-shape-recursive
container'
(:shapes shape))))))]
(let [root-id (if (ctn/page? container)
uuid/zero
(:main-instance-id container))]
(if-not (empty? (:objects container))
(update-shape-recursive container root-id)
container))))
(defn update-all-shapes
"Update all shapes in the file data, using the update-objects-tree function for each container"
[file-data f]
(when file-data
(update-containers
file-data
(fn [container]
(update-objects-tree container f)))))
;; Asset helpers ;; Asset helpers
(defn find-component-file (defn find-component-file
[file libraries component-file] [file libraries component-file]
@ -328,6 +408,27 @@
(get-ref-shape (:data component-file) component shape :with-context? with-context?))))] (get-ref-shape (:data component-file) component shape :with-context? with-context?))))]
(some find-ref-shape-in-head (ctn/get-parent-heads (:objects container) shape)))) (some find-ref-shape-in-head (ctn/get-parent-heads (:objects container) 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 (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 advance-shape-ref (defn advance-shape-ref
"Get the shape-ref of the near main of the shape, recursively repeated as many times "Get the shape-ref of the near main of the shape, recursively repeated as many times
as the given levels." as the given levels."

View File

@ -16,8 +16,6 @@
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid])) [app.common.uuid :as uuid]))
;; FIXME: the order of arguments seems arbitrary, container should be a first artgument
(defn add-shape (defn add-shape
"Insert a shape in the tree, at the given index below the given parent or frame. "Insert a shape in the tree, at the given index below the given parent or frame.
Update the parent as needed." Update the parent as needed."

View File

@ -60,8 +60,9 @@
:cljs (uuid (impl/v4)))) :cljs (uuid (impl/v4))))
(defn custom (defn custom
([a] #?(:clj (UUID. 0 a) :cljs (uuid (impl/custom 0 a)))) "Generate a uuid using directly the given number (specified as one or two long integers)"
([b a] #?(:clj (UUID. b a) :cljs (uuid (impl/custom b a))))) ([low] #?(:clj (UUID. 0 low) :cljs (uuid (impl/custom 0 low))))
([high low] #?(:clj (UUID. high low) :cljs (uuid (impl/custom high low)))))
(def zero (uuid "00000000-0000-0000-0000-000000000000")) (def zero (uuid "00000000-0000-0000-0000-000000000000"))
@ -137,6 +138,22 @@
(+ (clojure.lang.Murmur3/hashLong a) (+ (clojure.lang.Murmur3/hashLong a)
(clojure.lang.Murmur3/hashLong b))))) (clojure.lang.Murmur3/hashLong b)))))
;; Fake uuids generator
(def ^:private fake-ids (atom 0))
(defn reset-fake!
"Reset the fake uuid counter to 0, for reproducible results across tests."
[]
(reset! fake-ids 0))
(defn next-fake
"When you need predictable uuids, for example when debugging a failing test, wrap the code with
(with-redefs [uuid/next uuid/next-fake]
...tested code...)"
[]
(-> (swap! fake-ids inc)
(custom)))
;; Commented code used for debug ;; Commented code used for debug
;; #?(:cljs ;; #?(:cljs
;; (defn ^:export test-uuid ;; (defn ^:export test-uuid

View File

@ -0,0 +1,787 @@
;; 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 INC
(ns common-tests.files.comp-processors-test
(:require
[app.common.data :as d]
[app.common.files.comp-processors :as cfcp]
[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]))
(t/deftest test-remove-unneeded-objects-in-components
(t/testing "nil file should return nil"
(let [file nil
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
(t/is (nil? file'))))
(t/testing "empty file should not need any action"
(let [file (thf/sample-file :file1)
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file without components should not need any action"
(let [file
(-> (thf/sample-file :file1)
(tho/add-frame-with-child :frame1 :shape1))
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with non deleted components should not need any action"
(let [file
(-> (thf/sample-file :file1)
(tho/add-simple-component :component1 :frame1 :shape1))
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with deleted components should not need any action"
(let [file
(-> (thf/sample-file :file1)
(tho/add-simple-component :component1 :frame1 :shape1)
(tho/delete-shape :frame1))
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with non deleted components with :objects nil should remove it"
(let [file
(-> (thf/sample-file :file1)
(tho/add-simple-component :component1 :frame1 :shape1)
(thc/update-component :component1 {:objects nil}))
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)
diff (d/map-diff file file')
expected-diff {:data
{:components
{(thi/id :component1)
{}}}}]
(t/is (= expected-diff diff))))
(t/testing "file with non deleted components with :objects should remove it"
(let [file
(-> (thf/sample-file :file1)
(tho/add-simple-component :component1 :frame1 :shape1)
(thc/update-component :component1 {:objects {:sample 777}}))
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)
diff (d/map-diff file file')
expected-diff {:data
{:components
{(thi/id :component1)
{:objects
[{:sample 777} nil]}}}}]
(t/is (= expected-diff diff))))
(t/testing "file with deleted components without :objects should add an empty one"
(let [file
(-> (thf/sample-file :file1)
(tho/add-simple-component :component1 :frame1 :shape1)
(tho/delete-shape :frame1)
(ctf/update-file-data
(fn [file-data]
(ctkl/update-component file-data (thi/id :component1) #(dissoc % :objects)))))
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)
diff (d/map-diff file file')
expected-diff {:data
{:components
{(thi/id :component1)
{:objects
[nil {}]}}}}]
(t/is (= expected-diff diff)))))
(t/deftest test-fix-missing-swap-slots
(t/testing "nil file should return nil"
(let [file nil
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
(t/is (nil? file'))))
(t/testing "empty file should not need any action"
(let [file (thf/sample-file :file1)
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file without components should not need any action"
(let [file
;; :frame1 [:name Frame1]
;; :child1 [:name Rect1]
(-> (thf/sample-file :file1)
(tho/add-frame-with-child :frame1 :shape1))
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with nested not swapped components should not need any action"
(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
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-head
;; <no-label> [:name Rect1] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-nested-component-with-copy :component1 :main1-root :main1-child
:component2 :main2-root :nested-head
:copy2-root))
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with a normally swapped copy should not need any action"
(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
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; :copy2-root [: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)
(thc/instantiate-component :component2 :copy2-root :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-root :component3))
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with a swapped nested copy in a main should not need any action"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head [:name Frame3] @--> [Component :component3] :main3-root
;; {swap-slot :nested-head}
;; <no-label> [:name Rect3] ---> :main3-child
;;
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :nested-head
;; <no-label> [:name Rect3] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head)
(thc/instantiate-component :component2 :copy2-root :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-shape :nested-head :component3
:propagate-fn #(tho/propagate-component-changes % :component2)))
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with a swapped copy with broken slot should have it repaired"
(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
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :main3-root
;; NO SWAP SLOT
;; <no-label> [:name Rect3] ---> :main3-child
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head)
(thc/instantiate-component :component2 :copy2-root :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-root :component3)
(ths/update-shape :copy2-nested-head :touched nil))
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))
diff (d/map-diff file file')
expected-diff {:data
{:pages-index
{(thf/current-page-id file)
{:objects
{(thi/id :copy2-nested-head)
{:touched
[nil
#{(ctk/build-swap-slot-group (str (thi/id :nested-head)))}]}}}}}}]
(t/is (= expected-diff diff))))
(t/testing "file with a swapped copy inside a main with broken slot has no effect since it cannot be distinguished"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head [:name Frame3] @--> [Component :component3] :main3-root
;; NO SWAP SLOT
;; <no-label> [:name Rect3] ---> :main3-child
;;
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :nested-head
;; <no-label> [:name Rect3] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head)
(thc/instantiate-component :component2 :copy2-root :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-shape :nested-head :component3
:propagate-fn #(tho/propagate-component-changes % :component2))
(ths/update-shape :nested-head :touched nil))
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with a two levels nested copy in a main swapped with broken slot should have it repaired"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head1 [:name Frame1] @--> [Component :component1] :main1-root
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; {:main4-root} [:name Frame4] # [Component :component4]
;; :main4-child [:name Rect4]
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :nested-head2 [:name Frame2] @--> [Component :component2] :main2-root
;; :nested-subhead2 [:name Frame4] @--> [Component :component4] :main4-root
;; NO SWAP SLOT
;; <no-label> [:name Rect4] ---> :main4-child
;;
;; :copy2-root [:name Frame3] #--> [Component :component3] :main3-root
;; <no-label> [:name Frame2] @--> [Component :component2] :nested-head2
;; <no-label> [:name Frame4] @--> [Component :component4] :nested-subhead2
;; <no-label> [:name Rect4] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-two-levels-nested-component-with-copy :component1 :main1-root :main1-child
:component2 :main2-root :nested-head1
:component3 :main3-root :nested-head2 :nested-subhead2
:copy2-root)
(tho/add-simple-component :component4 :main4-root :main4-child
:root-params {:name "Frame4"}
:child-params {:name "Rect4"})
(tho/swap-component-in-shape :nested-subhead2 :component4
:propagate-fn #(tho/propagate-component-changes % :component3))
(ths/update-shape :nested-subhead2 :touched nil))
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))
diff (d/map-diff file file')
expected-diff {:data
{:pages-index
{(thf/current-page-id file)
{:objects
{(thi/id :nested-subhead2)
{:touched
[nil
#{(ctk/build-swap-slot-group (str (thi/id :nested-head1)))}]}}}}}}]
(t/is (= expected-diff diff))))
(t/testing "when components are in external libraries, the fix still works 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
;; NO SWAP SLOT
;; <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)
(ths/update-shape :copy2-nested-head :touched nil))
libraries {(:id library1) library1
(:id library2) library2}
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % libraries))
diff (d/map-diff file file')
expected-diff {:data
{:pages-index
{(thf/current-page-id file)
{:objects
{(thi/id :copy2-nested-head)
{:touched
[nil
#{(ctk/build-swap-slot-group (str (thi/id :nested2-head)))}]}}}}}}]
(t/is (= expected-diff diff)))))
(t/deftest test-sync-component-id-with-ref-shape
(t/testing "nil file should return nil"
(let [file nil
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
(t/is (nil? file'))))
(t/testing "empty file should not need any action"
(let [file (thf/sample-file :file1)
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file without components should not need any action"
(let [file
;; :frame1 [:name Frame1]
;; :child1 [:name Rect1]
(-> (thf/sample-file :file1)
(tho/add-frame-with-child :frame1 :shape1))
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with valid normal components should not need any action"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head1 [:name Frame1] @--> [Component :component1] :main1-root
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :nested-head2 [:name Frame2] @--> [Component :component2] :main2-root
;; :nested-subhead2 [:name Frame1] @--> [Component :component1] :nested-head1
;; <no-label> [:name Rect1] ---> <no-label>
;;
;; :copy2-root [:name Frame3] #--> [Component :component3] :main3-root
;; <no-label> [:name Frame2] @--> [Component :component2] :nested-head2
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-subhead2
;; <no-label> [:name Rect1] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-two-levels-nested-component-with-copy :component1 :main1-root :main1-child
:component2 :main2-root :nested-head1
:component3 :main3-root :nested-head2 :nested-subhead2
:copy2-root))
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
#_(thf/dump-file file') ;; Uncomment to debug
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with valid swapped components should not need any action"
(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
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-head
;; <no-label> [:name Rect1] ---> <no-label>
;;
;; :copy3-root [:name Frame2] #--> [Component :component2] :main2-root
;; :copy3-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-with-copy :component1 :main1-root :main1-child
:component2 :main2-root :nested-head
:copy2-root)
(tho/add-simple-component :component3 :main3-root :main3-child
:root-params {:name "Frame3"}
:child-params {:name "Rect3"})
(thc/instantiate-component :component2 :copy3-root :children-labels [:copy3-nested-head])
(tho/swap-component-in-first-child :copy3-root :component3))
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
#_(thf/dump-file file') ;; Uncomment to debug
(t/is (empty? (d/map-diff file file')))))
(t/testing "file with a non swapped copy with broken component id/file should have it repaired"
(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
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame1] @--> [Component <bad>] :nested-head ## <- BAD component-id
;; <no-label> [:name Rect1] ---> <no-label>
;;
;; :copy3-root [:name Frame2] #--> [Component :component2] :main2-root
;; :copy3-nested-head [:name Frame1] @--> [Component <bad>] :nested-head ## <- BAD component-file
;; <no-label> [:name Rect1] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head)
(thc/instantiate-component :component2 :copy2-root :children-labels [:copy2-nested-head])
(thc/instantiate-component :component2 :copy3-root :children-labels [:copy3-nested-head])
(ths/update-shape :copy2-nested-head :component-id (thi/new-id! :some-other-id))
(ths/update-shape :copy3-nested-head :component-file (thi/new-id! :some-other-file)))
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
diff (d/map-diff file file')
expected-diff {:data
{:pages-index
{(thf/current-page-id file)
{:objects
{(thi/id :copy2-nested-head)
{:component-id
[(thi/id :some-other-id) (thi/id :component1)]}
(thi/id :copy3-nested-head)
{:component-file
[(thi/id :some-other-file) (thi/id :file1)]}}}}}}]
#_(ctf/dump-tree file' (thf/current-page-id file') {(:id file') file'} {:show-ids true}) ;; Uncomment to debug
(t/is (= expected-diff diff))))
(t/testing "file with a copy of a swapped main with broken component id/file should have it repaired"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :main3-child [:name Rect3]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head [:name Frame3] @--> [Component :component3] :main3-root
;; {swap-slot :nested-head}
;; <no-label> [:name Rect3] ---> :main3-child
;;
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame3] @--> [Component: <bad>] :nested-head ## <- BAD component-id/file
;; <no-label> [:name Rect3] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head)
(thc/instantiate-component :component2 :copy2-root :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-shape :nested-head :component3
:propagate-fn #(tho/propagate-component-changes % :component2))
(ths/update-shape :copy2-nested-head :component-id (thi/new-id! :some-other-id))
(ths/update-shape :copy2-nested-head :component-file (thi/new-id! :some-other-file)))
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
diff (d/map-diff file file')
expected-diff {:data
{:pages-index
{(thf/current-page-id file)
{:objects
{(thi/id :copy2-nested-head)
{:component-id
[(thi/id :some-other-id) (thi/id :component3)]
:component-file
[(thi/id :some-other-file) (thi/id :file1)]}}}}}}]
#_(ctf/dump-tree file' (thf/current-page-id file') {(:id file') file'} {:show-ids true}) ;; Uncomment to debug
(t/is (= expected-diff diff))))
(t/testing "file with multiple copies of same component should sync all"
(let [file
(-> (thf/sample-file :file1)
(tho/add-simple-component :component1 :frame1 :shape1)
(thc/instantiate-component :component1 :copy1-root :children-labels [:copy1-child])
(thc/instantiate-component :component1 :copy2-root :children-labels [:copy2-child])
(ths/update-shape :copy1-child :component-id (thi/new-id! :wrong-id1))
(ths/update-shape :copy2-child :component-id (thi/new-id! :wrong-id2)))
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
diff (d/map-diff file file')]
;; Both copies should be corrected
(t/is (contains? diff :data))
(t/is (contains? (get-in diff [:data :pages-index]) (thf/current-page-id file)))))
(t/testing "file with a copy root with broken component id/file cannot be repaired. But it's propagated to copies."
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head [:name Frame1] @--> [Component <bad>] :main1-root ## <- BAD component-id/file
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame1] @--> [Component :component1] :nested-head
;; <no-label> [:name Rect1] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-nested-component :component1 :main1-root :main1-child
:component2 :main2-root :nested-head)
(thc/instantiate-component :component2 :copy2-root :children-labels [:copy2-nested-head])
(ths/update-shape :nested-head :component-id (thi/new-id! :some-other-id))
(ths/update-shape :nested-head :component-file (thi/new-id! :some-other-file)))
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
diff (d/map-diff file file')
expected-diff {:data
{:pages-index
{(thf/current-page-id file)
{:objects
{(thi/id :copy2-nested-head)
{:component-id
[(thi/id :component1) (thi/id :some-other-id)]
:component-file
[(thi/id :file1) (thi/id :some-other-file)]}}}}}}]
(t/is (= expected-diff diff))))
(t/testing "file with a 2nd nested copy inside a main with broken component/id should have it repaired, and propagated to copies"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head1 [:name Frame1] @--> [Component :component1] :main1-root
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :nested-head2 [:name Frame2] @--> [Component :component2] :main2-root
;; :nested-subhead2 [:name Frame1] @--> [Component <bad>] :nested-head1 ## <- BAD component-id/file
;; <no-label> [:name Rect1] ---> <no-label>
;;
;; :copy2-root [:name Frame3] #--> [Component :component3] :main3-root
;; <no-label> [:name Frame2] @--> [Component :component2] :nested-head2
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-subhead2
;; <no-label> [:name Rect1] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-two-levels-nested-component-with-copy :component1 :main1-root :main1-child
:component2 :main2-root :nested-head1
:component3 :main3-root :nested-head2 :nested-subhead2
:copy2-root)
(ths/update-shape :nested-subhead2 :component-id (thi/new-id! :some-other-id))
(ths/update-shape :nested-subhead2 :component-file (thi/new-id! :some-other-file)))
copy2-root (ths/get-shape file :copy2-root)
copy2-root-child1 (ths/get-shape-by-id file (first (:shapes copy2-root)))
copy2-root-child2 (ths/get-shape-by-id file (first (:shapes copy2-root-child1)))
file (-> file
(ths/update-shape-by-id (:id copy2-root-child2) :component-id (thi/id :some-other-id))
(ths/update-shape-by-id (:id copy2-root-child2) :component-file (thi/id :some-other-file)))
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
diff (d/map-diff file file')
expected-diff {:data
{:pages-index
{(thf/current-page-id file)
{:objects
{(thi/id :nested-subhead2)
{:component-id
[(thi/id :some-other-id) (thi/id :component1)]
:component-file
[(thi/id :some-other-file) (thi/id :file1)]}
(:id copy2-root-child2)
{:component-id
[(thi/id :some-other-id) (thi/id :component1)]
:component-file
[(thi/id :some-other-file) (thi/id :file1)]}}}}}}]
#_(ctf/dump-tree file' (thf/current-page-id file') {(:id file') file'} {:show-ids true}) ;; Uncomment to debug
(t/is (= expected-diff diff))))
(t/testing "when components are in external libraries, the fix still works well"
(let [library1
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested2-head [:name Frame4] @--> [Component :component4] :main4-root
;; {swap-slot :nested2-head}
;; :nested4-head [:name Frame3] @--> [Component: component3] :main3-root
;; :nested4-child [:name Rect3] ---> :nested4-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]}))
library1
(tho/swap-component-in-shape library1 :nested2-head :component4 :library library2)
file
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
;; :copy2-nested-head [:name Frame4] @--> [Component <bad>] :main4-root ## <- BAD component-id/file
;; <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)
(ths/update-shape :copy2-nested-head :component-id (thi/new-id! :some-other-id))
(ths/update-shape :copy2-nested-head :component-file (thi/new-id! :some-other-file)))
libraries {(:id library1) library1
(:id library2) library2}
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % libraries))
diff (d/map-diff file file')
expected-diff {:data
{:pages-index
{(thf/current-page-id file)
{:objects
{(thi/id :copy2-nested-head)
{:component-id
[(thi/id :some-other-id) (thi/id :component4)]
:component-file
[(thi/id :some-other-file) (thi/id :library2)]}}}}}}]
#_(thf/dump-file library2) ;; Uncomment to debug
(t/is (= expected-diff diff))))
(t/testing "file with several broken ids should propagate to all copies"
(let [file
;; {:main1-root} [:name Frame1] # [Component :component1]
;; :main1-child [:name Rect1]
;;
;; {:main2-root} [:name Frame2] # [Component :component2]
;; :nested-head1 [:name Frame1] @--> [Component :component1] :main1-root
;; <no-label> [:name Rect1] ---> :main1-child
;;
;; {:main3-root} [:name Frame3] # [Component :component3]
;; :nested-head2 [:name Frame2] @--> [Component <bad>] :main2-root ## <- BAD component-id
;; :nested-subhead2 [:name Frame1] @--> [Component <bad>] :nested-head1 ## <- BAD component-id
;; <no-label> [:name Rect1] ---> <no-label>
;;
;; :copy2-root [:name Frame3] #--> [Component :component3] :main3-root
;; <no-label> [:name Frame2] @--> [Component :component2] :nested-head2
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-subhead2
;; <no-label> [:name Rect1] ---> <no-label>
(-> (thf/sample-file :file1)
(tho/add-two-levels-nested-component-with-copy :component1 :main1-root :main1-child
:component2 :main2-root :nested-head1
:component3 :main3-root :nested-head2 :nested-subhead2
:copy2-root)
;; Corrupt both levels
(ths/update-shape :nested-head2 :component-id (thi/new-id! :wrong-comp2))
(ths/update-shape :nested-subhead2 :component-id (thi/new-id! :wrong-comp3)))
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
copy2-root (ths/get-shape file' :copy2-root)
copy2-root-child1 (ths/get-shape-by-id file' (first (:shapes copy2-root)))
copy2-root-child2 (ths/get-shape-by-id file' (first (:shapes copy2-root-child1)))
diff (d/map-diff file file')
expected-diff {:data
{:pages-index
{(thf/current-page-id file)
{:objects
{(:id copy2-root-child1)
{:component-id [(thi/id :component2) (thi/id :wrong-comp2)]}
(:id copy2-root-child2)
{:component-id [(thi/id :component1) (thi/id :wrong-comp3)]}}}}}}]
(thf/dump-file file') ;; Uncomment to debug
(t/is (= expected-diff diff)))))

View File

@ -465,9 +465,10 @@
page page
{(:id file) file} {(:id file) file}
(thi/id :nested-h-ellipse)) (thi/id :nested-h-ellipse))
file' (-> (thf/apply-changes file changes) file' (-> (thf/apply-changes file changes :validate? false)
(tho/propagate-component-changes :c-board-with-ellipse) (tho/propagate-component-changes :c-board-with-ellipse)
(tho/propagate-component-changes :c-big-board)) (tho/propagate-component-changes :c-big-board)
(thf/validate-file!))
;; ==== Get ;; ==== Get
nested2-h-ellipse (ths/get-shape file' :nested-h-ellipse) nested2-h-ellipse (ths/get-shape file' :nested-h-ellipse)

View File

@ -349,4 +349,73 @@
(t/is (= (:fill-color fill') "#FFFFFF")) (t/is (= (:fill-color fill') "#FFFFFF"))
(t/is (= (:fill-opacity fill') 1)) (t/is (= (:fill-opacity fill') 1))
(t/is (= (:touched copy2-root') nil)) (t/is (= (:touched copy2-root') nil))
(t/is (= (:touched copy2-child') nil)))) (t/is (= (:touched copy2-child') nil))))
(t/deftest test-reset-with-propagation-updates-copies
;; When a nested copy inside a main component has an override and we
;; reset it passing a propagate-fn, the reset must be propagated to
;; all copies of that component so they reflect the canonical color.
(let [;; ==== Setup
file
(-> (thf/sample-file :file1)
;; component1: main1-root / main1-child (fill "#aabbcc")
;; component2: main2-root contains nested-head (instance of component1)
;; copy2-root: copy of component2
(tho/add-nested-component-with-copy
:component1 :main1-root :main1-child
:component2 :main2-root :nested-head
:copy2-root
:main1-child-params {:fills (ths/sample-fills-color :fill-color "#aabbcc")}
:copy2-root-params {:children-labels [:copy2-nested-head]}))
propagate-fn (fn [f]
(-> f
(tho/propagate-component-changes :component1)
(tho/propagate-component-changes :component2)))
;; ==== Action override the nested-head color, then reset it with propagation
file'
(-> file
(tho/update-bottom-color :nested-head "#fabada" :propagate-fn propagate-fn)
(tho/reset-overrides (ths/get-shape file :nested-head) :propagate-fn propagate-fn))
;; ==== Get
copy2-bottom-color (tho/bottom-fill-color file' :copy2-root)]
;; ==== Check
;; After reset + propagation the copy should mirror the canonical color
(t/is (= copy2-bottom-color "#aabbcc"))))
(t/deftest test-reset-without-propagation-does-not-update-copies
;; This is the regression test for the misplaced-parenthesis bug: when
;; propagate-fn is NOT passed to reset-overrides the copies of the component
;; must still hold the overridden value because the component sync never ran.
(let [;; ==== Setup
file
(-> (thf/sample-file :file1)
(tho/add-nested-component-with-copy
:component1 :main1-root :main1-child
:component2 :main2-root :nested-head
:copy2-root
:main1-child-params {:fills (ths/sample-fills-color :fill-color "#aabbcc")}
:copy2-root-params {:children-labels [:copy2-nested-head]}))
propagate-fn (fn [f]
(-> f
(tho/propagate-component-changes :component1)
(tho/propagate-component-changes :component2)))
;; ==== Action override the nested-head color, then reset WITHOUT propagation
file'
(-> file
(tho/update-bottom-color :nested-head "#fabada" :propagate-fn propagate-fn)
;; Reset without propagate-fn: the component definition is updated but
;; the change is never pushed to the copy.
(tho/reset-overrides (ths/get-shape file :nested-head)))
;; ==== Get
copy2-bottom-color (tho/bottom-fill-color file' :copy2-root)]
;; ==== Check
;; Without propagation the copy still reflects the overridden color
(t/is (= copy2-bottom-color "#fabada"))))

View File

@ -64,9 +64,8 @@
(reset-all-overrides [file] (reset-all-overrides [file]
(-> file (-> file
(tho/reset-overrides-in-first-child :frame-board-1 :page-label :page-1) (tho/reset-overrides-in-first-child :frame-board-1 :page-label :page-1 :propagate-fn propagate-all-component-changes)
(tho/reset-overrides-in-first-child :copy-board-1 :page-label :page-2) (tho/reset-overrides-in-first-child :copy-board-1 :page-label :page-2 :propagate-fn propagate-all-component-changes)))
(propagate-all-component-changes)))
(fill-colors [file] (fill-colors [file]
[(tho/bottom-fill-color file :frame-ellipse-1 :page-label :page-1) [(tho/bottom-fill-color file :frame-ellipse-1 :page-label :page-1)

View File

@ -6,20 +6,11 @@
(ns common-tests.logic.multiple-nesting-levels-test (ns common-tests.logic.multiple-nesting-levels-test
(:require (:require
[app.common.files.changes :as ch]
[app.common.files.changes-builder :as pcb]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.pprint :as pp]
[app.common.test-helpers.components :as thc] [app.common.test-helpers.components :as thc]
[app.common.test-helpers.compositions :as tho] [app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf] [app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths] [app.common.test-helpers.shapes :as ths]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[clojure.test :as t])) [clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture) (t/use-fixtures :each thi/test-fixture)
@ -56,10 +47,9 @@
(reset-all-overrides [file] (reset-all-overrides [file]
(-> file (-> file
(tho/reset-overrides (ths/get-shape file :copy-simple-1)) (tho/reset-overrides (ths/get-shape file :copy-simple-1) :propagate-fn propagate-all-component-changes)
(tho/reset-overrides (ths/get-shape file :copy-frame-composed-1)) (tho/reset-overrides (ths/get-shape file :copy-frame-composed-1) :propagate-fn propagate-all-component-changes)
(tho/reset-overrides (ths/get-shape file :composed-1-composed-2-copy)) (tho/reset-overrides (ths/get-shape file :composed-1-composed-2-copy) :propagate-fn propagate-all-component-changes)))
(propagate-all-component-changes)))
(fill-colors [file] (fill-colors [file]
[(tho/bottom-fill-color file :frame-simple-1) [(tho/bottom-fill-color file :frame-simple-1)

View File

@ -6,20 +6,12 @@
(ns common-tests.logic.swap-as-override-test (ns common-tests.logic.swap-as-override-test
(:require (:require
[app.common.files.changes :as ch] [app.common.data :as d]
[app.common.files.changes-builder :as pcb]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.pprint :as pp]
[app.common.test-helpers.components :as thc] [app.common.test-helpers.components :as thc]
[app.common.test-helpers.compositions :as tho] [app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf] [app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths] [app.common.test-helpers.shapes :as ths]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[clojure.test :as t])) [clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture) (t/use-fixtures :each thi/test-fixture)
@ -27,23 +19,40 @@
(defn- setup [] (defn- setup []
(-> (thf/sample-file :file1) (-> (thf/sample-file :file1)
(tho/add-simple-component :component-1 :frame-component-1 :child-component-1 :child-params {:name "child-component-1" :type :rect :fills (ths/sample-fills-color :fill-color "#111111")}) (tho/add-simple-component :component-1 :frame-component-1 :child-component-1
(tho/add-simple-component :component-2 :frame-component-2 :child-component-2 :child-params {:name "child-component-2" :type :rect :fills (ths/sample-fills-color :fill-color "#222222")}) :root-params {:name "component-1"}
(tho/add-simple-component :component-3 :frame-component-3 :child-component-3 :child-params {:name "child-component-3" :type :rect :fills (ths/sample-fills-color :fill-color "#333333")}) :child-params {:name "child-component-1"
:type :rect
:fills (ths/sample-fills-color :fill-color "#111111")})
(tho/add-simple-component :component-2 :frame-component-2 :child-component-2
:root-params {:name "component-2"}
:child-params {:name "child-component-2"
:type :rect
:fills (ths/sample-fills-color :fill-color "#222222")})
(tho/add-simple-component :component-3 :frame-component-3 :child-component-3
:root-params {:name "component-3"}
:child-params {:name "child-component-3"
:type :rect
:fills (ths/sample-fills-color :fill-color "#333333")})
(tho/add-frame :frame-icon-and-text) (tho/add-frame :frame-icon-and-text :name "copy-component-1")
(thc/instantiate-component :component-1 :copy-component-1 :parent-label :frame-icon-and-text :children-labels [:component-1-icon-and-text]) (thc/instantiate-component :component-1 :copy-component-1
:parent-label :frame-icon-and-text
:children-labels [:component-1-icon-and-text])
(ths/add-sample-shape :text (ths/add-sample-shape :text
{:type :text {:type :text
:name "icon+text" :name "icon+text"
:parent-label :frame-icon-and-text}) :parent-label :frame-icon-and-text})
(thc/make-component :icon-and-text :frame-icon-and-text) (thc/make-component :icon-and-text :frame-icon-and-text)
(tho/add-frame :frame-panel) (tho/add-frame :frame-panel :name "icon-and-text")
(thc/instantiate-component :icon-and-text :copy-icon-and-text :parent-label :frame-panel :children-labels [:icon-and-text-panel]) (thc/instantiate-component :icon-and-text :copy-icon-and-text
:parent-label :frame-panel
:children-labels [:icon-and-text-panel])
(thc/make-component :panel :frame-panel) (thc/make-component :panel :frame-panel)
(thc/instantiate-component :panel :copy-panel :children-labels [:copy-icon-and-text-panel]))) (thc/instantiate-component :panel :copy-panel
:children-labels [:copy-icon-and-text-panel])))
(defn- propagate-all-component-changes [file] (defn- propagate-all-component-changes [file]
(-> file (-> file

View File

@ -30,7 +30,7 @@
copy (ths/get-shape file :copy01) copy (ths/get-shape file :copy01)
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy :circle {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :circle {:new-shape-label :copy02 :keep-touched? true})
copy' (ths/get-shape file' :copy02)] copy' (ths/get-shape file' :copy02)]
;; Both copies have the same id ;; Both copies have the same id

View File

@ -35,7 +35,7 @@
copy01 (ths/get-shape file :copy01) copy01 (ths/get-shape file :copy01)
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
copy01' (ths/get-shape file' :copy02)] copy01' (ths/get-shape file' :copy02)]
(thf/dump-file file :keys [:width]) (thf/dump-file file :keys [:width])
@ -61,7 +61,7 @@
rect01 (get-in page [:objects (-> copy01 :shapes first)]) rect01 (get-in page [:objects (-> copy01 :shapes first)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -100,7 +100,7 @@
copy01 (ths/get-shape file :copy01) copy01 (ths/get-shape file :copy01)
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
copy01' (ths/get-shape file' :copy02)] copy01' (ths/get-shape file' :copy02)]
(thf/dump-file file :keys [:width]) (thf/dump-file file :keys [:width])
@ -137,7 +137,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -180,7 +180,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -257,25 +257,19 @@
;; The copy clean has no overrides ;; The copy clean has no overrides
copy-clean (ths/get-shape file :copy-clean)
copy-clean-t (ths/get-shape file :copy-clean-t) copy-clean-t (ths/get-shape file :copy-clean-t)
;; Override font size on copy-font-size ;; Override font size on copy-font-size
file (update-attr file :copy-font-size-t font-size-path-0 "25") file (update-attr file :copy-font-size-t font-size-path-0 "25")
copy-font-size (ths/get-shape file :copy-font-size)
copy-font-size-t (ths/get-shape file :copy-font-size-t) copy-font-size-t (ths/get-shape file :copy-font-size-t)
;; Override text on copy-text ;; Override text on copy-text
file (update-attr file :copy-text-t text-path-0 "text overriden") file (update-attr file :copy-text-t text-path-0 "text overriden")
copy-text (ths/get-shape file :copy-text)
copy-text-t (ths/get-shape file :copy-text-t) copy-text-t (ths/get-shape file :copy-text-t)
;; Override both on copy-both ;; Override both on copy-both
file (update-attr file :copy-both-t font-size-path-0 "25") file (update-attr file :copy-both-t font-size-path-0 "25")
file (update-attr file :copy-both-t text-path-0 "text overriden") file (update-attr file :copy-both-t text-path-0 "text overriden")
copy-both (ths/get-shape file :copy-both)
copy-both-t (ths/get-shape file :copy-both-t) copy-both-t (ths/get-shape file :copy-both-t)
@ -283,10 +277,10 @@
file' (-> file file' (-> file
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
(tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
(tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true})) (tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
page' (thf/current-page file') page' (thf/current-page file')
copy-clean' (ths/get-shape file' :copy-clean-2) copy-clean' (ths/get-shape file' :copy-clean-2)
copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)]) copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
@ -387,25 +381,19 @@
;; The copy clean has no overrides ;; The copy clean has no overrides
copy-clean (ths/get-shape file :copy-clean)
copy-clean-t (ths/get-shape file :copy-clean-t) copy-clean-t (ths/get-shape file :copy-clean-t)
;; Override font size on copy-font-size ;; Override font size on copy-font-size
file (update-attr file :copy-font-size-t font-size-path-0 "25") file (update-attr file :copy-font-size-t font-size-path-0 "25")
copy-font-size (ths/get-shape file :copy-font-size)
copy-font-size-t (ths/get-shape file :copy-font-size-t) copy-font-size-t (ths/get-shape file :copy-font-size-t)
;; Override text on copy-text ;; Override text on copy-text
file (update-attr file :copy-text-t text-path-0 "text overriden") file (update-attr file :copy-text-t text-path-0 "text overriden")
copy-text (ths/get-shape file :copy-text)
copy-text-t (ths/get-shape file :copy-text-t) copy-text-t (ths/get-shape file :copy-text-t)
;; Override both on copy-both ;; Override both on copy-both
file (update-attr file :copy-both-t font-size-path-0 "25") file (update-attr file :copy-both-t font-size-path-0 "25")
file (update-attr file :copy-both-t text-path-0 "text overriden") file (update-attr file :copy-both-t text-path-0 "text overriden")
copy-both (ths/get-shape file :copy-both)
copy-both-t (ths/get-shape file :copy-both-t) copy-both-t (ths/get-shape file :copy-both-t)
@ -413,10 +401,10 @@
file' (-> file file' (-> file
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
(tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
(tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true})) (tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
page' (thf/current-page file') page' (thf/current-page file')
copy-clean' (ths/get-shape file' :copy-clean-2) copy-clean' (ths/get-shape file' :copy-clean-2)
copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)]) copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
@ -515,25 +503,19 @@
;; The copy clean has no overrides ;; The copy clean has no overrides
copy-clean (ths/get-shape file :copy-clean)
copy-clean-t (ths/get-shape file :copy-clean-t) copy-clean-t (ths/get-shape file :copy-clean-t)
;; Override font size on copy-font-size ;; Override font size on copy-font-size
file (update-attr file :copy-font-size-t font-size-path-0 "25") file (update-attr file :copy-font-size-t font-size-path-0 "25")
copy-font-size (ths/get-shape file :copy-font-size)
copy-font-size-t (ths/get-shape file :copy-font-size-t) copy-font-size-t (ths/get-shape file :copy-font-size-t)
;; Override text on copy-text ;; Override text on copy-text
file (update-attr file :copy-text-t text-path-0 "text overriden") file (update-attr file :copy-text-t text-path-0 "text overriden")
copy-text (ths/get-shape file :copy-text)
copy-text-t (ths/get-shape file :copy-text-t) copy-text-t (ths/get-shape file :copy-text-t)
;; Override both on copy-both ;; Override both on copy-both
file (update-attr file :copy-both-t font-size-path-0 "25") file (update-attr file :copy-both-t font-size-path-0 "25")
file (update-attr file :copy-both-t text-path-0 "text overriden") file (update-attr file :copy-both-t text-path-0 "text overriden")
copy-both (ths/get-shape file :copy-both)
copy-both-t (ths/get-shape file :copy-both-t) copy-both-t (ths/get-shape file :copy-both-t)
@ -541,10 +523,10 @@
file' (-> file file' (-> file
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
(tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
(tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true})) (tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
page' (thf/current-page file') page' (thf/current-page file')
copy-clean' (ths/get-shape file' :copy-clean-2) copy-clean' (ths/get-shape file' :copy-clean-2)
copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)]) copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
@ -645,25 +627,19 @@
;; The copy clean has no overrides ;; The copy clean has no overrides
copy-clean (ths/get-shape file :copy-clean)
copy-clean-t (ths/get-shape file :copy-clean-t) copy-clean-t (ths/get-shape file :copy-clean-t)
;; Override font size on copy-font-size ;; Override font size on copy-font-size
file (update-attr file :copy-font-size-t font-size-path-0 "25") file (update-attr file :copy-font-size-t font-size-path-0 "25")
copy-font-size (ths/get-shape file :copy-font-size)
copy-font-size-t (ths/get-shape file :copy-font-size-t) copy-font-size-t (ths/get-shape file :copy-font-size-t)
;; Override text on copy-text ;; Override text on copy-text
file (update-attr file :copy-text-t text-path-0 "text overriden") file (update-attr file :copy-text-t text-path-0 "text overriden")
copy-text (ths/get-shape file :copy-text)
copy-text-t (ths/get-shape file :copy-text-t) copy-text-t (ths/get-shape file :copy-text-t)
;; Override both on copy-both ;; Override both on copy-both
file (update-attr file :copy-both-t font-size-path-0 "25") file (update-attr file :copy-both-t font-size-path-0 "25")
file (update-attr file :copy-both-t text-path-0 "text overriden") file (update-attr file :copy-both-t text-path-0 "text overriden")
copy-both (ths/get-shape file :copy-both)
copy-both-t (ths/get-shape file :copy-both-t) copy-both-t (ths/get-shape file :copy-both-t)
@ -671,10 +647,10 @@
file' (-> file file' (-> file
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
(tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
(tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true})) (tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
page' (thf/current-page file') page' (thf/current-page file')
copy-clean' (ths/get-shape file' :copy-clean-2) copy-clean' (ths/get-shape file' :copy-clean-2)
copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)]) copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
@ -774,14 +750,12 @@
file (change-structure file :copy-structure-clean-t) file (change-structure file :copy-structure-clean-t)
copy-structure-clean (ths/get-shape file :copy-structure-clean)
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t) copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
;; Duplicate a text line in copy-structure-clean, updating ;; Duplicate a text line in copy-structure-clean, updating
;; both lines with the same attrs ;; both lines with the same attrs
file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25") file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
(change-structure :copy-structure-unif-t)) (change-structure :copy-structure-unif-t))
copy-structure-unif (ths/get-shape file :copy-structure-unif)
copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t) copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
;; Duplicate a text line in copy-structure-clean, updating ;; Duplicate a text line in copy-structure-clean, updating
@ -789,7 +763,6 @@
file (-> (change-structure file :copy-structure-mixed-t) file (-> (change-structure file :copy-structure-mixed-t)
(update-attr :copy-structure-mixed-t font-size-path-0 "35") (update-attr :copy-structure-mixed-t font-size-path-0 "35")
(update-attr :copy-structure-mixed-t font-size-path-1 "40")) (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t) copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
@ -797,9 +770,9 @@
file' (-> file file' (-> file
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
(tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true})) (tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
page' (thf/current-page file') page' (thf/current-page file')
copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2) copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)]) copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
@ -908,14 +881,12 @@
file (change-structure file :copy-structure-clean-t) file (change-structure file :copy-structure-clean-t)
copy-structure-clean (ths/get-shape file :copy-structure-clean)
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t) copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
;; Duplicate a text line in copy-structure-clean, updating ;; Duplicate a text line in copy-structure-clean, updating
;; both lines with the same attrs ;; both lines with the same attrs
file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25") file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
(change-structure :copy-structure-unif-t)) (change-structure :copy-structure-unif-t))
copy-structure-unif (ths/get-shape file :copy-structure-unif)
copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t) copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
;; Duplicate a text line in copy-structure-clean, updating ;; Duplicate a text line in copy-structure-clean, updating
@ -923,7 +894,6 @@
file (-> (change-structure file :copy-structure-mixed-t) file (-> (change-structure file :copy-structure-mixed-t)
(update-attr :copy-structure-mixed-t font-size-path-0 "35") (update-attr :copy-structure-mixed-t font-size-path-0 "35")
(update-attr :copy-structure-mixed-t font-size-path-1 "40")) (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t) copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
@ -931,9 +901,9 @@
file' (-> file file' (-> file
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
(tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true})) (tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
page' (thf/current-page file') page' (thf/current-page file')
copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2) copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)]) copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
@ -1038,14 +1008,12 @@
file (change-structure file :copy-structure-clean-t) file (change-structure file :copy-structure-clean-t)
copy-structure-clean (ths/get-shape file :copy-structure-clean)
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t) copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
;; Duplicate a text line in copy-structure-clean, updating ;; Duplicate a text line in copy-structure-clean, updating
;; both lines with the same attrs ;; both lines with the same attrs
file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25") file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
(change-structure :copy-structure-unif-t)) (change-structure :copy-structure-unif-t))
copy-structure-unif (ths/get-shape file :copy-structure-unif)
copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t) copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
;; Duplicate a text line in copy-structure-clean, updating ;; Duplicate a text line in copy-structure-clean, updating
@ -1053,7 +1021,6 @@
file (-> (change-structure file :copy-structure-mixed-t) file (-> (change-structure file :copy-structure-mixed-t)
(update-attr :copy-structure-mixed-t font-size-path-0 "35") (update-attr :copy-structure-mixed-t font-size-path-0 "35")
(update-attr :copy-structure-mixed-t font-size-path-1 "40")) (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t) copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
@ -1061,9 +1028,9 @@
file' (-> file file' (-> file
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
(tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true})) (tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
page' (thf/current-page file') page' (thf/current-page file')
copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2) copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)]) copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
@ -1169,14 +1136,12 @@
file (change-structure file :copy-structure-clean-t) file (change-structure file :copy-structure-clean-t)
copy-structure-clean (ths/get-shape file :copy-structure-clean)
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t) copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
;; Duplicate a text line in copy-structure-clean, updating ;; Duplicate a text line in copy-structure-clean, updating
;; both lines with the same attrs ;; both lines with the same attrs
file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25") file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
(change-structure :copy-structure-unif-t)) (change-structure :copy-structure-unif-t))
copy-structure-unif (ths/get-shape file :copy-structure-unif)
copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t) copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
;; Duplicate a text line in copy-structure-clean, updating ;; Duplicate a text line in copy-structure-clean, updating
@ -1184,7 +1149,6 @@
file (-> (change-structure file :copy-structure-mixed-t) file (-> (change-structure file :copy-structure-mixed-t)
(update-attr :copy-structure-mixed-t font-size-path-0 "35") (update-attr :copy-structure-mixed-t font-size-path-0 "35")
(update-attr :copy-structure-mixed-t font-size-path-1 "40")) (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t) copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
@ -1192,9 +1156,9 @@
file' (-> file file' (-> file
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true}) (tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
(tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true})) (tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
page' (thf/current-page file') page' (thf/current-page file')
copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2) copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)]) copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
@ -1290,7 +1254,6 @@
:children-labels [:copy-cp01])) :children-labels [:copy-cp01]))
page (thf/current-page file) page (thf/current-page file)
copy01 (ths/get-shape file :copy01)
copy-cp01 (ths/get-shape file :copy-cp01) copy-cp01 (ths/get-shape file :copy-cp01)
copy-cp01-rect-id (-> copy-cp01 :shapes first) copy-cp01-rect-id (-> copy-cp01 :shapes first)
@ -1309,7 +1272,7 @@
;; ==== Action ;; ==== Action
;; Switch :c01 for :c02 ;; Switch :c01 for :c02
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
copy02 (ths/get-shape file' :copy02) copy02 (ths/get-shape file' :copy02)
copy-cp02' (ths/get-shape-by-id file' (-> copy02 :shapes first)) copy-cp02' (ths/get-shape-by-id file' (-> copy02 :shapes first))
copy-cp02-rect' (ths/get-shape-by-id file' (-> copy-cp02' :shapes first))] copy-cp02-rect' (ths/get-shape-by-id file' (-> copy-cp02' :shapes first))]
@ -1337,17 +1300,16 @@
:children-labels [:copy-cp01])) :children-labels [:copy-cp01]))
copy01 (ths/get-shape file :copy01) copy01 (ths/get-shape file :copy01)
copy-cp01 (ths/get-shape file :copy-cp01)
external02 (thc/get-component file :external02) external02 (thc/get-component file :external02)
;; On :c01, swap the copy of :external01 for a copy of :external02 ;; On :c01, swap the copy of :external01 for a copy of :external02
file (-> file file (-> file
(tho/swap-component copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false})) (tho/swap-component-in-shape :copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false}))
copy-cp02 (ths/get-shape file :copy-cp02) copy-cp02 (ths/get-shape file :copy-cp02)
;; ==== Action ;; ==== Action
;; Switch :c01 for :c02 ;; Switch :c01 for :c02
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
copy-cp02' (ths/get-shape file' :copy-cp02)] copy-cp02' (ths/get-shape file' :copy-cp02)]
@ -1376,12 +1338,11 @@
page (thf/current-page file) page (thf/current-page file)
copy01 (ths/get-shape file :copy01) copy01 (ths/get-shape file :copy01)
copy-cp01 (ths/get-shape file :copy-cp01)
external02 (thc/get-component file :external02) external02 (thc/get-component file :external02)
;; On :c01, swap the copy of :external01 for a copy of :external02 ;; On :c01, swap the copy of :external01 for a copy of :external02
file (-> file file (-> file
(tho/swap-component copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false})) (tho/swap-component-in-shape :copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false}))
copy-cp02 (ths/get-shape file :copy-cp02) copy-cp02 (ths/get-shape file :copy-cp02)
copy-cp02-rect-id (-> copy-cp02 :shapes first) copy-cp02-rect-id (-> copy-cp02 :shapes first)
@ -1396,7 +1357,7 @@
;; ==== Action ;; ==== Action
;; Switch :c01 for :c02 ;; Switch :c01 for :c02
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
copy-cp02' (ths/get-shape file' :copy-cp02) copy-cp02' (ths/get-shape file' :copy-cp02)
@ -1463,7 +1424,7 @@
;; ==== Action ;; ==== Action
file' (tho/swap-component file c01-in-copy :c02 {:new-shape-label :c02-in-copy :keep-touched? true}) file' (tho/swap-component-in-shape file :c01-in-copy :c02 {:new-shape-label :c02-in-copy :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
c02-in-copy' (ths/get-shape file' :c02-in-copy) c02-in-copy' (ths/get-shape file' :c02-in-copy)
@ -1515,7 +1476,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -1564,7 +1525,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -1613,7 +1574,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -1660,7 +1621,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -1714,7 +1675,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -1763,7 +1724,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -1812,7 +1773,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -1859,7 +1820,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -1910,7 +1871,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -1956,7 +1917,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2023,7 +1984,7 @@
text01 (get-in page [:objects (:id text01)]) text01 (get-in page [:objects (:id text01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2055,7 +2016,7 @@
rect01 (get-in page [:objects (-> copy01 :shapes first)]) rect01 (get-in page [:objects (-> copy01 :shapes first)])
;; ==== Action - Try to switch to a component with different shape type ;; ==== Action - Try to switch to a component with different shape type
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2098,7 +2059,7 @@
path01 (get-in page [:objects (:id path01)]) path01 (get-in page [:objects (:id path01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2146,7 +2107,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2190,7 +2151,7 @@
rect01 (get-in page [:objects (-> copy01 :shapes first)]) rect01 (get-in page [:objects (-> copy01 :shapes first)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2243,7 +2204,7 @@
old-position-data (:position-data text01) old-position-data (:position-data text01)
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2306,7 +2267,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2357,7 +2318,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2411,7 +2372,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2468,7 +2429,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2532,7 +2493,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2588,7 +2549,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2653,7 +2614,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)
@ -2710,7 +2671,7 @@
rect01 (get-in page [:objects (:id rect01)]) rect01 (get-in page [:objects (:id rect01)])
;; ==== Action ;; ==== Action
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
page' (thf/current-page file') page' (thf/current-page file')
copy02' (ths/get-shape file' :copy02) copy02' (ths/get-shape file' :copy02)

View File

@ -6,9 +6,13 @@
(ns common-tests.types.components-test (ns common-tests.types.components-test
(:require (: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.ids-map :as thi]
[app.common.test-helpers.shapes :as ths] [app.common.test-helpers.shapes :as ths]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.file :as ctf]
[clojure.test :as t])) [clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture) (t/use-fixtures :each thi/test-fixture)
@ -39,3 +43,357 @@
(t/is (= (ctk/get-swap-slot s4) #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 (= (ctk/get-swap-slot s5) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
(t/is (nil? (ctk/get-swap-slot s6))))) (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))
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))
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))
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)]
(thf/dump-file library1 :keys [:name :swap-slot-label] :show-refs? true)
(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

@ -23,10 +23,11 @@
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module ;; Change this to :info :debug or :trace to debug this module
(log/set-level! :info) (log/set-level! :warn)
(def page-change? (def page-change?
#{:add-page :mod-page :del-page :mov-page}) #{:add-page :mod-page :del-page :mov-page})
(def update-layout-attr? (def update-layout-attr?
#{:hidden}) #{:hidden})
@ -207,6 +208,7 @@
;; Prevent commit changes by a viewer team member (it really should never happen) ;; Prevent commit changes by a viewer team member (it really should never happen)
(when (:can-edit permissions) (when (:can-edit permissions)
(log/trace :hint "commit-changes" :redo-changes redo-changes)
(let [selected (dm/get-in state [:workspace-local :selected])] (let [selected (dm/get-in state [:workspace-local :selected])]
(rx/of (-> params (rx/of (-> params
(assoc :undo-group undo-group) (assoc :undo-group undo-group)