Compare commits

...

7 Commits

Author SHA1 Message Date
Francis Santiago
e44488c372 🐛 Fix enable IPv6 support in nginx frontend 2026-02-05 20:07:44 +01:00
Andrey Antukh
2ccb33ba89 📎 Add missing for-update for the migration 145 2026-02-05 18:12:11 +01:00
Andrey Antukh
ee88ee63a2 Add data migration for fix plugins data on profiles 2026-02-05 18:08:28 +01:00
Eva Marco
f961f9a123 🐛 Fix several bugs (#8267)
* ♻️ Remove rename warning

* 🐛 Fix opacity value
2026-02-05 11:34:14 +01:00
Eva Marco
dda3377596 🐛 Allow detach broken token from input (#8242)
* 🐛 Allow detach broken token from input

* 🐛 Fix multiselection on multiple token applied

* ♻️ Remove detach-token new fn
2026-02-05 11:28:47 +01:00
Andrey Antukh
17935443df Move all tokenscript related adaptations to a separared package 2026-02-05 09:45:55 +01:00
Florian Schroedl
150d57b1eb Add tokenscript MVP 2026-02-05 09:45:55 +01:00
35 changed files with 2136 additions and 124 deletions

View File

@@ -10,6 +10,7 @@
[app.common.logging :as l]
[app.db :as db]
[app.migrations.clj.migration-0023 :as mg0023]
[app.migrations.clj.migration-0145 :as mg0145]
[app.util.migrations :as mg]
[integrant.core :as ig]))
@@ -459,7 +460,11 @@
:fn (mg/resource "app/migrations/sql/0143-add-http-session-v2-table.sql")}
{:name "0144-mod-server-error-report-table"
:fn (mg/resource "app/migrations/sql/0144-mod-server-error-report-table.sql")}])
:fn (mg/resource "app/migrations/sql/0144-mod-server-error-report-table.sql")}
{:name "0145-fix-plugins-uri-on-profile"
:fn mg0145/migrate}])
(defn apply-migrations!
[pool name migrations]

View File

@@ -0,0 +1,83 @@
;; 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.migrations.clj.migration-0145
"Migrate plugins references on profiles"
(:require
[app.common.data :as d]
[app.common.logging :as l]
[app.db :as db]
[cuerdas.core :as str]))
(def ^:private replacements
{"https://colors-to-tokens-plugin.pages.dev"
"https://colors-to-tokens.plugins.penpot.app"
"https://contrast-penpot-plugin.pages.dev"
"https://contrast.plugins.penpot.app"
"https://create-palette-penpot-plugin.pages.dev"
"https://create-palette.plugins.penpot.app"
"https://icons-penpot-plugin.pages.dev"
"https://icons.plugins.penpot.app"
"https://lorem-ipsum-penpot-plugin.pages.dev"
"https://lorem-ipsum.plugins.penpot.app"
"https://rename-layers-penpot-plugin.pages.dev"
"https://rename-layers.plugins.penpot.app"
"https://table-penpot-plugin.pages.dev"
"https://table.plugins.penpot.app"})
(defn- fix-url
[url]
(reduce-kv (fn [url prefix replacement]
(if (str/starts-with? url prefix)
(reduced (str replacement (subs url (count prefix))))
url))
url
replacements))
(defn- fix-manifest
[manifest]
(-> manifest
(d/update-when :url fix-url)
(d/update-when :host fix-url)))
(defn- fix-plugins-data
[props]
(d/update-in-when props [:plugins :data]
(fn [data]
(reduce-kv (fn [data id manifest]
(let [manifest' (fix-manifest manifest)]
(if (= manifest manifest')
data
(assoc data id manifest'))))
data
data))))
(def ^:private sql:get-profiles
"SELECT id, props FROM profile
WHERE props ?? '~:plugins'
ORDER BY created_at
FOR UPDATE")
(defn migrate
[conn]
(->> (db/plan conn [sql:get-profiles])
(run! (fn [{:keys [id props]}]
(when-let [props (some-> props db/decode-transit-pgobject)]
(let [props' (fix-plugins-data props)]
(when (not= props props')
(l/inf :hint "fixing plugins data on profile props" :profile-id (str id))
(db/update! conn :profile
{:props (db/tjson props')}
{:id id}
{::db/return-keys false}))))))))

View File

@@ -39,12 +39,12 @@
(into {})))
(defn remove-attributes-for-token
"Removes applied tokens with `token-id` for the given `attributes` set from `applied-tokens`."
[attributes token applied-tokens]
"Removes applied tokens with `token-name` for the given `attributes` set from `applied-tokens`."
[attributes token-name applied-tokens]
(let [attr? (set attributes)]
(->> (remove (fn [[k v]]
(and (attr? k)
(= v (or (token-identifier token) token))))
(= v token-name)))
applied-tokens)
(into {}))))

View File

@@ -124,6 +124,7 @@
:token-base-font-size
:token-color
:token-shadow
:token-tokenscript
:transit-readable-response
:user-feedback
;; TODO: remove this flag.

View File

@@ -1 +1 @@
resolver $PENPOT_INTERNAL_RESOLVER ipv6=off valid=10s;
resolver $PENPOT_INTERNAL_RESOLVER valid=10s;

View File

@@ -73,6 +73,7 @@ http {
server {
listen 8080 default_server;
listen [::]:8080 default_server;
server_name _;
client_max_body_size $PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE;

View File

@@ -47,6 +47,7 @@
"devDependencies": {
"@penpot/draft-js": "workspace:./packages/draft-js",
"@penpot/mousetrap": "workspace:./packages/mousetrap",
"@penpot/tokenscript": "workspace:./packages/tokenscript",
"@penpot/plugins-runtime": "1.4.2",
"@penpot/svgo": "penpot/svgo#v3.2",
"@penpot/text-editor": "workspace:./text-editor",

View File

@@ -0,0 +1,3 @@
node_modules
*.sublime-workspace
/.yarn

View File

@@ -0,0 +1,2 @@
export * from "./schemas.js";
export * from "@tokens-studio/tokenscript-interpreter";

View File

@@ -0,0 +1,13 @@
{
"name": "@penpot/tokenscript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
"author": "Andrey Antukh",
"license": "MPL-2.0",
"dependencies": {
"@tokens-studio/tokenscript-interpreter": "^0.23.1"
}
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -774,7 +774,7 @@ test.describe("Tokens: Apply token", () => {
await workspace.layers
.getByTestId("layer-row")
.nth(1)
.getByRole("button", { name: "Toggle layer" })
.getByTestId("toggle-content")
.click();
await workspace.layers.getByTestId("layer-row").nth(2).click();
@@ -831,15 +831,102 @@ test.describe("Tokens: Apply token", () => {
});
await detachButton.click();
await expect(marginPillXL).not.toBeVisible();
const horizontalMarginInput = layoutItemSectionSidebar.getByText('Horizontal marginOpen token');
const horizontalMarginInput = layoutItemSectionSidebar.getByText(
"Horizontal marginOpen token",
);
await expect(horizontalMarginInput).toBeVisible();
const tokenDropdown = horizontalMarginInput.getByRole('button', { name: 'Open token list' });
const tokenDropdown = horizontalMarginInput.getByRole("button", {
name: "Open token list",
});
await tokenDropdown.click();
await expect(dimensionTokenOptionXl).toBeVisible();
await dimensionTokenOptionXl.click();
await expect(marginPillXL).toBeVisible();
});
});
test.describe("Tokens: Detach token", () => {
test("User applies border-radius 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 border radius tokens
await page.getByRole("button", { name: "Border Radius 3" }).click();
await expect(
tokensSidebar.getByRole("button", { name: "borderRadius" }),
).toBeVisible();
await tokensSidebar.getByRole("button", { name: "borderRadius" }).click();
await expect(
tokensSidebar.getByRole("button", { name: "borderRadius.sm" }),
).toBeVisible();
// Apply border radius token from token panels
await tokensSidebar
.getByRole("button", { name: "borderRadius.sm" })
.click();
// Check if border radius sections is visible on right sidebar
const borderRadiusSection = page.getByRole("region", {
name: "border-radius-section",
});
await expect(borderRadiusSection).toBeVisible();
// Check if token pill is visible on design tab on right sidebar
const brTokenPillSM = borderRadiusSection.getByRole("button", {
name: "borderRadius.sm",
});
await expect(brTokenPillSM).toBeVisible();
await brTokenPillSM.click();
// Rename token
await tokensSidebar
.getByRole("button", { name: "borderRadius.sm" })
.click({ button: "right" });
await expect(page.getByText("Edit token")).toBeVisible();
await page.getByText("Edit token").click();
const editModal = page.getByTestId("token-update-create-modal");
await expect(editModal).toBeVisible();
await expect(
editModal.getByRole("textbox", { name: "Name" }),
).toBeVisible();
await editModal
.getByRole("textbox", { name: "Name" })
.fill("BorderRadius.smBis");
const submitButton = editModal.getByRole("button", { name: "Save" });
await expect(submitButton).toBeEnabled();
await submitButton.click();
await expect(page.getByText("Don't remap")).toBeVisible();
await page.getByText("Don't remap").click();
const brokenPill = borderRadiusSection.getByRole("button", {
name: "This token is not in any",
});
await expect(brokenPill).toBeVisible();
// Detach broken token
const detachButton = borderRadiusSection.getByRole("button", {
name: "Detach token",
});
await detachButton.click();
await expect(brokenPill).not.toBeVisible();
//De-select and select shape again to double check token is detached
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers.getByTestId("layer-row").nth(0).click();
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
await expect(brokenPill).not.toBeVisible();
});
});

123
frontend/pnpm-lock.yaml generated
View File

@@ -28,6 +28,9 @@ importers:
'@penpot/text-editor':
specifier: workspace:./text-editor
version: link:text-editor
'@penpot/tokenscript':
specifier: workspace:./packages/tokenscript
version: link:packages/tokenscript
'@playwright/test':
specifier: 1.58.0
version: 1.58.0
@@ -248,6 +251,12 @@ importers:
packages/mousetrap: {}
packages/tokenscript:
dependencies:
'@tokens-studio/tokenscript-interpreter':
specifier: ^0.23.1
version: 0.23.1
text-editor:
devDependencies:
'@playwright/test':
@@ -299,6 +308,12 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
'@ark/schema@0.56.0':
resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==}
'@ark/util@0.56.0':
resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==}
'@asamuzakjp/css-color@4.1.1':
resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==}
@@ -924,36 +939,42 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.1':
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.1':
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
@@ -1035,24 +1056,28 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@resvg/resvg-js-linux-arm64-musl@2.6.2':
resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@resvg/resvg-js-linux-x64-gnu@2.6.2':
resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@resvg/resvg-js-linux-x64-musl@2.6.2':
resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@resvg/resvg-js-win32-arm64-msvc@2.6.2':
resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==}
@@ -1149,121 +1174,145 @@ packages:
resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-gnueabihf@4.56.0':
resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.54.0':
resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm-musleabihf@4.56.0':
resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.54.0':
resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-gnu@4.56.0':
resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.54.0':
resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-musl@4.56.0':
resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.54.0':
resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-gnu@4.56.0':
resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.56.0':
resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.54.0':
resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-gnu@4.56.0':
resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.56.0':
resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.54.0':
resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.56.0':
resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.54.0':
resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-musl@4.56.0':
resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.54.0':
resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.56.0':
resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.54.0':
resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.56.0':
resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.54.0':
resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-x64-musl@4.56.0':
resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.56.0':
resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==}
@@ -1436,6 +1485,11 @@ packages:
peerDependencies:
style-dictionary: '>=4.3.0 < 6'
'@tokens-studio/tokenscript-interpreter@0.23.1':
resolution: {integrity: sha512-aIcJprCkHIyckl0Knn78Sn7ef3U3IXLjNv9MOePdNR0Mz3Z4PleerldtfLmr1DdXUXiroVSyJROyJrO3TfB2Gg==}
engines: {node: '>=16.0.0'}
hasBin: true
'@tokens-studio/types@0.5.2':
resolution: {integrity: sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==}
@@ -1674,6 +1728,12 @@ packages:
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
engines: {node: '>= 0.4'}
arkregex@0.0.5:
resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==}
arktype@2.1.29:
resolution: {integrity: sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ==}
array-buffer-byte-length@1.0.2:
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
engines: {node: '>= 0.4'}
@@ -1782,6 +1842,9 @@ packages:
buffer-builder@0.2.0:
resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==}
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -1952,6 +2015,10 @@ packages:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'}
commander@14.0.2:
resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}
engines: {node: '>=20'}
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
@@ -3360,6 +3427,9 @@ packages:
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
engines: {node: '>= 14.16'}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -3637,6 +3707,10 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
readline-sync@1.4.10:
resolution: {integrity: sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==}
engines: {node: '>= 0.8.0'}
recast@0.23.11:
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
engines: {node: '>= 4'}
@@ -3784,48 +3858,56 @@ packages:
engines: {node: '>=14.0.0'}
cpu: [arm64]
os: [linux]
libc: glibc
sass-embedded-linux-arm@1.97.1:
resolution: {integrity: sha512-48VxaTUApLyx1NXFdZhKqI/7FYLmz8Ju3Ki2V/p+mhn5raHgAiYeFgn8O1WGxTOh+hBb9y3FdSR5a8MNTbmKMQ==}
engines: {node: '>=14.0.0'}
cpu: [arm]
os: [linux]
libc: glibc
sass-embedded-linux-musl-arm64@1.97.1:
resolution: {integrity: sha512-kD35WSD9o0279Ptwid3Jnbovo1FYnuG2mayYk9z4ZI4mweXEK6vTu+tlvCE/MdF/zFKSj11qaxaH+uzXe2cO5A==}
engines: {node: '>=14.0.0'}
cpu: [arm64]
os: [linux]
libc: musl
sass-embedded-linux-musl-arm@1.97.1:
resolution: {integrity: sha512-FUFs466t3PVViVOKY/60JgLLtl61Pf7OW+g5BeEfuqVcSvYUECVHeiYHtX1fT78PEVa0h9tHpM6XpWti+7WYFA==}
engines: {node: '>=14.0.0'}
cpu: [arm]
os: [linux]
libc: musl
sass-embedded-linux-musl-riscv64@1.97.1:
resolution: {integrity: sha512-ZgpYps5YHuhA2+KiLkPukRbS5298QObgUhPll/gm5i0LOZleKCwrFELpVPcbhsSBuxqji2uaag5OL+n3JRBVVg==}
engines: {node: '>=14.0.0'}
cpu: [riscv64]
os: [linux]
libc: musl
sass-embedded-linux-musl-x64@1.97.1:
resolution: {integrity: sha512-wcAigOyyvZ6o1zVypWV7QLZqpOEVnlBqJr9MbpnRIm74qFTSbAEmShoh8yMXBymzuVSmEbThxAwW01/TLf62tA==}
engines: {node: '>=14.0.0'}
cpu: [x64]
os: [linux]
libc: musl
sass-embedded-linux-riscv64@1.97.1:
resolution: {integrity: sha512-9j1qE1ZrLMuGb+LUmBzw93Z4TNfqlRkkxjPVZy6u5vIggeSfvGbte7eRoYBNWX6SFew/yBCL90KXIirWFSGrlQ==}
engines: {node: '>=14.0.0'}
cpu: [riscv64]
os: [linux]
libc: glibc
sass-embedded-linux-x64@1.97.1:
resolution: {integrity: sha512-7nrLFYMH/UgvEgXR5JxQJ6y9N4IJmnFnYoDxN0nw0jUp+CQWQL4EJ4RqAKTGelneueRbccvt2sEyPK+X0KJ9Jg==}
engines: {node: '>=14.0.0'}
cpu: [x64]
os: [linux]
libc: glibc
sass-embedded-unknown-all@1.97.1:
resolution: {integrity: sha512-oPSeKc7vS2dx3ZJHiUhHKcyqNq0GWzAiR8zMVpPd/kVMl5ZfVyw+5HTCxxWDBGkX02lNpou27JkeBPCaneYGAQ==}
@@ -4156,6 +4238,7 @@ packages:
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
tdigest@0.1.2:
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
@@ -4677,6 +4760,10 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yauzl@3.2.0:
resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==}
engines: {node: '>=12'}
yocto-queue@1.2.2:
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
engines: {node: '>=12.20'}
@@ -4695,6 +4782,12 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
'@ark/schema@0.56.0':
dependencies:
'@ark/util': 0.56.0
'@ark/util@0.56.0': {}
'@asamuzakjp/css-color@4.1.1':
dependencies:
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
@@ -5608,6 +5701,13 @@ snapshots:
is-mergeable-object: 1.1.1
style-dictionary: 5.0.0-rc.1
'@tokens-studio/tokenscript-interpreter@0.23.1':
dependencies:
arktype: 2.1.29
commander: 14.0.2
readline-sync: 1.4.10
yauzl: 3.2.0
'@tokens-studio/types@0.5.2': {}
'@trysound/sax@0.2.0': {}
@@ -5901,6 +6001,16 @@ snapshots:
aria-query@5.3.2: {}
arkregex@0.0.5:
dependencies:
'@ark/util': 0.56.0
arktype@2.1.29:
dependencies:
'@ark/schema': 0.56.0
'@ark/util': 0.56.0
arkregex: 0.0.5
array-buffer-byte-length@1.0.2:
dependencies:
call-bound: 1.0.4
@@ -6040,6 +6150,8 @@ snapshots:
buffer-builder@0.2.0: {}
buffer-crc32@0.2.13: {}
buffer-from@1.1.2: {}
buffer@5.7.1:
@@ -6212,6 +6324,8 @@ snapshots:
commander@12.1.0: {}
commander@14.0.2: {}
commander@7.2.0: {}
component-emitter@2.0.0: {}
@@ -7735,6 +7849,8 @@ snapshots:
pathval@2.0.1: {}
pend@1.2.0: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@@ -8029,6 +8145,8 @@ snapshots:
readdirp@4.1.2: {}
readline-sync@1.4.10: {}
recast@0.23.11:
dependencies:
ast-types: 0.16.1
@@ -9195,6 +9313,11 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
yauzl@3.2.0:
dependencies:
buffer-crc32: 0.2.13
pend: 1.2.0
yocto-queue@1.2.2: {}
zod@3.25.76: {}

