Compare commits

..

33 Commits

Author SHA1 Message Date
David Barragán Merino
c1335961b4 🔧 Deploy penpot api documentation 2026-01-21 18:52:07 +01:00
Andrey Antukh
b70eb768e0 Merge remote-tracking branch 'origin/staging' into develop 2026-01-21 13:51:41 +01:00
Andrey Antukh
b8c70be9a2 Make frontend build and watch process more resilent to errors 2026-01-21 13:44:35 +01:00
Andrey Antukh
525adcfcbe Add wasm build on watch app script (devenv) 2026-01-21 13:44:35 +01:00
Eva Marco
7cce4c6532 🐛 Fix unhandled exception tokens creation dialog (#8136) 2026-01-21 13:09:22 +01:00
Alejandro Alonso
a3fdd8b691 Merge pull request #8147 from penpot/niwinz-staging-file-menu-issue
 Use correct team-id on file-menu on dashboard
2026-01-21 12:54:28 +01:00
Andrey Antukh
b6a9579c98 Use correct team-id on file-menu on dashboard
Before the changes on this commit, the team object is used for
retrieve the id, where we already have team-id. Additionally, the
team object resolution is async operation and is not available on
the first render which causes strange issues on automated flows
(playwright) where an option is clicked when the async flow is
still pending and we have no team object loaded.
2026-01-21 12:23:44 +01:00
Andrey Antukh
4397ede5c1 Merge branch 'staging-render' into develop 2026-01-21 10:18:15 +01:00
Andrey Antukh
ff25df0457 Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-21 10:17:22 +01:00
Eva Marco
8c7fd0af4b 🐛 Fix shadow reference validation (#8132) 2026-01-21 09:17:03 +01:00
Andrey Antukh
cf46051f56 🔥 Remove .traivis.yml file from the repository 2026-01-20 19:40:31 +01:00
Luis de Dios
079b3fbfad ♻️ Extract and create panel title component (#8090) 2026-01-20 18:56:25 +01:00
Andrey Antukh
299f628951 Merge pull request #8123 from penpot/GlobalStar117-fix/token-validation-crash
🐛 Fix Penpot crash when setting some name in Design tokens
2026-01-20 18:53:05 +01:00
David Barragán Merino
32d0fe6463 🔧 Use selfhosted runner 01 to generate the bundle 2026-01-20 18:09:18 +01:00
Andrey Antukh
6393330ee1 Merge remote-tracking branch 'origin/staging' 2026-01-20 16:25:10 +01:00
Andrey Antukh
8252bc485e 📚 Fix oidc callback related documentation issue 2026-01-20 16:24:12 +01:00
Andrey Antukh
cecd3d4a90 📎 Update changelog 2026-01-20 16:00:57 +01:00
Eva Marco
1c2c0987f5 🐛 Fix schema validation for references from other sets 2026-01-20 15:51:43 +01:00
Globalstar117
0418147e74 🐛 Add error handler to token form validation to prevent crash
When creating a token with a name that conflicts with existing
hierarchical token names (e.g., 'accent-color' when 'accent-color.blue.dark'
exists), the validation throws an error via rx/throw. However, the
rx/subs! subscriber in generic_form.cljs had no error handler, causing
an unhandled exception that resulted in an 'Internal Error' crash.

This fix adds an error handler that:
1. Catches validation errors from the reactive stream
2. Uses humanize-errors to convert them to user-friendly messages
3. Displays the error in the form's extra-errors field

Before: Crash with 'Internal Error' dialog
After: Form shows validation error message

Fixes #8110

---
This is a Gittensor contribution.
gittensor:user:GlobalStar117
2026-01-20 15:51:25 +01:00
Alonso Torres
47775a9e2c Merge pull request #8134 from penpot/alotor-fix-plugins-export
🐛 Fix problem with export in plugins
2026-01-20 15:03:04 +01:00
Andrey Antukh
8191d04114 Use non-legacy config example on docker compose file 2026-01-20 13:25:55 +01:00
Alejandro Alonso
b7c2d9a079 Merge pull request #8130 from penpot/superalex-improve-zoom-pan-performance-7
🐛 Fix some tiles disappear after fast zoom and pan
2026-01-20 12:56:02 +01:00
Alejandro Alonso
aeb34a6f64 Merge pull request #8109 from penpot/superalex-fix-text-selrect-calculation
🐛 Render wasm typography token issues
2026-01-20 12:54:45 +01:00
Alejandro Alonso
6fa0c3af0c 🐛 Fix some tiles disappear after fast zoom and pan 2026-01-20 12:40:01 +01:00
Alejandro Alonso
260b9fb040 🐛 Fix texts with auto size updated via tokens with render wasm
activated
2026-01-20 12:39:17 +01:00
Alejandro Alonso
884954f4ff 🐛 Fix text selrect calculation 2026-01-20 12:37:57 +01:00
Andrey Antukh
88f0f75174 Merge pull request #8129 from penpot/niwinz-staging-bugfix-1
 Several improvements for build process
2026-01-20 12:26:55 +01:00
Andrey Antukh
1ffa956251 Include timestamp on version tag 2026-01-20 12:26:39 +01:00
Andrey Antukh
31054099ff Use pseudo-names on release builds of frontend (#8105) 2026-01-20 12:26:39 +01:00
Eva Marco
983487d73c 🐛 Fix shadow token reference validation (#8128) 2026-01-20 10:56:27 +01:00
Andrey Antukh
5c71c57dd9 Merge tag '2.12.1' 2025-12-30 15:37:30 +01:00
Andrey Antukh
5abc1aafb4 Merge tag '2.12.0-RC3' 2025-12-12 12:19:29 +01:00
Andrey Antukh
935728aa39 🔧 Backport build-tag github workflow from develop 2025-12-05 10:26:01 +01:00
64 changed files with 661 additions and 1221 deletions

View File

@@ -40,7 +40,7 @@ on:
jobs: jobs:
build-bundle: build-bundle:
name: Build and Upload Penpot Bundle name: Build and Upload Penpot Bundle
runs-on: ubuntu-24.04 runs-on: penpot-runner-01
env: env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

View File

@@ -0,0 +1,73 @@
name: Plugins/api-doc deployer
on:
push:
branches:
- develop
- staging
- main
paths:
- "plugins/**"
- ".github/workflows/deploy-plugin-docs.yml"
- "wrangle-penpot-plugins-api-doc.toml"
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch or ref'
type: string
required: true
default: 'develop'
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: plugins
steps:
- name: Extract some useful variables
id: vars
run: |
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.vars.outputs.gh_ref }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: "pnpm"
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build docs
run: pnpm run build:doc
- name: Select Worker name
run: |
REF="${{ steps.vars.outputs.gh_ref }}"
case "$REF" in
main) echo "WORKER_NAME=penpot-plugins-api-doc-pro" >> $GITHUB_ENV ;;
staging) echo "WORKER_NAME=penpot-plugins-api-doc-pre" >> $GITHUB_ENV ;;
develop) echo "WORKER_NAME=penpot-plugins-api-doc-hourly" >> $GITHUB_ENV ;;
*) echo "Unsupported branch ${REF}" && exit 1 ;;
esac
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config wrangle-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}

View File

@@ -1,40 +0,0 @@
dist: xenial
language: generic
sudo: required
cache:
directories:
- $HOME/.m2
services:
- docker
branches:
only:
- master
- develop
install:
- curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh
- chmod +x linux-install-1.10.1.447.sh
- sudo ./linux-install-1.10.1.447.sh
before_script:
- env | sort
script:
- ./manage.sh build-devenv
- ./manage.sh run-frontend-tests
- ./manage.sh run-backend-tests
- ./manage.sh build-images
- ./manage.sh run
after_script:
- docker images
notifications:
email: false
env:
- NODE_VERSION=10.16.0

View File

@@ -24,6 +24,7 @@
- Fix wrong register image [Taiga #12955](https://tree.taiga.io/project/penpot/task/12955) - Fix wrong register image [Taiga #12955](https://tree.taiga.io/project/penpot/task/12955)
- Fix error message on components doesn't close automatically [Taiga #12012](https://tree.taiga.io/project/penpot/issue/12012) - Fix error message on components doesn't close automatically [Taiga #12012](https://tree.taiga.io/project/penpot/issue/12012)
- Fix incorrect default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051) - Fix incorrect default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051)
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
## 2.13.0 (Unreleased) ## 2.13.0 (Unreleased)

View File

@@ -474,8 +474,8 @@
:height #{:sizing :dimensions} :height #{:sizing :dimensions}
:max-width #{:sizing :dimensions} :max-width #{:sizing :dimensions}
:max-height #{:sizing :dimensions} :max-height #{:sizing :dimensions}
:x #{:dimensions} :x #{:spacing :dimensions}
:y #{:dimensions} :y #{:spacing :dimensions}
:rotation #{:number :rotation} :rotation #{:number :rotation}
:border-radius #{:border-radius :dimensions} :border-radius #{:border-radius :dimensions}
:row-gap #{:spacing :dimensions} :row-gap #{:spacing :dimensions}
@@ -488,7 +488,6 @@
:sided-margins #{:spacing :dimensions} :sided-margins #{:spacing :dimensions}
:line-height #{:line-height :number} :line-height #{:line-height :number}
:opacity #{:opacity} :opacity #{:opacity}
:stroke-width #{:stroke-width}
:font-size #{:font-size} :font-size #{:font-size}
:letter-spacing #{:letter-spacing} :letter-spacing #{:letter-spacing}
:fill #{:color} :fill #{:color}

View File

@@ -152,9 +152,9 @@ services:
# AWS_ACCESS_KEY_ID: <KEY_ID> # AWS_ACCESS_KEY_ID: <KEY_ID>
# AWS_SECRET_ACCESS_KEY: <ACCESS_KEY> # AWS_SECRET_ACCESS_KEY: <ACCESS_KEY>
# PENPOT_ASSETS_STORAGE_BACKEND: assets-s3 # PENPOT_OBJECTS_STORAGE_BACKEND: s3
# PENPOT_STORAGE_ASSETS_S3_ENDPOINT: <ENDPOINT> # PENPOT_OBJECTS_STORAGE_S3_ENDPOINT: <ENDPOINT>
# PENPOT_STORAGE_ASSETS_S3_BUCKET: <BUKET_NAME> # PENPOT_OBJECTS_STORAGE_S3_BUCKET: <BUKET_NAME>
## Telemetry. When enabled, a periodical process will send anonymous data about this ## Telemetry. When enabled, a periodical process will send anonymous data about this
## instance. Telemetry data will enable us to learn how the application is used, ## instance. Telemetry data will enable us to learn how the application is used,

View File

@@ -114,14 +114,7 @@ configuration.
The callback has the following format: The callback has the following format:
```html ```html
https://<your_domain>/api/auth/oauth/<oauth_provider>/callback https://<your_domain>/api/auth/oidc/callback
```
You will need to change <your_domain> and <oauth_provider> according to your setup.
This is how it looks with Gitlab provider:
```html
https://<your_domain>/api/auth/oauth/gitlab/callback
``` ```
#### Google #### Google

View File

