♻️ Refactor right sidebar state management

Also removing duplicated refs and improve efficiency of
several other refs used on sidebar.
This commit is contained in:
Andrey Antukh
2025-08-27 10:47:09 +02:00
parent bda24f3829
commit a303df9c34
36 changed files with 473 additions and 307 deletions

View File

@@ -10,6 +10,7 @@
### :sparkles: New features & Enhancements ### :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) - 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) - 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) - New Number token - unitless values [Taiga #10936](https://tree.taiga.io/project/penpot/us/10936)

View File

@@ -29,10 +29,10 @@
"~:created-at": "~m1713536343369", "~:created-at": "~m1713536343369",
"~:data": { "~:data": {
"~:pages": [ "~:pages": [
"~uc7ce0794-0992-8105-8004-38f28044384a" "~u66697432-c33d-8055-8006-2c62cc084cad"
], ],
"~:pages-index": { "~:pages-index": {
"~uc7ce0794-0992-8105-8004-38f28044384a": { "~u66697432-c33d-8055-8006-2c62cc084cad": {
"~#penpot/pointer": [ "~#penpot/pointer": [
"~ude58c8f6-c5c2-8196-8004-3df9e2e52d88", "~ude58c8f6-c5c2-8196-8004-3df9e2e52d88",
{ {

View File

@@ -91,7 +91,7 @@
} }
} }
}, },
"~:id": "~uc7ce0794-0992-8105-8004-38f28044384a", "~:id": "~u66697432-c33d-8055-8006-2c62cc084cad",
"~:name": "Page 1" "~:name": "Page 1"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"~:id": "~u51e13852-1a8e-8037-8005-9eabb500f7c7", "~:id": "~u51e13852-1a8e-8037-8005-9eabb500f7c7",
"~:file-id": "~u51e13852-1a8e-8037-8005-9e9413a1f1f6", "~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:created-at": "~m1737542758401", "~:created-at": "~m1737542758401",
"~:data": { "~:data": {
"~:options": {}, "~:options": {},
@@ -454,7 +454,7 @@
} }
} }
}, },
"~:id": "~u51e13852-1a8e-8037-8005-9e9413a1f1f7", "~:id": "~u66697432-c33d-8055-8006-2c62cc084cad",
"~:name": "Page 1", "~:name": "Page 1",
"~:background": "#e8eae9", "~:background": "#e8eae9",
"~:guides": { "~:guides": {

View File

@@ -1,6 +1,6 @@
{ {
"~:id": "~u021b87d4-813e-8066-8006-b36537098786", "~:id": "~u021b87d4-813e-8066-8006-b36537098786",
"~:file-id": "~uef9b2783-804c-8017-8006-ae6f7eab52ad", "~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:created-at": "~m1756113434655", "~:created-at": "~m1756113434655",
"~:data": { "~:data": {
"~:objects": { "~:objects": {
@@ -258,7 +258,7 @@
} }
} }
}, },
"~:id": "~uef9b2783-804c-8017-8006-ae6f7eab52ae", "~:id": "~u66697432-c33d-8055-8006-2c62cc084cad",
"~:name": "Page 1" "~:name": "Page 1"
} }
} }

View File