View File

@@ -6,4 +6,5 @@ shamefullyHoist: true
packages:
- "packages/draft-js"
- "packages/mousetrap"
- "packages/tokenscript"
- "text-editor"

View File

@@ -14,7 +14,9 @@
[app.common.time :as ct]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.config :as cf]
[app.main.data.tinycolor :as tinycolor]
[app.main.data.tokenscript :as ts]
[app.main.data.workspace.tokens.errors :as wte]
[app.main.data.workspace.tokens.warnings :as wtw]
[beicon.v2.core :as rx]
@@ -586,22 +588,25 @@
;; FIXME: this with effect with trigger all the time because
;; `config` will be always a different instance
(mf/with-effect [tokens config]
(let [cached (get @cache-atom tokens)]
(cond
(nil? tokens) nil
;; The tokens are already processing somewhere
(rx/observable? cached) (rx/sub! cached #(reset! tokens-state %))
;; Get the cached entry
(some? cached) (reset! tokens-state cached)
;; No cached entry, start processing
:else (let [resolved-tokens-s (if interactive?
(resolve-tokens-interactive tokens)
(resolve-tokens tokens))]
(swap! cache-atom assoc tokens resolved-tokens-s)
(rx/sub! resolved-tokens-s (fn [resolved-tokens]
(swap! cache-atom assoc tokens resolved-tokens)
(reset! tokens-state resolved-tokens)))))))
@tokens-state))
(when-not (contains? cf/flags :tokenscript)
(let [cached (get @cache-atom tokens)]
(cond
(nil? tokens) nil
;; The tokens are already processing somewhere
(rx/observable? cached) (rx/sub! cached #(reset! tokens-state %))
;; Get the cached entry
(some? cached) (reset! tokens-state cached)
;; No cached entry, start processing
:else (let [resolved-tokens-s (if interactive?
(resolve-tokens-interactive tokens)
(resolve-tokens tokens))]
(swap! cache-atom assoc tokens resolved-tokens-s)
(rx/sub! resolved-tokens-s (fn [resolved-tokens]
(swap! cache-atom assoc tokens resolved-tokens)
(reset! tokens-state resolved-tokens))))))))
(if (contains? cf/flags :tokenscript)
(ts/resolve-tokens tokens)
@tokens-state)))
(defn use-resolved-tokens*
"This hook will return the unresolved tokens as state until they are
@@ -612,16 +617,19 @@
[tokens & {:keys [interactive?]}]
(let [state* (mf/use-state tokens)]
(mf/with-effect [tokens interactive?]
(if (seq tokens)
(let [tpoint (ct/tpoint-ms)
tokens-s (if interactive?
(resolve-tokens-interactive tokens)
(resolve-tokens tokens))]
(when-not (contains? cf/flags :tokenscript)
(if (seq tokens)
(let [tpoint (ct/tpoint-ms)
tokens-s (if interactive?
(resolve-tokens-interactive tokens)
(resolve-tokens tokens))]
(-> tokens-s
(rx/sub! (fn [resolved-tokens]
(let [elapsed (tpoint)]
(l/dbg :hint "use-resolved-tokens*" :elapsed elapsed)
(reset! state* resolved-tokens))))))
(reset! state* tokens)))
@state*))
(-> tokens-s
(rx/sub! (fn [resolved-tokens]
(let [elapsed (tpoint)]
(l/dbg :hint "use-resolved-tokens*" :elapsed elapsed)
(reset! state* resolved-tokens))))))
(reset! state* tokens))))
(if (contains? cf/flags :tokenscript)
(ts/resolve-tokens tokens)
@state*)))