@@ -48,7 +48,7 @@
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook", "watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
"clear:shadow-cache": "rm -rf .shadow-cljs", "clear:shadow-cache": "rm -rf .shadow-cljs",
"watch": "exit 0", "watch": "exit 0",
"watch:app": "yarn run clear:shadow-cache && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"", "watch:app": "yarn run clear:shadow-cache && yarn run build:wasm && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
"watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\"" "watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\""
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,352 +0,0 @@
{
"~:features": {
"~#set": [
"fdata/path-data",
"plugins/runtime",
"design-tokens/v1",
"variants/v1",
"layout/grid",
"styles/v2",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:team-id": "~u55ffbd0f-2d3f-8023-8007-6b89af7ca1aa",
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true,
"~:can-read": true,
"~:is-logged": true
},
"~:has-media-trimmed": false,
"~:comment-thread-seqn": 0,
"~:name": "Nuevo Archivo 2",
"~:revn": 18,
"~:modified-at": "~m1768994999265",
"~:vern": 0,
"~:id": "~u8bb92298-e24e-805c-8007-6ce64314bfe8",
"~:is-shared": false,
"~:migrations": {
"~#ordered-set": [
"legacy-2",
"legacy-3",
"legacy-5",
"legacy-6",
"legacy-7",
"legacy-8",
"legacy-9",
"legacy-10",
"legacy-11",
"legacy-12",
"legacy-13",
"legacy-14",
"legacy-16",
"legacy-17",
"legacy-18",
"legacy-19",
"legacy-25",
"legacy-26",
"legacy-27",
"legacy-28",
"legacy-29",
"legacy-31",
"legacy-32",
"legacy-33",
"legacy-34",
"legacy-36",
"legacy-37",
"legacy-38",
"legacy-39",
"legacy-40",
"legacy-41",
"legacy-42",
"legacy-43",
"legacy-44",
"legacy-45",
"legacy-46",
"legacy-47",
"legacy-48",
"legacy-49",
"legacy-50",
"legacy-51",
"legacy-52",
"legacy-53",
"legacy-54",
"legacy-55",
"legacy-56",
"legacy-57",
"legacy-59",
"legacy-62",
"legacy-65",
"legacy-66",
"legacy-67",
"0001-remove-tokens-from-groups",
"0002-normalize-bool-content-v2",
"0002-clean-shape-interactions",
"0003-fix-root-shape",
"0003-convert-path-content-v2",
"0005-deprecate-image-type",
"0006-fix-old-texts-fills",
"0008-fix-library-colors-v4",
"0009-clean-library-colors",
"0009-add-partial-text-touched-flags",
"0010-fix-swap-slots-pointing-non-existent-shapes",
"0011-fix-invalid-text-touched-flags",
"0012-fix-position-data",
"0013-fix-component-path",
"0013-clear-invalid-strokes-and-fills",
"0014-fix-tokens-lib-duplicate-ids",
"0014-clear-components-nil-objects",
"0015-fix-text-attrs-blank-strings",
"0015-clean-shadow-color",
"0016-copy-fills-from-position-data-to-text-node"
]
},
"~:version": 67,
"~:project-id": "~u55ffbd0f-2d3f-8023-8007-6b89af7cd5eb",
"~:created-at": "~m1768562403410",
"~:backend": "legacy-db",
"~:data": {
"~:pages": [
"~u8bb92298-e24e-805c-8007-6ce64314cfc5"
],
"~:pages-index": {
"~u8bb92298-e24e-805c-8007-6ce64314cfc5": {
"~:objects": {
"~#penpot/objects-map/v2": {
"~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^H\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~u6c65a5dc-fb40-8072-8007-6ce644905054\",\"~u8506e3f3-e05b-807c-8007-6ceac380abc1\"]]]",
"~u6c65a5dc-fb40-8072-8007-6ce644905054": "[\"~#shape\",[\"^ \",\"~:y\",159,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",0,\"~:p3\",0,\"~:p4\",0],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:grow-type\",\"~:fixed\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",false,\"~:name\",\"Board\",\"~:layout-align-items\",\"~:start\",\"~:width\",389,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",303,\"~:y\",159]],[\"^L\",[\"^ \",\"~:x\",692,\"~:y\",159]],[\"^L\",[\"^ \",\"~:x\",692,\"~:y\",599]],[\"^L\",[\"^ \",\"~:x\",303,\"~:y\",599]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",0,\"~:column-gap\",0],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:layout-justify-content\",\"^E\",\"~:r1\",0,\"~:id\",\"~u6c65a5dc-fb40-8072-8007-6ce644905054\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:layout-flex-dir\",\"~:row\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",303,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",303,\"~:y\",159,\"^F\",389,\"~:height\",440,\"~:x1\",303,\"~:y1\",159,\"~:x2\",692,\"~:y2\",599]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^16\",440,\"~:flip-y\",null,\"~:shapes\",[\"~u6c65a5dc-fb40-8072-8007-6ce655b5dbe9\",\"~u6c65a5dc-fb40-8072-8007-6ce65472f217\",\"~u6c65a5dc-fb40-8072-8007-6ce6535e8d62\"]]]",
"~u6c65a5dc-fb40-8072-8007-6ce6535e8d62": "[\"~#shape\",[\"^ \",\"~:y\",159,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",95,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",303,\"~:y\",159]],[\"^<\",[\"^ \",\"~:x\",398,\"~:y\",159]],[\"^<\",[\"^ \",\"~:x\",398,\"~:y\",259]],[\"^<\",[\"^ \",\"~:x\",303,\"~:y\",259]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u6c65a5dc-fb40-8072-8007-6ce6535e8d62\",\"~:parent-id\",\"~u6c65a5dc-fb40-8072-8007-6ce644905054\",\"~:frame-id\",\"~u6c65a5dc-fb40-8072-8007-6ce644905054\",\"~:strokes\",[],\"~:x\",303,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",303,\"~:y\",159,\"^8\",95,\"~:height\",100,\"~:x1\",303,\"~:y1\",159,\"~:x2\",398,\"~:y2\",259]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",100,\"~:flip-y\",null]]",
"~u6c65a5dc-fb40-8072-8007-6ce65472f217": "[\"~#shape\",[\"^ \",\"~:y\",159,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",140.99999999999994,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",398,\"~:y\",159]],[\"^<\",[\"^ \",\"~:x\",539,\"~:y\",159]],[\"^<\",[\"^ \",\"~:x\",539,\"~:y\",296]],[\"^<\",[\"^ \",\"~:x\",398,\"~:y\",296]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u6c65a5dc-fb40-8072-8007-6ce65472f217\",\"~:parent-id\",\"~u6c65a5dc-fb40-8072-8007-6ce644905054\",\"~:frame-id\",\"~u6c65a5dc-fb40-8072-8007-6ce644905054\",\"~:strokes\",[],\"~:x\",398.00000000000006,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",398.00000000000006,\"~:y\",159,\"^8\",140.99999999999994,\"~:height\",137,\"~:x1\",398.00000000000006,\"~:y1\",159,\"~:x2\",539,\"~:y2\",296]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",137,\"~:flip-y\",null]]",
"~u6c65a5dc-fb40-8072-8007-6ce655b5dbe9": "[\"~#shape\",[\"^ \",\"~:y\",159,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",82,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",539,\"~:y\",159]],[\"^<\",[\"^ \",\"~:x\",621,\"~:y\",159]],[\"^<\",[\"^ \",\"~:x\",621,\"~:y\",259]],[\"^<\",[\"^ \",\"~:x\",539,\"~:y\",259]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u6c65a5dc-fb40-8072-8007-6ce655b5dbe9\",\"~:parent-id\",\"~u6c65a5dc-fb40-8072-8007-6ce644905054\",\"~:frame-id\",\"~u6c65a5dc-fb40-8072-8007-6ce644905054\",\"~:strokes\",[],\"~:x\",539,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",539,\"~:y\",159,\"^8\",82,\"~:height\",100,\"~:x1\",539,\"~:y1\",159,\"~:x2\",621,\"~:y2\",259]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",100,\"~:flip-y\",null]]",
"~u8506e3f3-e05b-807c-8007-6ceac380abc1": "[\"~#shape\",[\"^ \",\"~:y\",405,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",59,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",817,\"~:y\",405]],[\"^<\",[\"^ \",\"~:x\",876,\"~:y\",405]],[\"^<\",[\"^ \",\"~:x\",876,\"~:y\",472]],[\"^<\",[\"^ \",\"~:x\",817,\"~:y\",472]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u8506e3f3-e05b-807c-8007-6ceac380abc1\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",817,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",817,\"~:y\",405,\"^8\",59,\"~:height\",67,\"~:x1\",817,\"~:y1\",405,\"~:x2\",876,\"~:y2\",472]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",67,\"~:flip-y\",null]]"
}
},
"~:id": "~u8bb92298-e24e-805c-8007-6ce64314cfc5",
"~:name": "Page 1"
}
},
"~:id": "~u8bb92298-e24e-805c-8007-6ce64314bfe8",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
},
"~:tokens-lib": {
"~#penpot/tokens-lib": {
"~:sets": {
"~#ordered-map": [
[
"S-Global",
{
"~#penpot/token-set": {
"~:id": "~u7c1a66f7-5186-8060-8007-6ce67f8f2592",
"~:name": "Global",
"~:description": "",
"~:modified-at": "~m1768994999268",
"~:tokens": {
"~#ordered-map": [
[
"dim.xs",
{
"~#penpot/token": {
"~:id": "~u7c1a66f7-5186-8060-8007-6ce67f8f2591",
"~:name": "dim.xs",
"~:type": "~:dimensions",
"~:value": "20",
"~:description": "",
"~:modified-at": "~m1768562465340"
}
}
],
[
"dim.md",
{
"~#penpot/token": {
"~:id": "~u7c1a66f7-5186-8060-8007-6ce68a2f3c3f",
"~:name": "dim.md",
"~:type": "~:dimensions",
"~:value": "50",
"~:description": "",
"~:modified-at": "~m1768562476220"
}
}
],
[
"dim.xl",
{
"~#penpot/token": {
"~:id": "~u7c1a66f7-5186-8060-8007-6ce694f47726",
"~:name": "dim.xl",
"~:type": "~:dimensions",
"~:value": "100",
"~:description": "",
"~:modified-at": "~m1768562487249"
}
}
],
[
"sz.sm",
{
"~#penpot/token": {
"~:id": "~u7c1a66f7-5186-8060-8007-6ce6d6e519b1",
"~:name": "sz.sm",
"~:type": "~:sizing",
"~:value": "200",
"~:description": "",
"~:modified-at": "~m1768562554772"
}
}
],
[
"sz.xl",
{
"~#penpot/token": {
"~:id": "~u7c1a66f7-5186-8060-8007-6ce6e4165307",
"~:name": "sz.xl",
"~:type": "~:sizing",
"~:value": "500",
"~:description": "",
"~:modified-at": "~m1768562568281"
}
}
],
[
"sp.mid",
{
"~#penpot/token": {
"~:id": "~u7c1a66f7-5186-8060-8007-6ce6f5fb2867",
"~:name": "sp.mid",
"~:type": "~:spacing",
"~:value": "50",
"~:description": "",
"~:modified-at": "~m1768562586604"
}
}
],
[
"sp.l",
{
"~#penpot/token": {
"~:id": "~u7c1a66f7-5186-8060-8007-6ce71009cd91",
"~:name": "sp.l",
"~:type": "~:spacing",
"~:value": "{dim.xl}",
"~:description": "",
"~:modified-at": "~m1768562613287"
}
}
],
[
"test",
{
"~#penpot/token": {
"~:id": "~uc49f25e6-1298-8004-8007-709f660875be",
"~:name": "test",
"~:type": "~:dimensions",
"~:value": "20",
"~:description": "",
"~:modified-at": "~m1768812262433"
}
}
],
[
"width-big",
{
"~#penpot/token": {
"~:id": "~u3b3e4320-53fb-8096-8007-73585edb4dd6",
"~:name": "width-big",
"~:type": "~:stroke-width",
"~:value": "20",
"~:description": "",
"~:modified-at": "~m1768994969453"
}
}
],
[
"width-small",
{
"~#penpot/token": {
"~:id": "~u3b3e4320-53fb-8096-8007-73586a5ec516",
"~:name": "width-small",
"~:type": "~:stroke-width",
"~:value": "5",
"~:description": "",
"~:modified-at": "~m1768994981243"
}
}
],
[
"red",
{
"~#penpot/token": {
"~:id": "~u3b3e4320-53fb-8096-8007-735871dccede",
"~:name": "red",
"~:type": "~:color",
"~:value": "red",
"~:description": "",
"~:modified-at": "~m1768994988915"
}
}
],
[
"green",
{
"~#penpot/token": {
"~:id": "~u3b3e4320-53fb-8096-8007-735879045aa2",
"~:name": "green",
"~:type": "~:color",
"~:value": "green",
"~:description": "",
"~:modified-at": "~m1768994996241"
}
}
]
]
}
}
}
]
]
},
"~:themes": {
"~#ordered-map": [
[
"",
{
"~#ordered-map": [
[
"__PENPOT__HIDDEN__TOKEN__THEME__",
{
"~#penpot/token-theme": {
"~:id": "~u00000000-0000-0000-0000-000000000000",
"~:name": "__PENPOT__HIDDEN__TOKEN__THEME__",
"~:group": "",
"~:description": "",
"~:is-source": false,
"~:external-id": "",
"~:modified-at": "~m1768562468391",
"~:sets": {
"~#set": [
"Global"
]
}
}
}
]
]
}
]
]
},
"~:active-themes": {
"~#set": [
"/__PENPOT__HIDDEN__TOKEN__THEME__"
]
}
}
}
}
}

View File

