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/RegisterPage.js b/frontend/playwright/ui/pages/RegisterPage.js
index 945a3d6c42..097bbefb97 100644
--- a/frontend/playwright/ui/pages/RegisterPage.js
+++ b/frontend/playwright/ui/pages/RegisterPage.js
@@ -3,7 +3,9 @@ import { BasePage } from "./BasePage";
export class RegisterPage extends BasePage {
constructor(page) {
super(page);
- this.registerButton = page.getByRole("button", { name: "Create an account" });
+ this.registerButton = page.getByRole("button", {
+ name: "Create an account",
+ });
this.password = page.getByLabel("Password");
this.email = page.getByLabel("Work email");
this.fullName = page.getByLabel("Full name");
diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js
index 1effc84679..a2717632b4 100644
--- a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js
+++ b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js
@@ -124,11 +124,13 @@ test("Renders shapes with exif rotated images fills and strokes", async ({
"27270c45-35b4-80f3-8006-63a39cf292e7",
"27270c45-35b4-80f3-8006-63a41d147866",
"27270c45-35b4-80f3-8006-63a43dc4984b",
- "27270c45-35b4-80f3-8006-63a3ea82557f"
+ "27270c45-35b4-80f3-8006-63a3ea82557f",
],
"render-wasm/assets/landscape.jpg",
);
- await workspace.mockGetFile("render-wasm/get-file-shapes-exif-rotated-fills.json");
+ await workspace.mockGetFile(
+ "render-wasm/get-file-shapes-exif-rotated-fills.json",
+ );
await workspace.goToWorkspace({
id: "27270c45-35b4-80f3-8006-63a3912bdce8",
@@ -139,9 +141,7 @@ test("Renders shapes with exif rotated images fills and strokes", async ({
await expect(workspace.canvas).toHaveScreenshot();
});
-test("Updates canvas background", async ({
- page,
-}) => {
+test("Updates canvas background", async ({ page }) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-text.json");
@@ -152,7 +152,9 @@ test("Updates canvas background", async ({
});
await workspace.waitForFirstRender({ hideUI: false });
- const canvasBackgroundInput = workspace.page.getByRole("textbox", { name: 'Color' });
+ const canvasBackgroundInput = workspace.page.getByRole("textbox", {
+ name: "Color",
+ });
await canvasBackgroundInput.fill("FABADA");
await workspace.page.keyboard.press("Enter");
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js b/frontend/playwright/ui/render-wasm-specs/texts.spec.js
index 11c4e72e51..f7fcb4b754 100644
--- a/frontend/playwright/ui/render-wasm-specs/texts.spec.js
+++ b/frontend/playwright/ui/render-wasm-specs/texts.spec.js
@@ -12,37 +12,36 @@ test.beforeEach(async ({ page }) => {
async function mockGetEmojiFont(workspace) {
await workspace.mockGetAsset(
/notocoloremoji.*\.ttf$/,
- "render-wasm/assets/notocoloremojisubset.ttf"
+ "render-wasm/assets/notocoloremojisubset.ttf",
);
}
async function mockGetJapaneseFont(workspace) {
await workspace.mockGetAsset(
/notosansjp.*\.ttf$/,
- "render-wasm/assets/notosansjpsubset.ttf"
+ "render-wasm/assets/notosansjpsubset.ttf",
);
await workspace.mockGetAsset(
/notosanssc.*\.ttf$/,
- "render-wasm/assets/notosansjpsubset.ttf"
+ "render-wasm/assets/notosansjpsubset.ttf",
);
}
async function mockGetSymbolsFont(workspace) {
await workspace.mockGetAsset(
/notosanssymbols.*\.ttf$/,
- "render-wasm/assets/notosanssymbolssubset.ttf"
+ "render-wasm/assets/notosanssymbolssubset.ttf",
);
await workspace.mockGetAsset(
/notosanssymbols2.*\.ttf$/,
- "render-wasm/assets/notosanssymbols2subset.ttf"
+ "render-wasm/assets/notosanssymbols2subset.ttf",
);
await workspace.mockGetAsset(
/notomusic.*\.ttf$/,
- "render-wasm/assets/notomusicsubset.ttf"
+ "render-wasm/assets/notomusicsubset.ttf",
);
}
-
test("Renders a file with texts", async ({ page }) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
@@ -139,7 +138,7 @@ test("Renders a file with texts with images", async ({ page }) => {
await workspace.mockFileMediaAsset(
[
"4f89252d-ebbc-813e-8006-8699e4170e17",
- "4f89252d-ebbc-813e-8006-8699e4170e18"
+ "4f89252d-ebbc-813e-8006-8699e4170e18",
],
"render-wasm/assets/pattern.png",
);
@@ -156,7 +155,9 @@ test("Renders a file with texts with images", async ({ page }) => {
await expect(workspace.canvas).toHaveScreenshot();
});
-test("Renders a file with texts with emoji and different symbols", async ({ page }) => {
+test("Renders a file with texts with emoji and different symbols", async ({
+ page,
+}) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await mockGetEmojiFont(workspace);
@@ -176,9 +177,7 @@ test("Renders a file with text decoration", async ({ page }) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockFileMediaAsset(
- [
- "d6c33e7b-7b64-80f3-8006-78509a3a2d21",
- ],
+ ["d6c33e7b-7b64-80f3-8006-78509a3a2d21"],
"render-wasm/assets/pattern.png",
);
await mockGetEmojiFont(workspace);
@@ -199,7 +198,9 @@ test("Renders a file with emoji and text decoration", async ({ page }) => {
await workspace.setupEmptyFile();
await mockGetEmojiFont(workspace);
- await workspace.mockGetFile("render-wasm/get-file-emoji-and-text-decoration.json");
+ await workspace.mockGetFile(
+ "render-wasm/get-file-emoji-and-text-decoration.json",
+ );
await workspace.goToWorkspace({
id: "82d128e1-d3b1-80a5-8006-ae60fedcd5e7",
@@ -222,11 +223,13 @@ test("Renders a file with multiple emoji", async ({ page }) => {
pageId: "6bd7c17d-4f59-815e-8006-5e999f38f211",
});
- await workspace.waitForFirstRender();
+ await workspace.waitForFirstRender();
await expect(workspace.canvas).toHaveScreenshot();
});
-test("Renders a file with texts with different alignments", async ({ page }) => {
+test("Renders a file with texts with different alignments", async ({
+ page,
+}) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-text-align.json");
@@ -251,12 +254,16 @@ test("Updates text alignment edition - part 1", async ({ page }) => {
await workspace.waitForFirstRender({ hideUI: false });
await workspace.clickLeafLayer("Text 1");
- const textOptionsButton = workspace.page.getByTestId("text-align-options-button");
+ const textOptionsButton = workspace.page.getByTestId(
+ "text-align-options-button",
+ );
const autoWidthButton = workspace.page.getByTitle("Auto width");
const autoHeightButton = workspace.page.getByTitle("Auto height");
const alignMiddleButton = workspace.page.getByTitle("Align middle");
const alignBottomButton = workspace.page.getByTitle("Align bottom");
- const alignRightButton = workspace.page.getByTitle("Align right (Ctrl+Alt+R)");
+ const alignRightButton = workspace.page.getByTitle(
+ "Align right (Ctrl+Alt+R)",
+ );
await textOptionsButton.click();
@@ -276,7 +283,7 @@ test("Updates text alignment edition - part 1", async ({ page }) => {
await workspace.page.keyboard.press("Escape");
await workspace.hideUI();
- await expect(workspace.canvas).toHaveScreenshot({timeout: 10000});
+ await expect(workspace.canvas).toHaveScreenshot({ timeout: 10000 });
});
test("Updates text alignment edition - part 2", async ({ page }) => {
@@ -291,11 +298,15 @@ test("Updates text alignment edition - part 2", async ({ page }) => {
await workspace.waitForFirstRender({ hideUI: false });
await workspace.clickLeafLayer("Text 1");
- const textOptionsButton = workspace.page.getByTestId("text-align-options-button");
+ const textOptionsButton = workspace.page.getByTestId(
+ "text-align-options-button",
+ );
const alignTopButton = workspace.page.getByTitle("Align top");
const alignMiddleButton = workspace.page.getByTitle("Align middle");
const alignBottomButton = workspace.page.getByTitle("Align bottom");
- const alignCenterButton = workspace.page.getByTitle("Align center (Ctrl+Alt+T)");
+ const alignCenterButton = workspace.page.getByTitle(
+ "Align center (Ctrl+Alt+T)",
+ );
const alignJustifyButton = workspace.page.getByTitle("Justify (Ctrl+Alt+J)");
const LTRButton = workspace.page.getByTitle("LTR");
const RTLButton = workspace.page.getByTitle("RTL");
@@ -324,7 +335,7 @@ test("Updates text alignment edition - part 2", async ({ page }) => {
await workspace.page.keyboard.press("Escape");
await workspace.hideUI();
- await expect(workspace.canvas).toHaveScreenshot({timeout: 10000});
+ await expect(workspace.canvas).toHaveScreenshot({ timeout: 10000 });
});
test("Updates text alignment edition - part 3", async ({ page }) => {
@@ -339,13 +350,17 @@ test("Updates text alignment edition - part 3", async ({ page }) => {
await workspace.waitForFirstRender({ hideUI: false });
await workspace.clickLeafLayer("Text 1");
- const textOptionsButton = workspace.page.getByTestId("text-align-options-button");
+ const textOptionsButton = workspace.page.getByTestId(
+ "text-align-options-button",
+ );
const autoWidthButton = workspace.page.getByTitle("Auto width");
const autoHeightButton = workspace.page.getByTitle("Auto height");
const alignMiddleButton = workspace.page.getByTitle("Align middle");
const alignBottomButton = workspace.page.getByTitle("Align bottom");
const alignLeftButton = workspace.page.getByTitle("Align left (Ctrl+Alt+L)");
- const alignCenterButton = workspace.page.getByTitle("Align center (Ctrl+Alt+T)");
+ const alignCenterButton = workspace.page.getByTitle(
+ "Align center (Ctrl+Alt+T)",
+ );
const alignJustifyButton = workspace.page.getByTitle("Justify (Ctrl+Alt+J)");
const RTLButton = workspace.page.getByTitle("RTL");
@@ -375,5 +390,5 @@ test("Updates text alignment edition - part 3", async ({ page }) => {
await workspace.page.keyboard.press("Escape");
await workspace.hideUI();
- await expect(workspace.canvas).toHaveScreenshot({timeout: 10000});
-});
\ No newline at end of file
+ await expect(workspace.canvas).toHaveScreenshot({ timeout: 10000 });
+});
diff --git a/frontend/playwright/ui/specs/colorpicker.spec.js b/frontend/playwright/ui/specs/colorpicker.spec.js
index 012bb52379..db2e3ccdfe 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/register.spec.js b/frontend/playwright/ui/specs/register.spec.js
index 301701a225..24e835c829 100644
--- a/frontend/playwright/ui/specs/register.spec.js
+++ b/frontend/playwright/ui/specs/register.spec.js
@@ -7,15 +7,21 @@ test.beforeEach(async ({ page }) => {
});
test.describe("Register form errors", () => {
- test("User gets error message when email does not match invitation", async ({ page }) => {
+ test("User gets error message when email does not match invitation", async ({
+ page,
+ }) => {
const registerPage = new RegisterPage(page);
await registerPage.setupMismatchedEmailError();
- await registerPage.fillRegisterFormInputs("John Doe", "john.doe@example.com", "password123");
+ await registerPage.fillRegisterFormInputs(
+ "John Doe",
+ "john.doe@example.com",
+ "password123",
+ );
await registerPage.clickRegisterButton();
- await expect(page.getByText(
- "Email does not match the invitation.",
- )).toBeVisible();
+ await expect(
+ page.getByText("Email does not match the invitation."),
+ ).toBeVisible();
});
});
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..5878c762a8 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" });
@@ -116,7 +119,6 @@ test.describe("Tokens: Tokens Tab", () => {
.click();
// Create color token with mouse
-
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
diff --git a/frontend/scripts/_helpers.js b/frontend/scripts/_helpers.js
index b3f79fca45..323da942b0 100644
--- a/frontend/scripts/_helpers.js
+++ b/frontend/scripts/_helpers.js
@@ -255,9 +255,9 @@ const markedOptions = {
const text = token.text;
return `${text}`;
}
- }
- }
-}
+ },
+ },
+};
marked.use(markedOptions);
diff --git a/frontend/scripts/watch-storybook.js b/frontend/scripts/watch-storybook.js
index 36592145e1..d2bef90884 100644
--- a/frontend/scripts/watch-storybook.js
+++ b/frontend/scripts/watch-storybook.js
@@ -79,15 +79,11 @@ h.watch("translations", null, async function (path) {
});
log.info("watch: assets (~)");
-h.watch(
- ["resources/images", "resources/fonts"],
- null,
- async function (path) {
- log.info("changed:", path);
- await h.compileSvgSprites();
- await h.copyAssets();
- await h.compileTemplates();
- },
-);
+h.watch(["resources/images", "resources/fonts"], null, async function (path) {
+ log.info("changed:", path);
+ await h.compileSvgSprites();
+ await h.copyAssets();
+ await h.compileTemplates();
+});
worker.terminate();
diff --git a/frontend/scripts/watch.js b/frontend/scripts/watch.js
index eddd3df6d4..eeeb06eec9 100644
--- a/frontend/scripts/watch.js
+++ b/frontend/scripts/watch.js
@@ -78,16 +78,12 @@ h.watch("translations", null, async function (path) {
});
log.info("watch: assets (~)");
-h.watch(
- ["resources/images", "resources/fonts"],
- null,
- async function (path) {
- log.info("changed:", path);
- await h.compileSvgSprites();
- await h.copyAssets();
- await h.compileTemplates();
- },
-);
+h.watch(["resources/images", "resources/fonts"], null, async function (path) {
+ log.info("changed:", path);
+ await h.compileSvgSprites();
+ await h.copyAssets();
+ await h.compileTemplates();
+});
log.info("watch: wasm playground (~)");
h.watch(["resources/wasm-playground"], null, async function (path) {
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/controls/numeric_input.stories.jsx b/frontend/src/app/main/ui/ds/controls/numeric_input.stories.jsx
index 8cbf44a481..f3d2d20c8c 100644
--- a/frontend/src/app/main/ui/ds/controls/numeric_input.stories.jsx
+++ b/frontend/src/app/main/ui/ds/controls/numeric_input.stories.jsx
@@ -44,7 +44,7 @@ export default {
property: "search",
},
parameters: {
- controls: { exclude: [ "tokens" ] },
+ controls: { exclude: ["tokens"] },
},
render: ({ ...args }) => ,
};
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/ds/product/avatar.stories.jsx b/frontend/src/app/main/ui/ds/product/avatar.stories.jsx
index 6bbecd3448..9041a4a5ea 100644
--- a/frontend/src/app/main/ui/ds/product/avatar.stories.jsx
+++ b/frontend/src/app/main/ui/ds/product/avatar.stories.jsx
@@ -27,17 +27,17 @@ export default {
variant: "S",
selected: false,
},
- render: ({name, url, ...args }) => {
+ render: ({ name, url, ...args }) => {
const profile = {
id: "00000000-0000-0000-0000-000000000000",
- fullname: name
+ fullname: name,
};
if (url) {
profile.photoUrl = url;
- };
+ }
return ;
- }
+ },
};
export const Default = {};
diff --git a/frontend/src/app/main/ui/ds/product/milestone.stories.jsx b/frontend/src/app/main/ui/ds/product/milestone.stories.jsx
index 1af1762f26..85d637236b 100644
--- a/frontend/src/app/main/ui/ds/product/milestone.stories.jsx
+++ b/frontend/src/app/main/ui/ds/product/milestone.stories.jsx
@@ -38,10 +38,16 @@ export default {
active: false,
editing: false,
},
- render: ({ profileName, profileAvatar, profileColor, createdAt, ...args }) => {
+ render: ({
+ profileName,
+ profileAvatar,
+ profileColor,
+ createdAt,
+ ...args
+ }) => {
const profile = {
id: "00000000-0000-0000-0000-000000000000",
- fullname: profileName
+ fullname: profileName,
};
if (profileAvatar) {
diff --git a/frontend/src/app/main/ui/ds/product/milestone_group.stories.jsx b/frontend/src/app/main/ui/ds/product/milestone_group.stories.jsx
index 293ad54528..f43162967c 100644
--- a/frontend/src/app/main/ui/ds/product/milestone_group.stories.jsx
+++ b/frontend/src/app/main/ui/ds/product/milestone_group.stories.jsx
@@ -27,7 +27,7 @@ export default {
args: {
label: "Milestone 1",
active: false,
- snapshots: [1737452413841, 1737452422063, 1737452431603]
+ snapshots: [1737452413841, 1737452422063, 1737452431603],
},
render: ({ ...args }) => {
return ;
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..37510ba166 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,60 +38,101 @@
[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)
modifiers (mf/deref refs/workspace-modifiers)
modifiers (dm/get-in modifiers [shape-id :modifiers])
- shape (gsh/transform-shape shape modifiers)]
+ shape (gsh/transform-shape shape modifiers)
+ props (mf/spread-props props {:shape shape})]
- [:*
- (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 +145,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 +219,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/border_radius.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/border_radius.cljs
index 3901b56866..f41eba18f6 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/border_radius.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/border_radius.cljs
@@ -14,23 +14,39 @@
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
-(defn all-equal?
+(defn- all-equal?
[shape]
(= (:r1 shape) (:r2 shape) (:r3 shape) (:r4 shape)))
+(defn- check-border-radius-menu-props
+ [old-props new-props]
+ (let [old-values (unchecked-get old-props "values")
+ new-values (unchecked-get new-props "values")]
+ (and (identical? (unchecked-get old-props "class")
+ (unchecked-get new-props "class"))
+ (identical? (unchecked-get old-props "ids")
+ (unchecked-get new-props "ids"))
+ (identical? (get old-values :r1)
+ (get new-values :r1))
+ (identical? (get old-values :r2)
+ (get new-values :r2))
+ (identical? (get old-values :r3)
+ (get new-values :r3))
+ (identical? (get old-values :r4)
+ (get new-values :r4)))))
+
(mf/defc border-radius-menu*
- {::mf/props :obj
- ::mf/wrap [mf/memo]}
- [{:keys [class ids ids-with-children values]}]
+ {::mf/wrap [#(mf/memo' % check-border-radius-menu-props)]}
+ [{:keys [class ids values]}]
(let [all-equal? (all-equal? values)
radius-expanded* (mf/use-state false)
radius-expanded (deref radius-expanded*)
change-radius
(mf/use-fn
- (mf/deps ids-with-children)
+ (mf/deps ids)
(fn [update-fn]
- (dwsh/update-shapes ids-with-children
+ (dwsh/update-shapes ids
(fn [shape]
(if (ctsr/has-radius? shape)
(update-fn shape)
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/layer.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
index 4af557679b..f5c7eaf14f 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
@@ -9,7 +9,6 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
- [app.main.data.helpers :as dsh]
[app.main.data.workspace :as dw]
[app.main.data.workspace.shapes :as dwsh]
[app.main.features :as features]
@@ -24,7 +23,7 @@
(def layer-attrs
[:opacity :blend-mode :blocked :hidden])
-(defn opacity->string
+(defn- opacity->string
[opacity]
(if (not= opacity :multiple)
(dm/str (-> opacity
@@ -32,61 +31,70 @@
(* 100)))
:multiple))
-(mf/defc layer-menu
- {::mf/wrap-props false}
- [props]
- (let [ids (unchecked-get props "ids")
- values (unchecked-get props "values")
+(defn- on-change
+ [ids prop value]
+ (st/emit! (dwsh/update-shapes ids #(assoc % prop value))))
- hidden? (:hidden values)
- blocked? (:blocked values)
+(defn- check-layer-menu-props
+ [old-props new-props]
+ (let [old-values (unchecked-get old-props "values")
+ new-values (unchecked-get new-props "values")]
+ (and (identical? (unchecked-get old-props "class")
+ (unchecked-get new-props "class"))
+ (identical? (unchecked-get old-props "ids")
+ (unchecked-get new-props "ids"))
+ (identical? (get old-values :opacity)
+ (get new-values :opacity))
+ (identical? (get old-values :blend-mode)
+ (get new-values :blend-mode))
+ (identical? (get old-values :blocked)
+ (get new-values :blocked))
+ (identical? (get old-values :hidden)
+ (get new-values :hidden)))))
- current-blend-mode (or (:blend-mode values) :normal)
- current-opacity (opacity->string (:opacity values))
+(mf/defc layer-menu*
+ {::mf/wrap [#(mf/memo' % check-layer-menu-props)]}
+ [{:keys [ids values]}]
+ (let [hidden? (get values :hidden)
+ blocked? (get values :blocked)
- state* (mf/use-state
- {:selected-blend-mode current-blend-mode
- :option-highlighted? false
- :preview-complete? true})
+ current-blend-mode (or (get values :blend-mode) :normal)
+ current-opacity (opacity->string (:opacity values))
- state (deref state*)
- selected-blend-mode (get state :selected-blend-mode)
- option-highlighted? (get state :option-highlighted?)
- preview-complete? (get state :preview-complete?)
- wasm-renderer-enabled? (features/use-feature "render-wasm/v1")
+ state* (mf/use-state
+ #(do {:selected-blend-mode current-blend-mode
+ :option-highlighted? false
+ :preview-complete? true}))
- shapes (->
- (dsh/lookup-page-objects @st/state)
- (select-keys ids)
- vals)
+ state (deref state*)
+ selected-blend-mode (get state :selected-blend-mode)
+ option-highlighted? (get state :option-highlighted?)
+ preview-complete? (get state :preview-complete?)
- on-change
- (mf/use-fn
- (mf/deps ids)
- (fn [prop value]
- (st/emit! (dwsh/update-shapes ids #(assoc % prop value)))))
+ wasm-renderer-enabled?
+ (features/use-feature "render-wasm/v1")
handle-change-blend-mode
(mf/use-fn
- (mf/deps on-change)
+ (mf/deps ids)
(fn [value]
(swap! state* assoc
:selected-blend-mode value
:option-highlighted? false
:preview-complete? true)
(st/emit! (dw/unset-preview-blend-mode ids))
- (on-change :blend-mode value)))
+ (on-change ids :blend-mode value)))
handle-blend-mode-enter
(mf/use-fn
- (mf/deps on-change current-blend-mode)
+ (mf/deps ids current-blend-mode)
(fn [value]
(swap! state* assoc
:preview-complete? false
:option-highlighted? true)
(when wasm-renderer-enabled?
- (doseq [shape shapes]
+ (doseq [shape ids]
(wasm.api/use-shape (:id shape))
(wasm.api/set-shape-blend-mode value)))
@@ -95,46 +103,46 @@
handle-blend-mode-leave
(mf/use-fn
- (mf/deps on-change selected-blend-mode)
+ (mf/deps ids)
(fn [_value]
(swap! state* assoc :preview-complete? true)
(st/emit! (dw/unset-preview-blend-mode ids))))
handle-opacity-change
(mf/use-fn
- (mf/deps on-change ids)
+ (mf/deps ids)
(fn [value]
(st/emit! (dw/trigger-bounding-box-cloaking ids))
(let [value (/ value 100)]
- (on-change :opacity value))))
+ (on-change ids :opacity value))))
handle-set-hidden
(mf/use-fn
- (mf/deps on-change ids)
+ (mf/deps ids)
(fn [_]
(st/emit! (dw/trigger-bounding-box-cloaking ids))
- (on-change :hidden true)))
+ (on-change ids :hidden true)))
handle-set-visible
(mf/use-fn
- (mf/deps on-change ids)
+ (mf/deps ids)
(fn [_]
(st/emit! (dw/trigger-bounding-box-cloaking ids))
- (on-change :hidden false)))
+ (on-change ids :hidden false)))
handle-set-blocked
(mf/use-fn
- (mf/deps on-change ids)
+ (mf/deps ids)
(fn [_]
(st/emit! (dw/trigger-bounding-box-cloaking ids))
- (on-change :blocked true)))
+ (on-change ids :blocked true)))
handle-set-unblocked
(mf/use-fn
- (mf/deps on-change ids)
+ (mf/deps ids)
(fn [_]
(st/emit! (dw/trigger-bounding-box-cloaking ids))
- (on-change :blocked false)))
+ (on-change ids :blocked false)))
options
(mf/with-memo [current-blend-mode]
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/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
index 5712d0977b..c93bf3320e 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
@@ -92,7 +92,7 @@
(mf/defc measures-menu*
{::mf/memo true}
- [{:keys [ids ids-with-children values type shapes]}]
+ [{:keys [ids values type shapes]}]
(let [all-types
(mf/with-memo [type shapes]
;; We only need this when multiple type is used
@@ -105,9 +105,6 @@
(into #{} xf:mapcat-type-to-options all-types)
(type->options type)))
- ids-with-children
- (d/nilv ids-with-children ids)
-
frames
(mf/with-memo [shapes]
(let [objects (deref refs/workspace-page-objects)]
@@ -466,7 +463,6 @@
(when (options :radius)
[:> border-radius-menu* {:class (stl/css :border-radius)
:ids ids
- :ids-with-children ids-with-children
:values values
:shape shape}])])
(when (or (options :clip-content) (options :show-in-viewer))
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..56fb1dbdb2 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,9 +11,10 @@
[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]]
+ [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-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-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])
@@ -78,9 +79,9 @@
(mf/deref parents-by-ids-ref)]
[:*
- [:& layer-menu {:ids ids
- :type type
- :values layer-values}]
+ [:> layer-menu* {:ids ids
+ :type type
+ :values layer-values}]
[:> measures-menu* {:ids ids
:type type
@@ -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..89e9ad4f8c 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,9 +11,10 @@
[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]]
+ [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-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-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])
@@ -78,9 +79,9 @@
parents
(mf/deref parents-by-ids-ref)]
[:*
- [:& layer-menu {:ids ids
- :type type
- :values layer-values}]
+ [:> layer-menu* {:ids ids
+ :type type
+ :values layer-values}]
[:> measures-menu* {:ids ids
:type type
@@ -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..b1bca96a00 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,10 +15,11 @@
[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]
- [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-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [select-measure-keys measures-menu*]]
@@ -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)
@@ -104,9 +99,9 @@
(when variants? (ctk/is-variant-container? shape))]
[:*
- [:& layer-menu {:ids ids
- :type shape-type
- :values layer-values}]
+ [:> layer-menu* {:ids ids
+ :type shape-type
+ :values layer-values}]
[:> measures-menu* {:ids ids
:values measure-values
:type shape-type
@@ -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..4ef159d6d5 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,9 +14,10 @@
[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]]
+ [app.main.ui.workspace.sidebar.options.menus.layer :refer [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-item :refer [layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measures-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)
@@ -105,7 +106,9 @@
(get-attrs shapes objects :layout-item)]
[:div {:class (stl/css :options)}
- [:& layer-menu {:type type :ids layer-ids :values layer-values}]
+ [:> layer-menu* {:type type
+ :ids layer-ids
+ :values layer-values}]
[:> measures-menu* {:type type
:ids measure-ids
:values measure-values
@@ -157,6 +160,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..b19523e0f7 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,9 +23,9 @@
[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.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-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [select-measure-keys measure-attrs measures-menu*]]
@@ -425,7 +425,9 @@
[:div {:class (stl/css :options)}
(when-not (empty? layer-ids)
- [:& layer-menu {:type type :ids layer-ids :values layer-values}])
+ [:> layer-menu* {:type type
+ :ids layer-ids
+ :values layer-values}])
(when-not (empty? measure-ids)
[:> measures-menu*
@@ -485,4 +487,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..bba37db31a 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,9 +11,10 @@
[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]]
+ [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-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-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)
@@ -85,9 +80,9 @@
(mf/deref parents-by-ids-ref)]
[:*
- [:& layer-menu {:ids ids
- :type type
- :values layer-values}]
+ [:> layer-menu* {:ids ids
+ :type type
+ :values layer-values}]
[:> measures-menu* {:ids ids
:type type
:values measure-values
@@ -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..da138d37a5 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,9 +11,10 @@
[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]]
+ [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-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-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])
@@ -79,9 +80,9 @@
(mf/deref parents-by-ids-ref)]
[:*
- [:& layer-menu {:ids ids
- :type type
- :values layer-values}]
+ [:> layer-menu* {:ids ids
+ :type type
+ :values layer-values}]
[:> measures-menu* {:ids ids
:type type
:values measure-values
@@ -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..26b1c88e4e 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,9 +16,10 @@
[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]]
+ [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-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-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])
@@ -125,9 +126,9 @@
:attrs txt/text-node-attrs}))]
[:*
- [:& layer-menu {:ids ids
- :type type
- :values layer-values}]
+ [:> layer-menu* {:ids ids
+ :type type
+ :values layer-values}]
[:> measures-menu*
{:ids ids
:type type
@@ -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