@@ -23,15 +23,17 @@
"~:revn": 36, "~:revn": 36,
"~:modified-at": "~m1737542758402", "~:modified-at": "~m1737542758402",
"~:vern": 0, "~:vern": 0,
"~:id": "~u51e13852-1a8e-8037-8005-9e9413a1f1f6", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:is-shared": false, "~:is-shared": false,
"~:version": 60, "~:version": 60,
"~:project-id": "~u0df61468-6cbf-8067-8005-6b453ce996d0", "~:project-id": "~u0df61468-6cbf-8067-8005-6b453ce996d0",
"~:created-at": "~m1737536563847", "~:created-at": "~m1737536563847",
"~:data": { "~:data": {
"~:pages": ["~u51e13852-1a8e-8037-8005-9e9413a1f1f7"], "~:pages": [
"~u66697432-c33d-8055-8006-2c62cc084cad"
],
"~:pages-index": { "~:pages-index": {
"~u51e13852-1a8e-8037-8005-9e9413a1f1f7": { "~u66697432-c33d-8055-8006-2c62cc084cad": {
"~#penpot/pointer": [ "~#penpot/pointer": [
"~u51e13852-1a8e-8037-8005-9eabb500f7c7", "~u51e13852-1a8e-8037-8005-9eabb500f7c7",
{ {

View File

@@ -27,7 +27,7 @@
"~:revn": 133, "~:revn": 133,
"~:modified-at": "~m1756113434658", "~:modified-at": "~m1756113434658",
"~:vern": 0, "~:vern": 0,
"~:id": "~uef9b2783-804c-8017-8006-ae6f7eab52ad", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:is-shared": false, "~:is-shared": false,
"~:migrations": { "~:migrations": {
"~#ordered-set": [] "~#ordered-set": []
@@ -36,9 +36,11 @@
"~:project-id": "~u0df61468-6cbf-8067-8005-6b453ce996d0", "~:project-id": "~u0df61468-6cbf-8067-8005-6b453ce996d0",
"~:created-at": "~m1755780585133", "~:created-at": "~m1755780585133",
"~:data": { "~:data": {
"~:pages": ["~uef9b2783-804c-8017-8006-ae6f7eab52ae"], "~:pages": [
"~u66697432-c33d-8055-8006-2c62cc084cad"
],
"~:pages-index": { "~:pages-index": {
"~uef9b2783-804c-8017-8006-ae6f7eab52ae": { "~u66697432-c33d-8055-8006-2c62cc084cad": {
"~#penpot/pointer": [ "~#penpot/pointer": [
"~u021b87d4-813e-8066-8006-b36537098786", "~u021b87d4-813e-8066-8006-b36537098786",
{ {
@@ -47,7 +49,7 @@
] ]
} }
}, },
"~:id": "~uef9b2783-804c-8017-8006-ae6f7eab52ad", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": { "~:options": {
"~:components-v2": true, "~:components-v2": true,
"~:base-font-size": "16px" "~:base-font-size": "16px"

View File

@@ -159,7 +159,10 @@ export class WorkspacePage extends BaseWebSocketPage {
"get-profiles-for-file-comments?file-id=*", "get-profiles-for-file-comments?file-id=*",
"workspace/get-profile-for-file-comments.json", "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( await this.mockRPC(
"get-file-object-thumbnails?file-id=*", "get-file-object-thumbnails?file-id=*",
"workspace/get-file-object-thumbnails-blank.json", "workspace/get-file-object-thumbnails-blank.json",

View File

@@ -182,12 +182,17 @@ test("Gradient stops limit", async ({ page }) => {
const workspacePage = new WorkspacePage(page); const workspacePage = new WorkspacePage(page);
await workspacePage.mockConfigFlags(["enable-frontend-binary-fills"]); await workspacePage.mockConfigFlags(["enable-frontend-binary-fills"]);
await workspacePage.setupEmptyFile(page); await workspacePage.setupEmptyFile(page);
await workspacePage.mockRPC( await workspacePage.mockRPC(
"get-file-fragment?file-id=*&fragment-id=*", "get-file-fragment?file-id=*&fragment-id=*",
"workspace/get-file-fragment-gradient-limits.json", "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"); await workspacePage.clickLeafLayer("Rectangle");
const swatch = workspacePage.page.getByRole("button", { const swatch = workspacePage.page.getByRole("button", {

View File

@@ -15,7 +15,10 @@ test("BUG 11552 - Apply styles to the current caret", async ({ page }) => {
"text-editor/update-file-11552.json", "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");
await workspace.clickLeafLayer("Lorem ipsum"); await workspace.clickLeafLayer("Lorem ipsum");

View File

@@ -20,7 +20,10 @@ const setupEmptyTokensFile = async (page) => {
"workspace/update-file-create-rect.json", "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" }); const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click(); await tokensTabButton.click();
@@ -60,8 +63,8 @@ const setupTokensFile = async (page, options = {}) => {
); );
await workspacePage.goToWorkspace({ await workspacePage.goToWorkspace({
fileId: "51e13852-1a8e-8037-8005-9e9413a1f1f6", fileId: "c7ce0794-0992-8105-8004-38f280443849",
pageId: "51e13852-1a8e-8037-8005-9e9413a1f1f7", pageId: "66697432-c33d-8055-8006-2c62cc084cad",
}); });
const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); const tokensTabButton = page.getByRole("tab", { name: "Tokens" });

View File

@@ -69,14 +69,18 @@
(process-selected objects selected nil)) (process-selected objects selected nil))
([objects selected {:keys [omit-blocked?] :or {omit-blocked? false}}] ([objects selected {:keys [omit-blocked?] :or {omit-blocked? false}}]
(letfn [(selectable? [id] (let [selectable?
(and (contains? objects id) (fn [id]
(or (not omit-blocked?) (and (contains? objects id)
(not (dm/get-in objects [id :blocked] false)))))] (or (not omit-blocked?)
(let [selected (->> selected (cfh/clean-loops objects))] (not (dm/get-in objects [id :blocked] false)))))
(into (d/ordered-set)
(filter selectable?) selected
selected))))) (cfh/clean-loops objects selected)]
(into (d/ordered-set)
(filter selectable?)
selected))))
(defn split-text-shapes (defn split-text-shapes
"Split text shapes from non-text shapes" "Split text shapes from non-text shapes"

View File

@@ -16,7 +16,8 @@
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[cuerdas.core :as str])) [cuerdas.core :as str]
[okulary.core :as l]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts ;; Shortcuts
@@ -111,7 +112,6 @@
:font-weight (:weight new-variant) :font-weight (:weight new-variant)
:font-style (:style new-variant)}))) :font-style (:style new-variant)})))
(defn calculate-text-values (defn calculate-text-values
[shape] [shape]
(let [state-map (if (features/active-feature? @st/state "text-editor/v2") (let [state-map (if (features/active-feature? @st/state "text-editor/v2")
@@ -196,13 +196,26 @@
:else :else
props))) 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] (defn- update-attrs-when-no-readonly [props]
(let [undo-id (js/Symbol) (let [undo-id (js/Symbol)
can-edit? (:can-edit (deref refs/permissions)) can-edit? (:can-edit (deref refs/permissions))
read-only? (deref refs/workspace-read-only?) 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?) (filter cfh/text-shape?)
(not-empty)) (not-empty))

View File

@@ -422,8 +422,9 @@
(txt/update-text-content shape txt/is-root-node? d/txt-merge attrs) (txt/update-text-content shape txt/is-root-node? d/txt-merge attrs)
(assoc shape :content (d/txt-merge {:type "root"} attrs)))) (assoc shape :content (d/txt-merge {:type "root"} attrs))))
shape-ids (cond (cfh/text-shape? shape) [id] shape-ids
(cfh/group-shape? shape) (cfh/get-children-ids objects id))] (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)))))) (rx/of (dwsh/update-shapes shape-ids update-fn))))))

View File

@@ -151,8 +151,8 @@
"All tokens related ephimeral state" "All tokens related ephimeral state"
(l/derived :workspace-tokens st/state)) (l/derived :workspace-tokens st/state))
;; TODO: rename to workspace-selected (?) ;; WARNING: Don't use directly from components, this is a proxy to
;; Don't use directly from components, this is a proxy to improve performance of selected-shapes ;; improve performance of selected-shapes and
(def ^:private selected-shapes-data (def ^:private selected-shapes-data
(l/derived (l/derived
(fn [state] (fn [state]
@@ -295,10 +295,8 @@
[page-id shape-id] [page-id shape-id]
(l/derived #(dsh/lookup-shape % page-id shape-id) st/state =)) (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 (def workspace-page-objects
(l/derived dsh/lookup-page-objects st/state =)) (l/derived dsh/lookup-page-objects st/state))
(def workspace-read-only? (def workspace-read-only?
(l/derived :read-only? workspace-global)) (l/derived :read-only? workspace-global))
@@ -366,36 +364,35 @@
(l/derived :workspace-v2-editor-state st/state)) (l/derived :workspace-v2-editor-state st/state))
(def workspace-modifiers (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 (l/derived
(fn [state] (fn [state]
{:modifiers (:workspace-modifiers state) {:modifiers (get state :workspace-modifiers)
:objects (dsh/lookup-page-objects state)}) :objects (dsh/lookup-page-objects state)})
st/state st/state
(fn [a b] (fn [a b]
(and (= (:modifiers a) (:modifiers b)) (and (identical? (:modifiers a) (:modifiers b))
(identical? (:objects a) (:objects b)))))) (identical? (:objects a) (:objects b))))))
(def workspace-frame-modifiers (def workspace-frame-modifiers
(l/derived (l/derived
(fn [{:keys [modifiers objects]}] (fn [{:keys [modifiers objects]}]
(->> modifiers (reduce (fn [result [id modifiers]]
(reduce (let [shape (get objects id)
(fn [result [id modifiers]] frame-id (:frame-id shape)]
(let [shape (get objects id) (cond
frame-id (:frame-id shape)] (cph/frame-shape? shape)
(cond (assoc-in result [id id] modifiers)
(cph/frame-shape? shape)
(assoc-in result [id id] modifiers)
(some? frame-id) (some? frame-id)
(assoc-in result [frame-id id] modifiers) (assoc-in result [frame-id id] modifiers)
:else :else
result))) result)))
{}))) {}
modifiers))
workspace-modifiers-with-objects)) workspace-modifiers-with-objects))
(defn workspace-modifiers-by-frame-id (defn workspace-modifiers-by-frame-id
@@ -408,32 +405,14 @@
(defn select-bool-children [id] (defn select-bool-children [id]
(l/derived #(dsh/select-bool-children % id) st/state =)) (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? (defn is-child-selected?
[id] [id]
(letfn [(selector [{:keys [selected objects]}] (l/derived
(let [children (cph/get-children-ids objects id)] (fn [{:keys [selected objects]}]
(some #(contains? selected %) children)))] (let [children (cph/get-children-ids objects id)]
(l/derived selector selected-data =))) (some #(contains? selected %) children)))
selected-shapes-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 =)))
(def workspace-focus-selected (def workspace-focus-selected
(l/derived :workspace-focus-selected st/state)) (l/derived :workspace-focus-selected st/state))

View File

@@ -294,10 +294,12 @@
(def ^:icon-id view-as-list "view-as-list") (def ^:icon-id view-as-list "view-as-list")
(def ^:icon-id wrap "wrap") (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 ^:const icon-size-m 16)
(def ^:private icon-size-s 12) (def ^:private ^:const icon-size-s 12)
(def ^:private schema:icon (def ^:private schema:icon
[:map [:map
@@ -309,9 +311,18 @@
(mf/defc icon* (mf/defc icon*
{::mf/schema schema:icon} {::mf/schema schema:icon}
[{:keys [icon-id size class] :rest props}] [{:keys [icon-id size class] :rest props}]
(let [class (dm/str (or class "") " " (stl/css :icon)) (let [props (mf/spread-props props
props (mf/spread-props props {:class class :width icon-size-m :height icon-size-m}) {:class [class (stl/css :icon)]
size-px (cond (= size "s") icon-size-s :else icon-size-m) :width icon-size-m
offset (/ (- icon-size-m size-px) 2)] :height icon-size-m})
[:> "svg" props size-px (if (= size "s")
[:use {:href (dm/str "#icon-" icon-id) :width size-px :height size-px :x offset :y offset}]])) 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}]]))

View File

@@ -39,11 +39,11 @@
(assoc id {:id id (assoc id {:id id
:data local}))))) :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] [{:keys [frame page objects file selected shapes page-id file-id share-id from on-change-section on-expand]
:or {from :viewer}}] :or {from :viewer}}]
(let [color-space* (mf/use-state "hex") (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)) section (mf/use-state #(if (contains? cf/flags :inspect-styles) :styles :info))
objects (or objects (:objects page)) objects (or objects (:objects page))

View File

@@ -14,7 +14,7 @@
[app.main.ui.hooks.resize :refer [use-resize-hook]] [app.main.ui.hooks.resize :refer [use-resize-hook]]
[app.main.ui.inspect.left-sidebar :refer [left-sidebar]] [app.main.ui.inspect.left-sidebar :refer [left-sidebar]]
[app.main.ui.inspect.render :refer [render-frame-svg]] [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.dom :as dom]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[goog.events :as events] [goog.events :as events]
@@ -112,10 +112,10 @@
:on-pointer-down on-pointer-down :on-pointer-down on-pointer-down
:on-lost-pointer-capture on-lost-pointer-capture :on-lost-pointer-capture on-lost-pointer-capture
:on-pointer-move on-pointer-move}]) :on-pointer-move on-pointer-move}])
[:& right-sidebar {:frame frame [:> right-sidebar* {:frame frame
:selected (:selected local) :selected (:selected local)
:page page :page page
:file file :file file
:on-change-section handle-change-section :on-change-section handle-change-section
:on-expand handle-expand :on-expand handle-expand
:share-id share-id}]]])) :share-id share-id}]]]))