@@ -149,14 +149,12 @@ test.describe("Tokens: Apply token", () => {
await detachButton.click(); await detachButton.click();
// Open dropdown from input // Open dropdown from input
const dropdownBtn = layerMenuSection.getByLabel("Open token list"); const dropdownBtn = layerMenuSection.getByLabel('Open token list');
await expect(dropdownBtn).toBeVisible(); await expect(dropdownBtn).toBeVisible();
await dropdownBtn.click(); await dropdownBtn.click();
// Change token from dropdown // Change token from dropdown
const opacityLowOption = layerMenuSection.getByRole("option", { const opacityLowOption = layerMenuSection.getByRole('option', { name: 'opacity.low' });
name: "opacity.low",
});
await expect(opacityLowOption).toBeVisible(); await expect(opacityLowOption).toBeVisible();
await opacityLowOption.click(); await opacityLowOption.click();
@@ -484,279 +482,4 @@ test.describe("Tokens: Apply token", () => {
await expect(shadowSection).toHaveCount(2); await expect(shadowSection).toHaveCount(2);
}); });
}); });
test("User applies dimension token to a shape on width and height", async ({
page,
}) => {
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
// Unfolds dimensions on token panel
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
// Apply token to width and height token from token panel
await tokensSidebar.getByRole("button", { name: "dimension.sm" }).click();
// Check if measures sections is visible on right sidebar
const measuresSection = page.getByRole("region", {
name: "shape-measures-section",
});
await expect(measuresSection).toBeVisible();
// Check if token pill is visible on design tab on right sidebar
const dimensionSMTokenPill = measuresSection.getByRole("button", {
name: "dimension.sm",
});
await expect(dimensionSMTokenPill).toHaveCount(2);
await dimensionSMTokenPill.nth(1).click();
// Change token from dropdown
const dimensionTokenOptionXl = measuresSection.getByLabel("dimension.xl");
await expect(dimensionTokenOptionXl).toBeVisible();
await dimensionTokenOptionXl.click();
await expect(dimensionSMTokenPill).toHaveCount(1);
const dimensionXLTokenPill = measuresSection.getByRole("button", {
name: "dimension.xl",
});
await expect(dimensionXLTokenPill).toBeVisible();
// Detach token from design tab on right sidebar
const detachButton = measuresSection.getByRole("button", {
name: "Detach token",
});
await detachButton.nth(1).click();
await expect(dimensionXLTokenPill).not.toBeVisible();
});
test("User applies dimension token to a shape on x position", async ({
page,
}) => {
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
// Unfolds dimensions on token panel
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
// Apply token to width and height token from token panel
await tokensSidebar
.getByRole("button", { name: "dimension.sm" })
.click({ button: "right" });
await tokenContextMenuForToken.getByText("AxisX").click();
// Check if measures sections is visible on right sidebar
const measuresSection = page.getByRole("region", {
name: "shape-measures-section",
});
await expect(measuresSection).toBeVisible();
// Check if token pill is visible on design tab on right sidebar
const dimensionSMTokenPill = measuresSection.getByRole("button", {
name: "dimension.sm",
});
await expect(dimensionSMTokenPill).toBeVisible();
await dimensionSMTokenPill.click();
// Change token from dropdown
const dimensionTokenOptionXl = measuresSection.getByLabel("dimension.xl");
await expect(dimensionTokenOptionXl).toBeVisible();
await dimensionTokenOptionXl.click();
await expect(dimensionSMTokenPill).not.toBeVisible();
const dimensionXLTokenPill = measuresSection.getByRole("button", {
name: "dimension.xl",
});
await expect(dimensionXLTokenPill).toBeVisible();
// Detach token from design tab on right sidebar
const detachButton = measuresSection.getByRole("button", {
name: "Detach token",
});
await detachButton.nth(0).click();
await expect(dimensionXLTokenPill).not.toBeVisible();
});
test("User applies dimension token to a shape on y position", async ({
page,
}) => {
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
// Unfolds dimensions on token panel
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
// Apply token to width and height token from token panel
await tokensSidebar
.getByRole("button", { name: "dimension.sm" })
.click({ button: "right" });
await tokenContextMenuForToken.getByText("Y").click();
// Check if measures sections is visible on right sidebar
const measuresSection = page.getByRole("region", {
name: "shape-measures-section",
});
await expect(measuresSection).toBeVisible();
// Check if token pill is visible on design tab on right sidebar
const dimensionSMTokenPill = measuresSection.getByRole("button", {
name: "dimension.sm",
});
await expect(dimensionSMTokenPill).toBeVisible();
await dimensionSMTokenPill.click();
// Change token from dropdown
const dimensionTokenOptionXl = measuresSection.getByLabel("dimension.xl");
await expect(dimensionTokenOptionXl).toBeVisible();
await dimensionTokenOptionXl.click();
await expect(dimensionSMTokenPill).not.toBeVisible();
const dimensionXLTokenPill = measuresSection.getByRole("button", {
name: "dimension.xl",
});
await expect(dimensionXLTokenPill).toBeVisible();
// Detach token from design tab on right sidebar
const detachButton = measuresSection.getByRole("button", {
name: "Detach token",
});
await detachButton.nth(0).click();
await expect(dimensionXLTokenPill).not.toBeVisible();
});
test("User applies dimension token to a shape border-radius", async ({
page,
}) => {
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
// Unfolds dimensions on token panel
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers.getByTestId("layer-row").nth(2).click();
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.xs");
// Apply token to width and height token from token panel
await tokensSidebar
.getByRole("button", { name: "dimension.xs" })
.click({ button: "right" });
await tokenContextMenuForToken.getByText("Border radius").hover();
await tokenContextMenuForToken.getByText("RadiusAll").click();
// Check if border radius sections is visible on right sidebar
const borderRadiusSection = page.getByRole("region", {
name: "border-radius-section",
});
await expect(borderRadiusSection).toBeVisible();
// Check if token pill is visible on design tab on right sidebar
const dimensionXSTokenPill = borderRadiusSection.getByRole("button", {
name: "dimension.xs",
});
await expect(dimensionXSTokenPill).toBeVisible();
await dimensionXSTokenPill.click();
// Change token from dropdown
const dimensionTokenOptionXl =
borderRadiusSection.getByLabel("dimension.xl");
await expect(dimensionTokenOptionXl).toBeVisible();
await dimensionTokenOptionXl.click();
await expect(dimensionXSTokenPill).not.toBeVisible();
const dimensionXLTokenPill = borderRadiusSection.getByRole("button", {
name: "dimension.xl",
});
await expect(dimensionXLTokenPill).toBeVisible();
// Detach token from design tab on right sidebar
const detachButton = borderRadiusSection.getByRole("button", {
name: "Detach token",
});
await detachButton.nth(0).click();
await expect(dimensionXLTokenPill).not.toBeVisible();
});
test("User applies stroke width token to a shape", async ({ page }) => {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
// Set up
await workspace.mockConfigFlags(["enable-feature-token-input"]);
await workspace.setupEmptyFile();
await workspace.mockGetFile("workspace/get-file-layout-stroke-token-json");
await workspace.goToWorkspace();
// Select shape apply stroke
await workspace.layers.getByTestId("layer-row").nth(0).click();
const rightSidebar = page.getByTestId("right-sidebar");
await expect(rightSidebar).toBeVisible();
await rightSidebar.getByTestId("add-stroke").click();
// Apply stroke width token from token panel
const tokensTab = page.getByRole("tab", { name: "Tokens" });
await expect(tokensTab).toBeVisible();
await tokensTab.click();
await page.getByRole("button", { name: "Stroke Width 2" }).click();
const tokensSidebar = workspace.tokensSidebar;
await expect(
tokensSidebar.getByRole("button", { name: "width-big" }),
).toBeVisible();
await tokensSidebar.getByRole("button", { name: "width-big" }).click();
// Check if token pill is visible on right sidebar
const strokeSectionSidebar = rightSidebar.getByRole("region", {
name: "stroke-section",
});
await expect(strokeSectionSidebar).toBeVisible();
const firstStrokeRow = strokeSectionSidebar.getByLabel("stroke-row-0");
await expect(firstStrokeRow).toBeVisible();
const StrokeWidthPill = firstStrokeRow.getByRole("button", {
name: "width-big",
});
await expect(StrokeWidthPill).toBeVisible();
// Detach token from right sidebar and apply another from dropdown
const detachButton = firstStrokeRow.getByRole("button", {
name: "Detach token",
});
await detachButton.click();
await expect(StrokeWidthPill).not.toBeVisible();
const tokenDropdown = firstStrokeRow.getByRole("button", {
name: "Open token list",
});
await tokenDropdown.click();
const widthOptionSmall = firstStrokeRow.getByLabel("width-small");
await expect(widthOptionSmall).toBeVisible();
await widthOptionSmall.click();
const StrokeWidthPillSmall = firstStrokeRow.getByRole("button", {
name: "width-small",
});
await expect(StrokeWidthPillSmall).toBeVisible();
});
}); });

View File

@@ -17,7 +17,6 @@
--app-background: var(--color-background-primary); --app-background: var(--color-background-primary);
--loader-background: var(--color-background-primary); --loader-background: var(--color-background-primary);
--panel-title-background-color: var(--color-background-secondary);
// BUTTONS // BUTTONS
--button-foreground-hover: var(--color-accent-primary); --button-foreground-hover: var(--color-accent-primary);

View File

@@ -17,17 +17,18 @@
<meta name="twitter:site" content="@penpotapp"> <meta name="twitter:site" content="@penpotapp">
<meta name="twitter:creator" content="@penpotapp"> <meta name="twitter:creator" content="@penpotapp">
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
<link id="theme" href="css/main.css?version={{& version}}" rel="stylesheet" type="text/css" /> <link id="theme" href="css/main.css?version={{& version_tag}}" rel="stylesheet" type="text/css" />
{{#isDebug}} {{#isDebug}}
<link href="css/debug.css?version={{& version}}" rel="stylesheet" type="text/css" /> <link href="css/debug.css?version={{& version_tag}}" rel="stylesheet" type="text/css" />
{{/isDebug}} {{/isDebug}}
<link rel="icon" href="images/favicon.png" /> <link rel="icon" href="images/favicon.png?version={{& version_tag }}" />
<script type="importmap">{{& manifest.importmap }}</script> <script type="importmap">{{& manifest.importmap }}</script>
<script type="module"> <script type="module">
globalThis.penpotVersion = "{{& version}}"; globalThis.penpotVersion = "{{& version}}";
globalThis.penpotVersionTag = "{{& version_tag}}";
globalThis.penpotBuildDate = "{{& build_date}}"; globalThis.penpotBuildDate = "{{& build_date}}";
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}"; globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
</script> </script>

View File

@@ -3,10 +3,11 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Penpot - Rasterizer</title> <title>Penpot - Rasterizer</title>
<link rel="icon" href="images/favicon.png" /> <link rel="icon" href="images/favicon.png?version={{& version_tag }}" />
<script> <script>
globalThis.penpotVersion = "{{& version}}"; globalThis.penpotVersion = "{{& version}}";
globalThis.penpotVersionTag = "{{& version_tag}}";
globalThis.penpotBuildDate = "{{& build_date}}"; globalThis.penpotBuildDate = "{{& build_date}}";
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}"; globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
</script> </script>

View File

@@ -4,10 +4,12 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" /> <meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Penpot - Render</title> <title>Penpot - Render</title>
<link rel="icon" href="images/favicon.png" />
<link rel="icon" href="images/favicon.png?version={{& version_tag }}" />
<script> <script>
globalThis.penpotVersion = "{{& version}}"; globalThis.penpotVersion = "{{& version}}";
globalThis.penpotVersionTag = "{{& version_tag}}";
globalThis.penpotBuildDate = "{{& build_date}}"; globalThis.penpotBuildDate = "{{& build_date}}";
</script> </script>

View File

@@ -27,9 +27,11 @@ export function startWorker() {
}); });
} }
export const isDebug = process.env.NODE_ENV !== "production"; export const IS_DEBUG = process.env.NODE_ENV !== "production";
export const CURRENT_VERSION = process.env.CURRENT_VERSION || "develop"; export const BUILD_DATE = process.env.BUILD_DATE || (new Date().toString()) ;
export const BUILD_DATE = process.env.BUILD_DATE || "" + new Date(); export const BUILD_TS = process.env.BUILD_TS || Date.now();
export const VERSION = process.env.VERSION || "develop";
export const VERSION_TAG = process.env.VERSION_TAG || VERSION;
async function findFiles(basePath, predicate, options = {}) { async function findFiles(basePath, predicate, options = {}) {
predicate = predicate =
@@ -172,6 +174,7 @@ export async function watch(baseDir, predicate, callback) {
const watcher = new Watcher(baseDir, { const watcher = new Watcher(baseDir, {
persistent: true, persistent: true,
recursive: true, recursive: true,
debounce: 500
}); });
watcher.on("change", (path) => { watcher.on("change", (path) => {
@@ -179,8 +182,19 @@ export async function watch(baseDir, predicate, callback) {
callback(path); callback(path);
} }
}); });
watcher.on("error", (cause) => {
console.log("WATCHER ERROR", cause);
});
} }
export async function ensureDirectories() {
await fs.mkdir("./resources/public/js/worker/", { recursive: true });
await fs.mkdir("./resources/public/css/", { recursive: true });
}
async function readManifestFile(resource) { async function readManifestFile(resource) {
const manifestPath = "resources/public/" + resource; const manifestPath = "resources/public/" + resource;
let content = await fs.readFile(manifestPath, { encoding: "utf8" }); let content = await fs.readFile(manifestPath, { encoding: "utf8" });
@@ -193,25 +207,25 @@ async function generateManifest() {
render_main: "./js/render.js", render_main: "./js/render.js",
rasterizer_main: "./js/rasterizer.js", rasterizer_main: "./js/rasterizer.js",
config: "./js/config.js?version=" + CURRENT_VERSION, config: "./js/config.js?version=" + VERSION_TAG,
polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION, polyfills: "./js/polyfills.js?version=" + VERSION_TAG,
libs: "./js/libs.js?version=" + CURRENT_VERSION, libs: "./js/libs.js?version=" + VERSION_TAG,
worker_main: "./js/worker/main.js?version=" + CURRENT_VERSION, worker_main: "./js/worker/main.js?version=" + VERSION_TAG,
default_translations: "./js/translation.en.js?version=" + CURRENT_VERSION, default_translations: "./js/translation.en.js?version=" + VERSION_TAG,
importmap: JSON.stringify({ importmap: JSON.stringify({
"imports": { "imports": {
"./js/shared.js": "./js/shared.js?version=" + CURRENT_VERSION, "./js/shared.js": "./js/shared.js?version=" + VERSION_TAG,
"./js/main.js": "./js/main.js?version=" + CURRENT_VERSION, "./js/main.js": "./js/main.js?version=" + VERSION_TAG,
"./js/render.js": "./js/render.js?version=" + CURRENT_VERSION, "./js/render.js": "./js/render.js?version=" + VERSION_TAG,
"./js/render-wasm.js": "./js/render-wasm.js?version=" + CURRENT_VERSION, "./js/render-wasm.js": "./js/render-wasm.js?version=" + VERSION_TAG,
"./js/rasterizer.js": "./js/rasterizer.js?version=" + CURRENT_VERSION, "./js/rasterizer.js": "./js/rasterizer.js?version=" + VERSION_TAG,
"./js/main-dashboard.js": "./js/main-dashboard.js?version=" + CURRENT_VERSION, "./js/main-dashboard.js": "./js/main-dashboard.js?version=" + VERSION_TAG,
"./js/main-auth.js": "./js/main-auth.js?version=" + CURRENT_VERSION, "./js/main-auth.js": "./js/main-auth.js?version=" + VERSION_TAG,
"./js/main-viewer.js": "./js/main-viewer.js?version=" + CURRENT_VERSION, "./js/main-viewer.js": "./js/main-viewer.js?version=" + VERSION_TAG,
"./js/main-settings.js": "./js/main-settings.js?version=" + CURRENT_VERSION, "./js/main-settings.js": "./js/main-settings.js?version=" + VERSION_TAG,
"./js/main-workspace.js": "./js/main-workspace.js?version=" + CURRENT_VERSION, "./js/main-workspace.js": "./js/main-workspace.js?version=" + VERSION_TAG,
"./js/util-highlight.js": "./js/util-highlight.js?version=" + CURRENT_VERSION "./js/util-highlight.js": "./js/util-highlight.js?version=" + VERSION_TAG
} }
}) })
}; };
@@ -222,11 +236,12 @@ async function generateManifest() {
async function renderTemplate(path, context = {}, partials = {}) { async function renderTemplate(path, context = {}, partials = {}) {
const content = await fs.readFile(path, { encoding: "utf-8" }); const content = await fs.readFile(path, { encoding: "utf-8" });
const ts = Math.floor(new Date());
context = Object.assign({}, context, { context = Object.assign({}, context, {
ts: ts, isDebug: IS_DEBUG,
isDebug, version: VERSION,
version_tag: VERSION_TAG,
build_date: BUILD_DATE,
build_ts: BUILD_TS,
}); });
return mustache.render(content, context, partials); return mustache.render(content, context, partials);
@@ -257,6 +272,9 @@ const markedOptions = {
marked.use(markedOptions); marked.use(markedOptions);
export async function compileTranslations() { export async function compileTranslations() {
const outputDir = "resources/public/js/";
await fs.mkdir(outputDir, { recursive: true });
const langs = [ const langs = [
"ar", "ar",
"ca", "ca",
@@ -338,7 +356,6 @@ export async function compileTranslations() {
} }
const esm = `export default ${JSON.stringify(result, null, 0)};\n`; const esm = `export default ${JSON.stringify(result, null, 0)};\n`;
const outputDir = "resources/public/js/";
const outputFile = ph.join(outputDir, "translation." + lang + ".js"); const outputFile = ph.join(outputDir, "translation." + lang + ".js");
await fs.writeFile(outputFile, esm); await fs.writeFile(outputFile, esm);
} }
@@ -390,7 +407,6 @@ async function generateSvgSprites() {
} }
async function generateTemplates() { async function generateTemplates() {
const isDebug = process.env.NODE_ENV !== "production";
await fs.mkdir("./resources/public/", { recursive: true }); await fs.mkdir("./resources/public/", { recursive: true });
const manifest = await generateManifest(); const manifest = await generateManifest();
@@ -415,10 +431,7 @@ async function generateTemplates() {
}; };
const context = { const context = {
manifest: manifest, manifest: manifest
version: CURRENT_VERSION,
build_date: BUILD_DATE,
isDebug,
}; };
content = await renderTemplate( content = await renderTemplate(
@@ -487,7 +500,7 @@ export async function compileStyles() {
await fs.mkdir("./resources/public/css", { recursive: true }); await fs.mkdir("./resources/public/css", { recursive: true });
await fs.writeFile("./resources/public/css/main.css", result); await fs.writeFile("./resources/public/css/main.css", result);
if (isDebug) { if (IS_DEBUG) {
let debugCSS = await compileSassDebug(worker); let debugCSS = await compileSassDebug(worker);
await fs.writeFile("./resources/public/css/debug.css", debugCSS); await fs.writeFile("./resources/public/css/debug.css", debugCSS);
} }
@@ -500,18 +513,44 @@ export async function compileStyles() {
export async function compileSvgSprites() { export async function compileSvgSprites() {
const start = process.hrtime(); const start = process.hrtime();
log.info("init: compile svgsprite"); log.info("init: compile svgsprite");
let error = false;
try {
await generateSvgSprites(); await generateSvgSprites();
} catch (cause) {
error = cause;
}
const end = process.hrtime(start); const end = process.hrtime(start);
if (error) {
log.error("error: compile svgsprite", `(${ppt(end)})`);
console.error(error);
} else {
log.info("done: compile svgsprite", `(${ppt(end)})`); log.info("done: compile svgsprite", `(${ppt(end)})`);
} }
}
export async function compileTemplates() { export async function compileTemplates() {
const start = process.hrtime(); const start = process.hrtime();
let error = false;
log.info("init: compile templates"); log.info("init: compile templates");
try {
await generateTemplates(); await generateTemplates();
} catch (cause) {
error = cause;
}
const end = process.hrtime(start); const end = process.hrtime(start);
if (error) {
log.error("error: compile templates", `(${ppt(end)})`);
console.error(error);
} else {
log.info("done: compile templates", `(${ppt(end)})`); log.info("done: compile templates", `(${ppt(end)})`);
} }
}
export async function compilePolyfills() { export async function compilePolyfills() {
const start = process.hrtime(); const start = process.hrtime();

View File

@@ -28,14 +28,12 @@ async function compileFile(path) {
], ],
sourceMap: false, sourceMap: false,
}); });
// console.dir(result);
resolve({ resolve({
inputPath: path, inputPath: path,
outputPath: dest, outputPath: dest,
css: result.css, css: result.css,
}); });
} catch (cause) { } catch (cause) {
console.error(cause);
reject(cause); reject(cause);
} }
}); });

View File

@@ -2,26 +2,26 @@
# NOTE: this script should be called from the parent directory to # NOTE: this script should be called from the parent directory to
# properly work. # properly work.
set -ex
export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no}; export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no};
export INCLUDE_WASM=${BUILD_WASM:-yes}; export INCLUDE_WASM=${BUILD_WASM:-yes};
export CURRENT_VERSION=$1;
export BUILD_DATE=$(date -R);
export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)};
export EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS; export EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS;
export TS=$(date +%s);
export BUILD_DATE=$(date -R);
export BUILD_TS=$(date +%s);
export VERSION=${1:-develop};
export VERSION_TAG="${VERSION}-${BUILD_TS}";
# Some cljs reacts on this environment variable for define more # Some cljs reacts on this environment variable for define more
# performant code on macros (example: rumext) # performant code on macros (example: rumext)
export NODE_ENV=production; export NODE_ENV=production;
echo "Current path:"
echo $PATH
set -ex
corepack enable; corepack enable;
corepack install; corepack install;
yarn install || exit 1; yarn install;
rm -rf target/dist; rm -rf target/dist;
rm -rf resources/public; rm -rf resources/public;
@@ -37,7 +37,7 @@ yarn run build:app:main $EXTRA_PARAMS;
yarn run build:app:libs; yarn run build:app:libs;
yarn run build:app:assets; yarn run build:app:assets;
sed -i "s/\.\/render.js/.\/render.js?version=$CURRENT_VERSION/g" resources/public/js/worker/main*.js sed -i "s/\.\/render.js/.\/render.js?version=$VERSION_TAG/g" resources/public/js/worker/main*.js
rsync -avr resources/public/ target/dist/ rsync -avr resources/public/ target/dist/