View File

@@ -0,0 +1,164 @@
(ns app.main.data.tokenscript
(:require
["@penpot/tokenscript" :refer [BaseSymbolType
ColorSymbol
ListSymbol NumberSymbol
NumberWithUnitSymbol
ProcessorError
processTokens
TokenSymbol
makeConfig]]
[app.common.logging :as l]
[app.common.time :as ct]
[app.main.data.workspace.tokens.errors :as wte]))
(l/set-level! :debug)
;; Config ----------------------------------------------------------------------
(def config (makeConfig))
;; Class predicates ------------------------------------------------------------
;; Predicates to get information about the tokenscript interpreter symbol type
;; Or to determine the error
(defn tokenscript-symbol? [v]
(instance? BaseSymbolType v))
(defn structured-token? [v]
(instance? TokenSymbol v))
(defn structured-record-token? [^js v]
(and (structured-token? v) (instance? js/Map (.-value v))))
(defn structured-array-token? [^js v]
(and (structured-token? v) (instance? js/Array (.-value v))))
(defn number-with-unit-symbol? [v]
(instance? NumberWithUnitSymbol v))
(defn number-symbol? [v]
(instance? NumberSymbol v))
(defn list-symbol? [v]
(instance? ListSymbol v))
(defn color-symbol? [v]
(instance? ColorSymbol v))
(defn processor-error? [err]
(instance? ProcessorError err))
;; Conversion Tools ------------------------------------------------------------
;; Helpers to convert tokenscript symbols to penpot accepted formats
(defn color-symbol->hex-string [^js v]
(when (color-symbol? v)
(.toString (.to v "hex"))))
(defn color-alpha [^js v]
(if (.isHex v)
1
(or (.getAttribute v "alpha") 1)))
(defn color-symbol->penpot-color [^js v]
{:color (color-symbol->hex-string v)
:opacity (color-alpha v)})
(defn rem-number-with-unit? [v]
(and (number-with-unit-symbol? v)
(= (.-unit v) "rem")))
(defn rem->px [^js v]
(* (.-value v) 16))
(declare tokenscript-symbols->penpot-unit)
(defn structured-token->penpot-map
"Converts structured token (record or array) to penpot map format.
Structured tokens are non-primitive token types like `typography` or `box-shadow`."
[^js token-symbol]
(if (instance? js/Array (.-value token-symbol))
(mapv structured-token->penpot-map (.-value token-symbol))
(let [entries (es6-iterator-seq (.entries (.-value token-symbol)))]
(into {} (map (fn [[k v :as V]]
[(keyword k) (tokenscript-symbols->penpot-unit v)])
entries)))))
(defn tokenscript-symbols->penpot-unit [^js v]
(cond
(structured-token? v) (structured-token->penpot-map v)
(list-symbol? v) (tokenscript-symbols->penpot-unit (.nth 1 v))
(color-symbol? v) (.-value (.to v "hex"))
(rem-number-with-unit? v) (rem->px v)
:else (.-value v)))
;; Processors ------------------------------------------------------------------
;; The processor resolves tokens
;; resolved/error tokens get put back into a clojure structure directly during build time
;; For updating tokens we use the `TokenResolver` crud methods from the processing result
;; The `TokenResolver` has detailed information for each tokens dependency graph
(defn create-token-builder
"Collects resolved tokens during build time into a clojure structure.
Returns Tokenscript Symbols in `:resolved-value` key."
[tokens]
(let [output (volatile! tokens)
;; When a token is resolved (No parsing / reference errors) we assing `:resolved-value` for the original token
on-resolve
(fn [^js/String token-name ^js/Symbol resolved-symbol]
(vswap! output assoc-in [token-name :resolved-value] resolved-symbol))
;; When a token contains any errors we assing `:errors` for the original token
on-error
(fn [^js/String token-name ^js/Error _error ^js/String _original-value]
(let [value (get tokens token-name)
default-error [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]]
(vswap! output assoc-in [token-name :errors] default-error)))
;; Extract the atom value
get-result
(fn [] @output)]
#js {:onResolve on-resolve
:onError on-error
:getResult get-result}))
(defn clj->token->tokenscript-token
"Convert penpot token into a format that tokenscript can handle."
[{:keys [type value]}]
#js {"$type" (name type)
"$value" (clj->js value)})
(defn clj-tokens->tokenscript-tokens
"Convert penpot map of tokens into tokenscript map structure.
tokenscript accepts a map of [token-name {\"$type\": string, \"$value\": any}]"
[tokens]
(let [token-map (js/Map.)]
(doseq [[k token] tokens]
(.set token-map k (clj->token->tokenscript-token token)))
token-map))
(defn process-tokens
"Builds tokens using `tokenscript`."
[tokens]
(let [input (clj-tokens->tokenscript-tokens tokens)
result (processTokens input #js {:config config
:builder (create-token-builder tokens)})]
result))
(defn update-token
[tokens token]
(let [result (process-tokens tokens)
resolver (.-resolver result)]
(.updateToken resolver #js {:tokenPath (:name token)
:tokenData (clj->token->tokenscript-token token)})))
;; Main ------------------------------------------------------------------------
(defn resolve-tokens [tokens]
(let [tpoint (ct/tpoint-ms)
result (process-tokens tokens)
elapsed (tpoint)]
(l/dbg :hint "tokenscript/resolve-tokens" :elapsed elapsed)
(.-output result)))