View File

@@ -628,11 +628,10 @@
(mf/defc shape-context-menu* (mf/defc shape-context-menu*
{::mf/wrap [mf/memo] {::mf/wrap [mf/memo]
::mf/private true ::mf/private true}
::mf/props :obj}
[{:keys [mdata]}] [{:keys [mdata]}]
(let [{:keys [disable-booleans disable-flatten]} 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? %)))) is-not-variant-container? (->> shapes (d/seek #(not (ctk/is-variant-container? %))))
props (mf/props props (mf/props
{:shapes shapes {:shapes shapes
@@ -828,8 +827,8 @@
(if ^boolean read-only? (if ^boolean read-only?
[:> viewport-context-menu* {:mdata mdata}] [:> viewport-context-menu* {:mdata mdata}]
(case (:kind mdata) (case (:kind mdata)
:shape [:> shape-context-menu* {:mdata mdata}] :shape [:> shape-context-menu* {:mdata mdata}]
:page [:> page-item-context-menu* {:mdata mdata}] :page [:> page-item-context-menu* {:mdata mdata}]
:grid-track [:> grid-track-context-menu* {:mdata mdata}] :grid-track [:> grid-track-context-menu* {:mdata mdata}]
:grid-cells [:> grid-cells-context-menu* {:mdata mdata}] :grid-cells [:> grid-cells-context-menu* {:mdata mdata}]
[:> viewport-context-menu* {:mdata mdata}]))]]])) [:> viewport-context-menu* {:mdata mdata}]))]]]))

