diff --git a/CHANGES.md b/CHANGES.md index 6cccf6913b..dacdaeab9c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,7 @@ - Fix scroll on library modal [Taiga #13639](https://tree.taiga.io/project/penpot/issue/13639) - Fix dates to avoid show them in english when browser is in auto [Taiga #13786](https://tree.taiga.io/project/penpot/issue/13786) - Fix focus radio button [Taiga #13841](https://tree.taiga.io/project/penpot/issue/13841) +- Token tree should be expanded by default [Taiga #13631](https://tree.taiga.io/project/penpot/issue/13631) ## 2.15.0 (Unreleased) diff --git a/common/src/app/common/path_names.cljc b/common/src/app/common/path_names.cljc index 00038cdf6c..658ffe0349 100644 --- a/common/src/app/common/path_names.cljc +++ b/common/src/app/common/path_names.cljc @@ -148,16 +148,16 @@ Some naming conventions: :path 'one' :depth 0 :leaf nil - :children-fn (fn [] [{:name 'two' - :path 'one.two' - :depth 1 - :leaf nil - :children-fn (fn [] [{... :name 'three'} {... :name 'four'}])} - {:name 'five' - :path 'one.five' - :depth 1 - :leaf {... :name 'five'} - ...}])}]" + :children [{:name 'two' + :path 'one.two' + :depth 1 + :leaf nil + :children [{... :name 'three'} {... :name 'four'}]} + {:name 'five' + :path 'one.five' + :depth 1 + :leaf {... :name 'five'} + :children nil}]}]" (defn- sort-by-children "Sorts segments so that those with children come first." @@ -191,7 +191,7 @@ Some naming conventions: (into (sorted-map) grouped))) (defn- build-tree-node - "Builds a single tree node with lazy children." + "Builds a single tree node with computed children." [segment-name remaining-segments separator parent-path depth] (let [current-path (if parent-path (str parent-path "." segment-name) @@ -208,12 +208,11 @@ Some naming conventions: :path current-path :depth depth :leaf leaf-segment - :children-fn (when-not is-leaf? - (fn [] - (let [grouped-elements (sort-and-group-segments remaining-segments separator)] - (mapv (fn [[child-segment-name remaining-child-segments]] - (build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth))) - grouped-elements))))}] + :children (when-not is-leaf? + (let [grouped-elements (sort-and-group-segments remaining-segments separator)] + (mapv (fn [[child-segment-name remaining-child-segments]] + (build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth))) + grouped-elements)))}] node)) (defn build-tree-root diff --git a/frontend/playwright/ui/specs/tokens/apply.spec.js b/frontend/playwright/ui/specs/tokens/apply.spec.js index 69ed14f051..bda71e08ba 100644 --- a/frontend/playwright/ui/specs/tokens/apply.spec.js +++ b/frontend/playwright/ui/specs/tokens/apply.spec.js @@ -4,7 +4,7 @@ import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage"; import { setupTokensFileRender, setupTypographyTokensFileRender, - unfoldTokenTree, + unfoldTokenType, } from "./helpers"; test.beforeEach(async ({ page }) => { @@ -24,10 +24,9 @@ test.describe("Tokens: Apply token", () => { .filter({ hasText: "Button" }) .click(); - const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); - await tokensTabButton.click(); + await page.getByRole("tab", { name: "Tokens" }).click(); - unfoldTokenTree(tokensSidebar, "color", "colors.black"); + await unfoldTokenType(tokensSidebar, "color"); await tokensSidebar .getByRole("button", { name: "black" }) @@ -52,17 +51,15 @@ test.describe("Tokens: Apply token", () => { await workspacePage.layers.getByTestId("layer-row").nth(1).click(); // Open tokens sections on left sidebar - const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); - await tokensTabButton.click(); - // Unfold border radius tokens - await page.getByRole("button", { name: "Border Radius 3" }).click(); + await page.getByRole("tab", { name: "Tokens" }).click(); + + await unfoldTokenType(tokensSidebar, "border radius"); await expect( - tokensSidebar.getByRole("button", { name: "borderRadius" }), - ).toBeVisible(); - await tokensSidebar.getByRole("button", { name: "borderRadius" }).click(); - await expect( - tokensSidebar.getByRole("button", { name: "borderRadius.sm" }), + tokensSidebar.getByRole("button", { + name: "borderRadius.sm", + exact: true, + }), ).toBeVisible(); // Apply border radius token from token panels @@ -119,13 +116,7 @@ test.describe("Tokens: Apply token", () => { await tokensTabButton.click(); // Unfold opacity tokens - await page.getByRole("button", { name: "Opacity 3" }).click(); - await expect( - tokensSidebar.getByRole("button", { name: "opacity", exact: true }), - ).toBeVisible(); - await tokensSidebar - .getByRole("button", { name: "opacity", exact: true }) - .click(); + await unfoldTokenType(tokensSidebar, "opacity"); await expect( tokensSidebar.getByRole("button", { name: "opacity.high" }), ).toBeVisible(); @@ -203,12 +194,8 @@ test.describe("Tokens: Apply token", () => { test("User adds shadow token with multiple shadows and applies it to shape", async ({ page, }) => { - const { - tokensUpdateCreateModal, - tokensSidebar, - workspacePage, - tokenContextMenuForToken, - } = await setupTokensFileRender(page, { flags: ["enable-token-shadow"] }); + const { tokensUpdateCreateModal, tokensSidebar, workspacePage } = + await setupTokensFileRender(page, { flags: ["enable-token-shadow"] }); const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); @@ -476,8 +463,6 @@ test.describe("Tokens: Apply token", () => { await submitButton.click(); await expect(tokensUpdateCreateModal).not.toBeVisible(); - unfoldTokenTree(tokensSidebar, "shadow", "primary"); - // Verify token appears in sidebar const shadowToken = tokensSidebar.getByRole("button", { name: "primary", @@ -512,7 +497,7 @@ test.describe("Tokens: Apply token", () => { const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); await tokensTabButton.click(); - unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm"); + await unfoldTokenType(tokensSidebar, "dimensions"); // Apply token to width and height token from token panel await tokensSidebar.getByRole("button", { name: "dimension.sm" }).click(); @@ -565,7 +550,7 @@ test.describe("Tokens: Apply token", () => { const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); await tokensTabButton.click(); - unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm"); + await unfoldTokenType(tokensSidebar, "dimensions"); // Apply token to width and height token from token panel await tokensSidebar @@ -621,7 +606,7 @@ test.describe("Tokens: Apply token", () => { const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); await tokensTabButton.click(); - unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm"); + await unfoldTokenType(tokensSidebar, "dimensions"); // Apply token to width and height token from token panel await tokensSidebar @@ -677,7 +662,7 @@ test.describe("Tokens: Apply token", () => { const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); await tokensTabButton.click(); - unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.xs"); + await unfoldTokenType(tokensSidebar, "dimensions"); // Apply token to width and height token from token panel await tokensSidebar @@ -809,8 +794,7 @@ test.describe("Tokens: Apply token", () => { const tokensTab = page.getByRole("tab", { name: "Tokens" }); await expect(tokensTab).toBeVisible(); await tokensTab.click(); - await page.getByRole("button", { name: "Dimensions 4" }).click(); - await page.getByRole("button", { name: "dim", exact: true }).click(); + await unfoldTokenType(workspace.tokensSidebar, "dimensions"); const tokensSidebar = workspace.tokensSidebar; await expect( tokensSidebar.getByRole("button", { name: "dim.md" }), @@ -881,11 +865,7 @@ test.describe("Tokens: Detach token", () => { await tokensTabButton.click(); // Unfold border radius tokens - await page.getByRole("button", { name: "Border Radius 3" }).click(); - await expect( - tokensSidebar.getByRole("button", { name: "borderRadius" }), - ).toBeVisible(); - await tokensSidebar.getByRole("button", { name: "borderRadius" }).click(); + await unfoldTokenType(tokensSidebar, "Border Radius"); await expect( tokensSidebar.getByRole("button", { name: "borderRadius.sm" }), ).toBeVisible(); diff --git a/frontend/playwright/ui/specs/tokens/crud.spec.js b/frontend/playwright/ui/specs/tokens/crud.spec.js index 153f1dc25d..58e67be6e5 100644 --- a/frontend/playwright/ui/specs/tokens/crud.spec.js +++ b/frontend/playwright/ui/specs/tokens/crud.spec.js @@ -6,7 +6,7 @@ import { setupTokensFileRender, setupTypographyTokensFileRender, testTokenCreationFlow, - unfoldTokenTree, + unfoldTokenType, } from "./helpers"; test.beforeEach(async ({ page }) => { @@ -31,15 +31,9 @@ test.describe("Tokens - creation", () => { }); test("User creates border radius token with combobox", async ({ page }) => { - const invalidValueError = "Invalid token value"; - const emptyNameError = "Name should be at least 1 character"; - const selfReferenceError = "Token has self reference"; - const missingReferenceError = "Missing token references"; - - const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = - await setupEmptyTokensFileRender(page, { - flags: ["enable-token-combobox", "enable-feature-token-input"], - }); + const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page, { + flags: ["enable-token-combobox", "enable-feature-token-input"], + }); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); @@ -83,8 +77,10 @@ test.describe("Tokens - creation", () => { await submitButton.click(); + await unfoldTokenType(tokensTabPanel, "border radius"); + await expect( - tokensTabPanel.getByRole('button', { name: 'my-token' }), + tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // Create second token referencing the first one using the combobox options @@ -310,7 +306,7 @@ test.describe("Tokens - creation", () => { await expect(submitButton).toBeEnabled(); await submitButton.click(); - await unfoldTokenTree(tokensSidebar, "color", "color.primary"); + await unfoldTokenType(tokensSidebar, "color"); // Create token referencing the previous one with keyboard @@ -477,6 +473,8 @@ test.describe("Tokens - creation", () => { await submitButton.click(); + await unfoldTokenType(tokensTabPanel, "font family"); + await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); @@ -631,6 +629,8 @@ test.describe("Tokens - creation", () => { await submitButton.click(); + await unfoldTokenType(tokensTabPanel, "font weight"); + await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); @@ -767,6 +767,8 @@ test.describe("Tokens - creation", () => { await submitButton.click(); + await unfoldTokenType(tokensTabPanel, "text case"); + await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); @@ -885,6 +887,8 @@ test.describe("Tokens - creation", () => { await submitButton.click(); + await unfoldTokenType(tokensTabPanel, "text decoration"); + await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); @@ -1051,6 +1055,8 @@ test.describe("Tokens - creation", () => { await expect(submitButton).toBeEnabled(); await submitButton.click(); + await unfoldTokenType(tokensTabPanel, "shadow"); + await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); @@ -1088,6 +1094,8 @@ test.describe("Tokens - creation", () => { await expect(submitButton).toBeEnabled(); await submitButton.click(); + + await unfoldTokenType(tokensTabPanel, "shadow"); await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).toBeEnabled(); @@ -1109,7 +1117,9 @@ test.describe("Tokens - creation", () => { const nameField = tokensUpdateCreateModal.getByLabel("Name"); await nameField.fill("typography.empty"); - const valueField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Size" }); + const valueField = tokensUpdateCreateModal.getByRole("textbox", { + name: "Font Size", + }); // Insert a value and then delete it await valueField.fill("1"); @@ -1274,6 +1284,8 @@ test.describe("Tokens - creation", () => { await expect(submitButton).toBeEnabled(); await submitButton.click(); + await unfoldTokenType(tokensTabPanel, "shadow"); + await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); @@ -1642,7 +1654,7 @@ test.describe("Tokens - creation", () => { await expect(submitButton).toBeEnabled(); await submitButton.click(); - await unfoldTokenTree(tokensSidebar, "color", "dark.primary"); + await unfoldTokenType(tokensSidebar, "color"); await expect(tokensSidebar.getByLabel("primary")).toBeEnabled(); }); @@ -1681,10 +1693,10 @@ test.describe("Tokens - creation", () => { await expect(tokensSidebar).toBeVisible(); - unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); + await unfoldTokenType(tokensSidebar, "color"); const colorToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); await colorToken.click({ button: "right" }); @@ -1724,7 +1736,7 @@ test("User creates grouped color token", async ({ page }) => { await expect(submitButton).toBeEnabled(); await submitButton.click(); - await unfoldTokenTree(tokensSidebar, "color", "dark.primary"); + await unfoldTokenType(tokensSidebar, "color"); await expect(tokensSidebar.getByLabel("primary")).toBeEnabled(); }); @@ -1761,10 +1773,10 @@ test("User duplicate color token", async ({ page }) => { await expect(tokensSidebar).toBeVisible(); - unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); + await unfoldTokenType(tokensSidebar, "color"); const colorToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); await colorToken.click({ button: "right" }); @@ -1809,7 +1821,9 @@ test.describe("Tokens tab - edition", () => { await fontFamilyField.fill("OneWord"); // Invalidate incorrect values for font size - const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Size" }); + const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", { + name: "Font Size", + }); await fontSizeField.fill("invalid"); await expect( tokensUpdateCreateModal.getByText(/Invalid token value:/), @@ -1824,13 +1838,21 @@ test.describe("Tokens tab - edition", () => { await fontSizeField.fill("16"); await expect(saveButton).toBeEnabled(); - const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Weight" }); - const letterSpacingField = - tokensUpdateCreateModal.getByRole("textbox", { name: "Letter Spacing" }); - const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Line Height" }); - const textCaseField = tokensUpdateCreateModal.getByRole("textbox", { name: "Text Case" }); - const textDecorationField = - tokensUpdateCreateModal.getByRole("textbox", { name: "Text Decoration" }); + const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", { + name: "Font Weight", + }); + const letterSpacingField = tokensUpdateCreateModal.getByRole("textbox", { + name: "Letter Spacing", + }); + const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", { + name: "Line Height", + }); + const textCaseField = tokensUpdateCreateModal.getByRole("textbox", { + name: "Text Case", + }); + const textDecorationField = tokensUpdateCreateModal.getByRole("textbox", { + name: "Text Decoration", + }); // Capture all values before switching tabs const originalValues = { @@ -1883,10 +1905,10 @@ test.describe("Tokens tab - edition", () => { await expect(tokensSidebar).toBeVisible(); - await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); + await unfoldTokenType(tokensSidebar, "color"); const colorToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); await expect(colorToken).toBeVisible(); @@ -1904,7 +1926,7 @@ test.describe("Tokens tab - edition", () => { await expect(tokensUpdateCreateModal).not.toBeVisible(); - await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100.changed"); + await unfoldTokenType(tokensSidebar, "color"); const colorTokenChanged = tokensSidebar.getByRole("button", { name: "changed", @@ -1975,10 +1997,10 @@ test.describe("Tokens tab - delete", () => { await expect(tokensSidebar).toBeVisible(); - unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); + await unfoldTokenType(tokensSidebar, "color"); const colorToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); await expect(colorToken).toBeVisible(); await colorToken.click({ button: "right" }); @@ -1996,7 +2018,7 @@ test.describe("Tokens tab - delete", () => { await expect(tokensSidebar).toBeVisible(); // Expand color tokens - unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); + await unfoldTokenType(tokensSidebar, "color"); // Verify that the node and child token are visible before deletion const colorNode = tokensSidebar.getByRole("button", { @@ -2004,7 +2026,7 @@ test.describe("Tokens tab - delete", () => { exact: true, }); const colorNodeToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); // Select a node and right click on it to open context menu diff --git a/frontend/playwright/ui/specs/tokens/helpers.js b/frontend/playwright/ui/specs/tokens/helpers.js index 8f8974e40f..657bce8b54 100644 --- a/frontend/playwright/ui/specs/tokens/helpers.js +++ b/frontend/playwright/ui/specs/tokens/helpers.js @@ -207,7 +207,7 @@ const testTokenCreationFlow = async ( const selfReferenceError = "Token has self reference"; const missingReferenceError = "Missing token references"; - const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = + const { tokensUpdateCreateModal, tokensSidebar } = await setupEmptyTokensFileRender(page); // Open modal @@ -313,12 +313,11 @@ const testTokenCreationFlow = async ( ).toBeEnabled(); }; -const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => { - const tokenSegments = tokenName.split("."); - const tokenFolderTree = tokenSegments.slice(0, -1); - const tokenLeafName = tokenSegments.pop(); - - const typeParentWrapper = tokensTabPanel.getByTestId(`section-${type}`); +const unfoldTokenType = async (tokensTabPanel, type) => { + const kebabClaseType = type.toLocaleLowerCase().replace(/\s/g, "-"); + const typeParentWrapper = tokensTabPanel.getByTestId( + `section-${kebabClaseType}`, + ); const typeSectionButton = typeParentWrapper .getByRole("button", { name: type, @@ -331,24 +330,6 @@ const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => { if (isSectionExpanded === "false") { await typeSectionButton.click(); } - - for (const segment of tokenFolderTree) { - const segmentButton = typeParentWrapper - .getByRole("listitem") - .getByRole("button", { name: segment }) - .first(); - - const isExpanded = await segmentButton.getAttribute("aria-expanded"); - if (isExpanded === "false") { - await segmentButton.click(); - } - } - - await expect( - typeParentWrapper.getByRole("button", { - name: tokenLeafName, - }), - ).toBeEnabled(); }; export { @@ -359,5 +340,5 @@ export { setupTypographyTokensFile, setupTypographyTokensFileRender, testTokenCreationFlow, - unfoldTokenTree, + unfoldTokenType, }; diff --git a/frontend/playwright/ui/specs/tokens/tree.spec.js b/frontend/playwright/ui/specs/tokens/tree.spec.js index ae43197acc..6228b52a2a 100644 --- a/frontend/playwright/ui/specs/tokens/tree.spec.js +++ b/frontend/playwright/ui/specs/tokens/tree.spec.js @@ -1,7 +1,7 @@ import { test, expect } from "@playwright/test"; import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage"; import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage"; -import { setupTokensFileRender, unfoldTokenTree } from "./helpers"; +import { setupTokensFileRender } from "./helpers"; test.beforeEach(async ({ page }) => { await WasmWorkspacePage.init(page); @@ -20,10 +20,8 @@ test.describe("Tokens - node tree", () => { await expect(tokensColorGroup).toBeVisible(); await tokensColorGroup.click(); - await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); - const colorToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); await expect(colorToken).toBeVisible(); await tokensColorGroup.click(); diff --git a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs index 22bd7789fb..2fe57d491f 100644 --- a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs +++ b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs @@ -11,7 +11,6 @@ [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] [app.common.logic.tokens :as clt] - [app.common.path-names :as cpn] [app.common.types.shape :as cts] [app.common.types.tokens-lib :as ctob] [app.common.uuid :as uuid] @@ -62,52 +61,77 @@ (watch [_ _ _] (rx/of (dwsh/update-shapes [id] #(merge % attrs))))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Toggle tree nodes +;; TOKENS TREE - Type folders ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- remove-paths-recursively +(defn open-token-type + ([types type] + (conj (or types #{}) type)) + ([type] + (ptk/reify ::open-token-type + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-tokens :unfolded-token-types] + #(open-token-type % type)))))) + +(defn close-token-type + ([types type] + (disj (or types #{}) type)) + ([type] + (ptk/reify ::close-token-type + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-tokens :unfolded-token-types] + #(close-token-type % type)))))) + +(defn toggle-token-type + [type] + (ptk/reify ::toggle-token-type + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-tokens :unfolded-token-types] + (fn [types] + (if (contains? (or types #{}) type) + (close-token-type types type) + (open-token-type types type))))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TOKENS TREE - Toggle tree nodes +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- remove-path [path paths] (->> paths - (remove #(str/starts-with? % (str path))) + (remove #(= % path)) vec)) (defn add-path [path paths] - (let [split-path (cpn/split-path path :separator ".") - partial-paths (->> split-path - (reduce - (fn [acc segment] - (let [new-acc (if (empty? acc) - segment - (str (last acc) "." segment))] - (conj acc new-acc))) - []))] - (->> paths - (into partial-paths) - distinct - vec))) + (vec (conj paths path))) (defn clear-tokens-paths [] (ptk/reify ::clear-tokens-paths ptk/UpdateEvent (update [_ state] - (assoc-in state [:workspace-tokens :unfolded-token-paths] [])))) + (assoc-in state [:workspace-tokens :folded-token-paths] [])))) (defn toggle-token-path [path] (ptk/reify ::toggle-token-path ptk/UpdateEvent (update [_ state] - (update-in state [:workspace-tokens :unfolded-token-paths] + (update-in state [:workspace-tokens :folded-token-paths] (fn [paths] (let [paths (or paths [])] (if (some #(= % path) paths) - (remove-paths-recursively path paths) + (remove-path path paths) (add-path path paths)))))))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TOKENS Actions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/ui/workspace/tokens/management.cljs b/frontend/src/app/main/ui/workspace/tokens/management.cljs index 7e077cc1f4..f462b48e5e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management.cljs @@ -171,14 +171,11 @@ path (:name token) tokens-by-type (ctob/group-by-type selected-token-set-tokens) tokens-filtered-by-type (get tokens-by-type type) - tokens-in-path-ids (filter-tokens-by-path-ids type path) - remaining-tokens? (remaining-tokens-of-type-in-set? tokens-filtered-by-type tokens-in-path-ids)] - ;; Delete the token + remaining-tokens? (remaining-tokens-of-type-in-set? tokens-filtered-by-type [id])] (st/emit! (dwtl/delete-token selected-token-set-id id)) - ;; Remove from unfolded tree path (if remaining-tokens? (st/emit! (dwtl/toggle-token-path (str (name type) "." path))) - (st/emit! (dwtl/toggle-token-path (name type))))))) + (st/emit! (dwtl/close-token-type type)))))) delete-node (mf/with-memo [selected-token-set-tokens selected-token-set-id] @@ -193,7 +190,7 @@ ;; Remove from unfolded tree path (if remaining-tokens? (st/emit! (dwtl/toggle-token-path (str (name type) "." path))) - (st/emit! (dwtl/toggle-token-path (name type))))))) + (st/emit! (dwtl/close-token-type type)))))) bulk-rename-tokens-in-path ;; Rename tokens in bulk affected by a node rename. diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs index b968395e7c..a4cc813bbc 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.tokens.management.forms.generic-form (:require-macros [app.main.style :as stl]) (:require - [app.common.data :as d] [app.common.files.tokens :as cfo] [app.common.schema :as sm] [app.common.types.tokens-lib :as ctob] @@ -186,7 +185,6 @@ (mf/deps validate-token token tokens token-type value-subfield value-type active-tab on-remap-token on-rename-token is-create) (fn [form _event] (let [name (get-in @form [:clean-data :name]) - path (str (d/name token-type) "." name) description (get-in @form [:clean-data :description]) value (get-in @form [:clean-data :value]) value-for-validation (get-value-for-validator active-tab value value-subfield value-type)] @@ -221,7 +219,7 @@ {:name name :value (:value valid-token) :description description})) - (dwtl/toggle-token-path path) + (dwtl/open-token-type (:type token)) (dwtp/propagate-workspace-tokens) (modal/hide!))))) ;; WORKAROUND: display validation errors in the form instead of crashing diff --git a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs index 19c3636e28..83228ecaac 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs @@ -27,8 +27,11 @@ [okulary.core :as l] [rumext.v2 :as mf])) -(def ref:unfolded-token-paths - (l/derived (l/key :unfolded-token-paths) refs/workspace-tokens)) +(def ref:folded-token-paths + (l/derived (l/key :folded-token-paths) refs/workspace-tokens)) + +(def ref:unfolded-token-types + (l/derived (l/key :unfolded-token-types) refs/workspace-tokens)) (defn token-section-icon [type] @@ -72,8 +75,10 @@ (let [{:keys [modal title]} (get dwta/token-properties type) - unfolded-token-paths (mf/deref ref:unfolded-token-paths) - is-type-unfolded (contains? (set unfolded-token-paths) (name type)) + folded-token-paths (mf/deref ref:folded-token-paths) + unfolded-token-types (mf/deref ref:unfolded-token-types) + + is-type-unfolded (contains? (set unfolded-token-types) type) editing-ref (mf/deref refs/workspace-editor-state) edition (mf/deref refs/selected-edition) @@ -117,7 +122,7 @@ (mf/deps type expandable?) (fn [] (when expandable? - (st/emit! (dwtl/toggle-token-path (name type)))))) + (st/emit! (dwtl/toggle-token-type type))))) on-popover-open-click (mf/use-fn @@ -172,13 +177,12 @@ (when is-type-unfolded [:> token-tree* {:tokens tokens :type type - :id (dm/str "token-tree-" (name type)) - :tokens-lib tokens-lib - :unfolded-token-paths unfolded-token-paths + :folded-token-paths folded-token-paths :selected-shapes selected-shapes + :is-selected-inside-layout is-selected-inside-layout :active-theme-tokens active-theme-tokens :selected-token-set-id selected-token-set-id - :is-selected-inside-layout is-selected-inside-layout + :tokens-lib tokens-lib :on-token-pill-click on-token-pill-click :on-pill-context-menu on-pill-context-menu :on-node-context-menu on-node-context-menu}])])) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs b/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs index 3fdc067bd0..b27db0bc85 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs @@ -21,7 +21,7 @@ [:map [:node :any] [:type :keyword] - [:unfolded-token-paths {:optional true} [:vector :string]] + [:folded-token-paths {:optional true} [:maybe [:vector :string]]] [:selected-shapes :any] [:is-selected-inside-layout {:optional true} :boolean] [:active-theme-tokens {:optional true} :any] @@ -35,7 +35,7 @@ {::mf/schema schema:folder-node} [{:keys [node type - unfolded-token-paths + folded-token-paths selected-shapes is-selected-inside-layout active-theme-tokens @@ -45,12 +45,11 @@ on-pill-context-menu on-node-context-menu]}] (let [full-path (str (name type) "." (:path node)) - is-folder-expanded (contains? (set (or unfolded-token-paths [])) full-path) + is-folder-expanded (not (contains? (set (or folded-token-paths [])) full-path)) swap-folder-expanded (mf/use-fn - (mf/deps (:path node) type) + (mf/deps full-path) (fn [] - (let [path (str (name type) "." (:path node))] - (st/emit! (dwtl/toggle-token-path path))))) + (st/emit! (dwtl/toggle-token-path full-path)))) node-context-menu-prep (mf/use-fn (mf/deps on-node-context-menu node) @@ -66,18 +65,18 @@ :on-toggle-expand swap-folder-expanded :on-context-menu node-context-menu-prep}] (when is-folder-expanded - (let [children-fn (:children-fn node)] + (let [children (:children node)] [:div {:class (stl/css :folder-children-wrapper) :id (str "folder-children-" (:path node))} - (when children-fn - (let [sorted-children (d/natural-sort-by :name (children-fn))] + (when (seq children) + (let [sorted-children (d/natural-sort-by :name children)] (for [child sorted-children] (if (not (:leaf child)) [:ul {:class (stl/css :node-parent) :key (:path child)} [:> folder-node* {:type type :node child - :unfolded-token-paths unfolded-token-paths + :folded-token-paths folded-token-paths :selected-shapes selected-shapes :is-selected-inside-layout is-selected-inside-layout :active-theme-tokens active-theme-tokens @@ -101,12 +100,12 @@ [:map [:tokens :any] [:type :keyword] - [:unfolded-token-paths {:optional true} [:vector :string]] + [:folded-token-paths {:optional true} [:maybe [:vector :string]]] [:selected-shapes :any] [:is-selected-inside-layout {:optional true} :boolean] [:active-theme-tokens {:optional true} :any] - [:selected-token-set-id {:optional true} :any] [:tokens-lib {:optional true} :any] + [:selected-token-set-id {:optional true} :any] [:on-token-pill-click {:optional true} fn?] [:on-pill-context-menu {:optional true} fn?] [:on-node-context-menu {:optional true} fn?]]) @@ -115,7 +114,7 @@ {::mf/schema schema:token-tree} [{:keys [tokens type - unfolded-token-paths + folded-token-paths selected-shapes is-selected-inside-layout active-theme-tokens @@ -153,7 +152,7 @@ :key (:path node)} [:> folder-node* {:node node :type type - :unfolded-token-paths unfolded-token-paths + :folded-token-paths folded-token-paths :selected-shapes selected-shapes :is-selected-inside-layout is-selected-inside-layout :active-theme-tokens active-theme-tokens