View File

@@ -18,11 +18,13 @@
[app.common.types.tokens-lib :as ctob]
[app.common.types.typography :as cty]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.event :as ev]
[app.main.data.helpers :as dsh]
[app.main.data.notifications :as ntf]
[app.main.data.style-dictionary :as sd]
[app.main.data.tinycolor :as tinycolor]
[app.main.data.tokenscript :as ts]
[app.main.data.workspace :as udw]
[app.main.data.workspace.colors :as wdc]
[app.main.data.workspace.shape-layout :as dwsl]
@@ -627,7 +629,9 @@
(when-let [tokens (some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(ctob/get-tokens-in-active-sets))]
(->> (sd/resolve-tokens tokens)
(->> (if (contains? cf/flags :tokenscript)
(rx/of (ts/resolve-tokens tokens))
(sd/resolve-tokens tokens))
(rx/mapcat
(fn [resolved-tokens]
(let [undo-id (js/Symbol)
@@ -645,6 +649,9 @@
any-variant? (->> shapes vals (some ctk/is-variant?) boolean)
resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value])
resolved-value (if (contains? cf/flags :tokenscript)
(ts/tokenscript-symbols->penpot-unit resolved-value)
resolved-value)
tokenized-attributes (cft/attributes-map attributes token)
type (:type token)]
(rx/concat
@@ -699,17 +706,18 @@
"Removes `attributes` that match `token` for `shape-ids`.
Doesn't update shape attributes."
[{:keys [attributes token shape-ids] :as _props}]
[{:keys [attributes token-name shape-ids] :as _props}]
(ptk/reify ::unapply-token
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(let [remove-token #(when % (cft/remove-attributes-for-token attributes token %))]
(let [remove-token #(when % (cft/remove-attributes-for-token attributes token-name %))]
(dwsh/update-shapes
shape-ids
(fn [shape]
(update shape :applied-tokens remove-token))))))))
(defn toggle-token
[{:keys [token attrs shape-ids expand-with-children]}]
(ptk/reify ::on-toggle-token
@@ -739,7 +747,7 @@
(if unapply-tokens?
(rx/of
(unapply-token {:attributes (or attrs all-attributes attributes)
:token token
:token-name (:name token)
:shape-ids shape-ids}))
(rx/of
(cond

View File

@@ -7,7 +7,9 @@
(ns app.main.data.workspace.tokens.color
(:require
[app.common.files.tokens :as cft]
[app.main.data.tinycolor :as tinycolor]))
[app.config :as cf]
[app.main.data.tinycolor :as tinycolor]
[app.main.data.tokenscript :as ts]))
(defn color-bullet-color [token-color-value]
(when-let [tc (tinycolor/valid-color token-color-value)]
@@ -17,5 +19,8 @@
(tinycolor/->hex-string tc))))
(defn resolved-token-bullet-color [{:keys [resolved-value] :as token}]
(when (and resolved-value (cft/color-token? token))
(color-bullet-color resolved-value)))
(if (contains? cf/flags :tokenscript)
(when (and resolved-value (ts/color-symbol? resolved-value))
(ts/color-symbol->penpot-color resolved-value))
(when (and resolved-value (cft/color-token? token))
(color-bullet-color resolved-value))))

View File

@@ -1,5 +1,6 @@
(ns app.main.data.workspace.tokens.format
(:require
[app.main.data.tokenscript :as ts]
[cuerdas.core :as str]))
(def category-dictionary
@@ -23,14 +24,52 @@
:color "Color"
:inset "Inner Shadow"})
(declare format-token-value)
(defn- format-map-entries
"Formats a sequence of [k v] entries into a formatted string."
[entries]
(->> entries
(map (fn [[k v]]
(str "- " (category-dictionary (keyword k)) ": " (format-token-value v))))
(str/join "\n")
(str "\n")))
(defn- format-structured-token
"Formats tokenscript Token"
[token-symbol]
(->> (.-value token-symbol)
(.entries)
(es6-iterator-seq)
(format-map-entries)))
(defn format-tokenscript-symbol
[^js tokenscript-symbol]
(cond
(ts/rem-number-with-unit? tokenscript-symbol)
(str (ts/rem->px tokenscript-symbol) "px")
(ts/color-symbol? tokenscript-symbol)
(ts/color-symbol->hex-string tokenscript-symbol)
(ts/structured-record-token? tokenscript-symbol)
(format-structured-token tokenscript-symbol)
(ts/structured-array-token? tokenscript-symbol)
(str/join "\n" (map format-tokenscript-symbol (.-value tokenscript-symbol)))
:else
(.toString tokenscript-symbol)))
(defn format-token-value
"Converts token value of any shape to a string."
[token-value]
(cond
(ts/tokenscript-symbol? token-value)
(format-tokenscript-symbol token-value)
(map? token-value)
(->> (map (fn [[k v]] (str "- " (category-dictionary k) ": " (format-token-value v))) token-value)
(str/join "\n")
(str "\n"))
(format-map-entries token-value)
(and (sequential? token-value) (every? map? token-value))
(str/join "\n" (map format-token-value token-value))

View File

@@ -564,18 +564,17 @@
(mf/use-fn
(mf/deps on-detach tokens disabled token-applied)
(fn [event]
(let [token (get-token-op tokens token-applied)]
(when-not disabled
(dom/prevent-default event)
(dom/stop-propagation event)
(reset! token-applied* nil)
(reset! selected-id* nil)
(reset! focused-id* nil)
(when on-detach
(on-detach token))
(ts/schedule-on-idle
(fn []
(dom/focus! (mf/ref-val ref))))))))
(when-not disabled
(dom/prevent-default event)
(dom/stop-propagation event)
(reset! token-applied* nil)
(reset! selected-id* nil)
(reset! focused-id* nil)
(when on-detach
(on-detach token-applied))
(ts/schedule-on-idle
(fn []
(dom/focus! (mf/ref-val ref)))))))
on-token-key-down
(mf/use-fn

View File

@@ -9,10 +9,15 @@
(:require
[app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob]
[app.main.constants :refer [right-sidebar-default-width right-sidebar-default-max-width left-sidebar-default-max-width left-sidebar-default-width]]
[app.config :as cf]
[app.main.constants :refer [left-sidebar-default-max-width
left-sidebar-default-width
right-sidebar-default-max-width
right-sidebar-default-width]]
[app.main.data.common :as dcm]
[app.main.data.event :as ev]
[app.main.data.style-dictionary :as sd]
[app.main.data.tokenscript :as ts]
[app.main.data.workspace :as dw]
[app.main.features :as features]
[app.main.refs :as refs]
@@ -369,6 +374,12 @@
(ctob/get-tokens-in-active-sets tokens-lib)
{}))
tokenscript? (contains? cf/flags :tokenscript)
tokenscript-resolved-active-tokens
(mf/with-memo [tokens-lib tokenscript?]
(when tokenscript? (ts/resolve-tokens active-tokens)))
resolved-active-tokens
(sd/use-resolved-tokens* active-tokens)]
@@ -380,7 +391,9 @@
:page-id page-id
:tokens-lib tokens-lib
:active-tokens active-tokens
:resolved-active-tokens resolved-active-tokens}])
:resolved-active-tokens (if (contains? cf/flags :tokenscript)
tokenscript-resolved-active-tokens
resolved-active-tokens)}])
[:> right-sidebar* {:section section
:selected selected
:drawing-tool drawing-tool

View File

@@ -60,8 +60,8 @@
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
(fn [token-name attr]
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes #{attr}
:shape-ids ids}))))

