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
|
||||
|
||||
- Remap references when renaming tokens [Taiga #10202](https://tree.taiga.io/project/penpot/us/10202)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,18 @@
|
|||
self-reference? (get token-references token-name)]
|
||||
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
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
@ -558,3 +570,18 @@
|
|||
"Predicate if a shadow composite token is a reference value - a string pointing to another reference token."
|
||||
[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
|
||||
`: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-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"))
|
||||
|
||||
(declare parse-multi-set-dtcg-json)
|
||||
|
|
@ -1306,6 +1307,10 @@ Will return a value that matches this schema:
|
|||
tokens))
|
||||
|
||||
(get-all-tokens [this]
|
||||
(mapcat #(vals (get-tokens- %))
|
||||
(get-sets this)))
|
||||
|
||||
(get-all-tokens-map [this]
|
||||
(reduce
|
||||
(fn [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
|
||||
(st/emit! (show-unknown-types-warning unknown-tokens)))
|
||||
(try
|
||||
(->> (ctob/get-all-tokens tokens-lib)
|
||||
(->> (ctob/get-all-tokens-map tokens-lib)
|
||||
(sd/resolve-tokens-with-verbose-errors)
|
||||
(rx/map (fn [_]
|
||||
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.modal]
|
||||
[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.themes.create-modal]
|
||||
[app.main.ui.workspace.viewport :refer [viewport*]]
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
props
|
||||
(mf/spread-props props {:token-type token-type
|
||||
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
||||
:tokens-in-selected-set tokens-in-selected-set
|
||||
:token token})
|
||||
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")})
|
||||
|
|
|
|||
|
|
@ -12,19 +12,21 @@
|
|||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.data.helpers :as dh]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||
[app.main.data.workspace.tokens.remapping :as remap]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[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.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.remapping-modal :as remapping-modal]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
|
|
@ -92,6 +94,7 @@
|
|||
initial
|
||||
type
|
||||
value-subfield
|
||||
tokens-in-selected-set
|
||||
input-value-placeholder] :as props}]
|
||||
|
||||
(let [make-schema (or make-schema default-make-schema)
|
||||
|
|
@ -121,11 +124,11 @@
|
|||
(mf/deref refs/workspace-active-theme-sets-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
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
(cond-> tokens
|
||||
(cond-> (merge tokens tokens-in-selected-set)
|
||||
(and (:name token) (:value token))
|
||||
(assoc (:name token) token)))
|
||||
|
||||
|
|
@ -144,10 +147,6 @@
|
|||
(fm/use-form :schema schema
|
||||
:initial initial)
|
||||
|
||||
warning-name-change?
|
||||
(not= (get-in @form [:data :name])
|
||||
(:name initial))
|
||||
|
||||
on-cancel
|
||||
(mf/use-fn
|
||||
(fn [e]
|
||||
|
|
@ -191,19 +190,38 @@
|
|||
:tokens tokens})
|
||||
(rx/subs!
|
||||
(fn [valid-token]
|
||||
(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))))))))]
|
||||
(let [state @st/state
|
||||
file-data (dh/lookup-file-data state)
|
||||
old-name (:name token)
|
||||
is-rename (and (= action "edit") (not= name old-name))
|
||||
references-count (remap/count-token-references file-data old-name)]
|
||||
(if (and is-rename (> references-count 0))
|
||||
(remapping-modal/show-remapping-modal
|
||||
{:old-token-name old-name
|
||||
:new-token-name name
|
||||
:references-count references-count
|
||||
:on-confirm (fn []
|
||||
(st/emit!
|
||||
(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)
|
||||
:form form
|
||||
|
|
@ -222,12 +240,7 @@
|
|||
:placeholder (tr "workspace.tokens.enter-token-name" token-title)
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
: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")]])]
|
||||
:auto-focus true}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
(case type
|
||||
|
|
|
|||
|
|
@ -50,10 +50,6 @@
|
|||
grid-template-columns: 1fr auto auto;
|
||||
}
|
||||
|
||||
.warning-name-change-notification-wrapper {
|
||||
margin-block-start: var(--sp-l);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
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-data-test]
|
||||
[frontend-tests.tokens.style-dictionary-test]
|
||||
[frontend-tests.tokens.workspace-tokens-remap-test]
|
||||
[frontend-tests.util-object-test]
|
||||
[frontend-tests.util-range-tree-test]
|
||||
[frontend-tests.util-simple-math-test]
|
||||
|
|
@ -49,4 +50,5 @@
|
|||
'frontend-tests.util-object-test
|
||||
'frontend-tests.util-range-tree-test
|
||||
'frontend-tests.util-simple-math-test
|
||||
'frontend-tests.tokens.workspace-tokens-remap-test
|
||||
'frontend-tests.worker-snap-test))
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.color :as clr]
|
||||
[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]
|
||||
[cljs.pprint :refer [pprint]]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[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]
|
||||
[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"
|
||||
:value "{borderRadius.sm} * 200000000"
|
||||
:type :border-radius}))
|
||||
(ctob/get-all-tokens))]
|
||||
(ctob/get-all-tokens-map))]
|
||||
(-> (sd/resolve-tokens tokens)
|
||||
(rx/sub!
|
||||
(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"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Položky"
|
||||
|
|
|
|||
|
|
@ -7175,12 +7175,6 @@ msgstr "Der Wert ist nicht gültig"
|
|||
msgid "workspace.tokens.value-with-percent"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Assets"
|
||||
|
|
|
|||
|
|
@ -7543,6 +7543,42 @@ msgstr "Color"
|
|||
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."
|
||||
|
||||
#: 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
|
||||
msgid "workspace.tokens.create-new-theme"
|
||||
msgstr "Create your first theme now."
|
||||
|
|
@ -8076,7 +8112,7 @@ msgstr "Type '%s' is not supported (%s)\n"
|
|||
msgid "workspace.tokens.use-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"
|
||||
msgstr "The value is not valid"
|
||||
|
||||
|
|
@ -8088,10 +8124,6 @@ msgstr "Invalid value: % is not allowed."
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Assets"
|
||||
|
|
|
|||
|
|
@ -4420,6 +4420,42 @@ msgstr "Mostrar/ocultar recursos"
|
|||
msgid "shortcuts.toggle-colorpalette"
|
||||
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
|
||||
msgid "shortcuts.toggle-focus-mode"
|
||||
msgstr "Mostrar/ocultar focus mode"
|
||||
|
|
@ -7958,10 +7994,6 @@ msgstr "El valor no es válido"
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Recursos"
|
||||
|
|
|
|||
|
|
@ -7893,10 +7893,6 @@ msgstr "Valeur non valide : % n'est pas autorisé."
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Ressources"
|
||||
|
|
|
|||
|
|
@ -7810,10 +7810,6 @@ msgstr "ערך שגוי: אסור %."
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "משאבים"
|
||||
|
|
|
|||
|
|
@ -7300,10 +7300,6 @@ msgstr "मान मान्य नहीं है"
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "एसेट्स"
|
||||
|
|
|
|||
|
|
@ -6477,10 +6477,6 @@ msgstr "Alati"
|
|||
msgid "workspace.tokens.value-not-valid"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Stavke"
|
||||
|
|
|
|||
|
|
@ -6855,10 +6855,6 @@ msgstr "Peralatan"
|
|||
msgid "workspace.tokens.value-not-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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Aset"
|
||||
|
|
|
|||
|
|
@ -7940,12 +7940,6 @@ msgstr "Valore non valido: % non è consentito."
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Risorse"
|
||||
|
|
|
|||
|
|
@ -7582,12 +7582,6 @@ msgstr "Vērtība nav derīga"
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Līdzekļi"
|
||||
|
|
|
|||
|
|
@ -7968,12 +7968,6 @@ msgstr "Ongeldige waarde: % is niet toegestaan."
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Assets"
|
||||
|
|
|
|||
|
|
@ -5661,10 +5661,6 @@ msgstr "Ferramentas"
|
|||
msgid "workspace.tokens.value-not-valid"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Ativos"
|
||||
|
|
|
|||
|
|
@ -7980,10 +7980,6 @@ msgstr "Valoare invalidă: % nu este permis."
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Obiecte"
|
||||
|
|
|
|||
|
|
@ -7928,10 +7928,6 @@ msgstr "Ogiltigt värde: % är inte tillåtet."
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Tillgångar"
|
||||
|
|
|
|||
|
|
@ -7935,12 +7935,6 @@ msgstr "Geçersiz değer: % izin verilmiyor."
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Varlıklar"
|
||||
|
|
|
|||
|
|
@ -7437,10 +7437,6 @@ msgstr "Значення не є дійсним"
|
|||
msgid "workspace.tokens.value-with-units"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Ресурси"
|
||||
|
|
|
|||
|
|
@ -6295,10 +6295,6 @@ msgstr "工具"
|
|||
msgid "workspace.tokens.value-not-valid"
|
||||
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
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "資源"
|
||||
|
|
|
|||
Loading…
Reference in New Issue