View File

@@ -35,16 +35,14 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc layer-item-inner (mf/defc layer-item-inner
{::mf/wrap-props false {::mf/wrap-props false}
::mf/forward-ref true} [{:keys [item depth parent-size name-ref children ref
[{:keys [item depth parent-size name-ref children
;; Flags ;; Flags
read-only? highlighted? selected? component-tree? read-only? highlighted? selected? component-tree?
filtered? expanded? dnd-over? dnd-over-top? dnd-over-bot? hide-toggle? filtered? expanded? dnd-over? dnd-over-top? dnd-over-bot? hide-toggle?
;; Callbacks ;; Callbacks
on-select-shape on-context-menu on-pointer-enter on-pointer-leave on-zoom-to-selected 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]} on-toggle-collapse on-enable-drag on-disable-drag on-toggle-visibility on-toggle-blocking]}]
dref]
(let [id (:id item) (let [id (:id item)
name (:name item) name (:name item)
@@ -67,9 +65,10 @@
component (ctkl/get-component data (:component-id item)) component (ctkl/get-component data (:component-id item))
variant-properties (:variant-properties component) variant-properties (:variant-properties component)
icon-shape (usi/get-shape-icon item)] icon-shape (usi/get-shape-icon item)]
[:* [:*
[:div {:id id [:div {:id id
:ref dref :ref ref
:on-click on-select-shape :on-click on-select-shape
:on-context-menu on-context-menu :on-context-menu on-context-menu
:data-testid "layer-row" :data-testid "layer-row"

View File

@@ -12,6 +12,7 @@
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.main.data.helpers :as dsh]
[app.main.data.workspace :as udw] [app.main.data.workspace :as udw]
[app.main.data.workspace.common :as dwc] [app.main.data.workspace.common :as dwc]
[app.main.refs :as refs] [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.align :refer [align-options*]]
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-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.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.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]] [app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container] [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.svg-raw :as svg-raw]
[app.main.ui.workspace.sidebar.options.shapes.text :as text] [app.main.ui.workspace.sidebar.options.shapes.text :as text]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[okulary.core :as l]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
;; --- Options ;; --- Options
(mf/defc shape-options* (mf/defc single-shape-options*
{::mf/wrap [#(mf/throttle % 60)]} {::mf/private true}
[{:keys [shape shapes-with-children page-id file-id libraries] :as props}] [{:keys [shape page-id file-id libraries] :as props}]
(let [shape-type (dm/get-prop shape :type) (let [shape-type (dm/get-prop shape :type)
shape-id (dm/get-prop shape :id) shape-id (dm/get-prop shape :id)
@@ -53,45 +55,83 @@
shape (gsh/transform-shape shape modifiers)] shape (gsh/transform-shape shape modifiers)]
[:* (case shape-type
(case shape-type :frame [:> frame/options* props]
:frame [:> frame/options* props] :group [:> group/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 :page-id page-id :libraries libraries}]
:text [:> text/options* {:shape shape :file-id file-id :libraries libraries}] :rect [:> rect/options* {:shape shape :file-id file-id :page-id page-id}]
:rect [:> rect/options* {:shape shape}] :circle [:> circle/options* {:shape shape :file-id file-id :page-id page-id}]
:circle [:> circle/options* {:shape shape}] :path [:> path/options* {:shape shape :file-id file-id :page-id page-id}]
:path [:> path/options* {:shape shape}] :svg-raw [:> svg-raw/options* {:shape shape :file-id file-id :page-id page-id}]
:svg-raw [:> svg-raw/options* {:shape shape}] :bool [:> bool/options* {:shape shape :file-id file-id :page-id page-id}]
:bool [:> bool/options* {:shape shape}] nil)))
nil)
[:& exports-menu (mf/defc shape-options*
{:ids [(:id shape)] {::mf/wrap [#(mf/throttle % 100)]
:values (select-keys shape [:exports]) ::mf/private true}
:shape shape [{:keys [shapes shapes-with-children selected page-id file-id libraries]}]
:page-id page-id (if (= 1 (count selected))
:file-id file-id}]])) [:> 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/defc specialized-panel*
{::mf/wrap [mf/memo]} {::mf/private true}
[{:keys [panel]}] [{:keys [panel]}]
(when (= (:type panel) :component-swap) (when (= (:type panel) :component-swap)
[:& component-menu {:shapes (:shapes panel) :swap-opened? true}])) [:& component-menu {:shapes (:shapes panel) :swap-opened? true}]))
(mf/defc design-menu* (mf/defc design-menu*
{::mf/wrap [mf/memo]} {::mf/private true}
[{:keys [selected objects page-id file-id selected-shapes shapes-with-children]}] [{:keys [selected objects page-id file-id shapes]}]
(let [sp-panel (mf/deref refs/specialized-panel) (let [sp-panel (mf/deref refs/specialized-panel)
drawing (mf/deref refs/workspace-drawing) drawing (mf/deref refs/workspace-drawing)
libraries (mf/deref refs/libraries) edition (mf/deref refs/selected-edition)
edition (mf/deref refs/selected-edition)
edit-grid? (ctl/grid-layout? objects edition) files
grid-edition (mf/deref refs/workspace-grid-edition) (mf/deref refs/files)
selected-cells (->> (dm/get-in grid-edition [edition :selected])
(map #(dm/get-in objects [edition :layout-grid-cells %])))] 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)} [:div {:class (stl/css :element-options :design-options)}
[:> align-options*] [:> align-options* {:shapes shapes
[:> bool-options*] :objects objects}]
[:> bool-options* {:total-selected total-selected
:shapes shapes
:shapes-with-children shapes-with-children}]
(cond (cond
(and edit-grid? (d/not-empty? selected-cells)) (and edit-grid? (d/not-empty? selected-cells))
@@ -104,67 +144,71 @@
{:ids [edition] {:ids [edition]
:values (get objects edition)}] :values (get objects edition)}]
(not (nil? sp-panel)) (some? sp-panel)
[:> specialized-panel* {:panel sp-panel}] [:> specialized-panel* {:panel sp-panel}]
(d/not-empty? drawing) (d/not-empty? drawing)
[:> drawing/drawing-options* [:> drawing/drawing-options*
{:drawing-state drawing}] {:drawing-state drawing}]
(= 0 (count selected)) (zero? total-selected)
[:> page/options*] [:> 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 :else
[:> multiple/options* [:> shape-options*
{:shapes-with-children shapes-with-children {:shapes shapes
:shapes selected-shapes :shapes-with-children shapes-with-children
:page-id page-id :page-id page-id
:file-id file-id :file-id file-id
:selected selected
:libraries libraries}])])) :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/defc options-content*
{::mf/memo true {::mf/private true}
::mf/private true} [{:keys [objects selected page-id file-id on-change-section on-expand]}]
[{:keys [selected shapes shapes-with-children page-id file-id on-change-section on-expand]}] (let [permissions
(let [objects (mf/deref refs/workspace-page-objects) (mf/use-ctx ctx/permissions)
permissions (mf/use-ctx ctx/permissions)
selected-shapes (into [] (keep (d/getf objects)) selected) options-mode
first-selected-shape (first selected-shapes) (mf/deref refs/options-mode-global)
shape-parent-frame (cfh/get-frame objects (:frame-id first-selected-shape))
options-mode (mf/deref refs/options-mode-global) shapes
(mf/with-memo [selected objects]
on-change-tab (sequence (keep (d/getf objects)) selected))]
(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"}])]
[:div {:class (stl/css :tool-window)} [:div {:class (stl/css :tool-window)}
(if (:can-edit permissions) (if (:can-edit permissions)
[:> tab-switcher* {:tabs tabs [:> tab-switcher* {:tabs options-tabs
:on-change on-change-tab :on-change on-option-tab-change
:selected (name options-mode) :selected (name options-mode)
:class (stl/css :options-tab-switcher)} :class (stl/css :options-tab-switcher)}
(case options-mode (case options-mode
@@ -174,46 +218,46 @@
:inspect :inspect
[:div {:class (stl/css :element-options :inspect-options)} [:div {:class (stl/css :element-options :inspect-options)}
[:& hrs/right-sidebar {:page-id page-id [:> inspect-tab* {:page-id page-id
:objects objects :file-id file-id
:file-id file-id :objects objects
:frame shape-parent-frame :selected selected
:shapes selected-shapes :shapes shapes
:on-change-section on-change-section :on-change-section on-change-section
:on-expand on-expand :on-expand on-expand}]]
:from :workspace}]]
:design :design
[:> design-menu* {:selected selected [:> design-menu* {:selected selected
:objects objects :objects objects
:page-id page-id :page-id page-id
:file-id file-id :file-id file-id
:selected-shapes selected-shapes :shapes shapes}])]
:shapes-with-children shapes-with-children}])]
;; FIXME: Reuse tab???
[:div {:class (stl/css :element-options :inspect-options :read-only)} [:div {:class (stl/css :element-options :inspect-options :read-only)}
[:& hrs/right-sidebar {:page-id page-id [:> inspect-tab* {:page-id page-id
:objects objects :file-id file-id
:file-id file-id :objects objects
:frame shape-parent-frame :selected selected
:shapes selected-shapes :shapes shapes
:on-change-section on-change-section :on-change-section on-change-section
:on-expand on-expand :on-expand on-expand}]])]))
:from :workspace}]])]))
;; TODO: this need optimizations, selected-objects and (defn- make-page-objects-ref
;; selected-objects-with-children are derefed always but they only [file-id page-id]
;; need on multiple selection in majority of cases (l/derived #(dsh/lookup-page-objects % file-id page-id) st/state))
(mf/defc options-toolbox* (mf/defc options-toolbox*
{::mf/memo true} {::mf/memo true}
[{:keys [page-id file-id section selected on-change-section on-expand]}] [{:keys [page-id file-id section selected on-change-section on-expand]}]
(let [shapes (mf/deref refs/selected-objects) (let [objects-ref
shapes-with-children (mf/deref refs/selected-shapes-with-children)] (mf/with-memo [page-id file-id]
[:> options-content* {:shapes shapes (make-page-objects-ref file-id page-id))
objects
(mf/deref objects-ref)]
[:> options-content* {:objects objects
:selected selected :selected selected
:shapes-with-children shapes-with-children
:file-id file-id :file-id file-id
:page-id page-id :page-id page-id
:section section :section section

View File

@@ -9,7 +9,6 @@
(:require (:require
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.shortcuts :as sc] [app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
@@ -17,14 +16,12 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc align-options* (mf/defc align-options*
{::mf/memo true} [{:keys [shapes objects]}]
[] (let [disabled-align
(let [selected (mf/deref refs/selected-shapes) (not (dw/can-align? shapes objects))
;; don't need to watch objects, only read the value
objects (deref refs/workspace-page-objects)
disabled-align (not (dw/can-align? selected objects)) disabled-distribute
disabled-distribute (not (dw/can-distribute? selected)) (not (dw/can-distribute? shapes))
align-objects align-objects
(mf/use-fn (mf/use-fn
@@ -42,7 +39,7 @@
(keyword))] (keyword))]
(st/emit! (dw/distribute-objects value)))))] (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-options)}
[:div {:class (stl/css :align-group-horizontal)} [:div {:class (stl/css :align-group-horizontal)}
[:button {:class (stl/css-case :align-button true [:button {:class (stl/css-case :align-button true

View File

@@ -8,11 +8,12 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d] [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.bool :as dwb]
[app.main.data.workspace.path.shapes-to-path :as dwps] [app.main.data.workspace.path.shapes-to-path :as dwps]
[app.main.data.workspace.shortcuts :as sc] [app.main.data.workspace.shortcuts :as sc]
[app.main.features :as features] [app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
@@ -23,48 +24,61 @@
(i/icon-xref :boolean-flatten (stl/css :flatten-icon))) (i/icon-xref :boolean-flatten (stl/css :flatten-icon)))
(mf/defc bool-options* (mf/defc bool-options*
{::mf/memo true} [{:keys [total-selected shapes shapes-with-children]}]
[] (let [head (first shapes)
(let [selected (mf/deref refs/selected-objects) head-id (dm/get-prop head :id)
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))
first-not-group-like? is-group? (cfh/group-shape? head)
(and (= (count selected) 1) is-bool? (cfh/bool-shape? head)
(not (contains? #{:group :bool} (:type (first selected)))))
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 disabled-bool-btns
(if (features/active-feature? @st/state "render-wasm/v1") (if render-wasm-enabled?
false false
(or (empty? selected) has-invalid-shapes? first-not-group-like?)) (or (zero? total-selected)
has-invalid-shapes?
head-not-group-like?))
disabled-flatten disabled-flatten
(if (features/active-feature? @st/state "render-wasm/v1") (if render-wasm-enabled?
false false
(or (empty? selected) has-invalid-shapes?)) (or (zero? total-selected)
has-invalid-shapes?))
set-bool on-change
(mf/use-fn (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] (fn [bool-type]
(let [bool-type (keyword bool-type)] (let [bool-type (keyword bool-type)]
(cond (cond
(> (count selected) 1) (> total-selected 1)
(st/emit! (dwb/create-bool bool-type)) (st/emit! (dwb/create-bool bool-type))
(and (= (count selected) 1) is-group?) (and (= total-selected 1) is-group?)
(st/emit! (dwb/group-to-bool (:id head) bool-type)) (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) (if (= head-bool-type bool-type)
(st/emit! (dwb/bool-to-group (:id head))) (st/emit! (dwb/bool-to-group head-id))
(st/emit! (dwb/change-bool-type (:id head) bool-type))))))) (st/emit! (dwb/change-bool-type head-id bool-type)))))))
flatten-objects flatten-objects
(mf/use-fn #(st/emit! (dwps/convert-selected-to-path)))] (mf/use-fn #(st/emit! (dwps/convert-selected-to-path)))]
@@ -74,7 +88,7 @@
[:div {:class (stl/css :bool-group)} [:div {:class (stl/css :bool-group)}
[:& radio-buttons {:selected (d/name head-bool-type) [:& radio-buttons {:selected (d/name head-bool-type)
:class (stl/css :boolean-radio-btn) :class (stl/css :boolean-radio-btn)
:on-change set-bool :on-change on-change
:name "bool-options"} :name "bool-options"}
[:& radio-button {:icon i/boolean-union [:& radio-button {:icon i/boolean-union
:value "union" :value "union"

View File

@@ -9,7 +9,6 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.main.data.exports.assets :as de] [app.main.data.exports.assets :as de]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
@@ -26,29 +25,55 @@
"Shape attrs that corresponds to exports. Used in other namespaces." "Shape attrs that corresponds to exports. Used in other namespaces."
[:exports]) [:exports])
(mf/defc exports-menu (defn- check-exports-menu-props
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "page-id" "file-id"]))]} [old-props new-props]
[{:keys [ids type values page-id file-id] :as props}] (and (identical? (unchecked-get old-props "ids")
(let [exports (:exports values []) (unchecked-get new-props "ids"))
has-exports? (or (= :multiple exports) (some? (seq exports))) (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) ;; NOTE: we explicitly ignore "shapes" prop and use values for
open? (deref comp-state*) ;; 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) (let [exports (get values :exports [])
in-progress? (:in-progress state) open* (mf/use-state true)
open? (deref open*)
shapes-with-exports (->> (dsh/lookup-shapes @st/state ids) state (mf/deref refs/export)
(filter #(pos? (count (:exports %)))))
sname (when (seqable? exports) in-progress?
(let [sname (-> shapes-with-exports first :name) (get state :in-progress)
suffix (-> exports first :suffix)]
(cond-> sname has-exports?
(and (= 1 (count exports)) (some? suffix)) (or (= :multiple exports)
(str suffix)))) (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? scale-enabled?
(mf/use-fn (mf/use-fn

View File

@@ -309,6 +309,7 @@
:title "Align self end" :title "Align self end"
:id "align-self-end"}]]) :id "align-self-end"}]])
(mf/defc layout-item-menu (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/memo #{:ids :values :type :is-layout-child? :is-grid-parent :is-flex-parent? :is-grid-layout? :is-flex-layout?}
::mf/props :obj} ::mf/props :obj}

View File

@@ -11,6 +11,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [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.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.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [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]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
@@ -22,7 +23,7 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc options* (mf/defc options*
[{:keys [shape] :as props}] [{:keys [shape file-id page-id]}]
(let [id (dm/get-prop shape :id) (let [id (dm/get-prop shape :id)
type (dm/get-prop shape :type) type (dm/get-prop shape :type)
ids (mf/with-memo [id] [id]) ids (mf/with-memo [id] [id])
@@ -124,4 +125,13 @@
[:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:> shadow-menu* {:ids ids :values (get shape :shadow)}]
[:& blur-menu {:ids ids [:& 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}]]))

View File

@@ -11,6 +11,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [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.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.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [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]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
@@ -23,7 +24,7 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc options* (mf/defc options*
[{:keys [shape] :as props}] [{:keys [shape file-id page-id]}]
(let [id (dm/get-prop shape :id) (let [id (dm/get-prop shape :id)
type (dm/get-prop shape :type) type (dm/get-prop shape :type)
ids (mf/with-memo [id] [id]) ids (mf/with-memo [id] [id])
@@ -123,4 +124,11 @@
[:& blur-menu {:ids ids [:& blur-menu {:ids ids
:values (select-keys shape [:blur])}] :values (select-keys shape [:blur])}]
[:& svg-attrs-menu {:ids ids [:& 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}]]))

View File

@@ -15,6 +15,7 @@
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]] [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.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.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.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]] [app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
@@ -27,17 +28,11 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc options* (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) (let [shape-id (dm/get-prop shape :id)
shape-type (dm/get-prop shape :type) shape-type (dm/get-prop shape :type)
ids (mf/with-memo [shape-id] [shape-id])
ids shapes (mf/with-memo [shape] [shape])
(mf/with-memo [shape-id]
[shape-id])
shapes
(mf/with-memo [shape]
[shape])
stroke-values stroke-values
(select-keys shape stroke-attrs) (select-keys shape stroke-attrs)
@@ -160,4 +155,10 @@
[:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:> shadow-menu* {:ids ids :values (get shape :shadow)}]
[:& blur-menu {:ids ids [:& blur-menu {:ids ids
:values (select-keys shape [:blur])}] :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}]]))

View File

@@ -14,6 +14,7 @@
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [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.color-selection :refer [color-selection-menu*]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-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.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-menu]]
@@ -29,7 +30,7 @@
(mf/defc options* (mf/defc options*
{::mf/wrap [mf/memo]} {::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) (let [id (dm/get-prop shape :id)
type (dm/get-prop shape :type) type (dm/get-prop shape :type)
@@ -157,6 +158,14 @@
[:& ot/text-menu {:type type :ids text-ids :values text-values}]) [:& ot/text-menu {:type type :ids text-ids :values text-values}])
(when-not (empty? svg-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}]]))

View File

@@ -23,7 +23,7 @@
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]] [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.component :refer [component-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-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.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [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]] [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}]) [:& blur-menu {:type type :ids blur-ids :values blur-values}])
(when-not (empty? exports-ids) (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}])]))

View File

@@ -11,6 +11,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [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.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.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [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]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
@@ -23,17 +24,11 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc options* (mf/defc options*
[{:keys [shape] :as props}] [{:keys [shape file-id page-id]}]
(let [ids (let [id (dm/get-prop shape :id)
(mf/with-memo [shape] type (dm/get-prop shape :type)
[(dm/get-prop shape :id)]) ids (mf/with-memo [id] [id])
shapes (mf/with-memo [shape] [shape])
shapes
(mf/with-memo [shape]
[shape])
type
(dm/get-prop shape :type)
measure-values measure-values
(select-keys shape measure-attrs) (select-keys shape measure-attrs)
@@ -131,4 +126,11 @@
[:& blur-menu {:ids ids [:& blur-menu {:ids ids
:values (select-keys shape [:blur])}] :values (select-keys shape [:blur])}]
[:& svg-attrs-menu {:ids ids [:& 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}]]))

View File

@@ -11,6 +11,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [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.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.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [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]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
@@ -23,7 +24,7 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc options* (mf/defc options*
[{:keys [shape]}] [{:keys [shape file-id page-id]}]
(let [id (dm/get-prop shape :id) (let [id (dm/get-prop shape :id)
type (dm/get-prop shape :type) type (dm/get-prop shape :type)
ids (mf/with-memo [id] [id]) ids (mf/with-memo [id] [id])
@@ -127,4 +128,11 @@
:values (select-keys shape [:blur])}] :values (select-keys shape [:blur])}]
[:& svg-attrs-menu {:ids ids [:& 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}]]))

View File

@@ -13,6 +13,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [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.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.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [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]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
@@ -87,8 +88,7 @@
stroke-values)) stroke-values))
(mf/defc options* (mf/defc options*
{::mf/wrap [mf/memo]} [{:keys [shape file-id page-id]}]
[{:keys [shape] :as props}]
(let [id (dm/get-prop shape :id) (let [id (dm/get-prop shape :id)
type (dm/get-prop shape :type) type (dm/get-prop shape :type)
@@ -196,4 +196,11 @@
:values (select-keys shape [:blur])}] :values (select-keys shape [:blur])}]
[:& svg-attrs-menu {:ids ids [:& 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}]])))

View File

@@ -16,6 +16,7 @@
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [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.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.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.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [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]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
@@ -28,7 +29,7 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc options* (mf/defc options*
[{:keys [shape file-id libraries] :as props}] [{:keys [shape libraries file-id page-id]}]
(let [id (dm/get-prop shape :id) (let [id (dm/get-prop shape :id)
type (dm/get-prop shape :type) type (dm/get-prop shape :type)
ids (mf/with-memo [id] [id]) ids (mf/with-memo [id] [id])
@@ -186,4 +187,12 @@
[:& blur-menu [:& blur-menu
{:ids ids {: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}]]))

View File

@@ -14,10 +14,11 @@
(defn- get-bool-icon (defn- get-bool-icon
"Returns the icon for a boolean shape" "Returns the icon for a boolean shape"
[shape] [shape]
(case (:bool-type shape) (case (get shape :bool-type)
:difference "boolean-difference" :difference "boolean-difference"
:exclude "boolean-exclude" :exclude "boolean-exclude"
:intersection "boolean-intersection" :intersection "boolean-intersection"
:union "boolean-union"
nil)) nil))
(defn- get-frame-icon (defn- get-frame-icon