View File

@@ -1,5 +1,6 @@
import * as h from "./_helpers.js"; import * as h from "./_helpers.js";
await h.ensureDirectories();
await h.compileStyles(); await h.compileStyles();
await h.copyAssets(); await h.copyAssets();
await h.copyWasmPlayground(); await h.copyWasmPlayground();

View File

@@ -2,18 +2,16 @@
# NOTE: this script should be called from the parent directory to # NOTE: this script should be called from the parent directory to
# properly work. # properly work.
export CURRENT_VERSION=$1; set -ex
export BUILD_TS=$(date +%s);
export BUILD_DATE=$(date -R); export BUILD_DATE=$(date -R);
export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)};
export TS=$(date +%s); export VERSION=${1:-develop};
export VERSION_TAG="${VERSION}-${BUILD_TS}";
export NODE_ENV=production; export NODE_ENV=production;
echo "Current path:"
echo $PATH
set -ex
corepack enable; corepack enable;
corepack install || exit 1; corepack install || exit 1;
yarn install || exit 1; yarn install || exit 1;

View File

@@ -12,8 +12,11 @@ let sass = null;
async function compileSassAll() { async function compileSassAll() {
const start = process.hrtime(); const start = process.hrtime();
let error = false;
log.info("init: compile styles"); log.info("init: compile styles");
try {
sass = await h.compileSassAll(worker); sass = await h.compileSassAll(worker);
let output = await h.concatSass(sass); let output = await h.concatSass(sass);
await fs.writeFile("./resources/public/css/main.css", output); await fs.writeFile("./resources/public/css/main.css", output);
@@ -22,10 +25,19 @@ async function compileSassAll() {
let debugCSS = await h.compileSassDebug(worker); let debugCSS = await h.compileSassDebug(worker);
await fs.writeFile("./resources/public/css/debug.css", debugCSS); await fs.writeFile("./resources/public/css/debug.css", debugCSS);
} }
} catch (cause) {
error = cause;
}
const end = process.hrtime(start); const end = process.hrtime(start);
if (error) {
log.error("error: compile styles", `(${ppt(end)})`);
console.error(error);
} else {
log.info("done: compile styles", `(${ppt(end)})`); log.info("done: compile styles", `(${ppt(end)})`);
} }
}
async function compileSass(path) { async function compileSass(path) {
const start = process.hrtime(); const start = process.hrtime();
@@ -48,7 +60,7 @@ async function compileSass(path) {
} }
} }
await fs.mkdir("./resources/public/css/", { recursive: true }); await h.ensureDirectories();
await compileSassAll(); await compileSassAll();
await h.copyAssets(); await h.copyAssets();
await h.copyWasmPlayground(); await h.copyWasmPlayground();

View File

@@ -95,6 +95,7 @@
(def browser (parse-browser)) (def browser (parse-browser))
(def platform (parse-platform)) (def platform (parse-platform))
(def version-tag (obj/get global "penpotVersionTag"))
(def terms-of-service-uri (obj/get global "penpotTermsOfServiceURI")) (def terms-of-service-uri (obj/get global "penpotTermsOfServiceURI"))
(def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI")) (def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI"))
(def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/")) (def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/"))
@@ -190,9 +191,8 @@
(defn resolve-href (defn resolve-href
[resource] [resource]
(let [version (get version :full) (let [href (-> public-uri
href (-> public-uri
(u/ensure-path-slash) (u/ensure-path-slash)
(u/join resource) (u/join resource)
(get :path))] (get :path))]
(str href "?version=" version))) (str href "?version=" version-tag)))

View File

