diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index d8dc77086..258ac59fc 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -492,10 +492,13 @@ (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 [components-sample (-> (assets-sample (:components data) 4) + (update :sample + #(map (partial ctf/load-component-objects data) %)))] + {: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 +555,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))) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 2b7e2fc3f..53b8b2e37 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -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})] diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 3cf5c6100..158e5044a 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -15,6 +15,7 @@ [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.types.components-list :as ctkl] [app.common.types.file :as ctf] [app.common.uuid :as uuid])) @@ -598,13 +599,14 @@ (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] @@ -640,8 +642,9 @@ (defn delete-component [changes id components-v2] (assert-library changes) - (let [library-data (::library-data (meta changes)) - prev-component (get-in library-data [:components id])] + (let [library-data (::library-data (meta changes)) + component (ctkl/get-component library-data id) + shapes (ctf/get-component-shapes library-data component)] (-> changes (update :redo-changes conj {:type :del-component :id id}) @@ -655,11 +658,11 @@ :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))}))))))) + :name (:name component) + :path (:path component) + :main-instance-id (:main-instance-id component) + :main-instance-page (:main-instance-page component) + :shapes shapes}))))))) (defn restore-component [changes id] diff --git a/common/src/app/common/pages/changes_spec.cljc b/common/src/app/common/pages/changes_spec.cljc index 1cd63c7e2..f43832c51 100644 --- a/common/src/app/common/pages/changes_spec.cljc +++ b/common/src/app/common/pages/changes_spec.cljc @@ -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] diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 589d2249b..d28d4d044 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -270,25 +270,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] diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 7177a52f9..fee289292 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -34,6 +34,7 @@ (and (= shape-id (:main-instance-id component)) (= page-id (:main-instance-page component)))) +;; Obsolete in components-v2 (defn get-component-root [component] (get-in component [:objects (:id component)])) @@ -45,12 +46,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)) diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc index 137990c8a..7bbb5ab0b 100644 --- a/common/src/app/common/types/components_list.cljc +++ b/common/src/app/common/types/components_list.cljc @@ -23,11 +23,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 @@ -60,4 +62,3 @@ (defn delete-component [file-data component-id] (update file-data :components dissoc component-id)) - diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 7c9950a8b..c23bd1475 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -11,6 +11,7 @@ [app.common.geom.shapes :as gsh] [app.common.pages.common :as common] [app.common.spec :as us] + [app.common.types.pages-list :as ctpl] [app.common.types.shape-tree :as ctst] [clojure.spec.alpha :as s])) @@ -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)) (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)]))) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 8bc075b1e..6dfa41450 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -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,6 +116,75 @@ ([libraries library-id 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 "Delete a component and store it to be able to be recovered later. @@ -123,26 +192,29 @@ ([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]) + ([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)))] + ;; TODO: replace :deleted-components with a :deleted? flag in normal shapes + ;; see task https://tree.taiga.io/project/penpot/task/4998 + ;; + ;; add-to-deleted-components + ;; (fn [file-data] + ;; (let [component (ctkl/get-component file-data component-id)] + ;; (if (some? component) + ;; (let [main-instance (get-component-root file-data component) + ;; component (assoc component + ;; :main-instance-x (:x main-instance) ; An instance root is always a group, + ;; :main-instance-y (:y main-instance))] ; or a frame, 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) + ;; (and components-v2 (not skip-undelete?)) + ;; (add-to-deleted-components) :always (ctkl/delete-component component-id))))) @@ -243,8 +315,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 +335,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 +355,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 +367,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 +386,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 +401,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 +435,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 +460,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 +492,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 +516,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 +534,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) @@ -477,69 +559,82 @@ 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 + (dm/get-in component-file [:data :components 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 + (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) (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))))))))) diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 5f281a890..b6ab8f735 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -359,4 +359,3 @@ (iterate next-pos (with-meta start-pos {:counter 0})))) - diff --git a/common/test/common_tests/helpers/components.cljc b/common/test/common_tests/helpers/components.cljc index b96e0fea6..4cfbdd547 100644 --- a/common/test/common_tests/helpers/components.cljc +++ b/common/test/common_tests/helpers/components.cljc @@ -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))] diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index 66fe15034..3c20d584f 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -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 diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 279440ed0..aaa2f3936 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -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))) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 6f23a0487..726cd60e5 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -17,6 +17,7 @@ [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] @@ -346,8 +347,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 +357,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 +375,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 @@ -429,17 +437,22 @@ (ptk/reify ::restore-component ptk/WatchEvent (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) 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 + components-v2 + (features/active-feature? state :components-v2) + + ; Make a new main instance, with the same id of the original [_main-instance shapes] (ctn/make-component-instance page component - (:id file-data) + file-data (gpt/point (:main-instance-x component) (:main-instance-y component)) + components-v2 {:main-instance? true :force-id (:main-instance-id component)}) @@ -600,53 +613,53 @@ ptk/WatchEvent (watch [it state _] (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) - 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/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] diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 261b3ba5b..5b56bd116 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -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,10 +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.common.uuid :as uuid] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.state-helpers :as wsh] [cljs.spec.alpha :as s] @@ -84,54 +87,75 @@ 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)) + [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] - (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))] + (let [component-root (d/seek #(nil? (:parent-id %)) (vals (:objects component))) - [new-component-shape new-component-shapes - new-instance-shape new-instance-shapes])) + [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) + (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)) @@ -447,47 +471,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? + (ctf/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 +546,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 +571,7 @@ child-main (d/index-of children-main child-main) - component + component-container container root-inst root-main @@ -558,6 +584,7 @@ container child-inst component + library child-main root-inst root-main @@ -567,12 +594,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 +615,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 +638,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 +646,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 +677,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 +686,7 @@ (d/index-of children-inst child-inst) component + component-container container root-inst root-main)) @@ -674,6 +702,7 @@ container child-inst component + library child-main root-inst root-main @@ -681,12 +710,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 +792,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 +822,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 +860,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 +883,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 +932,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)] diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 525fc5397..5e57a888a 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -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))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 86ca9d33d..b3487c12a 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -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 diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index ff2c24ee8..f50975111 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -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]} diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 7e0c6ab0b..8498c1d97 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -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 [:* diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index eba451495..2e8378033 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index d411219c5..fb2e4b594 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -12,7 +12,7 @@ [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.file :as ctf] [app.config :as cf] [app.main.data.events :as ev] [app.main.data.modal :as modal] @@ -365,13 +365,17 @@ ;;---- 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) + unselect-all (mf/use-fn (fn [] @@ -440,15 +444,17 @@ :on-drag-over on-drag-over :on-drop on-drop} - [:& component-svg {:root-shape (ctk/get-component-root component) - :objects (:objects component)}] + [:& component-svg {:root-shape (ctf/get-component-root file component) + :objects (:objects (if components-v2 + (ctf/get-component-page file component) + component))}] (let [renaming? (= renaming (:id component))] [:* [:& editable-label {:class-name (dom/classnames - :cell-name listing-thumbs? - :item-name (not listing-thumbs?) - :editing renaming?) + :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) @@ -460,7 +466,7 @@ [: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 +501,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 +534,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 +546,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 +563,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 +586,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 +605,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 +723,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 +741,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 +757,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 @@ -2158,7 +2165,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?