From 2b978777d7de3ee2c1d0e454563a9625e5c6be79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 5 Jan 2023 16:06:41 +0100 Subject: [PATCH 1/3] :bug: Fix import/export components with boards inside --- common/src/app/common/file_builder.cljc | 81 ++++++++++--------- common/src/app/common/geom/shapes.cljc | 2 - .../src/app/common/pages/changes_builder.cljc | 1 - common/src/app/common/pages/changes_spec.cljc | 3 +- common/src/app/common/types/container.cljc | 59 +++++++++----- frontend/src/app/main/render.cljs | 16 ++-- frontend/src/app/util/import/parser.cljs | 18 ++--- frontend/src/app/worker/import.cljs | 14 ++-- 8 files changed, 113 insertions(+), 81 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 99f9584e5..ff30a2030 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -44,7 +44,12 @@ (let [component-id (:current-component-id file) change (cond-> change (and add-container? (some? component-id)) - (assoc :component-id component-id) + (cond-> + :always + (assoc :component-id component-id) + + (some? (:current-frame-id file)) + (assoc :frame-id (:current-frame-id file))) (and add-container? (nil? component-id)) (assoc :page-id (:current-page-id file) @@ -223,7 +228,6 @@ (clear-names))) (defn add-artboard [file data] - (assert (nil? (:current-component-id file))) (let [obj (-> (cts/make-minimal-shape :frame) (merge data) (check-name file :frame) @@ -237,11 +241,11 @@ (update :parent-stack conjv (:id obj))))) (defn close-artboard [file] - (assert (nil? (:current-component-id file))) - (let [parent-id (-> file :parent-id peek) parent (lookup-shape file parent-id) - current-frame-id (or (:frame-id parent) root-frame)] + current-frame-id (or (:frame-id parent) + (when (nil? (:current-component-id file)) + root-frame))] (-> file (assoc :current-frame-id current-frame-id) (update :parent-stack pop)))) @@ -561,35 +565,38 @@ :id id})))) (defn start-component - [file data] + ([file data] (start-component file data :group)) + ([file data root-type] + (let [selrect (if (and (:x data) (:y data) (:width data) (:height data)) + (gsh/make-selrect (:x data) (:y data) (:width data) (:height data)) + cts/empty-selrect) + name (:name data) + path (:path data) + main-instance-id (:main-instance-id data) + main-instance-page (:main-instance-page data) + obj (-> (cts/make-shape root-type selrect data) + (dissoc :path + :main-instance-id + :main-instance-page + :main-instance-x + :main-instance-y) + (check-name file root-type) + (d/without-nils))] + (-> file + (commit-change + {:type :add-component + :id (:id obj) + :name name + :path path + :main-instance-id main-instance-id + :main-instance-page main-instance-page + :shapes [obj]}) - (let [selrect cts/empty-selrect - name (:name data) - path (:path data) - main-instance-id (:main-instance-id data) - main-instance-page (:main-instance-page data) - obj (-> (cts/make-minimal-group nil selrect name) - (merge data) - (dissoc :path - :main-instance-id - :main-instance-page - :main-instance-x - :main-instance-y) - (check-name file :group) - (d/without-nils))] - (-> file - (commit-change - {:type :add-component - :id (:id obj) - :name name - :path path - :main-instance-id main-instance-id - :main-instance-page main-instance-page - :shapes [obj]}) - - (assoc :last-id (:id obj)) - (update :parent-stack conjv (:id obj)) - (assoc :current-component-id (:id obj))))) + (assoc :last-id (:id obj)) + (update :parent-stack conjv (:id obj)) + (assoc :current-component-id (:id obj)) + (assoc :current-frame-id (when (= (:type obj) :frame) + (:id obj))))))) (defn finish-component [file] @@ -624,7 +631,7 @@ {:add-container? true})) - :else + (= (:type component) :group) (let [component' (gsh/update-group-selrect component children)] (commit-change file @@ -637,11 +644,13 @@ {:type :set :attr :y :val (-> component' :selrect :y) :ignore-touched true} {:type :set :attr :width :val (-> component' :selrect :width) :ignore-touched true} {:type :set :attr :height :val (-> component' :selrect :height) :ignore-touched true}]} + {:add-container? true})) - {:add-container? true})))] + :else file)] (-> file (dissoc :current-component-id) + (dissoc :current-frame-id) (update :parent-stack pop)))) (defn finish-deleted-component @@ -700,7 +709,7 @@ (gpt/point x y) #_{:main-instance? true - :force-id main-instance-id})] + :force-id main-instance-id})] (as-> file $ (reduce #(commit-change %1 diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 732c2798c..be95e3416 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -183,7 +183,6 @@ (dm/export gsi/rect-contains-shape?) ;; Bool - (dm/export gsb/calc-bool-content) ;; Constraints @@ -196,4 +195,3 @@ ;; Modifiers (dm/export gsm/set-objects-modifiers) - diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index af40ae00c..b45ab53f1 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -270,7 +270,6 @@ (update :undo-changes #(reduce mk-undo-change % shapes)) (apply-changes-local))))) - (defn changed-attrs "Returns the list of attributes that will change when `update-fn` is applied" [object update-fn {:keys [attrs]}] diff --git a/common/src/app/common/pages/changes_spec.cljc b/common/src/app/common/pages/changes_spec.cljc index 1e12a643e..1cd63c7e2 100644 --- a/common/src/app/common/pages/changes_spec.cljc +++ b/common/src/app/common/pages/changes_spec.cljc @@ -12,6 +12,7 @@ [app.common.types.page :as ctp] [app.common.types.shape :as cts] [app.common.types.typography :as ctt] + [app.common.uuid :as uuid] [clojure.spec.alpha :as s])) (s/def ::index integer?) @@ -61,7 +62,7 @@ (some? (:frame-id o))) (and (contains? o :component-id) (not (contains? o :page-id)) - (nil? (:frame-id o))))) + (not= (:frame-id o) uuid/zero)))) (defn- valid-container-id? [o] diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index cb7911567..53185b092 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -69,13 +69,16 @@ (assert (nil? (:component-id shape))) (assert (nil? (:component-file shape))) (assert (nil? (:shape-ref shape))) - (let [;; Ensure that the component root is not an instance and - ;; it's no longer tied to a frame. - update-new-shape (fn [new-shape _original-shape] + (let [frame-ids-map (volatile! {}) + + ;; Ensure that the component root is not an instance + update-new-shape (fn [new-shape original-shape] + (when (= (:type original-shape) :frame) + (vswap! frame-ids-map assoc (:id original-shape) (:id new-shape))) + (cond-> new-shape true - (-> (assoc :frame-id nil) - (dissoc :component-root?)) + (dissoc :component-root?) (nil? (:parent-id new-shape)) (dissoc :component-id @@ -100,9 +103,17 @@ (assoc :main-instance? true) (some? (:parent-id new-shape)) - (dissoc :component-root?)))] + (dissoc :component-root?))) - (ctst/clone-object shape nil objects update-new-shape update-original-shape))) + [new-root-shape new-shapes updated-shapes] + (ctst/clone-object shape nil objects update-new-shape update-original-shape) + + ;; If frame-id points to a shape inside the component, remap it to the + ;; corresponding new frame shape. If not, set it to nil. + remap-frame-id (fn [shape] + (update shape :frame-id #(get @frame-ids-map % nil)))] + + [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 @@ -115,13 +126,14 @@ {:keys [main-instance? force-id] :or {main-instance? false force-id nil}}] (let [component-shape (get-shape component (:id component)) - orig-pos (gpt/point (:x component-shape) (:y component-shape)) - delta (gpt/subtract position orig-pos) + orig-pos (gpt/point (:x component-shape) (:y component-shape)) + delta (gpt/subtract position orig-pos) - objects (:objects container) - unames (volatile! (ctst/retrieve-used-names objects)) + objects (:objects container) + unames (volatile! (ctst/retrieve-used-names objects)) - frame-id (ctst/frame-id-by-position objects (gpt/add orig-pos delta)) + frame-id (ctst/frame-id-by-position objects (gpt/add orig-pos delta)) + frame-ids-map (volatile! {}) update-new-shape (fn [new-shape original-shape] @@ -130,14 +142,13 @@ (when (nil? (:parent-id original-shape)) (vswap! unames conj new-name)) + (when (= (:type original-shape) :frame) + (vswap! frame-ids-map assoc (:id original-shape) (:id new-shape))) + (cond-> new-shape true - (as-> $ - (gsh/move $ delta) - (assoc $ :frame-id frame-id) - (assoc $ :parent-id - (or (:parent-id $) (:frame-id $))) - (dissoc $ :touched)) + (-> (gsh/move delta) + (dissoc :touched)) (nil? (:shape-ref original-shape)) (assoc :shape-ref (:id original-shape)) @@ -160,7 +171,15 @@ (get component :objects) update-new-shape (fn [object _] object) - force-id)] + force-id) - [new-shape new-shapes]))) + ;; 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 $)))))] + + [new-shape (map remap-frame-id new-shapes)]))) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index b1953c6f4..7e22ea699 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -397,11 +397,13 @@ group-wrapper (mf/use-memo - (mf/deps objects root-shape) - (fn [] - (case (:type root-shape) - :group (group-wrapper-factory objects) - :frame (frame-wrapper-factory objects))))] + (mf/deps objects) + (fn [] (group-wrapper-factory objects))) + + frame-wrapper + (mf/use-memo + (mf/deps objects) + (fn [] (frame-wrapper-factory objects)))] [:> "symbol" #js {:id (str id) :viewBox vbox @@ -412,7 +414,9 @@ "penpot:main-instance-y" main-instance-y} [:title name] [:> shape-container {:shape root-shape} - [:& group-wrapper {:shape root-shape :view-box vbox}]]])) + (case (:type root-shape) + :group [:& group-wrapper {:shape root-shape :view-box vbox}] + :frame [:& frame-wrapper {:shape root-shape :view-box vbox}])]])) (mf/defc components-sprite-svg {::mf/wrap-props false} diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index cd908e112..f692f3543 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -64,9 +64,9 @@ (or (find-node node :penpot:shape) (find-node node :penpot:page))) - ([node tag] - (-> (get-data node) - (find-node tag)))) + ([node tag] + (-> (get-data node) + (find-node tag)))) (defn get-type [node] @@ -217,10 +217,10 @@ (let [;; Old .penpot files doesn't have "g" nodes. They have a clipPath reference as a node attribute to-url #(dm/str "url(#" % ")") frame-clip-rect-node (->> (find-all-nodes node :defs) - (mapcat #(find-all-nodes % :clipPath)) - (filter #(= (to-url (:id (:attrs %))) (:clip-path node-attrs))) - (mapcat #(find-all-nodes % #{:rect :path})) - (first)) + (mapcat #(find-all-nodes % :clipPath)) + (filter #(= (to-url (:id (:attrs %))) (:clip-path node-attrs))) + (mapcat #(find-all-nodes % #{:rect :path})) + (first)) ;; The nodes with the "frame-background" class can have some anidation depending on the strokes they have g-nodes (find-all-nodes node :g) @@ -1007,8 +1007,8 @@ (ctsi/has-overlay-opts interaction) (assoc :overlay-pos-type (get-meta node :overlay-pos-type keyword) :overlay-position (gpt/point - (get-meta node :overlay-position-x d/parse-double) - (get-meta node :overlay-position-y d/parse-double)) + (get-meta node :overlay-position-x d/parse-double) + (get-meta node :overlay-position-y d/parse-double)) :close-click-outside (get-meta node :close-click-outside str->bool) :background-overlay (get-meta node :background-overlay str->bool))))))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 05e34b585..4f514362f 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -389,21 +389,22 @@ old-id (cip/get-id node) id (resolve old-id) path (get-in node [:attrs :penpot:path] "") + type (cip/get-type content) main-instance-id (resolve (uuid (get-in node [:attrs :penpot:main-instance-id] ""))) main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] ""))) - data (-> (cip/parse-data :group content) + data (-> (cip/parse-data type content) (assoc :path path) (assoc :id id) (assoc :main-instance-id main-instance-id) (assoc :main-instance-page main-instance-page)) - file (-> file (fb/start-component data)) - children (cip/node-seq node)] + file (-> file (fb/start-component data type)) + children (cip/node-seq node)] (->> (rx/from children) (rx/filter cip/shape?) - (rx/skip 1) - (rx/skip-last 1) + (rx/skip 1) ;; Skip the outer component and the respective closint tag + (rx/skip-last 1) ;; because they are handled in start-component an finish-component (rx/mapcat (partial resolve-media context file-id)) (rx/reduce (partial process-import-node context) file) (rx/map fb/finish-component)))) @@ -419,8 +420,9 @@ main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] ""))) main-instance-x (get-in node [:attrs :penpot:main-instance-x] "") main-instance-y (get-in node [:attrs :penpot:main-instance-y] "") + type (cip/get-type content) - data (-> (cip/parse-data :group content) + data (-> (cip/parse-data type content) (assoc :path path) (assoc :id id) (assoc :main-instance-id main-instance-id) From 9367788898ea0f114f42557027185c79e0db26ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 20 Jan 2023 11:49:22 +0100 Subject: [PATCH 2/3] :sparkles: Small improvement --- common/src/app/common/file_builder.cljc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index ff30a2030..627ffc8a8 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -567,9 +567,8 @@ (defn start-component ([file data] (start-component file data :group)) ([file data root-type] - (let [selrect (if (and (:x data) (:y data) (:width data) (:height data)) - (gsh/make-selrect (:x data) (:y data) (:width data) (:height data)) - cts/empty-selrect) + (let [selrect (or (gsh/make-selrect (:x data) (:y data) (:width data) (:height data)) + cts/empty-selrect) name (:name data) path (:path data) main-instance-id (:main-instance-id data) From 85d56e6057f312a0c72452f4588a6ebdac358620 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 20 Jan 2023 12:22:44 +0100 Subject: [PATCH 3/3] :sparkles: Add debug tool to display name and id for shapes --- .../shapes/frame/dynamic_modifiers.cljs | 15 ++++++++-- .../app/main/ui/workspace/viewport/hooks.cljs | 10 ++++++- .../app/main/ui/workspace/viewport/utils.cljs | 8 ++--- .../main/ui/workspace/viewport/widgets.cljs | 29 ++++++++++++------- frontend/src/debug.cljs | 6 ++++ 5 files changed, 49 insertions(+), 19 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 2c849e422..1863e9cd3 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -18,6 +18,7 @@ [app.main.ui.workspace.viewport.utils :as vwu] [app.util.dom :as dom] [app.util.globals :as globals] + [debug :refer [debug?]] [rumext.v2 :as mf])) (defn get-shape-node @@ -52,19 +53,27 @@ masking-child? [shape-node (dom/query parent-node ".mask-clip-path") - (dom/query parent-node ".mask-shape")] + (dom/query parent-node ".mask-shape") + (when (debug? :shape-titles) + (dom/query (dm/str "#frame-title-" id)))] group? (let [shape-defs (dom/query shape-node "defs")] (d/concat-vec + [(when (debug? :shape-titles) + (dom/query (dm/str "#frame-title-" id)))] (dom/query-all shape-defs ".svg-def") (dom/query-all shape-defs ".svg-mask-wrapper"))) text? - [shape-node] + [shape-node + (when (debug? :shape-titles) + (dom/query (dm/str "#frame-title-" id)))] :else - [shape-node])))) + [shape-node + (when (debug? :shape-titles) + (dom/query (dm/str "#frame-title-" id)))])))) (defn transform-region! [node modifiers] diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 6df2d16c9..261ee5493 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -311,7 +311,15 @@ ;; Debug only: Disable the thumbnails new-active-frames - (if (debug? :disable-frame-thumbnails) (into #{} all-frames) new-active-frames)] + (cond + (debug? :disable-frame-thumbnails) + (into #{} all-frames) + + (debug? :force-frame-thumbnails) + #{} + + :else + new-active-frames)] (when (not= @active-frames new-active-frames) (reset! active-frames new-active-frames))))))) diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 59c411b4a..549489e4c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -42,7 +42,7 @@ (let [inv-zoom (/ 1 zoom)] (dm/fmt "scale(%, %) translate(%, %)" inv-zoom inv-zoom (* zoom x) (* zoom y)))) -(defn title-transform [frame zoom] - (let [frame-transform (gsh/transform-str frame {:no-flip true}) - label-pos (gpt/point (:x frame) (- (:y frame) (/ 10 zoom)))] - (dm/str frame-transform " " (text-transform label-pos zoom)))) +(defn title-transform [{:keys [selrect] :as shape} zoom] + (let [transform (gsh/transform-str shape {:no-flip true}) + label-pos (gpt/point (:x selrect) (- (:y selrect) (/ 10 zoom)))] + (dm/str transform " " (text-transform label-pos zoom)))) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index d95835fd5..6b1f42e87 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -90,7 +90,7 @@ (mf/defc frame-title {::mf/wrap [mf/memo #(mf/deferred % ts/raf)]} - [{:keys [frame selected? zoom show-artboard-names? on-frame-enter on-frame-leave on-frame-select]}] + [{:keys [frame selected? zoom show-artboard-names? show-id? on-frame-enter on-frame-leave on-frame-select]}] (let [workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) on-mouse-down (mf/use-callback @@ -148,7 +148,6 @@ :width (:width frame) :height 20 :class "workspace-frame-label" - ;:transform (dm/str frame-transform " " (text-transform label-pos zoom)) :style {:fill (when selected? "var(--color-primary-dark)")} :visibility (if show-artboard-names? "visible" "hidden") :on-mouse-down on-mouse-down @@ -156,7 +155,9 @@ :on-context-menu on-context-menu :on-pointer-enter on-pointer-enter :on-pointer-leave on-pointer-leave} - (:name frame)]]))) + (if show-id? + (dm/str (dm/str (:id frame)) " - " (:name frame)) + (:name frame))]]))) (mf/defc frame-titles {::mf/wrap-props false @@ -169,20 +170,26 @@ on-frame-enter (unchecked-get props "on-frame-enter") on-frame-leave (unchecked-get props "on-frame-leave") on-frame-select (unchecked-get props "on-frame-select") - frames (ctt/get-frames objects) + shapes (ctt/get-frames objects) + shapes (if (debug? :shape-titles) + (into (set shapes) + (map (d/getf objects)) + selected) + shapes) focus (unchecked-get props "focus")] [:g.frame-titles - (for [frame frames] + (for [{:keys [id parent-id] :as shape} shapes] (when (and - (= (:parent-id frame) uuid/zero) - (or (empty? focus) - (contains? focus (:id frame)))) - [:& frame-title {:key (dm/str "frame-title-" (:id frame)) - :frame frame - :selected? (contains? selected (:id frame)) + (not= id uuid/zero) + (or (debug? :shape-titles) (= parent-id uuid/zero)) + (or (empty? focus) (contains? focus id))) + [:& frame-title {:key (dm/str "frame-title-" id) + :frame shape + :selected? (contains? selected id) :zoom zoom :show-artboard-names? show-artboard-names? + :show-id? (debug? :shape-titles) :on-frame-enter on-frame-enter :on-frame-leave on-frame-leave :on-frame-select on-frame-select}]))])) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index e78a24dc8..4e45f7ae5 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -69,6 +69,9 @@ ;; Disable frame thumbnails :disable-frame-thumbnails + ;; Force thumbnails always (independent of selection or zoom level) + :force-frame-thumbnails + ;; Enable a widget to show the auto-layout drop-zones :layout-drop-zones @@ -89,6 +92,9 @@ ;; Show history overlay :history-overlay + + ;; Show shape name and id + :shape-titles }) ;; These events are excluded when we activate the :events flag