@@ -27,8 +27,10 @@
[app.main.data.workspace.colors :as wdc] [app.main.data.workspace.colors :as wdc]
[app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.transforms :as dwtr] [app.main.data.workspace.transforms :as dwtr]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.store :as st] [app.main.store :as st]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
@@ -191,6 +193,16 @@
(when (:fill attributes) (update-fill value shape-ids attributes page-id)) (when (:fill attributes) (update-fill value shape-ids attributes page-id))
(when (:stroke-color attributes) (update-stroke-color value shape-ids attributes page-id))))))) (when (:stroke-color attributes) (update-stroke-color value shape-ids attributes page-id)))))))
(defn update-shape-dimensions
([value shape-ids attributes] (update-shape-dimensions value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::update-shape-dimensions
ptk/WatchEvent
(watch [_ _ _]
(when (number? value)
(rx/of
(when (:width attributes) (dwtr/update-dimensions shape-ids :width value {:ignore-touched true :page-id page-id}))
(when (:height attributes) (dwtr/update-dimensions shape-ids :height value {:ignore-touched true :page-id page-id}))))))))
(defn- attributes->layout-gap [attributes value] (defn- attributes->layout-gap [attributes value]
(let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap}) (let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap})
@@ -238,6 +250,21 @@
{:ignore-touched true {:ignore-touched true
:page-id page-id})))))))) :page-id page-id}))))))))
(defn update-layout-spacing
([value shape-ids attributes] (update-layout-spacing value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::update-layout-spacing
ptk/WatchEvent
(watch [_ state _]
(when (number? value)
(let [ids-with-layout (shape-ids-with-layout state (or page-id (:current-page-id state)) shape-ids)
layout-attributes (attributes->layout-gap attributes value)]
(rx/of
(dwsl/update-layout ids-with-layout
layout-attributes
{:ignore-touched true
:page-id page-id}))))))))
(defn update-shape-position (defn update-shape-position
([value shape-ids attributes] (update-shape-position value shape-ids attributes nil)) ([value shape-ids attributes] (update-shape-position value shape-ids attributes nil))
([value shape-ids attributes page-id] ([value shape-ids attributes page-id]
@@ -251,20 +278,6 @@
{:ignore-touched true {:ignore-touched true
:page-id page-id}))))))))) :page-id page-id})))))))))
(defn update-layout-gap
[value shape-ids attributes page-id]
(ptk/reify ::update-layout-gao
ptk/WatchEvent
(watch [_ state _]
(when (number? value)
(let [ids-with-layout (shape-ids-with-layout state (or page-id (:current-page-id state)) shape-ids)
layout-attributes (attributes->layout-gap attributes value)]
(rx/of
(dwsl/update-layout ids-with-layout
layout-attributes
{:ignore-touched true
:page-id page-id})))))))
(defn update-layout-sizing-limits (defn update-layout-sizing-limits
([value shape-ids attributes] (update-layout-sizing-limits value shape-ids attributes nil)) ([value shape-ids attributes] (update-layout-sizing-limits value shape-ids attributes nil))
([value shape-ids attributes page-id] ([value shape-ids attributes page-id]
@@ -289,11 +302,20 @@
update-fn (fn [node _] update-fn (fn [node _]
(-> node (-> node
(d/txt-merge txt-attrs) (d/txt-merge txt-attrs)
(cty/remove-typography-from-node)))] (cty/remove-typography-from-node)))
(dwsh/update-shapes shape-ids ;; Check if any attribute affects text layout (requires resize)
affects-layout? (some #(contains? txt-attrs %) [:font-size :font-family :font-weight :letter-spacing :line-height])]
(ptk/reify ::generate-text-shape-update
ptk/WatchEvent
(watch [_ state _]
(cond-> (rx/of (dwsh/update-shapes shape-ids
#(txt/update-text-content % update-node? update-fn nil) #(txt/update-text-content % update-node? update-fn nil)
{:ignore-touched true {:ignore-touched true
:page-id page-id}))) :page-id page-id}))
(and affects-layout?
(features/active-feature? state "render-wasm/v1"))
(rx/merge
(rx/of (dwt/resize-wasm-text-all shape-ids))))))))
(defn update-line-height (defn update-line-height
([value shape-ids attributes] (update-line-height value shape-ids attributes nil)) ([value shape-ids attributes] (update-line-height value shape-ids attributes nil))
@@ -342,11 +364,17 @@
(-> node (-> node
(d/txt-merge txt-attrs) (d/txt-merge txt-attrs)
(cty/remove-typography-from-node))))] (cty/remove-typography-from-node))))]
(dwsh/update-shapes shape-ids (ptk/reify ::generate-font-family-text-shape-update
ptk/WatchEvent
(watch [_ state _]
(cond-> (rx/of (dwsh/update-shapes shape-ids
(fn [shape] (fn [shape]
(txt/update-text-content shape update-node? #(update-fn %1 (ctst/font-weight-applied? shape)) nil)) (txt/update-text-content shape update-node? #(update-fn %1 (ctst/font-weight-applied? shape)) nil))
{:ignore-touched true {:ignore-touched true
:page-id page-id}))) :page-id page-id}))
(features/active-feature? state "render-wasm/v1")
(rx/merge
(rx/of (dwt/resize-wasm-text-all shape-ids))))))))
(defn- create-font-family-text-attrs (defn- create-font-family-text-attrs
[value] [value]
@@ -414,10 +442,16 @@
(-> node (-> node
(d/txt-merge txt-attrs) (d/txt-merge txt-attrs)
(cty/remove-typography-from-node))))] (cty/remove-typography-from-node))))]
(dwsh/update-shapes shape-ids (ptk/reify ::generate-font-weight-text-shape-update
ptk/WatchEvent
(watch [_ state _]
(cond-> (rx/of (dwsh/update-shapes shape-ids
#(txt/update-text-content % update-node? update-fn nil) #(txt/update-text-content % update-node? update-fn nil)
{:ignore-touched true {:ignore-touched true
:page-id page-id}))) :page-id page-id}))
(features/active-feature? state "render-wasm/v1")
(rx/merge
(rx/of (dwt/resize-wasm-text-all shape-ids))))))))
(defn update-font-weight (defn update-font-weight
([value shape-ids attributes] (update-font-weight value shape-ids attributes nil)) ([value shape-ids attributes] (update-font-weight value shape-ids attributes nil))
@@ -459,126 +493,20 @@
value value
[shape-ids attributes page-id]))))) [shape-ids attributes page-id])))))
(defn update-shape-dimensions (defn update-typography-interactive
([value shape-ids attributes] (update-shape-dimensions value shape-ids attributes nil)) ([value shape-ids attributes] (update-typography value shape-ids attributes nil))
([value shape-ids attributes page-id] ([value shape-ids attributes page-id]
(ptk/reify ::update-shape-dimensions (when (map? value)
ptk/WatchEvent (rx/merge
(watch [_ _ _] (apply-functions-map
(when (number? value) {:font-size update-font-size
(rx/of :font-family update-font-family-interactive
(when (:width attributes) (dwtr/update-dimensions shape-ids :width value {:ignore-touched true :page-id page-id})) :font-weight update-font-weight-interactive
(when (:height attributes) (dwtr/update-dimensions shape-ids :height value {:ignore-touched true :page-id page-id})))))))) :letter-spacing update-letter-spacing
:text-case update-text-case
(defn- attributes->actions :text-decoration update-text-decoration-interactive}
[{:keys [value shape-ids attributes page-id]}] value
(cond-> [] [shape-ids attributes page-id])))))
(some attributes #{:width :height})
(conj #(update-shape-dimensions
value shape-ids
(set (filter attributes #{:width :height}))
page-id))
(some attributes #{:x :y})
(conj #(update-shape-position
value shape-ids
(set (filter attributes #{:x :y}))
page-id))
(some attributes #{:p1 :p2 :p3 :p4})
(conj #(update-layout-padding
value shape-ids
(set (filter attributes #{:p1 :p2 :p3 :p4}))
page-id))
(some attributes #{:m1 :m2 :m3 :m4})
(conj #(update-layout-item-margin
value shape-ids
(set (filter attributes #{:m1 :m2 :m3 :m4}))
page-id))
(some attributes #{:row-gap :column-gap})
(conj #(update-layout-gap
value shape-ids
(set (filter attributes #{:row-gap :column-gap}))
page-id))
(some attributes #{:r1 :r2 :r3 :r4})
(conj #(if (= attributes #{:r1 :r2 :r3 :r4})
(update-shape-radius-all value shape-ids attributes page-id)
(update-shape-radius-for-corners
value shape-ids
(set (filter attributes #{:r1 :r2 :r3 :r4}))
page-id)))
(some attributes #{:strole-width})
(conj #(update-stroke-width
value shape-ids
#{:strole-width}
page-id))
(some attributes #{:max-width :max-height})
(conj #(update-layout-sizing-limits
value shape-ids
(set (filter attributes #{:max-width :max-height}))
page-id))))
(defn use-dimensions-token
([value shape-ids attributes] (use-dimensions-token value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::use-dimensions-token
ptk/WatchEvent
(watch [_ state _]
(when (number? value)
(let [actions (attributes->actions
{:value value
:shape-ids shape-ids
:attributes attributes
:page-id page-id
:state state})]
(apply rx/of (map #(%) actions))))))))
(defn use-spacing-token
([value shape-ids attributes] (use-spacing-token value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::use-spacing-token
ptk/WatchEvent
(watch [_ state _]
(let [spacing-attrs
#{:row-gap :column-gap
:m1 :m2 :m3 :m4
:p1 :p2 :p3 :p4}]
(when (and (number? value)
(set? attributes)
(set/subset? attributes spacing-attrs))
(let [actions (attributes->actions
{:value value
:shape-ids shape-ids
:attributes attributes
:page-id page-id
:state state})]
(apply rx/of (map #(%) actions)))))))))
(defn use-sizing-token
([value shape-ids attributes] (use-sizing-token value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::use-sizing-token
ptk/WatchEvent
(watch [_ state _]
(let [sizing-attrs
#{:width :height
:max-width :max-height}]
(when (and (number? value)
(set? attributes)
(set/subset? attributes sizing-attrs))
(let [actions (attributes->actions
{:value value
:shape-ids shape-ids
:attributes attributes
:page-id page-id
:state state})]
(apply rx/of (map #(%) actions)))))))))
;; Events to apply / unapply tokens to shapes ------------------------------------------------------------ ;; Events to apply / unapply tokens to shapes ------------------------------------------------------------
@@ -718,19 +646,54 @@
:token token :token token
:shape-ids shape-ids})) :shape-ids shape-ids}))
(rx/of (rx/of
(cond (case (:type token)
(and (= (:type token) :spacing) :spacing
(nil? attrs))
(apply-spacing-token {:token token (apply-spacing-token {:token token
:attr attrs :attr attrs
:shapes shapes}) :shapes shapes})
:else
(apply-token {:attributes (if (empty? attrs) attributes attrs) (apply-token {:attributes (if (empty? attrs) attributes attrs)
:token token :token token
:shape-ids shape-ids :shape-ids shape-ids
:on-update-shape on-update-shape})))))))) :on-update-shape on-update-shape}))))))))
(defn toggle-border-radius-token
[{:keys [token attrs shape-ids expand-with-children]}]
(ptk/reify ::on-toggle-border-radius-token
ptk/WatchEvent
(watch [_ state _]
(let [objects (dsh/lookup-page-objects state)
shapes (into [] (keep (d/getf objects)) shape-ids)
shapes
(if expand-with-children
(into []
(mapcat (fn [shape]
(if (= (:type shape) :group)
(keep objects (:shapes shape))
[shape])))
shapes)
shapes)
{:keys [attributes all-attributes]}
(get token-properties (:type token))
unapply-tokens?
(cft/shapes-token-applied? token shapes (or attrs all-attributes attributes))
shape-ids (map :id shapes)]
(if unapply-tokens?
(rx/of
(unapply-token {:attributes (or attrs all-attributes attributes)
:token token
:shape-ids shape-ids}))
(rx/of
(apply-token {:attributes attrs
:token token
:shape-ids shape-ids
:on-update-shape update-shape-radius-for-corners})))))))
(defn apply-token-on-selected (defn apply-token-on-selected
[color-operations token] [color-operations token]
(ptk/reify ::apply-token-on-selected (ptk/reify ::apply-token-on-selected
@@ -860,7 +823,7 @@
{:title "Sizing" {:title "Sizing"
:attributes #{:width :height} :attributes #{:width :height}
:all-attributes ctt/sizing-keys :all-attributes ctt/sizing-keys
:on-update-shape use-sizing-token :on-update-shape update-shape-dimensions
:modal {:key :tokens/sizing :modal {:key :tokens/sizing
:fields [{:label "Sizing" :fields [{:label "Sizing"
:key :sizing}]}} :key :sizing}]}}
@@ -873,7 +836,7 @@
ctt/border-radius-keys ctt/border-radius-keys
ctt/axis-keys ctt/axis-keys
ctt/stroke-width-keys) ctt/stroke-width-keys)
:on-update-shape use-dimensions-token :on-update-shape update-shape-dimensions
:modal {:key :tokens/dimensions :modal {:key :tokens/dimensions
:fields [{:label "Dimensions" :fields [{:label "Dimensions"
:key :dimensions}]}} :key :dimensions}]}}
@@ -906,7 +869,7 @@
{:title "Spacing" {:title "Spacing"
:attributes #{:column-gap :row-gap} :attributes #{:column-gap :row-gap}
:all-attributes ctt/spacing-keys :all-attributes ctt/spacing-keys
:on-update-shape use-spacing-token :on-update-shape update-layout-spacing
:modal {:key :tokens/spacing :modal {:key :tokens/spacing
:fields [{:label "Spacing" :fields [{:label "Spacing"
:key :spacing}]}})) :key :spacing}]}}))

View File

@@ -54,7 +54,7 @@
{ctt/border-radius-keys dwta/update-shape-radius-for-corners {ctt/border-radius-keys dwta/update-shape-radius-for-corners
ctt/color-keys dwta/update-fill-stroke ctt/color-keys dwta/update-fill-stroke
ctt/stroke-width-keys dwta/update-stroke-width ctt/stroke-width-keys dwta/update-stroke-width
ctt/sizing-keys dwta/use-dimensions-token ctt/sizing-keys dwta/update-shape-dimensions
ctt/opacity-keys dwta/update-opacity ctt/opacity-keys dwta/update-opacity
ctt/rotation-keys dwta/update-rotation ctt/rotation-keys dwta/update-rotation
@@ -73,8 +73,8 @@
#{:x :y} dwta/update-shape-position #{:x :y} dwta/update-shape-position
#{:p1 :p2 :p3 :p4} dwta/update-layout-padding #{:p1 :p2 :p3 :p4} dwta/update-layout-padding
#{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin #{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin
#{:column-gap :row-gap} dwta/update-layout-gap #{:column-gap :row-gap} dwta/update-layout-spacing
#{:width :height} dwta/use-dimensions-token #{:width :height} dwta/update-shape-dimensions
#{:layout-item-min-w :layout-item-min-h :layout-item-max-w :layout-item-max-h} dwta/update-layout-sizing-limits}) #{:layout-item-min-w :layout-item-min-h :layout-item-max-w :layout-item-max-h} dwta/update-layout-sizing-limits})
(def ^:private attribute-actions-map (def ^:private attribute-actions-map

View File

@@ -483,6 +483,9 @@
(def workspace-active-theme-paths (def workspace-active-theme-paths
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))
(def workspace-all-tokens-map
(l/derived (d/nilf ctob/get-all-tokens-map) tokens-lib))
(defn token-sets-at-path-all-active (defn token-sets-at-path-all-active
[group-path] [group-path]
(l/derived (l/derived

View File

@@ -193,11 +193,11 @@
restore-fn restore-fn
(fn [_] (fn [_]
(st/emit! (dd/restore-files-immediately (st/emit! (dd/restore-files-immediately
(with-meta {:team-id (:id current-team) (with-meta {:team-id current-team-id
:ids (into #{} d/xf:map-id files)} :ids (into #{} d/xf:map-id files)}
{:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file))) {:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file)))
(dd/fetch-projects (:id current-team)) (dd/fetch-projects current-team-id)
(dd/fetch-deleted-files (:id current-team))) (dd/fetch-deleted-files current-team-id))
:on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))})))) :on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))}))))
on-restore-immediately on-restore-immediately
@@ -214,7 +214,7 @@
on-delete-immediately on-delete-immediately
(fn [] (fn []
(let [accept-fn #(st/emit! (dd/delete-files-immediately (let [accept-fn #(st/emit! (dd/delete-files-immediately
{:team-id (:id current-team) {:team-id current-team-id
:ids (into #{} d/xf:map-id files)}))] :ids (into #{} d/xf:map-id files)}))]
(st/emit! (st/emit!
(modal/show {:type :confirm (modal/show {:type :confirm
@@ -244,8 +244,7 @@
(for [project current-projects] (for [project current-projects]
{:name (get-project-name project) {:name (get-project-name project)
:id (get-project-id project) :id (get-project-id project)
:handler (on-move (:id current-team) :handler (on-move current-team-id (:id project))})
(:id project))})
(when (seq other-teams) (when (seq other-teams)
[{:name (tr "dashboard.move-to-other-team") [{:name (tr "dashboard.move-to-other-team")
:id "move-to-other-team" :id "move-to-other-team"

View File

@@ -38,6 +38,7 @@
[app.main.ui.ds.product.loader :refer [loader*]] [app.main.ui.ds.product.loader :refer [loader*]]
[app.main.ui.ds.product.milestone :refer [milestone*]] [app.main.ui.ds.product.milestone :refer [milestone*]]
[app.main.ui.ds.product.milestone-group :refer [milestone-group*]] [app.main.ui.ds.product.milestone-group :refer [milestone-group*]]
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
[app.main.ui.ds.storybook :as sb] [app.main.ui.ds.storybook :as sb]
[app.main.ui.ds.tooltip.tooltip :refer [tooltip*]] [app.main.ui.ds.tooltip.tooltip :refer [tooltip*]]
[app.main.ui.ds.utilities.date :refer [date*]] [app.main.ui.ds.utilities.date :refer [date*]]
@@ -81,6 +82,7 @@
:Milestone milestone* :Milestone milestone*
:MilestoneGroup milestone-group* :MilestoneGroup milestone-group*
:Date date* :Date date*
:PanelTitle panel-title*
:set-default-translations :set-default-translations
(fn [data] (fn [data]

View File

@@ -0,0 +1,34 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.product.panel-title
(:require-macros
[app.main.style :as stl])
(:require
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(def ^:private schema:panel-title
[:map
[:class {:optional true} :string]
[:text :string]
[:on-close {:optional true} fn?]])
(mf/defc panel-title*
{::mf/schema schema:panel-title}
[{:keys [class text on-close] :rest props}]
(let [props
(mf/spread-props props {:class [class (stl/css :panel-title)]})]
[:> :div props
[:span {:class (stl/css :panel-title-text)} text]
(when on-close
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.close")
:on-click on-close
:icon i/close}])]))

View File

@@ -0,0 +1,26 @@
{ /* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (c) KALEIDOS INC */ }
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import * as PanelTitle from "./panel_title.stories";
<Meta title="Product/PanelTitle" />
# PanelTitle
The `panel-title*` is used as a header for some sidebar sections.
<Canvas of={PanelTitle.Default} />
## Technical notes
The only mandatory parameter is `text`. Usually you'll want to pass a function property `on-close` that will be called when the user clicks on the close button on the right.
```clj
[:> panel-title* {:class class
:text text
:on-close on-close}]
```

View File

@@ -0,0 +1,25 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "ds/_sizes.scss" as *;
@use "ds/_borders.scss" as *;
@use "ds/typography.scss" as t;
.panel-title {
display: flex;
align-items: center;
justify-content: center;
block-size: $sz-32;
border-radius: $br-8;
background-color: var(--color-background-secondary);
}
.panel-title-text {
@include t.use-typography("headline-small");
flex-grow: 1;
text-align: center;
color: var(--color-foreground-primary);
}

View File

@@ -0,0 +1,21 @@
import * as React from "react";
import Components from "@target/components";
const { PanelTitle } = Components;
export default {
title: "Product/PanelTitle",
component: PanelTitle,
argTypes: {
text: {
control: { type: "text" },
},
},
args: {
text: "Lorem ipsum",
onClose: () => null,
},
render: ({ ...args }) => <PanelTitle {...args} />,
};
export const Default = {};

View File

@@ -23,6 +23,7 @@
touched? (and (contains? (:data @form) input-name) touched? (and (contains? (:data @form) input-name)
(get-in @form [:touched input-name])) (get-in @form [:touched input-name]))
error (get-in @form [:errors input-name]) error (get-in @form [:errors input-name])
value (get-in @form [:data input-name] "") value (get-in @form [:data input-name] "")
@@ -52,7 +53,8 @@
(let [form (mf/use-ctx context) (let [form (mf/use-ctx context)
disabled? (or (and (some? form) disabled? (or (and (some? form)
(or (not (:valid @form)) (or (not (:valid @form))
(seq (:external-errors @form)))) (seq (:async-errors @form))
(seq (:extra-errors @form))))
(true? disabled)) (true? disabled))
handle-key-down-save handle-key-down-save
(mf/use-fn (mf/use-fn

View File

@@ -16,9 +16,9 @@
[app.main.ui.comments :as cmt] [app.main.ui.comments :as cmt]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.product.empty-state :refer [empty-state*]] [app.main.ui.ds.product.empty-state :refer [empty-state*]]
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@@ -123,13 +123,10 @@
[:div {:class (stl/css-case :comments-section true [:div {:class (stl/css-case :comments-section true
:from-viewer from-viewer)} :from-viewer from-viewer)}
[:div {:class (stl/css-case :comments-section-title true
:viewer-title from-viewer)} [:> panel-title* {:class (stl/css :comments-title)
[:span (tr "labels.comments")] :text (tr "labels.comments")
[:> icon-button* {:variant "ghost" :on-close close-section}]
:aria-label (tr "labels.close")
:on-click close-section
:icon i/close}]]
[:button {:class (stl/css :mode-dropdown-wrapper) [:button {:class (stl/css :mode-dropdown-wrapper)
:on-click toggle-mode-selector} :on-click toggle-mode-selector}

View File

@@ -18,25 +18,8 @@
padding: 0 deprecated.$s-8; padding: 0 deprecated.$s-8;
} }
.comments-section-title { .comments-title {
@include deprecated.flexCenter; margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
min-height: deprecated.$s-32;
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
border-radius: deprecated.$br-8;
background-color: var(--panel-title-background-color);
span {
@include deprecated.flexCenter;
flex-grow: 1;
color: var(--title-foreground-color-hover);
}
}
.viewer-title {
margin: 0;
margin-block-start: deprecated.$s-8;
} }
.mode-dropdown-wrapper { .mode-dropdown-wrapper {

View File

@@ -11,12 +11,11 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.product.panel-title :refer [panel-title*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.icons :as deprecated-icon]
[app.util.debug :as dbg] [app.util.debug :as dbg]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :refer [tr]]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc debug-panel* (mf/defc debug-panel*
@@ -35,12 +34,9 @@
(st/emit! (dw/remove-layout-flag :debug-panel))))] (st/emit! (dw/remove-layout-flag :debug-panel))))]
[:div {:class (dm/str class " " (stl/css :debug-panel))} [:div {:class (dm/str class " " (stl/css :debug-panel))}
[:div {:class (stl/css :panel-title)} [:> panel-title* {:class (stl/css :debug-panel-title)
[:span "Debugging tools"] :text (tr "workspace.debug.title")
[:> icon-button* {:variant "ghost" :on-close handle-close}]
:aria-label (tr "labels.close")
:on-click handle-close
:icon i/close}]]
[:div {:class (stl/css :debug-panel-inner)} [:div {:class (stl/css :debug-panel-inner)}
(for [option (sort-by d/name dbg/options)] (for [option (sort-by d/name dbg/options)]

View File

@@ -12,21 +12,12 @@
background-color: var(--panel-background-color); background-color: var(--panel-background-color);
} }
.panel-title { .debug-panel-title {
@include deprecated.flexCenter; margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
min-height: deprecated.$s-32;
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
border-radius: deprecated.$br-8;
background-color: var(--panel-title-background-color);
span {
@include deprecated.flexCenter;
flex-grow: 1;
color: var(--title-foreground-color-hover);
} }
.debug-panel-inner {
padding: deprecated.$s-16 deprecated.$s-8;
} }
.checkbox-wrapper { .checkbox-wrapper {
@@ -39,7 +30,3 @@
@extend .checkbox-icon; @extend .checkbox-icon;
cursor: pointer; cursor: pointer;
} }
.debug-panel-inner {
padding: deprecated.$s-16 deprecated.$s-8;
}

View File

@@ -13,7 +13,7 @@
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.ds.product.panel-title :refer [panel-title*]]
[debug :as dbg] [debug :as dbg]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@@ -125,11 +125,9 @@
(map (d/getf objects)))] (map (d/getf objects)))]
[:div {:class (stl/css :shape-info)} [:div {:class (stl/css :shape-info)}
[:div {:class (stl/css :shape-info-title)} [:> panel-title* {:class (stl/css :shape-info-title)
[:span "Debug"] :text "Debug"
[:div {:class (stl/css :close-button) :on-close #(dbg/disable! :shape-panel)}]
:on-click #(dbg/disable! :shape-panel)}
deprecated-icon/close]]
(if (empty? selected) (if (empty? selected)
[:div {:class (stl/css :attrs-container)} "No shapes selected"] [:div {:class (stl/css :attrs-container)} "No shapes selected"]

View File

@@ -16,34 +16,7 @@
} }
.shape-info-title { .shape-info-title {
@include deprecated.flexCenter; margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
min-height: deprecated.$s-32;
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
border-radius: deprecated.$br-8;
background-color: var(--panel-title-background-color);
span {
@include deprecated.flexCenter;
flex-grow: 1;
color: var(--title-foreground-color-hover);
}
}
.close-button {
@extend .button-tertiary;
position: absolute;
right: deprecated.$s-2;
top: deprecated.$s-2;
height: deprecated.$s-28;
width: deprecated.$s-28;
border-radius: deprecated.$br-6;
svg {
@extend .button-icon;
stroke: var(--icon-foreground);
}
} }
.attrs-container { .attrs-container {

View File

@@ -13,23 +13,6 @@
background-color: var(--panel-background-color); background-color: var(--panel-background-color);
} }
.history-toolbox-title {
@include deprecated.flexCenter;
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
min-height: deprecated.$s-32;
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
border-radius: deprecated.$br-8;
background-color: var(--panel-title-background-color);
span {
@include deprecated.flexCenter;
flex-grow: 1;
color: var(--title-foreground-color-hover);
}
}
.history-entry-empty { .history-entry-empty {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -192,10 +192,11 @@
(st/emit! (st/emit!
(change-radius (fn [shape] (change-radius (fn [shape]
(ctsr/set-radius-to-all-corners shape value)))) (ctsr/set-radius-to-all-corners shape value))))
(doseq [attr [:r1 :r2 :r3 :r4]]
(st/emit! (st/emit!
(dwta/toggle-token {:token (first value) (dwta/toggle-token {:token (first value)
:attrs #{:r1 :r2 :r3 :r4} :attrs #{attr}
:shape-ids ids}))))) :shape-ids ids}))))))
on-single-radius-change on-single-radius-change
@@ -204,10 +205,9 @@
(fn [value attr] (fn [value attr]
(if (or (string? value) (number? value)) (if (or (string? value) (number? value))
(st/emit! (change-one-radius #(ctsr/set-radius-to-single-corner % attr value) attr)) (st/emit! (change-one-radius #(ctsr/set-radius-to-single-corner % attr value) attr))
(st/emit! (st/emit! (st/emit! (dwta/toggle-border-radius-token {:token (first value)
(dwta/toggle-token {:token (first value)
:attrs #{attr} :attrs #{attr}
:shape-ids ids})))))) :shape-ids ids})))))
on-radius-r1-change #(on-single-radius-change % :r1) on-radius-r1-change #(on-single-radius-change % :r1)
on-radius-r2-change #(on-single-radius-change % :r2) on-radius-r2-change #(on-single-radius-change % :r2)

View File

@@ -369,12 +369,12 @@
(if (or (string? value) (int? value)) (if (or (string? value) (int? value))
(on-change :simple attr value event) (on-change :simple attr value event)
(do (do
(st/emit! (let [resolved-value (:resolved-value (first value))
(dwta/toggle-token {:token (first value) updated-attr (if (= :p1 attr) #{:p1 :p3} #{:p2 :p4})]
:attrs (if (= :p1 attr) (st/emit! (dwta/toggle-token {:token (first value)
#{:p1 :p3} :attrs updated-attr
#{:p2 :p4}) :shape-ids ids}))
:shape-ids ids})))))) (on-change :simple attr resolved-value event))))))
on-detach-token on-detach-token
(mf/use-fn (mf/use-fn
@@ -483,9 +483,11 @@
(if (or (string? value) (int? value)) (if (or (string? value) (int? value))
(on-change :multiple attr value event) (on-change :multiple attr value event)
(do (do
(let [resolved-value (:resolved-value (first value))]
(st/emit! (dwta/toggle-token {:token (first value) (st/emit! (dwta/toggle-token {:token (first value)
:attrs #{attr} :attrs #{attr}
:shape-ids ids})))))) :shape-ids ids}))
(on-change :multiple attr resolved-value event))))))
on-focus on-focus
(mf/use-fn (mf/use-fn
@@ -714,12 +716,11 @@
(if (or (string? value) (int? value)) (if (or (string? value) (int? value))
(on-change (= "nowrap" wrap-type) attr value event) (on-change (= "nowrap" wrap-type) attr value event)
(do (do
(st/emit! (let [resolved-value (:resolved-value (first value))]
(dwta/toggle-token {:token (first value) (st/emit! (dwta/toggle-token {:token (first value)
:attrs (if (= "nowrap" wrap-type) :attrs #{attr}
#{:row-gap :colum-gap} :shape-ids ids}))
#{attr}) (on-change (= "nowrap" wrap-type) attr resolved-value event))))))
:shape-ids ids}))))))
on-detach-token on-detach-token
(mf/use-fn (mf/use-fn

View File

@@ -284,17 +284,28 @@
(st/emit! (udw/change-orientation ids (keyword orientation))))) (st/emit! (udw/change-orientation ids (keyword orientation)))))
;; SIZE AND PROPORTION LOCK ;; SIZE AND PROPORTION LOCK
do-size-change
(mf/use-fn
(mf/deps ids)
(fn [value attr]
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(udw/update-dimensions ids attr value))))
on-size-change on-size-change
(mf/use-fn (mf/use-fn
(mf/deps ids shapes) (mf/deps ids shapes)
(fn [value attr] (fn [value attr]
(if (or (string? value) (number? value)) (if (or (string? value) (number? value))
(st/emit! (udw/trigger-bounding-box-cloaking ids) (do
(udw/update-dimensions ids attr value)) (st/emit! (udw/trigger-bounding-box-cloaking ids))
(run! #(do-size-change value attr) shapes))
(do
(let [resolved-value (:resolved-value (first value))]
(st/emit! (udw/trigger-bounding-box-cloaking ids) (st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwta/toggle-token {:token (first value) (dwta/toggle-token {:token (first value)
:attrs #{attr} :attrs #{attr}
:shape-ids ids}))))) :shape-ids ids}))
(run! #(do-size-change resolved-value attr) shapes))))))
on-proportion-lock-change on-proportion-lock-change
(mf/use-fn (mf/use-fn
@@ -304,6 +315,11 @@
(run! #(st/emit! (udw/set-shape-proportion-lock % new-lock)) ids)))) (run! #(st/emit! (udw/set-shape-proportion-lock % new-lock)) ids))))
;; POSITION ;; POSITION
do-position-change
(mf/use-fn
(fn [shape' value attr]
(st/emit! (udw/update-position (:id shape') {attr value}))))
on-position-change on-position-change
(mf/use-fn (mf/use-fn
(mf/deps ids) (mf/deps ids)
@@ -311,11 +327,21 @@
(if (or (string? value) (number? value)) (if (or (string? value) (number? value))
(do (do
(st/emit! (udw/trigger-bounding-box-cloaking ids)) (st/emit! (udw/trigger-bounding-box-cloaking ids))
(st/emit! (udw/update-position ids {attr value}))) (run! #(do-position-change %1 value attr) shapes))
(do
(let [resolved-value (:resolved-value (first value))]
(st/emit! (udw/trigger-bounding-box-cloaking ids) (st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwta/toggle-token {:token (first value) (dwta/toggle-token {:token (first value)
:attrs #{attr} :attrs #{attr}
:shape-ids ids}))))) :shape-ids ids}))
(run! #(do-position-change %1 resolved-value attr) shapes))))))
;; ROTATION
do-rotation-change
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (udw/increase-rotation ids value))))
on-rotation-change on-rotation-change
(mf/use-fn (mf/use-fn
@@ -324,11 +350,14 @@
(if (or (string? value) (number? value)) (if (or (string? value) (number? value))
(do (do
(st/emit! (udw/trigger-bounding-box-cloaking ids)) (st/emit! (udw/trigger-bounding-box-cloaking ids))
(st/emit! (udw/increase-rotation ids value))) (run! #(do-rotation-change value) shapes))
(do
(let [resolved-value (:resolved-value (first value))]
(st/emit! (udw/trigger-bounding-box-cloaking ids) (st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwta/toggle-token {:token (first value) (dwta/toggle-token {:token (first value)
:attrs #{:rotation} :attrs #{:rotation}
:shape-ids ids}))))) :shape-ids ids}))
(run! #(do-rotation-change resolved-value) shapes))))))
on-width-change on-width-change
(mf/use-fn (mf/deps on-size-change) #(on-size-change % :width)) (mf/use-fn (mf/deps on-size-change) #(on-size-change % :width))
@@ -381,8 +410,7 @@
(fn [] (fn []
(st/emit! (dwt/selected-fit-content))))] (st/emit! (dwt/selected-fit-content))))]
[:section {:class (stl/css :element-set) [:div {:class (stl/css :element-set)}
:aria-label "shape-measures-section"}
(when (and (options :presets) (when (and (options :presets)
(or (nil? all-types) (= (count all-types) 1))) (or (nil? all-types) (= (count all-types) 1)))
[:div {:class (stl/css :presets)} [:div {:class (stl/css :presets)}

View File

@@ -176,8 +176,7 @@
:token token :token token
:shape-ids ids}))))] :shape-ids ids}))))]
[:section {:class (stl/css :stroke-section) [:div {:class (stl/css :stroke-section)}
:aria-label "stroke-section"}
[:div {:class (stl/css :stroke-title)} [:div {:class (stl/css :stroke-title)}
[:> title-bar* {:collapsable has-strokes? [:> title-bar* {:collapsable has-strokes?
:collapsed (not open?) :collapsed (not open?)

View File

@@ -9,50 +9,18 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.token :as tk]
[app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.application :as dwta]
[app.main.features :as features]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.numeric-input :as deprecated-input] [app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.reorder-handler :refer [reorder-handler*]] [app.main.ui.components.reorder-handler :refer [reorder-handler*]]
[app.main.ui.components.select :refer [select]] [app.main.ui.components.select :refer [select]]
[app.main.ui.context :as muc]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.hooks :as h] [app.main.ui.hooks :as h]
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row*]] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row*]]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc numeric-input-wrapper*
{::mf/private true}
[{:keys [values name applied-tokens align on-detach] :rest props}]
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
tokens (mf/with-memo [tokens name]
(delay
(-> (deref tokens)
(select-keys (get tk/tokens-by-input name))
(not-empty))))
on-detach-attr (mf/use-fn
(mf/deps on-detach name)
#(on-detach % name))
applied-token (get applied-tokens name)
props (mf/spread-props props
{:placeholder (if (= :multiple values)
(tr "settings.multiple")
"--")
:applied-token applied-token
:tokens (if (delay? tokens) @tokens tokens)
:align align
:on-detach on-detach-attr
:name name
:value values})]
[:> numeric-input* props]))
(mf/defc stroke-row* (mf/defc stroke-row*
[{:keys [index [{:keys [index
stroke stroke
@@ -77,10 +45,7 @@
select-on-focus select-on-focus
ids]}] ids]}]
(let [token-numeric-inputs (let [on-drop
(features/use-feature "tokens/numeric-input")
on-drop
(mf/use-fn (mf/use-fn
(mf/deps on-reorder index) (mf/deps on-reorder index)
(fn [relative-pos data] (fn [relative-pos data]
@@ -123,13 +88,7 @@
on-width-change on-width-change
(mf/use-fn (mf/use-fn
(mf/deps index on-stroke-width-change) (mf/deps index on-stroke-width-change)
(fn [value] #(on-stroke-width-change index %))
(if (or (string? value) (int? value))
(on-stroke-width-change index value)
(do
(st/emit! (dwta/toggle-token {:token (first value)
:attrs #{:stroke-width}
:shape-ids ids}))))))
stroke-alignment (or (:stroke-alignment stroke) :center) stroke-alignment (or (:stroke-alignment stroke) :center)
@@ -190,12 +149,6 @@
(fn [token] (fn [token]
(on-detach-token token #{:stroke-color}))) (on-detach-token token #{:stroke-color})))
on-detach-token-width
(mf/use-fn
(mf/deps on-detach-token)
(fn [token]
(on-detach-token (first token) #{:stroke-width})))
stroke-caps-options stroke-caps-options
[{:value nil :label (tr "workspace.options.stroke-cap.none")} [{:value nil :label (tr "workspace.options.stroke-cap.none")}
:separator :separator
@@ -216,8 +169,7 @@
[:div {:class (stl/css-case [:div {:class (stl/css-case
:stroke-data true :stroke-data true
:dnd-over-top (= (:over dprops) :top) :dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot)) :dnd-over-bot (= (:over dprops) :bot))}
:aria-label (str "stroke-row-" index)}
(when (some? on-reorder) (when (some? on-reorder)
[:> reorder-handler* {:ref dref}]) [:> reorder-handler* {:ref dref}])
@@ -243,30 +195,17 @@
;; Stroke Width, Alignment & Style ;; Stroke Width, Alignment & Style
[:div {:class (stl/css :stroke-options)} [:div {:class (stl/css :stroke-options)}
(if token-numeric-inputs
[:> numeric-input-wrapper* {:on-change on-width-change
:on-detach on-detach-token-width
:icon i/stroke-size
:min 0
:on-focus on-focus
:on-blur on-blur
:name :stroke-width
:class (stl/css :numeric-input-wrapper)
:property (tr "workspace.options.stroke-width")
:applied-tokens applied-tokens
:values stroke-width}]
[:div {:class (stl/css :stroke-width-input) [:div {:class (stl/css :stroke-width-input)
:title (tr "workspace.options.stroke-width")} :title (tr "workspace.options.stroke-width")}
[:> icon* {:icon-id i/stroke-size [:> icon* {:icon-id i/stroke-size
:size "s"}] :size "s"}]
[:> deprecated-input/numeric-input* {:value stroke-width [:> numeric-input* {:value stroke-width
:min 0 :min 0
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:on-change on-width-change :on-change on-width-change
:on-focus on-focus :on-focus on-focus
:select-on-focus select-on-focus :select-on-focus select-on-focus
:on-blur on-blur}]]) :on-blur on-blur}]]
[:div {:class (stl/css :stroke-alignment-select) [:div {:class (stl/css :stroke-alignment-select)
:data-testid "stroke.alignment"} :data-testid "stroke.alignment"}

View File

@@ -45,11 +45,6 @@
padding-inline-start: var(--sp-xs); padding-inline-start: var(--sp-xs);
} }
.numeric-input-wrapper {
grid-column: span 2;
--dropdown-width: var(--7-columns-dropdown-width);
}
.stroke-alignment-select { .stroke-alignment-select {
grid-column: span 3; grid-column: span 3;
} }

View File

@@ -18,8 +18,8 @@
[app.main.data.workspace.shortcuts] [app.main.data.workspace.shortcuts]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.search-bar :refer [search-bar*]] [app.main.ui.components.search-bar :refer [search-bar*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]] [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[app.util.strings :refer [matches-search]] [app.util.strings :refer [matches-search]]
@@ -487,13 +487,9 @@
(dom/focus! (dom/get-element "shortcut-search"))) (dom/focus! (dom/get-element "shortcut-search")))
[:div {:class (dm/str class " " (stl/css :shortcuts))} [:div {:class (dm/str class " " (stl/css :shortcuts))}
[:div {:class (stl/css :shortcuts-header)} [:> panel-title* {:class (stl/css :shortcuts-title)
[:div {:class (stl/css :shortcuts-title)} (tr "shortcuts.title")] :text (tr "shortcuts.title")
[:> icon-button* {:variant "ghost" :on-close close-fn}]
:icon i/close
:class (stl/css :shortcuts-close-button)
:on-click close-fn
:aria-label (tr "labels.close")}]]
[:div {:class (stl/css :search-field)} [:div {:class (stl/css :search-field)}
[:> search-bar* {:on-change on-search-term-change-2 [:> search-bar* {:on-change on-search-term-change-2

View File

@@ -18,27 +18,8 @@
margin: deprecated.$s-16 deprecated.$s-12 deprecated.$s-4 deprecated.$s-12; margin: deprecated.$s-16 deprecated.$s-12 deprecated.$s-4 deprecated.$s-12;
} }
.shortcuts-header {
@include deprecated.flexCenter;
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
padding: deprecated.$s-2 deprecated.$s-2 deprecated.$s-2 0;
margin: deprecated.$s-4 deprecated.$s-4 0 deprecated.$s-4;
border-radius: deprecated.$br-6;
background-color: var(--panel-title-background-color);
.shortcuts-title { .shortcuts-title {
@include deprecated.flexCenter; margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
flex-grow: 1;
color: var(--title-foreground-color-hover);
}
.shortcuts-close-button {
position: absolute;
right: 0;
top: 0;
}
} }
.section { .section {

View File

@@ -223,7 +223,7 @@
gap-items (all-or-separate-actions {:attribute-labels {:column-gap "Column Gap" gap-items (all-or-separate-actions {:attribute-labels {:column-gap "Column Gap"
:row-gap "Row Gap"} :row-gap "Row Gap"}
:hint (tr "workspace.tokens.gaps") :hint (tr "workspace.tokens.gaps")
:on-update-shape dwta/update-layout-gap} :on-update-shape dwta/update-layout-spacing}
context-data)] context-data)]
(->> (concat (->> (concat
gap-items gap-items
@@ -239,7 +239,7 @@
(all-or-separate-actions {:attribute-labels {:width "Width" (all-or-separate-actions {:attribute-labels {:width "Width"
:height "Height"} :height "Height"}
:hint (tr "workspace.tokens.size") :hint (tr "workspace.tokens.size")
:on-update-shape dwta/use-dimensions-token} :on-update-shape dwta/update-shape-dimensions}
context-data) context-data)
[:separator] [:separator]
(all-or-separate-actions {:attribute-labels {:layout-item-min-w "Min Width" (all-or-separate-actions {:attribute-labels {:layout-item-min-w "Min Width"

View File

@@ -140,6 +140,9 @@
error error
(get-in @form [:errors input-name]) (get-in @form [:errors input-name])
extra-error
(get-in @form [:extra-errors input-name])
value value
(get-in @form [:data input-name] "") (get-in @form [:data input-name] "")
@@ -247,9 +250,14 @@
:hint-type (:type hint)}) :hint-type (:type hint)})
props props
(if (and error touched?) (cond
(and error touched?)
(mf/spread-props props {:hint-type "error" (mf/spread-props props {:hint-type "error"
:hint-message (:message error)}) :hint-message (:message error)})
(and extra-error touched?)
(mf/spread-props props {:hint-type "error"
:hint-message (:message extra-error)})
:else
props)] props)]
(mf/with-effect [resolve-stream tokens token input-name] (mf/with-effect [resolve-stream tokens token input-name]

View File

@@ -236,12 +236,14 @@
(on-composite-input-change form field value false)) (on-composite-input-change form field value false))
([form field value trim?] ([form field value trim?]
(letfn [(clean-errors [errors] (letfn [(clean-errors [errors]
(-> errors (some-> errors
(dissoc field) (update :value #(when (map? %) (dissoc % field)))
(update :value #(when (seq %) %))
(not-empty)))] (not-empty)))]
(swap! form (fn [state] (swap! form (fn [state]
(-> state (-> state
(assoc-in [:data :value field] (if trim? (str/trim value) value)) (assoc-in [:data :value field] (if trim? (str/trim value) value))
(assoc-in [:touched :value field] true)
(update :errors clean-errors) (update :errors clean-errors)
(update :extra-errors clean-errors))))))) (update :extra-errors clean-errors)))))))
@@ -257,6 +259,9 @@
value value
(get-in @form [:data :value input-name] "") (get-in @form [:data :value input-name] "")
touched?
(get-in @form [:touched :value input-name])
resolve-stream resolve-stream
(mf/with-memo [token] (mf/with-memo [token]
(if-let [value (get-in token [:value input-name])] (if-let [value (get-in token [:value input-name])]
@@ -284,7 +289,7 @@
:hint-message (:message hint) :hint-message (:message hint)
:hint-type (:type hint)}) :hint-type (:type hint)})
props props
(if error (if (and touched? error)
(mf/spread-props props {:hint-type "error" (mf/spread-props props {:hint-type "error"
:hint-message (:message error)}) :hint-message (:message error)})
props) props)
@@ -332,6 +337,7 @@
message (tr "workspace.tokens.resolved-value" (or resolved-value value))] message (tr "workspace.tokens.resolved-value" (or resolved-value value))]
(swap! form update :errors dissoc :value) (swap! form update :errors dissoc :value)
(swap! form update :extra-errors dissoc :value) (swap! form update :extra-errors dissoc :value)
(swap! form update :async-errors dissoc :reference)
(if (= input-value (str resolved-value)) (if (= input-value (str resolved-value))
(reset! hint* {}) (reset! hint* {})
(reset! hint* {:message message :type "hint"})))))))] (reset! hint* {:message message :type "hint"})))))))]

View File

@@ -23,21 +23,19 @@
(let [token-type (let [token-type
(or (:type token) token-type) (or (:type token) token-type)
tokens-in-selected-set
(mf/deref refs/workspace-all-tokens-in-selected-set)
token-path token-path
(mf/with-memo [token] (mf/with-memo [token]
(cft/token-name->path (:name token))) (cft/token-name->path (:name token)))
tokens-tree-in-selected-set all-tokens (mf/deref refs/workspace-all-tokens-map)
(mf/with-memo [token-path tokens-in-selected-set]
(-> (ctob/tokens-tree tokens-in-selected-set) all-tokens
(mf/with-memo [token-path all-tokens]
(-> (ctob/tokens-tree all-tokens)
(d/dissoc-in token-path))) (d/dissoc-in token-path)))
props props
(mf/spread-props props {:token-type token-type (mf/spread-props props {:token-type token-type
:tokens-tree-in-selected-set tokens-tree-in-selected-set :all-token-tree all-tokens
:tokens-in-selected-set tokens-in-selected-set
:token token}) :token token})
text-case-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-case-value-enter")}) text-case-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-case-value-enter")})
text-decoration-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-decoration-value-enter")}) text-decoration-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-decoration-value-enter")})

View File

@@ -16,6 +16,7 @@
[app.main.data.helpers :as dh] [app.main.data.helpers :as dh]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.errors :as wte]
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.tokens.propagation :as dwtp] [app.main.data.workspace.tokens.propagation :as dwtp]
[app.main.data.workspace.tokens.remapping :as remap] [app.main.data.workspace.tokens.remapping :as remap]
@@ -88,14 +89,13 @@
action action
is-create is-create
selected-token-set-id selected-token-set-id
tokens-tree-in-selected-set all-token-tree
token-type token-type
make-schema make-schema
input-component input-component
initial initial
type type
value-subfield value-subfield
tokens-in-selected-set
input-value-placeholder] :as props}] input-value-placeholder] :as props}]
(let [make-schema (or make-schema default-make-schema) (let [make-schema (or make-schema default-make-schema)
@@ -105,13 +105,6 @@
active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite)) active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite))
active-tab (deref active-tab*) active-tab (deref active-tab*)
on-toggle-tab
(mf/use-fn
(mf/deps)
(fn [new-tab]
(let [new-tab (keyword new-tab)]
(reset! active-tab* new-tab))))
token token
(mf/with-memo [token] (mf/with-memo [token]
(or token {:type token-type})) (or token {:type token-type}))
@@ -124,6 +117,9 @@
tokens tokens
(mf/deref refs/workspace-active-theme-sets-tokens) (mf/deref refs/workspace-active-theme-sets-tokens)
tokens-in-selected-set
(mf/deref refs/workspace-all-tokens-in-selected-set)
tokens tokens
(mf/with-memo [tokens tokens-in-selected-set token] (mf/with-memo [tokens tokens-in-selected-set token]
;; Ensure that the resolved value uses the currently editing token ;; Ensure that the resolved value uses the currently editing token
@@ -134,8 +130,8 @@
(assoc (:name token) token))) (assoc (:name token) token)))
schema schema
(mf/with-memo [tokens-tree-in-selected-set active-tab] (mf/with-memo [all-token-tree active-tab]
(make-schema tokens-tree-in-selected-set active-tab)) (make-schema all-token-tree active-tab))
initial initial
(mf/with-memo [token] (mf/with-memo [token]
@@ -148,6 +144,17 @@
(fm/use-form :schema schema (fm/use-form :schema schema
:initial initial) :initial initial)
on-toggle-tab
(mf/use-fn
(mf/deps form)
(fn [new-tab]
(let [new-tab (keyword new-tab)]
(if (= new-tab :reference)
(swap! form assoc-in [:async-errors :reference]
{:message "Need valid reference"})
(swap! form update :async-errors dissoc :reference))
(reset! active-tab* new-tab))))
on-cancel on-cancel
(mf/use-fn (mf/use-fn
(fn [e] (fn [e]
@@ -224,7 +231,12 @@
:description description})) :description description}))
(dwtl/toggle-token-path path) (dwtl/toggle-token-path path)
(dwtp/propagate-workspace-tokens) (dwtp/propagate-workspace-tokens)
(modal/hide!))))))))))] (modal/hide!)))))
;; WORKAROUND: display validation errors in the form instead of crashing
(fn [{:keys [errors]}]
(let [error-messages (wte/humanize-errors errors)
error-message (first error-messages)]
(swap! form assoc-in [:extra-errors :value] {:message error-message}))))))))]
[:> fc/form* {:class (stl/css :form-wrapper) [:> fc/form* {:class (stl/css :form-wrapper)
:form form :form form

View File

@@ -291,6 +291,7 @@
[:color {:optional true} [:maybe :string]] [:color {:optional true} [:maybe :string]]
[:color-result {:optional true} ::sm/any] [:color-result {:optional true} ::sm/any]
[:inset {:optional true} [:maybe :boolean]]]]] [:inset {:optional true} [:maybe :boolean]]]]]
(if (= active-tab :reference) (if (= active-tab :reference)
[:reference {:optional false} ::sm/text] [:reference {:optional false} ::sm/text]
[:reference {:optional true} [:maybe :string]])]] [:reference {:optional true} [:maybe :string]])]]

View File

@@ -228,7 +228,7 @@
:class (stl/css :main-toolbar-options-button) :class (stl/css :main-toolbar-options-button)
:icon i/bug :icon i/bug
:aria-pressed (contains? layout :debug-panel) :aria-pressed (contains? layout :debug-panel)
:aria-label "Debugging tool" :aria-label (tr "workspace.toolbar.debug")
:tooltip-placement "bottom" :tooltip-placement "bottom"
:on-click toggle-debug-panel}]])]] :on-click toggle-debug-panel}]])]]

View File

@@ -1185,7 +1185,6 @@
{:cmd :export-shapes {:cmd :export-shapes
:profile-id (:profile-id @st/state) :profile-id (:profile-id @st/state)
:wait true :wait true
:skip-children (:skip-children value false)
:exports [{:file-id file-id :exports [{:file-id file-id
:page-id page-id :page-id page-id
:object-id id :object-id id

View File

@@ -114,7 +114,7 @@
(defn- load (defn- load
[locale] [locale]
(let [path (str "./translation." locale ".js?version=" (:full cf/version))] (let [path (str "./translation." locale ".js?version=" cf/version-tag)]
(->> (mod/import path) (->> (mod/import path)
(p/fmap (fn [result] (unchecked-get result "default"))) (p/fmap (fn [result] (unchecked-get result "default")))
(p/fnly (fn [data cause] (p/fnly (fn [data cause]

View File

@@ -260,7 +260,7 @@
events [(dwta/apply-token {:shape-ids [(:id rect-1)] events [(dwta/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height} :attributes #{:width :height}
:token (toht/get-token file "dimensions.sm") :token (toht/get-token file "dimensions.sm")
:on-update-shape dwta/use-dimensions-token})]] :on-update-shape dwta/update-shape-dimensions})]]
(tohs/run-store-async (tohs/run-store-async
store done events store done events
(fn [new-state] (fn [new-state]
@@ -333,7 +333,7 @@
events [(dwta/apply-token {:shape-ids [(:id rect-1)] events [(dwta/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height} :attributes #{:width :height}
:token (toht/get-token file "sizing.sm") :token (toht/get-token file "sizing.sm")
:on-update-shape dwta/use-dimensions-token})]] :on-update-shape dwta/update-shape-dimensions})]]
(tohs/run-store-async (tohs/run-store-async
store done events store done events
(fn [new-state] (fn [new-state]

View File

@@ -5476,6 +5476,10 @@ msgstr "Delete row and shapes"
msgid "workspace.context-menu.grid-track.row.duplicate" msgid "workspace.context-menu.grid-track.row.duplicate"
msgstr "Duplicate row" msgstr "Duplicate row"
#: src/app/main/ui/workspace/sidebar/debug.cljs:37
msgid "workspace.debug.title"
msgstr "Debugging tools"
#: src/app/main/ui/workspace/sidebar/layers.cljs:512 #: src/app/main/ui/workspace/sidebar/layers.cljs:512
msgid "workspace.focus.focus-mode" msgid "workspace.focus.focus-mode"
msgstr "Focus mode" msgstr "Focus mode"
@@ -8421,6 +8425,10 @@ msgstr "Comments (%s)"
msgid "workspace.toolbar.curve" msgid "workspace.toolbar.curve"
msgstr "Curve (%s)" msgstr "Curve (%s)"
#: src/app/main/ui/workspace/top_toolbar.cljs:231
msgid "workspace.toolbar.debug"
msgstr "Debugging tools"
#: src/app/main/ui/workspace/top_toolbar.cljs:172 #: src/app/main/ui/workspace/top_toolbar.cljs:172
msgid "workspace.toolbar.ellipse" msgid "workspace.toolbar.ellipse"
msgstr "Ellipse (%s)" msgstr "Ellipse (%s)"

View File

@@ -5461,6 +5461,10 @@ msgstr "Borrar fila con el contenido"
msgid "workspace.context-menu.grid-track.row.duplicate" msgid "workspace.context-menu.grid-track.row.duplicate"
msgstr "Duplicar fila" msgstr "Duplicar fila"
#: src/app/main/ui/workspace/sidebar/debug.cljs:37
msgid "workspace.debug.title"
msgstr "Herramientas de depuración"
#: src/app/main/ui/workspace/sidebar/layers.cljs:512 #: src/app/main/ui/workspace/sidebar/layers.cljs:512
msgid "workspace.focus.focus-mode" msgid "workspace.focus.focus-mode"
msgstr "Modo foco" msgstr "Modo foco"
@@ -7965,7 +7969,7 @@ msgstr "Line height (multiplicador, px o %) o {alias}"
#: src/app/main/data/workspace/tokens/errors.cljs:57 #: src/app/main/data/workspace/tokens/errors.cljs:57
msgid "workspace.tokens.missing-references" msgid "workspace.tokens.missing-references"
msgstr "Referéncias de tokens no encontradas:" msgstr "Referencias de tokens no encontradas: "
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123 #: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
msgid "workspace.tokens.more-options" msgid "workspace.tokens.more-options"
@@ -8282,6 +8286,10 @@ msgstr "Comentarios (%s)"
msgid "workspace.toolbar.curve" msgid "workspace.toolbar.curve"
msgstr "Curva (%s)" msgstr "Curva (%s)"
#: src/app/main/ui/workspace/top_toolbar.cljs:231
msgid "workspace.toolbar.debug"
msgstr "Herramientas de depuración"
#: src/app/main/ui/workspace/top_toolbar.cljs:172 #: src/app/main/ui/workspace/top_toolbar.cljs:172
msgid "workspace.toolbar.ellipse" msgid "workspace.toolbar.ellipse"
msgstr "Elipse (%s)" msgstr "Elipse (%s)"

View File

@@ -0,0 +1,4 @@
name = "penpot-plugins-api-doc"
compatibility_date = "2025-01-01"
assets = { directory = "dist/doc" }

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
export CURRENT_VERSION=${CURRENT_VERSION:-develop}; export VERSION_TAG=${VERSION:-develop};
if [ "$NODE_ENV" = "production" ]; then if [ "$NODE_ENV" = "production" ]; then
export BUILD_MODE="release"; export BUILD_MODE="release";
@@ -81,7 +81,7 @@ function copy_artifacts {
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js $DEST/$BUILD_NAME.js; cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js $DEST/$BUILD_NAME.js;
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm $DEST/$BUILD_NAME.wasm; cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm $DEST/$BUILD_NAME.wasm;
sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$CURRENT_VERSION/g" $DEST/$BUILD_NAME.js; sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$VERSION_TAG/g" $DEST/$BUILD_NAME.js;
yarn esbuild target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js \ yarn esbuild target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js \
--log-level=error \ --log-level=error \

View File

@@ -284,6 +284,7 @@ pub extern "C" fn set_view_end() {
performance::end_measure!("set_view_end::clear_tile_index"); performance::end_measure!("set_view_end::clear_tile_index");
performance::end_timed_log!("clear_tile_index", _clear_start); performance::end_timed_log!("clear_tile_index", _clear_start);
} }
state.render_state.sync_cached_viewbox();
performance::end_measure!("set_view_end"); performance::end_measure!("set_view_end");
performance::end_timed_log!("set_view_end", _end_start); performance::end_timed_log!("set_view_end", _end_start);
#[cfg(feature = "profile-macros")] #[cfg(feature = "profile-macros")]

View File

@@ -1136,6 +1136,7 @@ impl RenderState {
) -> Result<(), String> { ) -> Result<(), String> {
let _start = performance::begin_timed_log!("start_render_loop"); let _start = performance::begin_timed_log!("start_render_loop");
let scale = self.get_scale(); let scale = self.get_scale();
self.tile_viewbox.update(self.viewbox, scale); self.tile_viewbox.update(self.viewbox, scale);
self.focus_mode.reset(); self.focus_mode.reset();
@@ -2292,6 +2293,10 @@ impl RenderState {
(self.viewbox.zoom - self.cached_viewbox.zoom).abs() > f32::EPSILON (self.viewbox.zoom - self.cached_viewbox.zoom).abs() > f32::EPSILON
} }
pub fn sync_cached_viewbox(&mut self) {
self.cached_viewbox = self.viewbox;
}
pub fn mark_touched(&mut self, uuid: Uuid) { pub fn mark_touched(&mut self, uuid: Uuid) {
self.touched_ids.insert(uuid); self.touched_ids.insert(uuid);
} }

View File

@@ -1529,6 +1529,7 @@ impl Shape {
|| !self.transform.is_identity() || !self.transform.is_identity()
|| !math::is_close_to(self.rotation, 0.0) || !math::is_close_to(self.rotation, 0.0)
|| matches!(self.shape_type, Type::Group(_) | Type::Frame(_)) || matches!(self.shape_type, Type::Group(_) | Type::Frame(_))
|| matches!(self.shape_type, Type::Text(_))
} }
pub fn count_visible_inner_strokes(&self) -> usize { pub fn count_visible_inner_strokes(&self) -> usize {

View File

@@ -100,6 +100,16 @@ impl<'a> State<'a> {
} }
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> { pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
// If zoom changed, we MUST rebuild the tile index before using it.
// Otherwise, the index will have tiles from the old zoom level, causing visible
// tiles to appear empty. This can happen if start_render_loop() is called before
// set_view_end() finishes rebuilding the index, or if set_view_end() hasn't been
// called yet.
let zoom_changed = self.render_state.zoom_changed();
if zoom_changed {
self.rebuild_tiles_shallow();
}
self.render_state self.render_state
.start_render_loop(None, &self.shapes, timestamp, false)?; .start_render_loop(None, &self.shapes, timestamp, false)?;
Ok(()) Ok(())