View File

@@ -152,9 +152,9 @@
on-detach-token
(mf/use-fn
(mf/deps token-colors groups)
(fn [token]
(fn [token-name]
(let [prev-colors (mf/ref-val prev-colors-ref)
token-color (some #(when (= (:token-name %) (:name token)) %) token-colors)
token-color (some #(when (= (:token-name %) token-name) %) token-colors)
[color-operations _] (retrieve-color-operations groups token-color prev-colors)]
(doseq [op color-operations]
@@ -166,8 +166,8 @@
(d/without-nils))]
(mf/set-ref-val! prev-colors-ref
(conj prev-colors color))
(st/emit! (dwta/unapply-token {:attributes attr
:token token
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes attr
:shape-ids [(:shape-id op)]})))))))
select-only

View File

@@ -74,7 +74,6 @@
render-wasm? (feat/use-feature "render-wasm/v1")
^boolean
multiple? (= :multiple fills)
@@ -183,9 +182,9 @@
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token]
(st/emit! (dwta/unapply-token {:attributes #{:fill}
:token token
(fn [token-name]
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes #{:fill}
:shape-ids ids}))))]
(mf/with-layout-effect [hide-on-export]
@@ -215,7 +214,8 @@
(when open?
[:div {:class (stl/css :fill-content)}
(cond
(= :multiple fills)
(or (= :multiple fills)
(= :multiple fill-token-applied))
[:div {:class (stl/css :fill-multiple)}
[:div {:class (stl/css :fill-multiple-label)}
(tr "settings.multiple")]

View File

@@ -72,8 +72,8 @@
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
(fn [token-name attr]
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes #{attr}
:shape-ids ids}))))
@@ -229,13 +229,13 @@
:property (tr "workspace.options.opacity")
:applied-token (get applied-tokens :opacity)
:placeholder (if (or (= :multiple (get applied-tokens :opacity))
(= :multiple (or (get values name) 1)))
(= :multiple (or (get values :opacity) 1)))
(tr "settings.multiple")
"--")
:align :right
:class (stl/css :numeric-input-wrapper)
:value (* 100
(or (get values name) 1))}]
(or (get values :opacity) 1))}]
[:div {:class (stl/css :input)
:title (tr "workspace.options.opacity")}
@@ -248,7 +248,6 @@
:max 100
:className (stl/css :numeric-input)}]])
[:div {:class (stl/css :actions)}
(cond
(or (= :multiple hidden?) (not hidden?))

View File

@@ -339,8 +339,8 @@
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
(fn [token-name attr]
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes #{attr}
:shape-ids ids}))))
@@ -475,7 +475,7 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
(st/emit! (dwta/unapply-token {:token-name token
:attributes #{attr}
:shape-ids ids}))))
@@ -722,7 +722,7 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
(st/emit! (dwta/unapply-token {:token-name token
:attributes #{attr}
:shape-ids ids}))))

