mirror of https://github.com/penpot/penpot.git
✨ Add ability to remap tokens when renamed ones are referenced by other child tokens (#8035)
* 🎉 Add ability to remap tokens when renamed ones are referenced by other child tokens Signed-off-by: Akshay Gupta <gravity.akshay@gmail.com> * 🐛 Fix remap skipping tokens with same name in different sets * 📚 Update CHANGES.md * 🔧 Fix css styles --------- Signed-off-by: Akshay Gupta <gravity.akshay@gmail.com> Co-authored-by: Akshay Gupta <gravity.akshay@gmail.com>
This commit is contained in:
parent
795f65632a
commit
2ad42cfd9b
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
### :sparkles: New features & Enhancements
|
### :sparkles: New features & Enhancements
|
||||||
|
|
||||||
|
- Remap references when renaming tokens [Taiga #10202](https://tree.taiga.io/project/penpot/us/10202)
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,18 @@
|
||||||
self-reference? (get token-references token-name)]
|
self-reference? (get token-references token-name)]
|
||||||
self-reference?))
|
self-reference?))
|
||||||
|
|
||||||
|
(defn references-token?
|
||||||
|
"Recursively check if a value references the token name. Handles strings, maps, and sequences."
|
||||||
|
[value token-name]
|
||||||
|
(cond
|
||||||
|
(string? value)
|
||||||
|
(boolean (some #(= % token-name) (find-token-value-references value)))
|
||||||
|
(map? value)
|
||||||
|
(some true? (map #(references-token? % token-name) (vals value)))
|
||||||
|
(sequential? value)
|
||||||
|
(some true? (map #(references-token? % token-name) value))
|
||||||
|
:else false))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; SCHEMA
|
;; SCHEMA
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
@ -558,3 +570,18 @@
|
||||||
"Predicate if a shadow composite token is a reference value - a string pointing to another reference token."
|
"Predicate if a shadow composite token is a reference value - a string pointing to another reference token."
|
||||||
[token-value]
|
[token-value]
|
||||||
(string? token-value))
|
(string? token-value))
|
||||||
|
|
||||||
|
(defn update-token-value-references
|
||||||
|
"Recursively update token references within a token value, supporting complex token values (maps, sequences, strings)."
|
||||||
|
[value old-name new-name]
|
||||||
|
(cond
|
||||||
|
(string? value)
|
||||||
|
(str/replace value
|
||||||
|
(re-pattern (str "\\{" (str/replace old-name "." "\\.") "\\}"))
|
||||||
|
(str "{" new-name "}"))
|
||||||
|
(map? value)
|
||||||
|
(d/update-vals value #(update-token-value-references % old-name new-name))
|
||||||
|
(sequential? value)
|
||||||
|
(mapv #(update-token-value-references % old-name new-name) value)
|
||||||
|
:else
|
||||||
|
value))
|
||||||
|
|
|
||||||
|
|
@ -909,7 +909,8 @@ Will return a value that matches this schema:
|
||||||
`:all` All of the nested sets are active
|
`:all` All of the nested sets are active
|
||||||
`:partial` Mixed active state of nested sets")
|
`:partial` Mixed active state of nested sets")
|
||||||
(get-tokens-in-active-sets [_] "set of set names that are active in the the active themes")
|
(get-tokens-in-active-sets [_] "set of set names that are active in the the active themes")
|
||||||
(get-all-tokens [_] "all tokens in the lib")
|
(get-all-tokens [_] "all tokens in the lib, as a sequence")
|
||||||
|
(get-all-tokens-map [_] "all tokens in the lib, as a map name -> token")
|
||||||
(get-tokens [_ set-id] "return a map of tokens in the set, indexed by token-name"))
|
(get-tokens [_ set-id] "return a map of tokens in the set, indexed by token-name"))
|
||||||
|
|
||||||
(declare parse-multi-set-dtcg-json)
|
(declare parse-multi-set-dtcg-json)
|
||||||
|
|
@ -1306,6 +1307,10 @@ Will return a value that matches this schema:
|
||||||
tokens))
|
tokens))
|
||||||
|
|
||||||
(get-all-tokens [this]
|
(get-all-tokens [this]
|
||||||
|
(mapcat #(vals (get-tokens- %))
|
||||||
|
(get-sets this)))
|
||||||
|
|
||||||
|
(get-all-tokens-map [this]
|
||||||
(reduce
|
(reduce
|
||||||
(fn [tokens' set]
|
(fn [tokens' set]
|
||||||
(into tokens' (map (fn [x] [(:name x) x]) (vals (get-tokens- set)))))
|
(into tokens' (map (fn [x] [(:name x) x]) (vals (get-tokens- set)))))
|
||||||
|
|
|
||||||
|
|
@ -2740,3 +2740,639 @@ test.describe("Tokens: Apply token", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe("Tokens: Remapping Feature", () => {
|
||||||
|
test.describe("Box Shadow Token Remapping", () => {
|
||||||
|
test("User renames box shadow token with alias references", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
tokensUpdateCreateModal,
|
||||||
|
tokensSidebar,
|
||||||
|
tokenContextMenuForToken,
|
||||||
|
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
|
||||||
|
|
||||||
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
|
|
||||||
|
// Create base shadow token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Shadow" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("base-shadow");
|
||||||
|
|
||||||
|
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||||
|
name: "Color",
|
||||||
|
});
|
||||||
|
await colorField.fill("#000000");
|
||||||
|
|
||||||
|
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Create derived shadow token that references base-shadow
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Shadow" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("derived-shadow");
|
||||||
|
|
||||||
|
const referenceToggle =
|
||||||
|
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||||
|
await referenceToggle.click();
|
||||||
|
|
||||||
|
const referenceField = tokensUpdateCreateModal.getByLabel("Reference");
|
||||||
|
await referenceField.fill("{base-shadow}");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Rename base-shadow token
|
||||||
|
const baseToken = tokensSidebar.getByRole("button", {
|
||||||
|
name: "base-shadow",
|
||||||
|
});
|
||||||
|
await baseToken.click({ button: "right" });
|
||||||
|
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||||
|
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("foundation-shadow");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
|
||||||
|
// Check for remapping modal
|
||||||
|
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||||
|
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||||
|
await expect(remappingModal).toContainText("1");
|
||||||
|
|
||||||
|
const confirmButton = remappingModal.getByRole("button", {
|
||||||
|
name: /remap/i,
|
||||||
|
});
|
||||||
|
await confirmButton.click();
|
||||||
|
|
||||||
|
// Verify token was renamed
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "foundation-shadow" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "derived-shadow" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("User renames and updates shadow token - referenced token and applied shapes update", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
tokensUpdateCreateModal,
|
||||||
|
tokensSidebar,
|
||||||
|
tokenContextMenuForToken,
|
||||||
|
workspacePage,
|
||||||
|
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
|
||||||
|
|
||||||
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
|
|
||||||
|
// Create base shadow token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Shadow" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("primary-shadow");
|
||||||
|
|
||||||
|
let colorField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||||
|
name: "Color",
|
||||||
|
});
|
||||||
|
await colorField.fill("#000000");
|
||||||
|
|
||||||
|
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Create derived shadow token that references base
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Shadow" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("card-shadow");
|
||||||
|
|
||||||
|
const referenceToggle =
|
||||||
|
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||||
|
await referenceToggle.click();
|
||||||
|
|
||||||
|
const referenceField = tokensUpdateCreateModal.getByLabel("Reference");
|
||||||
|
await referenceField.fill("{primary-shadow}");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Apply the referenced token to a shape
|
||||||
|
await page.getByRole("tab", { name: "Layers" }).click();
|
||||||
|
await workspacePage.layers
|
||||||
|
.getByTestId("layer-row")
|
||||||
|
.filter({ hasText: "Button" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await page.getByRole("tab", { name: "Tokens" }).click();
|
||||||
|
const cardShadowToken = tokensSidebar.getByRole("button", {
|
||||||
|
name: "card-shadow",
|
||||||
|
});
|
||||||
|
await cardShadowToken.click();
|
||||||
|
|
||||||
|
// Rename and update value of base token
|
||||||
|
const primaryToken = tokensSidebar.getByRole("button", {
|
||||||
|
name: "primary-shadow",
|
||||||
|
});
|
||||||
|
await primaryToken.click({ button: "right" });
|
||||||
|
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||||
|
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("main-shadow");
|
||||||
|
|
||||||
|
// Update the color value
|
||||||
|
colorField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||||
|
name: "Color",
|
||||||
|
});
|
||||||
|
await colorField.fill("#FF0000");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
|
||||||
|
// Confirm remapping
|
||||||
|
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||||
|
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
|
const confirmButton = remappingModal.getByRole("button", {
|
||||||
|
name: /remap/i,
|
||||||
|
});
|
||||||
|
await confirmButton.click();
|
||||||
|
|
||||||
|
// Verify base token was renamed
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "main-shadow" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Verify referenced token still exists
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "card-shadow" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Verify the shape still has the token applied with the NEW name
|
||||||
|
await page.getByRole("tab", { name: "Layers" }).click();
|
||||||
|
await workspacePage.layers
|
||||||
|
.getByTestId("layer-row")
|
||||||
|
.filter({ hasText: "Button" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Verify the shape still has the shadow applied with the UPDATED color value
|
||||||
|
// Expand the shadow section to access the color field
|
||||||
|
const shadowSection = workspacePage.rightSidebar.getByText("Drop shadow");
|
||||||
|
await expect(shadowSection).toBeVisible();
|
||||||
|
|
||||||
|
// Click to expand the shadow options (the menu button)
|
||||||
|
const shadowMenuButton = workspacePage.rightSidebar
|
||||||
|
.getByRole("button", { name: "open more options" })
|
||||||
|
.first();
|
||||||
|
await shadowMenuButton.click();
|
||||||
|
// Wait for the advanced options to appear
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
// Verify the color value has updated from #000000 to #FF0000
|
||||||
|
// Find the color input - it should be a textbox with a 6-character hex value
|
||||||
|
// We look for all textboxes and find the one with a hex color pattern
|
||||||
|
const allInputs = await workspacePage.rightSidebar
|
||||||
|
.locator('input[type="text"]')
|
||||||
|
.all();
|
||||||
|
|
||||||
|
let colorInput = null;
|
||||||
|
for (const input of allInputs) {
|
||||||
|
const value = await input.inputValue().catch(() => '');
|
||||||
|
if (/^[A-Fa-f0-9]{6}$/.test(value)) {
|
||||||
|
colorInput = input;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(colorInput).not.toBeNull();
|
||||||
|
const colorValue = await colorInput.inputValue();
|
||||||
|
expect(colorValue.toUpperCase()).toBe("FF0000");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Typography Token Remapping", () => {
|
||||||
|
test("User renames typography token with alias references", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
tokensUpdateCreateModal,
|
||||||
|
tokensSidebar,
|
||||||
|
tokenContextMenuForToken,
|
||||||
|
} = await setupTypographyTokensFile(page);
|
||||||
|
|
||||||
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
|
|
||||||
|
// Create base typography token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Typography" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("base-text");
|
||||||
|
|
||||||
|
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||||
|
name: "Font size",
|
||||||
|
});
|
||||||
|
await fontSizeField.fill("16");
|
||||||
|
|
||||||
|
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Create derived typography token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Typography" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("body-text");
|
||||||
|
|
||||||
|
const referenceToggle =
|
||||||
|
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||||
|
await referenceToggle.click();
|
||||||
|
|
||||||
|
const referenceField = tokensUpdateCreateModal.getByLabel("Reference");
|
||||||
|
await referenceField.fill("{base-text}");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Rename base token
|
||||||
|
const baseToken = tokensSidebar.getByRole("button", {
|
||||||
|
name: "base-text",
|
||||||
|
});
|
||||||
|
await baseToken.click({ button: "right" });
|
||||||
|
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||||
|
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("default-text");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
|
||||||
|
// Check for remapping modal
|
||||||
|
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||||
|
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
|
const confirmButton = remappingModal.getByRole("button", {
|
||||||
|
name: /remap/i,
|
||||||
|
});
|
||||||
|
await confirmButton.click();
|
||||||
|
|
||||||
|
// Verify token was renamed
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "default-text" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "body-text" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("User renames and updates typography token - referenced token and applied shapes update", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
tokensUpdateCreateModal,
|
||||||
|
tokensSidebar,
|
||||||
|
tokenContextMenuForToken,
|
||||||
|
workspacePage,
|
||||||
|
} = await setupTypographyTokensFile(page);
|
||||||
|
|
||||||
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
|
|
||||||
|
// Create base typography token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Typography" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("body-style");
|
||||||
|
|
||||||
|
let fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||||
|
name: "Font size",
|
||||||
|
});
|
||||||
|
await fontSizeField.fill("16");
|
||||||
|
|
||||||
|
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Create derived typography token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Typography" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("paragraph-style");
|
||||||
|
|
||||||
|
const referenceToggle =
|
||||||
|
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||||
|
await referenceToggle.click();
|
||||||
|
|
||||||
|
const referenceField = tokensUpdateCreateModal.getByLabel("Reference");
|
||||||
|
await referenceField.fill("{body-style}");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Apply the referenced token to a text shape
|
||||||
|
await page.getByRole("tab", { name: "Layers" }).click();
|
||||||
|
await workspacePage.layers
|
||||||
|
.getByTestId("layer-row")
|
||||||
|
.filter({ hasText: "Some Text" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await page.getByRole("tab", { name: "Tokens" }).click();
|
||||||
|
const paragraphToken = tokensSidebar.getByRole("button", {
|
||||||
|
name: "paragraph-style",
|
||||||
|
});
|
||||||
|
await paragraphToken.click();
|
||||||
|
|
||||||
|
// Rename and update value of base token
|
||||||
|
const bodyToken = tokensSidebar.getByRole("button", {
|
||||||
|
name: "body-style",
|
||||||
|
});
|
||||||
|
await bodyToken.click({ button: "right" });
|
||||||
|
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||||
|
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("text-base");
|
||||||
|
|
||||||
|
// Update the font size value
|
||||||
|
fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||||
|
name: "Font size",
|
||||||
|
});
|
||||||
|
await fontSizeField.fill("18");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
|
||||||
|
// Confirm remapping
|
||||||
|
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||||
|
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
|
const confirmButton = remappingModal.getByRole("button", {
|
||||||
|
name: /remap/i,
|
||||||
|
});
|
||||||
|
await confirmButton.click();
|
||||||
|
|
||||||
|
// Verify base token was renamed
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "text-base" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Verify referenced token still exists
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "paragraph-style" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Verify the text shape still has the token applied with NEW name and value
|
||||||
|
await page.getByRole("tab", { name: "Layers" }).click();
|
||||||
|
await workspacePage.layers
|
||||||
|
.getByTestId("layer-row")
|
||||||
|
.filter({ hasText: "Some Text" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Verify the shape shows the updated font size value (18)
|
||||||
|
// This proves the remapping worked and the value update propagated through the reference
|
||||||
|
const fontSizeInput = workspacePage.rightSidebar.getByRole("textbox", {
|
||||||
|
name: "Font Size",
|
||||||
|
});
|
||||||
|
await expect(fontSizeInput).toBeVisible();
|
||||||
|
await expect(fontSizeInput).toHaveValue("18");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Border Radius Token Remapping", () => {
|
||||||
|
test("User renames border radius token with alias references", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
tokensUpdateCreateModal,
|
||||||
|
tokensSidebar,
|
||||||
|
tokenContextMenuForToken,
|
||||||
|
} = await setupTokensFile(page);
|
||||||
|
|
||||||
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
|
|
||||||
|
// Create base border radius token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("base-radius");
|
||||||
|
|
||||||
|
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
await valueField.fill("4");
|
||||||
|
|
||||||
|
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Create derived border radius token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("card-radius");
|
||||||
|
|
||||||
|
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
await valueField2.fill("{base-radius}");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Rename base token
|
||||||
|
const baseToken = tokensSidebar.getByRole("button", {
|
||||||
|
name: "base-radius",
|
||||||
|
});
|
||||||
|
await baseToken.click({ button: "right" });
|
||||||
|
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||||
|
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("primary-radius");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
|
||||||
|
// Check for remapping modal
|
||||||
|
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||||
|
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
|
const confirmButton = remappingModal.getByRole("button", {
|
||||||
|
name: /remap/i,
|
||||||
|
});
|
||||||
|
await confirmButton.click();
|
||||||
|
|
||||||
|
// Verify token was renamed
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "primary-radius" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "card-radius" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("User renames and updates border radius token - referenced token updates", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
tokensUpdateCreateModal,
|
||||||
|
tokensSidebar,
|
||||||
|
tokenContextMenuForToken,
|
||||||
|
} = await setupTokensFile(page);
|
||||||
|
|
||||||
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
|
|
||||||
|
// Create base border radius token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("radius-sm");
|
||||||
|
|
||||||
|
let valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
await valueField.fill("4");
|
||||||
|
|
||||||
|
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Create derived border radius token
|
||||||
|
await tokensTabPanel
|
||||||
|
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||||
|
.click();
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("button-radius");
|
||||||
|
|
||||||
|
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
await valueField2.fill("{radius-sm}");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
|
// Rename and update value of base token
|
||||||
|
const radiusToken = tokensSidebar.getByRole("button", {
|
||||||
|
name: "radius-sm",
|
||||||
|
});
|
||||||
|
await radiusToken.click({ button: "right" });
|
||||||
|
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||||
|
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
await nameField.fill("radius-base");
|
||||||
|
|
||||||
|
// Update the value
|
||||||
|
valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
await valueField.fill("8");
|
||||||
|
|
||||||
|
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Save",
|
||||||
|
});
|
||||||
|
await submitButton.click();
|
||||||
|
|
||||||
|
// Confirm remapping
|
||||||
|
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||||
|
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
|
const confirmButton = remappingModal.getByRole("button", {
|
||||||
|
name: /remap/i,
|
||||||
|
});
|
||||||
|
await confirmButton.click();
|
||||||
|
|
||||||
|
// Verify base token was renamed
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "radius-base" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Verify referenced token still exists
|
||||||
|
await expect(
|
||||||
|
tokensSidebar.getByRole("button", { name: "button-radius" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Verify the referenced token now points to the renamed token
|
||||||
|
// by opening it and checking the reference
|
||||||
|
const buttonRadiusToken = tokensSidebar.getByRole("button", {
|
||||||
|
name: "button-radius",
|
||||||
|
});
|
||||||
|
await buttonRadiusToken.click({ button: "right" });
|
||||||
|
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||||
|
|
||||||
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
const currentValue = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
await expect(currentValue).toHaveValue("{radius-base}");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@
|
||||||
(when unknown-tokens
|
(when unknown-tokens
|
||||||
(st/emit! (show-unknown-types-warning unknown-tokens)))
|
(st/emit! (show-unknown-types-warning unknown-tokens)))
|
||||||
(try
|
(try
|
||||||
(->> (ctob/get-all-tokens tokens-lib)
|
(->> (ctob/get-all-tokens-map tokens-lib)
|
||||||
(sd/resolve-tokens-with-verbose-errors)
|
(sd/resolve-tokens-with-verbose-errors)
|
||||||
(rx/map (fn [_]
|
(rx/map (fn [_]
|
||||||
tokens-lib))
|
tokens-lib))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.main.data.workspace.tokens.remapping
|
||||||
|
"Core logic for token remapping functionality"
|
||||||
|
(:require
|
||||||
|
[app.common.files.changes-builder :as pcb]
|
||||||
|
[app.common.files.tokens :as cft]
|
||||||
|
[app.common.logging :as log]
|
||||||
|
[app.common.types.container :refer [shapes-seq]]
|
||||||
|
[app.common.types.file :refer [object-containers-seq]]
|
||||||
|
[app.common.types.token :as cto]
|
||||||
|
[app.common.types.tokens-lib :as ctob]
|
||||||
|
[app.main.data.changes :as dch]
|
||||||
|
[app.main.data.helpers :as dh]
|
||||||
|
[beicon.v2.core :as rx]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[potok.v2.core :as ptk]))
|
||||||
|
|
||||||
|
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
|
||||||
|
(log/set-level! :warn)
|
||||||
|
|
||||||
|
;; Token Reference Scanning
|
||||||
|
;; ========================
|
||||||
|
|
||||||
|
(defn scan-shape-applied-tokens
|
||||||
|
"Scan a shape for applied token references to a specific token name"
|
||||||
|
[shape token-name container]
|
||||||
|
(when-let [applied-tokens (:applied-tokens shape)]
|
||||||
|
(for [[attribute applied-token-name] applied-tokens
|
||||||
|
:when (= applied-token-name token-name)]
|
||||||
|
{:type :applied-token
|
||||||
|
:shape-id (:id shape)
|
||||||
|
:attribute attribute
|
||||||
|
:token-name applied-token-name
|
||||||
|
:container container})))
|
||||||
|
|
||||||
|
(defn scan-token-value-references
|
||||||
|
"Scan a token value for references to a specific token name (alias), supporting complex token values."
|
||||||
|
[token token-name]
|
||||||
|
(letfn [(find-all-token-value-references [token-value]
|
||||||
|
(cond
|
||||||
|
(string? token-value)
|
||||||
|
(filter #(= % token-name) (cto/find-token-value-references token-value))
|
||||||
|
|
||||||
|
(map? token-value)
|
||||||
|
(mapcat find-all-token-value-references (vals token-value))
|
||||||
|
|
||||||
|
(sequential? token-value)
|
||||||
|
(mapcat find-all-token-value-references token-value)
|
||||||
|
|
||||||
|
:else
|
||||||
|
[]))]
|
||||||
|
(when-let [value (:value token)]
|
||||||
|
(for [referenced-token-name (find-all-token-value-references value)]
|
||||||
|
{:type :token-alias
|
||||||
|
:source-token-name (:name token)
|
||||||
|
:referenced-token-name referenced-token-name}))))
|
||||||
|
|
||||||
|
(defn scan-workspace-token-references
|
||||||
|
"Scan entire workspace for all token references to a specific token"
|
||||||
|
[file-data old-token-name]
|
||||||
|
(let [tokens-lib (:tokens-lib file-data)
|
||||||
|
containers (object-containers-seq file-data)
|
||||||
|
|
||||||
|
;; Scan all shapes for applied token references to the specific token
|
||||||
|
matching-applied (mapcat (fn [container]
|
||||||
|
(let [shapes (shapes-seq container)]
|
||||||
|
(mapcat #(scan-shape-applied-tokens % old-token-name container) shapes)))
|
||||||
|
containers)
|
||||||
|
|
||||||
|
;; Scan tokens library for alias references to the specific token
|
||||||
|
matching-aliases (if tokens-lib
|
||||||
|
(let [all-tokens (ctob/get-all-tokens tokens-lib)]
|
||||||
|
(mapcat #(scan-token-value-references % old-token-name) all-tokens))
|
||||||
|
[])]
|
||||||
|
(log/info :hint "token-scan-details"
|
||||||
|
:token-name old-token-name
|
||||||
|
:containers-count (count containers)
|
||||||
|
:total-applied-refs (count matching-applied)
|
||||||
|
:matching-applied (count matching-applied)
|
||||||
|
:total-alias-refs (count matching-aliases)
|
||||||
|
:matching-aliases (count matching-aliases))
|
||||||
|
|
||||||
|
{:applied-tokens matching-applied
|
||||||
|
:token-aliases matching-aliases
|
||||||
|
:total-references (+ (count matching-applied) (count matching-aliases))}))
|
||||||
|
|
||||||
|
;; Token Remapping Core Logic
|
||||||
|
;; ==========================
|
||||||
|
|
||||||
|
(defn remap-tokens
|
||||||
|
"Main function to remap all token references when a token name changes"
|
||||||
|
[old-token-name new-token-name]
|
||||||
|
(ptk/reify ::remap-tokens
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [file-data (dh/lookup-file-data state)
|
||||||
|
scan-results (scan-workspace-token-references file-data old-token-name)
|
||||||
|
tokens-lib (:tokens-lib file-data)
|
||||||
|
sets (ctob/get-sets tokens-lib)
|
||||||
|
tokens-with-sets (mapcat (fn [set]
|
||||||
|
(map (fn [token]
|
||||||
|
{:token token :set set})
|
||||||
|
(vals (ctob/get-tokens tokens-lib (ctob/get-id set)))))
|
||||||
|
sets)
|
||||||
|
|
||||||
|
;; Group applied token references by container
|
||||||
|
refs-by-container (group-by :container (:applied-tokens scan-results))
|
||||||
|
|
||||||
|
;; Use apply-token logic to update shapes for both direct and alias references
|
||||||
|
shape-changes (reduce-kv
|
||||||
|
(fn [changes container refs]
|
||||||
|
(let [shape-ids (map :shape-id refs)
|
||||||
|
;; Find the correct token to apply (new or alias)
|
||||||
|
token (or (some #(when (= (:name (:token %)) new-token-name) %) tokens-with-sets)
|
||||||
|
(some #(when (= (:name (:token %)) old-token-name) %) tokens-with-sets))
|
||||||
|
attributes (set (map :attribute refs))]
|
||||||
|
(if token
|
||||||
|
(-> (pcb/with-container changes container)
|
||||||
|
(pcb/update-shapes shape-ids
|
||||||
|
(fn [shape]
|
||||||
|
(update shape :applied-tokens
|
||||||
|
#(merge % (cft/attributes-map attributes (:token token)))))))
|
||||||
|
changes)))
|
||||||
|
(-> (pcb/empty-changes)
|
||||||
|
(pcb/with-file-data file-data)
|
||||||
|
(pcb/with-library-data file-data))
|
||||||
|
refs-by-container)
|
||||||
|
|
||||||
|
;; Create changes for updating token alias references
|
||||||
|
token-changes (reduce
|
||||||
|
(fn [changes ref]
|
||||||
|
(let [source-token-name (:source-token-name ref)]
|
||||||
|
(when-let [{:keys [token set]} (some #(when (= (:name (:token %)) source-token-name) %) tokens-with-sets)]
|
||||||
|
(let [old-value (:value token)
|
||||||
|
new-value (cto/update-token-value-references old-value old-token-name new-token-name)]
|
||||||
|
(pcb/set-token changes (ctob/get-id set) (:id token)
|
||||||
|
(assoc token :value new-value))))))
|
||||||
|
shape-changes
|
||||||
|
(:token-aliases scan-results))]
|
||||||
|
|
||||||
|
(log/info :hint "token-remapping"
|
||||||
|
:old-name old-token-name
|
||||||
|
:new-name new-token-name
|
||||||
|
:references-count (:total-references scan-results))
|
||||||
|
|
||||||
|
(rx/of (dch/commit-changes token-changes))))))
|
||||||
|
|
||||||
|
(defn validate-token-remapping
|
||||||
|
"Validate that a token remapping operation is safe to perform"
|
||||||
|
[old-name new-name]
|
||||||
|
(cond
|
||||||
|
(str/blank? new-name)
|
||||||
|
{:valid? false
|
||||||
|
:error :invalid-name
|
||||||
|
:message "Token name cannot be empty"}
|
||||||
|
(= old-name new-name)
|
||||||
|
{:valid? false
|
||||||
|
:error :no-change
|
||||||
|
:message "New name is the same as current name"}
|
||||||
|
:else
|
||||||
|
{:valid? true}))
|
||||||
|
|
||||||
|
(defn count-token-references
|
||||||
|
"Count the number of references to a token in the workspace"
|
||||||
|
[file-data token-name]
|
||||||
|
(let [scan-results (scan-workspace-token-references file-data token-name)]
|
||||||
|
(log/info :hint "token-reference-scan"
|
||||||
|
:token-name token-name
|
||||||
|
:applied-refs (count (:applied-tokens scan-results))
|
||||||
|
:alias-refs (count (:token-aliases scan-results))
|
||||||
|
:total (:total-references scan-results))
|
||||||
|
(:total-references scan-results)))
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
[app.main.ui.workspace.tokens.import]
|
[app.main.ui.workspace.tokens.import]
|
||||||
[app.main.ui.workspace.tokens.import.modal]
|
[app.main.ui.workspace.tokens.import.modal]
|
||||||
[app.main.ui.workspace.tokens.management.forms.modals]
|
[app.main.ui.workspace.tokens.management.forms.modals]
|
||||||
|
[app.main.ui.workspace.tokens.remapping-modal]
|
||||||
[app.main.ui.workspace.tokens.settings]
|
[app.main.ui.workspace.tokens.settings]
|
||||||
[app.main.ui.workspace.tokens.themes.create-modal]
|
[app.main.ui.workspace.tokens.themes.create-modal]
|
||||||
[app.main.ui.workspace.viewport :refer [viewport*]]
|
[app.main.ui.workspace.viewport :refer [viewport*]]
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
props
|
props
|
||||||
(mf/spread-props props {:token-type token-type
|
(mf/spread-props props {:token-type token-type
|
||||||
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
||||||
|
:tokens-in-selected-set tokens-in-selected-set
|
||||||
:token token})
|
:token token})
|
||||||
text-case-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-case-value-enter")})
|
text-case-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-case-value-enter")})
|
||||||
text-decoration-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-decoration-value-enter")})
|
text-decoration-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-decoration-value-enter")})
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,21 @@
|
||||||
[app.common.types.token :as cto]
|
[app.common.types.token :as cto]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
[app.main.constants :refer [max-input-length]]
|
[app.main.constants :refer [max-input-length]]
|
||||||
|
[app.main.data.helpers :as dh]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.workspace.tokens.application :as dwta]
|
[app.main.data.workspace.tokens.application :as dwta]
|
||||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||||
|
[app.main.data.workspace.tokens.remapping :as remap]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
|
||||||
[app.main.ui.forms :as fc]
|
[app.main.ui.forms :as fc]
|
||||||
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
|
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
|
||||||
[app.main.ui.workspace.tokens.management.forms.validators :refer [default-validate-token]]
|
[app.main.ui.workspace.tokens.management.forms.validators :refer [default-validate-token]]
|
||||||
|
[app.main.ui.workspace.tokens.remapping-modal :as remapping-modal]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.forms :as fm]
|
[app.util.forms :as fm]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
|
|
@ -92,6 +94,7 @@
|
||||||
initial
|
initial
|
||||||
type
|
type
|
||||||
value-subfield
|
value-subfield
|
||||||
|
tokens-in-selected-set
|
||||||
input-value-placeholder] :as props}]
|
input-value-placeholder] :as props}]
|
||||||
|
|
||||||
(let [make-schema (or make-schema default-make-schema)
|
(let [make-schema (or make-schema default-make-schema)
|
||||||
|
|
@ -121,11 +124,11 @@
|
||||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||||
|
|
||||||
tokens
|
tokens
|
||||||
(mf/with-memo [tokens token]
|
(mf/with-memo [tokens tokens-in-selected-set token]
|
||||||
;; Ensure that the resolved value uses the currently editing token
|
;; Ensure that the resolved value uses the currently editing token
|
||||||
;; even if the name has been overriden by a token with the same name
|
;; even if the name has been overriden by a token with the same name
|
||||||
;; in another set below.
|
;; in another set below.
|
||||||
(cond-> tokens
|
(cond-> (merge tokens tokens-in-selected-set)
|
||||||
(and (:name token) (:value token))
|
(and (:name token) (:value token))
|
||||||
(assoc (:name token) token)))
|
(assoc (:name token) token)))
|
||||||
|
|
||||||
|
|
@ -144,10 +147,6 @@
|
||||||
(fm/use-form :schema schema
|
(fm/use-form :schema schema
|
||||||
:initial initial)
|
:initial initial)
|
||||||
|
|
||||||
warning-name-change?
|
|
||||||
(not= (get-in @form [:data :name])
|
|
||||||
(:name initial))
|
|
||||||
|
|
||||||
on-cancel
|
on-cancel
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [e]
|
(fn [e]
|
||||||
|
|
@ -191,19 +190,38 @@
|
||||||
:tokens tokens})
|
:tokens tokens})
|
||||||
(rx/subs!
|
(rx/subs!
|
||||||
(fn [valid-token]
|
(fn [valid-token]
|
||||||
(st/emit!
|
(let [state @st/state
|
||||||
(if is-create
|
file-data (dh/lookup-file-data state)
|
||||||
(dwtl/create-token (ctob/make-token {:name name
|
old-name (:name token)
|
||||||
:type token-type
|
is-rename (and (= action "edit") (not= name old-name))
|
||||||
:value (:value valid-token)
|
references-count (remap/count-token-references file-data old-name)]
|
||||||
:description description}))
|
(if (and is-rename (> references-count 0))
|
||||||
|
(remapping-modal/show-remapping-modal
|
||||||
(dwtl/update-token (:id token)
|
{:old-token-name old-name
|
||||||
{:name name
|
:new-token-name name
|
||||||
:value (:value valid-token)
|
:references-count references-count
|
||||||
:description description}))
|
:on-confirm (fn []
|
||||||
(dwtp/propagate-workspace-tokens)
|
(st/emit!
|
||||||
(modal/hide))))))))]
|
(dwtl/update-token (:id token)
|
||||||
|
{:name name
|
||||||
|
:value (:value valid-token)
|
||||||
|
:description description})
|
||||||
|
(remap/remap-tokens old-name name)
|
||||||
|
(dwtp/propagate-workspace-tokens)
|
||||||
|
(modal/hide!)))
|
||||||
|
:on-cancel #(modal/hide!)})
|
||||||
|
(st/emit!
|
||||||
|
(if is-create
|
||||||
|
(dwtl/create-token (ctob/make-token {:name name
|
||||||
|
:type token-type
|
||||||
|
:value (:value valid-token)
|
||||||
|
:description description}))
|
||||||
|
(dwtl/update-token (:id token)
|
||||||
|
{:name name
|
||||||
|
:value (:value valid-token)
|
||||||
|
:description description}))
|
||||||
|
(dwtp/propagate-workspace-tokens)
|
||||||
|
(modal/hide!))))))))))]
|
||||||
|
|
||||||
[:> fc/form* {:class (stl/css :form-wrapper)
|
[:> fc/form* {:class (stl/css :form-wrapper)
|
||||||
:form form
|
:form form
|
||||||
|
|
@ -222,12 +240,7 @@
|
||||||
:placeholder (tr "workspace.tokens.enter-token-name" token-title)
|
:placeholder (tr "workspace.tokens.enter-token-name" token-title)
|
||||||
:max-length max-input-length
|
:max-length max-input-length
|
||||||
:variant "comfortable"
|
:variant "comfortable"
|
||||||
:auto-focus true}]
|
:auto-focus true}]]
|
||||||
|
|
||||||
(when (and warning-name-change? (= action "edit"))
|
|
||||||
[:div {:class (stl/css :warning-name-change-notification-wrapper)}
|
|
||||||
[:> context-notification*
|
|
||||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :input-row)}
|
[:div {:class (stl/css :input-row)}
|
||||||
(case type
|
(case type
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,6 @@
|
||||||
grid-template-columns: 1fr auto auto;
|
grid-template-columns: 1fr auto auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-name-change-notification-wrapper {
|
|
||||||
margin-block-start: var(--sp-l);
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-btn {
|
.delete-btn {
|
||||||
justify-self: start;
|
justify-self: start;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.main.ui.workspace.tokens.remapping-modal
|
||||||
|
"Token remapping confirmation modal"
|
||||||
|
(:require-macros [app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
[app.main.data.modal :as modal]
|
||||||
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||||
|
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||||
|
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[app.util.i18n :refer [tr]]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(defn show-remapping-modal
|
||||||
|
"Show the token remapping confirmation modal"
|
||||||
|
[{:keys [old-token-name new-token-name references-count on-confirm on-cancel]}]
|
||||||
|
(let [props {:old-token-name old-token-name
|
||||||
|
:new-token-name new-token-name
|
||||||
|
:references-count references-count
|
||||||
|
:on-confirm on-confirm
|
||||||
|
:on-cancel on-cancel}]
|
||||||
|
(st/emit! (modal/show :tokens/remapping-confirmation props))))
|
||||||
|
|
||||||
|
(defn hide-remapping-modal
|
||||||
|
"Hide the token remapping confirmation modal"
|
||||||
|
[]
|
||||||
|
(st/emit! (modal/hide)))
|
||||||
|
|
||||||
|
;; Remapping Modal Component
|
||||||
|
(mf/defc token-remapping-modal
|
||||||
|
{::mf/wrap-props false
|
||||||
|
::mf/register modal/components
|
||||||
|
::mf/register-as :tokens/remapping-confirmation}
|
||||||
|
[{:keys [old-token-name new-token-name references-count on-confirm on-cancel]}]
|
||||||
|
(let [remapping-in-progress* (mf/use-state false)
|
||||||
|
remapping-in-progress? (deref remapping-in-progress*)
|
||||||
|
|
||||||
|
;; Remap logic on confirm
|
||||||
|
on-confirm-remap
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-confirm remapping-in-progress*)
|
||||||
|
(fn [e]
|
||||||
|
(dom/prevent-default e)
|
||||||
|
(dom/stop-propagation e)
|
||||||
|
(reset! remapping-in-progress* true)
|
||||||
|
;; Call shared remapping logic
|
||||||
|
(let [state @st/state
|
||||||
|
remap-modal (:remap-modal state)
|
||||||
|
old-token-name (:old-token-name remap-modal)
|
||||||
|
new-token-name (:new-token-name remap-modal)]
|
||||||
|
(st/emit! [:tokens/remap-tokens old-token-name new-token-name]))
|
||||||
|
(when (fn? on-confirm)
|
||||||
|
(on-confirm))))
|
||||||
|
|
||||||
|
on-cancel-remap
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-cancel)
|
||||||
|
(fn [e]
|
||||||
|
(dom/prevent-default e)
|
||||||
|
(dom/stop-propagation e)
|
||||||
|
(modal/hide!)
|
||||||
|
(when (fn? on-cancel)
|
||||||
|
(on-cancel))))]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :modal-overlay)}
|
||||||
|
[:div {:class (stl/css :modal-dialog)
|
||||||
|
:data-testid "token-remapping-modal"}
|
||||||
|
[:div {:class (stl/css :modal-header)}
|
||||||
|
[:> heading* {:level 2
|
||||||
|
:typography "headline-medium"
|
||||||
|
:class (stl/css :modal-title)}
|
||||||
|
(tr "workspace.tokens.remap-token-references")]]
|
||||||
|
[:div {:class (stl/css :modal-content)}
|
||||||
|
[:> heading* {:level 3
|
||||||
|
:typography "title-medium"
|
||||||
|
:class (stl/css :modal-msg)}
|
||||||
|
(tr "workspace.tokens.renaming-token-from-to" old-token-name new-token-name)]
|
||||||
|
[:div {:class (stl/css :modal-scd-msg)}
|
||||||
|
(if (> references-count 0)
|
||||||
|
(tr "workspace.tokens.references-found" references-count)
|
||||||
|
(tr "workspace.tokens.no-references-found"))]
|
||||||
|
(when remapping-in-progress?
|
||||||
|
[:> context-notification*
|
||||||
|
{:level :info
|
||||||
|
:appearance :ghost}
|
||||||
|
(tr "workspace.tokens.remapping-in-progress")])]
|
||||||
|
[:div {:class (stl/css :modal-footer)}
|
||||||
|
[:div {:class (stl/css :action-buttons)}
|
||||||
|
[:> button* {:on-click on-cancel-remap
|
||||||
|
:type "button"
|
||||||
|
:variant "secondary"
|
||||||
|
:disabled remapping-in-progress?}
|
||||||
|
(tr "labels.cancel")]
|
||||||
|
[:> button* {:on-click on-confirm-remap
|
||||||
|
:type "button"
|
||||||
|
:variant "primary"
|
||||||
|
:disabled remapping-in-progress?}
|
||||||
|
(if (> references-count 0)
|
||||||
|
(tr "workspace.tokens.remap-and-rename")
|
||||||
|
(tr "workspace.tokens.rename-only"))]]]]]))
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
@use "ds/typography.scss" as t;
|
||||||
|
|
||||||
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
@extend .modal-overlay-base;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
inset-block-start: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--overlay-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog {
|
||||||
|
@extend .modal-container-base;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 32rem;
|
||||||
|
max-height: unset;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
margin-block-end: var(--sp-xxl);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
@include t.use-typography("headline-medium");
|
||||||
|
color: var(--modal-title-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn {
|
||||||
|
@extend .modal-close-btn-base;
|
||||||
|
position: absolute;
|
||||||
|
inset-block-start: var(--sp-s);
|
||||||
|
inset-inline-end: var(--sp-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
@include t.use-typography("body-large");
|
||||||
|
margin-block-end: var(--sp-xxl);
|
||||||
|
padding: var(--sp-xxl) 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
margin-block-start: var(--sp-xxl);
|
||||||
|
gap: var(--sp-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
@extend .modal-action-btns;
|
||||||
|
gap: var(--sp-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button {
|
||||||
|
@extend .modal-cancel-btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accept-btn {
|
||||||
|
@extend .modal-accept-btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-scd-msg,
|
||||||
|
.modal-msg {
|
||||||
|
@include t.use-typography("body-large");
|
||||||
|
color: var(--modal-text-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remap-explanation {
|
||||||
|
margin: var(--spacing-sm) 0 0 0;
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
font-size: var(--fs-12);
|
||||||
|
line-height: var(--lh-1-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-references-info {
|
||||||
|
margin-block-end: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-remap-explanation {
|
||||||
|
margin: var(--spacing-sm) 0 0 0;
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
font-size: var(--fs-12);
|
||||||
|
line-height: var(--lh-1-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-info {
|
||||||
|
margin-block-end: var(--spacing-md);
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
background: var(--color-background-secondary);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
@extend .modal-action-btns;
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
[frontend-tests.tokens.logic.token-actions-test]
|
[frontend-tests.tokens.logic.token-actions-test]
|
||||||
[frontend-tests.tokens.logic.token-data-test]
|
[frontend-tests.tokens.logic.token-data-test]
|
||||||
[frontend-tests.tokens.style-dictionary-test]
|
[frontend-tests.tokens.style-dictionary-test]
|
||||||
|
[frontend-tests.tokens.workspace-tokens-remap-test]
|
||||||
[frontend-tests.util-object-test]
|
[frontend-tests.util-object-test]
|
||||||
[frontend-tests.util-range-tree-test]
|
[frontend-tests.util-range-tree-test]
|
||||||
[frontend-tests.util-simple-math-test]
|
[frontend-tests.util-simple-math-test]
|
||||||
|
|
@ -49,4 +50,5 @@
|
||||||
'frontend-tests.util-object-test
|
'frontend-tests.util-object-test
|
||||||
'frontend-tests.util-range-tree-test
|
'frontend-tests.util-range-tree-test
|
||||||
'frontend-tests.util-simple-math-test
|
'frontend-tests.util-simple-math-test
|
||||||
|
'frontend-tests.tokens.workspace-tokens-remap-test
|
||||||
'frontend-tests.worker-snap-test))
|
'frontend-tests.worker-snap-test))
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.types.color :as clr]
|
[app.common.types.color :as clr]
|
||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
[app.test-helpers.events :as the]
|
|
||||||
[app.test-helpers.libraries :as thl]
|
|
||||||
[app.test-helpers.pages :as thp]
|
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[cljs.pprint :refer [pprint]]
|
[cljs.pprint :refer [pprint]]
|
||||||
[cljs.test :as t :include-macros true]
|
[cljs.test :as t :include-macros true]
|
||||||
[clojure.stacktrace :as stk]
|
[clojure.stacktrace :as stk]
|
||||||
|
[frontend-tests.helpers.events :as the]
|
||||||
|
[frontend-tests.helpers.libraries :as thl]
|
||||||
|
[frontend-tests.helpers.pages :as thp]
|
||||||
[linked.core :as lks]
|
[linked.core :as lks]
|
||||||
[potok.v2.core :as ptk]))
|
[potok.v2.core :as ptk]))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns frontend-tests.tokens.logic.token-remapping-test
|
||||||
|
(:require
|
||||||
|
[app.common.types.tokens-lib :as ctob]
|
||||||
|
[app.main.data.workspace.tokens.remapping :as dwtr]
|
||||||
|
[cljs.test :as t :include-macros true]
|
||||||
|
[frontend-tests.helpers.files :as thf]
|
||||||
|
[frontend-tests.helpers.ids :as thi]
|
||||||
|
[frontend-tests.helpers.objects :as tho]
|
||||||
|
[frontend-tests.helpers.state :as ths]
|
||||||
|
[frontend-tests.helpers.tokens :as tht]))
|
||||||
|
|
||||||
|
(defn setup-file-with-tokens
|
||||||
|
"Setup a test file with tokens and shapes that use those tokens"
|
||||||
|
[]
|
||||||
|
(let [color-token {:name "color.primary"
|
||||||
|
:value "#FF0000"
|
||||||
|
:type :color}
|
||||||
|
alias-token {:name "color.secondary"
|
||||||
|
:value "{color.primary}"
|
||||||
|
:type :color}]
|
||||||
|
(-> (thf/sample-file :file-1 :page-label :page-1)
|
||||||
|
(tho/add-rect :rect-1)
|
||||||
|
(tho/add-rect :rect-2)
|
||||||
|
(assoc-in [:data :tokens-lib]
|
||||||
|
(-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"}))
|
||||||
|
(ctob/set-active-themes #{"/Theme A"})
|
||||||
|
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a)
|
||||||
|
:name "Set A"))
|
||||||
|
(ctob/add-token (thi/id :set-a)
|
||||||
|
(ctob/make-token color-token))
|
||||||
|
(ctob/add-token (thi/id :set-a)
|
||||||
|
(ctob/make-token alias-token))))
|
||||||
|
;; Apply the token to rect-1
|
||||||
|
(tht/apply-token-to-shape :fill "color.primary" :rect-1))))
|
||||||
|
|
||||||
|
(t/deftest test-scan-workspace-token-references
|
||||||
|
(t/testing "should find applied token references"
|
||||||
|
(let [file (setup-file-with-tokens)
|
||||||
|
file-data (:data file)
|
||||||
|
scan-results (dwtr/scan-workspace-token-references file-data "color.primary")]
|
||||||
|
|
||||||
|
(t/is (= 1 (count (:applied-tokens scan-results))))
|
||||||
|
(t/is (= 1 (count (:token-aliases scan-results))))
|
||||||
|
(t/is (= 2 (:total-references scan-results)))
|
||||||
|
|
||||||
|
;; Check applied token reference
|
||||||
|
(let [applied-ref (first (:applied-tokens scan-results))]
|
||||||
|
(t/is (= :applied-token (:type applied-ref)))
|
||||||
|
(t/is (= "color.primary" (:token-name applied-ref)))
|
||||||
|
(t/is (= :fill (:attribute applied-ref))))
|
||||||
|
|
||||||
|
;; Check alias reference
|
||||||
|
(let [alias-ref (first (:token-aliases scan-results))]
|
||||||
|
(t/is (= :token-alias (:type alias-ref)))
|
||||||
|
(t/is (= "color.secondary" (:source-token-name alias-ref)))
|
||||||
|
(t/is (= "color.primary" (:referenced-token-name alias-ref)))))))
|
||||||
|
|
||||||
|
(t/deftest test-scan-token-value-references
|
||||||
|
(t/testing "should extract token references from alias values"
|
||||||
|
(let [token {:name "color.secondary"
|
||||||
|
:value "{color.primary}"
|
||||||
|
:type :color}
|
||||||
|
references (dwtr/scan-token-value-references token)]
|
||||||
|
|
||||||
|
(t/is (= 1 (count references)))
|
||||||
|
(let [ref (first references)]
|
||||||
|
(t/is (= :token-alias (:type ref)))
|
||||||
|
(t/is (= "color.secondary" (:source-token-name ref)))
|
||||||
|
(t/is (= "color.primary" (:referenced-token-name ref))))))
|
||||||
|
|
||||||
|
(t/testing "should handle multiple references in one value"
|
||||||
|
(let [token {:name "spacing.complex"
|
||||||
|
:value "calc({spacing.base} + {spacing.small})"
|
||||||
|
:type :spacing}
|
||||||
|
references (dwtr/scan-token-value-references token)]
|
||||||
|
|
||||||
|
(t/is (= 2 (count references)))
|
||||||
|
(t/is (some #(= "spacing.base" (:referenced-token-name %)) references))
|
||||||
|
(t/is (some #(= "spacing.small" (:referenced-token-name %)) references)))))
|
||||||
|
|
||||||
|
(t/deftest test-update-token-value-references
|
||||||
|
(t/testing "should update token references in alias values"
|
||||||
|
(let [old-value "{color.primary}"
|
||||||
|
new-value (dwtr/update-token-value-references old-value "color.primary" "brand.primary")]
|
||||||
|
(t/is (= "{brand.primary}" new-value))))
|
||||||
|
|
||||||
|
(t/testing "should update multiple references"
|
||||||
|
(let [old-value "calc({spacing.base} + {spacing.base})"
|
||||||
|
new-value (dwtr/update-token-value-references old-value "spacing.base" "spacing.foundation")]
|
||||||
|
(t/is (= "calc({spacing.foundation} + {spacing.foundation})" new-value))))
|
||||||
|
|
||||||
|
(t/testing "should not update partial matches"
|
||||||
|
(let [old-value "{color.primary.light}"
|
||||||
|
new-value (dwtr/update-token-value-references old-value "color.primary" "brand.primary")]
|
||||||
|
(t/is (= "{color.primary.light}" new-value)))))
|
||||||
|
|
||||||
|
(t/deftest test-count-token-references
|
||||||
|
(t/testing "should count total references to a token"
|
||||||
|
(let [file (setup-file-with-tokens)
|
||||||
|
file-data (:data file)
|
||||||
|
count (dwtr/count-token-references file-data "color.primary")]
|
||||||
|
(t/is (= 2 count)))))
|
||||||
|
|
||||||
|
(t/deftest test-validate-token-remapping
|
||||||
|
(t/testing "should validate remapping parameters"
|
||||||
|
(let [file-data (:data (setup-file-with-tokens))]
|
||||||
|
|
||||||
|
(t/testing "empty name should be invalid"
|
||||||
|
(let [result (dwtr/validate-token-remapping file-data "color.primary" "")]
|
||||||
|
(t/is (false? (:valid? result)))
|
||||||
|
(t/is (= :invalid-name (:error result)))))
|
||||||
|
|
||||||
|
(t/testing "same name should be invalid"
|
||||||
|
(let [result (dwtr/validate-token-remapping file-data "color.primary" "color.primary")]
|
||||||
|
(t/is (false? (:valid? result)))
|
||||||
|
(t/is (= :no-change (:error result)))))
|
||||||
|
|
||||||
|
(t/testing "valid new name should be valid"
|
||||||
|
(let [result (dwtr/validate-token-remapping file-data "color.primary" "brand.primary")]
|
||||||
|
(t/is (true? (:valid? result))))))))
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
(ctob/make-token {:name "borderRadius.largeFn"
|
(ctob/make-token {:name "borderRadius.largeFn"
|
||||||
:value "{borderRadius.sm} * 200000000"
|
:value "{borderRadius.sm} * 200000000"
|
||||||
:type :border-radius}))
|
:type :border-radius}))
|
||||||
(ctob/get-all-tokens))]
|
(ctob/get-all-tokens-map))]
|
||||||
(-> (sd/resolve-tokens tokens)
|
(-> (sd/resolve-tokens tokens)
|
||||||
(rx/sub!
|
(rx/sub!
|
||||||
(fn [resolved-tokens]
|
(fn [resolved-tokens]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,372 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns frontend-tests.tokens.workspace-tokens-remap-test
|
||||||
|
(:require
|
||||||
|
[app.common.test-helpers.compositions :as ctho]
|
||||||
|
[app.common.test-helpers.files :as cthf]
|
||||||
|
[app.common.test-helpers.ids-map :as cthi]
|
||||||
|
[app.common.test-helpers.shapes :as cths]
|
||||||
|
[app.common.types.text :as txt]
|
||||||
|
[app.common.types.tokens-lib :as ctob]
|
||||||
|
[app.main.data.workspace.tokens.remapping :as remap]
|
||||||
|
[cljs.test :as t :include-macros true]
|
||||||
|
[frontend-tests.helpers.state :as ths]
|
||||||
|
[frontend-tests.tokens.helpers.state :as tohs]
|
||||||
|
[frontend-tests.tokens.helpers.tokens :as toht]))
|
||||||
|
|
||||||
|
(t/use-fixtures :each
|
||||||
|
{:before cthi/reset-idmap!})
|
||||||
|
|
||||||
|
(def token-set-name "remap-test-set")
|
||||||
|
(def token-theme-name "remap-test-theme")
|
||||||
|
|
||||||
|
(defn- make-base-file []
|
||||||
|
(-> (cthf/sample-file :file-1 :page-label :page-1)
|
||||||
|
(ctho/add-rect :rect-shape)
|
||||||
|
(ctho/add-text :text-shape "Sample text")))
|
||||||
|
|
||||||
|
(defn- attach-token-set [file tokens]
|
||||||
|
(let [set-id (cthi/new-id! :token-set)
|
||||||
|
tokens-lib (reduce
|
||||||
|
(fn [lib token]
|
||||||
|
(ctob/add-token lib set-id (ctob/make-token token)))
|
||||||
|
(-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set :id set-id
|
||||||
|
:name token-set-name))
|
||||||
|
(ctob/add-theme (ctob/make-token-theme :name token-theme-name
|
||||||
|
:sets #{token-set-name}))
|
||||||
|
(ctob/set-active-themes #{(str "/" token-theme-name)}))
|
||||||
|
tokens)]
|
||||||
|
(assoc-in file [:data :tokens-lib] tokens-lib)))
|
||||||
|
|
||||||
|
(defn- tokens-lib [file]
|
||||||
|
(get-in file [:data :tokens-lib]))
|
||||||
|
|
||||||
|
(defn- find-token-entry [tokens-lib token-name]
|
||||||
|
(when tokens-lib
|
||||||
|
(some (fn [set]
|
||||||
|
(let [set-id (ctob/get-id set)
|
||||||
|
tokens (ctob/get-tokens tokens-lib set-id)]
|
||||||
|
(some (fn [[_ token]]
|
||||||
|
(when (= (:name token) token-name)
|
||||||
|
{:set-id set-id
|
||||||
|
:token token}))
|
||||||
|
tokens)))
|
||||||
|
(ctob/get-sets tokens-lib))))
|
||||||
|
|
||||||
|
(defn- rename-token-in-file [file old-name new-name]
|
||||||
|
(let [tokens-lib (tokens-lib file)]
|
||||||
|
(if-let [{:keys [set-id token]} (find-token-entry tokens-lib old-name)]
|
||||||
|
(let [tokens-lib' (ctob/update-token tokens-lib set-id (:id token) #(assoc % :name new-name))]
|
||||||
|
(assoc-in file [:data :tokens-lib] tokens-lib'))
|
||||||
|
file)))
|
||||||
|
|
||||||
|
(defn- alias-reference [token-name]
|
||||||
|
(str "{" token-name "}"))
|
||||||
|
|
||||||
|
(defn- alias-token-for-case [{:keys [token]}]
|
||||||
|
{:name (str (:name token) "-alias")
|
||||||
|
:type (:type token)
|
||||||
|
:value (alias-reference (:name token))})
|
||||||
|
|
||||||
|
(defn- file-for-case [{:keys [token attribute shape]}]
|
||||||
|
(let [file (-> (make-base-file)
|
||||||
|
(attach-token-set [token]))]
|
||||||
|
(toht/apply-token-to-shape file shape (:name token) #{attribute})))
|
||||||
|
|
||||||
|
(def token-remap-cases
|
||||||
|
[{:case :boolean
|
||||||
|
:token {:name "boolean-token" :type :boolean :value true}
|
||||||
|
:attribute :visible
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :border-radius
|
||||||
|
:token {:name "border-radius-token" :type :border-radius :value "12"}
|
||||||
|
:attribute :r1
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :shadow
|
||||||
|
:token {:name "shadow-token"
|
||||||
|
:type :shadow
|
||||||
|
:value [{:offset-x 0
|
||||||
|
:offset-y 1
|
||||||
|
:blur 2
|
||||||
|
:spread 0
|
||||||
|
:color "rgba(0,0,0,0.5)"
|
||||||
|
:inset false}]}
|
||||||
|
:attribute :shadow
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :color
|
||||||
|
:token {:name "color-token" :type :color :value "#ff0000"}
|
||||||
|
:attribute :fill
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :dimensions
|
||||||
|
:token {:name "dimensions-token" :type :dimensions :value "100px"}
|
||||||
|
:attribute :width
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :font-family
|
||||||
|
:token {:name "font-family-token"
|
||||||
|
:type :font-family
|
||||||
|
:value ["Arial" "Helvetica"]}
|
||||||
|
:attribute :font-family
|
||||||
|
:shape :text-shape}
|
||||||
|
|
||||||
|
{:case :font-size
|
||||||
|
:token {:name "font-size-token" :type :font-size :value "16px"}
|
||||||
|
:attribute :font-size
|
||||||
|
:shape :text-shape}
|
||||||
|
|
||||||
|
{:case :letter-spacing
|
||||||
|
:token {:name "letter-spacing-token" :type :letter-spacing :value "1"}
|
||||||
|
:attribute :letter-spacing
|
||||||
|
:shape :text-shape}
|
||||||
|
|
||||||
|
{:case :number
|
||||||
|
:token {:name "number-token" :type :number :value "42"}
|
||||||
|
:attribute :line-height
|
||||||
|
:shape :text-shape}
|
||||||
|
|
||||||
|
{:case :opacity
|
||||||
|
:token {:name "opacity-token" :type :opacity :value 0.5}
|
||||||
|
:attribute :opacity
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :other
|
||||||
|
:token {:name "other-token" :type :other :value "misc"}
|
||||||
|
:attribute :custom-data
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :rotation
|
||||||
|
:token {:name "rotation-token" :type :rotation :value 45}
|
||||||
|
:attribute :rotation
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :sizing
|
||||||
|
:token {:name "sizing-token" :type :sizing :value "200px"}
|
||||||
|
:attribute :height
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :spacing
|
||||||
|
:token {:name "spacing-token" :type :spacing :value "8"}
|
||||||
|
:attribute :spacing
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :string
|
||||||
|
:token {:name "string-token" :type :string :value "hello"}
|
||||||
|
:attribute :string-content
|
||||||
|
:shape :text-shape}
|
||||||
|
|
||||||
|
{:case :stroke-width
|
||||||
|
:token {:name "stroke-width-token" :type :stroke-width :value "2"}
|
||||||
|
:attribute :stroke-width
|
||||||
|
:shape :rect-shape}
|
||||||
|
|
||||||
|
{:case :text-case
|
||||||
|
:token {:name "text-case-token" :type :text-case :value "uppercase"}
|
||||||
|
:attribute :text-transform
|
||||||
|
:shape :text-shape}
|
||||||
|
|
||||||
|
{:case :text-decoration
|
||||||
|
:token {:name "text-decoration-token" :type :text-decoration :value "underline"}
|
||||||
|
:attribute :text-decoration
|
||||||
|
:shape :text-shape}
|
||||||
|
|
||||||
|
{:case :font-weight
|
||||||
|
:token {:name "font-weight-token" :type :font-weight :value "bold"}
|
||||||
|
:attribute :font-weight
|
||||||
|
:shape :text-shape}
|
||||||
|
|
||||||
|
{:case :typography
|
||||||
|
:token {:name "typography-token"
|
||||||
|
:type :typography
|
||||||
|
:value {:font-size "18px"
|
||||||
|
:font-family [(:font-id txt/default-text-attrs) "Arial"]
|
||||||
|
:font-weight "600"
|
||||||
|
:line-height "20px"
|
||||||
|
:letter-spacing "1"
|
||||||
|
:text-case "uppercase"
|
||||||
|
:text-decoration "underline"}}
|
||||||
|
:attribute :typography
|
||||||
|
:shape :text-shape}])
|
||||||
|
|
||||||
|
(def token-case-by-type
|
||||||
|
(into {} (map (juxt :case identity) token-remap-cases)))
|
||||||
|
|
||||||
|
(defn- fetch-token-case [case-key]
|
||||||
|
(or (get token-case-by-type case-key)
|
||||||
|
(throw (ex-info "Unknown token case" {:case case-key}))))
|
||||||
|
|
||||||
|
(defn- run-token-remap-test! [token-case done]
|
||||||
|
(let [{:keys [token attribute shape]} token-case
|
||||||
|
old-name (:name token)
|
||||||
|
new-name (str old-name "-renamed")
|
||||||
|
file (-> (file-for-case token-case)
|
||||||
|
(rename-token-in-file old-name new-name))
|
||||||
|
store (ths/setup-store file)
|
||||||
|
events [(remap/remap-tokens old-name new-name)]]
|
||||||
|
(tohs/run-store-async
|
||||||
|
store done events
|
||||||
|
(fn [new-state]
|
||||||
|
(let [file' (ths/get-file-from-state new-state)
|
||||||
|
shape' (cths/get-shape file' shape)
|
||||||
|
applied-name (get-in shape' [:applied-tokens attribute])]
|
||||||
|
(t/is (= new-name applied-name)
|
||||||
|
(str "attribute " attribute " now references renamed token"))
|
||||||
|
(t/is (not-any? #(= old-name %) (vals (or (:applied-tokens shape') {})))
|
||||||
|
"old token name removed from applied tokens"))))))
|
||||||
|
|
||||||
|
(defn- run-alias-remap-test! [token-case done]
|
||||||
|
(let [{:keys [token]} token-case
|
||||||
|
alias-token (alias-token-for-case token-case)
|
||||||
|
alias-name (:name alias-token)
|
||||||
|
old-name (:name token)
|
||||||
|
new-name (str old-name "-renamed")
|
||||||
|
file (-> (make-base-file)
|
||||||
|
(attach-token-set [token alias-token]))
|
||||||
|
tokens-lib-before (tokens-lib file)
|
||||||
|
set-id (some-> (ctob/get-set-by-name tokens-lib-before token-set-name) ctob/get-id)
|
||||||
|
alias-before (ctob/get-token-by-name tokens-lib-before token-set-name alias-name)
|
||||||
|
alias-id (:id alias-before)
|
||||||
|
file' (rename-token-in-file file old-name new-name)
|
||||||
|
store (ths/setup-store file')]
|
||||||
|
(tohs/run-store-async
|
||||||
|
store done [(remap/remap-tokens old-name new-name)]
|
||||||
|
(fn [new-state]
|
||||||
|
(let [file'' (ths/get-file-from-state new-state)
|
||||||
|
tokens-lib' (tokens-lib file'')
|
||||||
|
updated-alias (ctob/get-token tokens-lib' set-id alias-id)]
|
||||||
|
(t/is (= (alias-reference new-name) (:value updated-alias))
|
||||||
|
(str "alias for " alias-name " updated to new token name")))))))
|
||||||
|
|
||||||
|
(defn- define-remap-test [case-key test-fn]
|
||||||
|
(t/async done
|
||||||
|
(test-fn (fetch-token-case case-key) done)))
|
||||||
|
|
||||||
|
;; Direct remap tests
|
||||||
|
(t/deftest remap-boolean-token
|
||||||
|
(define-remap-test :boolean run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-border-radius-token
|
||||||
|
(define-remap-test :border-radius run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-shadow-token
|
||||||
|
(define-remap-test :shadow run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-color-token
|
||||||
|
(define-remap-test :color run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-dimensions-token
|
||||||
|
(define-remap-test :dimensions run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-font-family-token
|
||||||
|
(define-remap-test :font-family run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-font-size-token
|
||||||
|
(define-remap-test :font-size run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-letter-spacing-token
|
||||||
|
(define-remap-test :letter-spacing run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-number-token
|
||||||
|
(define-remap-test :number run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-opacity-token
|
||||||
|
(define-remap-test :opacity run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-other-token
|
||||||
|
(define-remap-test :other run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-rotation-token
|
||||||
|
(define-remap-test :rotation run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-sizing-token
|
||||||
|
(define-remap-test :sizing run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-spacing-token
|
||||||
|
(define-remap-test :spacing run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-string-token
|
||||||
|
(define-remap-test :string run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-stroke-width-token
|
||||||
|
(define-remap-test :stroke-width run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-text-case-token
|
||||||
|
(define-remap-test :text-case run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-text-decoration-token
|
||||||
|
(define-remap-test :text-decoration run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-font-weight-token
|
||||||
|
(define-remap-test :font-weight run-token-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-typography-token
|
||||||
|
(define-remap-test :typography run-token-remap-test!))
|
||||||
|
|
||||||
|
;; Alias remap tests
|
||||||
|
(t/deftest remap-boolean-alias
|
||||||
|
(define-remap-test :boolean run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-border-radius-alias
|
||||||
|
(define-remap-test :border-radius run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-shadow-alias
|
||||||
|
(define-remap-test :shadow run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-color-alias
|
||||||
|
(define-remap-test :color run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-dimensions-alias
|
||||||
|
(define-remap-test :dimensions run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-font-family-alias
|
||||||
|
(define-remap-test :font-family run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-font-size-alias
|
||||||
|
(define-remap-test :font-size run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-letter-spacing-alias
|
||||||
|
(define-remap-test :letter-spacing run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-number-alias
|
||||||
|
(define-remap-test :number run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-opacity-alias
|
||||||
|
(define-remap-test :opacity run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-other-alias
|
||||||
|
(define-remap-test :other run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-rotation-alias
|
||||||
|
(define-remap-test :rotation run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-sizing-alias
|
||||||
|
(define-remap-test :sizing run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-spacing-alias
|
||||||
|
(define-remap-test :spacing run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-string-alias
|
||||||
|
(define-remap-test :string run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-stroke-width-alias
|
||||||
|
(define-remap-test :stroke-width run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-text-case-alias
|
||||||
|
(define-remap-test :text-case run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-text-decoration-alias
|
||||||
|
(define-remap-test :text-decoration run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-font-weight-alias
|
||||||
|
(define-remap-test :font-weight run-alias-remap-test!))
|
||||||
|
|
||||||
|
(t/deftest remap-typography-alias
|
||||||
|
(define-remap-test :typography run-alias-remap-test!))
|
||||||
|
|
@ -6456,10 +6456,6 @@ msgstr "Nástroje"
|
||||||
msgid "workspace.tokens.value-not-valid"
|
msgid "workspace.tokens.value-not-valid"
|
||||||
msgstr "Hodnota není platná"
|
msgstr "Hodnota není platná"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Přejmenováním tohoto tokenu se přeruší jakýkoli odkaz na jeho starý název."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Položky"
|
msgstr "Položky"
|
||||||
|
|
|
||||||
|
|
@ -7175,12 +7175,6 @@ msgstr "Der Wert ist nicht gültig"
|
||||||
msgid "workspace.tokens.value-with-percent"
|
msgid "workspace.tokens.value-with-percent"
|
||||||
msgstr "Ungültiger Wert: % ist nicht zulässig."
|
msgstr "Ungültiger Wert: % ist nicht zulässig."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr ""
|
|
||||||
"Die Umbenennung dieses Tokens macht jeden Verweis auf seinen alten Namen "
|
|
||||||
"kaputt."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Assets"
|
msgstr "Assets"
|
||||||
|
|
|
||||||
|
|
@ -7543,6 +7543,42 @@ msgstr "Color"
|
||||||
msgid "workspace.tokens.composite-line-height-needs-font-size"
|
msgid "workspace.tokens.composite-line-height-needs-font-size"
|
||||||
msgstr "Line Height depends on Font Size. Add a Font Size to get the resolved value."
|
msgstr "Line Height depends on Font Size. Add a Font Size to get the resolved value."
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.remap-token-references"
|
||||||
|
msgstr "Remap Token References"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.renaming-token-from-to"
|
||||||
|
msgstr "Renaming token from '%s' to '%s'"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.references-found"
|
||||||
|
msgstr "%s references found in your design"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.remap-explanation"
|
||||||
|
msgstr "All references to this token will be automatically updated to use the new name."
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.no-references-found"
|
||||||
|
msgstr "No references found"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.no-remap-needed"
|
||||||
|
msgstr "This token is not currently used in your design, so no remapping is needed."
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.remapping-in-progress"
|
||||||
|
msgstr "Remapping token references..."
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.remap-and-rename"
|
||||||
|
msgstr "Remap & Rename"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.rename-only"
|
||||||
|
msgstr "Rename"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:78
|
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:78
|
||||||
msgid "workspace.tokens.create-new-theme"
|
msgid "workspace.tokens.create-new-theme"
|
||||||
msgstr "Create your first theme now."
|
msgstr "Create your first theme now."
|
||||||
|
|
@ -8076,7 +8112,7 @@ msgstr "Type '%s' is not supported (%s)\n"
|
||||||
msgid "workspace.tokens.use-reference"
|
msgid "workspace.tokens.use-reference"
|
||||||
msgstr "Use a reference"
|
msgstr "Use a reference"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:131
|
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:133
|
||||||
msgid "workspace.tokens.value-not-valid"
|
msgid "workspace.tokens.value-not-valid"
|
||||||
msgstr "The value is not valid"
|
msgstr "The value is not valid"
|
||||||
|
|
||||||
|
|
@ -8088,10 +8124,6 @@ msgstr "Invalid value: % is not allowed."
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Invalid value: Units are not allowed."
|
msgstr "Invalid value: Units are not allowed."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Renaming this token will break any reference to its old name."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Assets"
|
msgstr "Assets"
|
||||||
|
|
|
||||||
|
|
@ -4420,6 +4420,42 @@ msgstr "Mostrar/ocultar recursos"
|
||||||
msgid "shortcuts.toggle-colorpalette"
|
msgid "shortcuts.toggle-colorpalette"
|
||||||
msgstr "Mostrar/ocultar paleta de colores"
|
msgstr "Mostrar/ocultar paleta de colores"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.remap-token-references"
|
||||||
|
msgstr "Actualizar referencias de token"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.renaming-token-from-to"
|
||||||
|
msgstr "Renombrando el token de '%s' a '%s'"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.references-found"
|
||||||
|
msgstr "%s referencias encontradas en tu diseño"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.remap-explanation"
|
||||||
|
msgstr "Todas las referencias a este token se actualizarán automáticamente para usar el nuevo nombre."
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.no-references-found"
|
||||||
|
msgstr "No se encontraron referencias"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.no-remap-needed"
|
||||||
|
msgstr "Este token no se utiliza actualmente en tu diseño, por lo que no es necesario actualizar referencias."
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.remapping-in-progress"
|
||||||
|
msgstr "Actualizando referencias de token..."
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.remap-and-rename"
|
||||||
|
msgstr "Actualizar referencias y renombrar"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
|
||||||
|
msgid "workspace.tokens.rename-only"
|
||||||
|
msgstr "Renombrar"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:185
|
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:185
|
||||||
msgid "shortcuts.toggle-focus-mode"
|
msgid "shortcuts.toggle-focus-mode"
|
||||||
msgstr "Mostrar/ocultar focus mode"
|
msgstr "Mostrar/ocultar focus mode"
|
||||||
|
|
@ -7958,10 +7994,6 @@ msgstr "El valor no es válido"
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Valor no válido: No se permiten unidades."
|
msgstr "Valor no válido: No se permiten unidades."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Al renombrar este token se romperán las referencias al nombre anterior"
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Recursos"
|
msgstr "Recursos"
|
||||||
|
|
|
||||||
|
|
@ -7893,10 +7893,6 @@ msgstr "Valeur non valide : % n'est pas autorisé."
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Valeur non valide : les unités ne sont pas autorisées."
|
msgstr "Valeur non valide : les unités ne sont pas autorisées."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Si vous renommez ce token, toute référence à son ancien nom sera incorrecte."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Ressources"
|
msgstr "Ressources"
|
||||||
|
|
|
||||||
|
|
@ -7810,10 +7810,6 @@ msgstr "ערך שגוי: אסור %."
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "ערך שגוי: אסור יחידות."
|
msgstr "ערך שגוי: אסור יחידות."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "שינוי שם האסימון הזה יפגע בכל הפניה לשם הישן שלו."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "משאבים"
|
msgstr "משאבים"
|
||||||
|
|
|
||||||
|
|
@ -7300,10 +7300,6 @@ msgstr "मान मान्य नहीं है"
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "अमान्य मान: इकाइयाँ अनुमति नहीं हैं।"
|
msgstr "अमान्य मान: इकाइयाँ अनुमति नहीं हैं।"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "इस टोकन का नाम बदलने से इसके पुराने नाम के किसी भी संदर्भ टूट जाएंगे।"
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "एसेट्स"
|
msgstr "एसेट्स"
|
||||||
|
|
|
||||||
|
|
@ -6477,10 +6477,6 @@ msgstr "Alati"
|
||||||
msgid "workspace.tokens.value-not-valid"
|
msgid "workspace.tokens.value-not-valid"
|
||||||
msgstr "Vrijednost nije važeća"
|
msgstr "Vrijednost nije važeća"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Preimenovanje ovog tokena prekinut će sve reference na njegov stari naziv."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Stavke"
|
msgstr "Stavke"
|
||||||
|
|
|
||||||
|
|
@ -6855,10 +6855,6 @@ msgstr "Peralatan"
|
||||||
msgid "workspace.tokens.value-not-valid"
|
msgid "workspace.tokens.value-not-valid"
|
||||||
msgstr "Nilai tidak valid"
|
msgstr "Nilai tidak valid"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Mengubah nama token ini akan merusak referensi nama lamanya."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Aset"
|
msgstr "Aset"
|
||||||
|
|
|
||||||
|
|
@ -7940,12 +7940,6 @@ msgstr "Valore non valido: % non è consentito."
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Valore non valido: le unità non sono consentite."
|
msgstr "Valore non valido: le unità non sono consentite."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr ""
|
|
||||||
"Rinominare questo token interromperà qualsiasi riferimento al suo vecchio "
|
|
||||||
"nome."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Risorse"
|
msgstr "Risorse"
|
||||||
|
|
|
||||||
|
|
@ -7582,12 +7582,6 @@ msgstr "Vērtība nav derīga"
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Nederīga vērtība: mērvienības nav atļautas."
|
msgstr "Nederīga vērtība: mērvienības nav atļautas."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr ""
|
|
||||||
"Šīs tekstvienības pārdēvēšana salauzīs visas atsauces uz tās iepriekšējo "
|
|
||||||
"nosaukumu."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Līdzekļi"
|
msgstr "Līdzekļi"
|
||||||
|
|
|
||||||
|
|
@ -7968,12 +7968,6 @@ msgstr "Ongeldige waarde: % is niet toegestaan."
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Ongeldige waarde: Eenheden zijn niet toegestaan."
|
msgstr "Ongeldige waarde: Eenheden zijn niet toegestaan."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr ""
|
|
||||||
"Met het wijzigen van de naam van dit token, worden alle verwijzingen naar "
|
|
||||||
"de oude naam verbroken."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Assets"
|
msgstr "Assets"
|
||||||
|
|
|
||||||
|
|
@ -5661,10 +5661,6 @@ msgstr "Ferramentas"
|
||||||
msgid "workspace.tokens.value-not-valid"
|
msgid "workspace.tokens.value-not-valid"
|
||||||
msgstr "O valor não é válido"
|
msgstr "O valor não é válido"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Renomear este token quebrará quaisquer referência para o nome antigo."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Ativos"
|
msgstr "Ativos"
|
||||||
|
|
|
||||||
|
|
@ -7980,10 +7980,6 @@ msgstr "Valoare invalidă: % nu este permis."
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Valoare invalidă: Unitățile nu sunt permise."
|
msgstr "Valoare invalidă: Unitățile nu sunt permise."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Redenumirea acestui token va distruge orice referință la numele său vechi."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Obiecte"
|
msgstr "Obiecte"
|
||||||
|
|
|
||||||
|
|
@ -7928,10 +7928,6 @@ msgstr "Ogiltigt värde: % är inte tillåtet."
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Ogiltigt värde: Enheter är ej tillåtna."
|
msgstr "Ogiltigt värde: Enheter är ej tillåtna."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Om du byter namn på denna token bryts alla referenser till dess gamla namn."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Tillgångar"
|
msgstr "Tillgångar"
|
||||||
|
|
|
||||||
|
|
@ -7935,12 +7935,6 @@ msgstr "Geçersiz değer: % izin verilmiyor."
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Geçersiz değer: Birimlere izin verilmiyor."
|
msgstr "Geçersiz değer: Birimlere izin verilmiyor."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr ""
|
|
||||||
"Bu tokenin adını değiştirmek, eski adına yapılan tüm referansları "
|
|
||||||
"bozacaktır."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Varlıklar"
|
msgstr "Varlıklar"
|
||||||
|
|
|
||||||
|
|
@ -7437,10 +7437,6 @@ msgstr "Значення не є дійсним"
|
||||||
msgid "workspace.tokens.value-with-units"
|
msgid "workspace.tokens.value-with-units"
|
||||||
msgstr "Помилкове значення: Одиниці не дозволені."
|
msgstr "Помилкове значення: Одиниці не дозволені."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "Якщо перейменувати токен, посилання на старе імʼя буде розірвано."
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "Ресурси"
|
msgstr "Ресурси"
|
||||||
|
|
|
||||||
|
|
@ -6295,10 +6295,6 @@ msgstr "工具"
|
||||||
msgid "workspace.tokens.value-not-valid"
|
msgid "workspace.tokens.value-not-valid"
|
||||||
msgstr "該值無效"
|
msgstr "該值無效"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
|
|
||||||
msgid "workspace.tokens.warning-name-change"
|
|
||||||
msgstr "重新命名此權杖(token)將會中斷對其舊名稱的任何參照。"
|
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
|
||||||
msgid "workspace.toolbar.assets"
|
msgid "workspace.toolbar.assets"
|
||||||
msgstr "資源"
|
msgstr "資源"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue