🔧 Read component shapes from pages

This commit is contained in:
Andrés Moya 2023-03-07 12:03:38 +01:00
parent a4dd5fccff
commit 0711fa700b
21 changed files with 588 additions and 403 deletions

View file

@ -492,10 +492,13 @@
(library-summary [{:keys [id data] :as file}] (library-summary [{:keys [id data] :as file}]
(binding [pmap/*load-fn* (partial load-pointer conn id)] (binding [pmap/*load-fn* (partial load-pointer conn id)]
{:components (assets-sample (:components data) 4) (let [components-sample (-> (assets-sample (:components data) 4)
:media (assets-sample (:media data) 3) (update :sample
:colors (assets-sample (:colors data) 3) #(map (partial ctf/load-component-objects data) %)))]
:typographies (assets-sample (:typographies data) 3)}))] {: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]) (->> (db/exec! conn [sql:team-shared-files team-id])
(into #{} (comp (into #{} (comp
@ -552,7 +555,10 @@
(map (fn [{:keys [id] :as row}] (map (fn [{:keys [id] :as row}]
(binding [pmap/*load-fn* (partial load-pointer conn id)] (binding [pmap/*load-fn* (partial load-pointer conn id)]
(-> row (-> 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))))) (handle-file-features client-features)))))
(vec))) (vec)))

View file

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

View file

@ -15,6 +15,7 @@
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.uuid :as uuid])) [app.common.uuid :as uuid]))
@ -598,13 +599,14 @@
(update :redo-changes (update :redo-changes
(fn [redo-changes] (fn [redo-changes]
(-> redo-changes (-> redo-changes
(conj {:type :add-component (conj (cond-> {:type :add-component
:id id :id id
:path path :path path
:name name :name name
:main-instance-id main-instance-id :main-instance-id main-instance-id
:main-instance-page main-instance-page :main-instance-page main-instance-page}
:shapes new-shapes}) (some? new-shapes) ;; this will be null in components-v2
(assoc :shapes new-shapes)))
(into (map mk-change) updated-shapes)))) (into (map mk-change) updated-shapes))))
(update :undo-changes (update :undo-changes
(fn [undo-changes] (fn [undo-changes]
@ -640,8 +642,9 @@
(defn delete-component (defn delete-component
[changes id components-v2] [changes id components-v2]
(assert-library changes) (assert-library changes)
(let [library-data (::library-data (meta changes)) (let [library-data (::library-data (meta changes))
prev-component (get-in library-data [:components id])] component (ctkl/get-component library-data id)
shapes (ctf/get-component-shapes library-data component)]
(-> changes (-> changes
(update :redo-changes conj {:type :del-component (update :redo-changes conj {:type :del-component
:id id}) :id id})
@ -655,11 +658,11 @@
:always :always
(d/preconj {:type :add-component (d/preconj {:type :add-component
:id id :id id
:name (:name prev-component) :name (:name component)
:path (:path prev-component) :path (:path component)
:main-instance-id (:main-instance-id prev-component) :main-instance-id (:main-instance-id component)
:main-instance-page (:main-instance-page prev-component) :main-instance-page (:main-instance-page component)
:shapes (vals (:objects prev-component))}))))))) :shapes shapes})))))))
(defn restore-component (defn restore-component
[changes id] [changes id]

View file

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

View file

@ -270,25 +270,6 @@
[shape group] [shape group]
((or (:touched 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 (defn get-root-shape
"Get the root shape linked to a component for this shape, if any." "Get the root shape linked to a component for this shape, if any."
[objects shape] [objects shape]

View file

@ -34,6 +34,7 @@
(and (= shape-id (:main-instance-id component)) (and (= shape-id (:main-instance-id component))
(= page-id (:main-instance-page component)))) (= page-id (:main-instance-page component))))
;; Obsolete in components-v2
(defn get-component-root (defn get-component-root
[component] [component]
(get-in component [:objects (:id component)])) (get-in component [:objects (:id component)]))
@ -45,12 +46,12 @@
(= (:component-file shape) library-id))) (= (:component-file shape) library-id)))
(defn in-component-instance? (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] [shape]
(some? (:shape-ref shape))) (some? (:shape-ref shape)))
(defn in-component-instance-not-root? (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." is not the root shape."
[shape] [shape]
(and (some? (:shape-ref shape)) (and (some? (:shape-ref shape))

View file

@ -23,11 +23,13 @@
(assoc-in [:components id] (assoc-in [:components id]
{:id id {:id id
:name name :name name
:path path :path path})
:objects (->> shapes
(d/index-by :id)
(wrap-object-fn))})
(not components-v2)
(assoc-in [:components id :objects]
(->> shapes
(d/index-by :id)
(wrap-object-fn)))
components-v2 components-v2
(update-in [:components id] assoc (update-in [:components id] assoc
:main-instance-id main-instance-id :main-instance-id main-instance-id
@ -60,4 +62,3 @@
(defn delete-component (defn delete-component
[file-data component-id] [file-data component-id]
(update file-data :components dissoc component-id)) (update file-data :components dissoc component-id))

View file

@ -11,6 +11,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.pages.common :as common] [app.common.pages.common :as common]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[clojure.spec.alpha :as s])) [clojure.spec.alpha :as s]))
@ -62,6 +63,17 @@
[container shape-id f] [container shape-id f]
(update-in container [:objects 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 (defn make-component-shape
"Clone the shape and all children. Generate new ids and detach "Clone the shape and all children. Generate new ids and detach
from parent and frame. Update the original shapes to have links 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])) [new-root-shape (map remap-frame-id new-shapes) updated-shapes]))
(defn make-component-instance (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 each new shape to the corresponding one of the component. Place the new instance
coordinates in the given position." coordinates in the given position."
([container component component-file-id position] ([container component library-data position components-v2]
(make-component-instance container component component-file-id position {})) (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}}] {: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)) orig-pos (gpt/point (:x component-shape) (:y component-shape))
delta (gpt/subtract position orig-pos) delta (gpt/subtract position orig-pos)
@ -147,20 +166,20 @@
(vswap! frame-ids-map assoc (:id original-shape) (:id new-shape))) (vswap! frame-ids-map assoc (:id original-shape) (:id new-shape)))
(cond-> new-shape (cond-> new-shape
true :always
(-> (gsh/move delta) (-> (gsh/move delta)
(dissoc :touched)) (dissoc :touched :main-instance?))
(nil? (:shape-ref original-shape)) (nil? (:shape-ref original-shape))
(assoc :shape-ref (:id original-shape)) (assoc :shape-ref (:id original-shape))
(nil? (:parent-id original-shape)) (nil? (:parent-id original-shape))
(assoc :component-id (:id original-shape) (assoc :component-id (:id component)
:component-file component-file-id :component-file (:id library-data)
:component-root? true :component-root? true
:name new-name) :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) (assoc :main-instance? true)
(some? (:parent-id original-shape)) (some? (:parent-id original-shape))
@ -169,7 +188,7 @@
[new-shape new-shapes _] [new-shape new-shapes _]
(ctst/clone-object component-shape (ctst/clone-object component-shape
nil nil
(get component :objects) (if components-v2 (:objects component-page) (:objects component))
update-new-shape update-new-shape
(fn [object _] object) (fn [object _] object)
force-id) force-id)
@ -177,10 +196,10 @@
;; If frame-id points to a shape inside the component, remap it to the ;; 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. ;; corresponding new frame shape. If not, set it to the destination frame.
;; Also fix empty parent-id. ;; Also fix empty parent-id.
remap-frame-id (fn [shape] remap-frame-id (fn [shape]
(as-> shape $ (as-> shape $
(update $ :frame-id #(get @frame-ids-map % frame-id)) (update $ :frame-id #(get @frame-ids-map % frame-id))
(update $ :parent-id #(or % (:frame-id $)))))] (update $ :parent-id #(or % (:frame-id $)))))]
[new-shape (map remap-frame-id new-shapes)]))) [new-shape (map remap-frame-id new-shapes)])))

View file

@ -6,27 +6,27 @@
(ns app.common.types.file (ns app.common.types.file
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.features :as ffeat] [app.common.files.features :as ffeat]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.pages.common :refer [file-version]] [app.common.pages.common :refer [file-version]]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.colors-list :as ctcl] [app.common.types.colors-list :as ctcl]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file.media-object :as ctfm] [app.common.types.file.media-object :as ctfm]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.typographies-list :as ctyl] [app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as cty] [app.common.types.typography :as cty]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[cuerdas.core :as str])) [cuerdas.core :as str]))
;; Specs ;; Specs
@ -116,6 +116,75 @@
([libraries library-id component-id] ([libraries library-id component-id]
(ctkl/get-component (dm/get-in libraries [library-id :data]) component-id))) (ctkl/get-component (dm/get-in libraries [library-id :data]) component-id)))
(defn get-component-library
"Retrieve the library the component belongs to."
[libraries instance-root]
(get libraries (:component-file instance-root)))
(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 components-v2
(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 components-v2
(-> file-data
(get-component-page component)
(ctn/get-shape (:main-instance-id component)))
(ctk/get-component-root component))))
(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 get-component-shape
"Retrieve one shape in the component."
[file-data component shape-id]
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if components-v2
(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 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 components-v2
(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 (defn delete-component
"Delete a component and store it to be able to be recovered later. "Delete a component and store it to be able to be recovered later.
@ -123,26 +192,29 @@
([file-data component-id] ([file-data component-id]
(delete-component file-data component-id false)) (delete-component file-data component-id false))
([file-data component-id skip-undelete?] ([file-data component-id _skip-undelete?]
(let [components-v2 (dm/get-in file-data [:options :components-v2]) (let [_components-v2 (dm/get-in file-data [:options :components-v2])
add-to-deleted-components ;; TODO: replace :deleted-components with a :deleted? flag in normal shapes
(fn [file-data] ;; see task https://tree.taiga.io/project/penpot/task/4998
(let [component (ctkl/get-component file-data component-id)] ;;
(if (some? component) ;; add-to-deleted-components
(let [page (ctpl/get-page file-data (:main-instance-page component)) ;; (fn [file-data]
main-instance (ctn/get-shape page (:main-instance-id component)) ;; (let [component (ctkl/get-component file-data component-id)]
component (assoc component ;; (if (some? component)
:main-instance-x (:x main-instance) ; An instance root is always a group, ;; (let [main-instance (get-component-root file-data component)
:main-instance-y (:y main-instance))] ; so it will have :x and :y ;; component (assoc component
(when (nil? main-instance) ;; :main-instance-x (:x main-instance) ; An instance root is always a group,
(throw (ex-info "Cannot delete the main instance before the component" {:component-id component-id}))) ;; :main-instance-y (:y main-instance))] ; or a frame, so it will have :x and :y
(assoc-in file-data [:deleted-components component-id] component)) ;; (when (nil? main-instance)
file-data)))] ;; (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 (cond-> file-data
(and components-v2 (not skip-undelete?)) ;; (and components-v2 (not skip-undelete?))
(add-to-deleted-components) ;; (add-to-deleted-components)
:always :always
(ctkl/delete-component component-id))))) (ctkl/delete-component component-id)))))
@ -243,8 +315,9 @@
[(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)])))) [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)]))))
(defn migrate-to-components-v2 (defn migrate-to-components-v2
"If there is any component in the file library, add a new 'Library backup' and generate "If there is any component in the file library, add a new 'Library backup', generate
main instances for all components there. Mark the file with the :components-v2 option." main instances for all components there and remove shapes from library components.
Mark the file with the :components-v2 option."
[file-data] [file-data]
(let [components (ctkl/components-seq file-data)] (let [components (ctkl/components-seq file-data)]
(if (or (empty? components) (if (or (empty? components)
@ -262,8 +335,9 @@
[new-shape new-shapes] [new-shape new-shapes]
(ctn/make-component-instance page (ctn/make-component-instance page
component component
(:id file-data) file-data
position position
false
{:main-instance? true}) {:main-instance? true})
add-shapes add-shapes
@ -281,9 +355,10 @@
update-component update-component
(fn [component] (fn [component]
(assoc component (-> component
:main-instance-id (:id new-shape) (assoc :main-instance-id (:id new-shape)
:main-instance-page page-id))] :main-instance-page page-id)
(dissoc :objects)))]
(-> file-data (-> file-data
(ctpl/update-page page-id add-shapes) (ctpl/update-page page-id add-shapes)
@ -292,9 +367,9 @@
add-instance-grid add-instance-grid
(fn [file-data components] (fn [file-data components]
(let [position-seq (ctst/generate-shape-grid (let [position-seq (ctst/generate-shape-grid
(map ctk/get-component-root components) (map (partial get-component-root file-data) components)
start-pos start-pos
grid-gap)] grid-gap)]
(loop [file-data file-data (loop [file-data file-data
components-seq (seq components) components-seq (seq components)
position-seq position-seq] position-seq position-seq]
@ -311,7 +386,7 @@
(assoc-in [:options :components-v2] true)))))) (assoc-in [:options :components-v2] true))))))
(defn- absorb-components (defn- absorb-components
[file-data used-components] [file-data used-components library-data]
(let [grid-gap 50 (let [grid-gap 50
; Search for the library page. If not exists, create it. ; Search for the library page. If not exists, create it.
@ -326,10 +401,17 @@
[main-instance-shape main-instance-shapes] [main-instance-shape main-instance-shapes]
(ctn/make-component-instance page (ctn/make-component-instance page
component component
(:id file-data) library-data
position position
(dm/get-in file-data [:options :components-v2])
{:main-instance? true}) {: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 all shapes of the main instance to the library page
add-main-instance-shapes add-main-instance-shapes
(fn [page] (fn [page]
@ -353,7 +435,7 @@
:path (:path component) :path (:path component)
:main-instance-id (:id main-instance-shape) :main-instance-id (:id main-instance-shape)
:main-instance-page page-id :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 ; Change all existing instances to point to the local file
remap-instances remap-instances
@ -378,9 +460,9 @@
add-component-grid add-component-grid
(fn [data used-components] (fn [data used-components]
(let [position-seq (ctst/generate-shape-grid (let [position-seq (ctst/generate-shape-grid
(map #(ctk/get-component-root (first %)) used-components) (map #(get-component-root library-data (first %)) used-components)
start-pos start-pos
grid-gap)] grid-gap)]
(loop [data data (loop [data data
components-seq (seq used-components) components-seq (seq used-components)
position-seq position-seq] position-seq position-seq]
@ -410,9 +492,9 @@
remap-shape)) remap-shape))
% %
shapes)))] shapes)))]
(as-> file-data $ (as-> file-data $
(ctcl/add-color $ color) (ctcl/add-color $ color)
(reduce remap-shapes $ usages))))] (reduce remap-shapes $ usages))))]
(reduce absorb-color (reduce absorb-color
file-data file-data
@ -434,9 +516,9 @@
remap-shape)) remap-shape))
% %
shapes)))] shapes)))]
(as-> file-data $ (as-> file-data $
(ctyl/add-typography $ typography) (ctyl/add-typography $ typography)
(reduce remap-shapes $ usages))))] (reduce remap-shapes $ usages))))]
(reduce absorb-typography (reduce absorb-typography
file-data file-data
@ -452,7 +534,7 @@
(cond-> file-data (cond-> file-data
(d/not-empty? used-components) (d/not-empty? used-components)
(absorb-components used-components) (absorb-components used-components library-data)
(d/not-empty? used-colors) (d/not-empty? used-colors)
(absorb-colors used-colors) (absorb-colors used-colors)
@ -477,69 +559,82 @@
root (d/seek #(nil? (:parent-id %)) (vals objects))] root (d/seek #(nil? (:parent-id %)) (vals objects))]
(letfn [(show-shape [shape-id level objects] (letfn [(show-shape [shape-id level objects]
(let [shape (get objects shape-id)] (let [shape (get objects shape-id)]
(println (str/pad (str (str/repeat " " level) (println (str/pad (str (str/repeat " " level)
(:name shape) (when (:main-instance? shape) "{")
(when (seq (:touched shape)) "*") (:name shape)
(when show-ids (str/format " <%s>" (:id shape)))) (when (:main-instance? shape) "}")
{:length 20 (when (seq (:touched shape)) "*")
:type :right}) (when show-ids (str/format " <%s>" (:id shape))))
(show-component shape objects)) {:length 20
(when show-touched :type :right})
(when (seq (:touched shape)) (show-component-info shape objects))
(println (str (str/repeat " " level) (when show-touched
" " (when (seq (:touched shape))
(str (:touched shape))))) (println (str (str/repeat " " level)
(when (:remote-synced? shape) " "
(println (str (str/repeat " " level) (str (:touched shape)))))
" (remote-synced)")))) (when (:remote-synced? shape)
(when (:shapes shape) (println (str (str/repeat " " level)
(dorun (for [shape-id (:shapes shape)] " (remote-synced)"))))
(show-shape shape-id (inc level) objects)))))) (when (:shapes shape)
(dorun (for [shape-id (:shapes shape)]
(show-shape shape-id (inc level) objects))))))
(show-component [shape objects] (show-component-info [shape objects]
(if (nil? (:shape-ref shape)) (if (nil? (:shape-ref shape))
"" (if (:component-root? shape) " #" "")
(let [root-shape (cph/get-component-shape objects shape) (let [root-shape (ctn/get-component-shape objects shape)
component-id (when root-shape (:component-id root-shape)) component-id (when root-shape (:component-id root-shape))
component-file-id (when root-shape (:component-file 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-file (when component-file-id (get libraries component-file-id nil))
component (when component-id component (when component-id
(if component-file (if component-file
(dm/get-in component-file [:data :components component-id]) (dm/get-in component-file [:data :components component-id])
(get components component-id))) (get components component-id)))
component-shape (when (and component (:shape-ref shape)) component-shape (when component
(dm/get-in component [:objects (:shape-ref shape)]))] (if component-file
(str/format " %s--> %s%s%s" (get-ref-shape (:data component-file) component shape)
(cond (:component-root? shape) "#" (get-ref-shape file-data component 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))))))))]
(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
(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-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) (show-shape (:id root) 0 objects)
(dorun (for [component (vals components)] (dorun (for [component (vals components)]
(do (do
(println) (println)
(println (str/format "[%s]" (:name component)) (println (str/format "[%s]" (:name component)))
(when show-ids (when (:objects component)
(str/format " (main: %s/%s)" (show-shape (:id component) 0 (:objects component)))
(:main-instance-page component) (when (:main-instance-page component)
(:main-instance-id component)))) (show-component-instance component)))))))))
(show-shape (:id component) 0 (:objects component)))))))))

View file

@ -359,4 +359,3 @@
(iterate next-pos (iterate next-pos
(with-meta start-pos (with-meta start-pos
{:counter 0})))) {:counter 0}))))

View file

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

View file

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

View file

@ -233,6 +233,19 @@
(->> (filter (comp t/pointer? val) data) (->> (filter (comp t/pointer? val) data)
(resolve-pointers id) (resolve-pointers id)
(rx/map #(update file :data merge %))))) (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/reduce conj [])
(rx/map libraries-fetched)))))))) (rx/map libraries-fetched))))))))
@ -808,8 +821,8 @@
(not (:component-root? shape))) (not (:component-root? shape)))
parent (get objects parent-id) parent (get objects parent-id)
component-shape (cph/get-component-shape objects shape) component-shape (ctn/get-component-shape objects shape)
component-shape-parent (cph/get-component-shape objects parent) component-shape-parent (ctn/get-component-shape objects parent)
detach? (and instance-part? (not= (:id component-shape) detach? (and instance-part? (not= (:id component-shape)
(:id component-shape-parent))) (:id component-shape-parent)))

View file

@ -17,6 +17,7 @@
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
@ -346,8 +347,9 @@
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(when (and (some? new-name) (not= "" new-name)) (when (and (some? new-name) (not= "" new-name))
(let [data (get state :workspace-data) (let [data (get state :workspace-data)
[path name] (cph/parse-path-name new-name) [path name] (cph/parse-path-name new-name)
components-v2 (features/active-feature? state :components-v2)
update-fn update-fn
(fn [component] (fn [component]
@ -355,12 +357,15 @@
;; because there are small possibilities of race ;; because there are small possibilities of race
;; conditions with component deletion. ;; conditions with component deletion.
(when component (when component
(-> component (cond-> component
(assoc :path path) :always
(assoc :name name) (assoc :path path
(update :objects :name name)
(not components-v2)
(update :objects
;; Give the same name to the root shape ;; Give the same name to the root shape
#(assoc-in % [id :name] name))))) #(assoc-in % [id :name] name)))))
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(pcb/with-library-data data) (pcb/with-library-data data)
@ -370,30 +375,33 @@
(defn duplicate-component (defn duplicate-component
"Create a new component copied from the one with the given id." "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/reify ::duplicate-component
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [libraries (wsh/get-libraries 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) new-name (:name component)
components-v2 (features/active-feature? state :components-v2) components-v2 (features/active-feature? state :components-v2)
main-instance-page (when components-v2 main-instance-page (when components-v2
(wsh/lookup-page state (:main-instance-page component))) (ctf/get-component-page (:data library) component))
main-instance-shape (when components-v2
(ctn/get-shape main-instance-page (:main-instance-id 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] 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) changes (-> (pcb/empty-changes it nil)
(pcb/with-page main-instance-page) (pcb/with-page main-instance-page)
(pcb/with-objects (:objects main-instance-page)) (pcb/with-objects (:objects main-instance-page))
(pcb/add-objects new-main-instance-shapes {:ignore-touched true}) (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) (:path component)
new-name new-name
new-component-shapes new-component-shapes
@ -429,17 +437,22 @@
(ptk/reify ::restore-component (ptk/reify ::restore-component
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(assert "Restore component not implemented") ; until we allow a :deleted flag in shapes
(let [file-data (wsh/get-file state library-id) (let [file-data (wsh/get-file state library-id)
component (ctf/get-deleted-component file-data component-id) component (ctf/get-deleted-component file-data component-id)
page (ctpl/get-page file-data (:main-instance-page component)) page (ctpl/get-page file-data (:main-instance-page component))
; Make a new main instance, with the same id of the original components-v2
(features/active-feature? state :components-v2)
; Make a new main instance, with the same id of the original
[_main-instance shapes] [_main-instance shapes]
(ctn/make-component-instance page (ctn/make-component-instance page
component component
(:id file-data) file-data
(gpt/point (:main-instance-x component) (gpt/point (:main-instance-x component)
(:main-instance-y component)) (:main-instance-y component))
components-v2
{:main-instance? true {:main-instance? true
:force-id (:main-instance-id component)}) :force-id (:main-instance-id component)})
@ -600,53 +613,53 @@
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(log/info :msg "UPDATE-COMPONENT of shape" :id (str id)) (log/info :msg "UPDATE-COMPONENT of shape" :id (str id))
(let [page-id (get state :current-page-id) (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) (when (ctk/in-component-instance? shape)
libraries (wsh/get-libraries state) (let [libraries (wsh/get-libraries state)
container (cph/get-container local-file :page page-id) changes
shape (ctn/get-shape container id) (-> (pcb/empty-changes it)
(pcb/with-container container)
(dwlh/generate-sync-shape-inverse libraries container id))
changes file-id (:component-file shape)
(-> (pcb/empty-changes it) file (wsh/get-file state file-id)
(pcb/with-container container)
(dwlh/generate-sync-shape-inverse libraries container id))
file-id (:component-file shape) xf-filter (comp
file (wsh/get-file state file-id) (filter :local-change?)
(map #(dissoc % :local-change?)))
xf-filter (comp local-changes (-> changes
(filter :local-change?) (update :redo-changes #(into [] xf-filter %))
(map #(dissoc % :local-change?))) (update :undo-changes #(into [] xf-filter %)))
local-changes (-> changes xf-remove (comp
(update :redo-changes #(into [] xf-filter %)) (remove :local-change?)
(update :undo-changes #(into [] xf-filter %))) (map #(dissoc % :local-change?)))
xf-remove (comp nonlocal-changes (-> changes
(remove :local-change?) (update :redo-changes #(into [] xf-remove %))
(map #(dissoc % :local-change?))) (update :undo-changes #(into [] xf-remove %)))]
nonlocal-changes (-> changes (log/debug :msg "UPDATE-COMPONENT finished"
(update :redo-changes #(into [] xf-remove %)) :js/local-changes (log-changes
(update :undo-changes #(into [] xf-remove %)))] (:redo-changes local-changes)
file)
:js/nonlocal-changes (log-changes
(:redo-changes nonlocal-changes)
file))
(log/debug :msg "UPDATE-COMPONENT finished" (rx/of
:js/local-changes (log-changes (when (seq (:redo-changes local-changes))
(:redo-changes local-changes) (dch/commit-changes (assoc local-changes
file) :file-id (:id local-file))))
:js/nonlocal-changes (log-changes (when (seq (:redo-changes nonlocal-changes))
(:redo-changes nonlocal-changes) (dch/commit-changes (assoc nonlocal-changes
file)) :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 (defn update-component-sync
[shape-id file-id] [shape-id file-id]

View file

@ -7,6 +7,7 @@
(ns app.main.data.workspace.libraries-helpers (ns app.main.data.workspace.libraries-helpers
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.logging :as log] [app.common.logging :as log]
@ -17,10 +18,12 @@
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.typography :as cty] [app.common.types.typography :as cty]
[app.common.uuid :as uuid]
[app.main.data.workspace.groups :as dwg] [app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
@ -84,54 +87,75 @@
name (:name group) name (:name group)
[path name] (cph/parse-path-name name) [path name] (cph/parse-path-name name)
[new-shape new-shapes updated-shapes] [root-shape new-shapes updated-shapes]
(ctn/make-component-shape group objects file-id components-v2) (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 changes (-> changes
(pcb/add-component (:id new-shape) (pcb/add-component (:id root-shape)
path path
name name
new-shapes new-shapes
updated-shapes updated-shapes
(:id group) (:id group)
page-id))] page-id))]
[group new-shape changes])) [group (:id root-shape) changes]))
(defn duplicate-component (defn duplicate-component
"Clone the root shape of the component and all children. Generate new "Clone the root shape of the component and all children. Generate new
ids from all of them." ids from all of them."
[component main-instance-page main-instance-shape] [component library-data]
(let [position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape)) (let [components-v2 (dm/get-in library-data [:options :components-v2])]
(gpt/point (+ (:width main-instance-shape) 50) 0)) (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 _] position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape))
(ctst/clone-object component-root (gpt/point (+ (:width main-instance-shape) 50) 0))
nil
(get component :objects)
identity)
[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))]
[nil nil new-instance-shape new-instance-shapes])
[new-instance-shape new-instance-shapes] (let [component-root (d/seek #(nil? (:parent-id %)) (vals (:objects component)))
(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-component-shape new-component-shapes [new-component-shape new-component-shapes _]
new-instance-shape new-instance-shapes])) (ctst/clone-object component-root
nil
(get component :objects)
identity)]
[new-component-shape new-component-shapes nil nil]))))
(defn generate-instantiate-component (defn generate-instantiate-component
"Generate changes to create a new instance from a component." "Generate changes to create a new instance from a component."
[it file-id component-id position page libraries] [it file-id component-id position page libraries]
(let [component (cph/get-component libraries file-id component-id) (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] [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}) changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
(pcb/empty-changes it (:id page)) (pcb/empty-changes it (:id page))
@ -447,47 +471,47 @@
;; but it's not touched. ;; but it's not touched.
(defn generate-sync-shape-direct (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." instance, and all its children, from the given component."
[changes libraries container shape-id reset? components-v2] [changes libraries container shape-id reset? components-v2]
(log/debug :msg "Sync shape direct" :shape (str shape-id) :reset? reset?) (log/debug :msg "Sync shape direct" :shape (str shape-id) :reset? reset?)
(let [shape-inst (ctn/get-shape container shape-id) (let [shape-inst (ctn/get-shape container shape-id)]
component (cph/get-component libraries (if (ctk/in-component-instance? shape-inst)
(:component-file shape-inst) (let [library (dm/get-in libraries [(:component-file shape-inst) :data])
(:component-id shape-inst)) component (or (ctkl/get-component library (:component-id shape-inst))
component (or component (and reset?
(and reset? (ctf/get-deleted-component library (:component-id shape-inst))))
(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)))
initial-root? (:component-root? shape-inst) shape-main (when component
(ctf/get-ref-shape library component shape-inst))
root-inst shape-inst initial-root? (:component-root? shape-inst)
root-main (when component
(ctk/get-component-root component))]
(if component root-inst shape-inst
(generate-sync-shape-direct-recursive changes root-main (when component
container (ctf/get-component-root library component))]
shape-inst
component (if component
shape-main (generate-sync-shape-direct-recursive changes
root-inst container
root-main shape-inst
reset? component
initial-root? library
components-v2) shape-main
root-inst
root-main
reset?
initial-root?
components-v2)
; If the component is not found, because the master component has been ; If the component is not found, because the master component has been
; deleted or the library unlinked, do nothing in v2 or detach in v1. ; deleted or the library unlinked, do nothing in v2 or detach in v1.
(if components-v2 (if components-v2
changes changes
(generate-detach-instance changes container shape-id))))) (generate-detach-instance changes container shape-id))))
changes)))
(defn- generate-sync-shape-direct-recursive (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" (log/debug :msg "Sync shape direct recursive"
:shape (str (:name shape-inst)) :shape (str (:name shape-inst))
:component (:name component)) :component (:name component))
@ -522,20 +546,22 @@
set-remote-synced? set-remote-synced?
(change-remote-synced shape-inst container true)) (change-remote-synced shape-inst container true))
children-inst (mapv #(ctn/get-shape container %) component-container (ctf/get-component-container library component)
(:shapes shape-inst))
children-main (mapv #(ctn/get-shape component %) children-inst (mapv #(ctn/get-shape container %)
(:shapes shape-main)) (:shapes shape-inst))
children-main (mapv #(ctn/get-shape component-container %)
(:shapes shape-main))
only-inst (fn [changes child-inst] only-inst (fn [changes child-inst]
(if-not (and omit-touched? (if-not (and omit-touched?
(contains? (:touched shape-inst) (contains? (:touched shape-inst)
:shapes-group)) :shapes-group))
(remove-shape changes (remove-shape changes
child-inst child-inst
container container
omit-touched?) omit-touched?)
changes)) changes))
only-main (fn [changes child-main] only-main (fn [changes child-main]
(if-not (and omit-touched? (if-not (and omit-touched?
@ -545,7 +571,7 @@
child-main child-main
(d/index-of children-main (d/index-of children-main
child-main) child-main)
component component-container
container container
root-inst root-inst
root-main root-main
@ -558,6 +584,7 @@
container container
child-inst child-inst
component component
library
child-main child-main
root-inst root-inst
root-main root-main
@ -567,12 +594,12 @@
moved (fn [changes child-inst child-main] moved (fn [changes child-inst child-main]
(move-shape (move-shape
changes changes
child-inst child-inst
(d/index-of children-inst child-inst) (d/index-of children-inst child-inst)
(d/index-of children-main child-main) (d/index-of children-main child-main)
container container
omit-touched?))] omit-touched?))]
(compare-children changes (compare-children changes
children-inst children-inst
@ -588,22 +615,22 @@
the values in the shape and all its children." the values in the shape and all its children."
[changes libraries container shape-id] [changes libraries container shape-id]
(log/debug :msg "Sync shape inverse" :shape (str shape-id)) (log/debug :msg "Sync shape inverse" :shape (str shape-id))
(let [shape-inst (ctn/get-shape container shape-id) (let [shape-inst (ctn/get-shape container shape-id)
component (cph/get-component libraries library (dm/get-in libraries [(:component-file shape-inst) :data])
(:component-file shape-inst) component (ctkl/get-component library (:component-id shape-inst))
(:component-id shape-inst)) shape-main (ctf/get-ref-shape library component shape-inst)
shape-main (ctn/get-shape component (:shape-ref shape-inst))
initial-root? (:component-root? shape-inst) initial-root? (:component-root? shape-inst)
root-inst shape-inst root-inst shape-inst
root-main (ctk/get-component-root component)] root-main (ctf/get-component-root library component)]
(if component (if component
(generate-sync-shape-inverse-recursive changes (generate-sync-shape-inverse-recursive changes
container container
shape-inst shape-inst
component component
library
shape-main shape-main
root-inst root-inst
root-main root-main
@ -611,7 +638,7 @@
changes))) changes)))
(defn- generate-sync-shape-inverse-recursive (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" (log/trace :msg "Sync shape inverse recursive"
:shape (str (:name shape-inst)) :shape (str (:name shape-inst))
:component (:name component)) :component (:name component))
@ -619,7 +646,7 @@
(if (nil? shape-main) (if (nil? shape-main)
;; This should not occur, but protect against it in any case ;; This should not occur, but protect against it in any case
changes changes
(let [component-container (cph/make-container component :component) (let [component-container (ctf/get-component-container library component)
omit-touched? false omit-touched? false
set-remote-synced? (not initial-root?) set-remote-synced? (not initial-root?)
@ -650,7 +677,7 @@
children-inst (mapv #(ctn/get-shape container %) children-inst (mapv #(ctn/get-shape container %)
(:shapes shape-inst)) (:shapes shape-inst))
children-main (mapv #(ctn/get-shape component %) children-main (mapv #(ctn/get-shape component-container %)
(:shapes shape-main)) (:shapes shape-main))
only-inst (fn [changes child-inst] only-inst (fn [changes child-inst]
@ -659,6 +686,7 @@
(d/index-of children-inst (d/index-of children-inst
child-inst) child-inst)
component component
component-container
container container
root-inst root-inst
root-main)) root-main))
@ -674,6 +702,7 @@
container container
child-inst child-inst
component component
library
child-main child-main
root-inst root-inst
root-main root-main
@ -681,12 +710,12 @@
moved (fn [changes child-inst child-main] moved (fn [changes child-inst child-main]
(move-shape (move-shape
changes changes
child-main child-main
(d/index-of children-main child-main) (d/index-of children-main child-main)
(d/index-of children-inst child-inst) (d/index-of children-inst child-inst)
component-container component-container
false)) false))
changes changes
(compare-children changes (compare-children changes
@ -763,9 +792,9 @@
(moved-cb child-inst' child-main))))))))))) (moved-cb child-inst' child-main)))))))))))
(defn- add-shape-to-instance (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))) (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 %) parent-shape (d/seek #(ctk/is-main-of? component-parent-shape %)
(cph/get-children-with-self (:objects container) (cph/get-children-with-self (:objects container)
(:id root-instance))) (:id root-instance)))
@ -793,7 +822,7 @@
[_ new-shapes _] [_ new-shapes _]
(ctst/clone-object component-shape (ctst/clone-object component-shape
(:id parent-shape) (:id parent-shape)
(get component :objects) (get component-page :objects)
update-new-shape update-new-shape
update-original-shape) update-original-shape)
@ -831,14 +860,14 @@
changes'))) changes')))
(defn- add-shape-to-main (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))) (log/info :msg (str "ADD [C] " (:name shape)))
(let [parent-shape (ctn/get-shape page (:parent-id shape)) (let [parent-shape (ctn/get-shape page (:parent-id shape))
component-parent-shape (d/seek #(ctk/is-main-of? % parent-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))) (:id root-main)))
all-parents (into [(:id component-parent-shape)] all-parents (into [(:id component-parent-shape)]
(cph/get-parent-ids (:objects component) (cph/get-parent-ids (:objects component-container)
(:id component-parent-shape))) (:id component-parent-shape)))
update-new-shape (fn [new-shape _original-shape] update-new-shape (fn [new-shape _original-shape]
@ -854,20 +883,24 @@
[_new-shape new-shapes updated-shapes] [_new-shape new-shapes updated-shapes]
(ctst/clone-object shape (ctst/clone-object shape
(:id component-parent-shape) (:id component-parent-shape)
(get page :objects) (get page :objects)
update-new-shape update-new-shape
update-original-shape) update-original-shape)
add-obj-change (fn [changes shape'] add-obj-change (fn [changes shape']
(update changes :redo-changes conj (update changes :redo-changes conj
{:type :add-obj (cond-> (make-change
:id (:id shape') component-container
:component-id (:id component) {:type :add-obj
:parent-id (:parent-id shape') :id (:id shape')
:index index :parent-id (:parent-id shape')
:ignore-touched true :index index
:obj shape'})) :ignore-touched true
:obj shape'})
(ctn/page? component-container)
(assoc :frame-id (:frame-id shape')))))
mod-obj-change (fn [changes shape'] mod-obj-change (fn [changes shape']
(update changes :redo-changes conj (update changes :redo-changes conj
@ -899,8 +932,8 @@
changes' (reduce add-obj-change changes new-shapes) changes' (reduce add-obj-change changes new-shapes)
changes' (update changes' :redo-changes conj {:type :reg-objects changes' (update changes' :redo-changes conj {:type :reg-objects
:component-id (:id component) :component-id (:id component)
:shapes all-parents}) :shapes all-parents})
changes' (reduce mod-obj-change changes' updated-shapes) changes' (reduce mod-obj-change changes' updated-shapes)
changes' (reduce del-obj-change changes' new-shapes)] changes' (reduce del-obj-change changes' new-shapes)]

View file

@ -14,6 +14,7 @@
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
@ -171,7 +172,7 @@
;; not the root. In this case, they must not be deleted, ;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily). ;; but hidden (to be able to recover them more easily).
(let [shape (get objects shape-id) (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) (and (ctk/in-component-instance? shape)
(not= shape component-shape) (not= shape component-shape)
(not (ctk/main-instance? component-shape))))) (not (ctk/main-instance? component-shape)))))

View file

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

View file

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

View file

@ -243,7 +243,9 @@
(events/unlistenByKey key1)))) (events/unlistenByKey key1))))
(mf/use-effect on-resize) (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 (case section
:dashboard-projects :dashboard-projects
[:* [:*

View file

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

View file

@ -12,7 +12,7 @@
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.component :as ctk] [app.common.types.file :as ctf]
[app.config :as cf] [app.config :as cf]
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
@ -365,13 +365,17 @@
;;---- Components box ---- ;;---- Components box ----
(mf/defc components-item (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 on-asset-click on-context-menu on-drag-start do-rename cancel-rename
selected-components-full selected-components-paths]}] selected-components-full selected-components-paths]}]
(let [item-ref (mf/use-ref) (let [item-ref (mf/use-ref)
dragging? (mf/use-state false) dragging? (mf/use-state false)
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
components-v2 (mf/use-ctx ctx/components-v2)
file (or (:data file) file)
unselect-all unselect-all
(mf/use-fn (mf/use-fn
(fn [] (fn []
@ -440,15 +444,17 @@
:on-drag-over on-drag-over :on-drag-over on-drag-over
:on-drop on-drop} :on-drop on-drop}
[:& component-svg {:root-shape (ctk/get-component-root component) [:& component-svg {:root-shape (ctf/get-component-root file component)
:objects (:objects component)}] :objects (:objects (if components-v2
(ctf/get-component-page file component)
component))}]
(let [renaming? (= renaming (:id component))] (let [renaming? (= renaming (:id component))]
[:* [:*
[:& editable-label [:& editable-label
{:class-name (dom/classnames {:class-name (dom/classnames
:cell-name listing-thumbs? :cell-name listing-thumbs?
:item-name (not listing-thumbs?) :item-name (not listing-thumbs?)
:editing renaming?) :editing renaming?)
:value (cph/merge-path-item (:path component) (:name component)) :value (cph/merge-path-item (:path component) (:name component))
:tooltip (cph/merge-path-item (:path component) (:name component)) :tooltip (cph/merge-path-item (:path component) (:name component))
:display-value (:name component) :display-value (:name component)
@ -460,7 +466,7 @@
[:div.dragging])])])) [:div.dragging])])]))
(mf/defc components-group (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 on-drag-start do-rename cancel-rename on-rename-group on-group on-ungroup on-context-menu
selected-components-full]}] selected-components-full]}]
(let [group-open? (get open-groups prefix true) (let [group-open? (get open-groups prefix true)
@ -495,7 +501,7 @@
:on-drag-leave on-drag-leave :on-drag-leave on-drag-leave
:on-drag-over on-drag-over :on-drag-over on-drag-over
:on-drop on-drop} :on-drop on-drop}
[:& asset-group-title {:file-id file-id [:& asset-group-title {:file-id (:id file)
:box :components :box :components
:path prefix :path prefix
:group-open? group-open? :group-open? group-open?
@ -528,6 +534,7 @@
:key (:id component) :key (:id component)
:renaming renaming :renaming renaming
:listing-thumbs? listing-thumbs? :listing-thumbs? listing-thumbs?
:file file
:selected-components selected-components :selected-components selected-components
:on-asset-click on-asset-click :on-asset-click on-asset-click
:on-context-menu on-context-menu :on-context-menu on-context-menu
@ -539,7 +546,7 @@
:selected-components-paths selected-components-paths}])]) :selected-components-paths selected-components-paths}])])
(for [[path-item content] groups] (for [[path-item content] groups]
(when-not (empty? path-item) (when-not (empty? path-item)
[:& components-group {:file-id file-id [:& components-group {:file file
:prefix (cph/merge-path-item prefix path-item) :prefix (cph/merge-path-item prefix path-item)
:groups content :groups content
:open-groups open-groups :open-groups open-groups
@ -556,7 +563,7 @@
:selected-components-full selected-components-full}]))])])) :selected-components-full selected-components-full}]))])]))
(mf/defc components-box (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}] on-asset-click on-assets-delete on-clear-selection] :as props}]
(let [input-ref (mf/use-ref nil) (let [input-ref (mf/use-ref nil)
state (mf/use-state {:renaming nil state (mf/use-state {:renaming nil
@ -579,14 +586,14 @@
add-component add-component
(mf/use-fn (mf/use-fn
(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)))) (dom/click (mf/ref-val input-ref))))
on-file-selected on-file-selected
(mf/use-fn (mf/use-fn
(mf/deps file-id) (mf/deps file)
(fn [blobs] (fn [blobs]
(let [params {:file-id file-id (let [params {:file-id (:id file)
:blobs (seq blobs)}] :blobs (seq blobs)}]
(st/emit! (dwm/upload-media-components params) (st/emit! (dwm/upload-media-components params)
(ptk/event ::ev/event {::ev/name "add-asset-to-library" (ptk/event ::ev/event {::ev/name "add-asset-to-library"
@ -598,22 +605,22 @@
(fn [] (fn []
(let [undo-id (js/Symbol)] (let [undo-id (js/Symbol)]
(if (empty? selected-components) (if (empty? selected-components)
(st/emit! (dwl/duplicate-component {:id (:component-id @state)})) (st/emit! (dwl/duplicate-component (:id file) (:component-id @state)))
(do (do
(st/emit! (dwu/start-undo-transaction undo-id)) (st/emit! (dwu/start-undo-transaction undo-id))
(apply st/emit! (map #(dwl/duplicate-component {:id %}) selected-components)) (apply st/emit! (map (partial dwl/duplicate-component (:id file)) selected-components))
(st/emit! (dwu/commit-undo-transaction undo-id))))))) (st/emit! (dwu/commit-undo-transaction undo-id)))))))
on-delete on-delete
(mf/use-fn (mf/use-fn
(mf/deps @state file-id multi-components? multi-assets?) (mf/deps @state file multi-components? multi-assets?)
(fn [] (fn []
(let [undo-id (js/Symbol)] (let [undo-id (js/Symbol)]
(if (or multi-components? multi-assets?) (if (or multi-components? multi-assets?)
(on-assets-delete) (on-assets-delete)
(st/emit! (dwu/start-undo-transaction undo-id) (st/emit! (dwu/start-undo-transaction undo-id)
(dwl/delete-component {:id (:component-id @state)}) (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)))))) (dwu/commit-undo-transaction undo-id))))))
on-rename on-rename
@ -716,7 +723,7 @@
on-drag-start on-drag-start
(mf/use-fn (mf/use-fn
(fn [component event] (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}) :component component})
(dnd/set-allowed-effect! event "move"))) (dnd/set-allowed-effect! event "move")))
@ -734,7 +741,7 @@
(when (and main-instance-id main-instance-page) ;; Only when :components-v2 is enabled (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))))))] (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") :title (tr "workspace.assets.components")
:box :components :box :components
:assets-count (count components) :assets-count (count components)
@ -750,7 +757,7 @@
:on-selected on-file-selected}]])]) :on-selected on-file-selected}]])])
[:& asset-section-block {:role :content} [:& asset-section-block {:role :content}
[:& components-group {:file-id file-id [:& components-group {:file file
:prefix "" :prefix ""
:groups groups :groups groups
:open-groups open-groups :open-groups open-groups
@ -2158,7 +2165,7 @@
i/listing-thumbs)]] i/listing-thumbs)]]
(when show-components? (when show-components?
[:& components-box {:file-id (:id file) [:& components-box {:file file
:local? local? :local? local?
:components components :components components
:listing-thumbs? listing-thumbs? :listing-thumbs? listing-thumbs?