0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-13 07:21:40 -05:00

Merge pull request #2967 from penpot/hiru-refactor-instances

🔧 Read component shapes from pages
This commit is contained in:
Pablo Alba 2023-03-28 12:00:10 +02:00 committed by GitHub
commit b73ce14560
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 3351 additions and 2781 deletions

View file

@ -13,6 +13,7 @@
[app.common.pages.helpers :as cph]
[app.common.pages.migrations :as pmg]
[app.common.spec :as us]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.types.shape-tree :as ctt]
[app.config :as cf]
@ -200,6 +201,16 @@
::db/check-deleted? false})]
(blob/decode (:content row))))
(defn load-all-pointers!
[data]
(doseq [[_id page] (:pages-index data)]
(when (pmap/pointer-map? page)
(pmap/load! page)))
(doseq [[_id component] (:components data)]
(when (pmap/pointer-map? component)
(pmap/load! component)))
data)
(defn persist-pointers!
[conn file-id]
(doseq [[id item] @pmap/*tracked*]
@ -485,17 +496,23 @@
(defn get-team-shared-files
[conn team-id]
(letfn [(assets-sample [assets limit]
(let [sorted-assets (->> (vals assets)
(sort-by #(str/lower (:name %))))]
{:count (count sorted-assets)
:sample (into [] (take limit sorted-assets))}))
(let [sorted-assets (->> (vals assets)
(sort-by #(str/lower (:name %))))]
{:count (count sorted-assets)
:sample (into [] (take limit sorted-assets))}))
(library-summary [{:keys [id data] :as file}]
(binding [pmap/*load-fn* (partial load-pointer conn id)]
{:components (assets-sample (:components data) 4)
:media (assets-sample (:media data) 3)
:colors (assets-sample (:colors data) 3)
:typographies (assets-sample (:typographies data) 3)}))]
(let [load-objects (fn [component]
(binding [pmap/*load-fn* (partial load-pointer conn id)]
(ctf/load-component-objects data component)))
components-sample (-> (assets-sample (ctkl/components data) 4)
(update :sample
#(map load-objects %)))]
{:components components-sample
:media (assets-sample (:media data) 3)
:colors (assets-sample (:colors data) 3)
:typographies (assets-sample (:typographies data) 3)})))]
(->> (db/exec! conn [sql:team-shared-files team-id])
(into #{} (comp
@ -552,7 +569,10 @@
(map (fn [{:keys [id] :as row}]
(binding [pmap/*load-fn* (partial load-pointer conn id)]
(-> row
(update :data dissoc :pages-index)
;; TODO: re-enable this dissoc and replace call
;; with other that gets files individually
;; See task https://tree.taiga.io/project/penpot/task/4904
;; (update :data dissoc :pages-index)
(handle-file-features client-features)))))
(vec)))
@ -836,18 +856,23 @@
(let [library (db/get-by-id conn :file id)]
(when (:is-shared library)
(let [ldata (-> library decode-row pmg/migrate-file :data)]
(binding [pmap/*load-fn* (partial load-pointer conn id)]
(load-all-pointers! ldata))
(->> (db/query conn :file-library-rel {:library-file-id id})
(map :file-id)
(keep #(db/get-by-id conn :file % ::db/check-deleted? false))
(map decode-row)
(map pmg/migrate-file)
(run! (fn [{:keys [id data revn] :as file}]
(let [data (ctf/absorb-assets data ldata)]
(db/update! conn :file
{:revn (inc revn)
:data (blob/encode data)
:modified-at (dt/now)}
{:id id})))))))))
(binding [pmap/*tracked* (atom {})
pmap/*load-fn* (partial load-pointer conn id)]
(let [data (ctf/absorb-assets data ldata)]
(db/update! conn :file
{:revn (inc revn)
:data (blob/encode data)
:modified-at (dt/now)}
{:id id}))
(persist-pointers! conn id)))))))))
(s/def ::set-file-shared
(s/keys :req [::rpc/profile-id]

View file

@ -13,6 +13,7 @@
[app.common.data :as d]
[app.common.logging :as l]
[app.common.pages.migrations :as pmg]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.types.shape-tree :as ctt]
[app.config :as cf]
@ -204,36 +205,31 @@
(filter #(ctf/used-in? file-data library-id % :component))
components))
find-used-components
find-unused-components
(fn [components files-data]
; Find what components are used in any of the files.
; Find what components are NOT used in any of the files.
(loop [files-data files-data
components components
used-components #{}]
components components]
(let [file-data (first files-data)]
(if (or (nil? file-data) (empty? components))
used-components
components
(let [used-components-file (find-used-components-file components file-data)]
(recur (rest files-data)
(into #{} (remove used-components-file) components)
(into used-components used-components-file)))))))
(into #{} (remove used-components-file) components)))))))
deleted-components (set (vals (:deleted-components library-data)))
saved-components (find-used-components deleted-components
(cons library-data
(retrieve-client-files conn library-id)))
new-deleted-components (d/index-by :id (vec saved-components))
total (- (count deleted-components)
(count saved-components))]
deleted-components (set (ctkl/deleted-components-seq library-data))
unused-components (find-unused-components deleted-components
(cons library-data
(retrieve-client-files conn library-id)))
total (count unused-components)]
(when-not (zero? total)
(l/debug :hint "clean deleted components" :total total)
(let [new-data (-> library-data
(assoc :deleted-components new-deleted-components)
(blob/encode))]
(let [new-data (reduce #(ctkl/delete-component %1 (:id %2))
library-data
unused-components)]
(db/update! conn :file
{:data new-data}
{:data (blob/encode new-data)}
{:id library-id})))))
(def ^:private sql:get-unused-fragments

View file

@ -255,6 +255,11 @@
(subvec v 0 index)
(subvec v (inc index))))
(defn without-obj
"Clear collection from specified obj and without nil values."
[coll o]
(into [] (filter #(not= % o)) coll))
(defn zip [col1 col2]
(map vector col1 col2))
@ -477,6 +482,17 @@
(->> (apply c/iteration args)
(concat-all)))
(defn insert-at-index
"Insert a list of elements at the given index of a previous list.
Replace all existing elems."
[elems index new-elems]
(let [[before after] (split-at index elems)
p? (set new-elems)]
(concat-vec []
(remove p? before)
new-elems
(remove p? after))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Parsing / Conversion

View file

@ -8,6 +8,7 @@
"A version parsing helper."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
@ -664,9 +665,10 @@
[_ shapes]
(ctn/make-component-instance page
component
(:id file)
(:data file)
(gpt/point main-instance-x
main-instance-y)
true
{:main-instance? true
:force-id main-instance-id})]
(as-> file $
@ -701,12 +703,15 @@
component (ctkl/get-component (:data file) component-id)
;; main-instance-id (:main-instance-id component)
components-v2 (dm/get-in file [:options :components-v2])
[shape shapes]
(ctn/make-component-instance page
component
(:id file)
(gpt/point x
y)
components-v2
#_{:main-instance? true
:force-id main-instance-id})]

View file

@ -26,15 +26,6 @@
[app.common.types.shape-tree :as ctst]
[app.common.types.typographies-list :as ctyl]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Specific helpers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- without-obj
"Clear collection from specified obj and without nil values."
[coll o]
(into [] (filter #(not= % o)) coll))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Page Transformation Changes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -111,27 +102,9 @@
(defmethod process-change :del-obj
[data {:keys [page-id component-id id ignore-touched]}]
(letfn [(delete-from-parent [parent]
(let [parent (update parent :shapes without-obj id)]
(cond-> parent
(and (:shape-ref parent)
(not ignore-touched))
(-> (update :touched cph/set-touched-group :shapes-group)
(dissoc :remote-synced?)))))
(delete-from-objects [objects]
(if-let [target (get objects id)]
(let [parent-id (or (:parent-id target)
(:frame-id target))
children (cph/get-children id objects)]
(-> (reduce dissoc objects children)
(dissoc id)
(d/update-when parent-id delete-from-parent)))
objects))]
(if page-id
(d/update-in-when data [:pages-index page-id :objects] delete-from-objects)
(d/update-in-when data [:components component-id :objects] delete-from-objects))))
(if page-id
(d/update-in-when data [:pages-index page-id] ctst/delete-shape id ignore-touched)
(d/update-in-when data [:components component-id] ctst/delete-shape id ignore-touched)))
;; reg-objects operation "regenerates" the geometry and selrect of the parent groups
(defmethod process-change :reg-objects
@ -197,7 +170,7 @@
(insert-items [prev-shapes index shapes]
(let [prev-shapes (or prev-shapes [])]
(if index
(cph/insert-at-index prev-shapes index shapes)
(d/insert-at-index prev-shapes index shapes)
(cph/append-at-the-end prev-shapes shapes))))
(add-to-parent [parent index shapes]
@ -226,7 +199,7 @@
(not ignore-touched))]
(-> objects
(d/update-in-when [pid :shapes] without-obj sid)
(d/update-in-when [pid :shapes] d/without-obj sid)
(d/update-in-when [pid :shapes] d/vec-without-nils)
(cond-> component? (d/update-when pid #(-> %
(update :touched cph/set-touched-group :shapes-group)
@ -301,7 +274,7 @@
(defmethod process-change :mov-page
[data {:keys [id index]}]
(update data :pages cph/insert-at-index index [id]))
(update data :pages d/insert-at-index index [id]))
(defmethod process-change :add-color
[data {:keys [color]}]

View file

@ -39,6 +39,12 @@
[changes stack-undo?]
(assoc changes :stack-undo? stack-undo?))
(defn set-undo-group
[changes undo-group]
(cond-> changes
(some? undo-group)
(assoc :undo-group undo-group)))
(defn with-page
[changes page]
(vary-meta changes assoc
@ -80,7 +86,8 @@
[changes1 changes2]
{:redo-changes (d/concat-vec (:redo-changes changes1) (:redo-changes changes2))
:undo-changes (d/concat-vec (:undo-changes changes1) (:undo-changes changes2))
:origin (:origin changes1)})
:origin (:origin changes1)
:undo-group (:undo-group changes1)})
; TODO: remove this when not needed
(defn- assert-page-id
@ -598,19 +605,21 @@
(update :redo-changes
(fn [redo-changes]
(-> redo-changes
(conj {:type :add-component
:id id
:path path
:name name
:main-instance-id main-instance-id
:main-instance-page main-instance-page
:shapes new-shapes})
(conj (cond-> {:type :add-component
:id id
:path path
:name name
:main-instance-id main-instance-id
:main-instance-page main-instance-page}
(some? new-shapes) ;; this will be null in components-v2
(assoc :shapes new-shapes)))
(into (map mk-change) updated-shapes))))
(update :undo-changes
(fn [undo-changes]
(-> undo-changes
(d/preconj {:type :del-component
:id id})
:id id
:skip-undelete? true})
(into (comp (map :id)
(map lookupf)
(map mk-change))
@ -629,7 +638,7 @@
:id id
:name (:name new-component)
:path (:path new-component)
:objects (:objects new-component)})
:objects (:objects new-component)}) ;; this won't exist in components-v2
(update :undo-changes d/preconj {:type :mod-component
:id id
:name (:name prev-component)
@ -638,28 +647,13 @@
changes)))
(defn delete-component
[changes id components-v2]
[changes id]
(assert-library changes)
(let [library-data (::library-data (meta changes))
prev-component (get-in library-data [:components id])]
(-> changes
(update :redo-changes conj {:type :del-component
:id id})
(update :undo-changes
(fn [undo-changes]
(cond-> undo-changes
components-v2
(d/preconj {:type :purge-component
:id id})
:always
(d/preconj {:type :add-component
:id id
:name (:name prev-component)
:path (:path prev-component)
:main-instance-id (:main-instance-id prev-component)
:main-instance-page (:main-instance-page prev-component)
:shapes (vals (:objects prev-component))})))))))
(-> changes
(update :redo-changes conj {:type :del-component
:id id})
(update :undo-changes d/preconj {:type :restore-component
:id id})))
(defn restore-component
[changes id]

View file

@ -153,8 +153,8 @@
(s/coll-of ::cts/shape))
(defmethod change-spec :add-component [_]
(s/keys :req-un [::id ::name :internal.changes.add-component/shapes]
:opt-un [::path]))
(s/keys :req-un [::id ::name]
:opt-un [::path :internal.changes.add-component/shapes]))
(defmethod change-spec :mod-component [_]
(s/keys :req-un [::id]

View file

@ -9,6 +9,8 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.spec :as us]
[app.common.types.components-list :as ctkl]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
@ -270,25 +272,6 @@
[shape group]
((or (:touched shape) #{}) group))
(defn get-component
"Retrieve a component from libraries, if no library-id is provided, we
iterate over all libraries and find the component on it."
([libraries component-id]
(some #(-> % :data :components (get component-id)) (vals libraries)))
([libraries library-id component-id]
(get-in libraries [library-id :data :components component-id])))
(defn get-component-shape
"Get the parent shape linked to a component for this shape, if any"
[objects shape]
(if-not (:shape-ref shape)
nil
(if (:component-id shape)
shape
(if-let [parent-id (:parent-id shape)]
(get-component-shape objects (get objects parent-id))
nil))))
(defn get-root-shape
"Get the root shape linked to a component for this shape, if any."
[objects shape]
@ -319,8 +302,8 @@
(us/assert uuid? id)
(-> (if (= type :page)
(get-in file [:pages-index id])
(get-in file [:components id]))
(ctpl/get-page file id)
(ctkl/get-component file id))
(assoc :type type)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -340,15 +323,6 @@
(update page :objects
#(into % (d/index-by :id objects-list))))
(defn insert-at-index
[objects index ids]
(let [[before after] (split-at index objects)
p? (set ids)]
(d/concat-vec []
(remove p? before)
ids
(remove p? after))))
(defn append-at-the-end
[prev-ids ids]
(reduce (fn [acc id]

View file

@ -36,7 +36,9 @@
(defn get-component-root
[component]
(get-in component [:objects (:id component)]))
(if (some? (:main-instance-id component))
(get-in component [:objects (:main-instance-id component)])
(get-in component [:objects (:id component)])))
(defn uses-library-components?
"Check if the shape uses any component in the given library."
@ -45,12 +47,12 @@
(= (:component-file shape) library-id)))
(defn in-component-instance?
"Check if the shape is inside a component instance."
"Check if the shape is inside a component non-main instance."
[shape]
(some? (:shape-ref shape)))
(defn in-component-instance-not-root?
"Check if the shape is inside a component instance and
"Check if the shape is inside a component non-main instance and
is not the root shape."
[shape]
(and (some? (:shape-ref shape))
@ -66,5 +68,3 @@
:remote-synced?
:shape-ref
:touched))

View file

@ -10,9 +10,18 @@
[app.common.data.macros :as dm]
[app.common.files.features :as feat]))
(defn components
[file-data]
(d/removem (fn [[_ component]] (:deleted component))
(:components file-data)))
(defn components-seq
[file-data]
(vals (:components file-data)))
(remove :deleted (vals (:components file-data))))
(defn deleted-components-seq
[file-data]
(filter :deleted (vals (:components file-data))))
(defn add-component
[file-data {:keys [id name path main-instance-id main-instance-page shapes]}]
@ -23,11 +32,13 @@
(assoc-in [:components id]
{:id id
:name name
:path path
:objects (->> shapes
(d/index-by :id)
(wrap-object-fn))})
:path path})
(not components-v2)
(assoc-in [:components id :objects]
(->> shapes
(d/index-by :id)
(wrap-object-fn)))
components-v2
(update-in [:components id] assoc
:main-instance-id main-instance-id
@ -51,7 +62,15 @@
(defn get-component
[file-data component-id]
(get-in file-data [:components component-id]))
(let [component (get-in file-data [:components component-id])]
(when-not (:deleted component)
component)))
(defn get-deleted-component
[file-data component-id]
(let [component (get-in file-data [:components component-id])]
(when (:deleted component)
component)))
(defn update-component
[file-data component-id f]
@ -61,3 +80,10 @@
[file-data component-id]
(update file-data :components dissoc component-id))
(defn mark-component-deleted
[file-data component-id]
(assoc-in file-data [:components component-id :deleted] true))
(defn mark-component-undeleted
[file-data component-id]
(d/dissoc-in file-data [:components component-id :deleted]))

View file

@ -6,11 +6,12 @@
(ns app.common.types.container
(:require
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.common :as common]
[app.common.spec :as us]
[app.common.types.components-list :as ctkl]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst]
[clojure.spec.alpha :as s]))
@ -42,8 +43,8 @@
(us/assert uuid? id)
(-> (if (= type :page)
(dm/get-in file [:pages-index id])
(dm/get-in file [:components id]))
(ctpl/get-page file id)
(ctkl/get-component file id))
(assoc :type type)))
(defn get-shape
@ -62,6 +63,17 @@
[container shape-id f]
(update-in container [:objects shape-id] f))
(defn get-component-shape
"Get the parent shape linked to a component for this shape, if any"
[objects shape]
(if-not (:shape-ref shape)
nil
(if (:component-id shape)
shape
(if-let [parent-id (:parent-id shape)]
(get-component-shape objects (get objects parent-id))
nil))))
(defn make-component-shape
"Clone the shape and all children. Generate new ids and detach
from parent and frame. Update the original shapes to have links
@ -117,15 +129,22 @@
[new-root-shape (map remap-frame-id new-shapes) updated-shapes]))
(defn make-component-instance
"Clone the shapes of the component, generating new names and ids, and linking
"Generate a new instance of the component inside the given container.
Clone the shapes of the component, generating new names and ids, and linking
each new shape to the corresponding one of the component. Place the new instance
coordinates in the given position."
([container component component-file-id position]
(make-component-instance container component component-file-id position {}))
([container component library-data position components-v2]
(make-component-instance container component library-data position components-v2 {}))
([container component component-file-id position
([container component library-data position components-v2
{:keys [main-instance? force-id] :or {main-instance? false force-id nil}}]
(let [component-shape (get-shape component (:id component))
(let [component-page (when components-v2
(ctpl/get-page library-data (:main-instance-page component)))
component-shape (if components-v2
(-> (get-shape component-page (:main-instance-id component))
(assoc :parent-id nil))
(get-shape component (:id component)))
orig-pos (gpt/point (:x component-shape) (:y component-shape))
delta (gpt/subtract position orig-pos)
@ -147,20 +166,20 @@
(vswap! frame-ids-map assoc (:id original-shape) (:id new-shape)))
(cond-> new-shape
true
:always
(-> (gsh/move delta)
(dissoc :touched))
(dissoc :touched :main-instance?))
(nil? (:shape-ref original-shape))
(and (not main-instance?) (nil? (:shape-ref original-shape)))
(assoc :shape-ref (:id original-shape))
(nil? (:parent-id original-shape))
(assoc :component-id (:id original-shape)
:component-file component-file-id
(assoc :component-id (:id component)
:component-file (:id library-data)
:component-root? true
:name new-name)
(and (nil? (:parent-id original-shape)) main-instance?)
(and (nil? (:parent-id original-shape)) main-instance? components-v2)
(assoc :main-instance? true)
(some? (:parent-id original-shape))
@ -169,7 +188,7 @@
[new-shape new-shapes _]
(ctst/clone-object component-shape
nil
(get component :objects)
(if components-v2 (:objects component-page) (:objects component))
update-new-shape
(fn [object _] object)
force-id)
@ -177,10 +196,10 @@
;; If frame-id points to a shape inside the component, remap it to the
;; corresponding new frame shape. If not, set it to the destination frame.
;; Also fix empty parent-id.
remap-frame-id (fn [shape]
(as-> shape $
(update $ :frame-id #(get @frame-ids-map % frame-id))
(update $ :parent-id #(or % (:frame-id $)))))]
remap-frame-id (fn [shape]
(as-> shape $
(update $ :frame-id #(get @frame-ids-map % frame-id))
(update $ :parent-id #(or % (:frame-id $)))))]
[new-shape (map remap-frame-id new-shapes)])))

View file

@ -6,27 +6,27 @@
(ns app.common.types.file
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.features :as ffeat]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.common :refer [file-version]]
[app.common.pages.helpers :as cph]
[app.common.types.color :as ctc]
[app.common.types.colors-list :as ctcl]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.file.media-object :as ctfm]
[app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst]
[app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as cty]
[app.common.uuid :as uuid]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.features :as ffeat]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.common :refer [file-version]]
[app.common.pages.helpers :as cph]
[app.common.types.color :as ctc]
[app.common.types.colors-list :as ctcl]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.file.media-object :as ctfm]
[app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst]
[app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as cty]
[app.common.uuid :as uuid]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
;; Specs
@ -116,56 +116,100 @@
([libraries library-id component-id]
(ctkl/get-component (dm/get-in libraries [library-id :data]) component-id)))
(defn delete-component
"Delete a component and store it to be able to be recovered later.
(defn get-component-library
"Retrieve the library the component belongs to."
[libraries instance-root]
(get libraries (:component-file instance-root)))
Remember also the position of the main instance."
(defn get-component-page
"Retrieve the page where the main instance of the component resides."
[file-data component]
(ctpl/get-page file-data (:main-instance-page component)))
(defn get-component-container
"Retrieve the container that holds the component shapes (the page in components-v2
or the component itself in v1)"
[file-data component]
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (and components-v2 (not (:deleted component)))
(let [component-page (get-component-page file-data component)]
(cph/make-container component-page :page))
(cph/make-container component :component))))
(defn get-component-root
"Retrieve the root shape of the component."
[file-data component]
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (and components-v2 (not (:deleted component)))
(-> file-data
(get-component-page component)
(ctn/get-shape (:main-instance-id component)))
(ctk/get-component-root component))))
(defn get-component-shape
"Retrieve one shape in the component by id."
[file-data component shape-id]
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (and components-v2 (not (:deleted component)))
(let [component-page (get-component-page file-data component)]
(ctn/get-shape component-page shape-id))
(dm/get-in component [:objects shape-id]))))
(defn get-ref-shape
"Retrieve the shape in the component that is referenced by the
instance shape."
[file-data component shape]
(when (:shape-ref shape)
(get-component-shape file-data component (:shape-ref shape))))
(defn get-component-shapes
"Retrieve all shapes of the component"
[file-data component]
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if components-v2
(let [instance-page (get-component-page file-data component)]
(cph/get-children-with-self (:objects instance-page) (:main-instance-id component)))
(vals (:objects component)))))
(defn load-component-objects
"Add an :objects property to the component, with only the shapes that belong to it"
[file-data component]
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (and components-v2 component (nil? (:objects component))) ;; This operation may be called twice, e.g. in an idempotent change
(let [component-page (get-component-page file-data component)
page-objects (:objects component-page)
objects (->> (cons (:main-instance-id component)
(cph/get-children-ids page-objects (:main-instance-id component)))
(map #(get page-objects %))
(d/index-by :id))]
(assoc component :objects objects))
component)))
(defn delete-component
"Mark a component as deleted and store the main instance shapes iside it, to
be able to be recovered later."
([file-data component-id]
(delete-component file-data component-id false))
([file-data component-id skip-undelete?]
(let [components-v2 (dm/get-in file-data [:options :components-v2])
add-to-deleted-components
(fn [file-data]
(let [component (ctkl/get-component file-data component-id)]
(if (some? component)
(let [page (ctpl/get-page file-data (:main-instance-page component))
main-instance (ctn/get-shape page (:main-instance-id component))
component (assoc component
:main-instance-x (:x main-instance) ; An instance root is always a group,
:main-instance-y (:y main-instance))] ; so it will have :x and :y
(when (nil? main-instance)
(throw (ex-info "Cannot delete the main instance before the component" {:component-id component-id})))
(assoc-in file-data [:deleted-components component-id] component))
file-data)))]
(cond-> file-data
(and components-v2 (not skip-undelete?))
(add-to-deleted-components)
:always
(ctkl/delete-component component-id)))))
(defn get-deleted-component
"Retrieve a component that has been deleted but still is in the safe store."
[file-data component-id]
(dm/get-in file-data [:deleted-components component-id]))
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (or (not components-v2) skip-undelete?)
(ctkl/delete-component file-data component-id)
(-> file-data
(ctkl/update-component component-id (partial load-component-objects file-data))
(ctkl/mark-component-deleted component-id))))))
(defn restore-component
"Recover a deleted component and put it again in place."
"Recover a deleted component and all its shapes and put all this again in place."
[file-data component-id]
(let [component (-> (dm/get-in file-data [:deleted-components component-id])
(dissoc :main-instance-x :main-instance-y))]
(cond-> file-data
(some? component)
(-> (assoc-in [:components component-id] component)
(d/dissoc-in [:deleted-components component-id])))))
(-> file-data
(ctkl/update-component component-id #(dissoc % :objects))
(ctkl/mark-component-undeleted component-id)))
(defn purge-component
"Remove permanently a component."
[file-data component-id]
(d/dissoc-in file-data [:deleted-components component-id]))
(ctkl/delete-component file-data component-id))
(defmulti uses-asset?
"Checks if a shape uses the given asset."
@ -243,8 +287,9 @@
[(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)]))))
(defn migrate-to-components-v2
"If there is any component in the file library, add a new 'Library backup' and generate
main instances for all components there. Mark the file with the :components-v2 option."
"If there is any component in the file library, add a new 'Library backup', generate
main instances for all components there and remove shapes from library components.
Mark the file with the :components-v2 option."
[file-data]
(let [components (ctkl/components-seq file-data)]
(if (or (empty? components)
@ -262,8 +307,9 @@
[new-shape new-shapes]
(ctn/make-component-instance page
component
(:id file-data)
file-data
position
false
{:main-instance? true})
add-shapes
@ -281,9 +327,10 @@
update-component
(fn [component]
(assoc component
:main-instance-id (:id new-shape)
:main-instance-page page-id))]
(-> component
(assoc :main-instance-id (:id new-shape)
:main-instance-page page-id)
(dissoc :objects)))]
(-> file-data
(ctpl/update-page page-id add-shapes)
@ -292,9 +339,9 @@
add-instance-grid
(fn [file-data components]
(let [position-seq (ctst/generate-shape-grid
(map ctk/get-component-root components)
start-pos
grid-gap)]
(map (partial get-component-root file-data) components)
start-pos
grid-gap)]
(loop [file-data file-data
components-seq (seq components)
position-seq position-seq]
@ -311,7 +358,7 @@
(assoc-in [:options :components-v2] true))))))
(defn- absorb-components
[file-data used-components]
[file-data used-components library-data]
(let [grid-gap 50
; Search for the library page. If not exists, create it.
@ -326,10 +373,17 @@
[main-instance-shape main-instance-shapes]
(ctn/make-component-instance page
component
(:id file-data)
library-data
position
(dm/get-in file-data [:options :components-v2])
{:main-instance? true})
main-instance-shapes
(map #(cond-> %
(some? (:component-file %))
(assoc :component-file (:id file-data)))
main-instance-shapes)
; Add all shapes of the main instance to the library page
add-main-instance-shapes
(fn [page]
@ -353,7 +407,7 @@
:path (:path component)
:main-instance-id (:id main-instance-shape)
:main-instance-page page-id
:shapes (vals (:objects component))}))
:shapes (get-component-shapes library-data component)}))
; Change all existing instances to point to the local file
remap-instances
@ -378,9 +432,9 @@
add-component-grid
(fn [data used-components]
(let [position-seq (ctst/generate-shape-grid
(map #(ctk/get-component-root (first %)) used-components)
start-pos
grid-gap)]
(map #(get-component-root library-data (first %)) used-components)
start-pos
grid-gap)]
(loop [data data
components-seq (seq used-components)
position-seq position-seq]
@ -410,9 +464,9 @@
remap-shape))
%
shapes)))]
(as-> file-data $
(ctcl/add-color $ color)
(reduce remap-shapes $ usages))))]
(as-> file-data $
(ctcl/add-color $ color)
(reduce remap-shapes $ usages))))]
(reduce absorb-color
file-data
@ -434,9 +488,9 @@
remap-shape))
%
shapes)))]
(as-> file-data $
(ctyl/add-typography $ typography)
(reduce remap-shapes $ usages))))]
(as-> file-data $
(ctyl/add-typography $ typography)
(reduce remap-shapes $ usages))))]
(reduce absorb-typography
file-data
@ -452,7 +506,7 @@
(cond-> file-data
(d/not-empty? used-components)
(absorb-components used-components)
(absorb-components used-components library-data)
(d/not-empty? used-colors)
(absorb-colors used-colors)
@ -473,73 +527,86 @@
([file-data page-id libraries show-ids show-touched]
(let [page (ctpl/get-page file-data page-id)
objects (:objects page)
components (:components file-data)
components (ctkl/components file-data)
root (d/seek #(nil? (:parent-id %)) (vals objects))]
(letfn [(show-shape [shape-id level objects]
(let [shape (get objects shape-id)]
(println (str/pad (str (str/repeat " " level)
(:name shape)
(when (seq (:touched shape)) "*")
(when show-ids (str/format " <%s>" (:id shape))))
{:length 20
:type :right})
(show-component shape objects))
(when show-touched
(when (seq (:touched shape))
(println (str (str/repeat " " level)
" "
(str (:touched shape)))))
(when (:remote-synced? shape)
(println (str (str/repeat " " level)
" (remote-synced)"))))
(when (:shapes shape)
(dorun (for [shape-id (:shapes shape)]
(show-shape shape-id (inc level) objects))))))
(let [shape (get objects shape-id)]
(println (str/pad (str (str/repeat " " level)
(when (:main-instance? shape) "{")
(:name shape)
(when (:main-instance? shape) "}")
(when (seq (:touched shape)) "*")
(when show-ids (str/format " <%s>" (:id shape))))
{:length 20
:type :right})
(show-component-info shape objects))
(when show-touched
(when (seq (:touched shape))
(println (str (str/repeat " " level)
" "
(str (:touched shape)))))
(when (:remote-synced? shape)
(println (str (str/repeat " " level)
" (remote-synced)"))))
(when (:shapes shape)
(dorun (for [shape-id (:shapes shape)]
(show-shape shape-id (inc level) objects))))))
(show-component [shape objects]
(if (nil? (:shape-ref shape))
""
(let [root-shape (cph/get-component-shape objects shape)
component-id (when root-shape (:component-id root-shape))
component-file-id (when root-shape (:component-file root-shape))
component-file (when component-file-id (get libraries component-file-id nil))
component (when component-id
(if component-file
(dm/get-in component-file [:data :components component-id])
(get components component-id)))
component-shape (when (and component (:shape-ref shape))
(dm/get-in component [:objects (:shape-ref shape)]))]
(str/format " %s--> %s%s%s"
(cond (:component-root? shape) "#"
(:component-id shape) "@"
:else "-")
(when component-file (str/format "<%s> " (:name component-file)))
(or (:name component-shape) "?")
(if (or (:component-root? shape)
(nil? (:component-id shape))
true)
""
(let [component-id (:component-id shape)
component-file-id (:component-file shape)
component-file (when component-file-id (get libraries component-file-id nil))
component (if component-file
(dm/get-in component-file [:data :components component-id])
(get components component-id))]
(str/format " (%s%s)"
(when component-file (str/format "<%s> " (:name component-file)))
(:name component))))))))]
(show-component-info [shape objects]
(if (nil? (:shape-ref shape))
(if (:component-root? shape) " #" "")
(let [root-shape (ctn/get-component-shape objects shape)
component-id (when root-shape (:component-id root-shape))
component-file-id (when root-shape (:component-file root-shape))
component-file (when component-file-id (get libraries component-file-id nil))
component (when component-id
(if component-file
(ctkl/get-component (:data component-file) component-id)
(get components component-id)))
component-shape (when component
(if component-file
(get-ref-shape (:data component-file) component shape)
(get-ref-shape file-data component shape)))]
(println "[Page]")
(str/format " %s--> %s%s%s"
(cond (:component-root? shape) "#"
(:component-id shape) "@"
:else "-")
(when component-file (str/format "<%s> " (:name component-file)))
(or (:name component-shape) "?")
(if (or (:component-root? shape)
(nil? (:component-id shape))
true)
""
(let [component-id (:component-id shape)
component-file-id (:component-file shape)
component-file (when component-file-id (get libraries component-file-id nil))
component (if component-file
(ctkl/get-component (:data component-file) component-id)
(get components component-id))]
(str/format " (%s%s)"
(when component-file (str/format "<%s> " (:name component-file)))
(:name component))))))))
(show-component-instance [component]
(let [page (get-component-page file-data component)
root (get-component-root file-data component)]
(if-not show-ids
(println (str " [" (:name page) "] / " (:name root)))
(do
(println (str " " (:name page) (str/format " <%s>" (:id page))))
(println (str " " (:name root) (str/format " <%s>" (:id root))))))))]
(println (str "[Page: " (:name page) "]"))
(show-shape (:id root) 0 objects)
(dorun (for [component (vals components)]
(do
(println)
(println (str/format "[%s]" (:name component))
(when show-ids
(str/format " (main: %s/%s)"
(:main-instance-page component)
(:main-instance-id component))))
(show-shape (:id component) 0 (:objects component)))))))))
(println (str/format "[%s]" (:name component)))
(when (:objects component)
(show-shape (:id component) 0 (:objects component)))
(when (:main-instance-page component)
(show-component-instance component)))))))))

View file

@ -709,7 +709,7 @@
(update shape :shapes
(fn [shapes]
(if (vector? shapes)
(cph/insert-at-index shapes index value)
(d/insert-at-index shapes index value)
(d/concat-vec shapes value))))
(update shape :shapes d/concat-vec value)))

View file

@ -6,8 +6,8 @@
(ns app.common.types.pages-list
(:require
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]))
[app.common.data :as d]
[app.common.data.macros :as dm]))
(defn get-page
[file-data id]
@ -23,7 +23,7 @@
(cond
exists? pages
(nil? index) (conj pages id)
:else (cph/insert-at-index pages index [id])))))
:else (d/insert-at-index pages index [id])))))
(update :pages-index assoc id (dissoc page :index))))
(defn pages-seq

View file

@ -36,7 +36,7 @@
(conj shapes id)
:else
(cph/insert-at-index shapes index [id]))))
(d/insert-at-index shapes index [id]))))
update-parent
(fn [parent]
@ -49,28 +49,63 @@
(-> (update :touched cph/set-touched-group :shapes-group)
(dissoc :remote-synced?)))))
;; TODO: this looks wrong, why we allow nil values?
update-objects
(fn [objects parent-id]
(if (and (or (nil? parent-id) (contains? objects parent-id))
(or (nil? frame-id) (contains? objects frame-id)))
(let [parent-id (if (contains? objects parent-id)
parent-id
uuid/zero)
frame-id (if (contains? objects frame-id)
frame-id
uuid/zero)]
(-> objects
(assoc id (-> shape
(assoc :frame-id frame-id)
(assoc :parent-id parent-id)
(assoc :id id)))
(update parent-id update-parent))
objects))
(update parent-id update-parent))))
parent-id (or parent-id frame-id)]
(update container :objects update-objects parent-id)))
(defn get-shape
"Get a shape identified by id"
[container id]
(-> container :objects (get id)))
(defn set-shape
"Replace a shape in the tree with a new one"
[container shape]
(assoc-in container [:objects (:id shape)] shape))
(defn delete-shape
"Remove a shape and all its children from the tree.
Remove it also from its parent, and marks it as touched
if needed, unless ignore-touched is true."
([container shape-id]
(delete-shape container shape-id false))
([container shape-id ignore-touched]
(letfn [(delete-from-parent [parent]
(let [parent (update parent :shapes d/without-obj shape-id)]
(cond-> parent
(and (:shape-ref parent) (not ignore-touched))
(-> (update :touched cph/set-touched-group :shapes-group)
(dissoc :remote-synced?)))))
(delete-from-objects [objects]
(if-let [target (get objects shape-id)]
(let [parent-id (or (:parent-id target)
(:frame-id target))
children-ids (cph/get-children-ids objects shape-id)]
(-> (reduce dissoc objects children-ids)
(dissoc shape-id)
(d/update-when parent-id delete-from-parent)))
objects))]
(update container :objects delete-from-objects))))
(defn get-frames
"Retrieves all frame objects as vector"
[objects]
@ -359,4 +394,3 @@
(iterate next-pos
(with-meta start-pos
{:counter 0}))))

View file

@ -65,3 +65,41 @@
(t/is (= 0 (d/check-num [] 0)))
(t/is (= 0 (d/check-num :foo 0)))
(t/is (= 0 (d/check-num nil 0))))
(t/deftest insert-at-index
;; insert different object
(t/is (= (d/insert-at-index [:a :b] 1 [:c :d])
[:a :c :d :b]))
;; insert on the start
(t/is (= (d/insert-at-index [:a :b] 0 [:c])
[:c :a :b]))
;; insert on the end 1
(t/is (= (d/insert-at-index [:a :b] 2 [:c])
[:a :b :c]))
;; insert on the end with not existing index
(t/is (= (d/insert-at-index [:a :b] 10 [:c])
[:a :b :c]))
;; insert existing in a contiguous index
(t/is (= (d/insert-at-index [:a :b] 1 [:a])
[:a :b]))
;; insert existing in the same index
(t/is (= (d/insert-at-index [:a :b] 0 [:a])
[:a :b]))
;; insert existing in other index case 1
(t/is (= (d/insert-at-index [:a :b :c] 2 [:a])
[:b :a :c]))
;; insert existing in other index case 2
(t/is (= (d/insert-at-index [:a :b :c :d] 0 [:d])
[:d :a :b :c]))
;; insert existing in other index case 3
(t/is (= (d/insert-at-index [:a :b :c :d] 1 [:a])
[:a :b :c :d])))

View file

@ -9,7 +9,8 @@
[clojure.test :as t]
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]))
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]))
;; ---- Helpers to manage libraries and synchronization
@ -81,7 +82,7 @@
[page root-inst-id libraries]
(let [root-inst (ctn/get-shape page root-inst-id)
component (cph/get-component libraries (:component-id root-inst))
component (ctf/get-component libraries (:component-id root-inst))
shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)
shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst))
@ -90,10 +91,10 @@
main-exists? (fn [shape]
(let [component-shape
(cph/get-component-shape (:objects page) shape)
(ctn/get-component-shape (:objects page) shape)
component
(cph/get-component libraries (:component-id component-shape))
(ctf/get-component libraries (:component-id component-shape))
main-shape
(ctn/get-shape component (:shape-ref shape))]
@ -117,7 +118,7 @@
[page root-inst-id libraries]
(let [root-inst (ctn/get-shape page root-inst-id)
component (cph/get-component libraries (:component-id root-inst))
component (ctf/get-component libraries (:component-id root-inst))
shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)
shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst))
@ -126,10 +127,10 @@
main-exists? (fn [shape]
(let [component-shape
(cph/get-component-shape (:objects page) shape)
(ctn/get-component-shape (:objects page) shape)
component
(cph/get-component libraries (:component-id component-shape))
(ctf/get-component libraries (:component-id component-shape))
main-shape
(ctn/get-shape component (:shape-ref shape))]
@ -144,7 +145,7 @@
(defn resolve-component
"Get the component with the given id and all its shapes."
[page component-id libraries]
(let [component (cph/get-component libraries component-id)
(let [component (ctf/get-component libraries component-id)
root-main (ctk/get-component-root component)
shapes-main (cph/get-children-with-self (:objects component) (:id root-main))]

View file

@ -102,8 +102,9 @@
(let [[instance-shape instance-shapes]
(ctn/make-component-instance (ctpl/get-page file-data page-id)
(ctkl/get-component (:data library) component-id)
(:id library)
(gpt/point 0 0))]
(:data library)
(gpt/point 0 0)
true)]
(swap! idmap assoc label (:id instance-shape))
(-> file-data

View file

@ -10,50 +10,10 @@
[clojure.pprint :refer [pprint]]
[app.common.pages.helpers :as cph]))
(t/deftest insert-at-index
;; insert different object
(t/is (= (cph/insert-at-index [:a :b] 1 [:c :d])
[:a :c :d :b]))
;; insert on the start
(t/is (= (cph/insert-at-index [:a :b] 0 [:c])
[:c :a :b]))
;; insert on the end 1
(t/is (= (cph/insert-at-index [:a :b] 2 [:c])
[:a :b :c]))
;; insert on the end with not existing index
(t/is (= (cph/insert-at-index [:a :b] 10 [:c])
[:a :b :c]))
;; insert existing in a contiguous index
(t/is (= (cph/insert-at-index [:a :b] 1 [:a])
[:a :b]))
;; insert existing in the same index
(t/is (= (cph/insert-at-index [:a :b] 0 [:a])
[:a :b]))
;; insert existing in other index case 1
(t/is (= (cph/insert-at-index [:a :b :c] 2 [:a])
[:b :a :c]))
;; insert existing in other index case 2
(t/is (= (cph/insert-at-index [:a :b :c :d] 0 [:d])
[:d :a :b :c]))
;; insert existing in other index case 3
(t/is (= (cph/insert-at-index [:a :b :c :d] 1 [:a])
[:a :b :c :d]))
)
(t/deftest parse-path-name
(t/is (= ["foo" "bar"] (cph/parse-path-name "foo/bar")))
(t/is (= ["" "foo"] (cph/parse-path-name "foo")))
(t/is (= ["" "foo"] (cph/parse-path-name "/foo")))
(t/is (= ["" ""] (cph/parse-path-name "")))
(t/is (= ["" ""] (cph/parse-path-name nil)))
)
(t/is (= ["" ""] (cph/parse-path-name nil))))

View file

@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.file-builder :as fb]
[app.common.media :as cm]
[app.common.types.components-list :as ctkl]
[app.common.uuid :as uuid]
[app.util.dom :as dom]
[app.util.json :as json]
@ -108,7 +109,7 @@
components-stream
(->> files-stream
(rx/flat-map vals)
(rx/filter #(d/not-empty? (get-in % [:data :components])))
(rx/filter #(d/not-empty? (ctkl/components-seq (:data %))))
(rx/flat-map e/parse-library-components))
pages-stream

View file

@ -233,6 +233,19 @@
(->> (filter (comp t/pointer? val) data)
(resolve-pointers id)
(rx/map #(update file :data merge %)))))
(rx/mapcat
(fn [{:keys [id data] :as file}]
;; Resolve all pages of each library, if needed
(->> (rx/from (seq (:pages-index data)))
(rx/merge-map
(fn [[_ page :as kp]]
(if (t/pointer? page)
(resolve-pointer id kp)
(rx/of kp))))
(rx/reduce conj {})
(rx/map
(fn [pages-index]
(assoc-in file [:data :pages-index] pages-index))))))
(rx/reduce conj [])
(rx/map libraries-fetched))))))))
@ -808,8 +821,8 @@
(not (:component-root? shape)))
parent (get objects parent-id)
component-shape (cph/get-component-shape objects shape)
component-shape-parent (cph/get-component-shape objects parent)
component-shape (ctn/get-component-shape objects shape)
component-shape-parent (ctn/get-component-shape objects parent)
detach? (and instance-part? (not= (:id component-shape)
(:id component-shape-parent)))
@ -1638,7 +1651,7 @@
all-objects (merge (:objects page) paste-objects)
changes (-> (dws/prepare-duplicate-changes all-objects page selected delta it)
changes (-> (dws/prepare-duplicate-changes all-objects page selected delta it nil)
(pcb/amend-changes (partial process-rchange media-idx))
(pcb/amend-changes (partial change-add-obj-index paste-objects selected index)))

View file

@ -34,22 +34,20 @@
(declare commit-changes)
(defn- add-group-id
(defn- add-undo-group
[changes state]
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))
prev-item (when-not (or (empty? items) (= index -1))
(get items index))
group-id (:group-id prev-item)
add-group-id? (and
(not (nil? group-id))
(= (get-in changes [:redo-changes 0 :type]) :mod-obj)
(= (get-in prev-item [:redo-changes 0 :type]) :add-obj)) ;; This is a copy-and-move with mouse+alt
]
(cond-> changes add-group-id? (assoc :group-id group-id))))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))
prev-item (when-not (or (empty? items) (= index -1))
(get items index))
undo-group (:undo-group prev-item)
add-undo-group? (and
(not (nil? undo-group))
(= (get-in changes [:redo-changes 0 :type]) :mod-obj)
(= (get-in prev-item [:redo-changes 0 :type]) :add-obj))] ;; This is a copy-and-move with mouse+alt
(cond-> changes add-undo-group? (assoc :undo-group undo-group))))
(def commit-changes? (ptk/type? ::commit-changes))
@ -82,7 +80,7 @@
(pcb/set-stack-undo? stack-undo?)
(pcb/with-objects objects))
ids)
changes (add-group-id changes state)]
changes (add-undo-group changes state)]
(rx/concat
(if (seq (:redo-changes changes))
(let [changes (cond-> changes reg-objects? (pcb/resize-parents ids))
@ -165,15 +163,24 @@
changes)))
(defn commit-changes
"Schedules a list of changes to execute now, and add the corresponding undo changes to
the undo stack.
Options:
- save-undo?: if set to false, do not add undo changes.
- undo-group: if some consecutive changes (or even transactions) share the same
undo-group, they will be undone or redone in a single step
"
[{:keys [redo-changes undo-changes
origin save-undo? file-id group-id stack-undo?]
:or {save-undo? true stack-undo? false}}]
origin save-undo? file-id undo-group stack-undo?]
:or {save-undo? true stack-undo? false undo-group (uuid/next)}}]
(log/debug :msg "commit-changes"
:js/undo-group (str undo-group)
:js/redo-changes redo-changes
:js/undo-changes undo-changes)
(let [error (volatile! nil)
(let [error (volatile! nil)
page-id (:current-page-id @st/state)
frames (changed-frames redo-changes (wsh/lookup-page-objects @st/state))]
frames (changed-frames redo-changes (wsh/lookup-page-objects @st/state))]
(ptk/reify ::commit-changes
cljs.core/IDeref
(-deref [_]
@ -184,8 +191,8 @@
:page-id page-id
:frames frames
:save-undo? save-undo?
:stack-undo? stack-undo?
:group-id group-id})
:undo-group undo-group
:stack-undo? stack-undo?})
ptk/UpdateEvent
(update [_ state]
@ -215,7 +222,7 @@
add-page-id
(fn [{:keys [id type page] :as change}]
(cond-> change
(page-change? type)
(and (page-change? type) (nil? (:page-id change)))
(assoc :page-id (or id (:id page)))))
changes-by-pages
@ -234,5 +241,5 @@
(when (and save-undo? (seq undo-changes))
(let [entry {:undo-changes undo-changes
:redo-changes redo-changes
:group-id group-id}]
:undo-group undo-group}]
(rx/of (dwu/append-undo entry stack-undo?)))))))))))

View file

@ -63,16 +63,16 @@
(when-not (or (empty? items) (= index -1))
(let [item (get items index)
changes (:undo-changes item)
group-id (:group-id item)
undo-group (:undo-group item)
find-first-group-idx (fn ffgidx[index]
(let [item (get items index)]
(if (= (:group-id item) group-id)
(if (= (:undo-group item) undo-group)
(ffgidx (dec index))
(inc index))))
undo-group-index (when group-id
undo-group-index (when undo-group
(find-first-group-idx index))]
(if group-id
(if undo-group
(rx/of (undo-to-index (dec undo-group-index)))
(rx/of (dwu/materialize-undo changes (dec index))
(dch/commit-changes {:redo-changes changes
@ -94,16 +94,16 @@
(when-not (or (empty? items) (= index (dec (count items))))
(let [item (get items (inc index))
changes (:redo-changes item)
group-id (:group-id item)
undo-group (:undo-group item)
find-last-group-idx (fn flgidx [index]
(let [item (get items index)]
(if (= (:group-id item) group-id)
(if (= (:undo-group item) undo-group)
(flgidx (inc index))
(dec index))))
redo-group-index (when group-id
redo-group-index (when undo-group
(find-last-group-idx (inc index)))]
(if group-id
(if undo-group
(rx/of (undo-to-index redo-group-index))
(rx/of (dwu/materialize-undo changes (inc index))
(dch/commit-changes {:redo-changes changes

View file

@ -17,11 +17,11 @@
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.color :as ctc]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.file.media-object :as ctfm]
[app.common.types.pages-list :as ctpl]
[app.common.types.typography :as ctt]
[app.common.uuid :as uuid]
[app.main.data.dashboard :as dd]
@ -317,7 +317,7 @@
shapes (dwg/shapes-for-grouping objects selected)]
(when-not (empty? shapes)
(let [[group _ changes]
(dwlh/generate-add-component it shapes objects page-id file-id components-v2)]
(dwlh/generate-add-component it shapes objects page-id file-id components-v2 dwg/prepare-create-group)]
(when-not (empty? (:redo-changes changes))
(rx/of (dch/commit-changes changes)
(dws/select-shapes (d/ordered-set (:id group)))))))))))
@ -346,8 +346,9 @@
ptk/WatchEvent
(watch [it state _]
(when (and (some? new-name) (not= "" new-name))
(let [data (get state :workspace-data)
[path name] (cph/parse-path-name new-name)
(let [data (get state :workspace-data)
[path name] (cph/parse-path-name new-name)
components-v2 (features/active-feature? state :components-v2)
update-fn
(fn [component]
@ -355,12 +356,15 @@
;; because there are small possibilities of race
;; conditions with component deletion.
(when component
(-> component
(assoc :path path)
(assoc :name name)
(update :objects
(cond-> component
:always
(assoc :path path
:name name)
(not components-v2)
(update :objects
;; Give the same name to the root shape
#(assoc-in % [id :name] name)))))
#(assoc-in % [id :name] name)))))
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
@ -370,30 +374,33 @@
(defn duplicate-component
"Create a new component copied from the one with the given id."
[{:keys [id] :as params}]
[library-id component-id]
(ptk/reify ::duplicate-component
ptk/WatchEvent
(watch [it state _]
(let [libraries (wsh/get-libraries state)
component (cph/get-component libraries id)
library (get libraries library-id)
component (ctkl/get-component (:data library) component-id)
new-name (:name component)
components-v2 (features/active-feature? state :components-v2)
main-instance-page (when components-v2
(wsh/lookup-page state (:main-instance-page component)))
main-instance-shape (when components-v2
(ctn/get-shape main-instance-page (:main-instance-id component)))
(ctf/get-component-page (:data library) component))
[new-component-shape new-component-shapes
new-component (assoc component :id (uuid/next))
[new-component-shape new-component-shapes ; <- null in components-v2
new-main-instance-shape new-main-instance-shapes]
(dwlh/duplicate-component component main-instance-page main-instance-shape)
(dwlh/duplicate-component new-component (:data library))
changes (-> (pcb/empty-changes it nil)
(pcb/with-page main-instance-page)
(pcb/with-objects (:objects main-instance-page))
(pcb/add-objects new-main-instance-shapes {:ignore-touched true})
(pcb/add-component (:id new-component-shape)
(pcb/add-component (if components-v2
(:id new-component)
(:id new-component-shape))
(:path component)
new-name
new-component-shapes
@ -413,12 +420,12 @@
(let [data (get state :workspace-data)]
(if (features/active-feature? state :components-v2)
(let [component (ctkl/get-component data id)
page (ctpl/get-page data (:main-instance-page component))
shape (ctn/get-shape page (:main-instance-id component))]
(rx/of (dwsh/delete-shapes (:id page) #{(:id shape)})))
page (ctf/get-component-page data component)
shape (ctf/get-component-root data component)]
(rx/of (dwsh/delete-shapes (:id page) #{(:id shape)}))) ;; Deleting main root triggers component delete
(let [changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/delete-component id false))]
(pcb/delete-component id))]
(rx/of (dch/commit-changes changes))))))))
(defn restore-component
@ -429,34 +436,18 @@
(ptk/reify ::restore-component
ptk/WatchEvent
(watch [it state _]
(let [file-data (wsh/get-file state library-id)
component (ctf/get-deleted-component file-data component-id)
page (ctpl/get-page file-data (:main-instance-page component))
; Make a new main instance, with the same id of the original
[_main-instance shapes]
(ctn/make-component-instance page
component
(:id file-data)
(gpt/point (:main-instance-x component)
(:main-instance-y component))
{:main-instance? true
:force-id (:main-instance-id component)})
changes (-> (pcb/empty-changes it)
(pcb/with-library-data file-data)
(pcb/with-page page))
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
changes
shapes)
; restore-component change needs to be done after add main instance
; because when undo changes, the orden is inverse
changes (pcb/restore-component changes component-id)]
(rx/of (dch/commit-changes (assoc changes :file-id library-id)))))))
(let [library-data (wsh/get-file state library-id)
component (ctkl/get-deleted-component library-data component-id)
page (ctf/get-component-page library-data component)
shapes (cph/get-children-with-self (:objects component) (:main-instance-id component))
changes (-> (pcb/empty-changes it)
(pcb/with-library-data library-data)
(pcb/with-page page))
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
changes
shapes)
changes (pcb/restore-component changes component-id)]
(rx/of (dch/commit-changes changes))))))
(defn instantiate-component
"Create a new shape in the current page, from the component with the given id
@ -471,8 +462,10 @@
(let [page (wsh/lookup-page state)
libraries (wsh/get-libraries state)
changes (pcb/empty-changes it (:id page))
[new-shape changes]
(dwlh/generate-instantiate-component it
(dwlh/generate-instantiate-component changes
file-id
component-id
position
@ -594,76 +587,79 @@
NOTE: It's possible that the component to update is defined in an
external library file, so this function may cause to modify a file
different of that the one we are currently editing."
[id]
(us/assert ::us/uuid id)
(ptk/reify ::update-component
ptk/WatchEvent
(watch [it state _]
(log/info :msg "UPDATE-COMPONENT of shape" :id (str id))
(let [page-id (get state :current-page-id)
([id] (update-component id nil))
([id undo-group]
(us/assert ::us/uuid id)
(ptk/reify ::update-component
ptk/WatchEvent
(watch [it state _]
(log/info :msg "UPDATE-COMPONENT of shape" :id (str id) :undo-group undo-group)
(let [page-id (get state :current-page-id)
local-file (wsh/get-local-file state)
container (cph/get-container local-file :page page-id)
shape (ctn/get-shape container id)]
local-file (wsh/get-local-file state)
libraries (wsh/get-libraries state)
(when (ctk/in-component-instance? shape)
(let [libraries (wsh/get-libraries state)
container (cph/get-container local-file :page page-id)
shape (ctn/get-shape container id)
changes
(-> (pcb/empty-changes it)
(pcb/set-undo-group undo-group)
(pcb/with-container container)
(dwlh/generate-sync-shape-inverse libraries container id))
changes
(-> (pcb/empty-changes it)
(pcb/with-container container)
(dwlh/generate-sync-shape-inverse libraries container id))
file-id (:component-file shape)
file (wsh/get-file state file-id)
file-id (:component-file shape)
file (wsh/get-file state file-id)
xf-filter (comp
(filter :local-change?)
(map #(dissoc % :local-change?)))
xf-filter (comp
(filter :local-change?)
(map #(dissoc % :local-change?)))
local-changes (-> changes
(update :redo-changes #(into [] xf-filter %))
(update :undo-changes #(into [] xf-filter %)))
local-changes (-> changes
(update :redo-changes #(into [] xf-filter %))
(update :undo-changes #(into [] xf-filter %)))
xf-remove (comp
(remove :local-change?)
(map #(dissoc % :local-change?)))
xf-remove (comp
(remove :local-change?)
(map #(dissoc % :local-change?)))
nonlocal-changes (-> changes
(update :redo-changes #(into [] xf-remove %))
(update :undo-changes #(into [] xf-remove %)))]
nonlocal-changes (-> changes
(update :redo-changes #(into [] xf-remove %))
(update :undo-changes #(into [] xf-remove %)))]
(log/debug :msg "UPDATE-COMPONENT finished"
:js/local-changes (log-changes
(:redo-changes local-changes)
file)
:js/nonlocal-changes (log-changes
(:redo-changes nonlocal-changes)
file))
(log/debug :msg "UPDATE-COMPONENT finished"
:js/local-changes (log-changes
(:redo-changes local-changes)
file)
:js/nonlocal-changes (log-changes
(:redo-changes nonlocal-changes)
file))
(rx/of
(when (seq (:redo-changes local-changes))
(dch/commit-changes (assoc local-changes
:file-id (:id local-file))))
(when (seq (:redo-changes nonlocal-changes))
(dch/commit-changes (assoc nonlocal-changes
:file-id file-id))))))))
(rx/of
(when (seq (:redo-changes local-changes))
(dch/commit-changes (assoc local-changes
:file-id (:id local-file))))
(when (seq (:redo-changes nonlocal-changes))
(dch/commit-changes (assoc nonlocal-changes
:file-id file-id)))))))))))
(defn update-component-sync
[shape-id file-id]
(ptk/reify ::update-component-sync
ptk/WatchEvent
(watch [_ state _]
(let [current-file-id (:current-file-id state)
page (wsh/lookup-page state)
shape (ctn/get-shape page shape-id)
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(update-component shape-id)
(sync-file current-file-id file-id :components (:component-id shape))
(when (not= current-file-id file-id)
(sync-file file-id file-id :components (:component-id shape)))
(dwu/commit-undo-transaction undo-id))))))
([shape-id file-id] (update-component-sync shape-id file-id nil))
([shape-id file-id undo-group]
(ptk/reify ::update-component-sync
ptk/WatchEvent
(watch [_ state _]
(let [current-file-id (:current-file-id state)
page (wsh/lookup-page state)
shape (ctn/get-shape page shape-id)
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(update-component shape-id undo-group)
(sync-file current-file-id file-id :components (:component-id shape) undo-group)
(when (not= current-file-id file-id)
(sync-file file-id file-id :components (:component-id shape) undo-group))
(dwu/commit-undo-transaction undo-id)))))))
(defn update-component-in-bulk
[shapes file-id]
@ -673,7 +669,7 @@
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/map #(update-component-sync (:id %) file-id) (rx/from shapes))
(rx/map #(update-component-sync (:id %) file-id (uuid/next)) (rx/from shapes))
(rx/of (dwu/commit-undo-transaction undo-id)))))))
(declare sync-file-2nd-stage)
@ -690,6 +686,8 @@
([file-id library-id]
(sync-file file-id library-id nil nil))
([file-id library-id asset-type asset-id]
(sync-file file-id library-id asset-type asset-id nil))
([file-id library-id asset-type asset-id undo-group]
(us/assert ::us/uuid file-id)
(us/assert ::us/uuid library-id)
(us/assert (s/nilable #{:colors :components :typographies}) asset-type)
@ -709,7 +707,8 @@
:file (dwlh/pretty-file file-id state)
:library (dwlh/pretty-file library-id state)
:asset-type asset-type
:asset-id asset-id)
:asset-id asset-id
:undo-group undo-group)
(let [file (wsh/get-file state file-id)
sync-components? (or (nil? asset-type) (= asset-type :components))
@ -718,7 +717,8 @@
library-changes (reduce
pcb/concat-changes
(pcb/empty-changes it)
(-> (pcb/empty-changes it)
(pcb/set-undo-group undo-group))
[(when sync-components?
(dwlh/generate-sync-library it file-id :components asset-id library-id state))
(when sync-colors?
@ -727,7 +727,8 @@
(dwlh/generate-sync-library it file-id :typographies asset-id library-id state))])
file-changes (reduce
pcb/concat-changes
(pcb/empty-changes it)
(-> (pcb/empty-changes it)
(pcb/set-undo-group undo-group))
[(when sync-components?
(dwlh/generate-sync-file it file-id :components asset-id library-id state))
(when sync-colors?
@ -758,7 +759,7 @@
:library-id library-id})))
(when (and (seq (:redo-changes library-changes))
sync-components?)
(rx/of (sync-file-2nd-stage file-id library-id asset-id))))))))))
(rx/of (sync-file-2nd-stage file-id library-id asset-id undo-group))))))))))
(defn- sync-file-2nd-stage
"If some components have been modified, we need to launch another synchronization
@ -769,7 +770,7 @@
;; recursively. But for this not to cause an infinite loop, we need to
;; implement updated-at at component level, to detect what components have
;; not changed, and then not to apply sync and terminate the loop.
[file-id library-id asset-id]
[file-id library-id asset-id undo-group]
(us/assert ::us/uuid file-id)
(us/assert ::us/uuid library-id)
(us/assert (s/nilable ::us/uuid) asset-id)
@ -782,7 +783,8 @@
(let [file (wsh/get-file state file-id)
changes (reduce
pcb/concat-changes
(pcb/empty-changes it)
(-> (pcb/empty-changes it)
(pcb/set-undo-group undo-group))
[(dwlh/generate-sync-file it file-id :components asset-id library-id state)
(dwlh/generate-sync-library it file-id :components asset-id library-id state)])]
@ -856,7 +858,7 @@
check-changes
(fn [[event data]]
(let [{:keys [changes save-undo?]} (deref event)
(let [{:keys [changes save-undo? undo-group]} (deref event)
components-changed (reduce #(into %1 (ch/components-changed data %2))
#{}
changes)]
@ -864,7 +866,7 @@
(log/info :msg "DETECTED COMPONENTS CHANGED"
:ids (map str components-changed))
(run! st/emit!
(map #(update-component-sync % (:id data))
(map #(update-component-sync % (:id data) undo-group)
components-changed)))))]
(when components-v2

View file

@ -7,6 +7,7 @@
(ns app.main.data.workspace.libraries-helpers
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.logging :as log]
@ -17,11 +18,12 @@
[app.common.text :as txt]
[app.common.types.color :as ctc]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.shape-tree :as ctst]
[app.common.types.typography :as cty]
[app.main.data.workspace.groups :as dwg]
[app.common.uuid :as uuid]
[app.main.data.workspace.state-helpers :as wsh]
[cljs.spec.alpha :as s]
[clojure.set :as set]))
@ -63,7 +65,7 @@
"If there is exactly one id, and it's a group or a frame, and not already a component,
use it as root. Otherwise, create a group that contains all ids. Then, make a
component with it, and link all shapes to their corresponding one in the component."
[it shapes objects page-id file-id components-v2]
[it shapes objects page-id file-id components-v2 prepare-create-group]
(let [[group changes]
(if (and (= (count shapes) 1)
(or (= (:type (first shapes)) :group)
@ -74,67 +76,91 @@
(let [group-name (if (= 1 (count shapes))
(:name (first shapes))
"Component 1")]
(dwg/prepare-create-group it
objects
page-id
shapes
group-name
(not (ctk/instance-root? (first shapes))))))
(prepare-create-group it ; This function needs to be passed as argument
objects ; to avoid a circular dependence
page-id
shapes
group-name
(not (ctk/instance-root? (first shapes))))))
name (:name group)
[path name] (cph/parse-path-name name)
[new-shape new-shapes updated-shapes]
(ctn/make-component-shape group objects file-id components-v2)
[root-shape new-shapes updated-shapes]
(if-not components-v2
(ctn/make-component-shape group objects file-id components-v2)
(let [new-id (uuid/next)]
[(assoc group :id new-id)
nil
[(assoc group
:component-id new-id
:component-file file-id
:component-root? true
:main-instance? true)]]))
changes (-> changes
(pcb/add-component (:id new-shape)
(pcb/add-component (:id root-shape)
path
name
new-shapes
updated-shapes
(:id group)
page-id))]
[group new-shape changes]))
page-id))]
[group (:id root-shape) changes]))
(defn duplicate-component
"Clone the root shape of the component and all children. Generate new
ids from all of them."
[component main-instance-page main-instance-shape]
(let [position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape))
(gpt/point (+ (:width main-instance-shape) 50) 0))
[component library-data]
(let [components-v2 (dm/get-in library-data [:options :components-v2])]
(if components-v2
component-root (ctk/get-component-root component)
(let [main-instance-page (ctf/get-component-page library-data component)
main-instance-shape (ctf/get-component-root library-data component)
[new-component-shape new-component-shapes _]
(ctst/clone-object component-root
nil
(get component :objects)
identity)
position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape))
(gpt/point (+ (:width main-instance-shape) 50) 0))
component-instance-extra-data (if components-v2 {:main-instance? true} {})
[new-instance-shape new-instance-shapes]
(when (and (some? main-instance-page) (some? main-instance-shape))
(ctn/make-component-instance main-instance-page
{:id (:id new-component-shape)
:name (:name new-component-shape)
:objects (d/index-by :id new-component-shapes)}
(:component-file main-instance-shape)
position))]
[new-instance-shape new-instance-shapes]
(when (and (some? main-instance-page) (some? main-instance-shape))
(ctn/make-component-instance main-instance-page
component
library-data
position
true
component-instance-extra-data))]
[new-component-shape new-component-shapes
new-instance-shape new-instance-shapes]))
[nil nil new-instance-shape new-instance-shapes])
(let [component-root (d/seek #(nil? (:parent-id %)) (vals (:objects component)))
[new-component-shape new-component-shapes _]
(ctst/clone-object component-root
nil
(get component :objects)
identity)]
[new-component-shape new-component-shapes nil nil]))))
(defn generate-instantiate-component
"Generate changes to create a new instance from a component."
[it file-id component-id position page libraries]
(let [component (cph/get-component libraries file-id component-id)
[changes file-id component-id position page libraries]
(let [component (ctf/get-component libraries file-id component-id)
library (get libraries file-id)
components-v2 (dm/get-in library [:data :options :components-v2])
[new-shape new-shapes]
(ctn/make-component-instance page component file-id position)
(ctn/make-component-instance page
component
(:data library)
position
components-v2)
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
(pcb/empty-changes it (:id page))
changes
new-shapes)]
[new-shape changes]))
@ -217,7 +243,7 @@
(let [file (wsh/get-file state file-id)
components-v2 (get-in file [:options :components-v2])]
(loop [local-components (vals (get file :components))
(loop [local-components (ctkl/components-seq file)
changes (pcb/empty-changes it)]
(if-let [local-component (first local-components)]
(recur (next local-components)
@ -447,47 +473,47 @@
;; but it's not touched.
(defn generate-sync-shape-direct
"Generate changes to synchronize one shape that 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."
[changes libraries container shape-id reset? components-v2]
(log/debug :msg "Sync shape direct" :shape (str shape-id) :reset? reset?)
(let [shape-inst (ctn/get-shape container shape-id)
component (cph/get-component libraries
(:component-file shape-inst)
(:component-id shape-inst))
component (or component
(and reset?
(ctf/get-deleted-component
(get-in libraries [(:component-file shape-inst) :data])
(:component-id shape-inst))))
shape-main (when component
(ctn/get-shape component (:shape-ref shape-inst)))
(let [shape-inst (ctn/get-shape container shape-id)]
(if (ctk/in-component-instance? shape-inst)
(let [library (dm/get-in libraries [(:component-file shape-inst) :data])
component (or (ctkl/get-component library (:component-id shape-inst))
(and reset?
(ctkl/get-deleted-component library (:component-id shape-inst))))
initial-root? (:component-root? shape-inst)
shape-main (when component
(ctf/get-ref-shape library component shape-inst))
root-inst shape-inst
root-main (when component
(ctk/get-component-root component))]
initial-root? (:component-root? shape-inst)
(if component
(generate-sync-shape-direct-recursive changes
container
shape-inst
component
shape-main
root-inst
root-main
reset?
initial-root?
components-v2)
root-inst shape-inst
root-main (when component
(ctf/get-component-root library component))]
(if component
(generate-sync-shape-direct-recursive changes
container
shape-inst
component
library
shape-main
root-inst
root-main
reset?
initial-root?
components-v2)
; If the component is not found, because the master component has been
; deleted or the library unlinked, do nothing in v2 or detach in v1.
(if components-v2
changes
(generate-detach-instance changes container shape-id)))))
(if components-v2
changes
(generate-detach-instance changes container shape-id))))
changes)))
(defn- generate-sync-shape-direct-recursive
[changes container shape-inst component shape-main root-inst root-main reset? initial-root? components-v2]
[changes container shape-inst component library shape-main root-inst root-main reset? initial-root? components-v2]
(log/debug :msg "Sync shape direct recursive"
:shape (str (:name shape-inst))
:component (:name component))
@ -522,20 +548,22 @@
set-remote-synced?
(change-remote-synced shape-inst container true))
children-inst (mapv #(ctn/get-shape container %)
(:shapes shape-inst))
children-main (mapv #(ctn/get-shape component %)
(:shapes shape-main))
component-container (ctf/get-component-container library component)
children-inst (mapv #(ctn/get-shape container %)
(:shapes shape-inst))
children-main (mapv #(ctn/get-shape component-container %)
(:shapes shape-main))
only-inst (fn [changes child-inst]
(if-not (and omit-touched?
(contains? (:touched shape-inst)
:shapes-group))
(remove-shape changes
child-inst
container
omit-touched?)
changes))
(contains? (:touched shape-inst)
:shapes-group))
(remove-shape changes
child-inst
container
omit-touched?)
changes))
only-main (fn [changes child-main]
(if-not (and omit-touched?
@ -545,7 +573,7 @@
child-main
(d/index-of children-main
child-main)
component
component-container
container
root-inst
root-main
@ -558,6 +586,7 @@
container
child-inst
component
library
child-main
root-inst
root-main
@ -567,12 +596,12 @@
moved (fn [changes child-inst child-main]
(move-shape
changes
child-inst
(d/index-of children-inst child-inst)
(d/index-of children-main child-main)
container
omit-touched?))]
changes
child-inst
(d/index-of children-inst child-inst)
(d/index-of children-main child-main)
container
omit-touched?))]
(compare-children changes
children-inst
@ -588,22 +617,22 @@
the values in the shape and all its children."
[changes libraries container shape-id]
(log/debug :msg "Sync shape inverse" :shape (str shape-id))
(let [shape-inst (ctn/get-shape container shape-id)
component (cph/get-component libraries
(:component-file shape-inst)
(:component-id shape-inst))
shape-main (ctn/get-shape component (:shape-ref shape-inst))
(let [shape-inst (ctn/get-shape container shape-id)
library (dm/get-in libraries [(:component-file shape-inst) :data])
component (ctkl/get-component library (:component-id shape-inst))
shape-main (ctf/get-ref-shape library component shape-inst)
initial-root? (:component-root? shape-inst)
initial-root? (:component-root? shape-inst)
root-inst shape-inst
root-main (ctk/get-component-root component)]
root-inst shape-inst
root-main (ctf/get-component-root library component)]
(if component
(generate-sync-shape-inverse-recursive changes
container
shape-inst
component
library
shape-main
root-inst
root-main
@ -611,7 +640,7 @@
changes)))
(defn- generate-sync-shape-inverse-recursive
[changes container shape-inst component shape-main root-inst root-main initial-root?]
[changes container shape-inst component library shape-main root-inst root-main initial-root?]
(log/trace :msg "Sync shape inverse recursive"
:shape (str (:name shape-inst))
:component (:name component))
@ -619,7 +648,7 @@
(if (nil? shape-main)
;; This should not occur, but protect against it in any case
changes
(let [component-container (cph/make-container component :component)
(let [component-container (ctf/get-component-container library component)
omit-touched? false
set-remote-synced? (not initial-root?)
@ -650,7 +679,7 @@
children-inst (mapv #(ctn/get-shape container %)
(:shapes shape-inst))
children-main (mapv #(ctn/get-shape component %)
children-main (mapv #(ctn/get-shape component-container %)
(:shapes shape-main))
only-inst (fn [changes child-inst]
@ -659,6 +688,7 @@
(d/index-of children-inst
child-inst)
component
component-container
container
root-inst
root-main))
@ -674,6 +704,7 @@
container
child-inst
component
library
child-main
root-inst
root-main
@ -681,12 +712,12 @@
moved (fn [changes child-inst child-main]
(move-shape
changes
child-main
(d/index-of children-main child-main)
(d/index-of children-inst child-inst)
component-container
false))
changes
child-main
(d/index-of children-main child-main)
(d/index-of children-inst child-inst)
component-container
false))
changes
(compare-children changes
@ -763,9 +794,9 @@
(moved-cb child-inst' child-main)))))))))))
(defn- add-shape-to-instance
[changes component-shape index component container root-instance root-main omit-touched? set-remote-synced?]
[changes component-shape index component-page container root-instance root-main omit-touched? set-remote-synced?]
(log/info :msg (str "ADD [P] " (:name component-shape)))
(let [component-parent-shape (ctn/get-shape component (:parent-id component-shape))
(let [component-parent-shape (ctn/get-shape component-page (:parent-id component-shape))
parent-shape (d/seek #(ctk/is-main-of? component-parent-shape %)
(cph/get-children-with-self (:objects container)
(:id root-instance)))
@ -793,7 +824,7 @@
[_ new-shapes _]
(ctst/clone-object component-shape
(:id parent-shape)
(get component :objects)
(get component-page :objects)
update-new-shape
update-original-shape)
@ -831,14 +862,14 @@
changes')))
(defn- add-shape-to-main
[changes shape index component page root-instance root-main]
[changes shape index component component-container page root-instance root-main]
(log/info :msg (str "ADD [C] " (:name shape)))
(let [parent-shape (ctn/get-shape page (:parent-id shape))
component-parent-shape (d/seek #(ctk/is-main-of? % parent-shape)
(cph/get-children-with-self (:objects component)
(cph/get-children-with-self (:objects component-container)
(:id root-main)))
all-parents (into [(:id component-parent-shape)]
(cph/get-parent-ids (:objects component)
(cph/get-parent-ids (:objects component-container)
(:id component-parent-shape)))
update-new-shape (fn [new-shape _original-shape]
@ -854,20 +885,24 @@
[_new-shape new-shapes updated-shapes]
(ctst/clone-object shape
(:id component-parent-shape)
(get page :objects)
update-new-shape
update-original-shape)
(:id component-parent-shape)
(get page :objects)
update-new-shape
update-original-shape)
add-obj-change (fn [changes shape']
(update changes :redo-changes conj
{:type :add-obj
:id (:id shape')
:component-id (:id component)
:parent-id (:parent-id shape')
:index index
:ignore-touched true
:obj shape'}))
(cond-> (make-change
component-container
{:type :add-obj
:id (:id shape')
:parent-id (:parent-id shape')
:index index
:ignore-touched true
:obj shape'})
(ctn/page? component-container)
(assoc :frame-id (:frame-id shape')))))
mod-obj-change (fn [changes shape']
(update changes :redo-changes conj
@ -899,8 +934,8 @@
changes' (reduce add-obj-change changes new-shapes)
changes' (update changes' :redo-changes conj {:type :reg-objects
:component-id (:id component)
:shapes all-parents})
:component-id (:id component)
:shapes all-parents})
changes' (reduce mod-obj-change changes' updated-shapes)
changes' (reduce del-obj-change changes' new-shapes)]

View file

@ -14,12 +14,14 @@
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.component :as ctk]
[app.common.types.page :as ctp]
[app.common.types.shape.interactions :as ctsi]
[app.common.uuid :as uuid]
[app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.collapse :as dwc]
[app.main.data.workspace.libraries-helpers :as dwlh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.zoom :as dwz]
@ -330,7 +332,7 @@
(defn prepare-duplicate-changes
"Prepare objects to duplicate: generate new id, give them unique names,
move to the desired position, and recalculate parents and frames as needed."
[all-objects page ids delta it]
[all-objects page ids delta it libraries]
(let [shapes (map (d/getf all-objects) ids)
unames (volatile! (cp/retrieve-used-names (:objects page)))
update-unames! (fn [new-name] (vswap! unames conj new-name))
@ -351,7 +353,8 @@
update-unames!
ids-map
%2
delta)
delta
libraries)
init-changes))]
(-> changes
@ -359,11 +362,25 @@
(prepare-duplicate-guides shapes page ids-map delta))))
(defn- prepare-duplicate-shape-change
([changes objects page unames update-unames! ids-map obj delta]
(prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta (:frame-id obj) (:parent-id obj)))
([changes objects page unames update-unames! ids-map obj delta libraries]
(prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta libraries (:frame-id obj) (:parent-id obj)))
([changes objects page unames update-unames! ids-map obj delta frame-id parent-id]
(if (some? obj)
([changes objects page unames update-unames! ids-map obj delta libraries frame-id parent-id]
(cond
(nil? obj)
changes
(ctk/main-instance? obj)
(let [[_new-shape changes]
(dwlh/generate-instantiate-component changes
(:component-file obj)
(:component-id obj)
(gpt/point (:x obj) (:y obj))
page
libraries)]
changes)
:else
(let [frame? (cph/frame-shape? obj)
new-id (ids-map (:id obj))
parent-id (or parent-id frame-id)
@ -392,11 +409,11 @@
ids-map
child
delta
libraries
(if frame? new-id frame-id)
new-id))
changes
(map (d/getf objects) (:shapes obj))))
changes)))
(map (d/getf objects) (:shapes obj)))))))
(defn- prepare-duplicate-flows
[changes shapes page ids-map]
@ -531,7 +548,7 @@
(defn duplicate-selected
([move-delta?]
(duplicate-selected move-delta? false))
([move-delta? add-group-id?]
([move-delta? add-undo-group?]
(ptk/reify ::duplicate-selected
ptk/WatchEvent
(watch [it state _]
@ -545,10 +562,12 @@
(calc-duplicate-delta obj state objects)
(gpt/point 0 0))
changes (->> (prepare-duplicate-changes objects page selected delta it)
libraries (wsh/get-libraries state)
changes (->> (prepare-duplicate-changes objects page selected delta it libraries)
(duplicate-changes-update-indices objects selected))
changes (cond-> changes add-group-id? (assoc :group-id (uuid/random)))
changes (cond-> changes add-undo-group? (assoc :undo-group (uuid/random)))
id-original (first selected)

View file

@ -14,6 +14,7 @@
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.page :as ctp]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
@ -171,7 +172,7 @@
;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily).
(let [shape (get objects shape-id)
component-shape (cph/get-component-shape objects shape)]
component-shape (ctn/get-component-shape objects shape)]
(and (ctk/in-component-instance? shape)
(not= shape component-shape)
(not (ctk/main-instance? component-shape)))))
@ -291,7 +292,7 @@
changes (reduce (fn [changes component-id]
;; It's important to delete the component before the main instance, because we
;; need to store the instance position if we want to restore it later.
(pcb/delete-component changes component-id components-v2))
(pcb/delete-component changes component-id))
changes
components-to-delete)

View file

@ -40,10 +40,6 @@
([state page-id]
(dm/get-in state [:workspace-data :pages-index page-id :options])))
(defn lookup-component-objects
([state component-id]
(dm/get-in state [:workspace-data :components component-id :objects])))
(defn lookup-local-components
([state]
(dm/get-in state [:workspace-data :components])))

View file

@ -67,11 +67,11 @@
(add-undo-entry state entry))))
(defn- accumulate-undo-entry
[state {:keys [undo-changes redo-changes group-id]}]
[state {:keys [undo-changes redo-changes undo-group]}]
(-> state
(update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %))
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))
(assoc-in [:workspace-undo :transaction :group-id] group-id)))
(assoc-in [:workspace-undo :transaction :undo-group] undo-group)))
(defn append-undo
[entry stack?]
@ -79,29 +79,31 @@
(ptk/reify ::append-undo
ptk/UpdateEvent
(update [_ state]
(cond
(and (get-in state [:workspace-undo :transaction])
(or (not stack?)
(d/not-empty? (get-in state [:workspace-undo :transaction :undo-changes]))
(d/not-empty? (get-in state [:workspace-undo :transaction :redo-changes]))))
(accumulate-undo-entry state entry)
(cond
(and (get-in state [:workspace-undo :transaction])
(or (not stack?)
(d/not-empty? (get-in state [:workspace-undo :transaction :undo-changes]))
(d/not-empty? (get-in state [:workspace-undo :transaction :redo-changes]))))
(accumulate-undo-entry state entry)
stack?
(stack-undo-entry state entry)
stack?
(stack-undo-entry state entry)
:else
(add-undo-entry state entry)))))
:else
(add-undo-entry state entry)))))
(def empty-tx
{:undo-changes [] :redo-changes []})
(defn start-undo-transaction [id]
(defn start-undo-transaction
"Start a transaction, so that every changes inside are added together in a single undo entry."
[id]
(ptk/reify ::start-undo-transaction
ptk/UpdateEvent
(update [_ state]
;; We commit the old transaction before starting the new one
(let [current-tx (get-in state [:workspace-undo :transaction])
pending-tx (get-in state [:workspace-undo :transactions-pending])]
(let [current-tx (get-in state [:workspace-undo :transaction])
pending-tx (get-in state [:workspace-undo :transactions-pending])]
(cond-> state
(nil? current-tx) (assoc-in [:workspace-undo :transaction] empty-tx)
(nil? pending-tx) (assoc-in [:workspace-undo :transactions-pending] #{id})

View file

@ -208,7 +208,10 @@
data (:workspace-data state)]
(-> file
(dissoc :data)
(assoc :pages (:pages data)))))
(assoc :options (:options data)
:components (:components data)
:pages (:pages data)
:pages-index (:pages-index data)))))
st/state =))
(def workspace-data

View file

@ -310,16 +310,16 @@
update-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
(reduce update-fn objects children-ids))))
root-shape (get objects root-shape-id)
width (* (:width root-shape) zoom)
height (* (:height root-shape) zoom)
vbox (format-viewbox {:width (:width root-shape 0)
:height (:height root-shape 0)})
root-shape' (get objects root-shape-id)
width (* (:width root-shape') zoom)
height (* (:height root-shape') zoom)
vbox (format-viewbox {:width (:width root-shape' 0)
:height (:height root-shape' 0)})
root-shape-wrapper
(mf/use-memo
(mf/deps objects root-shape)
(mf/deps objects root-shape')
(fn []
(case (:type root-shape)
(case (:type root-shape')
:group (group-wrapper-factory objects)
:frame (frame-wrapper-factory objects))))]
@ -332,9 +332,9 @@
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
:fill "none"}
[:> shape-container {:shape root-shape}
[:> shape-container {:shape root-shape'}
[:& (mf/provider muc/is-component?) {:value true}
[:& root-shape-wrapper {:shape root-shape :view-box vbox}]]]]))
[:& root-shape-wrapper {:shape root-shape' :view-box vbox}]]]]))
(mf/defc object-svg
{::mf/wrap [mf/memo]}

View file

@ -243,7 +243,9 @@
(events/unlistenByKey key1))))
(mf/use-effect on-resize)
[:div.dashboard-content {:on-click #(st/emit! (dd/clear-selected-files)) :ref container}
[:div.dashboard-content {:on-click #(st/emit! (dd/clear-selected-files))
:ref container}
(case section
:dashboard-projects
[:*

View file

@ -105,12 +105,13 @@
[:span.num-assets (str "\u00A0(") (:count components) ")"]] ;; Unicode 00A0 is non-breaking space
[:div.asset-list
(for [component (:sample components)]
[:div.asset-list-item {:key (str "assets-component-" (:id component))}
[:& component-svg {:group (get-in component [:objects (:id component)])
:objects (:objects component)}]
[:div.name-block
[:span.item-name {:title (:name component)}
(:name component)]]])
(let [root-id (or (:main-instance-id component) (:id component))] ;; Check for components-v2 in library
[:div.asset-list-item {:key (str "assets-component-" (:id component))}
[:& component-svg {:root-shape (get-in component [:objects root-id])
:objects (:objects component)}] ;; Components in the summary come loaded with objects, even in v2
[:div.name-block
[:span.item-name {:title (:name component)}
(:name component)]]]))
(when (> (:count components) (count (:sample components)))
[:div.asset-list-item
[:div.name-block

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.libraries
(:require
[app.common.data :as d]
[app.common.types.components-list :as ctkl]
[app.main.data.dashboard :as dd]
[app.main.data.modal :as modal]
[app.main.data.workspace.libraries :as dwl]
@ -44,7 +45,7 @@
(defn local-library-str
[library]
(let [components-count (count (get-in library [:data :components] []))
(let [components-count (count (or (ctkl/components-seq (:data library)) []))
graphics-count (count (get-in library [:data :media] []))
colors-count (count (get-in library [:data :colors] []))
typography-count (count (get-in library [:data :typographies] []))]

View file

@ -12,7 +12,8 @@
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.text :as txt]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.config :as cf]
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
@ -365,13 +366,20 @@
;;---- Components box ----
(mf/defc components-item
[{:keys [component renaming listing-thumbs? selected-components
[{:keys [component renaming listing-thumbs? selected-components file
on-asset-click on-context-menu on-drag-start do-rename cancel-rename
selected-components-full selected-components-paths]}]
(let [item-ref (mf/use-ref)
dragging? (mf/use-state false)
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
components-v2 (mf/use-ctx ctx/components-v2)
file (or (:data file) file)
root-shape (ctf/get-component-root file component)
component-container (if components-v2
(ctf/get-component-page file component)
component)
unselect-all
(mf/use-fn
(fn []
@ -440,27 +448,29 @@
:on-drag-over on-drag-over
:on-drop on-drop}
[:& component-svg {:root-shape (ctk/get-component-root component)
:objects (:objects component)}]
(let [renaming? (= renaming (:id component))]
(when (and (some? root-shape) (some? component-container))
[:*
[:& editable-label
{:class-name (dom/classnames
:cell-name listing-thumbs?
:item-name (not listing-thumbs?)
:editing renaming?)
:value (cph/merge-path-item (:path component) (:name component))
:tooltip (cph/merge-path-item (:path component) (:name component))
:display-value (:name component)
:editing? renaming?
:disable-dbl-click? true
:on-change do-rename
:on-cancel cancel-rename}]
(when @dragging?
[:div.dragging])])]))
[:& component-svg {:root-shape root-shape
:objects (:objects component-container)}]
(let [renaming? (= renaming (:id component))]
[:*
[:& editable-label
{:class-name (dom/classnames
:cell-name listing-thumbs?
:item-name (not listing-thumbs?)
:editing renaming?)
:value (cph/merge-path-item (:path component) (:name component))
:tooltip (cph/merge-path-item (:path component) (:name component))
:display-value (:name component)
:editing? renaming?
:disable-dbl-click? true
:on-change do-rename
:on-cancel cancel-rename}]
(when @dragging?
[:div.dragging])])])]))
(mf/defc components-group
[{:keys [file-id prefix groups open-groups renaming listing-thumbs? selected-components on-asset-click
[{:keys [file prefix groups open-groups renaming listing-thumbs? selected-components on-asset-click
on-drag-start do-rename cancel-rename on-rename-group on-group on-ungroup on-context-menu
selected-components-full]}]
(let [group-open? (get open-groups prefix true)
@ -495,7 +505,7 @@
:on-drag-leave on-drag-leave
:on-drag-over on-drag-over
:on-drop on-drop}
[:& asset-group-title {:file-id file-id
[:& asset-group-title {:file-id (:id file)
:box :components
:path prefix
:group-open? group-open?
@ -528,6 +538,7 @@
:key (:id component)
:renaming renaming
:listing-thumbs? listing-thumbs?
:file file
:selected-components selected-components
:on-asset-click on-asset-click
:on-context-menu on-context-menu
@ -539,7 +550,7 @@
:selected-components-paths selected-components-paths}])])
(for [[path-item content] groups]
(when-not (empty? path-item)
[:& components-group {:file-id file-id
[:& components-group {:file file
:prefix (cph/merge-path-item prefix path-item)
:groups content
:open-groups open-groups
@ -556,7 +567,7 @@
:selected-components-full selected-components-full}]))])]))
(mf/defc components-box
[{:keys [file-id local? components listing-thumbs? open? reverse-sort? open-groups selected-assets
[{:keys [file local? components listing-thumbs? open? reverse-sort? open-groups selected-assets
on-asset-click on-assets-delete on-clear-selection] :as props}]
(let [input-ref (mf/use-ref nil)
state (mf/use-state {:renaming nil
@ -579,14 +590,14 @@
add-component
(mf/use-fn
(fn []
#(st/emit! (dwl/set-assets-box-open file-id :components true))
#(st/emit! (dwl/set-assets-box-open (:id file) :components true))
(dom/click (mf/ref-val input-ref))))
on-file-selected
(mf/use-fn
(mf/deps file-id)
(mf/deps file)
(fn [blobs]
(let [params {:file-id file-id
(let [params {:file-id (:id file)
:blobs (seq blobs)}]
(st/emit! (dwm/upload-media-components params)
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
@ -598,22 +609,22 @@
(fn []
(let [undo-id (js/Symbol)]
(if (empty? selected-components)
(st/emit! (dwl/duplicate-component {:id (:component-id @state)}))
(do
(st/emit! (dwu/start-undo-transaction undo-id))
(apply st/emit! (map #(dwl/duplicate-component {:id %}) selected-components))
(st/emit! (dwu/commit-undo-transaction undo-id)))))))
(st/emit! (dwl/duplicate-component (:id file) (:component-id @state)))
(do
(st/emit! (dwu/start-undo-transaction undo-id))
(apply st/emit! (map (partial dwl/duplicate-component (:id file)) selected-components))
(st/emit! (dwu/commit-undo-transaction undo-id)))))))
on-delete
(mf/use-fn
(mf/deps @state file-id multi-components? multi-assets?)
(mf/deps @state file multi-components? multi-assets?)
(fn []
(let [undo-id (js/Symbol)]
(if (or multi-components? multi-assets?)
(on-assets-delete)
(st/emit! (dwu/start-undo-transaction undo-id)
(dwl/delete-component {:id (:component-id @state)})
(dwl/sync-file file-id file-id :components (:component-id @state))
(dwl/sync-file (:id file) (:id file) :components (:component-id @state))
(dwu/commit-undo-transaction undo-id))))))
on-rename
@ -716,7 +727,7 @@
on-drag-start
(mf/use-fn
(fn [component event]
(dnd/set-data! event "penpot/component" {:file-id file-id
(dnd/set-data! event "penpot/component" {:file-id (:id file)
:component component})
(dnd/set-allowed-effect! event "move")))
@ -734,7 +745,7 @@
(when (and main-instance-id main-instance-page) ;; Only when :components-v2 is enabled
(st/emit! (dw/go-to-main-instance main-instance-page main-instance-id))))))]
[:& asset-section {:file-id file-id
[:& asset-section {:file-id (:id file)
:title (tr "workspace.assets.components")
:box :components
:assets-count (count components)
@ -750,7 +761,7 @@
:on-selected on-file-selected}]])])
[:& asset-section-block {:role :content}
[:& components-group {:file-id file-id
[:& components-group {:file file
:prefix ""
:groups groups
:open-groups open-groups
@ -1931,8 +1942,8 @@
(l/derived (fn [state]
(let [wfile (:workspace-data state)]
(if (= (:id wfile) id)
(vals (get wfile :components))
(vals (get-in state [:workspace-libraries id :data :components])))))
(ctkl/components-seq wfile)
(ctkl/components-seq (get-in state [:workspace-libraries id :data])))))
st/state =))
(defn file-typography-ref
@ -2158,7 +2169,7 @@
i/listing-thumbs)]]
(when show-components?
[:& components-box {:file-id (:id file)
[:& components-box {:file file
:local? local?
:components components
:listing-thumbs? listing-thumbs?

View file

@ -6,7 +6,7 @@
(ns app.main.ui.workspace.sidebar.options.menus.component
(:require
[app.common.types.components-list :as ctkl]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]

View file

@ -11,6 +11,7 @@
[app.common.logging :as l]
[app.common.math :as mth]
[app.common.spec :as us]
[app.common.types.components-list :as ctkl]
[app.common.uri :as u]
[app.main.data.fonts :as df]
[app.main.features :as features]
@ -247,7 +248,7 @@
:border "0px solid black"}]]])]
[:ul.nav
(for [[id data] (get-in file [:data :components])]
(for [[id data] (ctkl/components (:data file))]
(let [on-click (fn [event]
(dom/prevent-default event)
(swap! state assoc :component-id id))]

View file

@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.media :as cm]
[app.common.text :as ct]
[app.common.types.components-list :as ctkl]
[app.config :as cfg]
[app.main.render :as r]
[app.main.repo :as rp]
@ -51,7 +52,7 @@
:version current-version
:libraries (->> (:libraries file) (into #{}) (mapv str))
:exportType (d/name export-type)
:hasComponents (d/not-empty? (get-in file [:data :components]))
:hasComponents (d/not-empty? (ctkl/components-seq (:data file)))
:hasDeletedComponents (d/not-empty? (get-in file [:data :deleted-components]))
:hasMedia (d/not-empty? (get-in file [:data :media]))
:hasColors (d/not-empty? (get-in file [:data :colors]))
@ -329,7 +330,7 @@
(select-keys (get external-refs (:id file))))
media (-> (get-in file [:data :media])
(select-keys (get external-refs (:id file))))
components (-> (get-in file [:data :components])
components (-> (ctkl/components (:data file))
(select-keys (get external-refs (:id file))))]
(cond-> target
(d/not-empty? colors)
@ -434,7 +435,7 @@
components-stream
(->> files-stream
(rx/flat-map vals)
(rx/filter #(d/not-empty? (get-in % [:data :components])))
(rx/filter #(d/not-empty? (ctkl/components-seq (:data %))))
(rx/flat-map parse-library-components))
deleted-components-stream

View file

@ -7,15 +7,37 @@
(ns frontend-tests.helpers.libraries
(:require
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.main.data.workspace.state-helpers :as wsh]
[cljs.pprint :refer [pprint]]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]))
;; ---- Helpers to manage libraries and synchronization
(defn is-main-instance-root
[shape]
(t/is (nil? (:shape-ref shape)))
(t/is (some? (:component-id shape)))
(t/is (= (:component-root? shape) true)))
(defn is-main-instance-subroot
[shape]
(t/is (some? (:component-id shape))) ; shape-ref may or may be not nil
(t/is (= (:component-root? shape) true)))
(defn is-main-instance-child
[shape]
(t/is (nil? (:component-id shape))) ; shape-ref may or may be not nil
(t/is (nil? (:component-file shape)))
(t/is (nil? (:component-root? shape))))
(defn is-main-instance-inner
[shape]
(if (some? (:component-id shape))
(is-main-instance-subroot shape)
(is-main-instance-child shape)))
(defn is-instance-root
[shape]
(t/is (some? (:shape-ref shape)))
@ -58,27 +80,25 @@
(defn resolve-instance
"Get the shape with the given id and all its children, and
verify that they are a well constructed instance tree."
[state root-inst-id]
(let [page (thp/current-page state)
root-inst (ctn/get-shape page root-inst-id)
shapes-inst (cph/get-children-with-self (:objects page)
root-inst-id)]
(is-instance-root (first shapes-inst))
(run! is-instance-inner (rest shapes-inst))
[state root-id]
(let [page (thp/current-page state)
shapes (cph/get-children-with-self (:objects page)
root-id)]
(is-instance-root (first shapes))
(run! is-instance-inner (rest shapes))
shapes-inst))
shapes))
(defn resolve-noninstance
"Get the shape with the given id and all its children, and
verify that they are not a component instance."
[state root-inst-id]
(let [page (thp/current-page state)
root-inst (ctn/get-shape page root-inst-id)
shapes-inst (cph/get-children-with-self (:objects page)
root-inst-id)]
(run! is-noninstance shapes-inst)
[state root-id]
(let [page (thp/current-page state)
shapes (cph/get-children-with-self (:objects page)
root-id)]
(run! is-noninstance shapes)
shapes-inst))
shapes))
(defn resolve-instance-and-main
"Get the shape with the given id and all its children, and also
@ -87,38 +107,39 @@
(resolve-instance-and-main state root-inst-id false))
([state root-inst-id subinstance?]
(let [page (thp/current-page state)
root-inst (ctn/get-shape page root-inst-id)
(let [page (thp/current-page state)
root-inst (ctn/get-shape page root-inst-id)
main-instance? (:main-instance? root-inst)
libs (wsh/get-libraries state)
component (cph/get-component libs (:component-id root-inst))
libs (wsh/get-libraries state)
component (ctf/get-component libs (:component-id root-inst))
library (ctf/get-component-library libs root-inst)
shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)
shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst))
unique-refs (into #{} (map :shape-ref) shapes-inst)
shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)
shapes-main (ctf/get-component-shapes (:data library) component)
unique-refs (into #{} (map :shape-ref) shapes-inst)
main-exists? (fn [shape]
(let [component-shape
(cph/get-component-shape (:objects page) shape)
component
(cph/get-component libs (:component-id component-shape))
main-shape
(ctn/get-shape component (:shape-ref shape))]
(let [main-shape (ctf/get-ref-shape (:data library) component shape)]
(t/is (some? main-shape))))]
;; Validate that the instance tree is well constructed
(if subinstance?
(is-instance-subroot (first shapes-inst))
(is-instance-root (first shapes-inst)))
(run! is-instance-inner (rest shapes-inst))
(t/is (= (count shapes-inst)
(count shapes-main)
(count unique-refs)))
(run! main-exists? shapes-inst)
(if main-instance?
(do
(if subinstance?
(is-main-instance-subroot (first shapes-inst))
(is-main-instance-root (first shapes-inst)))
(run! is-main-instance-inner (rest shapes-inst)))
(do
(if subinstance?
(is-instance-subroot (first shapes-inst))
(is-instance-root (first shapes-inst)))
(run! is-instance-inner (rest shapes-inst))))
(t/is (= (count shapes-inst) (count shapes-main)))
(when-not main-instance?
(t/is (= (count shapes-inst) (count unique-refs)))
(run! main-exists? shapes-inst))
[shapes-inst shapes-main component])))
@ -131,24 +152,11 @@
root-inst (ctn/get-shape page root-inst-id)
libs (wsh/get-libraries state)
component (cph/get-component libs (:component-id root-inst))
component (ctf/get-component libs (:component-id root-inst))
library (ctf/get-component-library libs root-inst)
shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)
shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst))
unique-refs (into #{} (map :shape-ref) shapes-inst)
main-exists? (fn [shape]
(let [component-shape
(cph/get-component-shape (:objects page) shape)
component
(cph/get-component libs (:component-id component-shape))
main-shape
(ctn/get-shape component (:shape-ref shape))]
(t/is (some? main-shape))))]
shapes-main (ctf/get-component-shapes (:data library) component)]
;; Validate that the instance tree is well constructed
(is-instance-root (first shapes-inst))
@ -158,14 +166,12 @@
(defn resolve-component
"Get the component with the given id and all its shapes."
[state component-id]
(let [page (thp/current-page state)
libs (wsh/get-libraries state)
component (cph/get-component libs component-id)
root-main (ctk/get-component-root component)
shapes-main (cph/get-children-with-self (:objects component) (:id root-main))]
(let [libs (wsh/get-libraries state)
component (ctf/get-component libs component-id)
library (ctf/get-component-library libs component)
shapes-main (ctf/get-component-shapes (:data library) component)]
;; Validate that the component tree is well constructed
(run! is-noninstance shapes-main)
[shapes-main component]))

View file

@ -7,20 +7,15 @@
(ns frontend-tests.helpers.pages
(:require
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.layout :as layout]
[app.main.data.workspace.libraries-helpers :as dwlh]
[app.main.data.workspace.state-helpers :as wsh]
[beicon.core :as rx]
[cljs.pprint :refer [pprint]]
[cljs.test :as t :include-macros true]
[potok.core :as ptk]))
[app.main.data.workspace.state-helpers :as wsh]))
;; ---- Helpers to manage pages and objects
@ -32,10 +27,12 @@
:workspace-layout layout/default-layout
:workspace-global layout/default-global
:workspace-data {:id current-file-id
:options {:components-v2 true}
:components {}
:pages []
:pages-index {}}
:workspace-libraries {}})
:workspace-libraries {}
:features {:components-v2 true}})
(def ^:private idmap (atom {}))
@ -56,6 +53,11 @@
(let [page (current-page state)]
(get-in page [:objects (id label)])))
(defn get-children
[state label]
(let [page (current-page state)]
(cph/get-children (:objects page) (id label))))
(defn sample-page
([state] (sample-page state {}))
([state {:keys [id name] :as props
@ -106,16 +108,17 @@
objects (wsh/lookup-page-objects state (:id page))
shapes (dwg/shapes-for-grouping objects shape-ids)
[group component-root changes]
[group component-id changes]
(dwlh/generate-add-component nil
shapes
(:objects page)
(:id page)
current-file-id
true)]
true
dwg/prepare-create-group)]
(swap! idmap assoc instance-label (:id group)
component-label (:id component-root))
component-label component-id)
(update state :workspace-data
cp/process-changes (:redo-changes changes))))
@ -126,8 +129,10 @@
(let [page (current-page state)
libraries (wsh/get-libraries state)
changes (pcb/empty-changes nil (:id page))
[new-shape changes]
(dwlh/generate-instantiate-component nil
(dwlh/generate-instantiate-component changes
file-id
component-id
(gpt/point 100 100)
@ -148,7 +153,9 @@
assoc library-id {:id library-id
:name name
:data {:id library-id
:options (:options data)
:pages (:pages data)
:pages-index (:pages-index data)
:components (:components data)}})
(update :workspace-data
assoc :components {} :pages [] :pages-index {}))))

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,19 @@
(ns frontend-tests.state-components-test
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.pages.helpers :as cph]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.main.data.workspace :as dw]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.libraries-helpers :as dwlh]
[app.main.data.workspace.state-helpers :as wsh]
[beicon.core :as rx]
[cljs.pprint :refer [pprint]]
[cljs.test :as t :include-macros true]
[clojure.stacktrace :as stk]
[frontend-tests.helpers.events :as the]
[frontend-tests.helpers.libraries :as thl]
[frontend-tests.helpers.pages :as thp]
[linked.core :as lks]
[potok.core :as ptk]))
[app.common.geom.point :as gpt]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.main.data.workspace :as dw]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.state-helpers :as wsh]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.events :as the]
[frontend-tests.helpers.libraries :as thl]
[frontend-tests.helpers.pages :as thp]
[linked.core :as lks]
[potok.core :as ptk]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
@ -37,7 +32,8 @@
;; Uncomment to debug
;; (ctf/dump-tree (get new-state :workspace-data)
;; (get new-state :current-page-id)
;; (get new-state :workspace-libraries))
;; (get new-state :workspace-libraries)
;; false true)
; Expected shape tree:
;
@ -54,8 +50,8 @@
[[group shape1] [c-group c-shape1] component]
(thl/resolve-instance-and-main
new-state
(:parent-id shape1))
new-state
(:parent-id shape1))
file (wsh/get-local-file new-state)]
@ -73,40 +69,6 @@
(dwl/add-component)
:the/end)))))
;; Remove definitely when we ensure that the other method works
;; well in more advanced tests.
#_(t/deftest test-add-component-from-single-shape
(t/async
done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"}))]
(->> state
(the/do-update (dw/select-shape (thp/id :shape1)))
(the/do-watch-update dwl/add-component)
(rx/do
(fn [new-state]
(let [shape1 (thp/get-shape new-state :shape1)
[[group shape1] [c-group c-shape1] component]
(thl/resolve-instance-and-main
new-state
(:parent-id shape1))
file (wsh/get-local-file new-state)]
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:name group) "Component 1"))
(t/is (= (:name component) "Component 1"))
(t/is (= (:name c-shape1) "Rect 1"))
(t/is (= (:name c-group) "Component 1"))
(thl/is-from-file group file))))
(rx/subs done #(throw %))))))
(t/deftest test-add-component-from-several-shapes
(t/async
done
@ -120,17 +82,14 @@
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Component 1 #--> Component 1
; Rect 1 ---> Rect 1
; Rect-2 ---> Rect-2
;
; [Component 1]
; Component 1
; Rect 1
; Rect-2
; [Page]
; Root Frame
; Component 1
; Rect 1
; Rect-2
;
; [Component 1]
; page1 / Component 1
;
(let [shape1 (thp/get-shape new-state :shape1)
@ -138,8 +97,8 @@
[c-group c-shape1 c-shape2]
component]
(thl/resolve-instance-and-main
new-state
(:parent-id shape1))
new-state
(:parent-id shape1))
file (wsh/get-local-file new-state)]
@ -174,38 +133,36 @@
(thp/id :shape2)]))
store (the/prepare-store state done
(fn [new-state]
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Group #--> Group
; Rect 1 ---> Rect 1
; Rect-2 ---> Rect-2
; [Page]
; Root Frame
; Group
; Rect 1
; Rect-2
;
; [Group]
; page1 / Group
;
; [Group]
; Group
; Rect 1
; Rect-2
;
(let [[[group shape1 shape2]
[c-group c-shape1 c-shape2]
component]
(thl/resolve-instance-and-main
new-state
(thp/id :group1))
(let [[[group shape1 shape2]
[c-group c-shape1 c-shape2]
component]
(thl/resolve-instance-and-main
new-state
(thp/id :group1))
file (wsh/get-local-file new-state)]
file (wsh/get-local-file new-state)]
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:name shape2) "Rect-2"))
(t/is (= (:name group) "Group"))
(t/is (= (:name component) "Group"))
(t/is (= (:name c-shape1) "Rect 1"))
(t/is (= (:name c-shape2) "Rect-2"))
(t/is (= (:name c-group) "Group"))
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:name shape2) "Rect-2"))
(t/is (= (:name group) "Group"))
(t/is (= (:name component) "Group"))
(t/is (= (:name c-shape1) "Rect 1"))
(t/is (= (:name c-shape2) "Rect-2"))
(t/is (= (:name c-group) "Group"))
(thl/is-from-file group file))))]
(thl/is-from-file group file))))]
(ptk/emit!
store
@ -220,42 +177,39 @@
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :instance1 :component1
(thp/make-component :main1 :component1
[(thp/id :shape1)]))
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect 1 #--> Rect 1
; Rect 1 @--> Rect 1
; Rect 1 ---> Rect 1
;
; [Rect 1]
; Rect 1
; Rect 1
;
; [Rect 1]
; Rect 1
; Rect 1 @--> Rect 1
; Rect 1 ---> Rect 1
; [Page]
; Root Frame
; Rect 1
; Rect 1
; Rect 1
;
; [Rect 1]
; page1 / Rect 1
;
; [Rect 1]
; page1 / Rect 1
;
(let [[[instance1 shape1]
[c-instance1 c-shape1]
component1]
(thl/resolve-instance-and-main
new-state
(thp/id :instance1)
true)
new-state
(thp/id :main1)
true)
[[instance2 instance1' shape1']
[c-instance2 c-instance1' c-shape1']
component2]
(thl/resolve-instance-and-main
new-state
(:parent-id instance1))]
new-state
(:parent-id instance1))]
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:name instance1) "Rect 1"))
@ -273,7 +227,7 @@
(ptk/emit!
store
(dw/select-shape (thp/id :instance1))
(dw/select-shape (thp/id :main1))
(dwl/add-component)
:the/end))))
@ -284,34 +238,34 @@
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :instance1 :component-1
(thp/make-component :main1 :component1
[(thp/id :shape1)]))
instance1 (thp/get-shape state :instance1)
main1 (thp/get-shape state :main1)
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect-2 #--> Renamed component
; Rect 1 ---> Rect 1
;
; [Renamed]
; Renamed component
; Rect 1
(let [libs (wsh/get-libraries new-state)
component (cph/get-component libs
(:component-file instance1)
(:component-id instance1))]
(t/is (= (:name component)
"Renamed component")))))]
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect 1
; Rect 1
;
; [Renamed component]
; page1 / Rect 1
;
(let [libs (wsh/get-libraries new-state)
component (ctf/get-component libs
(:component-file main1)
(:component-id main1))]
(t/is (= (:name component)
"Renamed component")))))]
(ptk/emit!
store
(dwl/rename-component (:component-id instance1) "Renamed component")
:the/end))))
store
(dwl/rename-component (:component-id main1) "Renamed component")
:the/end))))
(t/deftest test-duplicate-component
(t/async
@ -320,28 +274,28 @@
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :instance1 :component-1
(thp/make-component :main1 :component1
[(thp/id :shape1)]))
instance1 (thp/get-shape state :instance1)
component-id (:component-id instance1)
main1 (thp/get-shape state :main1)
component-id (:component-id main1)
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect 1 #--> Rect 1
; Rect 1 ---> Rect 1
;
; [Page]
; Root Frame
; Rect 1
; Rect 1
; Rect 1 #--> Rect 1
; Rect 1 ---> Rect 1
;
; [Rect 1]
; Rect 1
; Rect 1
;
; [Rect 1]
; Rect 1
; Rect 1
; page1 / Rect 1
;
; [Rect 1]
; page1 / Rect 1
;
(let [new-component-id (->> (get-in new-state
[:workspace-data
@ -350,24 +304,24 @@
(filter #(not= % component-id))
(first))
[[instance1 shape1]
[c-instance1 c-shape1]
component1]
[[_instance1 _shape1]
[_c-instance1 _c-shape1]
_component1]
(thl/resolve-instance-and-main
new-state
(:id instance1))
new-state
(:id main1))
[[c-component2 c-shape2]
[[_c-component2 _c-shape2]
component2]
(thl/resolve-component
new-state
new-component-id)]
new-state
new-component-id)]
(t/is (= (:name component2) "Rect 1")))))]
(ptk/emit!
store
(dwl/duplicate-component {:id component-id})
(dwl/duplicate-component thp/current-file-id component-id)
:the/end))))
(t/deftest test-delete-component
@ -377,42 +331,115 @@
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :instance1 :component-1
[(thp/id :shape1)]))
file (wsh/get-local-file state)
instance1 (thp/get-shape state :instance1)
component-id (:component-id instance1)
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/instantiate-component :instance1
(thp/id :component1)))
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect-2
; Rect 1
;
(let [[instance1 shape1]
(thl/resolve-noninstance
new-state
(:id instance1))
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect 1 #--> ?
; Rect 1 ---> ?
;
(let [[main1 shape1]
(thl/resolve-noninstance
new-state
(thp/id :main1))
libs (wsh/get-libraries new-state)
component (cph/get-component libs
(:component-file instance1)
(:component-id instance1))]
[[instance1 shape2] [c-instance1 c-shape2] component1]
(thl/resolve-instance-and-main-allow-dangling
new-state
(thp/id :instance1))
(t/is (some? instance1))
(t/is (some? shape1))
(t/is (nil? component)))))]
file (wsh/get-local-file new-state)
component2 (ctkl/get-component file (thp/id :component1))
component3 (ctkl/get-deleted-component file (thp/id :component1))
saved-objects (:objects component3)
saved-main1 (get saved-objects (:shape-ref instance1))
saved-shape2 (get saved-objects (:shape-ref shape2))]
(t/is (nil? main1))
(t/is (nil? shape1))
(t/is (= (:name instance1) "Rect 1"))
(t/is (= (:touched instance1) nil))
(t/is (not= (:shape-ref instance1) nil))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:touched shape2) nil))
(t/is (not= (:shape-ref shape2) nil))
(t/is (nil? c-instance1))
(t/is (nil? c-shape2))
(t/is (nil? component1))
(t/is (nil? component2))
(t/is (= (:name component3) "Rect 1"))
(t/is (= (:deleted component3) true))
(t/is (some? (:objects component3)))
(t/is (= (:name saved-main1) "Rect 1"))
(t/is (= (:name saved-shape2) "Rect 1")))))]
(ptk/emit!
store
(dwl/delete-component {:id component-id})
(dwl/sync-file (:id file) (:id file) :components component-id)
:the/end))))
store
(dwl/delete-component {:id (thp/id :component1)})
:the/end))))
(t/deftest test-restore-component
(t/async
done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/instantiate-component :instance1
(thp/id :component1)))
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect 1 #--> Rect 1
; Rect 1 ---> Rect 1
; Rect 1
; Rect 1
;
; [Rect 1]
; page1 / Rect 1
;
(let [[[instance1 shape2] [c-instance1 c-shape2] component1]
(thl/resolve-instance-and-main
new-state
(thp/id :instance1))
file (wsh/get-local-file new-state)
component2 (ctkl/get-component file (thp/id :component1))
saved-objects (:objects component2)]
(t/is (= (:name instance1) "Rect 1"))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:name c-instance1) "Rect 1"))
(t/is (= (:name c-shape2) "Rect 1"))
(t/is (some? component1))
(t/is (some? component2))
(t/is (nil? saved-objects)))))]
(ptk/emit!
store
(dwl/delete-component {:id (thp/id :component1)})
(dwl/restore-component thp/current-file-id (thp/id :component1))
:the/end))))
(t/deftest test-instantiate-component
(t/async
@ -421,46 +448,45 @@
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :instance1 :component-1
(thp/make-component :main1 :component1
[(thp/id :shape1)]))
file (wsh/get-local-file state)
component-id (thp/id :component-1)
instance1 (thp/get-shape state :instance1)
component-id (thp/id :component1)
main1 (thp/get-shape state :main1)
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; [Page]
; Root Frame
; Rect 1
; Rect 1
; Rect 1 #--> Rect 1
; Rect 1 ---> Rect 1
; Rect 1 #--> Rect 1
; Rect 1 ---> Rect 1
;
; [Rect 1]
; Rect 1
; Rect 1
; Rect 1 ---> Rect 1
;
; [Rect 1]
; page1 / Rect 1
;
(let [new-instance-id (-> new-state
wsh/lookup-selected
first)
[[instance2 shape2]
[c-instance2 c-shape2]
[[instance1 shape2]
[c-instance1 c-shape2]
component]
(thl/resolve-instance-and-main
new-state
new-instance-id)]
new-state
new-instance-id)]
(t/is (not= (:id instance1) (:id instance2)))
(t/is (not= (:id main1) (:id instance1)))
(t/is (= (:id component) component-id))
(t/is (= (:name instance2) "Rect 1"))
(t/is (= (:name instance1) "Rect 1"))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:name c-instance2) "Rect 1"))
(t/is (= (:name c-instance1) "Rect 1"))
(t/is (= (:name c-shape2) "Rect 1"))
(t/is (= (:component-file instance2)
(t/is (= (:component-file instance1)
thp/current-file-id)))))]
(ptk/emit!
@ -477,13 +503,13 @@
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :instance1 :component-1
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/move-to-library :lib1 "Library 1")
(thp/sample-page))
library-id (thp/id :lib1)
component-id (thp/id :component-1)
component-id (thp/id :component1)
store (the/prepare-store state done
(fn [new-state]
@ -498,19 +524,19 @@
wsh/lookup-selected
first)
[[instance2 shape2]
[c-instance2 c-shape2]
[[instance1 shape2]
[c-instance1 c-shape2]
component]
(thl/resolve-instance-and-main
new-state
new-instance-id)]
(t/is (= (:id component) component-id))
(t/is (= (:name instance2) "Rect 1"))
(t/is (= (:name instance1) "Rect 1"))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:name c-instance2) "Rect 1"))
(t/is (= (:name c-instance1) "Rect 1"))
(t/is (= (:name c-shape2) "Rect 1"))
(t/is (= (:component-file instance2) library-id)))))]
(t/is (= (:component-file instance1) library-id)))))]
(ptk/emit!
store
@ -526,32 +552,34 @@
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :instance1 :component-1
[(thp/id :shape1)]))
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/instantiate-component :instance1
(thp/id :component1)))
instance1 (thp/get-shape state :instance1)
component-id (:component-id instance1)
instance1 (thp/get-shape state :instance1)
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect-2
; Rect 1
;
; [Rect-2]
; Rect-2
; Rect 1
;
(let [[instance1 shape1]
(thl/resolve-noninstance
new-state
(:id instance1))]
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect 1
; Rect 1
; Rect 1
; Rect 1
;
; [Rect 1]
; page1 / Rect 1
;
(let [[instance2 shape1]
(thl/resolve-noninstance
new-state
(:id instance1))]
(t/is (some? instance1))
(t/is (some? shape1)))))]
(t/is (some? instance2))
(t/is (some? shape1)))))]
(ptk/emit!
store
@ -566,28 +594,21 @@
(thp/sample-shape :shape1 :rect
{:name "Rect 1"}))
file (wsh/get-local-file state)
instance1 (thp/get-shape state :instance1)
component-id (:component-id instance1)
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Group #--> Group
; Rect 1 @--> Rect 1
; Rect 1 ---> Rect 1
;
; [Page]
; Root Frame
; Group
; Rect 1
; Rect 1
;
; [Rect 1]
; Rect 1
; Rect 1
;
; [Group]
; Group
; Rect 1 @--> Rect 1
; Rect 1 ---> Rect 1
; page1 / Rect 1
;
; [Group]
; page1 / Group
;
(let [page (thp/current-page new-state)
shape1 (thp/get-shape new-state :shape1)
@ -623,46 +644,41 @@
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :instance1 :component-1
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/group-shapes :group1
[(thp/id :instance1)])
(thp/make-component :instance2 :component-2
[(thp/id :group1)]))
(thp/make-component :main2 :component-2
[(thp/id :main1)]))
file (wsh/get-local-file state)
instance1 (thp/get-shape state :instance1)
instance2 (thp/get-shape state :instance2)
component-id (:component-id instance2)
main1 (thp/get-shape state :main1)
main2 (thp/get-shape state :main2)
component-id (:component-id main2)
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Rect 1 #--> Rect 1
; Rect 1 @--> Rect 1
; Rect 1 ---> Rect 1
; Rect 1 #--> Rect 1
; Rect 1 @--> Rect 1
; Rect 1 ---> Rect 1
;
; [Page]
; Root Frame
; Rect 1
; Rect 1
; Rect 1
; Rect 1 #--> Rect 1
; Rect 1 @--> Rect 1
; Rect 1 ---> Rect 1
;
; [Rect 1]
; Rect 1
; Rect 1
;
; [Rect 1]
; Rect 1
; Rect 1 @--> Rect 1
; Rect 1 ---> Rect 1
; page1 / Rect 1
;
; [Rect 1]
; page1 / Rect 1
;
(let [new-instance-id (-> new-state
wsh/lookup-selected
first)
[[instance3 shape3 shape4]
[c-instance3 c-shape3 c-shape4]
[[instance1 shape1 shape2]
[c-instance1 c-shape1 c-shape2]
component]
(thl/resolve-instance-and-main
new-state
@ -670,19 +686,19 @@
; TODO: get and check the instance inside component [Rect-2]
(t/is (not= (:id instance1) (:id instance3)))
(t/is (not= (:id main1) (:id instance1)))
(t/is (= (:id component) component-id))
(t/is (= (:name instance3) "Rect 1"))
(t/is (= (:name shape3) "Rect 1"))
(t/is (= (:name shape4) "Rect 1"))
(t/is (= (:name c-instance3) "Rect 1"))
(t/is (= (:name c-shape3) "Rect 1"))
(t/is (= (:name c-shape4) "Rect 1")))))]
(t/is (= (:name instance1) "Rect 1"))
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:name c-instance1) "Rect 1"))
(t/is (= (:name c-shape1) "Rect 1"))
(t/is (= (:name c-shape2) "Rect 1")))))]
(ptk/emit!
store
(dwl/instantiate-component (:id file)
(:component-id instance2)
(:component-id main2)
(gpt/point 100 100))
:the/end))))
@ -693,38 +709,36 @@
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/make-component :instance1 :component-1
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/move-to-library :lib1 "Library 1")
(thp/sample-page)
(thp/instantiate-component :instance2
(thp/id :component-1)
(thp/instantiate-component :instance1
(thp/id :component1)
(thp/id :lib1)))
library-id (thp/id :lib1)
component-id (thp/id :component-1)
file (wsh/get-local-file state)
library-id (thp/id :lib1)
store (the/prepare-store state done
(fn [new-state]
; Expected shape tree:
;
; [Page]
; Root Frame
; Group #--> Group
; Rect 1 @--> <Library 1> Rect 1
; Rect 1 ---> <Library 1> Rect 1
; [Page]
; Root Frame
; Group
; Rect 1 #--> <Library 1> Rect 1
; Rect 1 ---> <Library 1> Rect 1
;
; [Group]
; page1 / Group
;
; [Group]
; Group
; Rect 1 @--> <Library 1> Rect 1
; Rect 1 ---> <Library 1> Rect 1
;
(let [instance2 (thp/get-shape new-state :instance2)
(let [instance1 (thp/get-shape new-state :instance1)
[[group1 shape1 shape2] [c-group1 c-shape1 c-shape2] component]
[[group1 shape1 shape2] [c-group1 c-shape1 c-shape2] _component]
(thl/resolve-instance-and-main
new-state
(:parent-id instance2))]
(:parent-id instance1))]
(t/is (= (:name group1) "Group"))
(t/is (= (:name shape1) "Rect 1"))
@ -735,14 +749,13 @@
(t/is (= (:component-file group1) thp/current-file-id))
(t/is (= (:component-file shape1) library-id))
(t/is (= (:component-file shape2) nil))
(t/is (= (:component-file c-group1) nil))
(t/is (= (:component-file c-group1) (:id file)))
(t/is (= (:component-file c-shape1) library-id))
(t/is (= (:component-file c-shape2) nil)))))]
(ptk/emit!
store
(dw/select-shape (thp/id :instance2))
(dw/select-shape (thp/id :instance1))
dwg/group-selected
(dwl/add-component)
:the/end))))