View File

@@ -97,8 +97,8 @@
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
(fn [token-name attr]
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes #{attr}
:shape-ids ids}))))
@@ -220,8 +220,8 @@
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
(fn [token-name attr]
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes #{attr}
:shape-ids ids}))))
@@ -550,8 +550,8 @@
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
(fn [token-name attr]
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes #{attr}
:shape-ids ids}))))

View File

@@ -319,8 +319,8 @@
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
(fn [token-name attr]
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes #{attr}
:shape-ids ids}))))

View File

@@ -171,9 +171,9 @@
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token attrs]
(st/emit! (dwta/unapply-token {:attributes attrs
:token token
(fn [token-name attrs]
(st/emit! (dwta/unapply-token {:token-name token-name
:attributes attrs
:shape-ids ids}))))]
[:section {:class (stl/css :stroke-section)

View File

@@ -85,14 +85,14 @@
(mf/use-fn
(mf/deps detach-token token applied-token-name)
(fn []
(let [token (or token applied-token-name)]
(detach-token token))))
(let [token-name (or (:name token) applied-token-name)]
(detach-token token-name))))
has-errors (some? (:errors token))
token-name (:name token)
resolved (:resolved-value token)
not-active (and (empty? active-tokens)
(nil? token))
not-active (or (empty? active-tokens)
(nil? token))
id (dm/str (:id token) "-name")
swatch-tooltip-content (cond
not-active
@@ -344,7 +344,6 @@
(mf/with-effect [color prev-color disable-picker]
(when (and (not disable-picker) (not= prev-color color))
(modal/update-props! :colorpicker {:data (parse-color color)})))
[:div {:class [class row-class]}
;; Drag handler
(when (some? on-reorder)

View File

@@ -64,14 +64,17 @@
(let [selected? (selected-pred attribute)
props {:attributes #{attribute}
:token token
:shape-ids shape-ids}]
:shape-ids shape-ids}
unnaply-props {:token-name (:name token)
:attributes #{attribute}
:shape-ids shape-ids}]
{:title title
:hint hint
:selected? selected?
:action (fn []
(if selected?
(st/emit! (dwta/unapply-token props))
(st/emit! (dwta/unapply-token unnaply-props))
(st/emit! (dwta/apply-token (assoc props :on-update-shape on-update-shape-fn)))))}))
allowed-attributes)))
@@ -82,12 +85,15 @@
{:keys [all-selected? selected-pred shape-ids]} (attribute-actions token selected-shapes attributes)
all-action (let [props {:attributes attributes
:token token
:shape-ids shape-ids}]
:shape-ids shape-ids}
unnaply-props {:token-name (:name token)
:attributes attributes
:shape-ids shape-ids}]
{:title (tr "labels.all")
:selected? all-selected?
:hint hint
:action #(if all-selected?
(st/emit! (dwta/unapply-token props))
(st/emit! (dwta/unapply-token unnaply-props))
(st/emit! (dwta/apply-token (assoc props :on-update-shape (or on-update-shape-all on-update-shape)))))})
single-actions (map (fn [[attr title]]
(let [selected? (selected-pred attr)]
@@ -96,10 +102,13 @@
:action #(let [props {:attributes #{attr}
:token token
:shape-ids shape-ids}
unnaply-props {:token-name (:name token)
:attributes #{attr}
:shape-ids shape-ids}
event (cond
all-selected? (-> (assoc props :attributes-to-remove attributes)
(dwta/apply-token))
selected? (dwta/unapply-token props)
selected? (dwta/unapply-token unnaply-props)
:else (-> (assoc props :on-update-shape on-update-shape)
(dwta/apply-token)))]
(st/emit! event))}))
@@ -123,9 +132,12 @@
:action (fn []
(let [props {:attributes attrs
:token token
:shape-ids shape-ids}]
:shape-ids shape-ids}
unnaply-props {:token-name (:name token)
:attributes attrs
:shape-ids shape-ids}]
(if all-selected?
(st/emit! (dwta/unapply-token props))
(st/emit! (dwta/unapply-token unnaply-props))
(st/emit! (dwta/apply-token (assoc props :on-update-shape on-update-shape))))))}
{:title "Horizontal"
:selected? horizontal-selected?
@@ -165,10 +177,13 @@
:action #(let [props {:attributes #{attr}
:token token
:shape-ids shape-ids}
unnaply-props {:token-name (:name token)
:attributes #{attr}
:shape-ids shape-ids}
event (cond
all-selected? (-> (assoc props :attributes-to-remove attrs)
(dwta/apply-token))
selected? (dwta/unapply-token props)
selected? (dwta/unapply-token unnaply-props)
:else (-> (assoc props :on-update-shape on-update-shape)
(dwta/apply-token)))]
(st/emit! event))}))

