mirror of
https://github.com/penpot/penpot.git
synced 2025-08-03 19:28:27 +02:00
Merge remote-tracking branch 'penpot/develop' into token-studio-develop
This commit is contained in:
commit
dc14933f3a
116 changed files with 7413 additions and 6245 deletions
|
@ -5,7 +5,7 @@
|
|||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.0.2",
|
||||
"packageManager": "yarn@4.2.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
(and add-container? (nil? component-id))
|
||||
(assoc :page-id (:current-page-id file)
|
||||
:frame-id (:current-frame-id file)))
|
||||
|
||||
valid? (ch/check-change! change)]
|
||||
|
||||
(when-not valid?
|
||||
|
@ -135,13 +136,8 @@
|
|||
(create-file (uuid/next) name))
|
||||
|
||||
([id name]
|
||||
{:id id
|
||||
:name name
|
||||
:data (-> ctf/empty-file-data
|
||||
(assoc :id id))
|
||||
|
||||
;; We keep the changes so we can send them to the backend
|
||||
:changes []}))
|
||||
(-> (ctf/make-file {:id id :name name :create-page false})
|
||||
(assoc :changes [])))) ;; We keep the changes so we can send them to the backend
|
||||
|
||||
(defn add-page
|
||||
[file data]
|
||||
|
@ -511,9 +507,12 @@
|
|||
{:type :del-media
|
||||
:id id}))))
|
||||
|
||||
|
||||
(defn start-component
|
||||
([file data] (start-component file data :group))
|
||||
([file data]
|
||||
(let [components-v2 (dm/get-in file [:data :options :components-v2])
|
||||
root-type (if components-v2 :frame :group)]
|
||||
(start-component file data root-type)))
|
||||
|
||||
([file data root-type]
|
||||
;; FIXME: data probably can be a shape instance, then we can use gsh/shape->rect
|
||||
(let [selrect (or (grc/make-rect (:x data) (:y data) (:width data) (:height data))
|
||||
|
@ -566,9 +565,11 @@
|
|||
|
||||
file
|
||||
(cond
|
||||
;; Components-v2 component we skip this step
|
||||
;; In components-v2 components haven't any shape inside them.
|
||||
(and component-data (:main-instance-id component-data))
|
||||
file
|
||||
(update file :data
|
||||
(fn [data]
|
||||
(ctkl/update-component data component-id dissoc :objects)))
|
||||
|
||||
(empty? children)
|
||||
(commit-change
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
|
||||
(ns app.common.files.defaults)
|
||||
|
||||
(def version 46)
|
||||
(def version 47)
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.common.svg :as csvg]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.uuid :as uuid]
|
||||
|
@ -898,6 +900,29 @@
|
|||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
|
||||
(defn migrate-up-47
|
||||
[data]
|
||||
(letfn [(fix-shape [page shape]
|
||||
(let [file {:id (:id data) :data data}
|
||||
component-file (:component-file shape)
|
||||
;; On cloning a file, the component-file of the shapes point to the old file id
|
||||
;; this is a workaround to be able to found the components in that case
|
||||
libraries {component-file {:id component-file :data data}}
|
||||
ref-shape (ctf/find-ref-shape file page libraries shape {:include-deleted? true :with-context? true})
|
||||
ref-parent (get (:objects (:container (meta ref-shape))) (:parent-id ref-shape))
|
||||
shape-swap-slot (ctk/get-swap-slot shape)
|
||||
ref-swap-slot (ctk/get-swap-slot ref-shape)]
|
||||
(if (and (some? shape-swap-slot)
|
||||
(= shape-swap-slot ref-swap-slot)
|
||||
(ctk/main-instance? ref-parent))
|
||||
(ctk/remove-swap-slot shape)
|
||||
shape)))
|
||||
|
||||
(update-page [page]
|
||||
(d/update-when page :objects update-vals (partial fix-shape page)))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-page))))
|
||||
|
||||
(def migrations
|
||||
"A vector of all applicable migrations"
|
||||
[{:id 2 :migrate-up migrate-up-2}
|
||||
|
@ -935,4 +960,5 @@
|
|||
{:id 43 :migrate-up migrate-up-43}
|
||||
{:id 44 :migrate-up migrate-up-44}
|
||||
{:id 45 :migrate-up migrate-up-45}
|
||||
{:id 46 :migrate-up migrate-up-46}])
|
||||
{:id 46 :migrate-up migrate-up-46}
|
||||
{:id 47 :migrate-up migrate-up-47}])
|
||||
|
|
|
@ -460,6 +460,21 @@
|
|||
(pcb/with-library-data file-data)
|
||||
(pcb/update-component (:id shape) repair-component))))
|
||||
|
||||
(defmethod repair-error :missing-slot
|
||||
[_ {:keys [shape page-id args] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
;; Set the desired swap slot
|
||||
(let [slot (:swap-slot args)]
|
||||
(when (some? slot)
|
||||
(log/debug :hint (str " -> set swap-slot to " slot))
|
||||
(update shape :touched cfh/set-touched-group (ctk/build-swap-slot-group slot)))))]
|
||||
|
||||
(log/dbg :hint "repairing shape :missing-slot" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :default
|
||||
[_ error file _]
|
||||
(log/error :hint "Unknown error code, don't know how to repair" :code (:code error))
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.common.files.validate
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.files.helpers :as cfh]
|
||||
|
@ -50,7 +51,8 @@
|
|||
:not-head-copy-not-allowed
|
||||
:not-component-not-allowed
|
||||
:component-nil-objects-not-allowed
|
||||
:instance-head-not-frame})
|
||||
:instance-head-not-frame
|
||||
:missing-slot})
|
||||
|
||||
(def ^:private
|
||||
schema:error
|
||||
|
@ -454,6 +456,8 @@
|
|||
;; PUBLIC API: VALIDATION FUNCTIONS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(declare check-swap-slots)
|
||||
|
||||
(defn validate-file
|
||||
"Validate full referential integrity and semantic coherence on file data.
|
||||
|
||||
|
@ -464,6 +468,8 @@
|
|||
|
||||
(doseq [page (filter :id (ctpl/pages-seq data))]
|
||||
(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)
|
||||
(run! #(check-shape % file page libraries))))
|
||||
|
||||
|
@ -517,3 +523,41 @@
|
|||
:hint "error on validating file referential integrity"
|
||||
:file-id (:id file)
|
||||
: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)))))))))
|
||||
|
|
|
@ -122,12 +122,12 @@
|
|||
#(ctst/add-shape (:id shape)
|
||||
shape
|
||||
%
|
||||
(:parent-id shape)
|
||||
(:frame-id shape)
|
||||
(:parent-id shape)
|
||||
nil
|
||||
true)))
|
||||
$
|
||||
(remove #(= (:id %) (:did copy-root')) copy-shapes)))))]
|
||||
(remove #(= (:id %) (:id copy-root')) copy-shapes)))))]
|
||||
(when children-labels
|
||||
(dotimes [idx (count children-labels)]
|
||||
(set-child-label file' copy-root-label idx (nth children-labels idx))))
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
|
@ -20,7 +21,7 @@
|
|||
(defn add-rect
|
||||
[file rect-label & {:keys [] :as params}]
|
||||
;; Generated shape tree:
|
||||
;; :rect-label [:type :rect :name: Rect1]
|
||||
;; :rect-label [:type :rect :name Rect1]
|
||||
(ths/add-sample-shape file rect-label
|
||||
(merge {:type :rect
|
||||
:name "Rect1"}
|
||||
|
@ -29,17 +30,26 @@
|
|||
(defn add-frame
|
||||
[file frame-label & {:keys [] :as params}]
|
||||
;; Generated shape tree:
|
||||
;; :frame-label [:type :frame :name: Frame1]
|
||||
;; :frame-label [:type :frame :name Frame1]
|
||||
(ths/add-sample-shape file frame-label
|
||||
(merge {:type :frame
|
||||
:name "Frame1"}
|
||||
params)))
|
||||
|
||||
(defn add-group
|
||||
[file group-label & {:keys [] :as params}]
|
||||
;; Generated shape tree:
|
||||
;; :group-label [:type :group :name Group1]
|
||||
(ths/add-sample-shape file group-label
|
||||
(merge {:type :group
|
||||
:name "Group1"}
|
||||
params)))
|
||||
|
||||
(defn add-frame-with-child
|
||||
[file frame-label child-label & {:keys [frame-params child-params]}]
|
||||
;; Generated shape tree:
|
||||
;; :frame-label [:name: Frame1]
|
||||
;; :child-label [:name: Rect1]
|
||||
;; :frame-label [:name Frame1]
|
||||
;; :child-label [:name Rect1]
|
||||
(-> file
|
||||
(add-frame frame-label frame-params)
|
||||
(ths/add-sample-shape child-label
|
||||
|
@ -52,8 +62,8 @@
|
|||
[file component-label root-label child-label
|
||||
& {:keys [component-params root-params child-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:root-label} [:name: Frame1] # [Component :component-label]
|
||||
;; :child-label [:name: Rect1]
|
||||
;; {:root-label} [:name Frame1] # [Component :component-label]
|
||||
;; :child-label [:name Rect1]
|
||||
(-> file
|
||||
(add-frame-with-child root-label child-label :frame-params root-params :child-params child-params)
|
||||
(thc/make-component component-label root-label component-params)))
|
||||
|
@ -62,11 +72,11 @@
|
|||
[file component-label main-root-label main-child-label copy-root-label
|
||||
& {:keys [component-params main-root-params main-child-params copy-root-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:main-root-label} [:name: Frame1] # [Component :component-label]
|
||||
;; :main-child-label [:name: Rect1]
|
||||
;; {:main-root-label} [:name Frame1] # [Component :component-label]
|
||||
;; :main-child-label [:name Rect1]
|
||||
;;
|
||||
;; :copy-root-label [:name: Frame1] #--> [Component :component-label] :main-root-label
|
||||
;; <no-label> [:name: Rect1] ---> :main-child-label
|
||||
;; :copy-root-label [:name Frame1] #--> [Component :component-label] :main-root-label
|
||||
;; <no-label> [:name Rect1] ---> :main-child-label
|
||||
(-> file
|
||||
(add-simple-component component-label
|
||||
main-root-label
|
||||
|
@ -80,10 +90,10 @@
|
|||
[file component-label root-label child-labels
|
||||
& {:keys [component-params root-params child-params-list]}]
|
||||
;; Generated shape tree:
|
||||
;; {:root-label} [:name: Frame1] # [Component :component-label]
|
||||
;; :child1-label [:name: Rect1]
|
||||
;; :child2-label [:name: Rect2]
|
||||
;; :child3-label [:name: Rect3]
|
||||
;; {:root-label} [:name Frame1] # [Component :component-label]
|
||||
;; :child1-label [:name Rect1]
|
||||
;; :child2-label [:name Rect2]
|
||||
;; :child3-label [:name Rect3]
|
||||
(as-> file $
|
||||
(add-frame $ root-label root-params)
|
||||
(reduce (fn [file [index [label params]]]
|
||||
|
@ -101,15 +111,15 @@
|
|||
[file component-label main-root-label main-child-labels copy-root-label
|
||||
& {:keys [component-params main-root-params main-child-params-list copy-root-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:root-label} [:name: Frame1] # [Component :component-label]
|
||||
;; :child1-label [:name: Rect1]
|
||||
;; :child2-label [:name: Rect2]
|
||||
;; :child3-label [:name: Rect3]
|
||||
;; {:root-label} [:name Frame1] # [Component :component-label]
|
||||
;; :child1-label [:name Rect1]
|
||||
;; :child2-label [:name Rect2]
|
||||
;; :child3-label [:name Rect3]
|
||||
;;
|
||||
;; :copy-root-label [:name: Frame1] #--> [Component :component-label] :root-label
|
||||
;; <no-label> [:name: Rect1] ---> :child1-label
|
||||
;; <no-label> [:name: Rect2] ---> :child2-label
|
||||
;; <no-label> [:name: Rect3] ---> :child3-label
|
||||
;; :copy-root-label [:name Frame1] #--> [Component :component-label] :root-label
|
||||
;; <no-label> [:name Rect1] ---> :child1-label
|
||||
;; <no-label> [:name Rect2] ---> :child2-label
|
||||
;; <no-label> [:name Rect3] ---> :child3-label
|
||||
(-> file
|
||||
(add-component-with-many-children component-label
|
||||
main-root-label
|
||||
|
@ -123,12 +133,12 @@
|
|||
[file 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:
|
||||
;; {:main1-root-label} [:name: Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name: Rect1]
|
||||
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root-label} [:name: Frame2] # [Component :component2-label]
|
||||
;; :nested-head-label [:name: Frame1] @--> [Component :component1-label] :main1-root-label
|
||||
;; <no-label> [:name: Rect1] ---> :main1-child-label
|
||||
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
|
||||
;; :nested-head-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
|
||||
|
@ -150,16 +160,16 @@
|
|||
[file 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:
|
||||
;; {:main1-root-label} [:name: Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name: Rect1]
|
||||
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root-label} [:name: Frame2] # [Component :component2-label]
|
||||
;; :nested-head-label [:name: Frame1] @--> [Component :component1-label] :main1-root-label
|
||||
;; <no-label> [:name: Rect1] ---> :main1-child-label
|
||||
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
|
||||
;; :nested-head-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
|
||||
;; <no-label> [:name Rect1] ---> :main1-child-label
|
||||
;;
|
||||
;; :copy2-label [:name: Frame2] #--> [Component :component2-label] :main2-root-label
|
||||
;; <no-label> [:name: Frame1] @--> [Component :component1-label] :nested-head-label
|
||||
;; <no-label> [:name: Rect1] ---> <no-label>
|
||||
;; :copy2-label [:name Frame2] #--> [Component :component2-label] :main2-root-label
|
||||
;; <no-label> [:name Frame1] @--> [Component :component1-label] :nested-head-label
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
(-> file
|
||||
(add-nested-component component1-label
|
||||
main1-root-label
|
||||
|
@ -334,3 +344,25 @@
|
|||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
file')))
|
||||
|
||||
(defn duplicate-shape [file shape-tag & {:keys [page-label propagate-fn]}]
|
||||
(let [page (if page-label
|
||||
(thf/get-page file page-label)
|
||||
(thf/current-page file))
|
||||
shape (ths/get-shape file shape-tag :page-label page-label)
|
||||
changes
|
||||
(-> (pcb/empty-changes nil)
|
||||
(cll/generate-duplicate-changes (:objects page) ;; objects
|
||||
page ;; page
|
||||
#{(:id shape)} ;; ids
|
||||
(gpt/point 0 0) ;; delta
|
||||
{(:id file) file} ;; libraries
|
||||
(:data file) ;; library-data
|
||||
(:id file)) ;; file-id
|
||||
(cll/generate-duplicate-changes-update-indices (:objects page) ;; objects
|
||||
#{(:id shape)}))
|
||||
file' (thf/apply-changes file changes)]
|
||||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
file')))
|
||||
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
;; ---- Helpers to manage ids as known identifiers
|
||||
|
||||
(def ^:private idmap (atom {}))
|
||||
(def ^:private next-uuid-val (atom 1))
|
||||
|
||||
(defn reset-idmap! []
|
||||
(reset! idmap {}))
|
||||
(reset! idmap {})
|
||||
(reset! next-uuid-val 1))
|
||||
|
||||
(defn set-id!
|
||||
[label id]
|
||||
|
@ -41,3 +43,8 @@
|
|||
(map key)
|
||||
(first))
|
||||
(str "<no-label #" (subs (str id) (- (count (str id)) 6)) ">")))
|
||||
|
||||
(defn next-uuid []
|
||||
(let [current (uuid/custom @next-uuid-val)]
|
||||
(swap! next-uuid-val inc)
|
||||
current))
|
||||
|
|
|
@ -386,8 +386,7 @@
|
|||
(fn [new-shape original-shape]
|
||||
(let [new-name (:name new-shape)
|
||||
root? (or (ctk/instance-root? original-shape) ; If shape is inside a component (not components-v2)
|
||||
(nil? (:parent-id original-shape))) ; we detect it by having no parent)
|
||||
swap-slot (ctk/get-swap-slot original-shape)]
|
||||
(nil? (:parent-id original-shape)))] ; we detect it by having no parent)
|
||||
|
||||
(when root?
|
||||
(vswap! unames conj new-name))
|
||||
|
@ -399,9 +398,6 @@
|
|||
(-> (gsh/move delta)
|
||||
(dissoc :touched))
|
||||
|
||||
(some? swap-slot)
|
||||
(assoc :touched #{(ctk/build-swap-slot-group swap-slot)})
|
||||
|
||||
(and main-instance? root?)
|
||||
(assoc :main-instance true)
|
||||
|
||||
|
|
BIN
common/test/cases/copying-and-duplicating.penpot
Normal file
BIN
common/test/cases/copying-and-duplicating.penpot
Normal file
Binary file not shown.
122
common/test/common_tests/logic/copying_and_duplicating_test.cljc
Normal file
122
common/test/common_tests/logic/copying_and_duplicating_test.cljc
Normal file
|
@ -0,0 +1,122 @@
|
|||
;; 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.logic.copying-and-duplicating-test
|
||||
(: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.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.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(defn- setup []
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :simple-1 :frame-simple-1 :rect-simple-1
|
||||
:child-params {:type :rect :fills (ths/sample-fills-color :fill-color "#2152e5") :name "rect-simple-1"})
|
||||
|
||||
(tho/add-frame :frame-composed-1 :name "frame-composed-1")
|
||||
(thc/instantiate-component :simple-1 :copy-simple-1 :parent-label :frame-composed-1 :children-labels [:composed-1-simple-1])
|
||||
(ths/add-sample-shape :rect-composed-1 :parent-label :frame-composed-1 :fills (ths/sample-fills-color :fill-color "#B1B2B5"))
|
||||
(thc/make-component :composed-1 :frame-composed-1)
|
||||
|
||||
(tho/add-frame :frame-composed-2 :name "frame-composed-2")
|
||||
(thc/instantiate-component :composed-1 :copy-composed-1-composed-2 :parent-label :frame-composed-2 :children-labels [:composed-1-composed-2])
|
||||
(thc/make-component :composed-2 :frame-composed-2)
|
||||
|
||||
(thc/instantiate-component :composed-2 :copy-composed-2)
|
||||
|
||||
(tho/add-frame :frame-composed-3 :name "frame-composed-3")
|
||||
(tho/add-group :group-3 :parent-label :frame-composed-3)
|
||||
(thc/instantiate-component :composed-2 :copy-composed-1-composed-3 :parent-label :group-3 :children-labels [:composed-1-composed-2])
|
||||
(ths/add-sample-shape :circle-composed-3 :parent-label :group-3 :fills (ths/sample-fills-color :fill-color "#B1B2B5"))
|
||||
(thc/make-component :composed-3 :frame-composed-3)
|
||||
|
||||
(thc/instantiate-component :composed-3 :copy-composed-3 :children-labels [:composed-2-composed-3])))
|
||||
|
||||
(defn- propagate-all-component-changes [file]
|
||||
(-> file
|
||||
(tho/propagate-component-changes :simple-1)
|
||||
(tho/propagate-component-changes :composed-1)
|
||||
(tho/propagate-component-changes :composed-2)
|
||||
(tho/propagate-component-changes :composed-3)))
|
||||
|
||||
(defn- count-shapes [file name color]
|
||||
(let [page (thf/current-page file)]
|
||||
(->> (vals (:objects page))
|
||||
(filter #(and
|
||||
(= (:name %) name)
|
||||
(-> (ths/get-shape-by-id file (:id %))
|
||||
:fills
|
||||
first
|
||||
:fill-color
|
||||
(= color))))
|
||||
(count))))
|
||||
|
||||
(defn- validate [file validator]
|
||||
(validator file)
|
||||
file)
|
||||
|
||||
;; Related .penpot file: common/test/cases/copying-and-duplicating.penpot
|
||||
(t/deftest main-and-first-level-copy
|
||||
(-> (setup)
|
||||
;; For each main and first level copy:
|
||||
;; - Duplicate it two times.
|
||||
(tho/duplicate-shape :frame-simple-1)
|
||||
(tho/duplicate-shape :frame-simple-1)
|
||||
(tho/duplicate-shape :frame-composed-1)
|
||||
(tho/duplicate-shape :frame-composed-1)
|
||||
(tho/duplicate-shape :frame-composed-2)
|
||||
(tho/duplicate-shape :frame-composed-2)
|
||||
(tho/duplicate-shape :frame-composed-3)
|
||||
(tho/duplicate-shape :frame-composed-3)
|
||||
(tho/duplicate-shape :copy-composed-2)
|
||||
(tho/duplicate-shape :copy-composed-2)
|
||||
(tho/duplicate-shape :copy-composed-3)
|
||||
(tho/duplicate-shape :copy-composed-3)
|
||||
|
||||
;; - Change color of Simple1 and check propagation to all copies.
|
||||
(tho/update-bottom-color :frame-simple-1 "#111111" :propagate-fn propagate-all-component-changes)
|
||||
(validate #(t/is (= (count-shapes % "rect-simple-1" "#111111") 18)))
|
||||
;; - Change color of the nearest main and check propagation to duplicated.
|
||||
(tho/update-bottom-color :frame-composed-1 "#222222" :propagate-fn propagate-all-component-changes)
|
||||
(validate #(t/is (= (count-shapes % "rect-simple-1" "#222222") 15)))
|
||||
(tho/update-bottom-color :frame-composed-2 "#333333" :propagate-fn propagate-all-component-changes)
|
||||
(validate #(t/is (= (count-shapes % "rect-simple-1" "#333333") 12)))
|
||||
(tho/update-bottom-color :frame-composed-3 "#444444" :propagate-fn propagate-all-component-changes)
|
||||
(validate #(t/is (= (count-shapes % "rect-simple-1" "#444444") 6)))))
|
||||
|
||||
(t/deftest copy-nested-in-main
|
||||
(-> (setup)
|
||||
;; For each copy of Simple1 nested in a main, and the group inside Composed3 main:
|
||||
;; - Duplicate it two times, keeping the duplicated inside the same main.
|
||||
(tho/duplicate-shape :copy-simple-1)
|
||||
(tho/duplicate-shape :copy-simple-1)
|
||||
(tho/duplicate-shape :group-3)
|
||||
(tho/duplicate-shape :group-3)
|
||||
|
||||
;; - Change color of Simple1 and check propagation to all copies.
|
||||
(tho/update-bottom-color :frame-simple-1 "#111111" :propagate-fn propagate-all-component-changes)
|
||||
(validate #(t/is (= (count-shapes % "rect-simple-1" "#111111") 28)))
|
||||
|
||||
;; - Change color of the nearest main and check propagation to duplicated.
|
||||
(tho/update-bottom-color :frame-composed-1 "#222222" :propagate-fn propagate-all-component-changes)
|
||||
(validate #(t/is (= (count-shapes % "rect-simple-1" "#222222") 9)))
|
||||
|
||||
;; - Change color of the copy you duplicated from, and check that it's NOT PROPAGATED.
|
||||
(tho/update-bottom-color :group-3 "#333333" :propagate-fn propagate-all-component-changes)
|
||||
(validate #(t/is (= (count-shapes % "rect-simple-1" "#333333") 2)))))
|
|
@ -59,7 +59,7 @@
|
|||
(validator file)
|
||||
file)
|
||||
|
||||
;; Related .penpot file: common/test/cases/xxxxxx
|
||||
;; Related .penpot file: common/test/cases/swap-as-override.penpot
|
||||
(t/deftest swap-main-then-copy
|
||||
(-> (setup)
|
||||
;; Swap icon in icon+text main. Check that it propagates to copies.
|
||||
|
|
699
common/yarn.lock
699
common/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue