mirror of
https://github.com/penpot/penpot.git
synced 2026-01-21 04:40:48 -05:00
Compare commits
2 Commits
niwinz-dev
...
hiru-plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
362200ce99 | ||
|
|
8166d773bb |
2
.github/workflows/build-bundle.yml
vendored
2
.github/workflows/build-bundle.yml
vendored
@@ -40,7 +40,7 @@ on:
|
||||
jobs:
|
||||
build-bundle:
|
||||
name: Build and Upload Penpot Bundle
|
||||
runs-on: penpot-runner-01
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
5
.github/workflows/build-docker-devenv.yml
vendored
5
.github/workflows/build-docker-devenv.yml
vendored
@@ -10,11 +10,6 @@ jobs:
|
||||
runs-on: penpot-runner-02
|
||||
|
||||
steps:
|
||||
- name: Set common environment variables
|
||||
run: |
|
||||
# Each job execution will use its own docker configuration.
|
||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
14
.github/workflows/build-docker.yml
vendored
14
.github/workflows/build-docker.yml
vendored
@@ -22,11 +22,6 @@ jobs:
|
||||
runs-on: penpot-runner-02
|
||||
|
||||
steps:
|
||||
- name: Set common environment variables
|
||||
run: |
|
||||
# Each job execution will use its own docker configuration.
|
||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -71,15 +66,6 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
# To avoid the “429 Too Many Requests” error when downloading
|
||||
# images from DockerHub for unregistered users.
|
||||
# https://docs.docker.com/docker-hub/usage/
|
||||
- name: Login to DockerHub Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
- Tokens panel nested path view [Taiga #9966](https://tree.taiga.io/project/penpot/us/9966)
|
||||
- Improve usability of lock and hide buttons in the layer panel. [Taiga #12916](https://tree.taiga.io/project/penpot/issue/12916)
|
||||
- Optimize sidebar performance for deeply nested shapes [Taiga #13017](https://tree.taiga.io/project/penpot/task/13017)
|
||||
- Remove tokens path node and bulk remove tokens [Taiga #13007](https://tree.taiga.io/project/penpot/us/13007)
|
||||
- Replace themes management modal radio buttons for switches [Taiga #9215](https://tree.taiga.io/project/penpot/us/9215)
|
||||
- Change the default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
@@ -23,9 +22,6 @@
|
||||
- Fix wrong image in the onboarding invitation block [Taiga #13040](https://tree.taiga.io/project/penpot/issue/13040)
|
||||
- Fix wrong register image [Taiga #12955](https://tree.taiga.io/project/penpot/task/12955)
|
||||
- Fix error message on components doesn't close automatically [Taiga #12012](https://tree.taiga.io/project/penpot/issue/12012)
|
||||
- Fix incorrect default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051)
|
||||
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
|
||||
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
|
||||
|
||||
## 2.13.0 (Unreleased)
|
||||
|
||||
@@ -173,6 +169,7 @@ example. It's still usable as before, we just removed the example.
|
||||
|
||||
- Deprecated configuration variables with the prefix `PENPOT_ASSETS_*`, and will be
|
||||
removed in future versions:
|
||||
|
||||
- The `PENPOT_ASSETS_STORAGE_BACKEND` becomes `PENPOT_OBJECTS_STORAGE_BACKEND` and its
|
||||
values passes from (`assets-fs` or `assets-s3`) to (`fs` or `s3`)
|
||||
- The `PENPOT_STORAGE_ASSETS_FS_DIRECTORY` becomes `PENPOT_OBJECTS_STORAGE_FS_DIRECTORY`
|
||||
|
||||
@@ -487,7 +487,6 @@
|
||||
:vertical-margin #{:spacing :dimensions}
|
||||
:sided-margins #{:spacing :dimensions}
|
||||
:line-height #{:line-height :number}
|
||||
:opacity #{:opacity}
|
||||
:font-size #{:font-size}
|
||||
:letter-spacing #{:letter-spacing}
|
||||
:fill #{:color}
|
||||
|
||||
@@ -101,70 +101,6 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(brTokenPillXL).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies opacity token to a shape from sidebar", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
// Open tokens sections on left sidebar
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
// Unfold opacity tokens
|
||||
await page.getByRole("button", { name: "Opacity 3" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "opacity", exact: true }),
|
||||
).toBeVisible();
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "opacity", exact: true })
|
||||
.click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "opacity.high" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Apply opacity token from token panels
|
||||
await tokensSidebar.getByRole("button", { name: "opacity.high" }).click();
|
||||
|
||||
// Check if opacity sections is visible on right sidebar
|
||||
const layerMenuSection = page.getByRole("region", {
|
||||
name: "layer-menu-section",
|
||||
});
|
||||
await expect(layerMenuSection).toBeVisible();
|
||||
|
||||
// Check if token pill is visible on design tab on right sidebar
|
||||
const opacityHighPill = layerMenuSection.getByRole("button", {
|
||||
name: "opacity.high",
|
||||
});
|
||||
await expect(opacityHighPill).toBeVisible();
|
||||
|
||||
// Detach token from design tab on right sidebar
|
||||
const detachButton = layerMenuSection.getByRole("button", {
|
||||
name: "Detach token",
|
||||
});
|
||||
await detachButton.click();
|
||||
|
||||
// Open dropdown from input
|
||||
const dropdownBtn = layerMenuSection.getByLabel('Open token list');
|
||||
await expect(dropdownBtn).toBeVisible();
|
||||
await dropdownBtn.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const opacityLowOption = layerMenuSection.getByRole('option', { name: 'opacity.low' });
|
||||
await expect(opacityLowOption).toBeVisible();
|
||||
await opacityLowOption.click();
|
||||
|
||||
await expect(opacityHighPill).not.toBeVisible();
|
||||
const opacityLowPill = layerMenuSection.getByRole("button", {
|
||||
name: "opacity.low",
|
||||
});
|
||||
await expect(opacityLowPill).toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies typography token to a text shape", async ({ page }) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTypographyTokensFile(page);
|
||||
@@ -193,6 +129,189 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(fontSizeInput).toHaveValue("100");
|
||||
});
|
||||
|
||||
test("User edits typography token and all fields are valid", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button")
|
||||
.filter({ hasText: "Typography" })
|
||||
.click();
|
||||
|
||||
// Open edit modal for "Full" typography token
|
||||
const token = tokensSidebar.getByRole("button", { name: "Full" });
|
||||
await token.click({ button: "right" });
|
||||
await page.getByText("Edit token").click();
|
||||
|
||||
// Modal opens
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const saveButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: /save/i,
|
||||
});
|
||||
|
||||
// Fill font-family to verify to verify that input value doesn't get split into list of characters
|
||||
const fontFamilyField = tokensUpdateCreateModal
|
||||
.getByLabel("Font family")
|
||||
.first();
|
||||
await fontFamilyField.fill("OneWord");
|
||||
|
||||
// Invalidate incorrect values for font size
|
||||
const fontSizeField = tokensUpdateCreateModal.getByLabel(/Font Size/i);
|
||||
await fontSizeField.fill("invalid");
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByText(/Invalid token value:/),
|
||||
).toBeVisible();
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
// Show error with line-height depending on invalid font-size
|
||||
await fontSizeField.fill("");
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
// Fill in values for all fields and verify they persist when switching tabs
|
||||
await fontSizeField.fill("16");
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
const fontWeightField = tokensUpdateCreateModal.getByLabel(/Font Weight/i);
|
||||
const letterSpacingField =
|
||||
tokensUpdateCreateModal.getByLabel(/Letter Spacing/i);
|
||||
const lineHeightField = tokensUpdateCreateModal.getByLabel(/Line Height/i);
|
||||
const textCaseField = tokensUpdateCreateModal.getByLabel(/Text Case/i);
|
||||
const textDecorationField =
|
||||
tokensUpdateCreateModal.getByLabel(/Text Decoration/i);
|
||||
|
||||
// Capture all values before switching tabs
|
||||
const originalValues = {
|
||||
fontSize: await fontSizeField.inputValue(),
|
||||
fontFamily: await fontFamilyField.inputValue(),
|
||||
fontWeight: await fontWeightField.inputValue(),
|
||||
letterSpacing: await letterSpacingField.inputValue(),
|
||||
lineHeight: await lineHeightField.inputValue(),
|
||||
textCase: await textCaseField.inputValue(),
|
||||
textDecoration: await textDecorationField.inputValue(),
|
||||
};
|
||||
|
||||
// Switch to reference tab and back to composite tab
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceTabButton.click();
|
||||
|
||||
// Empty reference tab should be disabled
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
const compositeTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("composite-opt");
|
||||
await compositeTabButton.click();
|
||||
|
||||
// Filled composite tab should be enabled
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
// Verify all values are preserved after switching tabs
|
||||
await expect(fontSizeField).toHaveValue(originalValues.fontSize);
|
||||
await expect(fontFamilyField).toHaveValue(originalValues.fontFamily);
|
||||
await expect(fontWeightField).toHaveValue(originalValues.fontWeight);
|
||||
await expect(letterSpacingField).toHaveValue(originalValues.letterSpacing);
|
||||
await expect(lineHeightField).toHaveValue(originalValues.lineHeight);
|
||||
await expect(textCaseField).toHaveValue(originalValues.textCase);
|
||||
await expect(textDecorationField).toHaveValue(
|
||||
originalValues.textDecoration,
|
||||
);
|
||||
|
||||
await saveButton.click();
|
||||
|
||||
// Modal should close, token should be visible (with new name) in sidebar
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User cant submit empty typography token or reference", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("typography.empty");
|
||||
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Font Size");
|
||||
|
||||
// Insert a value and then delete it
|
||||
await valueField.fill("1");
|
||||
await valueField.fill("");
|
||||
|
||||
// Submit button should be disabled when field is empty
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Switch to reference tab, should not be submittable either
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceTabButton.click();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test("User adds typography token with reference", async ({ page }) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const newTokenTitle = "NewReference";
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill(newTokenTitle);
|
||||
|
||||
const referenceTabButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Use a reference",
|
||||
});
|
||||
referenceTabButton.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{Full}");
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
|
||||
const resolvedValue =
|
||||
await tokensUpdateCreateModal.getByText("Resolved value:");
|
||||
await expect(resolvedValue).toBeVisible();
|
||||
await expect(resolvedValue).toContainText("Font Family: 42dot Sans");
|
||||
await expect(resolvedValue).toContainText("Font Size: 100");
|
||||
await expect(resolvedValue).toContainText("Font Weight: 300");
|
||||
await expect(resolvedValue).toContainText("Letter Spacing: 2");
|
||||
await expect(resolvedValue).toContainText("Text Case: uppercase");
|
||||
await expect(resolvedValue).toContainText("Text Decoration: underline");
|
||||
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
const newToken = tokensSidebar.getByRole("button", {
|
||||
name: newTokenTitle,
|
||||
});
|
||||
|
||||
await expect(newToken).toBeVisible();
|
||||
});
|
||||
|
||||
test("User adds shadow token with multiple shadows and applies it to shape", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@@ -14,7 +14,7 @@ test.beforeEach(async ({ page }) => {
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
|
||||
});
|
||||
|
||||
test.describe("Tokens - creation", () => {
|
||||
test.describe("Tokens - CRUD", () => {
|
||||
test("User creates border radius token", async ({ page }) => {
|
||||
await testTokenCreationFlow(page, {
|
||||
tokenLabel: "Border Radius",
|
||||
@@ -1008,41 +1008,6 @@ test.describe("Tokens - creation", () => {
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
test("User cant submit empty typography token or reference", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("typography.empty");
|
||||
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Font Size");
|
||||
|
||||
// Insert a value and then delete it
|
||||
await valueField.fill("1");
|
||||
await valueField.fill("");
|
||||
|
||||
// Submit button should be disabled when field is empty
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Switch to reference tab, should not be submittable either
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceTabButton.click();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test("User creates typography token", async ({ page }) => {
|
||||
const emptyNameError = "Name should be at least 1 character";
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||
@@ -1291,228 +1256,6 @@ test.describe("Tokens - creation", () => {
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
test("User adds typography token with reference", async ({ page }) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const newTokenTitle = "NewReference";
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill(newTokenTitle);
|
||||
|
||||
const referenceTabButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Use a reference",
|
||||
});
|
||||
referenceTabButton.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{Full}");
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
|
||||
const resolvedValue =
|
||||
await tokensUpdateCreateModal.getByText("Resolved value:");
|
||||
await expect(resolvedValue).toBeVisible();
|
||||
await expect(resolvedValue).toContainText("Font Family: 42dot Sans");
|
||||
await expect(resolvedValue).toContainText("Font Size: 100");
|
||||
await expect(resolvedValue).toContainText("Font Weight: 300");
|
||||
await expect(resolvedValue).toContainText("Letter Spacing: 2");
|
||||
await expect(resolvedValue).toContainText("Text Case: uppercase");
|
||||
await expect(resolvedValue).toContainText("Text Decoration: underline");
|
||||
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
const newToken = tokensSidebar.getByRole("button", {
|
||||
name: newTokenTitle,
|
||||
});
|
||||
|
||||
await expect(newToken).toBeVisible();
|
||||
});
|
||||
|
||||
test("User creates grouped color token", async ({ page }) => {
|
||||
const { workspacePage, tokensUpdateCreateModal, tokensSidebar } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
|
||||
// Create grouped color token with mouse
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
|
||||
await nameField.click();
|
||||
await nameField.fill("dark.primary");
|
||||
|
||||
await valueField.click();
|
||||
await valueField.fill("red");
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
|
||||
|
||||
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
|
||||
});
|
||||
|
||||
test("User cant create regular token with value missing", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal } = await setupEmptyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
|
||||
// Initially submit button should be disabled
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Fill in name but leave value empty
|
||||
await nameField.click();
|
||||
await nameField.fill("primary");
|
||||
|
||||
// Submit button should remain disabled when value is empty
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test("User duplicate color token", async ({ page }) => {
|
||||
const { tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
});
|
||||
|
||||
await colorToken.click({ button: "right" });
|
||||
await expect(tokenContextMenuForToken).toBeVisible();
|
||||
|
||||
await tokenContextMenuForToken.getByText("Duplicate token").click();
|
||||
await expect(tokenContextMenuForToken).not.toBeVisible();
|
||||
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "colors.blue.100-copy" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
test("User creates grouped color token", async ({ page }) => {
|
||||
const { workspacePage, tokensUpdateCreateModal, tokensSidebar } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
|
||||
// Create grouped color token with mouse
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
|
||||
await nameField.click();
|
||||
await nameField.fill("dark.primary");
|
||||
|
||||
await valueField.click();
|
||||
await valueField.fill("red");
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
|
||||
|
||||
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
|
||||
});
|
||||
|
||||
test("User cant create regular token with value missing", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal } = await setupEmptyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
|
||||
// Initially submit button should be disabled
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Fill in name but leave value empty
|
||||
await nameField.click();
|
||||
await nameField.fill("primary");
|
||||
|
||||
// Submit button should remain disabled when value is empty
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test("User duplicate color token", async ({ page }) => {
|
||||
const { tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
});
|
||||
|
||||
await colorToken.click({ button: "right" });
|
||||
await expect(tokenContextMenuForToken).toBeVisible();
|
||||
|
||||
await tokenContextMenuForToken.getByText("Duplicate token").click();
|
||||
await expect(tokenContextMenuForToken).not.toBeVisible();
|
||||
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "colors.blue.100-copy" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe("Tokens tab - edition", () => {
|
||||
test("User edits typography token and all fields are valid", async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -1645,7 +1388,67 @@ test.describe("Tokens tab - edition", () => {
|
||||
await expect(colorTokenChanged).toBeVisible();
|
||||
});
|
||||
|
||||
test("User edits color token color while keeping custom color space", async ({
|
||||
test("User creates grouped color token", async ({ page }) => {
|
||||
const { workspacePage, tokensUpdateCreateModal, tokensSidebar } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
|
||||
// Create grouped color token with mouse
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
|
||||
await nameField.click();
|
||||
await nameField.fill("dark.primary");
|
||||
|
||||
await valueField.click();
|
||||
await valueField.fill("red");
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
|
||||
|
||||
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
|
||||
});
|
||||
|
||||
test("User cant create regular token with value missing", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal } = await setupEmptyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
|
||||
// Initially submit button should be disabled
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Fill in name but leave value empty
|
||||
await nameField.click();
|
||||
await nameField.fill("primary");
|
||||
|
||||
// Submit button should remain disabled when value is empty
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test("User changes color token color while keeping custom color space", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||
@@ -1699,9 +1502,30 @@ test.describe("Tokens tab - edition", () => {
|
||||
await valueSaturationSelector.click({ position: { x: 0, y: 0 } });
|
||||
await expect(valueField).toHaveValue(/^rgba(.*)$/);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Tokens tab - delete", () => {
|
||||
test("User duplicate color token", async ({ page }) => {
|
||||
const { tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
});
|
||||
|
||||
await colorToken.click({ button: "right" });
|
||||
await expect(tokenContextMenuForToken).toBeVisible();
|
||||
|
||||
await tokenContextMenuForToken.getByText("Duplicate token").click();
|
||||
await expect(tokenContextMenuForToken).not.toBeVisible();
|
||||
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "colors.blue.100-copy" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User delete color token", async ({ page }) => {
|
||||
const { tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
@@ -1722,40 +1546,4 @@ test.describe("Tokens tab - delete", () => {
|
||||
await expect(tokenContextMenuForToken).not.toBeVisible();
|
||||
await expect(colorToken).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User removes node and all child tokens", async ({ page }) => {
|
||||
const { tokensSidebar, workspacePage } = await setupTokensFile(page);
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
// Expand color tokens
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
// Verify that the node and child token are visible before deletion
|
||||
const colorNode = tokensSidebar.getByRole("button", {
|
||||
name: "blue",
|
||||
exact: true,
|
||||
});
|
||||
const colorNodeToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
});
|
||||
|
||||
// Select a node and right click on it to open context menu
|
||||
await expect(colorNode).toBeVisible();
|
||||
await expect(colorNodeToken).toBeVisible();
|
||||
await colorNode.click({ button: "right" });
|
||||
|
||||
// select "Delete" from the context menu
|
||||
const deleteNodeButton = page.getByRole("button", {
|
||||
name: "Delete",
|
||||
exact: true,
|
||||
});
|
||||
await expect(deleteNodeButton).toBeVisible();
|
||||
await deleteNodeButton.click();
|
||||
|
||||
// Verify that the node is removed
|
||||
await expect(colorNode).not.toBeVisible();
|
||||
// Verify that child token is also removed
|
||||
await expect(colorNodeToken).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
--app-background: var(--color-background-primary);
|
||||
--loader-background: var(--color-background-primary);
|
||||
--panel-title-background-color: var(--color-background-secondary);
|
||||
|
||||
// BUTTONS
|
||||
--button-foreground-hover: var(--color-accent-primary);
|
||||
|
||||
@@ -433,22 +433,11 @@
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [data (dsh/lookup-file-data state)
|
||||
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/set-token set-id token-id nil))]
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
|
||||
(defn bulk-delete-tokens
|
||||
[set-id token-ids]
|
||||
(dm/assert! (uuid? set-id))
|
||||
(dm/assert! (every? uuid? token-ids))
|
||||
(ptk/reify ::bulk-delete-tokens
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(apply rx/of
|
||||
(map #(delete-token set-id %) token-ids)))))
|
||||
|
||||
(defn duplicate-token
|
||||
[token-id]
|
||||
(dm/assert! (uuid? token-id))
|
||||
@@ -516,19 +505,6 @@
|
||||
(update state :workspace-tokens assoc :token-context-menu params)
|
||||
(update state :workspace-tokens dissoc :token-context-menu)))))
|
||||
|
||||
(defn assign-token-node-context-menu
|
||||
[{:keys [position] :as params}]
|
||||
|
||||
(when params
|
||||
(assert (gpt/point? position) "expected a point instance for `position` param"))
|
||||
|
||||
(ptk/reify ::show-token-node-context-menu
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if params
|
||||
(update state :workspace-tokens assoc :token-node-context-menu params)
|
||||
(update state :workspace-tokens dissoc :token-node-context-menu)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TOKEN-SET UI OPS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -483,9 +483,6 @@
|
||||
(def workspace-active-theme-paths
|
||||
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))
|
||||
|
||||
(def workspace-all-tokens-map
|
||||
(l/derived (d/nilf ctob/get-all-tokens-map) tokens-lib))
|
||||
|
||||
(defn token-sets-at-path-all-active
|
||||
[group-path]
|
||||
(l/derived
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.ds.product.milestone :refer [milestone*]]
|
||||
[app.main.ui.ds.product.milestone-group :refer [milestone-group*]]
|
||||
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||
[app.main.ui.ds.storybook :as sb]
|
||||
[app.main.ui.ds.tooltip.tooltip :refer [tooltip*]]
|
||||
[app.main.ui.ds.utilities.date :refer [date*]]
|
||||
@@ -82,7 +81,6 @@
|
||||
:Milestone milestone*
|
||||
:MilestoneGroup milestone-group*
|
||||
:Date date*
|
||||
:PanelTitle panel-title*
|
||||
|
||||
:set-default-translations
|
||||
(fn [data]
|
||||
|
||||
@@ -216,7 +216,7 @@
|
||||
is-selected-on-focus nillable
|
||||
tokens applied-token empty-to-end
|
||||
on-change on-blur on-focus on-detach
|
||||
property align ref name]
|
||||
property align ref]
|
||||
:rest props}]
|
||||
|
||||
(let [;; NOTE: we use mfu/bean here for transparently handle
|
||||
@@ -662,10 +662,7 @@
|
||||
label (get token :name)
|
||||
token-value (or (get token :resolved-value)
|
||||
(or (mf/ref-val last-value*)
|
||||
(fmt/format-number value)))
|
||||
token-value (if (= name :opacity)
|
||||
(* 100 token-value)
|
||||
token-value)]
|
||||
(fmt/format-number value)))]
|
||||
(mf/spread-props props
|
||||
{:id id
|
||||
:label label
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/typography.scss" as *;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.option-list {
|
||||
--options-dropdown-icon-fg-color: var(--color-foreground-secondary);
|
||||
@@ -16,32 +15,32 @@
|
||||
--options-dropdown-border-color: var(--color-background-quaternary);
|
||||
|
||||
position: absolute;
|
||||
inset-block-start: $sz-36;
|
||||
inline-size: var(--dropdown-width, 100%);
|
||||
top: $sz-36;
|
||||
width: var(--dropdown-width, 100%);
|
||||
transform: translateX(var(--dropdown-translate-distance, 0));
|
||||
background-color: var(--options-dropdown-bg-color);
|
||||
border-radius: $br-8;
|
||||
border: $b-1 solid var(--options-dropdown-border-color);
|
||||
padding-block: var(--sp-xs);
|
||||
margin-block-end: 0;
|
||||
max-block-size: $sz-400;
|
||||
max-height: $sz-400;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
z-index: var(--z-index-dropdown);
|
||||
}
|
||||
|
||||
.left-align {
|
||||
inset-inline-start: var(--dropdown-offset, 0);
|
||||
left: var(--dropdown-offset, 0);
|
||||
}
|
||||
|
||||
.right-align {
|
||||
inset-inline-end: var(--dropdown-offset, 0);
|
||||
right: var(--dropdown-offset, 0);
|
||||
}
|
||||
|
||||
.option-separator {
|
||||
border: $b-1 solid var(--options-dropdown-border-color);
|
||||
margin-block-start: var(--sp-xs);
|
||||
margin-block-end: var(--sp-xs);
|
||||
margin-top: var(--sp-xs);
|
||||
margin-bottom: var(--sp-xs);
|
||||
}
|
||||
|
||||
.group-option,
|
||||
@@ -52,11 +51,11 @@
|
||||
gap: var(--sp-xs);
|
||||
color: var(--color-foreground-secondary);
|
||||
padding-inline: var(--sp-s);
|
||||
block-size: var(--sp-xxxl);
|
||||
height: var(--sp-xxxl);
|
||||
}
|
||||
|
||||
.option-empty {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 0 px2rem(40);
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
@@ -19,19 +19,17 @@
|
||||
[:expandable {:optional true} :boolean]
|
||||
[:expanded {:optional true} :boolean]
|
||||
[:icon {:optional true} :string]
|
||||
[:on-toggle-expand {:optional true} fn?]
|
||||
[:on-context-menu {:optional true} fn?]])
|
||||
[:on-toggle-expand fn?]])
|
||||
|
||||
(mf/defc layer-button*
|
||||
{::mf/schema schema:layer-button}
|
||||
[{:keys [label description class is-expandable expanded icon on-toggle-expand on-context-menu children] :rest props}]
|
||||
[{:keys [label description class is-expandable expanded icon on-toggle-expand children] :rest props}]
|
||||
(let [button-props (mf/spread-props props
|
||||
{:class [class (stl/css-case :layer-button true
|
||||
:layer-button--expandable is-expandable
|
||||
:layer-button--expanded expanded)]
|
||||
:type "button"
|
||||
:on-click on-toggle-expand
|
||||
:on-context-menu on-context-menu})]
|
||||
:on-click on-toggle-expand})]
|
||||
[:div {:class (stl/css :layer-button-wrapper)}
|
||||
[:> "button" button-props
|
||||
[:div {:class (stl/css :layer-button-content)}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
;; 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.ds.product.panel-title
|
||||
(:require-macros
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema:panel-title
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:text :string]
|
||||
[:on-close {:optional true} fn?]])
|
||||
|
||||
(mf/defc panel-title*
|
||||
{::mf/schema schema:panel-title}
|
||||
[{:keys [class text on-close] :rest props}]
|
||||
(let [props
|
||||
(mf/spread-props props {:class [class (stl/css :panel-title)]})]
|
||||
|
||||
[:> :div props
|
||||
[:span {:class (stl/css :panel-title-text)} text]
|
||||
(when on-close
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click on-close
|
||||
:icon i/close}])]))
|
||||
@@ -1,26 +0,0 @@
|
||||
{ /* 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 */ }
|
||||
|
||||
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
|
||||
import * as PanelTitle from "./panel_title.stories";
|
||||
|
||||
<Meta title="Product/PanelTitle" />
|
||||
|
||||
# PanelTitle
|
||||
|
||||
The `panel-title*` is used as a header for some sidebar sections.
|
||||
|
||||
<Canvas of={PanelTitle.Default} />
|
||||
|
||||
## Technical notes
|
||||
|
||||
The only mandatory parameter is `text`. Usually you'll want to pass a function property `on-close` that will be called when the user clicks on the close button on the right.
|
||||
|
||||
```clj
|
||||
[:> panel-title* {:class class
|
||||
:text text
|
||||
:on-close on-close}]
|
||||
```
|
||||
@@ -1,25 +0,0 @@
|
||||
// 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/_borders.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
block-size: $sz-32;
|
||||
border-radius: $br-8;
|
||||
background-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.panel-title-text {
|
||||
@include t.use-typography("headline-small");
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Components from "@target/components";
|
||||
|
||||
const { PanelTitle } = Components;
|
||||
|
||||
export default {
|
||||
title: "Product/PanelTitle",
|
||||
component: PanelTitle,
|
||||
argTypes: {
|
||||
text: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
text: "Lorem ipsum",
|
||||
onClose: () => null,
|
||||
},
|
||||
render: ({ ...args }) => <PanelTitle {...args} />,
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
@@ -312,8 +312,8 @@
|
||||
[]
|
||||
(let [on-reload (mf/use-fn #(js/location.reload))]
|
||||
[:> error-container* {}
|
||||
[:div {:class (stl/css :main-message)} (tr "errors.webgl-context-lost.main-message")]
|
||||
[:div {:class (stl/css :desc-message)} (tr "errors.webgl-context-lost.desc-message")]
|
||||
[:div {:class (stl/css :main-message)} (tr "labels.webgl-context-lost.main-message")]
|
||||
[:div {:class (stl/css :desc-message)} (tr "labels.webgl-context-lost.desc-message")]
|
||||
[:div {:class (stl/css :buttons-container)}
|
||||
[:> button* {:variant "primary" :on-click on-reload}
|
||||
(tr "labels.reload-page")]]]))
|
||||
|
||||
@@ -276,11 +276,7 @@
|
||||
:wglobal wglobal
|
||||
:layout layout}])
|
||||
(when (or (not (and file-loaded? page-id))
|
||||
;; in wasm renderer, extend the pixel loader until the first frame is rendered
|
||||
;; but do not apply it when switching pages
|
||||
(and wasm-renderer-enabled?
|
||||
(not file-loaded?)
|
||||
(not @first-frame-rendered?)))
|
||||
(and wasm-renderer-enabled? (not @first-frame-rendered?)))
|
||||
[:> workspace-loader*])]]]]]]))
|
||||
|
||||
(mf/defc workspace-page*
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
[app.main.ui.comments :as cmt]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -121,12 +121,15 @@
|
||||
(st/emit! (with-meta (dcmt/open-thread thread) {::ev/origin "viewer"}))
|
||||
(st/emit! (dwcm/navigate-to-comment thread)))))]
|
||||
|
||||
[:div {:class (stl/css-case :comments-section true
|
||||
:from-viewer from-viewer)}
|
||||
|
||||
[:> panel-title* {:class (stl/css :comments-title)
|
||||
:text (tr "labels.comments")
|
||||
:on-close close-section}]
|
||||
[:div {:class (stl/css-case :comments-section true
|
||||
:from-viewer from-viewer)}
|
||||
[:div {:class (stl/css-case :comments-section-title true
|
||||
:viewer-title from-viewer)}
|
||||
[:span (tr "labels.comments")]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click close-section
|
||||
:icon i/close}]]
|
||||
|
||||
[:button {:class (stl/css :mode-dropdown-wrapper)
|
||||
:on-click toggle-mode-selector}
|
||||
|
||||
@@ -18,8 +18,25 @@
|
||||
padding: 0 deprecated.$s-8;
|
||||
}
|
||||
|
||||
.comments-title {
|
||||
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
|
||||
.comments-section-title {
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
position: relative;
|
||||
height: deprecated.$s-32;
|
||||
min-height: deprecated.$s-32;
|
||||
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--panel-title-background-color);
|
||||
span {
|
||||
@include deprecated.flexCenter;
|
||||
flex-grow: 1;
|
||||
color: var(--title-foreground-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.viewer-title {
|
||||
margin: 0;
|
||||
margin-block-start: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.mode-dropdown-wrapper {
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.debug :as dbg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc debug-panel*
|
||||
@@ -34,9 +35,12 @@
|
||||
(st/emit! (dw/remove-layout-flag :debug-panel))))]
|
||||
|
||||
[:div {:class (dm/str class " " (stl/css :debug-panel))}
|
||||
[:> panel-title* {:class (stl/css :debug-panel-title)
|
||||
:text (tr "workspace.debug.title")
|
||||
:on-close handle-close}]
|
||||
[:div {:class (stl/css :panel-title)}
|
||||
[:span "Debugging tools"]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click handle-close
|
||||
:icon i/close}]]
|
||||
|
||||
[:div {:class (stl/css :debug-panel-inner)}
|
||||
(for [option (sort-by d/name dbg/options)]
|
||||
|
||||
@@ -12,12 +12,21 @@
|
||||
background-color: var(--panel-background-color);
|
||||
}
|
||||
|
||||
.debug-panel-title {
|
||||
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
|
||||
}
|
||||
.panel-title {
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
position: relative;
|
||||
height: deprecated.$s-32;
|
||||
min-height: deprecated.$s-32;
|
||||
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--panel-title-background-color);
|
||||
|
||||
.debug-panel-inner {
|
||||
padding: deprecated.$s-16 deprecated.$s-8;
|
||||
span {
|
||||
@include deprecated.flexCenter;
|
||||
flex-grow: 1;
|
||||
color: var(--title-foreground-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-wrapper {
|
||||
@@ -30,3 +39,7 @@
|
||||
@extend .checkbox-icon;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.debug-panel-inner {
|
||||
padding: deprecated.$s-16 deprecated.$s-8;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[debug :as dbg]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
@@ -125,9 +125,11 @@
|
||||
(map (d/getf objects)))]
|
||||
|
||||
[:div {:class (stl/css :shape-info)}
|
||||
[:> panel-title* {:class (stl/css :shape-info-title)
|
||||
:text "Debug"
|
||||
:on-close #(dbg/disable! :shape-panel)}]
|
||||
[:div {:class (stl/css :shape-info-title)}
|
||||
[:span "Debug"]
|
||||
[:div {:class (stl/css :close-button)
|
||||
:on-click #(dbg/disable! :shape-panel)}
|
||||
deprecated-icon/close]]
|
||||
|
||||
(if (empty? selected)
|
||||
[:div {:class (stl/css :attrs-container)} "No shapes selected"]
|
||||
|
||||
@@ -16,7 +16,34 @@
|
||||
}
|
||||
|
||||
.shape-info-title {
|
||||
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
position: relative;
|
||||
height: deprecated.$s-32;
|
||||
min-height: deprecated.$s-32;
|
||||
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--panel-title-background-color);
|
||||
|
||||
span {
|
||||
@include deprecated.flexCenter;
|
||||
flex-grow: 1;
|
||||
color: var(--title-foreground-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.close-button {
|
||||
@extend .button-tertiary;
|
||||
position: absolute;
|
||||
right: deprecated.$s-2;
|
||||
top: deprecated.$s-2;
|
||||
height: deprecated.$s-28;
|
||||
width: deprecated.$s-28;
|
||||
border-radius: deprecated.$br-6;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.attrs-container {
|
||||
|
||||
@@ -13,6 +13,23 @@
|
||||
background-color: var(--panel-background-color);
|
||||
}
|
||||
|
||||
.history-toolbox-title {
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
position: relative;
|
||||
height: deprecated.$s-32;
|
||||
min-height: deprecated.$s-32;
|
||||
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--panel-title-background-color);
|
||||
|
||||
span {
|
||||
@include deprecated.flexCenter;
|
||||
flex-grow: 1;
|
||||
color: var(--title-foreground-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.history-entry-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
|
||||
@@ -9,17 +9,13 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -43,16 +39,11 @@
|
||||
(defn- check-layer-menu-props
|
||||
[old-props new-props]
|
||||
(let [old-values (unchecked-get old-props "values")
|
||||
new-values (unchecked-get new-props "values")
|
||||
|
||||
old-applied-tokens (unchecked-get old-props "appliedTokens")
|
||||
new-applied-tokens (unchecked-get new-props "appliedTokens")]
|
||||
new-values (unchecked-get new-props "values")]
|
||||
(and (identical? (unchecked-get old-props "class")
|
||||
(unchecked-get new-props "class"))
|
||||
(identical? (unchecked-get old-props "ids")
|
||||
(unchecked-get new-props "ids"))
|
||||
(identical? old-applied-tokens
|
||||
new-applied-tokens)
|
||||
(identical? (get old-values :opacity)
|
||||
(get new-values :opacity))
|
||||
(identical? (get old-values :blend-mode)
|
||||
@@ -62,53 +53,12 @@
|
||||
(identical? (get old-values :hidden)
|
||||
(get new-values :hidden)))))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
tokens (mf/with-memo [tokens name]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
|
||||
on-detach-attr (mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
applied-token (get applied-tokens name)
|
||||
opacity-value (or (get values name) 1)
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder (if (or (= :multiple (:applied-tokens values))
|
||||
(= :multiple opacity-value))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-token
|
||||
:tokens (if (delay? tokens) @tokens tokens)
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:name name
|
||||
:value (* 100 opacity-value)})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
(mf/defc layer-menu*
|
||||
{::mf/wrap [#(mf/memo' % check-layer-menu-props)]}
|
||||
[{:keys [ids values applied-tokens]}]
|
||||
(let [token-numeric-inputs
|
||||
(features/use-feature "tokens/numeric-input")
|
||||
|
||||
hidden? (get values :hidden)
|
||||
[{:keys [ids values]}]
|
||||
(let [hidden? (get values :hidden)
|
||||
blocked? (get values :blocked)
|
||||
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
current-blend-mode (or (get values :blend-mode) :normal)
|
||||
current-opacity (opacity->string (:opacity values))
|
||||
|
||||
@@ -168,17 +118,6 @@
|
||||
(let [value (/ value 100)]
|
||||
(on-change ids :opacity value))))
|
||||
|
||||
on-opacity-change
|
||||
(mf/use-fn
|
||||
(mf/deps on-change handle-opacity-change)
|
||||
(fn [value]
|
||||
(if (or (string? value) (int? value))
|
||||
(handle-opacity-change value)
|
||||
(do
|
||||
(st/emit! (dwta/toggle-token {:token (first value)
|
||||
:attrs #{:opacity}
|
||||
:shape-ids ids}))))))
|
||||
|
||||
handle-set-hidden
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
@@ -237,9 +176,8 @@
|
||||
preview-complete?))
|
||||
(swap! state* assoc :selected-blend-mode current-blend-mode)))
|
||||
|
||||
[:section {:class (stl/css-case :element-set-content true
|
||||
:hidden hidden?)
|
||||
:aria-label "layer-menu-section"}
|
||||
[:div {:class (stl/css-case :element-set-content true
|
||||
:hidden hidden?)}
|
||||
[:div {:class (stl/css :select)}
|
||||
[:& select
|
||||
{:default-value selected-blend-mode
|
||||
@@ -249,34 +187,16 @@
|
||||
:class (stl/css-case :hidden-select hidden?)
|
||||
:on-pointer-enter-option handle-blend-mode-enter
|
||||
:on-pointer-leave-option handle-blend-mode-leave}]]
|
||||
|
||||
|
||||
|
||||
(if token-numeric-inputs
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-opacity-change
|
||||
:on-detach on-detach-token
|
||||
:icon i/percentage
|
||||
:min 0
|
||||
:max 100
|
||||
:name :opacity
|
||||
:property (tr "workspace.options.opacity")
|
||||
:applied-tokens applied-tokens
|
||||
:align :right
|
||||
:class (stl/css :numeric-input-wrapper)
|
||||
:values values}]
|
||||
|
||||
[:div {:class (stl/css :input)
|
||||
:title (tr "workspace.options.opacity")}
|
||||
[:span {:class (stl/css :icon)} "%"]
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:value current-opacity
|
||||
:placeholder "--"
|
||||
:on-change handle-opacity-change
|
||||
:min 0
|
||||
:max 100
|
||||
:className (stl/css :numeric-input)}]])
|
||||
[:div {:class (stl/css :input)
|
||||
:title (tr "workspace.options.opacity")}
|
||||
[:span {:class (stl/css :icon)} "%"]
|
||||
[:> numeric-input*
|
||||
{:value current-opacity
|
||||
:placeholder "--"
|
||||
:on-change handle-opacity-change
|
||||
:min 0
|
||||
:max 100
|
||||
:className (stl/css :numeric-input)}]]
|
||||
|
||||
|
||||
[:div {:class (stl/css :actions)}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "../../../sidebar/common/sidebar.scss" as sidebar;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.element-set-content {
|
||||
@include sidebar.option-grid-structure;
|
||||
@@ -44,9 +43,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.numeric-input-wrapper {
|
||||
grid-column: span 2;
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
--dropdown-offset: #{px2rem(-35)};
|
||||
}
|
||||
|
||||
@@ -85,7 +85,6 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
|
||||
[:> measures-menu* {:ids ids
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
|
||||
[:> measures-menu* {:ids ids
|
||||
|
||||
@@ -100,7 +100,6 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type shape-type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:ids ids
|
||||
:applied-tokens applied-tokens
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
[:div {:class (stl/css :options)}
|
||||
[:> layer-menu* {:type type
|
||||
:ids layer-ids
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:type type
|
||||
:ids measure-ids
|
||||
|
||||
@@ -382,7 +382,7 @@
|
||||
objects
|
||||
objects)))
|
||||
|
||||
[layer-ids layer-values layer-tokens]
|
||||
[layer-ids layer-values]
|
||||
(get-attrs shapes objects :layer)
|
||||
|
||||
[text-ids text-values]
|
||||
@@ -406,7 +406,7 @@
|
||||
[exports-ids exports-values]
|
||||
(get-attrs shapes objects :exports)
|
||||
|
||||
[layout-container-ids layout-container-values layout-container-tokens]
|
||||
[layout-container-ids layout-container-values layout-contianer-tokens]
|
||||
(get-attrs shapes objects :layout-container)
|
||||
|
||||
[layout-item-ids layout-item-values {}]
|
||||
@@ -442,7 +442,6 @@
|
||||
(when-not (empty? layer-ids)
|
||||
[:> layer-menu* {:type type
|
||||
:ids layer-ids
|
||||
:applied-tokens layer-tokens
|
||||
:values layer-values}])
|
||||
|
||||
(when-not (empty? measure-ids)
|
||||
@@ -460,7 +459,7 @@
|
||||
{:type type
|
||||
:ids layout-container-ids
|
||||
:values layout-container-values
|
||||
:applied-tokens layout-container-tokens
|
||||
:applied-tokens layout-contianer-tokens
|
||||
:multiple true}]
|
||||
|
||||
(when (or is-layout-child? has-flex-layout-container?)
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:applied-tokens applied-tokens
|
||||
:type type
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:ids ids
|
||||
|
||||
@@ -85,7 +85,6 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:ids ids
|
||||
:type type
|
||||
|
||||
@@ -125,7 +125,6 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu*
|
||||
{:ids ids
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
[app.main.data.workspace.shortcuts]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
||||
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.strings :refer [matches-search]]
|
||||
@@ -487,9 +487,13 @@
|
||||
(dom/focus! (dom/get-element "shortcut-search")))
|
||||
|
||||
[:div {:class (dm/str class " " (stl/css :shortcuts))}
|
||||
[:> panel-title* {:class (stl/css :shortcuts-title)
|
||||
:text (tr "shortcuts.title")
|
||||
:on-close close-fn}]
|
||||
[:div {:class (stl/css :shortcuts-header)}
|
||||
[:div {:class (stl/css :shortcuts-title)} (tr "shortcuts.title")]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/close
|
||||
:class (stl/css :shortcuts-close-button)
|
||||
:on-click close-fn
|
||||
:aria-label (tr "labels.close")}]]
|
||||
|
||||
[:div {:class (stl/css :search-field)}
|
||||
[:> search-bar* {:on-change on-search-term-change-2
|
||||
|
||||
@@ -18,8 +18,27 @@
|
||||
margin: deprecated.$s-16 deprecated.$s-12 deprecated.$s-4 deprecated.$s-12;
|
||||
}
|
||||
|
||||
.shortcuts-title {
|
||||
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
|
||||
.shortcuts-header {
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
position: relative;
|
||||
height: deprecated.$s-32;
|
||||
padding: deprecated.$s-2 deprecated.$s-2 deprecated.$s-2 0;
|
||||
margin: deprecated.$s-4 deprecated.$s-4 0 deprecated.$s-4;
|
||||
border-radius: deprecated.$br-6;
|
||||
background-color: var(--panel-title-background-color);
|
||||
|
||||
.shortcuts-title {
|
||||
@include deprecated.flexCenter;
|
||||
flex-grow: 1;
|
||||
color: var(--title-foreground-color-hover);
|
||||
}
|
||||
|
||||
.shortcuts-close-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
@@ -23,11 +22,9 @@
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.notifications.badge :refer [badge-notification]]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.timers :as timers]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
@@ -55,8 +52,6 @@
|
||||
refs/workspace-data
|
||||
=))
|
||||
|
||||
|
||||
|
||||
;; --- Page Item
|
||||
|
||||
(mf/defc page-item
|
||||
@@ -68,22 +63,6 @@
|
||||
navigate-fn (mf/use-fn (mf/deps id) #(st/emit! :interrupt (dcm/go-to-workspace :page-id id)))
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
(fn []
|
||||
;; when using the wasm renderer, apply a blur effect to the viewport canvas
|
||||
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||
(do
|
||||
(wasm.api/capture-canvas-pixels)
|
||||
(wasm.api/apply-canvas-blur)
|
||||
;; NOTE: it seems we need two RAF so the blur is actually applied and visible
|
||||
;; in the canvas :(
|
||||
(timers/raf
|
||||
(fn []
|
||||
(timers/raf navigate-fn))))
|
||||
(navigate-fn))))
|
||||
|
||||
on-delete
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
@@ -176,7 +155,7 @@
|
||||
:selected selected?)
|
||||
:data-testid (dm/str "page-" id)
|
||||
:tab-index "0"
|
||||
:on-click on-click
|
||||
:on-click navigate-fn
|
||||
:on-double-click on-double-click
|
||||
:on-context-menu on-context-menu}
|
||||
[:div {:class (stl/css :page-icon)}
|
||||
|
||||
@@ -14,10 +14,8 @@
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.main.ui.workspace.tokens.management.context-menu :refer [token-context-menu]]
|
||||
[app.main.ui.workspace.tokens.management.group :refer [token-group*]]
|
||||
[app.main.ui.workspace.tokens.management.node-context-menu :refer [token-node-context-menu*]]
|
||||
[app.util.array :as array]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- get-sorted-token-groups
|
||||
@@ -122,27 +120,7 @@
|
||||
|
||||
[empty-group filled-group]
|
||||
(mf/with-memo [tokens-by-type]
|
||||
(get-sorted-token-groups tokens-by-type))
|
||||
|
||||
;; Filter tokens by their path and return their ids
|
||||
filter-tokens-by-path-ids
|
||||
(mf/use-fn
|
||||
(mf/deps tokens)
|
||||
(fn [type path]
|
||||
(->> tokens
|
||||
(filter (fn [token]
|
||||
(let [[_ token-value] token]
|
||||
(and (= (:type token-value) type) (str/starts-with? (:name token-value) path)))))
|
||||
(mapv (fn [token]
|
||||
(let [[_ token-value] token]
|
||||
(:id token-value)))))))
|
||||
|
||||
delete-node
|
||||
(mf/with-memo [tokens selected-token-set-id]
|
||||
(fn [node type]
|
||||
(let [path (:path node)
|
||||
tokens-in-path-ids (filter-tokens-by-path-ids type path)]
|
||||
(st/emit! (dwtl/bulk-delete-tokens selected-token-set-id tokens-in-path-ids)))))]
|
||||
(get-sorted-token-groups tokens-by-type))]
|
||||
|
||||
(mf/with-effect [tokens-lib selected-token-set-id]
|
||||
(when (and tokens-lib
|
||||
@@ -156,7 +134,6 @@
|
||||
|
||||
[:*
|
||||
[:& token-context-menu]
|
||||
[:> token-node-context-menu* {:on-delete-node delete-node}]
|
||||
|
||||
[:& selected-set-info* {:tokens-lib tokens-lib
|
||||
:selected-token-set-id selected-token-set-id}]
|
||||
|
||||
@@ -140,9 +140,6 @@
|
||||
error
|
||||
(get-in @form [:errors input-name])
|
||||
|
||||
extra-error
|
||||
(get-in @form [:extra-errors input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data input-name] "")
|
||||
|
||||
@@ -250,14 +247,9 @@
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(cond
|
||||
(and error touched?)
|
||||
(if (and error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
(and extra-error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message extra-error)})
|
||||
:else
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
|
||||
@@ -23,19 +23,21 @@
|
||||
(let [token-type
|
||||
(or (:type token) token-type)
|
||||
|
||||
tokens-in-selected-set
|
||||
(mf/deref refs/workspace-all-tokens-in-selected-set)
|
||||
|
||||
token-path
|
||||
(mf/with-memo [token]
|
||||
(cft/token-name->path (:name token)))
|
||||
|
||||
all-tokens (mf/deref refs/workspace-all-tokens-map)
|
||||
|
||||
all-tokens
|
||||
(mf/with-memo [token-path all-tokens]
|
||||
(-> (ctob/tokens-tree all-tokens)
|
||||
tokens-tree-in-selected-set
|
||||
(mf/with-memo [token-path tokens-in-selected-set]
|
||||
(-> (ctob/tokens-tree tokens-in-selected-set)
|
||||
(d/dissoc-in token-path)))
|
||||
props
|
||||
(mf/spread-props props {:token-type token-type
|
||||
:all-token-tree all-tokens
|
||||
: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")})
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
[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.errors :as wte]
|
||||
[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]
|
||||
@@ -89,13 +88,14 @@
|
||||
action
|
||||
is-create
|
||||
selected-token-set-id
|
||||
all-token-tree
|
||||
tokens-tree-in-selected-set
|
||||
token-type
|
||||
make-schema
|
||||
input-component
|
||||
initial
|
||||
type
|
||||
value-subfield
|
||||
tokens-in-selected-set
|
||||
input-value-placeholder] :as props}]
|
||||
|
||||
(let [make-schema (or make-schema default-make-schema)
|
||||
@@ -124,9 +124,6 @@
|
||||
tokens
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens-in-selected-set
|
||||
(mf/deref refs/workspace-all-tokens-in-selected-set)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens tokens-in-selected-set token]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
@@ -137,8 +134,8 @@
|
||||
(assoc (:name token) token)))
|
||||
|
||||
schema
|
||||
(mf/with-memo [all-token-tree active-tab]
|
||||
(make-schema all-token-tree active-tab))
|
||||
(mf/with-memo [tokens-tree-in-selected-set active-tab]
|
||||
(make-schema tokens-tree-in-selected-set active-tab))
|
||||
|
||||
initial
|
||||
(mf/with-memo [token]
|
||||
@@ -227,12 +224,7 @@
|
||||
:description description}))
|
||||
(dwtl/toggle-token-path path)
|
||||
(dwtp/propagate-workspace-tokens)
|
||||
(modal/hide!)))))
|
||||
;; WORKAROUND: display validation errors in the form instead of crashing
|
||||
(fn [{:keys [errors]}]
|
||||
(let [error-messages (wte/humanize-errors errors)
|
||||
error-message (first error-messages)]
|
||||
(swap! form assoc-in [:extra-errors :value] {:message error-message}))))))))]
|
||||
(modal/hide!))))))))))]
|
||||
|
||||
[:> fc/form* {:class (stl/css :form-wrapper)
|
||||
:form form
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
expandable? (d/nilv (seq tokens) false)
|
||||
|
||||
on-pill-context-menu
|
||||
on-context-menu
|
||||
(mf/use-fn
|
||||
(fn [event token]
|
||||
(dom/prevent-default event)
|
||||
@@ -98,15 +98,6 @@
|
||||
:errors (:errors token)
|
||||
:token-id (:id token)}))))
|
||||
|
||||
on-node-context-menu
|
||||
(mf/use-fn
|
||||
(fn [event node]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (dwtl/assign-token-node-context-menu
|
||||
{:node node
|
||||
:type type
|
||||
:position (dom/get-client-position event)}))))
|
||||
|
||||
on-toggle-open-click
|
||||
(mf/use-fn
|
||||
(mf/deps type expandable?)
|
||||
@@ -168,5 +159,4 @@
|
||||
:selected-token-set-id selected-token-set-id
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:on-token-pill-click on-token-pill-click
|
||||
:on-pill-context-menu on-pill-context-menu
|
||||
:on-node-context-menu on-node-context-menu}])]))
|
||||
:on-context-menu on-context-menu}])]))
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
(ns app.main.ui.workspace.tokens.management.node-context-menu
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema:token-node-context-menu
|
||||
[:map
|
||||
[:on-delete-node fn?]])
|
||||
|
||||
(def ^:private tokens-node-menu-ref
|
||||
(l/derived :token-node-context-menu refs/workspace-tokens))
|
||||
|
||||
(defn- prevent-default
|
||||
[event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event))
|
||||
|
||||
(mf/defc token-node-context-menu*
|
||||
{::mf/schema schema:token-node-context-menu}
|
||||
[{:keys [on-delete-node]}]
|
||||
(let [mdata (mf/deref tokens-node-menu-ref)
|
||||
is-open? (boolean mdata)
|
||||
dropdown-ref (mf/use-ref)
|
||||
dropdown-action (mf/use-ref)
|
||||
dropdown-direction* (mf/use-state "down")
|
||||
dropdown-direction (deref dropdown-direction*)
|
||||
dropdown-direction-change* (mf/use-ref 0)
|
||||
top (+ (get-in mdata [:position :y]) 5)
|
||||
left (+ (get-in mdata [:position :x]) 5)
|
||||
|
||||
delete-node (mf/use-fn
|
||||
(mf/deps mdata)
|
||||
(fn []
|
||||
(let [node (get mdata :node)
|
||||
type (get mdata :type)]
|
||||
(when node
|
||||
(on-delete-node node type)))))]
|
||||
|
||||
(mf/with-effect [is-open?]
|
||||
(when (and (not= 0 (mf/ref-val dropdown-direction-change*)) (= false is-open?))
|
||||
(reset! dropdown-direction* "down")
|
||||
(mf/set-ref-val! dropdown-direction-change* 0)))
|
||||
|
||||
(mf/with-effect [is-open? dropdown-ref dropdown-action]
|
||||
(let [dropdown-element (mf/ref-val dropdown-ref)]
|
||||
(when (and (= 0 (mf/ref-val dropdown-direction-change*)) dropdown-element)
|
||||
(let [is-outside? (dom/is-element-outside? dropdown-element)]
|
||||
(reset! dropdown-direction* (if is-outside? "up" "down"))
|
||||
(mf/set-ref-val! dropdown-direction-change* (inc (mf/ref-val dropdown-direction-change*)))))))
|
||||
|
||||
;; FIXME: perf optimization
|
||||
|
||||
(when is-open?
|
||||
(mf/portal
|
||||
(mf/html
|
||||
[:& dropdown {:show is-open?
|
||||
:on-close #(st/emit! (dwtl/assign-token-node-context-menu nil))}
|
||||
[:div {:class (stl/css :token-node-context-menu)
|
||||
:data-testid "tokens-context-menu-for-token-node"
|
||||
:ref dropdown-ref
|
||||
:data-direction dropdown-direction
|
||||
:style {:--bottom (if (= dropdown-direction "up")
|
||||
"40px"
|
||||
"unset")
|
||||
:--top (dm/str top "px")
|
||||
:left (dm/str left "px")}
|
||||
:on-context-menu prevent-default}
|
||||
(when mdata
|
||||
[:ul {:class (stl/css :token-node-context-menu-list)}
|
||||
[:li {:class (stl/css :token-node-context-menu-listitem)}
|
||||
[:button {:class (stl/css :token-node-context-menu-action)
|
||||
:type "button"
|
||||
:on-click delete-node}
|
||||
(tr "labels.delete")]]])]])
|
||||
(dom/get-body)))))
|
||||
@@ -1,70 +0,0 @@
|
||||
// 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/_utils.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
@use "ds/spacing.scss" as *;
|
||||
@use "ds/mixins.scss" as *;
|
||||
|
||||
.token-node-context-menu {
|
||||
--menu-inline-size: #{px2rem(240)};
|
||||
|
||||
position: absolute;
|
||||
z-index: var(--z-index-dropdown);
|
||||
}
|
||||
|
||||
.token-node-context-menu[data-direction="up"] {
|
||||
bottom: var(--bottom);
|
||||
}
|
||||
|
||||
.token-node-context-menu[data-direction="down"] {
|
||||
top: var(--top);
|
||||
}
|
||||
|
||||
.token-node-context-menu-list {
|
||||
inline-size: var(--menu-inline-size);
|
||||
padding: var(--sp-xs);
|
||||
border-radius: $br-8;
|
||||
border: $b-2 solid var(--color-background-quaternary);
|
||||
background-color: var(--color-background-tertiary);
|
||||
max-block-size: 100vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0px 0px $sz-12 0px var(--menu-shadow-color);
|
||||
}
|
||||
|
||||
.token-node-context-menu-action {
|
||||
--context-menu-item-bg-color: none;
|
||||
--context-menu-item-fg-color: var(--color-foreground-primary);
|
||||
--context-menu-item-border-color: none;
|
||||
|
||||
@include t.use-typography("body-small");
|
||||
appearance: none;
|
||||
background: var(--context-menu-item-bg-color);
|
||||
border: $b-1 solid var(--context-menu-item-border-color);
|
||||
color: var(--context-menu-item-fg-color);
|
||||
border-radius: $br-8;
|
||||
cursor: pointer;
|
||||
block-size: px2rem(32);
|
||||
inline-size: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--sp-xs);
|
||||
|
||||
&:hover {
|
||||
--context-menu-item-bg-color: var(--color-background-quaternary);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
--context-menu-item-bg-color: var(--menu-background-color-focus);
|
||||
--context-menu-item-border-color: var(--color-background-tertiary);
|
||||
}
|
||||
|
||||
&[aria-selected="true"] {
|
||||
--context-menu-item-bg-color: var(--color-background-quaternary);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
[app.common.path-names :as cpn]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.layers.layer-button :refer [layer-button*]]
|
||||
[app.main.ui.workspace.tokens.management.token-pill :refer [token-pill*]]
|
||||
@@ -27,8 +26,7 @@
|
||||
[:selected-token-set-id {:optional true} :any]
|
||||
[:tokens-lib {:optional true} :any]
|
||||
[:on-token-pill-click {:optional true} fn?]
|
||||
[:on-pill-context-menu {:optional true} fn?]
|
||||
[:on-node-context-menu {:optional true} fn?]])
|
||||
[:on-context-menu {:optional true} fn?]])
|
||||
|
||||
(mf/defc folder-node*
|
||||
{::mf/schema schema:folder-node}
|
||||
@@ -41,29 +39,22 @@
|
||||
selected-token-set-id
|
||||
tokens-lib
|
||||
on-token-pill-click
|
||||
on-pill-context-menu
|
||||
on-node-context-menu]}]
|
||||
on-context-menu]}]
|
||||
(let [full-path (str (name type) "." (:path node))
|
||||
is-folder-expanded (contains? (set (or unfolded-token-paths [])) full-path)
|
||||
|
||||
swap-folder-expanded (mf/use-fn
|
||||
(mf/deps (:path node) type)
|
||||
(fn []
|
||||
(let [path (str (name type) "." (:path node))]
|
||||
(st/emit! (dwtl/toggle-token-path path)))))
|
||||
|
||||
node-context-menu-prep (mf/use-fn
|
||||
(mf/deps on-node-context-menu node)
|
||||
(fn [event]
|
||||
(when on-node-context-menu
|
||||
(on-node-context-menu event node))))]
|
||||
(st/emit! (dwtl/toggle-token-path path)))))]
|
||||
[:li {:class (stl/css :folder-node)}
|
||||
[:> layer-button* {:label (:name node)
|
||||
:expanded is-folder-expanded
|
||||
:aria-expanded is-folder-expanded
|
||||
:aria-controls (str "folder-children-" (:path node))
|
||||
:is-expandable (not (:leaf node))
|
||||
:on-toggle-expand swap-folder-expanded
|
||||
:on-context-menu node-context-menu-prep}]
|
||||
:on-toggle-expand swap-folder-expanded}]
|
||||
(when is-folder-expanded
|
||||
(let [children-fn (:children-fn node)]
|
||||
[:div {:class (stl/css :folder-children-wrapper)
|
||||
@@ -72,17 +63,16 @@
|
||||
(let [children (children-fn)]
|
||||
(for [child children]
|
||||
(if (not (:leaf child))
|
||||
[:ul {:class (stl/css :node-parent)
|
||||
:key (:path child)}
|
||||
[:> folder-node* {:type type
|
||||
[:ul {:class (stl/css :node-parent)}
|
||||
[:> folder-node* {:key (:path child)
|
||||
:type type
|
||||
:node child
|
||||
:unfolded-token-paths unfolded-token-paths
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens
|
||||
:on-token-pill-click on-token-pill-click
|
||||
:on-pill-context-menu on-pill-context-menu
|
||||
:on-node-context-menu on-node-context-menu
|
||||
:on-context-menu on-context-menu
|
||||
:tokens-lib tokens-lib
|
||||
:selected-token-set-id selected-token-set-id}]]
|
||||
(let [id (:id (:leaf child))
|
||||
@@ -94,7 +84,7 @@
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens
|
||||
:on-click on-token-pill-click
|
||||
:on-context-menu on-pill-context-menu}])))))]))]))
|
||||
:on-context-menu on-context-menu}])))))]))]))
|
||||
|
||||
(def ^:private schema:token-tree
|
||||
[:map
|
||||
@@ -107,8 +97,7 @@
|
||||
[:selected-token-set-id {:optional true} :any]
|
||||
[:tokens-lib {:optional true} :any]
|
||||
[:on-token-pill-click {:optional true} fn?]
|
||||
[:on-pill-context-menu {:optional true} fn?]
|
||||
[:on-node-context-menu {:optional true} fn?]])
|
||||
[:on-context-menu {:optional true} fn?]])
|
||||
|
||||
(mf/defc token-tree*
|
||||
{::mf/schema schema:token-tree}
|
||||
@@ -121,19 +110,12 @@
|
||||
tokens-lib
|
||||
selected-token-set-id
|
||||
on-token-pill-click
|
||||
on-pill-context-menu
|
||||
on-node-context-menu]}]
|
||||
on-context-menu]}]
|
||||
(let [separator "."
|
||||
tree (mf/use-memo
|
||||
(mf/deps tokens)
|
||||
(fn []
|
||||
(cpn/build-tree-root tokens separator)))
|
||||
can-edit? (:can-edit (deref refs/permissions))
|
||||
on-node-context-menu (mf/use-fn
|
||||
(mf/deps can-edit? on-node-context-menu)
|
||||
(fn [event node]
|
||||
(when can-edit?
|
||||
(on-node-context-menu event node))))]
|
||||
(cpn/build-tree-root tokens separator)))]
|
||||
[:div {:class (stl/css :token-tree-wrapper)}
|
||||
(for [node tree]
|
||||
(if (:leaf node)
|
||||
@@ -145,7 +127,7 @@
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens
|
||||
:on-click on-token-pill-click
|
||||
:on-context-menu on-pill-context-menu}])
|
||||
:on-context-menu on-context-menu}])
|
||||
;; Render segment folder
|
||||
[:ul {:class (stl/css :node-parent)
|
||||
:key (:path node)}
|
||||
@@ -156,7 +138,6 @@
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens
|
||||
:on-token-pill-click on-token-pill-click
|
||||
:on-node-context-menu on-node-context-menu
|
||||
:on-pill-context-menu on-pill-context-menu
|
||||
:on-context-menu on-context-menu
|
||||
:tokens-lib tokens-lib
|
||||
:selected-token-set-id selected-token-set-id}]]))]))
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.combobox :refer [combobox*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.switch :refer [switch*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.controls.utilities.label :refer [label*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
@@ -85,6 +85,21 @@
|
||||
:on-click create-theme}
|
||||
(tr "workspace.tokens.add-new-theme")]]]]))
|
||||
|
||||
(mf/defc switch*
|
||||
[{:keys [selected? name on-change]}]
|
||||
(let [selected (if selected? :on :off)]
|
||||
[:> radio-buttons* {:selected selected
|
||||
:on-change on-change
|
||||
:name name
|
||||
:options [{:id "on"
|
||||
:icon i/tick
|
||||
:label (tr "workspace.tokens.theme.enable")
|
||||
:value :on}
|
||||
{:id "off"
|
||||
:icon i/close
|
||||
:label (tr "workspace.tokens.theme.disable")
|
||||
:value :off}]}]))
|
||||
|
||||
(mf/defc themes-overview
|
||||
[{:keys [change-view]}]
|
||||
(let [active-theme-paths (mf/deref refs/workspace-active-theme-paths)
|
||||
@@ -122,9 +137,6 @@
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(st/emit! (dwtl/delete-token-theme id)))
|
||||
on-switch-theme
|
||||
(fn []
|
||||
(st/emit! (dwtl/toggle-token-theme-active id)))
|
||||
on-edit-theme
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
@@ -134,10 +146,16 @@
|
||||
:class (stl/css :theme-row)}
|
||||
[:div {:class (stl/css :theme-switch-row)}
|
||||
|
||||
[:> switch* {:id name
|
||||
:label name
|
||||
:on-change on-switch-theme
|
||||
:default-checked selected?}]]
|
||||
;; FIXME: FIREEEEEEEEEE THIS
|
||||
[:div {:on-click (fn [e]
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(st/emit! (dwtl/toggle-token-theme-active id)))}
|
||||
[:> switch* {:name (tr "workspace.tokens.theme-name" name)
|
||||
:on-change (constantly nil)
|
||||
:selected? selected?}]]]
|
||||
[:div {:class (stl/css :theme-name-row)}
|
||||
[:> text* {:as "span" :typography "body-medium" :class (stl/css :theme-name) :title name} name]]
|
||||
|
||||
|
||||
[:div {:class (stl/css :theme-actions-row)}
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
:class (stl/css :main-toolbar-options-button)
|
||||
:icon i/bug
|
||||
:aria-pressed (contains? layout :debug-panel)
|
||||
:aria-label (tr "workspace.toolbar.debug")
|
||||
:aria-label "Debugging tool"
|
||||
:tooltip-placement "bottom"
|
||||
:on-click toggle-debug-panel}]])]]
|
||||
|
||||
|
||||
@@ -312,11 +312,6 @@
|
||||
(js/console.error "Error initializing canvas context:" e)
|
||||
false))]
|
||||
(reset! canvas-init? init?)
|
||||
(when init?
|
||||
;; Restore previous canvas pixels immediately after context initialization
|
||||
;; This happens before initialize-viewport is called
|
||||
(wasm.api/apply-canvas-blur)
|
||||
(wasm.api/restore-previous-canvas-pixels))
|
||||
(when-not init?
|
||||
(js/alert "WebGL not supported")
|
||||
(st/emit! (dcm/go-to-dashboard-recent))))))))
|
||||
@@ -345,7 +340,6 @@
|
||||
|
||||
(mf/with-effect [@canvas-init? zoom vbox background]
|
||||
(when (and @canvas-init? (not @initialized?))
|
||||
(wasm.api/clear-canvas-pixels)
|
||||
(wasm.api/initialize-viewport base-objects zoom vbox background)
|
||||
(reset! initialized? true)))
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
[app.main.worker :as mw]
|
||||
[app.render-wasm.api.fonts :as f]
|
||||
[app.render-wasm.api.texts :as t]
|
||||
[app.render-wasm.api.webgl :as webgl]
|
||||
[app.render-wasm.deserializers :as dr]
|
||||
[app.render-wasm.helpers :as h]
|
||||
[app.render-wasm.mem :as mem]
|
||||
@@ -38,6 +37,7 @@
|
||||
[app.render-wasm.serializers :as sr]
|
||||
[app.render-wasm.serializers.color :as sr-clr]
|
||||
[app.render-wasm.svg-filters :as svg-filters]
|
||||
;; FIXME: rename; confunsing name
|
||||
[app.render-wasm.wasm :as wasm]
|
||||
[app.util.debug :as dbg]
|
||||
[app.util.dom :as dom]
|
||||
@@ -279,6 +279,30 @@
|
||||
[string]
|
||||
(+ (count string) 1))
|
||||
|
||||
(defn- create-webgl-texture-from-image
|
||||
"Creates a WebGL texture from an HTMLImageElement or ImageBitmap and returns the texture object"
|
||||
[gl image-element]
|
||||
(let [texture (.createTexture ^js gl)]
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-element)
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
|
||||
texture))
|
||||
|
||||
(defn- get-webgl-context
|
||||
"Gets the WebGL context from the WASM module"
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(let [gl-obj (unchecked-get wasm/internal-module "GL")]
|
||||
(when gl-obj
|
||||
;; Get the current WebGL context from Emscripten
|
||||
;; The GL object has a currentContext property that contains the context handle
|
||||
(let [current-ctx (.-currentContext ^js gl-obj)]
|
||||
(when current-ctx
|
||||
(.-GLctx ^js current-ctx)))))))
|
||||
|
||||
(defn- get-texture-id-for-gl-object
|
||||
"Registers a WebGL texture with Emscripten's GL object system and returns its ID"
|
||||
@@ -308,8 +332,8 @@
|
||||
(->> (retrieve-image url)
|
||||
(rx/map
|
||||
(fn [img]
|
||||
(when-let [gl (webgl/get-webgl-context)]
|
||||
(let [texture (webgl/create-webgl-texture-from-image gl img)
|
||||
(when-let [gl (get-webgl-context)]
|
||||
(let [texture (create-webgl-texture-from-image gl img)
|
||||
texture-id (get-texture-id-for-gl-object texture)
|
||||
width (.-width ^js img)
|
||||
height (.-height ^js img)
|
||||
@@ -955,7 +979,6 @@
|
||||
(set-shape-grow-type grow-type))
|
||||
|
||||
(set-shape-layout shape)
|
||||
(set-layout-data shape)
|
||||
(set-shape-selrect selrect)
|
||||
|
||||
(let [pending_thumbnails (into [] (concat
|
||||
@@ -1032,9 +1055,8 @@
|
||||
(perf/end-measure "set-objects")
|
||||
(process-pending shapes thumbnails full noop-fn
|
||||
(fn []
|
||||
(if render-callback
|
||||
(render-callback)
|
||||
(render-finish))
|
||||
(when render-callback (render-callback))
|
||||
(render-finish)
|
||||
(ug/dispatch! (ug/event "penpot:wasm:set-objects")))))))
|
||||
|
||||
(defn clear-focus-mode
|
||||
@@ -1362,9 +1384,8 @@
|
||||
all-children
|
||||
(->> ids
|
||||
(mapcat #(cfh/get-children-with-self objects %)))]
|
||||
|
||||
(h/call wasm/internal-module "_init_shapes_pool" (count all-children))
|
||||
(run! set-object all-children)
|
||||
(run! (partial set-object objects) all-children)
|
||||
|
||||
(let [content (-> (calculate-bool* bool-type ids)
|
||||
(path.impl/path-data))]
|
||||
@@ -1427,12 +1448,6 @@
|
||||
|
||||
result)))
|
||||
|
||||
(defn apply-canvas-blur
|
||||
[]
|
||||
(when wasm/canvas
|
||||
(dom/set-style! wasm/canvas "filter" "blur(4px)")))
|
||||
|
||||
|
||||
(defn init-wasm-module
|
||||
[module]
|
||||
(let [default-fn (unchecked-get module "default")
|
||||
@@ -1454,8 +1469,3 @@
|
||||
(js/console.error cause)
|
||||
(p/resolved false)))))
|
||||
(p/resolved false))))
|
||||
|
||||
;; Re-export public WebGL functions
|
||||
(def capture-canvas-pixels webgl/capture-canvas-pixels)
|
||||
(def restore-previous-canvas-pixels webgl/restore-previous-canvas-pixels)
|
||||
(def clear-canvas-pixels webgl/clear-canvas-pixels)
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
;; 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.render-wasm.api.webgl
|
||||
"WebGL utilities for pixel capture and rendering"
|
||||
(:require
|
||||
[app.common.logging :as log]
|
||||
[app.render-wasm.wasm :as wasm]
|
||||
[app.util.dom :as dom]))
|
||||
|
||||
(defn get-webgl-context
|
||||
"Gets the WebGL context from the WASM module"
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(let [gl-obj (unchecked-get wasm/internal-module "GL")]
|
||||
(when gl-obj
|
||||
;; Get the current WebGL context from Emscripten
|
||||
;; The GL object has a currentContext property that contains the context handle
|
||||
(let [current-ctx (.-currentContext ^js gl-obj)]
|
||||
(when current-ctx
|
||||
(.-GLctx ^js current-ctx)))))))
|
||||
|
||||
(defn create-webgl-texture-from-image
|
||||
"Creates a WebGL texture from an HTMLImageElement or ImageBitmap and returns the texture object"
|
||||
[gl image-element]
|
||||
(let [texture (.createTexture ^js gl)]
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-element)
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
|
||||
texture))
|
||||
|
||||
;; FIXME: temporary function until we are able to keep the same <canvas> across pages.
|
||||
(defn- draw-imagedata-to-webgl
|
||||
"Draws ImageData to a WebGL2 context by creating a texture"
|
||||
[gl image-data]
|
||||
(let [width (.-width ^js image-data)
|
||||
height (.-height ^js image-data)
|
||||
texture (.createTexture ^js gl)]
|
||||
;; Bind texture and set parameters
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-data)
|
||||
|
||||
;; Set up viewport
|
||||
(.viewport ^js gl 0 0 width height)
|
||||
|
||||
;; Vertex & Fragment shaders
|
||||
;; Since we are only calling this function once (on page switch), we don't need
|
||||
;; to cache the compiled shaders somewhere else (cannot be reused in a differen context).
|
||||
(let [vertex-shader-source "#version 300 es
|
||||
in vec2 a_position;
|
||||
in vec2 a_texCoord;
|
||||
out vec2 v_texCoord;
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
v_texCoord = a_texCoord;
|
||||
}"
|
||||
fragment-shader-source "#version 300 es
|
||||
precision highp float;
|
||||
in vec2 v_texCoord;
|
||||
uniform sampler2D u_texture;
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = texture(u_texture, v_texCoord);
|
||||
}"
|
||||
vertex-shader (.createShader ^js gl (.-VERTEX_SHADER ^js gl))
|
||||
fragment-shader (.createShader ^js gl (.-FRAGMENT_SHADER ^js gl))
|
||||
program (.createProgram ^js gl)]
|
||||
(.shaderSource ^js gl vertex-shader vertex-shader-source)
|
||||
(.compileShader ^js gl vertex-shader)
|
||||
(when-not (.getShaderParameter ^js gl vertex-shader (.-COMPILE_STATUS ^js gl))
|
||||
(log/error :hint "Vertex shader compilation failed"
|
||||
:log (.getShaderInfoLog ^js gl vertex-shader)))
|
||||
|
||||
(.shaderSource ^js gl fragment-shader fragment-shader-source)
|
||||
(.compileShader ^js gl fragment-shader)
|
||||
(when-not (.getShaderParameter ^js gl fragment-shader (.-COMPILE_STATUS ^js gl))
|
||||
(log/error :hint "Fragment shader compilation failed"
|
||||
:log (.getShaderInfoLog ^js gl fragment-shader)))
|
||||
|
||||
(.attachShader ^js gl program vertex-shader)
|
||||
(.attachShader ^js gl program fragment-shader)
|
||||
(.linkProgram ^js gl program)
|
||||
|
||||
(if (.getProgramParameter ^js gl program (.-LINK_STATUS ^js gl))
|
||||
(do
|
||||
(.useProgram ^js gl program)
|
||||
|
||||
;; Create full-screen quad vertices (normalized device coordinates)
|
||||
(let [position-location (.getAttribLocation ^js gl program "a_position")
|
||||
texcoord-location (.getAttribLocation ^js gl program "a_texCoord")
|
||||
position-buffer (.createBuffer ^js gl)
|
||||
texcoord-buffer (.createBuffer ^js gl)
|
||||
positions #js [-1.0 -1.0 1.0 -1.0 -1.0 1.0 -1.0 1.0 1.0 -1.0 1.0 1.0]
|
||||
texcoords #js [0.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0 1.0 0.0 1.0 1.0]]
|
||||
;; Set up position buffer
|
||||
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) position-buffer)
|
||||
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. positions) (.-STATIC_DRAW ^js gl))
|
||||
(.enableVertexAttribArray ^js gl position-location)
|
||||
(.vertexAttribPointer ^js gl position-location 2 (.-FLOAT ^js gl) false 0 0)
|
||||
;; Set up texcoord buffer
|
||||
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) texcoord-buffer)
|
||||
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. texcoords) (.-STATIC_DRAW ^js gl))
|
||||
(.enableVertexAttribArray ^js gl texcoord-location)
|
||||
(.vertexAttribPointer ^js gl texcoord-location 2 (.-FLOAT ^js gl) false 0 0)
|
||||
;; Set texture uniform
|
||||
(.activeTexture ^js gl (.-TEXTURE0 ^js gl))
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
||||
(let [texture-location (.getUniformLocation ^js gl program "u_texture")]
|
||||
(.uniform1i ^js gl texture-location 0))
|
||||
|
||||
;; draw
|
||||
(.drawArrays ^js gl (.-TRIANGLES ^js gl) 0 6)
|
||||
|
||||
;; cleanup
|
||||
(.deleteBuffer ^js gl position-buffer)
|
||||
(.deleteBuffer ^js gl texcoord-buffer)
|
||||
(.deleteShader ^js gl vertex-shader)
|
||||
(.deleteShader ^js gl fragment-shader)
|
||||
(.deleteProgram ^js gl program)))
|
||||
(log/error :hint "Program linking failed"
|
||||
:log (.getProgramInfoLog ^js gl program)))
|
||||
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
|
||||
(.deleteTexture ^js gl texture))))
|
||||
|
||||
(defn restore-previous-canvas-pixels
|
||||
"Restores previous canvas pixels into the new canvas"
|
||||
[]
|
||||
(when-let [previous-canvas-pixels wasm/canvas-pixels]
|
||||
(when-let [gl wasm/gl-context]
|
||||
(draw-imagedata-to-webgl gl previous-canvas-pixels)
|
||||
(set! wasm/canvas-pixels nil))))
|
||||
|
||||
(defn clear-canvas-pixels
|
||||
[]
|
||||
(when wasm/canvas
|
||||
(let [context wasm/gl-context]
|
||||
(.clearColor ^js context 0 0 0 0.0)
|
||||
(.clear ^js context (.-COLOR_BUFFER_BIT ^js context))
|
||||
(.clear ^js context (.-DEPTH_BUFFER_BIT ^js context))
|
||||
(.clear ^js context (.-STENCIL_BUFFER_BIT ^js context)))
|
||||
(dom/set-style! wasm/canvas "filter" "none")
|
||||
(set! wasm/canvas-pixels nil)))
|
||||
|
||||
(defn capture-canvas-pixels
|
||||
"Captures the pixels of the viewport canvas"
|
||||
[]
|
||||
(when wasm/canvas
|
||||
(let [context wasm/gl-context
|
||||
width (.-width wasm/canvas)
|
||||
height (.-height wasm/canvas)
|
||||
buffer (js/Uint8ClampedArray. (* width height 4))
|
||||
_ (.readPixels ^js context 0 0 width height (.-RGBA ^js context) (.-UNSIGNED_BYTE ^js context) buffer)
|
||||
image-data (js/ImageData. buffer width height)]
|
||||
(set! wasm/canvas-pixels image-data))))
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
;; Reference to the HTML canvas element.
|
||||
(defonce canvas nil)
|
||||
;; Reference to the captured pixels of the canvas (for page switching effect)
|
||||
(defonce canvas-pixels nil)
|
||||
|
||||
;; Reference to the Emscripten GL context wrapper.
|
||||
(defonce gl-context-handle nil)
|
||||
@@ -58,4 +56,3 @@
|
||||
:stroke-linecap shared/RawStrokeLineCap
|
||||
:stroke-linejoin shared/RawStrokeLineJoin
|
||||
:fill-rule shared/RawFillRule})
|
||||
|
||||
|
||||
@@ -802,10 +802,9 @@
|
||||
([uri name]
|
||||
(open-new-window uri name "noopener,noreferrer"))
|
||||
([uri name features]
|
||||
(when-let [new-window (.open js/window (str uri) name features)]
|
||||
(let [new-window (.open js/window (str uri) name features)]
|
||||
(when (not= name "_blank")
|
||||
(when-let [location (.-location new-window)]
|
||||
(.reload location))))))
|
||||
(.reload (.-location new-window))))))
|
||||
|
||||
(defn browser-back
|
||||
[]
|
||||
|
||||
@@ -179,7 +179,6 @@
|
||||
|
||||
(->> (render-canvas-blob canvas width height bgcolor)
|
||||
(p/fnly (fn [data cause]
|
||||
(wasm.api/clear-canvas)
|
||||
(if cause
|
||||
(rx/error! subs cause)
|
||||
(rx/push! subs
|
||||
|
||||
@@ -5476,10 +5476,6 @@ msgstr "Delete row and shapes"
|
||||
msgid "workspace.context-menu.grid-track.row.duplicate"
|
||||
msgstr "Duplicate row"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/debug.cljs:37
|
||||
msgid "workspace.debug.title"
|
||||
msgstr "Debugging tools"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/layers.cljs:512
|
||||
msgid "workspace.focus.focus-mode"
|
||||
msgstr "Focus mode"
|
||||
@@ -8425,10 +8421,6 @@ msgstr "Comments (%s)"
|
||||
msgid "workspace.toolbar.curve"
|
||||
msgstr "Curve (%s)"
|
||||
|
||||
#: src/app/main/ui/workspace/top_toolbar.cljs:231
|
||||
msgid "workspace.toolbar.debug"
|
||||
msgstr "Debugging tools"
|
||||
|
||||
#: src/app/main/ui/workspace/top_toolbar.cljs:172
|
||||
msgid "workspace.toolbar.ellipse"
|
||||
msgstr "Ellipse (%s)"
|
||||
@@ -8739,10 +8731,10 @@ msgstr ""
|
||||
msgid "workspace.versions.warning.text"
|
||||
msgstr "Autosaved versions will be kept for %s days."
|
||||
|
||||
msgid "errors.webgl-context-lost.main-message"
|
||||
msgid "labels.webgl-context-lost.main-message"
|
||||
msgstr "Oops! The canvas context was lost"
|
||||
|
||||
msgid "errors.webgl-context-lost.desc-message"
|
||||
msgid "labels.webgl-context-lost.desc-message"
|
||||
msgstr "WebGL has stopped working. Please reload the page to reset it"
|
||||
|
||||
#, unused
|
||||
|
||||
@@ -5461,10 +5461,6 @@ msgstr "Borrar fila con el contenido"
|
||||
msgid "workspace.context-menu.grid-track.row.duplicate"
|
||||
msgstr "Duplicar fila"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/debug.cljs:37
|
||||
msgid "workspace.debug.title"
|
||||
msgstr "Herramientas de depuración"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/layers.cljs:512
|
||||
msgid "workspace.focus.focus-mode"
|
||||
msgstr "Modo foco"
|
||||
@@ -7969,7 +7965,7 @@ msgstr "Line height (multiplicador, px o %) o {alias}"
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:57
|
||||
msgid "workspace.tokens.missing-references"
|
||||
msgstr "Referencias de tokens no encontradas: "
|
||||
msgstr "Referéncias de tokens no encontradas:"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
|
||||
msgid "workspace.tokens.more-options"
|
||||
@@ -8286,10 +8282,6 @@ msgstr "Comentarios (%s)"
|
||||
msgid "workspace.toolbar.curve"
|
||||
msgstr "Curva (%s)"
|
||||
|
||||
#: src/app/main/ui/workspace/top_toolbar.cljs:231
|
||||
msgid "workspace.toolbar.debug"
|
||||
msgstr "Herramientas de depuración"
|
||||
|
||||
#: src/app/main/ui/workspace/top_toolbar.cljs:172
|
||||
msgid "workspace.toolbar.ellipse"
|
||||
msgstr "Elipse (%s)"
|
||||
@@ -8590,8 +8582,8 @@ msgstr "Los autoguardados duran %s días."
|
||||
msgid "workspace.viewport.click-to-close-path"
|
||||
msgstr "Pulsar para cerrar la ruta"
|
||||
|
||||
msgid "errors.webgl-context-lost.main-message"
|
||||
msgid "labels.webgl-context-lost.main-message"
|
||||
msgstr "Ups! Se ha perdido el contexto del canvas"
|
||||
|
||||
msgid "errors.webgl-context-lost.desc-message"
|
||||
msgstr "WebGL ha dejado de funcionar. Por favor, recarga la página para restaurarlo"
|
||||
msgid "labels.webgl-context-lost.desc-message"
|
||||
msgstr "WebGL ha dejado de funcionar. Por favor, recarga la página para restaurarlo"
|
||||
@@ -19,7 +19,7 @@ In the `apps` folder you'll find some examples that use the libraries mentioned
|
||||
- example-styles: to run this example you should run
|
||||
|
||||
```
|
||||
npm run start:styles-example
|
||||
pnpm run start:styles-example
|
||||
```
|
||||
|
||||
Open in your browser: `http://localhost:4202/`
|
||||
@@ -28,8 +28,8 @@ Open in your browser: `http://localhost:4202/`
|
||||
|
||||
This guide will help you launch a Penpot plugin from the penpot-plugins repository. Before proceeding, ensure that you have Penpot running locally by following the [setup instructions](https://help.penpot.app/technical-guide/developer/devenv/).
|
||||
|
||||
In the terminal, navigate to the **penpot-plugins** repository and run `npm install` to install the required dependencies.
|
||||
Then, run `npm start` to launch the plugins wrapper.
|
||||
In the terminal, navigate to the **penpot-plugins** repository and run `pnpm install` to install the required dependencies.
|
||||
Then, run `pnpm run start` to launch the plugins wrapper.
|
||||
|
||||
After installing the dependencies, choose a plugin to launch. You can either run one of the provided examples or create your own (see "Creating a plugin from scratch" below).
|
||||
To launch a plugin, Open a new terminal tab and run the appropriate startup script for the chosen plugin.
|
||||
@@ -38,7 +38,7 @@ For instance, to launch the Contrast plugin, use the following command:
|
||||
|
||||
```
|
||||
// for the contrast plugin
|
||||
npm run start:plugin:contrast
|
||||
pnpm run start:plugin:contrast
|
||||
```
|
||||
|
||||
Finally, open in your browser the specific port. In this specific example would be `http://localhost:4302`
|
||||
@@ -49,21 +49,22 @@ A table listing the available plugins and their corresponding startup commands i
|
||||
|
||||
| Plugin | Description | PORT | Start command | Manifest URL |
|
||||
| ----------------------- | ----------------------------------------------------------- | ---- | ------------------------------------- | ------------------------------------------ |
|
||||
| poc-state-plugin | Sandbox plugin to test new plugins api functionality | 4301 | npm run start:plugin:poc-state | http://localhost:4301/assets/manifest.json |
|
||||
| contrast-plugin | Sample plugin that gives you color contrast information | 4302 | npm run start:plugin:contrast | http://localhost:4302/assets/manifest.json |
|
||||
| icons-plugin | Tool to add icons from [Feather](https://feathericons.com/) | 4303 | npm run start:plugin:icons | http://localhost:4303/assets/manifest.json |
|
||||
| lorem-ipsum-plugin | Generate Lorem ipsum text | 4304 | npm run start:plugin:loremipsum | http://localhost:4304/assets/manifest.json |
|
||||
| create-palette-plugin | Creates a board with all the palette colors | 4305 | npm run start:plugin:palette | http://localhost:4305/assets/manifest.json |
|
||||
| table-plugin | Create or import table | 4306 | npm run start:table-plugin | http://localhost:4306/assets/manifest.json |
|
||||
| rename-layers-plugin | Rename layers in bulk | 4307 | npm run start:plugin:renamelayers | http://localhost:4307/assets/manifest.json |
|
||||
| colors-to-tokens-plugin | Generate tokens JSON file | 4308 | npm run start:plugin:colors-to-tokens | http://localhost:4308/assets/manifest.json |
|
||||
| poc-state-plugin | Sandbox plugin to test new plugins api functionality | 4301 | pnpm run start:plugin:poc-state | http://localhost:4301/assets/manifest.json |
|
||||
| contrast-plugin | Sample plugin that gives you color contrast information | 4302 | pnpm run start:plugin:contrast | http://localhost:4302/assets/manifest.json |
|
||||
| icons-plugin | Tool to add icons from [Feather](https://feathericons.com/) | 4303 | pnpm run start:plugin:icons | http://localhost:4303/assets/manifest.json |
|
||||
| lorem-ipsum-plugin | Generate Lorem ipsum text | 4304 | pnpm run start:plugin:loremipsum | http://localhost:4304/assets/manifest.json |
|
||||
| create-palette-plugin | Creates a board with all the palette colors | 4305 | pnpm run start:plugin:palette | http://localhost:4305/assets/manifest.json |
|
||||
| table-plugin | Create or import table | 4306 | pnpm run start:table-plugin | http://localhost:4306/assets/manifest.json |
|
||||
| rename-layers-plugin | Rename layers in bulk | 4307 | pnpm run start:plugin:renamelayers | http://localhost:4307/assets/manifest.json |
|
||||
| colors-to-tokens-plugin | Generate tokens JSON file | 4308 | pnpm run start:plugin:colors-to-tokens | http://localhost:4308/assets/manifest.json |
|
||||
| poc-tokens-plugin | Sandbox plugin to test tokens functionality | 4309 | pnpm run start:plugin:poc-tokens | http://localhost:4309/assets/manifest.json |
|
||||
|
||||
## Web Apps
|
||||
|
||||
| App | Description | PORT | Start command | URL |
|
||||
| --------------- | ----------------------------------------------------------------- | ---- | -------------------------------- | ---------------------- |
|
||||
| plugins-runtime | Runtime for the plugins subsystem | 4200 | npm run start:app:runtime | |
|
||||
| example-styles | Showcase of some of the Penpot styles that can be used in plugins | 4201 | npm run start:app:styles-example | http://localhost:4201/ |
|
||||
| plugins-runtime | Runtime for the plugins subsystem | 4200 | pnpm run start:app:runtime | |
|
||||
| example-styles | Showcase of some of the Penpot styles that can be used in plugins | 4201 | pnpm run start:app:styles-example | http://localhost:4201/ |
|
||||
|
||||
## Creating a plugin from scratch
|
||||
|
||||
|
||||
51
plugins/apps/poc-tokens-plugin/eslint.config.js
Normal file
51
plugins/apps/poc-tokens-plugin/eslint.config.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import baseConfig from '../../eslint.config.js';
|
||||
import { compat } from '../../eslint.base.config.js';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...compat
|
||||
.config({
|
||||
extends: [
|
||||
'plugin:@nx/angular',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'app',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'app',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({ extends: ['plugin:@nx/angular-template'] })
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.html'],
|
||||
rules: {},
|
||||
})),
|
||||
{ ignores: ['**/assets/*.js'] },
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: './tsconfig.*?.json',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
79
plugins/apps/poc-tokens-plugin/project.json
Normal file
79
plugins/apps/poc-tokens-plugin/project.json
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "poc-tokens-plugin",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"sourceRoot": "apps/poc-tokens-plugin/src",
|
||||
"tags": ["type:plugin"],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@angular-devkit/build-angular:application",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/apps/poc-tokens-plugin",
|
||||
"index": "apps/poc-tokens-plugin/src/index.html",
|
||||
"browser": "apps/poc-tokens-plugin/src/main.ts",
|
||||
"polyfills": ["zone.js"],
|
||||
"tsConfig": "apps/poc-tokens-plugin/tsconfig.app.json",
|
||||
"assets": [
|
||||
"apps/poc-tokens-plugin/src/favicon.ico",
|
||||
"apps/poc-tokens-plugin/src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"libs/plugins-styles/src/lib/styles.css",
|
||||
"apps/poc-tokens-plugin/src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"optimization": {
|
||||
"scripts": true,
|
||||
"styles": true,
|
||||
"fonts": false
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production",
|
||||
"dependsOn": ["buildPlugin"]
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "poc-tokens-plugin:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "poc-tokens-plugin:build:development",
|
||||
"port": 4309,
|
||||
"host": "0.0.0.0"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"executor": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "poc-tokens-plugin:build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
plugins/apps/poc-tokens-plugin/src/app/app.component.css
Normal file
127
plugins/apps/poc-tokens-plugin/src/app/app.component.css
Normal file
@@ -0,0 +1,127 @@
|
||||
/* @import "@penpot/plugin-styles/styles.css"; */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.title-l {
|
||||
margin: var(--spacing-16) 0;
|
||||
}
|
||||
|
||||
.columns {
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
flex-grow: 1;
|
||||
margin-block-end: var(--spacing-16);
|
||||
}
|
||||
|
||||
.panels {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding: 0 var(--spacing-8);
|
||||
}
|
||||
|
||||
.panel {
|
||||
padding: var(--spacing-8);
|
||||
display: flex;
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.panel:not(:first-child) {
|
||||
border-block-start: 1px solid var(--df-secondary);
|
||||
padding-block-start: var(--spacing-16);
|
||||
}
|
||||
|
||||
.panel-heading,
|
||||
.token-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-inline-end: var(--spacing-8);
|
||||
}
|
||||
|
||||
.panel-heading p,
|
||||
.token-group span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.panel-heading button,
|
||||
.token-group button {
|
||||
background: none;
|
||||
padding: var(--spacing-4) calc(var(--spacing-12) / 2);
|
||||
}
|
||||
|
||||
.panel-heading button:focus,
|
||||
.token-group button:focus {
|
||||
padding: calc(var(--spacing-4) - 2px) calc(var(--spacing-12) / 2 - 2px);
|
||||
}
|
||||
|
||||
.panel-item button {
|
||||
opacity: 0;
|
||||
margin-inline-end: var(--spacing-8);
|
||||
padding: var(--spacing-4) calc(var(--spacing-12) / 2);
|
||||
}
|
||||
|
||||
.panel-item button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.panel-item button:focus {
|
||||
opacity: 1;
|
||||
padding: calc(var(--spacing-4) - 2px) calc(var(--spacing-12) / 2 - 2px);
|
||||
}
|
||||
|
||||
.panel ul {
|
||||
/* flex-grow: 1; */
|
||||
overflow-y: auto;
|
||||
padding-inline-end: var(--spacing-8);
|
||||
}
|
||||
|
||||
.panel-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.panel-item span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.set-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.set-item.selected {
|
||||
background-color: var(--db-quaternary);
|
||||
}
|
||||
|
||||
.set-item:hover {
|
||||
color: var(--da-primary);
|
||||
background-color: var(--db-secondary);
|
||||
}
|
||||
|
||||
.token-group:not(:first-child) {
|
||||
margin-top: var(--spacing-8);
|
||||
}
|
||||
|
||||
.token-group {
|
||||
border-block-end: 1px solid var(--df-secondary);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.token-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.token-item:hover {
|
||||
color: var(--da-primary);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
144
plugins/apps/poc-tokens-plugin/src/app/app.component.html
Normal file
144
plugins/apps/poc-tokens-plugin/src/app/app.component.html
Normal file
@@ -0,0 +1,144 @@
|
||||
<div class="container">
|
||||
<p class="title-l">Design tokens plugin POC</p>
|
||||
|
||||
<div class="columns">
|
||||
<div class="panels">
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<p class="headline-m">THEMES</p>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="secondary"
|
||||
(click)="addTheme()"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul data-handler="themes-list">
|
||||
@for (theme of themes; track theme.id) {
|
||||
<li class="body-m panel-item theme-item">
|
||||
<span>{{ theme.group }} / {{ theme.name }}</span>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="secondary"
|
||||
(click)="renameTheme(theme.id, theme.name)"
|
||||
>
|
||||
🖊️
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="secondary"
|
||||
(click)="deleteTheme(theme.id)"
|
||||
>
|
||||
❌
|
||||
</button>
|
||||
<div class="checkbox-container">
|
||||
<input
|
||||
class="checkbox-input"
|
||||
type="checkbox"
|
||||
id="checkbox1"
|
||||
[checked]="isThemeActive(theme.id)"
|
||||
(change)="toggleTheme(theme.id)"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<p class="headline-m">SETS</p>
|
||||
<button type="button" data-appearance="secondary" (click)="addSet()">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul data-handler="sets-list">
|
||||
@for (set of sets; track set.id) {
|
||||
<li
|
||||
class="body-m panel-item set-item"
|
||||
[class.selected]="set.id === currentSetId"
|
||||
>
|
||||
<span (click)="loadTokens(set.id)">
|
||||
{{ set.name }}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="secondary"
|
||||
(click)="renameSet(set.id, set.name)"
|
||||
>
|
||||
🖊️
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="secondary"
|
||||
(click)="deleteSet(set.id)"
|
||||
>
|
||||
❌
|
||||
</button>
|
||||
<div class="checkbox-container">
|
||||
<input
|
||||
class="checkbox-input"
|
||||
type="checkbox"
|
||||
id="checkbox1"
|
||||
[checked]="isSetActive(set.id)"
|
||||
(change)="toggleSet(set.id)"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panels">
|
||||
<div class="panel">
|
||||
<p class="headline-m">TOKENS</p>
|
||||
|
||||
<ul data-handler="tokens-list">
|
||||
@for (group of tokenGroups; track group[0]) {
|
||||
<li class="body-m token-group">
|
||||
<span>{{ group[0] }}</span>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="secondary"
|
||||
(click)="addToken(group[0])"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</li>
|
||||
@for (token of group[1]; track token.id) {
|
||||
<li
|
||||
class="body-m panel-item token-item"
|
||||
(click)="applyToken(token.id)"
|
||||
>
|
||||
<span>{{ token.name }}</span>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="secondary"
|
||||
(click)="renameToken(token.id, token.name)"
|
||||
>
|
||||
🖊️
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="secondary"
|
||||
(click)="deleteToken(token.id)"
|
||||
>
|
||||
❌
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="button" data-appearance="primary" (click)="loadLibrary()">
|
||||
Load
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
290
plugins/apps/poc-tokens-plugin/src/app/app.component.ts
Normal file
290
plugins/apps/poc-tokens-plugin/src/app/app.component.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { fromEvent, map, filter, take, merge } from 'rxjs';
|
||||
import { PluginMessageEvent, PluginUIEvent } from '../model';
|
||||
|
||||
type TokenTheme = {
|
||||
id: string;
|
||||
name: string;
|
||||
group: string;
|
||||
description: string;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
type TokenSet = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
type Token = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
type TokensGroup = [string, Token[]];
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
host: {
|
||||
'[attr.data-theme]': 'theme()',
|
||||
},
|
||||
})
|
||||
export class AppComponent {
|
||||
public route = inject(ActivatedRoute);
|
||||
|
||||
public messages$ = fromEvent<MessageEvent<PluginMessageEvent>>(
|
||||
window,
|
||||
'message',
|
||||
);
|
||||
|
||||
public initialTheme$ = this.route.queryParamMap.pipe(
|
||||
map((params) => params.get('theme')),
|
||||
filter((theme) => !!theme),
|
||||
take(1),
|
||||
);
|
||||
|
||||
public theme = toSignal(
|
||||
merge(
|
||||
this.initialTheme$,
|
||||
this.messages$.pipe(
|
||||
filter((event) => event.data.type === 'theme'),
|
||||
map((event) => {
|
||||
return event.data.content;
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
public themes: TokenTheme[] = [];
|
||||
public sets: TokenSet[] = [];
|
||||
public tokenGroups: TokensGroup[] = [];
|
||||
public currentSetId: string | undefined = undefined;
|
||||
|
||||
constructor() {
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data.type === 'set-themes') {
|
||||
this.#setThemes(event.data.themesData);
|
||||
} else if (event.data.type === 'set-sets') {
|
||||
this.#setSets(event.data.setsData);
|
||||
} else if (event.data.type === 'set-tokens') {
|
||||
this.#setTokens(event.data.tokenGroupsData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadLibrary() {
|
||||
this.#sendMessage({ type: 'load-library' });
|
||||
}
|
||||
|
||||
loadTokens(setId: string) {
|
||||
this.currentSetId = setId;
|
||||
this.#sendMessage({ type: 'load-tokens', setId });
|
||||
}
|
||||
|
||||
addTheme() {
|
||||
this.#sendMessage({
|
||||
type: 'add-theme',
|
||||
themeGroup: this.#randomString(),
|
||||
themeName: this.#randomString(),
|
||||
});
|
||||
}
|
||||
|
||||
addSet() {
|
||||
this.#sendMessage({ type: 'add-set', setName: this.#randomString() });
|
||||
}
|
||||
|
||||
addToken(tokenType: string) {
|
||||
let tokenValue;
|
||||
switch (tokenType) {
|
||||
case 'borderRadius':
|
||||
tokenValue = 25;
|
||||
break;
|
||||
case 'shadow':
|
||||
tokenValue = [
|
||||
{
|
||||
color: '#123456',
|
||||
inset: 'false',
|
||||
offsetX: '6',
|
||||
offsetY: '6',
|
||||
spread: '0',
|
||||
blur: '4',
|
||||
},
|
||||
];
|
||||
break;
|
||||
case 'color':
|
||||
tokenValue = '#fabada';
|
||||
break;
|
||||
case 'dimension':
|
||||
tokenValue = 100;
|
||||
break;
|
||||
case 'fontFamilies':
|
||||
tokenValue = ['Source Sans Pro', 'Sans serif'];
|
||||
break;
|
||||
case 'fontSizes':
|
||||
tokenValue = 24;
|
||||
break;
|
||||
case 'fontWeights':
|
||||
tokenValue = 'bold';
|
||||
break;
|
||||
case 'letterSpacing':
|
||||
tokenValue = 0.5;
|
||||
break;
|
||||
case 'number':
|
||||
tokenValue = 33;
|
||||
break;
|
||||
case 'opacity':
|
||||
tokenValue = 0.6;
|
||||
break;
|
||||
case 'rotation':
|
||||
tokenValue = 45;
|
||||
break;
|
||||
case 'sizing':
|
||||
tokenValue = 200;
|
||||
break;
|
||||
case 'spacing':
|
||||
tokenValue = 16;
|
||||
break;
|
||||
case 'borderWidth':
|
||||
tokenValue = 3;
|
||||
break;
|
||||
case 'textCase':
|
||||
tokenValue = 'lowercase';
|
||||
break;
|
||||
case 'textDecoration':
|
||||
tokenValue = 'underline';
|
||||
break;
|
||||
case 'typography':
|
||||
tokenValue = {
|
||||
fontFamilies: ['Acme', 'Arial', 'Sans Serif'],
|
||||
fontSizes: '36',
|
||||
letterSpacing: '0.8',
|
||||
textCase: 'uppercase',
|
||||
textDecoration: 'none',
|
||||
fontWeights: '600',
|
||||
lineHeight: '1.5',
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.currentSetId && tokenValue) {
|
||||
this.#sendMessage({
|
||||
type: 'add-token',
|
||||
setId: this.currentSetId,
|
||||
tokenType,
|
||||
tokenName: this.#randomString(),
|
||||
tokenValue,
|
||||
});
|
||||
} else {
|
||||
console.log('Invalid token type');
|
||||
}
|
||||
}
|
||||
|
||||
renameTheme(themeId: string, themeName: string) {
|
||||
const newName = prompt('Rename theme', themeName);
|
||||
if (newName && newName !== '') {
|
||||
this.#sendMessage({ type: 'rename-theme', themeId, newName });
|
||||
}
|
||||
}
|
||||
|
||||
renameSet(setId: string, setName: string) {
|
||||
const newName = prompt('Rename set', setName);
|
||||
if (newName && newName !== '') {
|
||||
this.#sendMessage({ type: 'rename-set', setId, newName });
|
||||
}
|
||||
}
|
||||
|
||||
renameToken(tokenId: string, tokenName: string) {
|
||||
const newName = prompt('Rename token', tokenName);
|
||||
if (this.currentSetId && newName && newName !== '') {
|
||||
this.#sendMessage({
|
||||
type: 'rename-token',
|
||||
setId: this.currentSetId,
|
||||
tokenId,
|
||||
newName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
deleteTheme(themeId: string) {
|
||||
this.#sendMessage({ type: 'delete-theme', themeId });
|
||||
}
|
||||
|
||||
deleteSet(setId: string) {
|
||||
this.#sendMessage({ type: 'delete-set', setId });
|
||||
}
|
||||
|
||||
deleteToken(tokenId: string) {
|
||||
if (this.currentSetId) {
|
||||
this.#sendMessage({
|
||||
type: 'delete-token',
|
||||
setId: this.currentSetId,
|
||||
tokenId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isThemeActive(themeId: string) {
|
||||
for (const theme of this.themes) {
|
||||
if (theme.id === themeId) {
|
||||
return theme.active;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
toggleTheme(themeId: string) {
|
||||
this.#sendMessage({ type: 'toggle-theme', themeId });
|
||||
}
|
||||
|
||||
isSetActive(setId: string) {
|
||||
for (const set of this.sets) {
|
||||
if (set.id === setId) {
|
||||
return set.active;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
toggleSet(setId: string) {
|
||||
this.#sendMessage({ type: 'toggle-set', setId });
|
||||
}
|
||||
|
||||
applyToken(tokenId: string) {
|
||||
if (this.currentSetId) {
|
||||
this.#sendMessage({
|
||||
type: 'apply-token',
|
||||
setId: this.currentSetId,
|
||||
tokenId,
|
||||
// attributes: ['stroke-color'] // Uncomment to choose attribute to apply
|
||||
}); // (incompatible attributes will have no effect)
|
||||
}
|
||||
}
|
||||
|
||||
#sendMessage(message: PluginUIEvent) {
|
||||
parent.postMessage(message, '*');
|
||||
}
|
||||
|
||||
#setThemes(themes: TokenTheme[]) {
|
||||
this.themes = themes;
|
||||
}
|
||||
|
||||
#setSets(sets: TokenSet[]) {
|
||||
this.sets = sets;
|
||||
}
|
||||
|
||||
#setTokens(tokenGroups: TokensGroup[]) {
|
||||
this.tokenGroups = tokenGroups;
|
||||
}
|
||||
|
||||
#randomString() {
|
||||
// Generate a big random number and convert it to string using base 36
|
||||
// (the number of letters in the ascii alphabet)
|
||||
return Math.floor(Math.random() * Date.now()).toString(36);
|
||||
}
|
||||
}
|
||||
11
plugins/apps/poc-tokens-plugin/src/app/app.config.ts
Normal file
11
plugins/apps/poc-tokens-plugin/src/app/app.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
],
|
||||
};
|
||||
3
plugins/apps/poc-tokens-plugin/src/app/app.routes.ts
Normal file
3
plugins/apps/poc-tokens-plugin/src/app/app.routes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export const routes: Routes = [];
|
||||
1
plugins/apps/poc-tokens-plugin/src/assets/CORS
Normal file
1
plugins/apps/poc-tokens-plugin/src/assets/CORS
Normal file
@@ -0,0 +1 @@
|
||||
*
|
||||
2
plugins/apps/poc-tokens-plugin/src/assets/_headers
Normal file
2
plugins/apps/poc-tokens-plugin/src/assets/_headers
Normal file
@@ -0,0 +1,2 @@
|
||||
/*
|
||||
Access-Control-Allow-Origin: *
|
||||
BIN
plugins/apps/poc-tokens-plugin/src/assets/favicon.ico
Normal file
BIN
plugins/apps/poc-tokens-plugin/src/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
plugins/apps/poc-tokens-plugin/src/assets/icon.png
Normal file
BIN
plugins/apps/poc-tokens-plugin/src/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
14
plugins/apps/poc-tokens-plugin/src/assets/manifest.json
Normal file
14
plugins/apps/poc-tokens-plugin/src/assets/manifest.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Design tokens plugin POC",
|
||||
"description": "This is a plugin to try Design Tokens in Penpot API",
|
||||
"code": "/assets/plugin.js",
|
||||
"permissions": [
|
||||
"page:read",
|
||||
"content:read",
|
||||
"file:read",
|
||||
"selection:read",
|
||||
"content:write",
|
||||
"library:read",
|
||||
"library:write"
|
||||
]
|
||||
}
|
||||
13
plugins/apps/poc-tokens-plugin/src/index.html
Normal file
13
plugins/apps/poc-tokens-plugin/src/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Angular example plugin</title>
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
7
plugins/apps/poc-tokens-plugin/src/main.ts
Normal file
7
plugins/apps/poc-tokens-plugin/src/main.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { AppComponent } from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||
console.error(err),
|
||||
);
|
||||
112
plugins/apps/poc-tokens-plugin/src/model.ts
Normal file
112
plugins/apps/poc-tokens-plugin/src/model.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { TokenProperty } from '@penpot/plugin-types';
|
||||
|
||||
/**
|
||||
* This file contains the typescript interfaces for the plugin events.
|
||||
*/
|
||||
|
||||
// Events sent from the ui to the plugin
|
||||
|
||||
export interface LoadLibraryEvent {
|
||||
type: 'load-library';
|
||||
}
|
||||
|
||||
export interface LoadTokensEvent {
|
||||
type: 'load-tokens';
|
||||
setId: string;
|
||||
}
|
||||
|
||||
export interface AddThemeEvent {
|
||||
type: 'add-theme';
|
||||
themeGroup: string;
|
||||
themeName: string;
|
||||
}
|
||||
|
||||
export interface AddSetEvent {
|
||||
type: 'add-set';
|
||||
setName: string;
|
||||
}
|
||||
|
||||
export interface AddTokenEvent {
|
||||
type: 'add-token';
|
||||
setId: string;
|
||||
tokenType: string;
|
||||
tokenName: string;
|
||||
tokenValue: unknown;
|
||||
}
|
||||
|
||||
export interface RenameThemeEvent {
|
||||
type: 'rename-theme';
|
||||
themeId: string;
|
||||
newName: string;
|
||||
}
|
||||
|
||||
export interface RenameSetEvent {
|
||||
type: 'rename-set';
|
||||
setId: string;
|
||||
newName: string;
|
||||
}
|
||||
|
||||
export interface RenameTokenEvent {
|
||||
type: 'rename-token';
|
||||
setId: string;
|
||||
tokenId: string;
|
||||
newName: string;
|
||||
}
|
||||
|
||||
export interface DeleteThemeEvent {
|
||||
type: 'delete-theme';
|
||||
themeId: string;
|
||||
}
|
||||
|
||||
export interface DeleteSetEvent {
|
||||
type: 'delete-set';
|
||||
setId: string;
|
||||
}
|
||||
|
||||
export interface DeleteTokenEvent {
|
||||
type: 'delete-token';
|
||||
setId: string;
|
||||
tokenId: string;
|
||||
}
|
||||
|
||||
export interface ToggleThemeEvent {
|
||||
type: 'toggle-theme';
|
||||
themeId: string;
|
||||
}
|
||||
|
||||
export interface ToggleSetEvent {
|
||||
type: 'toggle-set';
|
||||
setId: string;
|
||||
}
|
||||
|
||||
export interface ApplyTokenEvent {
|
||||
type: 'apply-token';
|
||||
setId: string;
|
||||
tokenId: string;
|
||||
attributes?: TokenProperty[];
|
||||
}
|
||||
|
||||
export type PluginUIEvent =
|
||||
| LoadLibraryEvent
|
||||
| LoadTokensEvent
|
||||
| AddThemeEvent
|
||||
| AddSetEvent
|
||||
| AddTokenEvent
|
||||
| RenameThemeEvent
|
||||
| RenameSetEvent
|
||||
| RenameTokenEvent
|
||||
| DeleteThemeEvent
|
||||
| DeleteSetEvent
|
||||
| DeleteTokenEvent
|
||||
| ToggleThemeEvent
|
||||
| ToggleSetEvent
|
||||
| ApplyTokenEvent;
|
||||
|
||||
// Events sent from the plugin to the ui
|
||||
|
||||
export interface ThemePluginEvent {
|
||||
type: 'theme';
|
||||
content: string;
|
||||
}
|
||||
|
||||
export type PluginMessageEvent = ThemePluginEvent;
|
||||
246
plugins/apps/poc-tokens-plugin/src/plugin.ts
Normal file
246
plugins/apps/poc-tokens-plugin/src/plugin.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import type { PluginMessageEvent, PluginUIEvent } from './model.js';
|
||||
import { TokenType, TokenProperty } from '@penpot/plugin-types';
|
||||
|
||||
penpot.ui.open('Design Tokens test', `?theme=${penpot.theme}`, {
|
||||
width: 1000,
|
||||
height: 800,
|
||||
});
|
||||
|
||||
penpot.on('themechange', (theme) => {
|
||||
sendMessage({ type: 'theme', content: theme });
|
||||
});
|
||||
|
||||
penpot.ui.onMessage<PluginUIEvent>(async (message) => {
|
||||
if (message.type === 'load-library') {
|
||||
loadLibrary();
|
||||
} else if (message.type === 'load-tokens') {
|
||||
loadTokens(message.setId);
|
||||
} else if (message.type === 'add-theme') {
|
||||
addTheme(message.themeGroup, message.themeName);
|
||||
} else if (message.type === 'add-set') {
|
||||
addSet(message.setName);
|
||||
} else if (message.type === 'add-token') {
|
||||
addToken(
|
||||
message.setId,
|
||||
message.tokenType,
|
||||
message.tokenName,
|
||||
message.tokenValue,
|
||||
);
|
||||
} else if (message.type === 'rename-theme') {
|
||||
renameTheme(message.themeId, message.newName);
|
||||
} else if (message.type === 'rename-set') {
|
||||
renameSet(message.setId, message.newName);
|
||||
} else if (message.type === 'rename-token') {
|
||||
renameToken(message.setId, message.tokenId, message.newName);
|
||||
} else if (message.type === 'delete-theme') {
|
||||
deleteTheme(message.themeId);
|
||||
} else if (message.type === 'delete-set') {
|
||||
deleteSet(message.setId);
|
||||
} else if (message.type === 'delete-token') {
|
||||
deleteToken(message.setId, message.tokenId);
|
||||
} else if (message.type === 'toggle-theme') {
|
||||
toggleTheme(message.themeId);
|
||||
} else if (message.type === 'toggle-set') {
|
||||
toggleSet(message.setId);
|
||||
} else if (message.type === 'apply-token') {
|
||||
applyToken(message.setId, message.tokenId, message.attributes);
|
||||
}
|
||||
});
|
||||
|
||||
function sendMessage(message: PluginMessageEvent) {
|
||||
penpot.ui.sendMessage(message);
|
||||
}
|
||||
|
||||
function loadLibrary() {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
|
||||
const themes = tokensCatalog.themes;
|
||||
|
||||
const themesData = themes.map((theme) => {
|
||||
return {
|
||||
id: theme.id,
|
||||
group: theme.group,
|
||||
name: theme.name,
|
||||
active: theme.active,
|
||||
};
|
||||
});
|
||||
|
||||
penpot.ui.sendMessage({
|
||||
source: 'penpot',
|
||||
type: 'set-themes',
|
||||
themesData,
|
||||
});
|
||||
|
||||
const sets = tokensCatalog.sets;
|
||||
|
||||
const setsData = sets.map((set) => {
|
||||
return {
|
||||
id: set.id,
|
||||
name: set.name,
|
||||
active: set.active,
|
||||
};
|
||||
});
|
||||
|
||||
penpot.ui.sendMessage({
|
||||
source: 'penpot',
|
||||
type: 'set-sets',
|
||||
setsData,
|
||||
});
|
||||
}
|
||||
|
||||
function loadTokens(setId: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const set = tokensCatalog?.getSetById(setId);
|
||||
const tokensByType = set?.tokensByType;
|
||||
|
||||
const tokenGroupsData = [];
|
||||
if (tokensByType) {
|
||||
for (const group of tokensByType) {
|
||||
const type = group[0];
|
||||
const tokens = group[1];
|
||||
tokenGroupsData.push([
|
||||
type,
|
||||
tokens.map((token) => {
|
||||
return {
|
||||
id: token.id,
|
||||
name: token.name,
|
||||
description: token.description,
|
||||
};
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
penpot.ui.sendMessage({
|
||||
source: 'penpot',
|
||||
type: 'set-tokens',
|
||||
tokenGroupsData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addTheme(themeGroup: string, themeName: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const theme = tokensCatalog?.addTheme(themeGroup, themeName);
|
||||
if (theme) {
|
||||
loadLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
function addSet(setName: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const set = tokensCatalog?.addSet(setName);
|
||||
if (set) {
|
||||
loadLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
function addToken(
|
||||
setId: string,
|
||||
tokenType: string,
|
||||
tokenName: string,
|
||||
tokenValue: unknown,
|
||||
) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const set = tokensCatalog?.getSetById(setId);
|
||||
const token = set?.addToken(tokenType as TokenType, tokenName, tokenValue);
|
||||
if (token) {
|
||||
loadTokens(setId);
|
||||
}
|
||||
}
|
||||
|
||||
function renameTheme(themeId: string, newName: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const theme = tokensCatalog?.getThemeById(themeId);
|
||||
if (theme) {
|
||||
theme.name = newName;
|
||||
loadLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
function renameSet(setId: string, newName: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const set = tokensCatalog?.getSetById(setId);
|
||||
if (set) {
|
||||
set.name = newName;
|
||||
loadLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
function renameToken(setId: string, tokenId: string, newName: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const set = tokensCatalog?.getSetById(setId);
|
||||
const token = set?.getTokenById(tokenId);
|
||||
if (token) {
|
||||
token.name = newName;
|
||||
loadTokens(setId);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteTheme(themeId: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const theme = tokensCatalog?.getThemeById(themeId);
|
||||
if (theme) {
|
||||
theme.remove();
|
||||
loadLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSet(setId: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const set = tokensCatalog?.getSetById(setId);
|
||||
if (set) {
|
||||
set.remove();
|
||||
loadLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteToken(setId: string, tokenId: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const set = tokensCatalog?.getSetById(setId);
|
||||
const token = set?.getTokenById(tokenId);
|
||||
if (token) {
|
||||
token.remove();
|
||||
loadTokens(setId);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme(themeId: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const theme = tokensCatalog?.getThemeById(themeId);
|
||||
if (theme) {
|
||||
theme.toggleActive();
|
||||
loadLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSet(setId: string) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const set = tokensCatalog?.getSetById(setId);
|
||||
if (set) {
|
||||
set.toggleActive();
|
||||
loadLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
function applyToken(
|
||||
setId: string,
|
||||
tokenId: string,
|
||||
attributes: TokenProperty[] | undefined,
|
||||
) {
|
||||
const tokensCatalog = penpot.library.local.tokens;
|
||||
const set = tokensCatalog?.getSetById(setId);
|
||||
const token = set?.getTokenById(tokenId);
|
||||
|
||||
if (token) {
|
||||
token.applyToSelected(attributes);
|
||||
}
|
||||
|
||||
// Alternatve way
|
||||
//
|
||||
// const selection = penpot.selection;
|
||||
// if (token && selection) {
|
||||
// for (const shape of selection) {
|
||||
// shape.applyToken(token, attributes);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
23
plugins/apps/poc-tokens-plugin/src/styles.css
Normal file
23
plugins/apps/poc-tokens-plugin/src/styles.css
Normal file
@@ -0,0 +1,23 @@
|
||||
/* @import "@penpot/plugin-styles/styles.css"; */
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
line-height: 1.5;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-block-start: var(--spacing-12);
|
||||
}
|
||||
|
||||
.title-l {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.headline-l {
|
||||
margin-block-start: var(--spacing-8);
|
||||
}
|
||||
10
plugins/apps/poc-tokens-plugin/tsconfig.app.json
Normal file
10
plugins/apps/poc-tokens-plugin/tsconfig.app.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/main.ts"],
|
||||
"include": ["src/**/*.d.ts"],
|
||||
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"]
|
||||
}
|
||||
7
plugins/apps/poc-tokens-plugin/tsconfig.editor.json
Normal file
7
plugins/apps/poc-tokens-plugin/tsconfig.editor.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src/**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
33
plugins/apps/poc-tokens-plugin/tsconfig.json
Normal file
33
plugins/apps/poc-tokens-plugin/tsconfig.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"useDefineForClassFields": false,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.editor.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.plugin.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
8
plugins/apps/poc-tokens-plugin/tsconfig.plugin.json
Normal file
8
plugins/apps/poc-tokens-plugin/tsconfig.plugin.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/plugin.ts"],
|
||||
"include": ["../../libs/plugin-types/index.d.ts"]
|
||||
}
|
||||
1078
plugins/libs/plugin-types/index.d.ts
vendored
1078
plugins/libs/plugin-types/index.d.ts
vendored
File diff suppressed because it is too large
Load Diff
27170
plugins/package-lock.json
generated
27170
plugins/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@
|
||||
"start:plugin:table": "nx run table-plugin:init",
|
||||
"start:plugin:renamelayers": "nx run rename-layers-plugin:init",
|
||||
"start:plugin:colors-to-tokens": "nx run colors-to-tokens-plugin:init",
|
||||
"start:plugin:poc-tokens": "nx run poc-tokens-plugin:init",
|
||||
"build": "nx build plugins-runtime --emptyOutDir=true",
|
||||
"build:plugins": "nx run-many -t build --parallel -p tag:type:plugin --exclude=poc-state-plugin",
|
||||
"build:styles-example": "nx run example-styles:build",
|
||||
|
||||
@@ -90,18 +90,6 @@ impl Type {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_corners(&mut self) {
|
||||
match self {
|
||||
Type::Rect(data) => {
|
||||
data.corners = None;
|
||||
}
|
||||
Type::Frame(data) => {
|
||||
data.corners = None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Type::Path(path) => Some(path),
|
||||
@@ -706,11 +694,9 @@ impl Shape {
|
||||
pub fn set_corners(&mut self, raw_corners: (f32, f32, f32, f32)) {
|
||||
if let Some(corners) = make_corners(raw_corners) {
|
||||
self.shape_type.set_corners(corners);
|
||||
} else {
|
||||
self.shape_type.clear_corners();
|
||||
self.invalidate_bounds();
|
||||
self.invalidate_extrect();
|
||||
}
|
||||
self.invalidate_bounds();
|
||||
self.invalidate_extrect();
|
||||
}
|
||||
|
||||
pub fn set_svg(&mut self, svg: skia::svg::Dom) {
|
||||
@@ -1604,13 +1590,6 @@ mod tests {
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
shape.set_corners((0.0, 0.0, 0.0, 0.0));
|
||||
if let Type::Rect(Rect { corners, .. }) = shape.shape_type {
|
||||
assert_eq!(corners, None);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user