From a303df9c349e1c2c501403bb2fee86e32ad22cc6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 27 Aug 2025 10:47:09 +0200 Subject: [PATCH] :recycle: Refactor right sidebar state management Also removing duplicated refs and improve efficiency of several other refs used on sidebar. --- CHANGES.md | 1 + .../data/workspace/get-file-blank.json | 6 +- .../workspace/get-file-fragment-blank.json | 2 +- .../get-file-fragment-gradient-limits.json | 2 +- .../workspace/get-file-fragment-tokens.json | 4 +- .../get-file-fragment-typography-tokens.json | 4 +- .../data/workspace/get-file-tokens.json | 8 +- .../workspace/get-file-typography-tokens.json | 10 +- frontend/playwright/ui/pages/WorkspacePage.js | 5 +- .../playwright/ui/specs/colorpicker.spec.js | 7 +- .../ui/specs/text-editor-v2.spec.js | 5 +- frontend/playwright/ui/specs/tokens.spec.js | 9 +- frontend/src/app/main/data/helpers.cljs | 20 +- .../main/data/workspace/text/shortcuts.cljs | 19 +- .../src/app/main/data/workspace/texts.cljs | 5 +- frontend/src/app/main/refs.cljs | 71 ++--- .../main/ui/ds/foundations/assets/icon.cljs | 29 +- .../app/main/ui/inspect/right_sidebar.cljs | 4 +- frontend/src/app/main/ui/viewer/inspect.cljs | 16 +- .../app/main/ui/workspace/context_menu.cljs | 9 +- .../main/ui/workspace/sidebar/layer_item.cljs | 11 +- .../main/ui/workspace/sidebar/options.cljs | 248 +++++++++++------- .../sidebar/options/menus/align.cljs | 15 +- .../workspace/sidebar/options/menus/bool.cljs | 70 +++-- .../sidebar/options/menus/exports.cljs | 63 +++-- .../sidebar/options/menus/layout_item.cljs | 1 + .../sidebar/options/shapes/bool.cljs | 14 +- .../sidebar/options/shapes/circle.cljs | 12 +- .../sidebar/options/shapes/frame.cljs | 21 +- .../sidebar/options/shapes/group.cljs | 13 +- .../sidebar/options/shapes/multiple.cljs | 9 +- .../sidebar/options/shapes/path.cljs | 26 +- .../sidebar/options/shapes/rect.cljs | 12 +- .../sidebar/options/shapes/svg_raw.cljs | 13 +- .../sidebar/options/shapes/text.cljs | 13 +- frontend/src/app/util/shape_icon.cljs | 3 +- 36 files changed, 473 insertions(+), 307 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 116e008ec9..a222eeaaec 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ ### :sparkles: New features & Enhancements +- Add efficiency enhancements to right sidebar [Github #7182](https://github.com/penpot/penpot/pull/7182) - Add defaults for artboard drawing [Taiga #494](https://tree.taiga.io/project/penpot/us/494?milestone=465047) - Continuous display of distances between elements when moving a layer with the keyboard [Taiga #1780](https://tree.taiga.io/project/penpot/us/1780) - New Number token - unitless values [Taiga #10936](https://tree.taiga.io/project/penpot/us/10936) diff --git a/frontend/playwright/data/workspace/get-file-blank.json b/frontend/playwright/data/workspace/get-file-blank.json index 9e05e3b50a..160137844c 100644 --- a/frontend/playwright/data/workspace/get-file-blank.json +++ b/frontend/playwright/data/workspace/get-file-blank.json @@ -29,10 +29,10 @@ "~:created-at": "~m1713536343369", "~:data": { "~:pages": [ - "~uc7ce0794-0992-8105-8004-38f28044384a" + "~u66697432-c33d-8055-8006-2c62cc084cad" ], "~:pages-index": { - "~uc7ce0794-0992-8105-8004-38f28044384a": { + "~u66697432-c33d-8055-8006-2c62cc084cad": { "~#penpot/pointer": [ "~ude58c8f6-c5c2-8196-8004-3df9e2e52d88", { @@ -55,4 +55,4 @@ } ] } -} \ No newline at end of file +} diff --git a/frontend/playwright/data/workspace/get-file-fragment-blank.json b/frontend/playwright/data/workspace/get-file-fragment-blank.json index 7760aaa927..02f0e289e0 100644 --- a/frontend/playwright/data/workspace/get-file-fragment-blank.json +++ b/frontend/playwright/data/workspace/get-file-fragment-blank.json @@ -91,7 +91,7 @@ } } }, - "~:id": "~uc7ce0794-0992-8105-8004-38f28044384a", + "~:id": "~u66697432-c33d-8055-8006-2c62cc084cad", "~:name": "Page 1" } } diff --git a/frontend/playwright/data/workspace/get-file-fragment-gradient-limits.json b/frontend/playwright/data/workspace/get-file-fragment-gradient-limits.json index f1c89c7922..16a238cb18 100644 --- a/frontend/playwright/data/workspace/get-file-fragment-gradient-limits.json +++ b/frontend/playwright/data/workspace/get-file-fragment-gradient-limits.json @@ -276,4 +276,4 @@ "~:id": "~u66697432-c33d-8055-8006-2c62cc084cad", "~:name": "Page 1" } -} \ No newline at end of file +} diff --git a/frontend/playwright/data/workspace/get-file-fragment-tokens.json b/frontend/playwright/data/workspace/get-file-fragment-tokens.json index c41deaef14..128f45d28d 100644 --- a/frontend/playwright/data/workspace/get-file-fragment-tokens.json +++ b/frontend/playwright/data/workspace/get-file-fragment-tokens.json @@ -1,6 +1,6 @@ { "~:id": "~u51e13852-1a8e-8037-8005-9eabb500f7c7", - "~:file-id": "~u51e13852-1a8e-8037-8005-9e9413a1f1f6", + "~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849", "~:created-at": "~m1737542758401", "~:data": { "~:options": {}, @@ -454,7 +454,7 @@ } } }, - "~:id": "~u51e13852-1a8e-8037-8005-9e9413a1f1f7", + "~:id": "~u66697432-c33d-8055-8006-2c62cc084cad", "~:name": "Page 1", "~:background": "#e8eae9", "~:guides": { diff --git a/frontend/playwright/data/workspace/get-file-fragment-typography-tokens.json b/frontend/playwright/data/workspace/get-file-fragment-typography-tokens.json index a0de35cfac..4752ca68f5 100644 --- a/frontend/playwright/data/workspace/get-file-fragment-typography-tokens.json +++ b/frontend/playwright/data/workspace/get-file-fragment-typography-tokens.json @@ -1,6 +1,6 @@ { "~:id": "~u021b87d4-813e-8066-8006-b36537098786", - "~:file-id": "~uef9b2783-804c-8017-8006-ae6f7eab52ad", + "~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849", "~:created-at": "~m1756113434655", "~:data": { "~:objects": { @@ -258,7 +258,7 @@ } } }, - "~:id": "~uef9b2783-804c-8017-8006-ae6f7eab52ae", + "~:id": "~u66697432-c33d-8055-8006-2c62cc084cad", "~:name": "Page 1" } } diff --git a/frontend/playwright/data/workspace/get-file-tokens.json b/frontend/playwright/data/workspace/get-file-tokens.json index 9b5303e64a..597aa2d55b 100644 --- a/frontend/playwright/data/workspace/get-file-tokens.json +++ b/frontend/playwright/data/workspace/get-file-tokens.json @@ -23,15 +23,17 @@ "~:revn": 36, "~:modified-at": "~m1737542758402", "~:vern": 0, - "~:id": "~u51e13852-1a8e-8037-8005-9e9413a1f1f6", + "~:id": "~uc7ce0794-0992-8105-8004-38f280443849", "~:is-shared": false, "~:version": 60, "~:project-id": "~u0df61468-6cbf-8067-8005-6b453ce996d0", "~:created-at": "~m1737536563847", "~:data": { - "~:pages": ["~u51e13852-1a8e-8037-8005-9e9413a1f1f7"], + "~:pages": [ + "~u66697432-c33d-8055-8006-2c62cc084cad" + ], "~:pages-index": { - "~u51e13852-1a8e-8037-8005-9e9413a1f1f7": { + "~u66697432-c33d-8055-8006-2c62cc084cad": { "~#penpot/pointer": [ "~u51e13852-1a8e-8037-8005-9eabb500f7c7", { diff --git a/frontend/playwright/data/workspace/get-file-typography-tokens.json b/frontend/playwright/data/workspace/get-file-typography-tokens.json index 746aeb132d..35ed171867 100644 --- a/frontend/playwright/data/workspace/get-file-typography-tokens.json +++ b/frontend/playwright/data/workspace/get-file-typography-tokens.json @@ -27,7 +27,7 @@ "~:revn": 133, "~:modified-at": "~m1756113434658", "~:vern": 0, - "~:id": "~uef9b2783-804c-8017-8006-ae6f7eab52ad", + "~:id": "~uc7ce0794-0992-8105-8004-38f280443849", "~:is-shared": false, "~:migrations": { "~#ordered-set": [] @@ -36,9 +36,11 @@ "~:project-id": "~u0df61468-6cbf-8067-8005-6b453ce996d0", "~:created-at": "~m1755780585133", "~:data": { - "~:pages": ["~uef9b2783-804c-8017-8006-ae6f7eab52ae"], + "~:pages": [ + "~u66697432-c33d-8055-8006-2c62cc084cad" + ], "~:pages-index": { - "~uef9b2783-804c-8017-8006-ae6f7eab52ae": { + "~u66697432-c33d-8055-8006-2c62cc084cad": { "~#penpot/pointer": [ "~u021b87d4-813e-8066-8006-b36537098786", { @@ -47,7 +49,7 @@ ] } }, - "~:id": "~uef9b2783-804c-8017-8006-ae6f7eab52ad", + "~:id": "~uc7ce0794-0992-8105-8004-38f280443849", "~:options": { "~:components-v2": true, "~:base-font-size": "16px" diff --git a/frontend/playwright/ui/pages/WorkspacePage.js b/frontend/playwright/ui/pages/WorkspacePage.js index 5afc536b9a..20a16ecdd8 100644 --- a/frontend/playwright/ui/pages/WorkspacePage.js +++ b/frontend/playwright/ui/pages/WorkspacePage.js @@ -159,7 +159,10 @@ export class WorkspacePage extends BaseWebSocketPage { "get-profiles-for-file-comments?file-id=*", "workspace/get-profile-for-file-comments.json", ); - await this.mockRPC(/get\-file\?/, "workspace/get-file-blank.json"); + await this.mockRPC( + /get\-file\?/, + "workspace/get-file-blank.json" + ); await this.mockRPC( "get-file-object-thumbnails?file-id=*", "workspace/get-file-object-thumbnails-blank.json", diff --git a/frontend/playwright/ui/specs/colorpicker.spec.js b/frontend/playwright/ui/specs/colorpicker.spec.js index 012bb52379..9521cdb06c 100644 --- a/frontend/playwright/ui/specs/colorpicker.spec.js +++ b/frontend/playwright/ui/specs/colorpicker.spec.js @@ -182,12 +182,17 @@ test("Gradient stops limit", async ({ page }) => { const workspacePage = new WorkspacePage(page); await workspacePage.mockConfigFlags(["enable-frontend-binary-fills"]); await workspacePage.setupEmptyFile(page); + await workspacePage.mockRPC( "get-file-fragment?file-id=*&fragment-id=*", "workspace/get-file-fragment-gradient-limits.json", ); - await workspacePage.goToWorkspace(); + await workspacePage.goToWorkspace({ + fileId: "c7ce0794-0992-8105-8004-38f280443849", + pageId: "66697432-c33d-8055-8006-2c62cc084cad" + }); + await workspacePage.clickLeafLayer("Rectangle"); const swatch = workspacePage.page.getByRole("button", { diff --git a/frontend/playwright/ui/specs/text-editor-v2.spec.js b/frontend/playwright/ui/specs/text-editor-v2.spec.js index 2b4559cadc..dec3b71a0b 100644 --- a/frontend/playwright/ui/specs/text-editor-v2.spec.js +++ b/frontend/playwright/ui/specs/text-editor-v2.spec.js @@ -15,7 +15,10 @@ test("BUG 11552 - Apply styles to the current caret", async ({ page }) => { "text-editor/update-file-11552.json", ); - await workspace.goToWorkspace(); + await workspace.goToWorkspace({ + fileId: "238a17e0-75ff-8075-8006-934586ea2230", + pageId: "238a17e0-75ff-8075-8006-934586ea2231", + }); await workspace.clickLeafLayer("Lorem ipsum"); await workspace.clickLeafLayer("Lorem ipsum"); diff --git a/frontend/playwright/ui/specs/tokens.spec.js b/frontend/playwright/ui/specs/tokens.spec.js index 0ef78107d0..e74397db68 100644 --- a/frontend/playwright/ui/specs/tokens.spec.js +++ b/frontend/playwright/ui/specs/tokens.spec.js @@ -20,7 +20,10 @@ const setupEmptyTokensFile = async (page) => { "workspace/update-file-create-rect.json", ); - await workspacePage.goToWorkspace(); + await workspacePage.goToWorkspace({ + fileId: "c7ce0794-0992-8105-8004-38f280443849", + pageId: "66697432-c33d-8055-8006-2c62cc084cad", + }); const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); await tokensTabButton.click(); @@ -60,8 +63,8 @@ const setupTokensFile = async (page, options = {}) => { ); await workspacePage.goToWorkspace({ - fileId: "51e13852-1a8e-8037-8005-9e9413a1f1f6", - pageId: "51e13852-1a8e-8037-8005-9e9413a1f1f7", + fileId: "c7ce0794-0992-8105-8004-38f280443849", + pageId: "66697432-c33d-8055-8006-2c62cc084cad", }); const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); diff --git a/frontend/src/app/main/data/helpers.cljs b/frontend/src/app/main/data/helpers.cljs index ee3d406c53..05237fcd1b 100644 --- a/frontend/src/app/main/data/helpers.cljs +++ b/frontend/src/app/main/data/helpers.cljs @@ -69,14 +69,18 @@ (process-selected objects selected nil)) ([objects selected {:keys [omit-blocked?] :or {omit-blocked? false}}] - (letfn [(selectable? [id] - (and (contains? objects id) - (or (not omit-blocked?) - (not (dm/get-in objects [id :blocked] false)))))] - (let [selected (->> selected (cfh/clean-loops objects))] - (into (d/ordered-set) - (filter selectable?) - selected))))) + (let [selectable? + (fn [id] + (and (contains? objects id) + (or (not omit-blocked?) + (not (dm/get-in objects [id :blocked] false))))) + + selected + (cfh/clean-loops objects selected)] + + (into (d/ordered-set) + (filter selectable?) + selected)))) (defn split-text-shapes "Split text shapes from non-text shapes" diff --git a/frontend/src/app/main/data/workspace/text/shortcuts.cljs b/frontend/src/app/main/data/workspace/text/shortcuts.cljs index 0379e7a398..93a02f5bca 100644 --- a/frontend/src/app/main/data/workspace/text/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/text/shortcuts.cljs @@ -16,7 +16,8 @@ [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.store :as st] - [cuerdas.core :as str])) + [cuerdas.core :as str] + [okulary.core :as l])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Shortcuts @@ -111,7 +112,6 @@ :font-weight (:weight new-variant) :font-style (:style new-variant)}))) - (defn calculate-text-values [shape] (let [state-map (if (features/active-feature? @st/state "text-editor/v2") @@ -196,13 +196,26 @@ :else props))) +(def ^:private selected-shapes-with-children + "A derived state that resolves to a lazy sequence of all selected + shapes and its children." + (l/derived + (fn [{:keys [objects selected]}] + (let [xform (comp (remove nil?) + (mapcat #(cfh/get-children-ids objects %))) + shapes (into selected xform selected)] + (sequence (keep (d/getf objects)) shapes))) + ;; WORKAROUND: we should not use it here, but util we restructure + ;; this, the simplest way is just deref private var + @#'refs/selected-shapes-data)) + (defn- update-attrs-when-no-readonly [props] (let [undo-id (js/Symbol) can-edit? (:can-edit (deref refs/permissions)) read-only? (deref refs/workspace-read-only?) - text-shapes (->> (deref refs/selected-shapes-with-children) + text-shapes (->> (deref selected-shapes-with-children) (filter cfh/text-shape?) (not-empty)) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index e780226bd5..9c92f72f91 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -422,8 +422,9 @@ (txt/update-text-content shape txt/is-root-node? d/txt-merge attrs) (assoc shape :content (d/txt-merge {:type "root"} attrs)))) - shape-ids (cond (cfh/text-shape? shape) [id] - (cfh/group-shape? shape) (cfh/get-children-ids objects id))] + shape-ids + (cond (cfh/text-shape? shape) [id] + (cfh/group-shape? shape) (cfh/get-children-ids objects id))] (rx/of (dwsh/update-shapes shape-ids update-fn)))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 73b41500e2..74b509ff6b 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -151,8 +151,8 @@ "All tokens related ephimeral state" (l/derived :workspace-tokens st/state)) -;; TODO: rename to workspace-selected (?) -;; Don't use directly from components, this is a proxy to improve performance of selected-shapes +;; WARNING: Don't use directly from components, this is a proxy to +;; improve performance of selected-shapes and (def ^:private selected-shapes-data (l/derived (fn [state] @@ -295,10 +295,8 @@ [page-id shape-id] (l/derived #(dsh/lookup-shape % page-id shape-id) st/state =)) -;; TODO: Looks like using the `=` comparator can be pretty expensive -;; on large pages, we are using this for some reason? (def workspace-page-objects - (l/derived dsh/lookup-page-objects st/state =)) + (l/derived dsh/lookup-page-objects st/state)) (def workspace-read-only? (l/derived :read-only? workspace-global)) @@ -366,36 +364,35 @@ (l/derived :workspace-v2-editor-state st/state)) (def workspace-modifiers - (l/derived :workspace-modifiers st/state =)) + (l/derived :workspace-modifiers st/state)) -(def workspace-modifiers-with-objects +(def ^:private workspace-modifiers-with-objects (l/derived (fn [state] - {:modifiers (:workspace-modifiers state) + {:modifiers (get state :workspace-modifiers) :objects (dsh/lookup-page-objects state)}) st/state (fn [a b] - (and (= (:modifiers a) (:modifiers b)) + (and (identical? (:modifiers a) (:modifiers b)) (identical? (:objects a) (:objects b)))))) (def workspace-frame-modifiers (l/derived (fn [{:keys [modifiers objects]}] - (->> modifiers - (reduce - (fn [result [id modifiers]] - (let [shape (get objects id) - frame-id (:frame-id shape)] - (cond - (cph/frame-shape? shape) - (assoc-in result [id id] modifiers) + (reduce (fn [result [id modifiers]] + (let [shape (get objects id) + frame-id (:frame-id shape)] + (cond + (cph/frame-shape? shape) + (assoc-in result [id id] modifiers) - (some? frame-id) - (assoc-in result [frame-id id] modifiers) + (some? frame-id) + (assoc-in result [frame-id id] modifiers) - :else - result))) - {}))) + :else + result))) + {} + modifiers)) workspace-modifiers-with-objects)) (defn workspace-modifiers-by-frame-id @@ -408,32 +405,14 @@ (defn select-bool-children [id] (l/derived #(dsh/select-bool-children % id) st/state =)) -(def selected-data - (l/derived #(let [selected (dsh/lookup-selected %) - objects (dsh/lookup-page-objects %)] - (hash-map :selected selected - :objects objects)) - st/state =)) - (defn is-child-selected? [id] - (letfn [(selector [{:keys [selected objects]}] - (let [children (cph/get-children-ids objects id)] - (some #(contains? selected %) children)))] - (l/derived selector selected-data =))) - -(def selected-objects - (letfn [(selector [{:keys [selected objects]}] - (into [] (keep (d/getf objects)) selected))] - (l/derived selector selected-data =))) - -(def selected-shapes-with-children - (letfn [(selector [{:keys [selected objects]}] - (let [xform (comp (remove nil?) - (mapcat #(cph/get-children-ids objects %))) - shapes (into selected xform selected)] - (mapv (d/getf objects) shapes)))] - (l/derived selector selected-data =))) + (l/derived + (fn [{:keys [selected objects]}] + (let [children (cph/get-children-ids objects id)] + (some #(contains? selected %) children))) + selected-shapes-data + =)) (def workspace-focus-selected (l/derived :workspace-focus-selected st/state)) diff --git a/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs b/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs index f4b0474c1f..0ca9fbeb59 100644 --- a/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs +++ b/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs @@ -294,10 +294,12 @@ (def ^:icon-id view-as-list "view-as-list") (def ^:icon-id wrap "wrap") -(def icon-list "A collection of all icons" (collect-icons)) +(def icon-list + "A collection of all icons" + (collect-icons)) -(def ^:private icon-size-m 16) -(def ^:private icon-size-s 12) +(def ^:private ^:const icon-size-m 16) +(def ^:private ^:const icon-size-s 12) (def ^:private schema:icon [:map @@ -309,9 +311,18 @@ (mf/defc icon* {::mf/schema schema:icon} [{:keys [icon-id size class] :rest props}] - (let [class (dm/str (or class "") " " (stl/css :icon)) - props (mf/spread-props props {:class class :width icon-size-m :height icon-size-m}) - size-px (cond (= size "s") icon-size-s :else icon-size-m) - offset (/ (- icon-size-m size-px) 2)] - [:> "svg" props - [:use {:href (dm/str "#icon-" icon-id) :width size-px :height size-px :x offset :y offset}]])) + (let [props (mf/spread-props props + {:class [class (stl/css :icon)] + :width icon-size-m + :height icon-size-m}) + size-px (if (= size "s") + icon-size-s + icon-size-m) + offset (/ (- icon-size-m size-px) 2)] + + [:> :svg props + [:use {:href (dm/str "#icon-" icon-id) + :width size-px + :height size-px + :x offset + :y offset}]])) diff --git a/frontend/src/app/main/ui/inspect/right_sidebar.cljs b/frontend/src/app/main/ui/inspect/right_sidebar.cljs index 4c25d5c3cc..3b3d5d37b4 100644 --- a/frontend/src/app/main/ui/inspect/right_sidebar.cljs +++ b/frontend/src/app/main/ui/inspect/right_sidebar.cljs @@ -39,11 +39,11 @@ (assoc id {:id id :data local}))))) -(mf/defc right-sidebar +(mf/defc right-sidebar* [{:keys [frame page objects file selected shapes page-id file-id share-id from on-change-section on-expand] :or {from :viewer}}] (let [color-space* (mf/use-state "hex") - color-space (deref color-space*) + color-space (deref color-space*) section (mf/use-state #(if (contains? cf/flags :inspect-styles) :styles :info)) objects (or objects (:objects page)) diff --git a/frontend/src/app/main/ui/viewer/inspect.cljs b/frontend/src/app/main/ui/viewer/inspect.cljs index 0093292edd..36a9f83239 100644 --- a/frontend/src/app/main/ui/viewer/inspect.cljs +++ b/frontend/src/app/main/ui/viewer/inspect.cljs @@ -14,7 +14,7 @@ [app.main.ui.hooks.resize :refer [use-resize-hook]] [app.main.ui.inspect.left-sidebar :refer [left-sidebar]] [app.main.ui.inspect.render :refer [render-frame-svg]] - [app.main.ui.inspect.right-sidebar :refer [right-sidebar]] + [app.main.ui.inspect.right-sidebar :refer [right-sidebar*]] [app.util.dom :as dom] [app.util.keyboard :as kbd] [goog.events :as events] @@ -112,10 +112,10 @@ :on-pointer-down on-pointer-down :on-lost-pointer-capture on-lost-pointer-capture :on-pointer-move on-pointer-move}]) - [:& right-sidebar {:frame frame - :selected (:selected local) - :page page - :file file - :on-change-section handle-change-section - :on-expand handle-expand - :share-id share-id}]]])) + [:> right-sidebar* {:frame frame + :selected (:selected local) + :page page + :file file + :on-change-section handle-change-section + :on-expand handle-expand + :share-id share-id}]]])) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 33a5ff7030..7d7cee43ea 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -628,11 +628,10 @@ (mf/defc shape-context-menu* {::mf/wrap [mf/memo] - ::mf/private true - ::mf/props :obj} + ::mf/private true} [{:keys [mdata]}] (let [{:keys [disable-booleans disable-flatten]} mdata - shapes (mf/deref refs/selected-objects) + shapes (mf/deref refs/selected-shapes) is-not-variant-container? (->> shapes (d/seek #(not (ctk/is-variant-container? %)))) props (mf/props {:shapes shapes @@ -828,8 +827,8 @@ (if ^boolean read-only? [:> viewport-context-menu* {:mdata mdata}] (case (:kind mdata) - :shape [:> shape-context-menu* {:mdata mdata}] - :page [:> page-item-context-menu* {:mdata mdata}] + :shape [:> shape-context-menu* {:mdata mdata}] + :page [:> page-item-context-menu* {:mdata mdata}] :grid-track [:> grid-track-context-menu* {:mdata mdata}] :grid-cells [:> grid-cells-context-menu* {:mdata mdata}] [:> viewport-context-menu* {:mdata mdata}]))]]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs index b4cd1fcd00..3f2024e8b8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs @@ -35,16 +35,14 @@ [rumext.v2 :as mf])) (mf/defc layer-item-inner - {::mf/wrap-props false - ::mf/forward-ref true} - [{:keys [item depth parent-size name-ref children + {::mf/wrap-props false} + [{:keys [item depth parent-size name-ref children ref ;; Flags read-only? highlighted? selected? component-tree? filtered? expanded? dnd-over? dnd-over-top? dnd-over-bot? hide-toggle? ;; Callbacks on-select-shape on-context-menu on-pointer-enter on-pointer-leave on-zoom-to-selected - on-toggle-collapse on-enable-drag on-disable-drag on-toggle-visibility on-toggle-blocking]} - dref] + on-toggle-collapse on-enable-drag on-disable-drag on-toggle-visibility on-toggle-blocking]}] (let [id (:id item) name (:name item) @@ -67,9 +65,10 @@ component (ctkl/get-component data (:component-id item)) variant-properties (:variant-properties component) icon-shape (usi/get-shape-icon item)] + [:* [:div {:id id - :ref dref + :ref ref :on-click on-select-shape :on-context-menu on-context-menu :data-testid "layer-row" diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index d88d29b019..f2811ba786 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -12,6 +12,7 @@ [app.common.files.helpers :as cfh] [app.common.geom.shapes :as gsh] [app.common.types.shape.layout :as ctl] + [app.main.data.helpers :as dsh] [app.main.data.workspace :as udw] [app.main.data.workspace.common :as dwc] [app.main.refs :as refs] @@ -23,7 +24,6 @@ [app.main.ui.workspace.sidebar.options.menus.align :refer [align-options*]] [app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options*]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu]] - [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container] @@ -38,13 +38,15 @@ [app.main.ui.workspace.sidebar.options.shapes.svg-raw :as svg-raw] [app.main.ui.workspace.sidebar.options.shapes.text :as text] [app.util.i18n :as i18n :refer [tr]] + [okulary.core :as l] + [rumext.v2 :as mf])) ;; --- Options -(mf/defc shape-options* - {::mf/wrap [#(mf/throttle % 60)]} - [{:keys [shape shapes-with-children page-id file-id libraries] :as props}] +(mf/defc single-shape-options* + {::mf/private true} + [{:keys [shape page-id file-id libraries] :as props}] (let [shape-type (dm/get-prop shape :type) shape-id (dm/get-prop shape :id) @@ -53,45 +55,83 @@ shape (gsh/transform-shape shape modifiers)] - [:* - (case shape-type - :frame [:> frame/options* props] - :group [:> group/options* {:shape shape :shape-with-children shapes-with-children :file-id file-id :libraries libraries}] - :text [:> text/options* {:shape shape :file-id file-id :libraries libraries}] - :rect [:> rect/options* {:shape shape}] - :circle [:> circle/options* {:shape shape}] - :path [:> path/options* {:shape shape}] - :svg-raw [:> svg-raw/options* {:shape shape}] - :bool [:> bool/options* {:shape shape}] - nil) - [:& exports-menu - {:ids [(:id shape)] - :values (select-keys shape [:exports]) - :shape shape - :page-id page-id - :file-id file-id}]])) + (case shape-type + :frame [:> frame/options* props] + :group [:> group/options* props] + :text [:> text/options* {:shape shape :file-id file-id :page-id page-id :libraries libraries}] + :rect [:> rect/options* {:shape shape :file-id file-id :page-id page-id}] + :circle [:> circle/options* {:shape shape :file-id file-id :page-id page-id}] + :path [:> path/options* {:shape shape :file-id file-id :page-id page-id}] + :svg-raw [:> svg-raw/options* {:shape shape :file-id file-id :page-id page-id}] + :bool [:> bool/options* {:shape shape :file-id file-id :page-id page-id}] + nil))) + +(mf/defc shape-options* + {::mf/wrap [#(mf/throttle % 100)] + ::mf/private true} + [{:keys [shapes shapes-with-children selected page-id file-id libraries]}] + (if (= 1 (count selected)) + [:> single-shape-options* + {:page-id page-id + :file-id file-id + :libraries libraries + :shape (first shapes) + :shapes-with-children shapes-with-children}] + [:> multiple/options* + {:shapes-with-children shapes-with-children + :shapes shapes + :page-id page-id + :file-id file-id + :libraries libraries}])) (mf/defc specialized-panel* - {::mf/wrap [mf/memo]} + {::mf/private true} [{:keys [panel]}] (when (= (:type panel) :component-swap) [:& component-menu {:shapes (:shapes panel) :swap-opened? true}])) (mf/defc design-menu* - {::mf/wrap [mf/memo]} - [{:keys [selected objects page-id file-id selected-shapes shapes-with-children]}] - (let [sp-panel (mf/deref refs/specialized-panel) - drawing (mf/deref refs/workspace-drawing) - libraries (mf/deref refs/libraries) - edition (mf/deref refs/selected-edition) - edit-grid? (ctl/grid-layout? objects edition) - grid-edition (mf/deref refs/workspace-grid-edition) - selected-cells (->> (dm/get-in grid-edition [edition :selected]) - (map #(dm/get-in objects [edition :layout-grid-cells %])))] + {::mf/private true} + [{:keys [selected objects page-id file-id shapes]}] + (let [sp-panel (mf/deref refs/specialized-panel) + drawing (mf/deref refs/workspace-drawing) + edition (mf/deref refs/selected-edition) + + files + (mf/deref refs/files) + + libraries + (mf/with-memo [files file-id] + (refs/select-libraries files file-id)) + + edit-grid? + (mf/with-memo [objects edition] + (ctl/grid-layout? objects edition)) + + grid-edition + (mf/deref refs/workspace-grid-edition) + + selected-cells + (->> (dm/get-in grid-edition [edition :selected]) + (map #(dm/get-in objects [edition :layout-grid-cells %]))) + + shapes-with-children + (mf/with-memo [selected objects shapes] + (let [xform (comp (remove nil?) + (mapcat #(cfh/get-children-ids objects %))) + selected (into selected xform selected)] + (sequence (keep (d/getf objects)) selected))) + + + total-selected + (count selected)] [:div {:class (stl/css :element-options :design-options)} - [:> align-options*] - [:> bool-options*] + [:> align-options* {:shapes shapes + :objects objects}] + [:> bool-options* {:total-selected total-selected + :shapes shapes + :shapes-with-children shapes-with-children}] (cond (and edit-grid? (d/not-empty? selected-cells)) @@ -104,67 +144,71 @@ {:ids [edition] :values (get objects edition)}] - (not (nil? sp-panel)) + (some? sp-panel) [:> specialized-panel* {:panel sp-panel}] (d/not-empty? drawing) [:> drawing/drawing-options* {:drawing-state drawing}] - (= 0 (count selected)) + (zero? total-selected) [:> page/options*] - (= 1 (count selected)) - [:> shape-options* - {:shape (first selected-shapes) - :page-id page-id - :file-id file-id - :libraries libraries - :shapes-with-children shapes-with-children}] - :else - [:> multiple/options* - {:shapes-with-children shapes-with-children - :shapes selected-shapes + [:> shape-options* + {:shapes shapes + :shapes-with-children shapes-with-children :page-id page-id :file-id file-id + :selected selected :libraries libraries}])])) -;; FIXME: need optimizations +(mf/defc inspect-tab* + {::mf/private true} + [{:keys [objects shapes] :as props}] + (let [frame + (cfh/get-frame objects (first shapes)) + + props + (mf/spread-props props + {:frame frame + :from :workspace})] + + [:> hrs/right-sidebar* props])) + +(def ^:private options-tabs + [{:label (tr "workspace.options.design") + :id "design"} + {:label (tr "workspace.options.prototype") + :id "prototype"} + {:label (tr "workspace.options.inspect") + :id "inspect"}]) + +(defn- on-option-tab-change + [mode] + (let [mode (keyword mode)] + (st/emit! (udw/set-options-mode mode)) + (if (= mode :inspect) + (st/emit! :interrupt (dwc/set-workspace-read-only true)) + (st/emit! :interrupt (dwc/set-workspace-read-only false))))) + (mf/defc options-content* - {::mf/memo true - ::mf/private true} - [{:keys [selected shapes shapes-with-children page-id file-id on-change-section on-expand]}] - (let [objects (mf/deref refs/workspace-page-objects) - permissions (mf/use-ctx ctx/permissions) + {::mf/private true} + [{:keys [objects selected page-id file-id on-change-section on-expand]}] + (let [permissions + (mf/use-ctx ctx/permissions) - selected-shapes (into [] (keep (d/getf objects)) selected) - first-selected-shape (first selected-shapes) - shape-parent-frame (cfh/get-frame objects (:frame-id first-selected-shape)) + options-mode + (mf/deref refs/options-mode-global) - options-mode (mf/deref refs/options-mode-global) - - on-change-tab - (fn [options-mode] - (let [options-mode (keyword options-mode)] - (st/emit! (udw/set-options-mode options-mode)) - (if (= options-mode :inspect) - (st/emit! :interrupt (dwc/set-workspace-read-only true)) - (st/emit! :interrupt (dwc/set-workspace-read-only false))))) - - tabs - (mf/with-memo [] - [{:label (tr "workspace.options.design") - :id "design"} - {:label (tr "workspace.options.prototype") - :id "prototype"} - {:label (tr "workspace.options.inspect") - :id "inspect"}])] + shapes + (mf/with-memo [selected objects] + (sequence (keep (d/getf objects)) selected))] [:div {:class (stl/css :tool-window)} (if (:can-edit permissions) - [:> tab-switcher* {:tabs tabs - :on-change on-change-tab + [:> tab-switcher* {:tabs options-tabs + :on-change on-option-tab-change :selected (name options-mode) :class (stl/css :options-tab-switcher)} (case options-mode @@ -174,46 +218,46 @@ :inspect [:div {:class (stl/css :element-options :inspect-options)} - [:& hrs/right-sidebar {:page-id page-id - :objects objects - :file-id file-id - :frame shape-parent-frame - :shapes selected-shapes - :on-change-section on-change-section - :on-expand on-expand - :from :workspace}]] + [:> inspect-tab* {:page-id page-id + :file-id file-id + :objects objects + :selected selected + :shapes shapes + :on-change-section on-change-section + :on-expand on-expand}]] :design [:> design-menu* {:selected selected :objects objects :page-id page-id :file-id file-id - :selected-shapes selected-shapes - :shapes-with-children shapes-with-children}])] + :shapes shapes}])] - ;; FIXME: Reuse tab??? [:div {:class (stl/css :element-options :inspect-options :read-only)} - [:& hrs/right-sidebar {:page-id page-id - :objects objects - :file-id file-id - :frame shape-parent-frame - :shapes selected-shapes - :on-change-section on-change-section - :on-expand on-expand - :from :workspace}]])])) + [:> inspect-tab* {:page-id page-id + :file-id file-id + :objects objects + :selected selected + :shapes shapes + :on-change-section on-change-section + :on-expand on-expand}]])])) -;; TODO: this need optimizations, selected-objects and -;; selected-objects-with-children are derefed always but they only -;; need on multiple selection in majority of cases +(defn- make-page-objects-ref + [file-id page-id] + (l/derived #(dsh/lookup-page-objects % file-id page-id) st/state)) (mf/defc options-toolbox* {::mf/memo true} [{:keys [page-id file-id section selected on-change-section on-expand]}] - (let [shapes (mf/deref refs/selected-objects) - shapes-with-children (mf/deref refs/selected-shapes-with-children)] - [:> options-content* {:shapes shapes + (let [objects-ref + (mf/with-memo [page-id file-id] + (make-page-objects-ref file-id page-id)) + + objects + (mf/deref objects-ref)] + + [:> options-content* {:objects objects :selected selected - :shapes-with-children shapes-with-children :file-id file-id :page-id page-id :section section diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs index b4f97f78ec..f0c0778a64 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs @@ -9,7 +9,6 @@ (:require [app.main.data.workspace :as dw] [app.main.data.workspace.shortcuts :as sc] - [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.icons :as i] [app.util.dom :as dom] @@ -17,14 +16,12 @@ [rumext.v2 :as mf])) (mf/defc align-options* - {::mf/memo true} - [] - (let [selected (mf/deref refs/selected-shapes) - ;; don't need to watch objects, only read the value - objects (deref refs/workspace-page-objects) + [{:keys [shapes objects]}] + (let [disabled-align + (not (dw/can-align? shapes objects)) - disabled-align (not (dw/can-align? selected objects)) - disabled-distribute (not (dw/can-distribute? selected)) + disabled-distribute + (not (dw/can-distribute? shapes)) align-objects (mf/use-fn @@ -42,7 +39,7 @@ (keyword))] (st/emit! (dw/distribute-objects value)))))] - (when (not (and disabled-align disabled-distribute)) + (when-not (and disabled-align disabled-distribute) [:div {:class (stl/css :align-options)} [:div {:class (stl/css :align-group-horizontal)} [:button {:class (stl/css-case :align-button true diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs index 5d6359a082..5e61e91514 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs @@ -8,11 +8,12 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.files.helpers :as cfh] [app.main.data.workspace.bool :as dwb] [app.main.data.workspace.path.shapes-to-path :as dwps] [app.main.data.workspace.shortcuts :as sc] [app.main.features :as features] - [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] [app.main.ui.icons :as i] @@ -23,48 +24,61 @@ (i/icon-xref :boolean-flatten (stl/css :flatten-icon))) (mf/defc bool-options* - {::mf/memo true} - [] - (let [selected (mf/deref refs/selected-objects) - head (first selected) - selected-with-children (mf/deref refs/selected-shapes-with-children) - has-invalid-shapes? (->> selected-with-children - (some (comp #{:frame :text} :type))) - is-group? (and (some? head) (= :group (:type head))) - is-bool? (and (some? head) (= :bool (:type head))) - head-bool-type (and (some? head) is-bool? (:bool-type head)) + [{:keys [total-selected shapes shapes-with-children]}] + (let [head (first shapes) + head-id (dm/get-prop head :id) - first-not-group-like? - (and (= (count selected) 1) - (not (contains? #{:group :bool} (:type (first selected))))) + is-group? (cfh/group-shape? head) + is-bool? (cfh/bool-shape? head) + + head-bool-type + (and is-bool? (get head :bool-type)) + + render-wasm-enabled? + (features/use-feature "render-wasm/v1") + + has-invalid-shapes? + (if render-wasm-enabled? + false + (some (fn [shape] + (or (cfh/frame-shape? shape) + (cfh/text-shape? shape))) + shapes-with-children)) + + head-not-group-like? + (and (= 1 total-selected) + (not is-group?) + (not is-bool?)) disabled-bool-btns - (if (features/active-feature? @st/state "render-wasm/v1") + (if render-wasm-enabled? false - (or (empty? selected) has-invalid-shapes? first-not-group-like?)) + (or (zero? total-selected) + has-invalid-shapes? + head-not-group-like?)) disabled-flatten - (if (features/active-feature? @st/state "render-wasm/v1") + (if render-wasm-enabled? false - (or (empty? selected) has-invalid-shapes?)) + (or (zero? total-selected) + has-invalid-shapes?)) - set-bool + on-change (mf/use-fn - (mf/deps selected is-group? is-bool?) + (mf/deps total-selected is-group? is-bool? head-id head-bool-type) (fn [bool-type] (let [bool-type (keyword bool-type)] - (cond - (> (count selected) 1) + (> total-selected 1) (st/emit! (dwb/create-bool bool-type)) - (and (= (count selected) 1) is-group?) - (st/emit! (dwb/group-to-bool (:id head) bool-type)) + (and (= total-selected 1) is-group?) + (st/emit! (dwb/group-to-bool head-id bool-type)) - (and (= (count selected) 1) is-bool?) + (and (= total-selected 1) is-bool?) (if (= head-bool-type bool-type) - (st/emit! (dwb/bool-to-group (:id head))) - (st/emit! (dwb/change-bool-type (:id head) bool-type))))))) + (st/emit! (dwb/bool-to-group head-id)) + (st/emit! (dwb/change-bool-type head-id bool-type))))))) flatten-objects (mf/use-fn #(st/emit! (dwps/convert-selected-to-path)))] @@ -74,7 +88,7 @@ [:div {:class (stl/css :bool-group)} [:& radio-buttons {:selected (d/name head-bool-type) :class (stl/css :boolean-radio-btn) - :on-change set-bool + :on-change on-change :name "bool-options"} [:& radio-button {:icon i/boolean-union :value "union" diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs index dd486cfece..b29de7957e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs @@ -9,7 +9,6 @@ (:require [app.common.data :as d] [app.main.data.exports.assets :as de] - [app.main.data.helpers :as dsh] [app.main.data.workspace.shapes :as dwsh] [app.main.refs :as refs] [app.main.store :as st] @@ -26,29 +25,55 @@ "Shape attrs that corresponds to exports. Used in other namespaces." [:exports]) -(mf/defc exports-menu - {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "page-id" "file-id"]))]} - [{:keys [ids type values page-id file-id] :as props}] - (let [exports (:exports values []) - has-exports? (or (= :multiple exports) (some? (seq exports))) +(defn- check-exports-menu-props + [old-props new-props] + (and (identical? (unchecked-get old-props "ids") + (unchecked-get new-props "ids")) + (identical? (unchecked-get old-props "type") + (unchecked-get new-props "type")) + (identical? (unchecked-get old-props "pageId") + (unchecked-get new-props "pageId")) + (identical? (unchecked-get old-props "fileId") + (unchecked-get new-props "fileId")) - comp-state* (mf/use-state true) - open? (deref comp-state*) + ;; NOTE: we explicitly ignore "shapes" prop and use values for + ;; track if the "value" changes (checking by value equality); + ;; this prevents rerender the component when no real change is + ;; made to exports + (= (unchecked-get old-props "values") + (unchecked-get new-props "values")))) - toggle-content (mf/use-fn #(swap! comp-state* not)) +(mf/defc exports-menu* + {::mf/wrap [#(mf/memo' % check-exports-menu-props)]} + [{:keys [ids type shapes values file-id page-id]}] - state (mf/deref refs/export) - in-progress? (:in-progress state) + (let [exports (get values :exports []) + open* (mf/use-state true) + open? (deref open*) - shapes-with-exports (->> (dsh/lookup-shapes @st/state ids) - (filter #(pos? (count (:exports %))))) + state (mf/deref refs/export) - sname (when (seqable? exports) - (let [sname (-> shapes-with-exports first :name) - suffix (-> exports first :suffix)] - (cond-> sname - (and (= 1 (count exports)) (some? suffix)) - (str suffix)))) + in-progress? + (get state :in-progress) + + has-exports? + (or (= :multiple exports) + (some? (seq exports))) + + toggle-content + (mf/use-fn #(swap! open* not)) + + shapes-with-exports + (mf/with-memo [shapes] + (filter (comp seq :exports) shapes)) + + sname + (when (seqable? exports) + (let [sname (-> shapes-with-exports first :name) + suffix (-> exports first :suffix)] + (cond-> sname + (and (= 1 (count exports)) (some? suffix)) + (str suffix)))) scale-enabled? (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index f76c6a06fb..4a83f2b0b5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -309,6 +309,7 @@ :title "Align self end" :id "align-self-end"}]]) + (mf/defc layout-item-menu {::mf/memo #{:ids :values :type :is-layout-child? :is-grid-parent :is-flex-parent? :is-grid-layout? :is-flex-layout?} ::mf/props :obj} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs index 5fb56162b2..bddea47dcb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs @@ -11,6 +11,7 @@ [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] + [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu* exports-attrs]] [app.main.ui.workspace.sidebar.options.menus.fill :as fill] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] @@ -22,7 +23,7 @@ [rumext.v2 :as mf])) (mf/defc options* - [{:keys [shape] :as props}] + [{:keys [shape file-id page-id]}] (let [id (dm/get-prop shape :id) type (dm/get-prop shape :type) ids (mf/with-memo [id] [id]) @@ -124,4 +125,13 @@ [:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:& blur-menu {:ids ids - :values (select-keys shape [:blur])}]])) + :values (select-keys shape [:blur])}] + + [:> exports-menu* {:type type + :ids ids + :shapes shapes + :values (select-keys shape exports-attrs) + :page-id page-id + :file-id file-id}]])) + + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs index de2524a82e..4ef21adcdd 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs @@ -11,6 +11,7 @@ [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] + [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu* exports-attrs]] [app.main.ui.workspace.sidebar.options.menus.fill :as fill] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] @@ -23,7 +24,7 @@ [rumext.v2 :as mf])) (mf/defc options* - [{:keys [shape] :as props}] + [{:keys [shape file-id page-id]}] (let [id (dm/get-prop shape :id) type (dm/get-prop shape :type) ids (mf/with-memo [id] [id]) @@ -123,4 +124,11 @@ [:& blur-menu {:ids ids :values (select-keys shape [:blur])}] [:& svg-attrs-menu {:ids ids - :values (select-keys shape [:svg-attrs])}]])) + :values (select-keys shape [:svg-attrs])}] + [:> exports-menu* {:type type + :ids ids + :shapes shapes + :values (select-keys shape exports-attrs) + :page-id page-id + :file-id file-id}]])) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 29d51767dd..b676ef0b52 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -15,6 +15,7 @@ [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu variant-menu*]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] + [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu* exports-attrs]] [app.main.ui.workspace.sidebar.options.menus.fill :as fill] [app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] @@ -27,17 +28,11 @@ [rumext.v2 :as mf])) (mf/defc options* - [{:keys [shape file-id shapes-with-children libraries] :as props}] + [{:keys [shape shapes-with-children libraries file-id page-id] :as props}] (let [shape-id (dm/get-prop shape :id) shape-type (dm/get-prop shape :type) - - ids - (mf/with-memo [shape-id] - [shape-id]) - - shapes - (mf/with-memo [shape] - [shape]) + ids (mf/with-memo [shape-id] [shape-id]) + shapes (mf/with-memo [shape] [shape]) stroke-values (select-keys shape stroke-attrs) @@ -160,4 +155,10 @@ [:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:& blur-menu {:ids ids :values (select-keys shape [:blur])}] - [:& frame-grid {:shape shape}]])) + [:& frame-grid {:shape shape}] + [:> exports-menu* {:type type + :ids ids + :shapes shapes + :values (select-keys shape exports-attrs) + :page-id page-id + :file-id file-id}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index a7adc2aeda..13289a0bb3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -14,6 +14,7 @@ [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-menu]] + [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu* exports-attrs]] [app.main.ui.workspace.sidebar.options.menus.fill :as fill] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-menu]] @@ -29,7 +30,7 @@ (mf/defc options* {::mf/wrap [mf/memo]} - [{:keys [shape shapes-with-children libraries file-id] :as props}] + [{:keys [shape shapes-with-children libraries file-id page-id]}] (let [id (dm/get-prop shape :id) type (dm/get-prop shape :type) @@ -157,6 +158,14 @@ [:& ot/text-menu {:type type :ids text-ids :values text-values}]) (when-not (empty? svg-values) - [:& svg-attrs-menu {:ids ids :values svg-values}])])) + [:& svg-attrs-menu {:ids ids :values svg-values}]) + + [:> exports-menu* {:type type + :ids ids + :shapes shapes + :values (select-keys shape exports-attrs) + :page-id page-id + :file-id file-id}]])) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 29628903c7..6d5e5cd0cf 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -23,7 +23,7 @@ [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] - [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-attrs exports-menu]] + [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-attrs exports-menu*]] [app.main.ui.workspace.sidebar.options.menus.fill :as fill] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] @@ -485,4 +485,9 @@ [:& blur-menu {:type type :ids blur-ids :values blur-values}]) (when-not (empty? exports-ids) - [:& exports-menu {:type type :ids exports-ids :values exports-values :page-id page-id :file-id file-id}])])) + [:> exports-menu* {:type type + :ids exports-ids + :shapes shapes + :values exports-values + :page-id page-id + :file-id file-id}])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs index 8ad6e802ea..bade487724 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs @@ -11,6 +11,7 @@ [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] + [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu* exports-attrs]] [app.main.ui.workspace.sidebar.options.menus.fill :as fill] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] @@ -23,17 +24,11 @@ [rumext.v2 :as mf])) (mf/defc options* - [{:keys [shape] :as props}] - (let [ids - (mf/with-memo [shape] - [(dm/get-prop shape :id)]) - - shapes - (mf/with-memo [shape] - [shape]) - - type - (dm/get-prop shape :type) + [{:keys [shape file-id page-id]}] + (let [id (dm/get-prop shape :id) + type (dm/get-prop shape :type) + ids (mf/with-memo [id] [id]) + shapes (mf/with-memo [shape] [shape]) measure-values (select-keys shape measure-attrs) @@ -131,4 +126,11 @@ [:& blur-menu {:ids ids :values (select-keys shape [:blur])}] [:& svg-attrs-menu {:ids ids - :values (select-keys shape [:svg-attrs])}]])) + :values (select-keys shape [:svg-attrs])}] + [:> exports-menu* {:type type + :ids ids + :shapes shapes + :values (select-keys shape exports-attrs) + :page-id page-id + :file-id file-id}]])) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs index 75d67efe29..9b5e62fd31 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs @@ -11,6 +11,7 @@ [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] + [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu* exports-attrs]] [app.main.ui.workspace.sidebar.options.menus.fill :as fill] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] @@ -23,7 +24,7 @@ [rumext.v2 :as mf])) (mf/defc options* - [{:keys [shape]}] + [{:keys [shape file-id page-id]}] (let [id (dm/get-prop shape :id) type (dm/get-prop shape :type) ids (mf/with-memo [id] [id]) @@ -127,4 +128,11 @@ :values (select-keys shape [:blur])}] [:& svg-attrs-menu {:ids ids - :values (select-keys shape [:svg-attrs])}]])) + :values (select-keys shape [:svg-attrs])}] + [:> exports-menu* {:type type + :ids ids + :shapes shapes + :values (select-keys shape exports-attrs) + :page-id page-id + :file-id file-id}]])) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index e0af0d6d07..faba956f2b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -13,6 +13,7 @@ [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] + [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu* exports-attrs]] [app.main.ui.workspace.sidebar.options.menus.fill :as fill] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] @@ -87,8 +88,7 @@ stroke-values)) (mf/defc options* - {::mf/wrap [mf/memo]} - [{:keys [shape] :as props}] + [{:keys [shape file-id page-id]}] (let [id (dm/get-prop shape :id) type (dm/get-prop shape :type) @@ -196,4 +196,11 @@ :values (select-keys shape [:blur])}] [:& svg-attrs-menu {:ids ids - :values (select-keys shape [:svg-attrs])}]]))) + :values (select-keys shape [:svg-attrs])}] + [:> exports-menu* {:type type + :ids ids + :shapes shapes + :values (select-keys shape exports-attrs) + :page-id page-id + :file-id file-id}]]))) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 8e344ad41d..0641652975 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -16,6 +16,7 @@ [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] + [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu* exports-attrs]] [app.main.ui.workspace.sidebar.options.menus.fill :as fill] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] @@ -28,7 +29,7 @@ [rumext.v2 :as mf])) (mf/defc options* - [{:keys [shape file-id libraries] :as props}] + [{:keys [shape libraries file-id page-id]}] (let [id (dm/get-prop shape :id) type (dm/get-prop shape :type) ids (mf/with-memo [id] [id]) @@ -186,4 +187,12 @@ [:& blur-menu {:ids ids - :values (select-keys shape [:blur])}]])) + :values (select-keys shape [:blur])}] + + [:> exports-menu* {:type type + :ids ids + :shapes shapes + :values (select-keys shape exports-attrs) + :page-id page-id + :file-id file-id}]])) + diff --git a/frontend/src/app/util/shape_icon.cljs b/frontend/src/app/util/shape_icon.cljs index bb8bc3f5ba..dcc484184b 100644 --- a/frontend/src/app/util/shape_icon.cljs +++ b/frontend/src/app/util/shape_icon.cljs @@ -14,10 +14,11 @@ (defn- get-bool-icon "Returns the icon for a boolean shape" [shape] - (case (:bool-type shape) + (case (get shape :bool-type) :difference "boolean-difference" :exclude "boolean-exclude" :intersection "boolean-intersection" + :union "boolean-union" nil)) (defn- get-frame-icon