View File

@@ -10,7 +10,10 @@
[app.common.files.tokens :as cft]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.config :as cf]
[app.main.data.style-dictionary :as sd]
[app.main.data.tokenscript :as ts]
[app.main.data.workspace.tokens.errors :as wte]
[app.main.data.workspace.tokens.format :as dwtf]
[app.main.ui.ds.controls.input :as ds]
[app.main.ui.forms :as fc]
@@ -139,6 +142,18 @@
;; -----------------------------------------------------------------------------
(defn- resolve-value-tokenscript
[tokens prev-token value]
(let [result (ts/update-token tokens (assoc prev-token :value value))
token-result (.-resolved result)]
(rx/of
(cond
(ts/processor-error? token-result) {:error (wte/error-with-value :error.style-dictionary/missing-reference (some->> (.-dependencyChain token-result)
(seq)
(rest)))}
(instance? js/Error token-result) {:error (wte/error-with-value :error.style-dictionary/invalid-token-value value)}
:else {:value token-result}))))
(defn- resolve-value
[tokens prev-token token-name value]
(let [valid-token-name?
@@ -216,7 +231,10 @@
(mf/with-effect [resolve-stream tokens token input-name token-name]
(let [subs (->> resolve-stream
(let [resolve-value (if (contains? cf/flags :tokenscript)
resolve-value-tokenscript
resolve-value)
subs (->> resolve-stream
(rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token token-name))
(rx/map (fn [result]

View File

@@ -25,7 +25,6 @@
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.main.ui.forms :as fc]
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
[app.main.ui.workspace.tokens.management.forms.validators :refer [default-validate-token]]
@@ -143,10 +142,6 @@
(fm/use-form :schema schema
:initial initial)
warning-name-change?
(not= (get-in @form [:data :name])
(:name initial))
on-toggle-tab
(mf/use-fn
(mf/deps form)
@@ -276,12 +271,7 @@
:max-length max-input-length
:variant "comfortable"
:trim true
:auto-focus true}]
(when (and warning-name-change? (= action "edit"))
[:div {:class (stl/css :warning-name-change-notification-wrapper)}
[:> context-notification*
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
:auto-focus true}]]
[:div {:class (stl/css :input-row)}
(case type

View File

@@ -13,6 +13,7 @@
[app.common.files.tokens :as cft]
[app.common.path-names :as cpn]
[app.common.types.token :as ctt]
[app.config :as cf]
[app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.color :as dwtc]
[app.main.data.workspace.tokens.format :as dwtf]
@@ -177,6 +178,8 @@
[{:keys [on-click token on-context-menu selected-shapes is-selected-inside-layout active-theme-tokens]}]
(let [{:keys [name value errors type]} token
resolved-token (get active-theme-tokens (:name token))
has-selected? (pos? (count selected-shapes))
is-reference? (cft/is-reference? token)
contains-path? (str/includes? name ".")
@@ -209,8 +212,10 @@
is-viewer? (not can-edit?)
ref-not-in-active-set
(and is-reference?
(not (contains-reference-value? value active-theme-tokens)))
(if (contains? cf/flags :tokenscript)
(seq (:errors resolved-token))
(and is-reference?
(not (contains-reference-value? value active-theme-tokens))))
no-valid-value (seq errors)
@@ -220,9 +225,8 @@
color
(when (cft/color-token? token)
(let [theme-token (get active-theme-tokens name)]
(or (dwtc/resolved-token-bullet-color theme-token)
(dwtc/resolved-token-bullet-color token))))
(or (dwtc/resolved-token-bullet-color resolved-token)
(dwtc/resolved-token-bullet-color token)))
status-icon? (contains? token-types-with-status-icon type)

View File

@@ -177,7 +177,7 @@
;; ==== Action
events [(dwta/unapply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-1")})]
:token-name "test-token-1"})]
step2 (fn [_]
(let [events2 [(dwl/sync-file (:id file) (:id file))]]
@@ -289,7 +289,7 @@
;; ==== Action
events [(dwta/unapply-token {:shape-ids [(cthi/id :c-frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-1")})
:token-name "test-token-1"})
(dwta/apply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-3")