mirror of
https://github.com/penpot/penpot.git
synced 2026-02-05 12:12:07 -05:00
Compare commits
4 Commits
niwinz-lay
...
hiru-plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ddfb5cce5 | ||
|
|
6e48e73b40 | ||
|
|
17935443df | ||
|
|
150d57b1eb |
@@ -58,3 +58,4 @@
|
||||
(when (nil? (:data file))
|
||||
(migrate-file conn file)))
|
||||
(db/exec-one! conn ["drop table page cascade;"])))
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
:token-base-font-size
|
||||
:token-color
|
||||
:token-shadow
|
||||
:token-tokenscript
|
||||
:transit-readable-response
|
||||
:user-feedback
|
||||
;; TODO: remove this flag.
|
||||
|
||||
@@ -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",
|
||||
|
||||
3
frontend/packages/tokenscript/.gitignore
vendored
Normal file
3
frontend/packages/tokenscript/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
*.sublime-workspace
|
||||
/.yarn
|
||||
2
frontend/packages/tokenscript/index.js
Normal file
2
frontend/packages/tokenscript/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./schemas.js";
|
||||
export * from "@tokens-studio/tokenscript-interpreter";
|
||||
13
frontend/packages/tokenscript/package.json
Normal file
13
frontend/packages/tokenscript/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
1431
frontend/packages/tokenscript/schemas.js
Normal file
1431
frontend/packages/tokenscript/schemas.js
Normal file
File diff suppressed because one or more lines are too long
@@ -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();
|
||||
|
||||
123
frontend/pnpm-lock.yaml
generated
123
frontend/pnpm-lock.yaml
generated
@@ -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: {}
|
||||
|
||||
@@ -6,4 +6,5 @@ shamefullyHoist: true
|
||||
packages:
|
||||
- "packages/draft-js"
|
||||
- "packages/mousetrap"
|
||||
- "packages/tokenscript"
|
||||
- "text-editor"
|
||||
|
||||
@@ -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*)))
|
||||
|
||||
164
frontend/src/app/main/data/tokenscript.cljs
Normal file
164
frontend/src/app/main/data/tokenscript.cljs
Normal 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)))
|
||||
@@ -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
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -183,6 +183,9 @@
|
||||
[id]
|
||||
(l/derived #(contains? % id) selected-shapes))
|
||||
|
||||
(def highlighted-shapes
|
||||
(l/derived :highlighted workspace-local))
|
||||
|
||||
(def export-in-progress?
|
||||
(l/derived :export-in-progress? export))
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
[app.common.types.component :as ctk]
|
||||
[app.main.data.viewer :as dv]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.workspace.sidebar.layer-item :refer [layer-item-inner*]]
|
||||
[app.main.ui.workspace.sidebar.layer-item :refer [layer-item-inner]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.keyboard :as kbd]
|
||||
[okulary.core :as l]
|
||||
@@ -26,6 +26,7 @@
|
||||
(mf/defc layer-item
|
||||
[{:keys [item selected objects depth component-child? hide-toggle?] :as props}]
|
||||
(let [id (:id item)
|
||||
hidden? (:hidden item)
|
||||
selected? (contains? selected id)
|
||||
item-ref (mf/use-ref nil)
|
||||
depth (+ depth 1)
|
||||
@@ -67,17 +68,18 @@
|
||||
(when (and (= (count selected) 1) selected?)
|
||||
(dom/scroll-into-view-if-needed! (mf/ref-val item-ref) true))))
|
||||
|
||||
[:> layer-item-inner*
|
||||
[:& layer-item-inner
|
||||
{:ref item-ref
|
||||
:item item
|
||||
:depth depth
|
||||
:is-read-only true
|
||||
:is-highlighted false
|
||||
:is-selected selected?
|
||||
:is-component-tree component-tree?
|
||||
:is-filtered false
|
||||
:is-expanded expanded?
|
||||
:hide-toggle hide-toggle?
|
||||
:read-only? true
|
||||
:highlighted? false
|
||||
:selected? selected?
|
||||
:component-tree? component-tree?
|
||||
:hidden? hidden?
|
||||
:filtered? false
|
||||
:expanded? expanded?
|
||||
:hide-toggle? hide-toggle?
|
||||
:on-select-shape select-shape
|
||||
:on-toggle-collapse toggle-collapse}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,13 +10,11 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.weak :as weak]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.collapse :as dwc]
|
||||
[app.main.refs :as refs]
|
||||
@@ -39,8 +37,6 @@
|
||||
(defonce ^:private sidebar-hover-queue (atom {:enter #{} :leave #{}}))
|
||||
(defonce ^:private sidebar-hover-pending? (atom false))
|
||||
|
||||
(def ^:const default-chunk-size 50)
|
||||
|
||||
(defn- schedule-sidebar-hover-flush []
|
||||
(when (compare-and-set! sidebar-hover-pending? false true)
|
||||
(ts/raf
|
||||
@@ -52,11 +48,12 @@
|
||||
(when (seq enter)
|
||||
(apply st/emit! (map dw/highlight-shape enter))))))))
|
||||
|
||||
(mf/defc layer-item-inner*
|
||||
[{:keys [item depth parent-size name-ref children ref style rename-id
|
||||
(mf/defc layer-item-inner
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [item depth parent-size name-ref children ref style
|
||||
;; Flags
|
||||
is-read-only is-highlighted is-selected is-component-tree
|
||||
is-filtered is-expanded dnd-over dnd-over-top dnd-over-bot hide-toggle
|
||||
read-only? highlighted? selected? component-tree?
|
||||
filtered? expanded? dnd-over? dnd-over-top? dnd-over-bot? hide-toggle?
|
||||
;; Callbacks
|
||||
on-select-shape on-context-menu on-pointer-enter on-pointer-leave on-zoom-to-selected
|
||||
on-toggle-collapse on-enable-drag on-disable-drag on-toggle-visibility on-toggle-blocking]}]
|
||||
@@ -67,7 +64,7 @@
|
||||
hidden? (:hidden item)
|
||||
has-shapes? (-> item :shapes seq boolean)
|
||||
touched? (-> item :touched seq boolean)
|
||||
root-board? (and (cfh/frame-shape? item)
|
||||
parent-board? (and (cfh/frame-shape? item)
|
||||
(= uuid/zero (:parent-id item)))
|
||||
absolute? (ctl/item-absolute? item)
|
||||
is-variant? (ctk/is-variant? item)
|
||||
@@ -76,12 +73,9 @@
|
||||
variant-name (when is-variant? (:variant-name item))
|
||||
variant-error (when is-variant? (:variant-error item))
|
||||
|
||||
component-id (get item :component-id)
|
||||
variant-properties (mf/with-memo [component-id]
|
||||
;; We use non-reactive deref explicitly here
|
||||
(let [data (deref refs/workspace-data)]
|
||||
(-> (ctkl/get-component data component-id)
|
||||
(get :variant-properties))))
|
||||
data (deref refs/workspace-data)
|
||||
component (ctkl/get-component data (:component-id item))
|
||||
variant-properties (:variant-properties component)
|
||||
icon-shape (usi/get-shape-icon item)]
|
||||
|
||||
[:*
|
||||
@@ -92,27 +86,27 @@
|
||||
:data-testid "layer-row"
|
||||
:class (stl/css-case
|
||||
:layer-row true
|
||||
:highlight is-highlighted
|
||||
:highlight highlighted?
|
||||
:component (ctk/instance-head? item)
|
||||
:masked (:masked-group item)
|
||||
:selected is-selected
|
||||
:selected selected?
|
||||
:type-frame (cfh/frame-shape? item)
|
||||
:type-bool (cfh/bool-shape? item)
|
||||
:type-comp (or is-component-tree is-variant-container?)
|
||||
:type-comp (or component-tree? is-variant-container?)
|
||||
:hidden hidden?
|
||||
:dnd-over dnd-over
|
||||
:dnd-over-top dnd-over-top
|
||||
:dnd-over-bot dnd-over-bot
|
||||
:root-board root-board?)
|
||||
:dnd-over dnd-over?
|
||||
:dnd-over-top dnd-over-top?
|
||||
:dnd-over-bot dnd-over-bot?
|
||||
:root-board parent-board?)
|
||||
:style style}
|
||||
[:span {:class (stl/css-case
|
||||
:tab-indentation true
|
||||
:filtered is-filtered)
|
||||
:filtered filtered?)
|
||||
:style {"--depth" depth}}]
|
||||
[:div {:class (stl/css-case
|
||||
:element-list-body true
|
||||
:filtered is-filtered
|
||||
:selected is-selected
|
||||
:filtered filtered?
|
||||
:selected selected?
|
||||
:icon-layer (= (:type item) :icon))
|
||||
:style {"--depth" depth}
|
||||
:on-pointer-enter on-pointer-enter
|
||||
@@ -121,12 +115,12 @@
|
||||
|
||||
(if (< 0 (count (:shapes item)))
|
||||
[:div {:class (stl/css :button-content)}
|
||||
(when (and (not hide-toggle) (not is-filtered))
|
||||
(when (and (not hide-toggle?) (not filtered?))
|
||||
[:button {:class (stl/css-case
|
||||
:toggle-content true
|
||||
:inverse is-expanded)
|
||||
:inverse expanded?)
|
||||
:data-testid "toggle-content"
|
||||
:aria-expanded is-expanded
|
||||
:aria-expanded expanded?
|
||||
:on-click on-toggle-collapse}
|
||||
deprecated-icon/arrow])
|
||||
|
||||
@@ -137,7 +131,7 @@
|
||||
[:> icon* {:icon-id icon-shape :size "s" :data-testid (str "icon-" icon-shape)}]]]
|
||||
|
||||
[:div {:class (stl/css :button-content)}
|
||||
(when (not ^boolean is-filtered)
|
||||
(when (not ^boolean filtered?)
|
||||
[:span {:class (stl/css :toggle-content)}])
|
||||
[:div {:class (stl/css :icon-shape)
|
||||
:on-double-click on-zoom-to-selected}
|
||||
@@ -146,26 +140,25 @@
|
||||
[:> icon* {:icon-id icon-shape :size "s" :data-testid (str "icon-" icon-shape)}]]])
|
||||
|
||||
[:> layer-name* {:ref name-ref
|
||||
:rename-id rename-id
|
||||
:shape-id id
|
||||
:shape-name name
|
||||
:is-shape-touched touched?
|
||||
:disabled-double-click is-read-only
|
||||
:disabled-double-click read-only?
|
||||
:on-start-edit on-disable-drag
|
||||
:on-stop-edit on-enable-drag
|
||||
:depth depth
|
||||
:is-blocked blocked?
|
||||
:parent-size parent-size
|
||||
:is-selected is-selected
|
||||
:type-comp (or is-component-tree is-variant-container?)
|
||||
:is-selected selected?
|
||||
:type-comp (or component-tree? is-variant-container?)
|
||||
:type-frame (cfh/frame-shape? item)
|
||||
:variant-id variant-id
|
||||
:variant-name variant-name
|
||||
:variant-properties variant-properties
|
||||
:variant-error variant-error
|
||||
:component-id component-id
|
||||
:component-id (:id component)
|
||||
:is-hidden hidden?}]]
|
||||
(when (not ^boolean is-read-only)
|
||||
(when (not read-only?)
|
||||
[:div {:class (stl/css-case
|
||||
:element-actions true
|
||||
:is-parent has-shapes?
|
||||
@@ -190,87 +183,41 @@
|
||||
|
||||
children]))
|
||||
|
||||
(mf/defc layer-item*
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [index item selected objects objects-ref rename-id
|
||||
is-sortable is-filtered depth is-component-child
|
||||
highlighted style render-children parent-size]
|
||||
:or {render-children true}}]
|
||||
(let [id (get item :id)
|
||||
blocked? (get item :blocked)
|
||||
hidden? (get item :hidden)
|
||||
|
||||
shapes (get item :shapes)
|
||||
shapes (mf/with-memo [shapes]
|
||||
(loop [counter 0
|
||||
shapes (seq shapes)
|
||||
result (list)]
|
||||
|
||||
(if-let [id (first shapes)]
|
||||
(if-let [obj (-> (mf/ref-val objects-ref)
|
||||
(get id))]
|
||||
(do
|
||||
;; NOTE: this is a bit hacky, but reduces substantially
|
||||
;; the allocation; If we use enumeration, we allocate
|
||||
;; new sequence and add one iteration on each render,
|
||||
;; independently if objects are changed or not. If we
|
||||
;; store counter on metadata, we still need to create a
|
||||
;; new allocation for each shape; with this method we
|
||||
;; bypass this by mutating a private property on the
|
||||
;; object removing extra allocation and extra iteration
|
||||
;; on every request.
|
||||
(unchecked-set obj "__$__counter" counter)
|
||||
(recur (inc counter)
|
||||
(rest shapes)
|
||||
(conj result obj)))
|
||||
(recur (inc counter)
|
||||
(rest shapes)
|
||||
result))
|
||||
|
||||
(-> result vec not-empty))))
|
||||
;; Memoized for performance
|
||||
(mf/defc layer-item
|
||||
{::mf/props :obj
|
||||
::mf/wrap [mf/memo]}
|
||||
[{:keys [index item selected objects sortable? filtered? depth parent-size component-child? highlighted style render-children?]
|
||||
:or {render-children? true}}]
|
||||
(let [id (:id item)
|
||||
blocked? (:blocked item)
|
||||
hidden? (:hidden item)
|
||||
|
||||
drag-disabled* (mf/use-state false)
|
||||
drag-disabled? (deref drag-disabled*)
|
||||
|
||||
scroll-middle-ref (mf/use-ref true)
|
||||
scroll-to-middle? (mf/use-var true)
|
||||
expanded-iref (mf/with-memo [id]
|
||||
(l/derived #(dm/get-in % [:expanded :id]) refs/workspace-local))
|
||||
is-expanded (mf/deref expanded-iref)
|
||||
(-> (l/in [:expanded id])
|
||||
(l/derived refs/workspace-local)))
|
||||
expanded? (mf/deref expanded-iref)
|
||||
|
||||
is-selected (contains? selected id)
|
||||
is-highlighted (contains? highlighted id)
|
||||
selected? (contains? selected id)
|
||||
highlighted? (contains? highlighted id)
|
||||
|
||||
container? (or (cfh/frame-shape? item)
|
||||
(cfh/group-shape? item))
|
||||
|
||||
is-read-only (mf/use-ctx ctx/workspace-read-only?)
|
||||
root-board? (and (cfh/frame-shape? item)
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
parent-board? (and (cfh/frame-shape? item)
|
||||
(= uuid/zero (:parent-id item)))
|
||||
|
||||
name-node-ref (mf/use-ref)
|
||||
|
||||
depth (+ depth 1)
|
||||
|
||||
is-component-tree (or ^boolean is-component-child
|
||||
^boolean (ctk/instance-root? item)
|
||||
^boolean (ctk/instance-head? item))
|
||||
|
||||
enable-drag (mf/use-fn #(reset! drag-disabled* false))
|
||||
disable-drag (mf/use-fn #(reset! drag-disabled* true))
|
||||
|
||||
;; Lazy loading of child elements via IntersectionObserver
|
||||
children-count* (mf/use-state 0)
|
||||
children-count (deref children-count*)
|
||||
|
||||
lazy-ref (mf/use-ref nil)
|
||||
observer-ref (mf/use-ref nil)
|
||||
|
||||
toggle-collapse
|
||||
(mf/use-fn
|
||||
(mf/deps is-expanded)
|
||||
(mf/deps expanded?)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(if (and is-expanded (kbd/shift? event))
|
||||
(if (and expanded? (kbd/shift? event))
|
||||
(st/emit! (dwc/collapse-all))
|
||||
(st/emit! (dwc/toggle-collapse id)))))
|
||||
|
||||
@@ -295,16 +242,15 @@
|
||||
|
||||
select-shape
|
||||
(mf/use-fn
|
||||
(mf/deps id is-filtered)
|
||||
(mf/deps id filtered? objects)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(mf/set-ref-val! scroll-middle-ref false)
|
||||
(reset! scroll-to-middle? false)
|
||||
(cond
|
||||
(kbd/shift? event)
|
||||
(let [objects (mf/ref-val objects-ref)]
|
||||
(if is-filtered
|
||||
(st/emit! (dw/shift-select-shapes id objects))
|
||||
(st/emit! (dw/shift-select-shapes id))))
|
||||
(if filtered?
|
||||
(st/emit! (dw/shift-select-shapes id objects))
|
||||
(st/emit! (dw/shift-select-shapes id)))
|
||||
|
||||
(kbd/mod? event)
|
||||
(st/emit! (dw/select-shape id true))
|
||||
@@ -337,11 +283,11 @@
|
||||
|
||||
on-context-menu
|
||||
(mf/use-fn
|
||||
(mf/deps item is-read-only)
|
||||
(mf/deps item read-only?)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(when-not is-read-only
|
||||
(when-not read-only?
|
||||
(let [pos (dom/get-client-position event)]
|
||||
(st/emit! (dw/show-shape-context-menu {:position pos :shape item}))))))
|
||||
|
||||
@@ -354,10 +300,9 @@
|
||||
|
||||
on-drop
|
||||
(mf/use-fn
|
||||
(mf/deps id index is-expanded selected)
|
||||
(mf/deps id index objects expanded? selected)
|
||||
(fn [side _data]
|
||||
(let [objects (mf/ref-val objects-ref)
|
||||
single? (= (count selected) 1)
|
||||
(let [single? (= (count selected) 1)
|
||||
same? (and single? (= (first selected) id))]
|
||||
(when-not same?
|
||||
(let [files (deref refs/files)
|
||||
@@ -368,32 +313,28 @@
|
||||
(= side :center)
|
||||
id
|
||||
|
||||
(and is-expanded (= side :bot) (d/not-empty? (:shapes shape)))
|
||||
(and expanded? (= side :bot) (d/not-empty? (:shapes shape)))
|
||||
id
|
||||
|
||||
:else
|
||||
(cfh/get-parent-id objects id))
|
||||
|
||||
[parent-id _]
|
||||
(ctn/find-valid-parent-and-frame-ids parent-id objects (map #(get objects %) selected) false files)
|
||||
[parent-id _] (ctn/find-valid-parent-and-frame-ids parent-id objects (map #(get objects %) selected) false files)
|
||||
|
||||
parent
|
||||
(get objects parent-id)
|
||||
|
||||
to-index
|
||||
(cond
|
||||
(= side :center) 0
|
||||
(and is-expanded (= side :bot) (d/not-empty? (:shapes shape))) (count (:shapes parent))
|
||||
(= side :top) (inc index)
|
||||
:else index)]
|
||||
parent (get objects parent-id)
|
||||
|
||||
to-index (cond
|
||||
(= side :center) 0
|
||||
(and expanded? (= side :bot) (d/not-empty? (:shapes shape))) (count (:shapes parent))
|
||||
(= side :top) (inc index)
|
||||
:else index)]
|
||||
(st/emit! (dw/relocate-selected-shapes parent-id to-index)))))))
|
||||
|
||||
on-hold
|
||||
(mf/use-fn
|
||||
(mf/deps id is-expanded)
|
||||
(mf/deps id expanded?)
|
||||
(fn []
|
||||
(when-not is-expanded
|
||||
(when-not expanded?
|
||||
(st/emit! (dwc/toggle-collapse id)))))
|
||||
|
||||
zoom-to-selected
|
||||
@@ -414,114 +355,116 @@
|
||||
:data {:id (:id item)
|
||||
:index index
|
||||
:name (:name item)}
|
||||
;; We don't want to change the structure of component copies
|
||||
:draggable? (and ^boolean is-sortable
|
||||
^boolean (not is-read-only)
|
||||
^boolean (not (ctn/has-any-copy-parent? objects item))))]
|
||||
:draggable? (and
|
||||
sortable?
|
||||
(not read-only?)
|
||||
(not (ctn/has-any-copy-parent? objects item)))) ;; We don't want to change the structure of component copies
|
||||
|
||||
(mf/with-effect [is-selected selected]
|
||||
ref (mf/use-ref)
|
||||
depth (+ depth 1)
|
||||
component-tree? (or component-child? (ctk/instance-root? item) (ctk/instance-head? item))
|
||||
|
||||
enable-drag (mf/use-fn #(reset! drag-disabled* false))
|
||||
disable-drag (mf/use-fn #(reset! drag-disabled* true))
|
||||
|
||||
;; Lazy loading of child elements via IntersectionObserver
|
||||
children-count* (mf/use-state 0)
|
||||
children-count (deref children-count*)
|
||||
lazy-ref (mf/use-ref nil)
|
||||
observer-var (mf/use-var nil)
|
||||
chunk-size 50]
|
||||
|
||||
(mf/with-effect [selected? selected]
|
||||
(let [single? (= (count selected) 1)
|
||||
node (mf/ref-val name-node-ref)
|
||||
scroll-node (dom/get-parent-with-data node "scroll-container")
|
||||
parent-node (dom/get-parent-at node 2)
|
||||
first-child-node (dom/get-first-child parent-node)
|
||||
scroll-to-middle? (mf/ref-val scroll-middle-ref)
|
||||
node (mf/ref-val ref)
|
||||
scroll-node (dom/get-parent-with-data node "scroll-container")
|
||||
parent-node (dom/get-parent-at node 2)
|
||||
first-child-node (dom/get-first-child parent-node)
|
||||
|
||||
subid
|
||||
(when (and ^boolean single?
|
||||
^boolean is-selected
|
||||
^boolean scroll-to-middle?)
|
||||
(when (and single? selected? @scroll-to-middle?)
|
||||
(ts/schedule
|
||||
100
|
||||
#(when (and node scroll-node)
|
||||
(let [scroll-distance-ratio (dom/get-scroll-distance-ratio node scroll-node)
|
||||
scroll-behavior (if (> scroll-distance-ratio 1) "instant" "smooth")]
|
||||
(dom/scroll-into-view-if-needed! first-child-node #js {:block "center" :behavior scroll-behavior :inline "start"})
|
||||
(mf/set-ref-val! scroll-middle-ref true)))))]
|
||||
(reset! scroll-to-middle? true)))))]
|
||||
|
||||
#(when (some? subid)
|
||||
(rx/dispose! subid))))
|
||||
|
||||
;; Setup scroll-driven lazy loading when expanded
|
||||
;; and ensures selected children are loaded immediately
|
||||
(mf/with-effect [is-expanded shapes selected]
|
||||
(let [total (count shapes)]
|
||||
(if ^boolean is-expanded
|
||||
(mf/with-effect [expanded? (:shapes item) selected]
|
||||
(let [shapes-vec (:shapes item)
|
||||
total (count shapes-vec)]
|
||||
(if expanded?
|
||||
(let [;; Children are rendered in reverse order, so index 0 in render = last in shapes-vec
|
||||
;; Find if any selected id is a direct child and get its render index
|
||||
selected-child-render-idx
|
||||
(when (> total default-chunk-size)
|
||||
(some (fn [sel-id]
|
||||
(let [idx (.indexOf shapes sel-id)]
|
||||
(when (>= idx 0) idx)))
|
||||
selected))
|
||||
|
||||
(when (and (> total chunk-size) (seq selected))
|
||||
(let [shapes-reversed (vec (reverse shapes-vec))]
|
||||
(some (fn [sel-id]
|
||||
(let [idx (.indexOf shapes-reversed sel-id)]
|
||||
(when (>= idx 0) idx)))
|
||||
selected)))
|
||||
;; Load at least enough to include the selected child plus extra
|
||||
;; for context (so it can be centered in the scroll view)
|
||||
min-count
|
||||
(if selected-child-render-idx
|
||||
(+ selected-child-render-idx default-chunk-size)
|
||||
default-chunk-size)
|
||||
|
||||
current-count
|
||||
@children-count*
|
||||
|
||||
new-count
|
||||
(mth/min total (mth/max current-count default-chunk-size min-count))]
|
||||
|
||||
min-count (if selected-child-render-idx
|
||||
(+ selected-child-render-idx chunk-size)
|
||||
chunk-size)
|
||||
current @children-count*
|
||||
new-count (min total (max current chunk-size min-count))]
|
||||
(reset! children-count* new-count))
|
||||
|
||||
(reset! children-count* 0))
|
||||
|
||||
(fn []
|
||||
(when-let [obs (mf/ref-val observer-ref)]
|
||||
(.disconnect obs)
|
||||
(mf/set-ref-val! obs nil)))))
|
||||
(reset! children-count* 0)))
|
||||
(fn []
|
||||
(when-let [obs ^js @observer-var]
|
||||
(.disconnect obs)
|
||||
(reset! observer-var nil))))
|
||||
|
||||
;; Re-observe sentinel whenever children-count changes (sentinel moves)
|
||||
;; and (shapes item) to reconnect observer after shape changes
|
||||
(mf/with-effect [children-count is-expanded shapes]
|
||||
(let [total (count shapes)
|
||||
name-node (mf/ref-val name-node-ref)
|
||||
scroll-node (dom/get-parent-with-data name-node "scroll-container")
|
||||
lazy-node (mf/ref-val lazy-ref)]
|
||||
|
||||
(mf/with-effect [children-count expanded? (:shapes item)]
|
||||
(let [total (count (:shapes item))
|
||||
node (mf/ref-val ref)
|
||||
scroll-node (dom/get-parent-with-data node "scroll-container")
|
||||
lazy-node (mf/ref-val lazy-ref)]
|
||||
;; Disconnect previous observer
|
||||
(when-let [obs (mf/ref-val observer-ref)]
|
||||
(when-let [obs ^js @observer-var]
|
||||
(.disconnect obs)
|
||||
(mf/set-ref-val! observer-ref nil))
|
||||
|
||||
(reset! observer-var nil))
|
||||
;; Setup new observer if there are more children to load
|
||||
(when (and ^boolean is-expanded
|
||||
^boolean (< children-count total)
|
||||
^boolean scroll-node
|
||||
^boolean lazy-node)
|
||||
(when (and expanded?
|
||||
(< children-count total)
|
||||
scroll-node
|
||||
lazy-node)
|
||||
(let [cb (fn [entries]
|
||||
(when (and (pos? (alength entries))
|
||||
(.-isIntersecting ^js (aget entries 0)))
|
||||
(when (and (seq entries)
|
||||
(.-isIntersecting (first entries)))
|
||||
;; Load next chunk when sentinel intersects
|
||||
(let [next-count (mth/min total (+ children-count default-chunk-size))]
|
||||
(let [current @children-count*
|
||||
next-count (min total (+ current chunk-size))]
|
||||
(reset! children-count* next-count))))
|
||||
observer (js/IntersectionObserver. cb #js {:root scroll-node})]
|
||||
(.observe observer lazy-node)
|
||||
(mf/set-ref-val! observer-ref observer)))))
|
||||
(reset! observer-var observer)))))
|
||||
|
||||
[:> layer-item-inner*
|
||||
[:& layer-item-inner
|
||||
{:ref dref
|
||||
:item item
|
||||
:depth depth
|
||||
:parent-size parent-size
|
||||
:name-ref name-node-ref
|
||||
:rename-id rename-id
|
||||
:is-read-only is-read-only
|
||||
:is-highlighted is-highlighted
|
||||
:is-selected is-selected
|
||||
:is-component-tree is-component-tree
|
||||
:is-filtered is-filtered
|
||||
:is-expanded is-expanded
|
||||
:dnd-over (= (:over dprops) :center)
|
||||
:dnd-over-top (= (:over dprops) :top)
|
||||
:dnd-over-bot (= (:over dprops) :bot)
|
||||
:name-ref ref
|
||||
:read-only? read-only?
|
||||
:highlighted? highlighted?
|
||||
:selected? selected?
|
||||
:component-tree? component-tree?
|
||||
:filtered? filtered?
|
||||
:expanded? expanded?
|
||||
:dnd-over? (= (:over dprops) :center)
|
||||
:dnd-over-top? (= (:over dprops) :top)
|
||||
:dnd-over-bot? (= (:over dprops) :bot)
|
||||
:on-select-shape select-shape
|
||||
:on-context-menu on-context-menu
|
||||
:on-pointer-enter on-pointer-enter
|
||||
@@ -534,29 +477,29 @@
|
||||
:on-toggle-blocking toggle-blocking
|
||||
:style style}
|
||||
|
||||
(when (and ^boolean render-children
|
||||
^boolean shapes
|
||||
^boolean is-expanded)
|
||||
(when (and render-children?
|
||||
(:shapes item)
|
||||
expanded?)
|
||||
[:div {:class (stl/css-case
|
||||
:element-children true
|
||||
:parent-selected is-selected
|
||||
:sticky-children root-board?)
|
||||
:parent-selected selected?
|
||||
:sticky-children parent-board?)
|
||||
:data-testid (dm/str "children-" id)}
|
||||
(for [item (take children-count shapes)]
|
||||
[:> layer-item*
|
||||
{:item item
|
||||
:rename-id rename-id
|
||||
:highlighted highlighted
|
||||
:selected selected
|
||||
:index (unchecked-get item "__$__counter")
|
||||
:objects objects
|
||||
:objects-ref objects-ref
|
||||
:key (weak/weak-key item)
|
||||
:is-sortable is-sortable
|
||||
:depth depth
|
||||
:parent-size parent-size
|
||||
:is-component-child is-component-tree}])
|
||||
|
||||
(when (< children-count (count shapes))
|
||||
(let [all-children (reverse (d/enumerate (:shapes item)))
|
||||
visible (take children-count all-children)]
|
||||
(for [[index id] visible]
|
||||
(when-let [item (get objects id)]
|
||||
[:& layer-item
|
||||
{:item item
|
||||
:highlighted highlighted
|
||||
:selected selected
|
||||
:index index
|
||||
:objects objects
|
||||
:key (dm/str id)
|
||||
:sortable? sortable?
|
||||
:depth depth
|
||||
:parent-size parent-size
|
||||
:component-child? component-tree?}])))
|
||||
(when (< children-count (count (:shapes item)))
|
||||
[:div {:ref lazy-ref
|
||||
:style {:min-height 1}}])])]))
|
||||
|
||||
@@ -16,35 +16,39 @@
|
||||
[app.util.dom :as dom]
|
||||
[app.util.keyboard :as kbd]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private ^:const space-for-icons 110)
|
||||
(def ^:private space-for-icons 110)
|
||||
|
||||
(def lens:shape-for-rename
|
||||
(-> (l/in [:workspace-local :shape-for-rename])
|
||||
(l/derived st/state)))
|
||||
|
||||
(mf/defc layer-name*
|
||||
[{:keys [shape-id rename-id shape-name is-shape-touched disabled-double-click
|
||||
{::mf/wrap-props false
|
||||
::mf/forward-ref true}
|
||||
[{:keys [shape-id shape-name is-shape-touched disabled-double-click
|
||||
on-start-edit on-stop-edit depth parent-size is-selected
|
||||
type-comp type-frame component-id is-hidden is-blocked
|
||||
variant-id variant-name variant-properties variant-error ref]}]
|
||||
|
||||
variant-id variant-name variant-properties variant-error]} external-ref]
|
||||
(let [edition* (mf/use-state false)
|
||||
edition? (deref edition*)
|
||||
|
||||
local-ref (mf/use-ref)
|
||||
ref (d/nilv ref local-ref)
|
||||
ref (d/nilv external-ref local-ref)
|
||||
|
||||
shape-name
|
||||
(if variant-id
|
||||
(d/nilv variant-error variant-name)
|
||||
shape-name)
|
||||
shape-for-rename (mf/deref lens:shape-for-rename)
|
||||
|
||||
default-value
|
||||
(mf/with-memo [variant-id variant-error variant-properties]
|
||||
(if variant-id
|
||||
(or variant-error (ctv/properties-map->formula variant-properties))
|
||||
shape-name))
|
||||
shape-name (if variant-id
|
||||
(d/nilv variant-error variant-name)
|
||||
shape-name)
|
||||
|
||||
has-path?
|
||||
(str/includes? shape-name "/")
|
||||
default-value (if variant-id
|
||||
(or variant-error (ctv/properties-map->formula variant-properties))
|
||||
shape-name)
|
||||
|
||||
has-path? (str/includes? shape-name "/")
|
||||
|
||||
start-edit
|
||||
(mf/use-fn
|
||||
@@ -81,11 +85,10 @@
|
||||
(when (kbd/enter? event) (accept-edit))
|
||||
(when (kbd/esc? event) (cancel-edit))))
|
||||
|
||||
parent-size
|
||||
(dm/str (- parent-size space-for-icons) "px")]
|
||||
parent-size (dm/str (- parent-size space-for-icons) "px")]
|
||||
|
||||
(mf/with-effect [rename-id edition? start-edit shape-id]
|
||||
(when (and (= rename-id shape-id)
|
||||
(mf/with-effect [shape-for-rename edition? start-edit shape-id]
|
||||
(when (and (= shape-for-rename shape-id)
|
||||
(not ^boolean edition?))
|
||||
(start-edit)))
|
||||
|
||||
@@ -107,24 +110,21 @@
|
||||
:auto-focus true
|
||||
:id (dm/str "layer-name-" shape-id)
|
||||
:default-value (d/nilv default-value "")}]
|
||||
|
||||
[:*
|
||||
[:span {:class (stl/css-case
|
||||
:element-name true
|
||||
:left-ellipsis has-path?
|
||||
:selected is-selected
|
||||
:hidden is-hidden
|
||||
:type-comp type-comp
|
||||
:type-frame type-frame)
|
||||
:id (dm/str "layer-name-" shape-id)
|
||||
:style {"--depth" depth "--parent-size" parent-size}
|
||||
:ref ref
|
||||
:on-double-click start-edit}
|
||||
|
||||
(if ^boolean (dbg/enabled? :show-ids)
|
||||
(dm/str (d/nilv shape-name "") " | " (str/slice (str shape-id) 24))
|
||||
[:span
|
||||
{:class (stl/css-case
|
||||
:element-name true
|
||||
:left-ellipsis has-path?
|
||||
:selected is-selected
|
||||
:hidden is-hidden
|
||||
:type-comp type-comp
|
||||
:type-frame type-frame)
|
||||
:id (dm/str "layer-name-" shape-id)
|
||||
:style {"--depth" depth "--parent-size" parent-size}
|
||||
:ref ref
|
||||
:on-double-click start-edit}
|
||||
(if (dbg/enabled? :show-ids)
|
||||
(str (d/nilv shape-name "") " | " (str/slice (str shape-id) 24))
|
||||
(d/nilv shape-name ""))]
|
||||
|
||||
(when (and ^boolean (dbg/enabled? :show-touched)
|
||||
^boolean is-shape-touched)
|
||||
(when (and (dbg/enabled? :show-touched) ^boolean is-shape-touched)
|
||||
[:span {:class (stl/css :element-name-touched)} "*"])])))
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.weak :as weak]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
@@ -22,7 +21,7 @@
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.notifications.badge :refer [badge-notification]]
|
||||
[app.main.ui.workspace.sidebar.layer-item :refer [layer-item*]]
|
||||
[app.main.ui.workspace.sidebar.layer-item :refer [layer-item]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as globals]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -32,173 +31,92 @@
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[goog.events :as events]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private ref:highlighted-shapes
|
||||
(l/derived (fn [local]
|
||||
(-> local
|
||||
(get :highlighted)
|
||||
(not-empty)))
|
||||
refs/workspace-local))
|
||||
|
||||
(def ^:private ref:shape-for-rename
|
||||
(l/derived (l/key :shape-for-rename) refs/workspace-local))
|
||||
|
||||
(defn- use-selected-shapes
|
||||
"A convencience hook wrapper for get selected shapes"
|
||||
[]
|
||||
(let [selected (mf/deref refs/selected-shapes)]
|
||||
(hooks/use-equal-memo selected)))
|
||||
[rumext.v2 :as mf])
|
||||
(:import
|
||||
goog.events.EventType))
|
||||
|
||||
;; This components is a piece for sharding equality check between top
|
||||
;; level frames and try to avoid rerender frames that are does not
|
||||
;; affected by the selected set.
|
||||
(mf/defc frame-wrapper*
|
||||
(mf/defc frame-wrapper
|
||||
{::mf/props :obj}
|
||||
[{:keys [selected] :as props}]
|
||||
(let [pending-selected-ref
|
||||
(mf/use-ref selected)
|
||||
|
||||
current-selected
|
||||
(mf/use-state selected)
|
||||
|
||||
props
|
||||
(mf/spread-object props {:selected @current-selected})
|
||||
(let [pending-selected (mf/use-var selected)
|
||||
current-selected (mf/use-state selected)
|
||||
props (mf/spread-object props {:selected @current-selected})
|
||||
|
||||
set-selected
|
||||
(mf/with-memo []
|
||||
(throttle-fn 50 #(when-let [pending-selected (mf/ref-val pending-selected-ref)]
|
||||
(reset! current-selected pending-selected))))]
|
||||
(mf/use-memo
|
||||
(fn []
|
||||
(throttle-fn
|
||||
50
|
||||
#(when-let [pending-selected @pending-selected]
|
||||
(reset! current-selected pending-selected)))))]
|
||||
|
||||
(mf/with-effect [selected set-selected]
|
||||
(mf/set-ref-val! pending-selected-ref selected)
|
||||
(^function set-selected)
|
||||
(reset! pending-selected selected)
|
||||
(set-selected)
|
||||
(fn []
|
||||
(mf/set-ref-val! pending-selected-ref nil)
|
||||
(rx/dispose! set-selected)))
|
||||
(reset! pending-selected nil)
|
||||
#(rx/dispose! set-selected)))
|
||||
|
||||
[:> layer-item* props]))
|
||||
|
||||
(mf/defc layers-tree*
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects is-filtered parent-size] :as props}]
|
||||
(let [selected (use-selected-shapes)
|
||||
highlighted (mf/deref ref:highlighted-shapes)
|
||||
root (get objects uuid/zero)
|
||||
|
||||
rename-id (mf/deref ref:shape-for-rename)
|
||||
|
||||
shapes (get root :shapes)
|
||||
shapes (mf/with-memo [shapes objects]
|
||||
(loop [counter 0
|
||||
shapes (seq shapes)
|
||||
result (list)]
|
||||
|
||||
(if-let [id (first shapes)]
|
||||
(if-let [obj (get objects id)]
|
||||
(do
|
||||
;; NOTE: this is a bit hacky, but reduces substantially
|
||||
;; the allocation; If we use enumeration, we allocate
|
||||
;; new sequence and add one iteration on each render,
|
||||
;; independently if objects are changed or not. If we
|
||||
;; store counter on metadata, we still need to create a
|
||||
;; new allocation for each shape; with this method we
|
||||
;; bypass this by mutating a private property on the
|
||||
;; object removing extra allocation and extra iteration
|
||||
;; on every request.
|
||||
(unchecked-set obj "__$__counter" counter)
|
||||
(recur (inc counter)
|
||||
(rest shapes)
|
||||
(conj result obj)))
|
||||
(recur (inc counter)
|
||||
(rest shapes)
|
||||
result))
|
||||
result)))
|
||||
|
||||
;; We pass objects just for trigger rerender but we internally use objects-ref
|
||||
;; for make all callbacks and memoized blocks do not depen strictly on objects
|
||||
;; and do not perform calculation if the local related shapes are not
|
||||
;; changed. For this purpose we created a single objects-ref var for not doing
|
||||
;; this on each layer item.
|
||||
objects-ref (mf/use-ref objects)]
|
||||
|
||||
(mf/with-effect [objects]
|
||||
(mf/set-ref-val! objects-ref objects))
|
||||
[:> layer-item props]))
|
||||
|
||||
(mf/defc layers-tree
|
||||
{::mf/wrap [mf/memo #(mf/throttle % 200)]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [objects filtered? parent-size] :as props}]
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
selected (hooks/use-equal-memo selected)
|
||||
highlighted (mf/deref refs/highlighted-shapes)
|
||||
highlighted (hooks/use-equal-memo highlighted)
|
||||
root (get objects uuid/zero)]
|
||||
[:div {:class (stl/css :element-list) :data-testid "layer-item"}
|
||||
[:> hooks/sortable-container* {}
|
||||
(for [obj shapes]
|
||||
(if (cfh/frame-shape? obj)
|
||||
[:> frame-wrapper*
|
||||
{:item obj
|
||||
:rename-id rename-id
|
||||
:selected selected
|
||||
:highlighted highlighted
|
||||
:index (unchecked-get obj "__$__counter")
|
||||
:objects objects
|
||||
:objects-ref objects-ref
|
||||
:key (weak/weak-key obj)
|
||||
:is-sortable true
|
||||
:is-filtered is-filtered
|
||||
:parent-size parent-size
|
||||
:depth -1}]
|
||||
[:> layer-item*
|
||||
{:item obj
|
||||
:rename-id rename-id
|
||||
:selected selected
|
||||
:highlighted highlighted
|
||||
:index (unchecked-get obj "__$__counter")
|
||||
:objects objects
|
||||
:objects-ref objects-ref
|
||||
:key (weak/weak-key obj)
|
||||
:is-sortable true
|
||||
:is-filtered is-filtered
|
||||
:depth -1
|
||||
:parent-size parent-size}]))]]))
|
||||
(for [[index id] (reverse (d/enumerate (:shapes root)))]
|
||||
(when-let [obj (get objects id)]
|
||||
(if (cfh/frame-shape? obj)
|
||||
[:& frame-wrapper
|
||||
{:item obj
|
||||
:selected selected
|
||||
:highlighted highlighted
|
||||
:index index
|
||||
:objects objects
|
||||
:key id
|
||||
:sortable? true
|
||||
:filtered? filtered?
|
||||
:parent-size parent-size
|
||||
:depth -1}]
|
||||
[:& layer-item
|
||||
{:item obj
|
||||
:selected selected
|
||||
:highlighted highlighted
|
||||
:index index
|
||||
:objects objects
|
||||
:key id
|
||||
:sortable? true
|
||||
:filtered? filtered?
|
||||
:depth -1
|
||||
:parent-size parent-size}])))]]))
|
||||
|
||||
(mf/defc layers-tree-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [objects] :as props}]
|
||||
;; This is a performance sensitive componet, so we use lower-level primitives for
|
||||
;; reduce residual allocation for this specific case
|
||||
(let [state-tmp (mf/useState objects)
|
||||
objects' (aget state-tmp 0)
|
||||
set-objects (aget state-tmp 1)
|
||||
|
||||
subject-s (mf/with-memo []
|
||||
(rx/subject))
|
||||
changes-s (mf/with-memo [subject-s]
|
||||
(->> subject-s
|
||||
(rx/debounce 500)))
|
||||
|
||||
props (mf/spread-props props {:objects objects'})]
|
||||
|
||||
(mf/with-effect [objects subject-s]
|
||||
(rx/push! subject-s objects))
|
||||
|
||||
(mf/with-effect [changes-s]
|
||||
(let [sub (rx/subscribe changes-s set-objects)]
|
||||
#(rx/dispose! sub)))
|
||||
|
||||
[:> layers-tree* props]))
|
||||
|
||||
(mf/defc filters-tree*
|
||||
{::mf/wrap [mf/memo #(mf/throttle % 300)]
|
||||
::mf/private true}
|
||||
(mf/defc filters-tree
|
||||
{::mf/wrap [mf/memo #(mf/throttle % 200)]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [objects parent-size]}]
|
||||
(let [selected (use-selected-shapes)
|
||||
root (get objects uuid/zero)]
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
selected (hooks/use-equal-memo selected)
|
||||
root (get objects uuid/zero)]
|
||||
[:ul {:class (stl/css :element-list)}
|
||||
(for [[index id] (d/enumerate (:shapes root))]
|
||||
(when-let [obj (get objects id)]
|
||||
[:> layer-item*
|
||||
[:& layer-item
|
||||
{:item obj
|
||||
:selected selected
|
||||
:index index
|
||||
:objects objects
|
||||
:key id
|
||||
:is-sortable false
|
||||
:is-filtered true
|
||||
:sortable? false
|
||||
:filtered? true
|
||||
:depth -1
|
||||
:parent-size parent-size}]))]))
|
||||
|
||||
@@ -214,7 +132,6 @@
|
||||
keys
|
||||
(filter #(not= uuid/zero %))
|
||||
vec)]
|
||||
|
||||
(update reparented-objects uuid/zero assoc :shapes reparented-shapes)))
|
||||
|
||||
;; --- Layers Toolbox
|
||||
@@ -360,11 +277,9 @@
|
||||
(swap! state* update :num-items + 100))))]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [key1 (events/listen globals/document "keydown" on-key-down)
|
||||
key2 (events/listen globals/document "click" hide-menu)]
|
||||
(fn []
|
||||
(events/unlistenByKey key1)
|
||||
(events/unlistenByKey key2))))
|
||||
(let [keys [(events/listen globals/document EventType.KEYDOWN on-key-down)
|
||||
(events/listen globals/document EventType.CLICK hide-menu)]]
|
||||
(fn [] (doseq [key keys] (events/unlistenByKey key)))))
|
||||
|
||||
[filtered-objects
|
||||
handle-show-more
|
||||
@@ -548,8 +463,6 @@
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [size-parent]}]
|
||||
(let [page (mf/deref refs/workspace-page)
|
||||
page-id (get page :id)
|
||||
|
||||
focus (mf/deref refs/workspace-focus-selected)
|
||||
|
||||
objects (hooks/with-focus-objects (:objects page) focus)
|
||||
@@ -559,8 +472,7 @@
|
||||
observer-var (mf/use-var nil)
|
||||
lazy-load-ref (mf/use-ref nil)
|
||||
|
||||
[filtered-objects show-more filter-component]
|
||||
(use-search page objects)
|
||||
[filtered-objects show-more filter-component] (use-search page objects)
|
||||
|
||||
intersection-callback
|
||||
(fn [entries]
|
||||
@@ -606,9 +518,9 @@
|
||||
[:div {:class (stl/css :tool-window-content)
|
||||
:data-scroll-container true
|
||||
:ref on-render-container}
|
||||
[:> filters-tree* {:objects filtered-objects
|
||||
:key (dm/str page-id)
|
||||
:parent-size size-parent}]
|
||||
[:& filters-tree {:objects filtered-objects
|
||||
:key (dm/str (:id page))
|
||||
:parent-size size-parent}]
|
||||
[:div {:ref lazy-load-ref
|
||||
:style {:min-height 16}}]]
|
||||
[:div {:on-scroll on-scroll
|
||||
@@ -616,16 +528,16 @@
|
||||
:data-scroll-container true
|
||||
:style {:display (when (some? filtered-objects) "none")}}
|
||||
|
||||
[:> layers-tree-wrapper* {:objects filtered-objects
|
||||
:key (dm/str page-id)
|
||||
:is-filtered true
|
||||
:parent-size size-parent}]]]
|
||||
[:& layers-tree {:objects filtered-objects
|
||||
:key (dm/str (:id page))
|
||||
:filtered? true
|
||||
:parent-size size-parent}]]]
|
||||
|
||||
[:div {:on-scroll on-scroll
|
||||
:class (stl/css :tool-window-content)
|
||||
:data-scroll-container true
|
||||
:style {:display (when (some? filtered-objects) "none")}}
|
||||
[:> layers-tree-wrapper* {:objects objects
|
||||
:key (dm/str page-id)
|
||||
:is-filtered false
|
||||
:parent-size size-parent}]])]))
|
||||
[:& layers-tree {:objects objects
|
||||
:key (dm/str (:id page))
|
||||
:filtered? false
|
||||
:parent-size size-parent}]])]))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user