Compare commits
194 Commits
eva-fix-ne
...
hiru-plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9f347b402 | ||
|
|
cfee9b4f96 | ||
|
|
dc973dac36 | ||
|
|
4467827218 | ||
|
|
6470db8d5f | ||
|
|
dc44156b53 | ||
|
|
f0e53d70ae | ||
|
|
ef73a263b2 | ||
|
|
9b1e007a49 | ||
|
|
ea8632e56a | ||
|
|
2d00e64ede | ||
|
|
1246250198 | ||
|
|
34f2943dcd | ||
|
|
3fb78116b8 | ||
|
|
072e415b9e | ||
|
|
67a904824c | ||
|
|
68184209be | ||
|
|
d2295862b4 | ||
|
|
23cbf33d1b | ||
|
|
b4ff0ccf3a | ||
|
|
6c6666a39a | ||
|
|
c1335961b4 | ||
|
|
eaf64b6e16 | ||
|
|
560a0d09d5 | ||
|
|
b70eb768e0 | ||
|
|
4397ede5c1 | ||
|
|
ff25df0457 | ||
|
|
079b3fbfad | ||
|
|
299f628951 | ||
|
|
32d0fe6463 | ||
|
|
cecd3d4a90 | ||
|
|
1c2c0987f5 | ||
|
|
0418147e74 | ||
|
|
7499a5bca6 | ||
|
|
6cd5bc76d7 | ||
|
|
bbe6ee2e19 | ||
|
|
fb6d8309b6 | ||
|
|
b7c2d9a079 | ||
|
|
aeb34a6f64 | ||
|
|
6fa0c3af0c | ||
|
|
260b9fb040 | ||
|
|
884954f4ff | ||
|
|
689467bcf9 | ||
|
|
7724450037 | ||
|
|
368fa954ce | ||
|
|
6fd0f5377c | ||
|
|
eb54bc485e | ||
|
|
12c24a36b4 | ||
|
|
324d54ad28 | ||
|
|
f42ff27f3d | ||
|
|
0ecb2bc838 | ||
|
|
2c1cc89f53 | ||
|
|
498b0b30fe | ||
|
|
89f40dcda2 | ||
|
|
e92f3fb3cb | ||
|
|
5193cfd56e | ||
|
|
813d5d8e69 | ||
|
|
84f1ff092d | ||
|
|
ccac7bd510 | ||
|
|
f2b082b93e | ||
|
|
d73197625d | ||
|
|
fdf5bb250b | ||
|
|
786736fadd | ||
|
|
1ff6e00398 | ||
|
|
25455523ad | ||
|
|
8dfeb21978 | ||
|
|
43d1d127dc | ||
|
|
8bd3ef717c | ||
|
|
53bc647783 | ||
|
|
ad2833bb7a | ||
|
|
21911e898f | ||
|
|
538073debf | ||
|
|
6029f9bb51 | ||
|
|
e0fd8bac81 | ||
|
|
34737ddfc9 | ||
|
|
a8dfd19338 | ||
|
|
e33e8a8c3b | ||
|
|
214b0efa02 | ||
|
|
661436ecae | ||
|
|
c411aefc6c | ||
|
|
311e124658 | ||
|
|
afc914f486 | ||
|
|
0d5fe6e527 | ||
|
|
e7230d9da4 | ||
|
|
5054f6bc38 | ||
|
|
84f750da0d | ||
|
|
38396ba299 | ||
|
|
b1997a83b3 | ||
|
|
68f5671eab | ||
|
|
92976143bb | ||
|
|
dd2d03e6a0 | ||
|
|
fd675e0194 | ||
|
|
a3119bef5e | ||
|
|
c60d74df62 | ||
|
|
d593e299e3 | ||
|
|
4a8e02987f | ||
|
|
ee766e85a0 | ||
|
|
35e3b7f19a | ||
|
|
1810df232b | ||
|
|
3e99ad036c | ||
|
|
042a3a4080 | ||
|
|
f0687fd1f7 | ||
|
|
2c9159288f | ||
|
|
c98373658e | ||
|
|
01e42b0458 | ||
|
|
7529673812 | ||
|
|
d2dad35d7a | ||
|
|
f7b5266304 | ||
|
|
09c23256b7 | ||
|
|
1ae1c0460e | ||
|
|
291c7349db | ||
|
|
b605a3b53d | ||
|
|
cc2dab2756 | ||
|
|
d0c0664338 | ||
|
|
2240f25143 | ||
|
|
93a5ec2f5d | ||
|
|
d6784771a8 | ||
|
|
930c814ded | ||
|
|
1a5a69bca2 | ||
|
|
9ad323a220 | ||
|
|
5fa4368d70 | ||
|
|
b8efd2518d | ||
|
|
7b2271ec38 | ||
|
|
2240d93069 | ||
|
|
3f4506284b | ||
|
|
af1dfd91aa | ||
|
|
24feebd73b | ||
|
|
33e5a9a538 | ||
|
|
9c69b07a62 | ||
|
|
56f5be4f37 | ||
|
|
8a70204d41 | ||
|
|
57a27f7e7f | ||
|
|
3b0b2a78d6 | ||
|
|
10bf4610df | ||
|
|
77e8414aea | ||
|
|
20ecf3b066 | ||
|
|
49b1032973 | ||
|
|
5ba7dd8c56 | ||
|
|
38b5125186 | ||
|
|
6677ae83d4 | ||
|
|
0737c055f0 | ||
|
|
4b88748fe3 | ||
|
|
92107e5b1e | ||
|
|
ebc0e3a23c | ||
|
|
ebe4f2da50 | ||
|
|
a07c1d6eaa | ||
|
|
613bfda955 | ||
|
|
f7ef6618e5 | ||
|
|
fe334d9cbe | ||
|
|
268b883c73 | ||
|
|
f6a4effa29 | ||
|
|
ced848077e | ||
|
|
7d9d318539 | ||
|
|
9781fceadb | ||
|
|
3178bd9a27 | ||
|
|
e5d677f449 | ||
|
|
6bf928893c | ||
|
|
53dd90aa24 | ||
|
|
9fd0f6a8f3 | ||
|
|
638c3356d3 | ||
|
|
6879f54e5d | ||
|
|
a71baa5a78 | ||
|
|
8e4a89bd1c | ||
|
|
7aad9da285 | ||
|
|
ab57a4ae52 | ||
|
|
266ee29bb9 | ||
|
|
69ca86bb6c | ||
|
|
ee14a845fc | ||
|
|
73639f5d16 | ||
|
|
9bd106b2bc | ||
|
|
59c75afc7b | ||
|
|
bbc81586e3 | ||
|
|
c9c30eab75 | ||
|
|
86ba9280db | ||
|
|
5800cc4bb2 | ||
|
|
aa29a34c4c | ||
|
|
3276129cc7 | ||
|
|
67a96de475 | ||
|
|
48785b4846 | ||
|
|
3f0573f95d | ||
|
|
d94a2a8881 | ||
|
|
1c237a0968 | ||
|
|
b7eaeffa88 | ||
|
|
722fcc1f82 | ||
|
|
2ad42cfd9b | ||
|
|
795f65632a | ||
|
|
7819e6c440 | ||
|
|
952f622ce9 | ||
|
|
a6c6f97f47 | ||
|
|
88424eb54a | ||
|
|
de9a21121a | ||
|
|
cea10308b7 | ||
|
|
5223c9c881 | ||
|
|
be62fa10c4 |
38
.github/ISSUE_TEMPLATE/new-render-bug-report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: New Render Bug Report
|
||||
about: Create a report about the bugs you have found in the new render
|
||||
title: ''
|
||||
labels: new render
|
||||
assignees: claragvinola
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps to Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots or screen recordings**
|
||||
If applicable, add screenshots or screen recording to help illustrate your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
2
.github/workflows/build-bundle.yml
vendored
@@ -40,7 +40,7 @@ on:
|
||||
jobs:
|
||||
build-bundle:
|
||||
name: Build and Upload Penpot Bundle
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-01
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
7
.github/workflows/build-docker-devenv.yml
vendored
@@ -7,9 +7,14 @@ jobs:
|
||||
build-and-push:
|
||||
name: Build and push DevEnv Docker image
|
||||
environment: release-admins
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
|
||||
steps:
|
||||
- name: Set common environment variables
|
||||
run: |
|
||||
# Each job execution will use its own docker configuration.
|
||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
16
.github/workflows/build-docker.yml
vendored
@@ -19,9 +19,14 @@ on:
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push Penpot Docker Images
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: penpot-runner-02
|
||||
|
||||
steps:
|
||||
- name: Set common environment variables
|
||||
run: |
|
||||
# Each job execution will use its own docker configuration.
|
||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -66,6 +71,15 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
# To avoid the “429 Too Many Requests” error when downloading
|
||||
# images from DockerHub for unregistered users.
|
||||
# https://docs.docker.com/docker-hub/usage/
|
||||
- name: Login to DockerHub Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
|
||||
2
.github/workflows/build-tag.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
||||
TEXT: |
|
||||
🐳 *[PENPOT] Docker image available: {{ github.ref_name }}*
|
||||
🐳 *[PENPOT] Docker image available: ${{ github.ref_name }}*
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@infra
|
||||
|
||||
|
||||
2
.github/workflows/commit-checker.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Check Commit Type
|
||||
uses: gsactions/commit-message-checker@v2
|
||||
with:
|
||||
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert).+[^.])$'
|
||||
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert|Reapply).+[^.])$'
|
||||
flags: 'gm'
|
||||
error: 'Commit should match CONTRIBUTING.md guideline'
|
||||
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
|
||||
|
||||
24
.github/workflows/tests.yml
vendored
@@ -21,7 +21,7 @@ concurrency:
|
||||
jobs:
|
||||
lint:
|
||||
name: "Linter"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
|
||||
steps:
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
test-common:
|
||||
name: "Common Tests"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
|
||||
steps:
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
test-plugins:
|
||||
name: Plugins Runtime Linter & Tests
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
|
||||
test-frontend:
|
||||
name: "Frontend Tests"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
|
||||
steps:
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
|
||||
test-render-wasm:
|
||||
name: "Render WASM Tests"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
|
||||
steps:
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
|
||||
test-backend:
|
||||
name: "Backend Tests"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
|
||||
services:
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
|
||||
test-library:
|
||||
name: "Library Tests"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
|
||||
steps:
|
||||
@@ -196,7 +196,7 @@ jobs:
|
||||
|
||||
build-integration:
|
||||
name: "Build Integration Bundle"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
|
||||
steps:
|
||||
@@ -217,7 +217,7 @@ jobs:
|
||||
|
||||
test-integration-1:
|
||||
name: "Integration Tests 1/4"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
needs: build-integration
|
||||
|
||||
@@ -247,7 +247,7 @@ jobs:
|
||||
|
||||
test-integration-2:
|
||||
name: "Integration Tests 2/4"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
needs: build-integration
|
||||
|
||||
@@ -277,7 +277,7 @@ jobs:
|
||||
|
||||
test-integration-3:
|
||||
name: "Integration Tests 3/4"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
needs: build-integration
|
||||
|
||||
@@ -307,7 +307,7 @@ jobs:
|
||||
|
||||
test-integration-4:
|
||||
name: "Integration Tests 4/4"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: penpot-runner-02
|
||||
container: penpotapp/devenv:latest
|
||||
needs: build-integration
|
||||
|
||||
|
||||
2
.gitignore
vendored
@@ -54,6 +54,8 @@
|
||||
/exporter/target
|
||||
/frontend/.storybook/preview-body.html
|
||||
/frontend/.storybook/preview-head.html
|
||||
/frontend/playwright-report/
|
||||
/frontend/text-editor/src/wasm/
|
||||
/frontend/dist/
|
||||
/frontend/npm-debug.log
|
||||
/frontend/out/
|
||||
|
||||
105
.gitpod.yml
@@ -1,105 +0,0 @@
|
||||
image:
|
||||
file: docker/gitpod/Dockerfile
|
||||
|
||||
ports:
|
||||
# nginx
|
||||
- port: 3449
|
||||
onOpen: open-preview
|
||||
|
||||
# frontend nREPL
|
||||
- port: 3447
|
||||
onOpen: ignore
|
||||
visibility: private
|
||||
|
||||
# frontend shadow server
|
||||
- port: 3448
|
||||
onOpen: ignore
|
||||
visibility: private
|
||||
|
||||
# backend
|
||||
- port: 6060
|
||||
onOpen: ignore
|
||||
|
||||
# exporter shadow server
|
||||
- port: 9630
|
||||
onOpen: ignore
|
||||
visibility: private
|
||||
|
||||
# exporter http server
|
||||
- port: 6061
|
||||
onOpen: ignore
|
||||
|
||||
# mailhog web interface
|
||||
- port: 8025
|
||||
onOpen: ignore
|
||||
|
||||
# mailhog postfix
|
||||
- port: 1025
|
||||
onOpen: ignore
|
||||
|
||||
# postgres
|
||||
- port: 5432
|
||||
onOpen: ignore
|
||||
|
||||
# redis
|
||||
- port: 6379
|
||||
onOpen: ignore
|
||||
|
||||
# openldap
|
||||
- port: 389
|
||||
onOpen: ignore
|
||||
|
||||
tasks:
|
||||
# https://github.com/gitpod-io/gitpod/issues/666#issuecomment-534347856
|
||||
- name: gulp
|
||||
command: >
|
||||
cd $GITPOD_REPO_ROOT/frontend/;
|
||||
yarn && gp sync-done 'frontend-yarn';
|
||||
npx gulp --theme=${PENPOT_THEME} watch
|
||||
|
||||
- name: frontend shadow watch
|
||||
command: >
|
||||
cd $GITPOD_REPO_ROOT/frontend/;
|
||||
gp sync-await 'frontend-yarn';
|
||||
npx shadow-cljs watch main
|
||||
|
||||
- init: gp await-port 5432 && psql -f $GITPOD_REPO_ROOT/docker/gitpod/files/postgresql_init.sql
|
||||
name: backend
|
||||
command: >
|
||||
cd $GITPOD_REPO_ROOT/backend/;
|
||||
./scripts/start-dev
|
||||
|
||||
- name: exporter shadow watch
|
||||
command:
|
||||
cd $GITPOD_REPO_ROOT/exporter/;
|
||||
gp sync-await 'frontend-yarn';
|
||||
yarn && npx shadow-cljs watch main
|
||||
|
||||
- name: exporter web server
|
||||
command: >
|
||||
cd $GITPOD_REPO_ROOT/exporter/;
|
||||
./scripts/wait-and-start.sh
|
||||
|
||||
- name: signed terminal
|
||||
before: >
|
||||
[[ ! -z ${GNUGPG} ]] &&
|
||||
cd ~ &&
|
||||
rm -rf .gnupg &&
|
||||
echo ${GNUGPG} | base64 -d | tar --no-same-owner -xzvf -
|
||||
init: >
|
||||
[[ ! -z ${GNUGPG_KEY} ]] &&
|
||||
git config --global commit.gpgsign true &&
|
||||
git config --global user.signingkey ${GNUGPG_KEY}
|
||||
command: cd $GITPOD_REPO_ROOT
|
||||
|
||||
- name: redis
|
||||
command: redis-server
|
||||
|
||||
- before: go get github.com/mailhog/MailHog
|
||||
name: mailhog
|
||||
command: MailHog
|
||||
|
||||
- name: Nginx
|
||||
command: >
|
||||
nginx &&
|
||||
multitail /var/log/nginx/access.log -I /var/log/nginx/error.log
|
||||
11
.yarnrc.yml
@@ -1,11 +0,0 @@
|
||||
enableGlobalCache: true
|
||||
|
||||
enableImmutableCache: false
|
||||
|
||||
enableImmutableInstalls: false
|
||||
|
||||
enableTelemetry: false
|
||||
|
||||
httpTimeout: 600000
|
||||
|
||||
nodeLinker: node-modules
|
||||
35
CHANGES.md
@@ -1,5 +1,32 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.14.0 (Unreleased)
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
- Remap references when renaming tokens [Taiga #10202](https://tree.taiga.io/project/penpot/us/10202)
|
||||
- Tokens panel nested path view [Taiga #9966](https://tree.taiga.io/project/penpot/us/9966)
|
||||
- Improve usability of lock and hide buttons in the layer panel. [Taiga #12916](https://tree.taiga.io/project/penpot/issue/12916)
|
||||
- Optimize sidebar performance for deeply nested shapes [Taiga #13017](https://tree.taiga.io/project/penpot/task/13017)
|
||||
- Remove tokens path node and bulk remove tokens [Taiga #13007](https://tree.taiga.io/project/penpot/us/13007)
|
||||
- Replace themes management modal radio buttons for switches [Taiga #9215](https://tree.taiga.io/project/penpot/us/9215)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix prototype connections lost when switching between variants [Taiga #12812](https://tree.taiga.io/project/penpot/issue/12812)
|
||||
- Fix wrong image in the onboarding invitation block [Taiga #13040](https://tree.taiga.io/project/penpot/issue/13040)
|
||||
- Fix wrong register image [Taiga #12955](https://tree.taiga.io/project/penpot/task/12955)
|
||||
- Fix error message on components doesn't close automatically [Taiga #12012](https://tree.taiga.io/project/penpot/issue/12012)
|
||||
- Fix incorrect default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051)
|
||||
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
|
||||
- Fix displaying a hidden user avatar when there is only one more [Taiga #13058](https://tree.taiga.io/project/penpot/issue/13058)
|
||||
|
||||
## 2.13.0 (Unreleased)
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
@@ -23,14 +50,14 @@
|
||||
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
|
||||
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
|
||||
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
|
||||
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
|
||||
- Fix color assets from shared libraries not appearing as assets in Selected colors panel [Taiga #12957](https://tree.taiga.io/project/penpot/issue/12957)
|
||||
- Fix CSS generated box-shadow property [Taiga #12997](https://tree.taiga.io/project/penpot/issue/12997)
|
||||
- Fix inner shadow selector on shadow token [Taiga #12951](https://tree.taiga.io/project/penpot/issue/12951)
|
||||
- Fix missing text color token from selected shapes in selected colors list [Taiga #12956](https://tree.taiga.io/project/penpot/issue/12956)
|
||||
- Fix dropdown option width in Guides columns dropdown [Taiga #12959](https://tree.taiga.io/project/penpot/issue/12959)
|
||||
- Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865)
|
||||
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
|
||||
- Fix allow negative spread values on shadow token creation [Taiga #13167](https://tree.taiga.io/project/penpot/issue/13167)
|
||||
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
|
||||
|
||||
## 2.12.1
|
||||
|
||||
@@ -40,7 +67,6 @@
|
||||
- Fix problem with style in fonts input [Taiga #12935](https://tree.taiga.io/project/penpot/issue/12935)
|
||||
- Fix problem with path editor and right click [Github #7917](https://github.com/penpot/penpot/issues/7917)
|
||||
|
||||
|
||||
## 2.12.0
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
@@ -52,7 +78,6 @@ The backend RPC API URLS are changed from `/api/rpc/command/<name>` to
|
||||
compatibility; however, if you are a user of this API, it is strongly
|
||||
recommended that you adapt your code to use the new PATH.
|
||||
|
||||
|
||||
#### Updated SSO Callback URL
|
||||
|
||||
The OAuth / Single Sign-On (SSO) callback endpoint has changed to
|
||||
@@ -85,7 +110,6 @@ This update standardizes all authentication flows under the single URL
|
||||
and makis it more modular, enabling the ability to configure SSO auth
|
||||
provider dinamically.
|
||||
|
||||
|
||||
#### Changes on default docker compose
|
||||
|
||||
We have updated the `docker/images/docker-compose.yaml` with a small
|
||||
@@ -149,7 +173,6 @@ example. It's still usable as before, we just removed the example.
|
||||
|
||||
- Deprecated configuration variables with the prefix `PENPOT_ASSETS_*`, and will be
|
||||
removed in future versions:
|
||||
|
||||
- The `PENPOT_ASSETS_STORAGE_BACKEND` becomes `PENPOT_OBJECTS_STORAGE_BACKEND` and its
|
||||
values passes from (`assets-fs` or `assets-s3`) to (`fs` or `s3`)
|
||||
- The `PENPOT_STORAGE_ASSETS_FS_DIRECTORY` becomes `PENPOT_OBJECTS_STORAGE_FS_DIRECTORY`
|
||||
|
||||
@@ -120,17 +120,12 @@ them on your system, you can run them with:
|
||||
|
||||
```bash
|
||||
# Check formatting
|
||||
yarn fmt:clj:check
|
||||
./scripts/fmt
|
||||
|
||||
# Check and fix formatting
|
||||
yarn fmt:clj
|
||||
|
||||
# Run the linter
|
||||
yarn lint:clj
|
||||
# Lint
|
||||
./scripts/lint
|
||||
```
|
||||
|
||||
There are more choices in `package.json`.
|
||||
|
||||
Ideally, you should run these commands as git pre-commit hooks. A convenient way
|
||||
of defining them is to use [Husky](https://typicode.github.io/husky/#/).
|
||||
|
||||
|
||||
7
backend/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c",
|
||||
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
||||
306
backend/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,306 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
luxon:
|
||||
specifier: ^3.4.4
|
||||
version: 3.7.2
|
||||
sax:
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.3
|
||||
devDependencies:
|
||||
nodemon:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.11
|
||||
source-map-support:
|
||||
specifier: ^0.5.21
|
||||
version: 0.5.21
|
||||
ws:
|
||||
specifier: ^8.17.0
|
||||
version: 8.18.3
|
||||
|
||||
packages:
|
||||
|
||||
anymatch@3.1.3:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
binary-extensions@2.3.0:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
chokidar@3.6.0:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
fill-range@7.1.1:
|
||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
has-flag@3.0.0:
|
||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
ignore-by-default@1.0.1:
|
||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
luxon@3.7.2:
|
||||
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
nodemon@3.1.11:
|
||||
resolution: {integrity: sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
pstree.remy@1.1.8:
|
||||
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
||||
|
||||
readdirp@3.6.0:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
sax@1.4.3:
|
||||
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
|
||||
|
||||
semver@7.7.3:
|
||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
simple-update-notifier@2.0.0:
|
||||
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
source-map-support@0.5.21:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
|
||||
source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
touch@3.1.1:
|
||||
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
|
||||
hasBin: true
|
||||
|
||||
undefsafe@2.0.5:
|
||||
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
|
||||
|
||||
ws@8.18.3:
|
||||
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
snapshots:
|
||||
|
||||
anymatch@3.1.3:
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
chokidar@3.6.0:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
braces: 3.0.3
|
||||
glob-parent: 5.1.2
|
||||
is-binary-path: 2.1.0
|
||||
is-glob: 4.0.3
|
||||
normalize-path: 3.0.0
|
||||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
debug@4.4.3(supports-color@5.5.0):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
optionalDependencies:
|
||||
supports-color: 5.5.0
|
||||
|
||||
fill-range@7.1.1:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
|
||||
has-flag@3.0.0: {}
|
||||
|
||||
ignore-by-default@1.0.1: {}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
dependencies:
|
||||
binary-extensions: 2.3.0
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
luxon@3.7.2: {}
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nodemon@3.1.11:
|
||||
dependencies:
|
||||
chokidar: 3.6.0
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
ignore-by-default: 1.0.1
|
||||
minimatch: 3.1.2
|
||||
pstree.remy: 1.1.8
|
||||
semver: 7.7.3
|
||||
simple-update-notifier: 2.0.0
|
||||
supports-color: 5.5.0
|
||||
touch: 3.1.1
|
||||
undefsafe: 2.0.5
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
||||
pstree.remy@1.1.8: {}
|
||||
|
||||
readdirp@3.6.0:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
sax@1.4.3: {}
|
||||
|
||||
semver@7.7.3: {}
|
||||
|
||||
simple-update-notifier@2.0.0:
|
||||
dependencies:
|
||||
semver: 7.7.3
|
||||
|
||||
source-map-support@0.5.21:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: 0.6.1
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
supports-color@5.5.0:
|
||||
dependencies:
|
||||
has-flag: 3.0.0
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
touch@3.1.1: {}
|
||||
|
||||
undefsafe@2.0.5: {}
|
||||
|
||||
ws@8.18.3: {}
|
||||
0
backend/pnpm-workspace.yaml
Normal file
1145
backend/yarn.lock
7
common/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
@@ -59,7 +59,7 @@
|
||||
thheller/shadow-cljs {:mvn/version "3.2.0"}
|
||||
com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
|
||||
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
||||
criterium/criterium {:mvn/version "RELEASE"}
|
||||
criterium/criterium {:mvn/version "0.4.6"}
|
||||
mockery/mockery {:mvn/version "RELEASE"}}
|
||||
:extra-paths ["test" "dev"]}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c",
|
||||
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -23,9 +23,9 @@
|
||||
"fmt:clj:check": "cljfmt check --parallel=false src/ test/",
|
||||
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
|
||||
"lint:clj": "clj-kondo --parallel=true --lint src/",
|
||||
"lint": "yarn run lint:clj",
|
||||
"lint": "pnpm run lint:clj",
|
||||
"watch:test": "concurrently \"clojure -M:dev:shadow-cljs watch test\" \"nodemon -C -d 2 -w target/tests/ --exec 'node target/tests/test.js'\"",
|
||||
"build:test": "clojure -M:dev:shadow-cljs compile test",
|
||||
"test": "yarn run build:test && node target/tests/test.js"
|
||||
"test": "pnpm run build:test && node target/tests/test.js"
|
||||
}
|
||||
}
|
||||
|
||||
489
common/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,489 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
date-fns:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
devDependencies:
|
||||
concurrently:
|
||||
specifier: ^9.1.2
|
||||
version: 9.2.1
|
||||
nodemon:
|
||||
specifier: ^3.1.10
|
||||
version: 3.1.11
|
||||
source-map-support:
|
||||
specifier: ^0.5.21
|
||||
version: 0.5.21
|
||||
ws:
|
||||
specifier: ^8.18.2
|
||||
version: 8.18.3
|
||||
|
||||
packages:
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
anymatch@3.1.3:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
binary-extensions@2.3.0:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
chokidar@3.6.0:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
|
||||
cliui@8.0.1:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
concurrently@9.2.1:
|
||||
resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
date-fns@4.1.0:
|
||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
escalade@3.2.0:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
fill-range@7.1.1:
|
||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
has-flag@3.0.0:
|
||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ignore-by-default@1.0.1:
|
||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
nodemon@3.1.11:
|
||||
resolution: {integrity: sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
pstree.remy@1.1.8:
|
||||
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
||||
|
||||
readdirp@3.6.0:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
rxjs@7.8.2:
|
||||
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
||||
|
||||
semver@7.7.3:
|
||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
shell-quote@1.8.3:
|
||||
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
simple-update-notifier@2.0.0:
|
||||
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
source-map-support@0.5.21:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
|
||||
source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
supports-color@8.1.1:
|
||||
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
touch@3.1.1:
|
||||
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
|
||||
hasBin: true
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
undefsafe@2.0.5:
|
||||
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ws@8.18.3:
|
||||
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs@17.7.2:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
snapshots:
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
anymatch@3.1.3:
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
chokidar@3.6.0:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
braces: 3.0.3
|
||||
glob-parent: 5.1.2
|
||||
is-binary-path: 2.1.0
|
||||
is-glob: 4.0.3
|
||||
normalize-path: 3.0.0
|
||||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
concurrently@9.2.1:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
rxjs: 7.8.2
|
||||
shell-quote: 1.8.3
|
||||
supports-color: 8.1.1
|
||||
tree-kill: 1.2.2
|
||||
yargs: 17.7.2
|
||||
|
||||
date-fns@4.1.0: {}
|
||||
|
||||
debug@4.4.3(supports-color@5.5.0):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
optionalDependencies:
|
||||
supports-color: 5.5.0
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
fill-range@7.1.1:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
|
||||
has-flag@3.0.0: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
ignore-by-default@1.0.1: {}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
dependencies:
|
||||
binary-extensions: 2.3.0
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nodemon@3.1.11:
|
||||
dependencies:
|
||||
chokidar: 3.6.0
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
ignore-by-default: 1.0.1
|
||||
minimatch: 3.1.2
|
||||
pstree.remy: 1.1.8
|
||||
semver: 7.7.3
|
||||
simple-update-notifier: 2.0.0
|
||||
supports-color: 5.5.0
|
||||
touch: 3.1.1
|
||||
undefsafe: 2.0.5
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
||||
pstree.remy@1.1.8: {}
|
||||
|
||||
readdirp@3.6.0:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
rxjs@7.8.2:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
semver@7.7.3: {}
|
||||
|
||||
shell-quote@1.8.3: {}
|
||||
|
||||
simple-update-notifier@2.0.0:
|
||||
dependencies:
|
||||
semver: 7.7.3
|
||||
|
||||
source-map-support@0.5.21:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: 0.6.1
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
supports-color@5.5.0:
|
||||
dependencies:
|
||||
has-flag: 3.0.0
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
supports-color@8.1.1:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
touch@3.1.1: {}
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
undefsafe@2.0.5: {}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
ws@8.18.3: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yargs@17.7.2:
|
||||
dependencies:
|
||||
cliui: 8.0.1
|
||||
escalade: 3.2.0
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
0
common/pnpm-workspace.yaml
Normal file
@@ -3,5 +3,5 @@
|
||||
set -ex
|
||||
corepack enable;
|
||||
corepack install;
|
||||
yarn install;
|
||||
yarn run test;
|
||||
pnpm install;
|
||||
pnpm run test;
|
||||
|
||||
@@ -526,20 +526,25 @@
|
||||
ids))
|
||||
|
||||
(defn clean-loops
|
||||
"Clean a list of ids from circular references."
|
||||
"Clean a list of ids from circular references. Optimized fast-path for single selections."
|
||||
[objects ids]
|
||||
(let [parent-selected?
|
||||
(fn [id]
|
||||
(let [parents (get-parent-ids objects id)]
|
||||
(some ids parents)))
|
||||
(if (<= (count ids) 1)
|
||||
;; For single selection, there can't be circularity; return as ordered-set.
|
||||
(into (d/ordered-set) ids)
|
||||
(let [ids-set (if (set? ids) ids (set ids))
|
||||
parent-selected?
|
||||
(fn [id]
|
||||
;; Stop early as soon as we find any selected parent
|
||||
(let [parents (get-parent-ids objects id)]
|
||||
(some #(contains? ids-set %) parents)))
|
||||
|
||||
add-element
|
||||
(fn [result id]
|
||||
(cond-> result
|
||||
(not (parent-selected? id))
|
||||
(conj id)))]
|
||||
add-element
|
||||
(fn [result id]
|
||||
(cond-> result
|
||||
(not (parent-selected? id))
|
||||
(conj id)))]
|
||||
|
||||
(reduce add-element (d/ordered-set) ids)))
|
||||
(reduce add-element (d/ordered-set) ids))))
|
||||
|
||||
(defn- indexed-shapes
|
||||
"Retrieves a vector with the indexes for each element in the layer
|
||||
|
||||
@@ -75,20 +75,25 @@
|
||||
|
||||
#?(:cljs
|
||||
(defn ->clj
|
||||
[o & {:keys [key-fn val-fn] :or {key-fn read-kebab-key val-fn identity}}]
|
||||
[o & {:keys [key-fn val-fn recursive] :or {key-fn read-kebab-key val-fn identity recursive true}}]
|
||||
(let [f (fn this-fn [x]
|
||||
(let [x (val-fn x)]
|
||||
(cond
|
||||
(array? x)
|
||||
(persistent!
|
||||
(.reduce ^js/Array x
|
||||
#(conj! %1 (this-fn %2))
|
||||
#(conj! %1 (if recursive
|
||||
(this-fn %2)
|
||||
%2))
|
||||
(transient [])))
|
||||
|
||||
(identical? (type x) js/Object)
|
||||
(persistent!
|
||||
(.reduce ^js/Array (js-keys x)
|
||||
#(assoc! %1 (key-fn %2) (this-fn (unchecked-get x %2)))
|
||||
#(assoc! %1 (key-fn %2)
|
||||
(if recursive
|
||||
(this-fn (unchecked-get x %2))
|
||||
(unchecked-get x %2)))
|
||||
(transient {})))
|
||||
|
||||
:else
|
||||
|
||||
@@ -2497,7 +2497,7 @@
|
||||
(-> changes
|
||||
(cls/generate-delete-shapes
|
||||
file page objects (d/ordered-set (:id shape))
|
||||
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true}))
|
||||
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true :ignore-flows-for #{(:id shape)}}))
|
||||
[new-shape changes]
|
||||
(-> changes
|
||||
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
|
||||
|
||||
@@ -124,9 +124,11 @@
|
||||
;; on the deletion process. It should receive a shape and
|
||||
;; return a boolean
|
||||
ignore-children-fn
|
||||
ignore-mask]
|
||||
ignore-mask
|
||||
ignore-flows-for]
|
||||
:or {ignore-children-fn (constantly false)
|
||||
ignore-mask false}}]
|
||||
ignore-mask false
|
||||
ignore-flows-for #{}}}]
|
||||
(let [objects (pcb/get-objects changes)
|
||||
data (pcb/get-library-data changes)
|
||||
page-id (pcb/get-page-id changes)
|
||||
@@ -194,7 +196,8 @@
|
||||
(->> (:flows page)
|
||||
(reduce
|
||||
(fn [changes [id flow]]
|
||||
(if (id-to-delete? (:starting-frame flow))
|
||||
(if (and (id-to-delete? (:starting-frame flow))
|
||||
(not (contains? ignore-flows-for (:starting-frame flow))))
|
||||
(-> changes
|
||||
(pcb/with-page page)
|
||||
(pcb/set-flow id nil))
|
||||
|
||||
@@ -132,3 +132,94 @@ Some naming conventions:
|
||||
(if-let [last-period (str/last-index-of s ".")]
|
||||
[(subs s 0 (inc last-period)) (subs s (inc last-period))]
|
||||
[s ""]))
|
||||
|
||||
;; Tree building functions --------------------------------------------------
|
||||
|
||||
"Build tree structure from flat list of paths"
|
||||
|
||||
"`build-tree-root` is the main function to build the tree."
|
||||
|
||||
"Receives a list of segments with 'name' properties representing paths,
|
||||
and a separator string."
|
||||
"E.g segments = [{... :name 'one/two/three'} {... :name 'one/two/four'} {... :name 'one/five'}]"
|
||||
|
||||
"Transforms into a tree structure like:
|
||||
[{:name 'one'
|
||||
:path 'one'
|
||||
:depth 0
|
||||
:leaf nil
|
||||
:children-fn (fn [] [{:name 'two'
|
||||
:path 'one.two'
|
||||
:depth 1
|
||||
:leaf nil
|
||||
:children-fn (fn [] [{... :name 'three'} {... :name 'four'}])}
|
||||
{:name 'five'
|
||||
:path 'one.five'
|
||||
:depth 1
|
||||
:leaf {... :name 'five'}
|
||||
...}])}]"
|
||||
|
||||
(defn- sort-by-children
|
||||
"Sorts segments so that those with children come first."
|
||||
[segments separator]
|
||||
(sort-by (fn [segment]
|
||||
(let [path (split-path (:name segment) :separator separator)
|
||||
path-length (count path)]
|
||||
(if (= path-length 1)
|
||||
1
|
||||
0)))
|
||||
segments))
|
||||
|
||||
(defn- group-by-first-segment
|
||||
"Groups segments by their first path segment and update segment name."
|
||||
[segments separator]
|
||||
(reduce (fn [acc segment]
|
||||
(let [[first-segment & remaining-segments] (split-path (:name segment) :separator separator)
|
||||
rest-path (when (seq remaining-segments) (join-path remaining-segments :separator separator :with-spaces? false))]
|
||||
(update acc first-segment (fnil conj [])
|
||||
(if rest-path
|
||||
(assoc segment :name rest-path)
|
||||
segment))))
|
||||
{}
|
||||
segments))
|
||||
|
||||
(defn- sort-and-group-segments
|
||||
"Sorts elements and groups them by their first path segment."
|
||||
[segments separator]
|
||||
(let [sorted (sort-by-children segments separator)
|
||||
grouped (group-by-first-segment sorted separator)]
|
||||
grouped))
|
||||
|
||||
(defn- build-tree-node
|
||||
"Builds a single tree node with lazy children."
|
||||
[segment-name remaining-segments separator parent-path depth]
|
||||
(let [current-path (if parent-path
|
||||
(str parent-path "." segment-name)
|
||||
segment-name)
|
||||
|
||||
is-leaf? (and (seq remaining-segments)
|
||||
(every? (fn [segment]
|
||||
(let [remaining-segment-name (first (split-path (:name segment) :separator separator))]
|
||||
(= segment-name remaining-segment-name)))
|
||||
remaining-segments))
|
||||
|
||||
leaf-segment (when is-leaf? (first remaining-segments))
|
||||
node {:name segment-name
|
||||
:path current-path
|
||||
:depth depth
|
||||
:leaf leaf-segment
|
||||
:children-fn (when-not is-leaf?
|
||||
(fn []
|
||||
(let [grouped-elements (sort-and-group-segments remaining-segments separator)]
|
||||
(mapv (fn [[child-segment-name remaining-child-segments]]
|
||||
(build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth)))
|
||||
grouped-elements))))}]
|
||||
node))
|
||||
|
||||
(defn build-tree-root
|
||||
"Builds the root level of the tree."
|
||||
[segments separator]
|
||||
(let [grouped-elements (sort-and-group-segments segments separator)]
|
||||
(mapv (fn [[segment-name remaining-segments]]
|
||||
(build-tree-node segment-name remaining-segments separator nil 0))
|
||||
grouped-elements)))
|
||||
|
||||
@@ -140,7 +140,8 @@
|
||||
:layout-item-min-w
|
||||
:layout-item-absolute
|
||||
:layout-item-z-index
|
||||
:layout-item-align-self})
|
||||
:layout-item-align-self
|
||||
:interactions})
|
||||
|
||||
(defn component-attr?
|
||||
"Check if some attribute is one that is involved in component syncrhonization.
|
||||
|
||||
@@ -47,6 +47,18 @@
|
||||
self-reference? (get token-references token-name)]
|
||||
self-reference?))
|
||||
|
||||
(defn references-token?
|
||||
"Recursively check if a value references the token name. Handles strings, maps, and sequences."
|
||||
[value token-name]
|
||||
(cond
|
||||
(string? value)
|
||||
(boolean (some #(= % token-name) (find-token-value-references value)))
|
||||
(map? value)
|
||||
(some true? (map #(references-token? % token-name) (vals value)))
|
||||
(sequential? value)
|
||||
(some true? (map #(references-token? % token-name) value))
|
||||
:else false))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMA
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -475,6 +487,7 @@
|
||||
:vertical-margin #{:spacing :dimensions}
|
||||
:sided-margins #{:spacing :dimensions}
|
||||
:line-height #{:line-height :number}
|
||||
:opacity #{:opacity}
|
||||
:font-size #{:font-size}
|
||||
:letter-spacing #{:letter-spacing}
|
||||
:fill #{:color}
|
||||
@@ -558,3 +571,18 @@
|
||||
"Predicate if a shadow composite token is a reference value - a string pointing to another reference token."
|
||||
[token-value]
|
||||
(string? token-value))
|
||||
|
||||
(defn update-token-value-references
|
||||
"Recursively update token references within a token value, supporting complex token values (maps, sequences, strings)."
|
||||
[value old-name new-name]
|
||||
(cond
|
||||
(string? value)
|
||||
(str/replace value
|
||||
(re-pattern (str "\\{" (str/replace old-name "." "\\.") "\\}"))
|
||||
(str "{" new-name "}"))
|
||||
(map? value)
|
||||
(d/update-vals value #(update-token-value-references % old-name new-name))
|
||||
(sequential? value)
|
||||
(mapv #(update-token-value-references % old-name new-name) value)
|
||||
:else
|
||||
value))
|
||||
|
||||
@@ -909,7 +909,8 @@ Will return a value that matches this schema:
|
||||
`:all` All of the nested sets are active
|
||||
`:partial` Mixed active state of nested sets")
|
||||
(get-tokens-in-active-sets [_] "set of set names that are active in the the active themes")
|
||||
(get-all-tokens [_] "all tokens in the lib")
|
||||
(get-all-tokens [_] "all tokens in the lib, as a sequence")
|
||||
(get-all-tokens-map [_] "all tokens in the lib, as a map name -> token")
|
||||
(get-tokens [_ set-id] "return a map of tokens in the set, indexed by token-name"))
|
||||
|
||||
(declare parse-multi-set-dtcg-json)
|
||||
@@ -1306,6 +1307,10 @@ Will return a value that matches this schema:
|
||||
tokens))
|
||||
|
||||
(get-all-tokens [this]
|
||||
(mapcat #(vals (get-tokens- %))
|
||||
(get-sets this)))
|
||||
|
||||
(get-all-tokens-map [this]
|
||||
(reduce
|
||||
(fn [tokens' set]
|
||||
(into tokens' (map (fn [x] [(:name x) x]) (vals (get-tokens- set)))))
|
||||
|
||||
1291
common/yarn.lock
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo chown penpot:users /home/penpot
|
||||
|
||||
cd ~;
|
||||
|
||||
source ~/.bashrc
|
||||
|
||||
set -e;
|
||||
|
||||
echo "[start-tmux.sh] Installing node dependencies"
|
||||
pushd ~/penpot/exporter/
|
||||
yarn install
|
||||
popd
|
||||
|
||||
tmux -2 new-session -d -s penpot
|
||||
|
||||
tmux rename-window -t penpot:0 'exporter'
|
||||
tmux select-window -t penpot:0
|
||||
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
|
||||
tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l
|
||||
tmux send-keys -t penpot 'clojure -M:dev:shadow-cljs watch main' enter
|
||||
|
||||
tmux split-window -v
|
||||
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
|
||||
tmux send-keys -t penpot './scripts/wait-and-start.sh' enter
|
||||
|
||||
tmux new-window -t penpot:1 -n 'backend'
|
||||
tmux select-window -t penpot:1
|
||||
tmux send-keys -t penpot 'cd penpot/backend' enter C-l
|
||||
tmux send-keys -t penpot './scripts/start-dev' enter
|
||||
|
||||
tmux -2 attach-session -t penpot
|
||||
@@ -112,10 +112,6 @@ COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
|
||||
WORKDIR /opt/penpot/exporter
|
||||
USER penpot:penpot
|
||||
|
||||
RUN set -ex; \
|
||||
corepack install; \
|
||||
yarn install; \
|
||||
yarn run playwright install chromium; \
|
||||
rm -rf /opt/penpot/.yarn
|
||||
RUN ./setup
|
||||
|
||||
CMD ["node", "app.js"]
|
||||
|
||||
@@ -144,7 +144,7 @@ http {
|
||||
location / {
|
||||
include /etc/nginx/overrides/location.d/*.conf;
|
||||
|
||||
location ~* \.(js|css|jpg|png|svg|gif|ttf|woff|woff2|wasm)$ {
|
||||
location ~* \.(js|css|jpg|png|svg|gif|ttf|woff|woff2|wasm|map)$ {
|
||||
add_header Cache-Control "public, max-age=604800" always; # 7 days
|
||||
}
|
||||
|
||||
@@ -152,8 +152,10 @@ http {
|
||||
return 301 " /404";
|
||||
}
|
||||
|
||||
add_header X-Frame-Options SAMEORIGIN always;
|
||||
add_header Cache-Control "no-store, no-cache, max-age=0" always;
|
||||
try_files $uri /index.html$is_args$args /index.html =404;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,15 @@ To view this site locally, first set up the environment:
|
||||
# only if necessary
|
||||
nvm install
|
||||
nvm use
|
||||
# only if necessary
|
||||
corepack enable
|
||||
|
||||
yarn install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
And launch a development server:
|
||||
|
||||
```sh
|
||||
yarn start
|
||||
pnpm start
|
||||
```
|
||||
|
||||
You can then point a browser to [http://localhost:8080](http://localhost:8080).
|
||||
|
||||
@@ -39,5 +39,5 @@
|
||||
"markdown-it-anchor": "^9.0.1",
|
||||
"markdown-it-plantuml": "^1.4.1"
|
||||
},
|
||||
"packageManager": "yarn@4.3.1"
|
||||
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48"
|
||||
}
|
||||
|
||||
2065
docs/pnpm-lock.yaml
generated
Normal file
@@ -6,5 +6,5 @@ corepack enable;
|
||||
corepack install;
|
||||
|
||||
rm -rf ./_dist
|
||||
yarn install
|
||||
yarn run build
|
||||
pnpm install
|
||||
pnpm run build
|
||||
|
||||
@@ -281,8 +281,8 @@ for how to define custom metadata and other ways of selecting tests.
|
||||
it, but for now we use shadow-cljs with <code class="language-text">package.json</code> scripts:
|
||||
|
||||
```bash
|
||||
yarn run test
|
||||
yarn run test:watch
|
||||
pnpm run test
|
||||
pnpm run test:watch
|
||||
```
|
||||
|
||||
#### Test output
|
||||
|
||||
@@ -217,7 +217,7 @@ repository:
|
||||
|
||||
```bash
|
||||
# cd <repo>/frontend
|
||||
yarn run translations
|
||||
pnpm run translations
|
||||
```
|
||||
|
||||
At Penpot core team we maintain manually the english and spanish .po files. All
|
||||
@@ -308,7 +308,7 @@ Ensure your development environment docker image is up to date.
|
||||
This is not required, but it may be convenient to compile Penpot in release mode before running the tests. This way they will be much quicker and stable. For this, go to the frontend window in the tmux session (<code class="language-bash">Ctrl + b 1</code>), interrupt the watch process with <code class="language-bash">Ctrl + C</code> and type:
|
||||
|
||||
```bash
|
||||
yarn run build:app
|
||||
./scripts/build
|
||||
```
|
||||
|
||||
Obviously, in this mode if you make changes to the source code, you will need to repeat the build manually each time. It may be useful to use wath mode when debugging a single test, and use release mode to run all the suite.
|
||||
@@ -328,17 +328,17 @@ Here's how to run the tests with a headless browser (i.e. within the terminal, n
|
||||
cd penpot/frontend
|
||||
```
|
||||
|
||||
3. Run the tests with <code class="language-bash">yarn</code>:
|
||||
3. Run the tests with <code class="language-bash">pnpm</code>:
|
||||
|
||||
```bash
|
||||
yarn test:e2e
|
||||
pnpm run test:e2e
|
||||
```
|
||||
|
||||
> 💡 **TIP:** By default, the tests will _not_ run in parallel. You can set the amount of workers to run the tests with <code class="language-bash">--workers</code>. Note that, depending on your machine, this might make some tests flaky.
|
||||
|
||||
```bash
|
||||
# run in parallel with 4 workers
|
||||
yarn test:e2e --workers 4
|
||||
pnpm run test:e2e --workers 4
|
||||
```
|
||||
|
||||
#### Running the tests in Chromium
|
||||
@@ -356,7 +356,7 @@ npx playwright test --ui
|
||||
|
||||
> ❗️ **IMPORTANT**: You might need to [install Playwright's browsers and dependencies](https://playwright.dev/docs/intro) in your host machine with: <code class="language-bash">npx playwright install --with-deps</code>. In case you are using a Linux distribution other than Ubuntu, [you might need to install the dependencies manually](https://github.com/microsoft/playwright/issues/11122).
|
||||
|
||||
> You will also need yarn in your host nodejs. For this, do <code class="language-bash">corepack enable</code> and then just <code class="language-bash">yarn</code>.
|
||||
> You will also need pnpm in your host nodejs. For this, do <code class="language-bash">corepack enable</code> and then just <code class="language-bash">pnpm</code>.
|
||||
|
||||
### How to write a test
|
||||
|
||||
|
||||
@@ -677,7 +677,7 @@ The Storybook is available at the <code class="language-bash">/storybook</code>
|
||||
|
||||
#### Local development
|
||||
|
||||
Use <code class="language-bash">yarn watch:storybook</code> to develop the Design System components with the help of Storybook.
|
||||
Use <code class="language-bash">pnpm run watch:storybook</code> to develop the Design System components with the help of Storybook.
|
||||
|
||||
> **⚠️ WARNING**: Do stop any existing Shadow CLJS and asset compilation jobs (like the ones running at tabs <code class="language-bash">0</code> and <code class="language-bash">1</code> in the devenv tmux), because <code class="language-bash">watch:storybook</code> will spawn their own.
|
||||
|
||||
|
||||
3169
docs/yarn.lock
7
exporter/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8",
|
||||
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
@@ -30,10 +30,10 @@
|
||||
},
|
||||
"scripts": {
|
||||
"clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target",
|
||||
"watch:app": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main",
|
||||
"watch": "yarn run watch:app",
|
||||
"watch:app": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main",
|
||||
"watch": "pnpm run watch:app",
|
||||
"build:app": "clojure -M:dev:shadow-cljs release main",
|
||||
"build": "yarn run clear:shadow-cache && yarn run build:app",
|
||||
"build": "pnpm run clear:shadow-cache && pnpm run build:app",
|
||||
"fmt:clj:check": "cljfmt check --parallel=false src/",
|
||||
"fmt:clj": "cljfmt fix --parallel=true src/",
|
||||
"lint:clj": "clj-kondo --parallel --lint src/"
|
||||
|
||||
1048
exporter/pnpm-lock.yaml
generated
Normal file
0
exporter/pnpm-workspace.yaml
Normal file
@@ -7,15 +7,13 @@ export NODE_ENV=production;
|
||||
|
||||
corepack enable;
|
||||
corepack install || exit 1;
|
||||
yarn install || exit 1;
|
||||
pnpm install || exit 1;
|
||||
rm -rf target
|
||||
|
||||
# Build the application
|
||||
yarn run build;
|
||||
pnpm run build;
|
||||
|
||||
# Copy package*.json files
|
||||
cp ../.yarnrc.yml target/;
|
||||
cp yarn.lock target/;
|
||||
cp pnpm-lock.yaml target/;
|
||||
cp package.json target/;
|
||||
|
||||
cat <<EOF | tee target/setup
|
||||
@@ -23,8 +21,8 @@ cat <<EOF | tee target/setup
|
||||
set -e;
|
||||
corepack enable;
|
||||
corepack install;
|
||||
yarn install
|
||||
yarn run playwright install chromium;
|
||||
pnpm install
|
||||
pnpx playwright install chromium;
|
||||
EOF
|
||||
|
||||
chmod +x target/setup;
|
||||
|
||||
@@ -4,5 +4,5 @@ set -e;
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
yarn install;
|
||||
yarn playwright install chromium
|
||||
pnpm install;
|
||||
pnpx playwright install chromium
|
||||
|
||||
@@ -4,4 +4,4 @@ TARGET=${1:-app};
|
||||
|
||||
set -ex
|
||||
|
||||
exec yarn run watch:$TARGET
|
||||
exec pnpm run watch:$TARGET
|
||||
|
||||
1658
exporter/yarn.lock
14
frontend/.gitignore
vendored
@@ -1,14 +0,0 @@
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/playwright/**/visual-specs/**/*.png
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
|
||||
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
@@ -13,32 +13,25 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
},
|
||||
"resolutions": {
|
||||
"@zip.js/zip.js@npm:^2.7.44": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"playwright": "1.52.0",
|
||||
"playwright-core": "1.52.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build:app:assets": "node ./scripts/build-app-assets.js",
|
||||
"build:storybook": "yarn run build:storybook:assets && yarn run build:storybook:cljs && storybook build",
|
||||
"build:storybook": "pnpm run build:storybook:assets && pnpm run build:storybook:cljs && storybook build",
|
||||
"build:storybook:assets": "node ./scripts/build-storybook-assets.js",
|
||||
"build:wasm": "../render-wasm/build",
|
||||
"build:storybook:cljs": "clojure -M:dev:shadow-cljs compile storybook",
|
||||
"build:app:libs": "node ./scripts/build-libs.js",
|
||||
"build:app:main": "clojure -M:dev:shadow-cljs release main worker",
|
||||
"build:app:worker": "clojure -M:dev:shadow-cljs release worker",
|
||||
"build:app": "yarn run clear:shadow-cache && yarn run build:app:main && yarn run build:app:libs",
|
||||
"e2e:server": "node ./scripts/e2e-server.js",
|
||||
"build:app": "pnpm run clear:shadow-cache && pnpm run build:app:main && pnpm run build:app:libs",
|
||||
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
|
||||
"fmt:clj:check": "cljfmt check --parallel=false src/ test/",
|
||||
"fmt:js": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -c text-editor/**/*.js -w",
|
||||
"fmt:js:check": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js text-editor/**/*.js",
|
||||
"fmt:js": "pnpx prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -c text-editor/**/*.js -w",
|
||||
"fmt:js:check": "pnpx prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js text-editor/**/*.js",
|
||||
"lint:clj": "clj-kondo --parallel --lint src/",
|
||||
"lint:scss": "yarn run prettier -c resources/styles -c src/**/*.scss",
|
||||
"lint:scss:fix": "yarn run prettier -c resources/styles -c src/**/*.scss -w",
|
||||
"lint:scss": "pnpx prettier -c resources/styles -c src/**/*.scss",
|
||||
"lint:scss:fix": "pnpx prettier -c resources/styles -c src/**/*.scss -w",
|
||||
"build:test": "clojure -M:dev:shadow-cljs compile test",
|
||||
"test": "yarn run build:test && node target/tests/test.js",
|
||||
"test": "pnpm run build:test && node target/tests/test.js",
|
||||
"test:storybook": "vitest run --project=storybook",
|
||||
"watch:test": "mkdir -p target/tests && concurrently \"clojure -M:dev:shadow-cljs watch test\" \"nodemon -C -d 2 -w target/tests --exec 'node target/tests/test.js'\"",
|
||||
"test:e2e": "playwright test --project default",
|
||||
@@ -48,31 +41,31 @@
|
||||
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
|
||||
"clear:shadow-cache": "rm -rf .shadow-cljs",
|
||||
"watch": "exit 0",
|
||||
"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:app": "pnpm run clear:shadow-cache && pnpm run build:wasm && concurrently --kill-others-on-fail \"pnpm run watch:app:assets\" \"pnpm run watch:app:main\" \"pnpm run watch:app:libs\"",
|
||||
"watch:storybook": "pnpm run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@penpot/draft-js": "portal:./packages/draft-js",
|
||||
"@penpot/mousetrap": "portal:./packages/mousetrap",
|
||||
"@penpot/draft-js": "workspace:./packages/draft-js",
|
||||
"@penpot/mousetrap": "workspace:./packages/mousetrap",
|
||||
"@penpot/plugins-runtime": "1.4.2",
|
||||
"@penpot/svgo": "penpot/svgo#v3.2",
|
||||
"@penpot/text-editor": "portal:./text-editor",
|
||||
"@penpot/text-editor": "workspace:./text-editor",
|
||||
"@playwright/test": "1.57.0",
|
||||
"@storybook/addon-docs": "10.1.11",
|
||||
"@storybook/addon-themes": "10.1.11",
|
||||
"@storybook/addon-vitest": "10.1.11",
|
||||
"@storybook/react-vite": "10.1.11",
|
||||
"@tokens-studio/sd-transforms": "1.2.11",
|
||||
"@types/node": "^22.19.3",
|
||||
"@types/node": "^25.0.3",
|
||||
"@vitest/browser": "4.0.16",
|
||||
"@vitest/browser-playwright": "^4.0.16",
|
||||
"@vitest/coverage-v8": "4.0.16",
|
||||
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
|
||||
"@zip.js/zip.js": "2.8.11",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"compression": "^1.8.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"esbuild": "^0.27.2",
|
||||
"eventsource-parser": "^3.0.6",
|
||||
"express": "^5.1.0",
|
||||
"fancy-log": "^2.0.0",
|
||||
@@ -91,7 +84,7 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"opentype.js": "^1.3.4",
|
||||
"p-limit": "^6.2.0",
|
||||
"playwright": "1.56.1",
|
||||
"playwright": "1.57.0",
|
||||
"postcss": "^8.5.4",
|
||||
"postcss-clean": "^1.2.2",
|
||||
"postcss-modules": "^6.0.1",
|
||||
@@ -99,9 +92,9 @@
|
||||
"pretty-time": "^1.1.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"randomcolor": "^0.6.2",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-error-boundary": "^6.1.0",
|
||||
"react-virtualized": "^9.22.6",
|
||||
"rimraf": "^6.0.1",
|
||||
"rxjs": "8.0.0-alpha.14",
|
||||
@@ -115,7 +108,7 @@
|
||||
"tdigest": "^0.1.2",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"typescript": "^5.9.2",
|
||||
"ua-parser-js": "2.0.5",
|
||||
"ua-parser-js": "2.0.7",
|
||||
"vite": "^7.3.0",
|
||||
"vitest": "^4.0.16",
|
||||
"wait-on": "^9.0.3",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Penpot Draft-JS Wrapper",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"packageManager": "yarn@4.3.1",
|
||||
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
@@ -16,6 +16,6 @@
|
||||
"react-dom": ">=0.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.24.0"
|
||||
"esbuild": "^0.27.2"
|
||||
}
|
||||
}
|
||||
|
||||
449
frontend/packages/draft-js/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,449 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
draft-js:
|
||||
specifier: penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0
|
||||
version: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
immutable:
|
||||
specifier: ^5.1.4
|
||||
version: 5.1.4
|
||||
react:
|
||||
specifier: '>=0.17.0'
|
||||
version: 19.2.3
|
||||
react-dom:
|
||||
specifier: '>=0.17.0'
|
||||
version: 19.2.3(react@19.2.3)
|
||||
devDependencies:
|
||||
esbuild:
|
||||
specifier: ^0.27.2
|
||||
version: 0.27.2
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.2':
|
||||
resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.27.2':
|
||||
resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.27.2':
|
||||
resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.27.2':
|
||||
resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.2':
|
||||
resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.27.2':
|
||||
resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.27.2':
|
||||
resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.27.2':
|
||||
resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.2':
|
||||
resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.2':
|
||||
resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.2':
|
||||
resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.27.2':
|
||||
resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.27.2':
|
||||
resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.2':
|
||||
resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.2':
|
||||
resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.27.2':
|
||||
resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.27.2':
|
||||
resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.27.2':
|
||||
resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
asap@2.0.6:
|
||||
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
|
||||
|
||||
cross-fetch@3.2.0:
|
||||
resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==}
|
||||
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0:
|
||||
resolution: {tarball: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0}
|
||||
version: 0.11.7
|
||||
peerDependencies:
|
||||
react: '>=0.14.0'
|
||||
react-dom: '>=0.14.0'
|
||||
|
||||
esbuild@0.27.2:
|
||||
resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
fbjs-css-vars@1.0.2:
|
||||
resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==}
|
||||
|
||||
fbjs@3.0.5:
|
||||
resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==}
|
||||
|
||||
immutable@3.7.6:
|
||||
resolution: {integrity: sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
immutable@5.1.4:
|
||||
resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
promise@7.3.1:
|
||||
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
|
||||
|
||||
react-dom@19.2.3:
|
||||
resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
|
||||
peerDependencies:
|
||||
react: ^19.2.3
|
||||
|
||||
react@19.2.3:
|
||||
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
setimmediate@1.0.5:
|
||||
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
||||
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
ua-parser-js@1.0.41:
|
||||
resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==}
|
||||
hasBin: true
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
asap@2.0.6: {}
|
||||
|
||||
cross-fetch@3.2.0:
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
fbjs: 3.0.5
|
||||
immutable: 3.7.6
|
||||
object-assign: 4.1.1
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
esbuild@0.27.2:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.2
|
||||
'@esbuild/android-arm': 0.27.2
|
||||
'@esbuild/android-arm64': 0.27.2
|
||||
'@esbuild/android-x64': 0.27.2
|
||||
'@esbuild/darwin-arm64': 0.27.2
|
||||
'@esbuild/darwin-x64': 0.27.2
|
||||
'@esbuild/freebsd-arm64': 0.27.2
|
||||
'@esbuild/freebsd-x64': 0.27.2
|
||||
'@esbuild/linux-arm': 0.27.2
|
||||
'@esbuild/linux-arm64': 0.27.2
|
||||
'@esbuild/linux-ia32': 0.27.2
|
||||
'@esbuild/linux-loong64': 0.27.2
|
||||
'@esbuild/linux-mips64el': 0.27.2
|
||||
'@esbuild/linux-ppc64': 0.27.2
|
||||
'@esbuild/linux-riscv64': 0.27.2
|
||||
'@esbuild/linux-s390x': 0.27.2
|
||||
'@esbuild/linux-x64': 0.27.2
|
||||
'@esbuild/netbsd-arm64': 0.27.2
|
||||
'@esbuild/netbsd-x64': 0.27.2
|
||||
'@esbuild/openbsd-arm64': 0.27.2
|
||||
'@esbuild/openbsd-x64': 0.27.2
|
||||
'@esbuild/openharmony-arm64': 0.27.2
|
||||
'@esbuild/sunos-x64': 0.27.2
|
||||
'@esbuild/win32-arm64': 0.27.2
|
||||
'@esbuild/win32-ia32': 0.27.2
|
||||
'@esbuild/win32-x64': 0.27.2
|
||||
|
||||
fbjs-css-vars@1.0.2: {}
|
||||
|
||||
fbjs@3.0.5:
|
||||
dependencies:
|
||||
cross-fetch: 3.2.0
|
||||
fbjs-css-vars: 1.0.2
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
promise: 7.3.1
|
||||
setimmediate: 1.0.5
|
||||
ua-parser-js: 1.0.41
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
immutable@3.7.6: {}
|
||||
|
||||
immutable@5.1.4: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
promise@7.3.1:
|
||||
dependencies:
|
||||
asap: 2.0.6
|
||||
|
||||
react-dom@19.2.3(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
scheduler: 0.27.0
|
||||
|
||||
react@19.2.3: {}
|
||||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
setimmediate@1.0.5: {}
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
ua-parser-js@1.0.41: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
@@ -1,424 +0,0 @@
|
||||
# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 8
|
||||
cacheKey: 10c0
|
||||
|
||||
"@esbuild/aix-ppc64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.24.0"
|
||||
conditions: os=aix & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/android-arm64@npm:0.24.0"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/android-arm@npm:0.24.0"
|
||||
conditions: os=android & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-x64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/android-x64@npm:0.24.0"
|
||||
conditions: os=android & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-arm64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.24.0"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-x64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/darwin-x64@npm:0.24.0"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-arm64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.24.0"
|
||||
conditions: os=freebsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-x64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.24.0"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/linux-arm64@npm:0.24.0"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/linux-arm@npm:0.24.0"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ia32@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/linux-ia32@npm:0.24.0"
|
||||
conditions: os=linux & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-loong64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/linux-loong64@npm:0.24.0"
|
||||
conditions: os=linux & cpu=loong64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-mips64el@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.24.0"
|
||||
conditions: os=linux & cpu=mips64el
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ppc64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.24.0"
|
||||
conditions: os=linux & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-riscv64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.24.0"
|
||||
conditions: os=linux & cpu=riscv64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-s390x@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/linux-s390x@npm:0.24.0"
|
||||
conditions: os=linux & cpu=s390x
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-x64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/linux-x64@npm:0.24.0"
|
||||
conditions: os=linux & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-x64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.24.0"
|
||||
conditions: os=netbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-arm64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/openbsd-arm64@npm:0.24.0"
|
||||
conditions: os=openbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-x64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.24.0"
|
||||
conditions: os=openbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/sunos-x64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/sunos-x64@npm:0.24.0"
|
||||
conditions: os=sunos & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-arm64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/win32-arm64@npm:0.24.0"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-ia32@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/win32-ia32@npm:0.24.0"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-x64@npm:0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "@esbuild/win32-x64@npm:0.24.0"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@penpot/draft-js@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@penpot/draft-js@workspace:."
|
||||
dependencies:
|
||||
draft-js: "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
|
||||
esbuild: "npm:^0.24.0"
|
||||
immutable: "npm:^5.1.4"
|
||||
peerDependencies:
|
||||
react: ">=0.17.0"
|
||||
react-dom: ">=0.17.0"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"asap@npm:~2.0.3":
|
||||
version: 2.0.6
|
||||
resolution: "asap@npm:2.0.6"
|
||||
checksum: 10c0/c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-fetch@npm:^3.1.5":
|
||||
version: 3.1.8
|
||||
resolution: "cross-fetch@npm:3.1.8"
|
||||
dependencies:
|
||||
node-fetch: "npm:^2.6.12"
|
||||
checksum: 10c0/4c5e022ffe6abdf380faa6e2373c0c4ed7ef75e105c95c972b6f627c3f083170b6886f19fb488a7fa93971f4f69dcc890f122b0d97f0bf5f41ca1d9a8f58c8af
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"draft-js@penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0":
|
||||
version: 0.11.7
|
||||
resolution: "draft-js@https://github.com/penpot/draft-js.git#commit=4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
|
||||
dependencies:
|
||||
fbjs: "npm:^3.0.4"
|
||||
immutable: "npm:~3.7.4"
|
||||
object-assign: "npm:^4.1.1"
|
||||
peerDependencies:
|
||||
react: ">=0.14.0"
|
||||
react-dom: ">=0.14.0"
|
||||
checksum: 10c0/dcd6fd9481b445c0df31a414d5bf0b84ad691d50ac90d805b65c36fb4d26b1ada787f37a63cb437e2a1b6d8dc0f95b4f3c41f6a8082480235ab48b391900a43b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esbuild@npm:^0.24.0":
|
||||
version: 0.24.0
|
||||
resolution: "esbuild@npm:0.24.0"
|
||||
dependencies:
|
||||
"@esbuild/aix-ppc64": "npm:0.24.0"
|
||||
"@esbuild/android-arm": "npm:0.24.0"
|
||||
"@esbuild/android-arm64": "npm:0.24.0"
|
||||
"@esbuild/android-x64": "npm:0.24.0"
|
||||
"@esbuild/darwin-arm64": "npm:0.24.0"
|
||||
"@esbuild/darwin-x64": "npm:0.24.0"
|
||||
"@esbuild/freebsd-arm64": "npm:0.24.0"
|
||||
"@esbuild/freebsd-x64": "npm:0.24.0"
|
||||
"@esbuild/linux-arm": "npm:0.24.0"
|
||||
"@esbuild/linux-arm64": "npm:0.24.0"
|
||||
"@esbuild/linux-ia32": "npm:0.24.0"
|
||||
"@esbuild/linux-loong64": "npm:0.24.0"
|
||||
"@esbuild/linux-mips64el": "npm:0.24.0"
|
||||
"@esbuild/linux-ppc64": "npm:0.24.0"
|
||||
"@esbuild/linux-riscv64": "npm:0.24.0"
|
||||
"@esbuild/linux-s390x": "npm:0.24.0"
|
||||
"@esbuild/linux-x64": "npm:0.24.0"
|
||||
"@esbuild/netbsd-x64": "npm:0.24.0"
|
||||
"@esbuild/openbsd-arm64": "npm:0.24.0"
|
||||
"@esbuild/openbsd-x64": "npm:0.24.0"
|
||||
"@esbuild/sunos-x64": "npm:0.24.0"
|
||||
"@esbuild/win32-arm64": "npm:0.24.0"
|
||||
"@esbuild/win32-ia32": "npm:0.24.0"
|
||||
"@esbuild/win32-x64": "npm:0.24.0"
|
||||
dependenciesMeta:
|
||||
"@esbuild/aix-ppc64":
|
||||
optional: true
|
||||
"@esbuild/android-arm":
|
||||
optional: true
|
||||
"@esbuild/android-arm64":
|
||||
optional: true
|
||||
"@esbuild/android-x64":
|
||||
optional: true
|
||||
"@esbuild/darwin-arm64":
|
||||
optional: true
|
||||
"@esbuild/darwin-x64":
|
||||
optional: true
|
||||
"@esbuild/freebsd-arm64":
|
||||
optional: true
|
||||
"@esbuild/freebsd-x64":
|
||||
optional: true
|
||||
"@esbuild/linux-arm":
|
||||
optional: true
|
||||
"@esbuild/linux-arm64":
|
||||
optional: true
|
||||
"@esbuild/linux-ia32":
|
||||
optional: true
|
||||
"@esbuild/linux-loong64":
|
||||
optional: true
|
||||
"@esbuild/linux-mips64el":
|
||||
optional: true
|
||||
"@esbuild/linux-ppc64":
|
||||
optional: true
|
||||
"@esbuild/linux-riscv64":
|
||||
optional: true
|
||||
"@esbuild/linux-s390x":
|
||||
optional: true
|
||||
"@esbuild/linux-x64":
|
||||
optional: true
|
||||
"@esbuild/netbsd-x64":
|
||||
optional: true
|
||||
"@esbuild/openbsd-arm64":
|
||||
optional: true
|
||||
"@esbuild/openbsd-x64":
|
||||
optional: true
|
||||
"@esbuild/sunos-x64":
|
||||
optional: true
|
||||
"@esbuild/win32-arm64":
|
||||
optional: true
|
||||
"@esbuild/win32-ia32":
|
||||
optional: true
|
||||
"@esbuild/win32-x64":
|
||||
optional: true
|
||||
bin:
|
||||
esbuild: bin/esbuild
|
||||
checksum: 10c0/9f1aadd8d64f3bff422ae78387e66e51a5e09de6935a6f987b6e4e189ed00fdc2d1bc03d2e33633b094008529c8b6e06c7ad1a9782fb09fec223bf95998c0683
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fbjs-css-vars@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "fbjs-css-vars@npm:1.0.2"
|
||||
checksum: 10c0/dfb64116b125a64abecca9e31477b5edb9a2332c5ffe74326fe36e0a72eef7fc8a49b86adf36c2c293078d79f4524f35e80f5e62546395f53fb7c9e69821f54f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fbjs@npm:^3.0.4":
|
||||
version: 3.0.5
|
||||
resolution: "fbjs@npm:3.0.5"
|
||||
dependencies:
|
||||
cross-fetch: "npm:^3.1.5"
|
||||
fbjs-css-vars: "npm:^1.0.0"
|
||||
loose-envify: "npm:^1.0.0"
|
||||
object-assign: "npm:^4.1.0"
|
||||
promise: "npm:^7.1.1"
|
||||
setimmediate: "npm:^1.0.5"
|
||||
ua-parser-js: "npm:^1.0.35"
|
||||
checksum: 10c0/66d0a2fc9a774f9066e35ac2ac4bf1245931d27f3ac287c7d47e6aa1fc152b243c2109743eb8f65341e025621fb51a12038fadb9fd8fda2e3ddae04ebab06f91
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"immutable@npm:^5.1.4":
|
||||
version: 5.1.4
|
||||
resolution: "immutable@npm:5.1.4"
|
||||
checksum: 10c0/f1c98382e4cde14a0b218be3b9b2f8441888da8df3b8c064aa756071da55fbed6ad696e5959982508456332419be9fdeaf29b2e58d0eadc45483cc16963c0446
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"immutable@npm:~3.7.4":
|
||||
version: 3.7.6
|
||||
resolution: "immutable@npm:3.7.6"
|
||||
checksum: 10c0/efe2bbb2620aa897afbb79545b9eda4dd3dc072e05ae7004895a7efb43187e4265612a88f8723f391eb1c87c46c52fd11e2d1968e42404450c63e49558d7ca4e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-tokens@npm:^3.0.0 || ^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "js-tokens@npm:4.0.0"
|
||||
checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"loose-envify@npm:^1.0.0":
|
||||
version: 1.4.0
|
||||
resolution: "loose-envify@npm:1.4.0"
|
||||
dependencies:
|
||||
js-tokens: "npm:^3.0.0 || ^4.0.0"
|
||||
bin:
|
||||
loose-envify: cli.js
|
||||
checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^2.6.12":
|
||||
version: 2.7.0
|
||||
resolution: "node-fetch@npm:2.7.0"
|
||||
dependencies:
|
||||
whatwg-url: "npm:^5.0.0"
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-assign@npm:^4.1.0, object-assign@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "object-assign@npm:4.1.1"
|
||||
checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"promise@npm:^7.1.1":
|
||||
version: 7.3.1
|
||||
resolution: "promise@npm:7.3.1"
|
||||
dependencies:
|
||||
asap: "npm:~2.0.3"
|
||||
checksum: 10c0/742e5c0cc646af1f0746963b8776299701ad561ce2c70b49365d62c8db8ea3681b0a1bf0d4e2fe07910bf72f02d39e51e8e73dc8d7503c3501206ac908be107f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"setimmediate@npm:^1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "setimmediate@npm:1.0.5"
|
||||
checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tr46@npm:~0.0.3":
|
||||
version: 0.0.3
|
||||
resolution: "tr46@npm:0.0.3"
|
||||
checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ua-parser-js@npm:^1.0.35":
|
||||
version: 1.0.39
|
||||
resolution: "ua-parser-js@npm:1.0.39"
|
||||
bin:
|
||||
ua-parser-js: script/cli.js
|
||||
checksum: 10c0/c6452b0c683000f10975cb0a7e74cb1119ea95d4522ae85f396fa53b0b17884358a24ffdd86a66030c6b2981bdc502109a618c79fdaa217ee9032c9e46fcc78a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webidl-conversions@npm:^3.0.0":
|
||||
version: 3.0.1
|
||||
resolution: "webidl-conversions@npm:3.0.1"
|
||||
checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-url@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "whatwg-url@npm:5.0.0"
|
||||
dependencies:
|
||||
tr46: "npm:~0.0.3"
|
||||
webidl-conversions: "npm:^3.0.0"
|
||||
checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Simple library for handling keyboard shortcuts",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"packageManager": "yarn@4.3.1",
|
||||
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
|
||||
"author": "Craig Campbell",
|
||||
"license": "Apache-2.0 WITH LLVM-exception"
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 8
|
||||
cacheKey: 10c0
|
||||
|
||||
"@penpot/mousetrap@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@penpot/mousetrap@workspace:."
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
17
frontend/patches/@zip.js__zip.js@2.8.11.patch
Normal file
@@ -0,0 +1,17 @@
|
||||
diff --git a/lib/zip-core-base.js b/lib/zip-core-base.js
|
||||
index 142155c8c3ab1a6caa7c370e8a2931a954556ca9..61b0fe6efb91312f437750e0002218f218afad8b 100644
|
||||
--- a/lib/zip-core-base.js
|
||||
+++ b/lib/zip-core-base.js
|
||||
@@ -28,12 +28,6 @@
|
||||
|
||||
import { configure } from "./core/configuration.js";
|
||||
|
||||
-try {
|
||||
- configure({ baseURI: import.meta.url });
|
||||
-} catch {
|
||||
- // ignored
|
||||
-}
|
||||
-
|
||||
export * from "./zip-core-reader.js";
|
||||
export * from "./zip-core-writer.js";
|
||||
export {
|
||||
@@ -80,7 +80,7 @@ export default defineConfig({
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
timeout: 2 * 60 * 1000,
|
||||
command: "yarn run e2e:server",
|
||||
command: "caddy file-server --root resources/public/ --listen :3000",
|
||||
url: "http://localhost:3000",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
|
||||
@@ -5,3 +5,19 @@ export const presenceFixture = {
|
||||
"~:profile-id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:topic": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
};
|
||||
|
||||
export const joinFixture2 = {
|
||||
"~:type": "~:join-file",
|
||||
"~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:session-id": "~u37730924-d520-80f1-8004-4ae6e5c3942e",
|
||||
"~:profile-id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:topic": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
};
|
||||
|
||||
export const joinFixture3 = {
|
||||
"~:type": "~:join-file",
|
||||
"~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:session-id": "~u37730924-d520-80f1-8004-4ae6e5c3942f",
|
||||
"~:profile-id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:topic": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
};
|
||||
|
||||
@@ -198,10 +198,10 @@ export class WorkspacePage extends BaseWebSocketPage {
|
||||
`[id="shape-00000000-0000-0000-0000-000000000000"]`,
|
||||
);
|
||||
this.toolbarOptions = page.getByTestId("toolbar-options");
|
||||
this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" });
|
||||
this.ellipseShapeButton = page.getByRole("button", { name: "Ellipse (E)" });
|
||||
this.moveButton = page.getByRole("button", { name: "Move (V)" });
|
||||
this.boardButton = page.getByRole("button", { name: "Board (B)" });
|
||||
this.rectShapeButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Rectangle" });
|
||||
this.ellipseShapeButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Ellipse" });
|
||||
this.moveButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Move" });
|
||||
this.boardButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Board" });
|
||||
this.toggleToolbarButton = page.getByRole("button", {
|
||||
name: "Toggle toolbar",
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 25 KiB |
@@ -189,8 +189,8 @@ test("BUG 7760 - Layout losing properties when changing parents", async ({
|
||||
await workspacePage.clickLeafLayer("Flex Board");
|
||||
|
||||
// Move the first board into the second
|
||||
const hAuto = await workspacePage.page.getByTitle("Fit content (Horizontal)");
|
||||
const vAuto = await workspacePage.page.getByTitle("Fit content (Vertical)");
|
||||
const hAuto = await workspacePage.page.getByTestId("behaviour-h-auto");
|
||||
const vAuto = await workspacePage.page.getByTestId("behaviour-v-auto");
|
||||
|
||||
await expect(vAuto.locator("input")).toBeChecked();
|
||||
await expect(hAuto.locator("input")).toBeChecked();
|
||||
|
||||
485
frontend/playwright/ui/specs/tokens/apply.spec.js
Normal file
@@ -0,0 +1,485 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../../pages/WorkspacePage";
|
||||
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
|
||||
import {
|
||||
setupEmptyTokensFile,
|
||||
setupTokensFile,
|
||||
setupTypographyTokensFile,
|
||||
unfoldTokenTree,
|
||||
} from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
|
||||
});
|
||||
|
||||
test.describe("Tokens: Apply token", () => {
|
||||
test("User applies color token to a shape", async ({ page }) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Button" })
|
||||
.click();
|
||||
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.black");
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "black" })
|
||||
.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Fill").click();
|
||||
|
||||
await expect(
|
||||
workspacePage.page.getByLabel("Name: colors.black"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies border-radius token to a shape from sidebar", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
// Open tokens sections on left sidebar
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
// Unfold border radius tokens
|
||||
await page.getByRole("button", { name: "Border Radius 3" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius" }),
|
||||
).toBeVisible();
|
||||
await tokensSidebar.getByRole("button", { name: "borderRadius" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius.sm" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Apply border radius token from token panels
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "borderRadius.sm" })
|
||||
.click();
|
||||
|
||||
// Check if border radius sections is visible on right sidebar
|
||||
const borderRadiusSection = page.getByRole("region", {
|
||||
name: "border-radius-section",
|
||||
});
|
||||
await expect(borderRadiusSection).toBeVisible();
|
||||
|
||||
// Check if token pill is visible on design tab on right sidebar
|
||||
const brTokenPillSM = borderRadiusSection.getByRole("button", {
|
||||
name: "borderRadius.sm",
|
||||
});
|
||||
await expect(brTokenPillSM).toBeVisible();
|
||||
await brTokenPillSM.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const brTokenOptionXl = borderRadiusSection.getByLabel("borderRadius.xl");
|
||||
await expect(brTokenOptionXl).toBeVisible();
|
||||
await brTokenOptionXl.click();
|
||||
|
||||
await expect(brTokenPillSM).not.toBeVisible();
|
||||
const brTokenPillXL = borderRadiusSection.getByRole("button", {
|
||||
name: "borderRadius.xl",
|
||||
});
|
||||
await expect(brTokenPillXL).toBeVisible();
|
||||
|
||||
// Detach token from design tab on right sidebar
|
||||
const detachButton = borderRadiusSection.getByRole("button", {
|
||||
name: "Detach token",
|
||||
});
|
||||
await detachButton.click();
|
||||
await expect(brTokenPillXL).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies opacity token to a shape from sidebar", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
// Open tokens sections on left sidebar
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
// Unfold opacity tokens
|
||||
await page.getByRole("button", { name: "Opacity 3" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "opacity", exact: true }),
|
||||
).toBeVisible();
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "opacity", exact: true })
|
||||
.click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "opacity.high" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Apply opacity token from token panels
|
||||
await tokensSidebar.getByRole("button", { name: "opacity.high" }).click();
|
||||
|
||||
// Check if opacity sections is visible on right sidebar
|
||||
const layerMenuSection = page.getByRole("region", {
|
||||
name: "layer-menu-section",
|
||||
});
|
||||
await expect(layerMenuSection).toBeVisible();
|
||||
|
||||
// Check if token pill is visible on design tab on right sidebar
|
||||
const opacityHighPill = layerMenuSection.getByRole("button", {
|
||||
name: "opacity.high",
|
||||
});
|
||||
await expect(opacityHighPill).toBeVisible();
|
||||
|
||||
// Detach token from design tab on right sidebar
|
||||
const detachButton = layerMenuSection.getByRole("button", {
|
||||
name: "Detach token",
|
||||
});
|
||||
await detachButton.click();
|
||||
|
||||
// Open dropdown from input
|
||||
const dropdownBtn = layerMenuSection.getByLabel('Open token list');
|
||||
await expect(dropdownBtn).toBeVisible();
|
||||
await dropdownBtn.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const opacityLowOption = layerMenuSection.getByRole('option', { name: 'opacity.low' });
|
||||
await expect(opacityLowOption).toBeVisible();
|
||||
await opacityLowOption.click();
|
||||
|
||||
await expect(opacityHighPill).not.toBeVisible();
|
||||
const opacityLowPill = layerMenuSection.getByRole("button", {
|
||||
name: "opacity.low",
|
||||
});
|
||||
await expect(opacityLowPill).toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies typography token to a text shape", async ({ page }) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Some Text" })
|
||||
.click();
|
||||
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button")
|
||||
.filter({ hasText: "Typography" })
|
||||
.click();
|
||||
|
||||
await tokensSidebar.getByRole("button", { name: "Full" }).click();
|
||||
|
||||
const fontSizeInput = workspacePage.rightSidebar.getByRole("textbox", {
|
||||
name: "Font Size",
|
||||
});
|
||||
await expect(fontSizeInput).toBeVisible();
|
||||
await expect(fontSizeInput).toHaveValue("100");
|
||||
});
|
||||
|
||||
test("User adds shadow token with multiple shadows and applies it to shape", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
workspacePage,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
await test.step("Stage 1: Basic open", async () => {
|
||||
// User adds shadow via the sidebar
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Shadow" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("primary");
|
||||
|
||||
// User adds first shadow with a color from the color ramp
|
||||
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-0",
|
||||
);
|
||||
await expect(firstShadowFields).toBeVisible();
|
||||
|
||||
// Fill in the shadow values
|
||||
const offsetXInput = firstShadowFields.getByLabel("X");
|
||||
const offsetYInput = firstShadowFields.getByLabel("Y");
|
||||
const blurInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Blur",
|
||||
});
|
||||
const spreadInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Spread",
|
||||
});
|
||||
|
||||
await offsetXInput.fill("2");
|
||||
await offsetYInput.fill("2");
|
||||
await blurInput.fill("4");
|
||||
await spreadInput.fill("0");
|
||||
|
||||
// Add color using the color picker
|
||||
const colorBullet = firstShadowFields.getByTestId(
|
||||
"token-form-color-bullet",
|
||||
);
|
||||
await colorBullet.click();
|
||||
|
||||
// Click on the color ramp to select a color
|
||||
const valueSaturationSelector = tokensUpdateCreateModal.getByTestId(
|
||||
"value-saturation-selector",
|
||||
);
|
||||
await expect(valueSaturationSelector).toBeVisible();
|
||||
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
|
||||
|
||||
// Verify that a color value was set
|
||||
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
await expect(colorInput).toHaveValue(/^rgb(.*)$/);
|
||||
|
||||
// Wait for validation to complete
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByText(/Resolved value:/).first(),
|
||||
).toBeVisible();
|
||||
|
||||
// Save button should be enabled
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeEnabled();
|
||||
});
|
||||
|
||||
await test.step("Stage 2: Shadow adding/removing works", async () => {
|
||||
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-0",
|
||||
);
|
||||
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
const firstColorValue = await colorInput.inputValue();
|
||||
|
||||
// User adds a second shadow
|
||||
const addButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Add Shadow",
|
||||
});
|
||||
await addButton.click();
|
||||
|
||||
const secondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-1",
|
||||
);
|
||||
await expect(secondShadowFields).toBeVisible();
|
||||
|
||||
// User adds a third shadow
|
||||
await addButton.click();
|
||||
|
||||
const thirdShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-2",
|
||||
);
|
||||
await expect(thirdShadowFields).toBeVisible();
|
||||
|
||||
// User adds values for the third shadow
|
||||
const thirdOffsetXInput = thirdShadowFields.getByLabel("X");
|
||||
const thirdOffsetYInput = thirdShadowFields.getByLabel("Y");
|
||||
const thirdBlurInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Blur",
|
||||
});
|
||||
const thirdSpreadInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Spread",
|
||||
});
|
||||
const thirdColorInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
|
||||
await thirdOffsetXInput.fill("10");
|
||||
await thirdOffsetYInput.fill("10");
|
||||
await thirdBlurInput.fill("20");
|
||||
await thirdSpreadInput.fill("5");
|
||||
await thirdColorInput.fill("#FF0000");
|
||||
|
||||
// User removes the 2nd shadow
|
||||
const removeButton2 = secondShadowFields.getByRole("button", {
|
||||
name: "Remove Shadow",
|
||||
});
|
||||
await removeButton2.click();
|
||||
|
||||
// Verify that we have only two shadow fields
|
||||
await expect(thirdShadowFields).not.toBeVisible();
|
||||
|
||||
// Verify that the first shadow kept its values
|
||||
const firstOffsetXValue = await firstShadowFields
|
||||
.getByLabel("X")
|
||||
.inputValue();
|
||||
const firstOffsetYValue = await firstShadowFields
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const firstBlurValue = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.inputValue();
|
||||
const firstSpreadValue = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.inputValue();
|
||||
const firstColorValueAfter = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.inputValue();
|
||||
|
||||
await expect(firstOffsetXValue).toBe("2");
|
||||
await expect(firstOffsetYValue).toBe("2");
|
||||
await expect(firstBlurValue).toBe("4");
|
||||
await expect(firstSpreadValue).toBe("0");
|
||||
await expect(firstColorValueAfter).toBe(firstColorValue);
|
||||
|
||||
// Verify that the second kept its values (after shadow 3)
|
||||
// After removing index 1, the third shadow becomes the second shadow at index 1
|
||||
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-1",
|
||||
);
|
||||
await expect(newSecondShadowFields).toBeVisible();
|
||||
|
||||
const secondOffsetXValue = await newSecondShadowFields
|
||||
.getByLabel("X")
|
||||
.inputValue();
|
||||
const secondOffsetYValue = await newSecondShadowFields
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const secondBlurValue = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.inputValue();
|
||||
const secondSpreadValue = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.inputValue();
|
||||
const secondColorValue = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.inputValue();
|
||||
|
||||
await expect(secondOffsetXValue).toBe("10");
|
||||
await expect(secondOffsetYValue).toBe("10");
|
||||
await expect(secondBlurValue).toBe("20");
|
||||
await expect(secondSpreadValue).toBe("5");
|
||||
await expect(secondColorValue).toBe("#FF0000");
|
||||
});
|
||||
|
||||
await test.step("Stage 3: Restore when switching tabs works", async () => {
|
||||
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-0",
|
||||
);
|
||||
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-1",
|
||||
);
|
||||
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
const firstColorValue = await colorInput.inputValue();
|
||||
|
||||
// Switch to reference tab
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceTabButton.click();
|
||||
|
||||
// Verify we're in reference mode - the composite fields should not be visible
|
||||
await expect(firstShadowFields).not.toBeVisible();
|
||||
|
||||
// Switch back to composite tab
|
||||
const compositeTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("composite-opt");
|
||||
await compositeTabButton.click();
|
||||
|
||||
// Verify that shadows are restored
|
||||
await expect(firstShadowFields).toBeVisible();
|
||||
await expect(newSecondShadowFields).toBeVisible();
|
||||
|
||||
// Verify first shadow values are still there
|
||||
const restoredFirstOffsetX = await firstShadowFields
|
||||
.getByLabel("X")
|
||||
.inputValue();
|
||||
const restoredFirstOffsetY = await firstShadowFields
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const restoredFirstBlur = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.inputValue();
|
||||
const restoredFirstSpread = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.inputValue();
|
||||
const restoredFirstColor = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.inputValue();
|
||||
|
||||
await expect(restoredFirstOffsetX).toBe("2");
|
||||
await expect(restoredFirstOffsetY).toBe("2");
|
||||
await expect(restoredFirstBlur).toBe("4");
|
||||
await expect(restoredFirstSpread).toBe("0");
|
||||
await expect(restoredFirstColor).toBe(firstColorValue);
|
||||
|
||||
// Verify second shadow values are still there
|
||||
const restoredSecondOffsetX = await newSecondShadowFields
|
||||
.getByLabel("X")
|
||||
.inputValue();
|
||||
const restoredSecondOffsetY = await newSecondShadowFields
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const restoredSecondBlur = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.inputValue();
|
||||
const restoredSecondSpread = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.inputValue();
|
||||
const restoredSecondColor = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.inputValue();
|
||||
|
||||
await expect(restoredSecondOffsetX).toBe("10");
|
||||
await expect(restoredSecondOffsetY).toBe("10");
|
||||
await expect(restoredSecondBlur).toBe("20");
|
||||
await expect(restoredSecondSpread).toBe("5");
|
||||
await expect(restoredSecondColor).toBe("#FF0000");
|
||||
});
|
||||
|
||||
await test.step("Stage 4: Layer application works", async () => {
|
||||
// Save the token
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "shadow", "primary");
|
||||
|
||||
// Verify token appears in sidebar
|
||||
const shadowToken = tokensSidebar.getByRole("button", {
|
||||
name: "primary",
|
||||
});
|
||||
await expect(shadowToken).toBeEnabled();
|
||||
|
||||
// Apply the shadow
|
||||
await workspacePage.clickLayers();
|
||||
await workspacePage.clickLeafLayer("Button");
|
||||
|
||||
const shadowSection = workspacePage.rightSidebar.getByText("Drop shadow");
|
||||
await expect(shadowSection).toHaveCount(0);
|
||||
|
||||
await page.getByRole("tab", { name: "Tokens" }).click();
|
||||
await shadowToken.click();
|
||||
|
||||
await expect(shadowSection).toHaveCount(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
22
frontend/playwright/ui/specs/tokens/general.spec.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../../pages/WorkspacePage";
|
||||
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
|
||||
import { setupEmptyTokensFile } from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
|
||||
});
|
||||
|
||||
test.describe("Tokens tab - common tests", () => {
|
||||
test("Clicking tokens tab button opens tokens sidebar tab", async ({
|
||||
page,
|
||||
}) => {
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
await expect(tokensTabPanel).toHaveText(/TOKENS/);
|
||||
await expect(tokensTabPanel).toHaveText(/Themes/);
|
||||
});
|
||||
});
|
||||
266
frontend/playwright/ui/specs/tokens/helpers.js
Normal file
@@ -0,0 +1,266 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../../pages/WorkspacePage";
|
||||
|
||||
const setupEmptyTokensFile = async (page, options = {}) => {
|
||||
const { flags = [] } = options;
|
||||
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
if (flags.length > 0) {
|
||||
await workspacePage.mockConfigFlags(flags);
|
||||
}
|
||||
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPC(
|
||||
"get-team?id=*",
|
||||
"workspace/get-team-tokens.json",
|
||||
);
|
||||
|
||||
await workspacePage.mockRPC(
|
||||
"update-file?id=*",
|
||||
"workspace/update-file-create-rect.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "c7ce0794-0992-8105-8004-38f280443849",
|
||||
pageId: "66697432-c33d-8055-8006-2c62cc084cad",
|
||||
});
|
||||
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
return {
|
||||
workspacePage,
|
||||
tokenThemeUpdateCreateModal: workspacePage.tokenThemeUpdateCreateModal,
|
||||
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
|
||||
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
|
||||
tokenSetItems: workspacePage.tokenSetItems,
|
||||
tokensSidebar: workspacePage.tokensSidebar,
|
||||
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
|
||||
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
|
||||
};
|
||||
};
|
||||
|
||||
const setupTokensFile = async (page, options = {}) => {
|
||||
const {
|
||||
file = "workspace/get-file-tokens.json",
|
||||
fileFragment = "workspace/get-file-fragment-tokens.json",
|
||||
flags = ["enable-feature-token-input"],
|
||||
} = options;
|
||||
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
if (flags.length > 0) {
|
||||
await workspacePage.mockConfigFlags(flags);
|
||||
}
|
||||
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPC(
|
||||
"get-team?id=*",
|
||||
"workspace/get-team-tokens.json",
|
||||
);
|
||||
await workspacePage.mockRPC(/get\-file\?/, file);
|
||||
await workspacePage.mockRPC(/get\-file\-fragment\?/, fileFragment);
|
||||
await workspacePage.mockRPC(
|
||||
"update-file?id=*",
|
||||
"workspace/update-file-create-rect.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "c7ce0794-0992-8105-8004-38f280443849",
|
||||
pageId: "66697432-c33d-8055-8006-2c62cc084cad",
|
||||
});
|
||||
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
return {
|
||||
workspacePage,
|
||||
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
|
||||
tokenThemeUpdateCreateModal: workspacePage.tokenThemeUpdateCreateModal,
|
||||
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
|
||||
tokenSetItems: workspacePage.tokenSetItems,
|
||||
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
|
||||
tokensSidebar: workspacePage.tokensSidebar,
|
||||
tokenContextMenuForToken: workspacePage.tokenContextMenuForToken,
|
||||
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
|
||||
};
|
||||
};
|
||||
|
||||
const setupTypographyTokensFile = async (page, options = {}) => {
|
||||
return setupTokensFile(page, {
|
||||
file: "workspace/get-file-typography-tokens.json",
|
||||
fileFragment: "workspace/get-file-fragment-typography-tokens.json",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
const testTokenCreationFlow = async (
|
||||
page,
|
||||
{
|
||||
tokenLabel,
|
||||
namePlaceholder,
|
||||
valuePlaceholder,
|
||||
validValue,
|
||||
invalidValue,
|
||||
selfReferenceValue,
|
||||
missingReferenceValue,
|
||||
secondValidValue,
|
||||
resolvedValueText,
|
||||
secondResolvedValueText,
|
||||
},
|
||||
) => {
|
||||
const invalidValueError = "Invalid token value";
|
||||
const emptyNameError = "Name should be at least 1 character";
|
||||
const selfReferenceError = "Token has self reference";
|
||||
const missingReferenceError = "Missing token references";
|
||||
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
// Open modal
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
const addTokenButton = tokensTabPanel.getByRole("button", {
|
||||
name: `Add Token: ${tokenLabel}`,
|
||||
});
|
||||
|
||||
await addTokenButton.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
// Placeholder checks
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByPlaceholder(namePlaceholder),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByPlaceholder(valuePlaceholder),
|
||||
).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
|
||||
// 1. Name filled + empty value → disabled
|
||||
await nameField.fill("my-token");
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// 2. Invalid value → disabled + error message
|
||||
await valueField.fill(invalidValue);
|
||||
|
||||
const invalidValueErrorNode =
|
||||
tokensUpdateCreateModal.getByText(invalidValueError);
|
||||
|
||||
await expect(invalidValueErrorNode).toBeVisible();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// 3. Empty name → disabled + error message
|
||||
await nameField.fill("");
|
||||
|
||||
const emptyNameErrorNode = tokensUpdateCreateModal.getByText(emptyNameError);
|
||||
|
||||
await expect(emptyNameErrorNode).toBeVisible();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// 4. Self reference → disabled + error message
|
||||
await nameField.fill("my-token");
|
||||
await valueField.fill(selfReferenceValue);
|
||||
|
||||
const selfRefErrorNode =
|
||||
tokensUpdateCreateModal.getByText(selfReferenceError);
|
||||
|
||||
await expect(selfRefErrorNode).toBeVisible();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// 5. Missing reference → disabled + error message
|
||||
await valueField.fill(missingReferenceValue);
|
||||
|
||||
const missingRefErrorNode = tokensUpdateCreateModal.getByText(
|
||||
missingReferenceError,
|
||||
);
|
||||
|
||||
await expect(missingRefErrorNode).toBeVisible();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
//
|
||||
// ------- SUCCESSFUL CREATION -------
|
||||
//
|
||||
|
||||
// 6. Basic valid value → enabled
|
||||
await valueField.fill(validValue);
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByText(resolvedValueText),
|
||||
).toBeVisible();
|
||||
await expect(submitButton).toBeEnabled();
|
||||
|
||||
await submitButton.click();
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token" }),
|
||||
).toBeEnabled();
|
||||
|
||||
//
|
||||
// ------- SECOND TOKEN WITH VALID REFERENCE -------
|
||||
//
|
||||
|
||||
await addTokenButton.click();
|
||||
|
||||
await nameField.fill("my-token-2");
|
||||
await valueField.fill(secondValidValue);
|
||||
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByText(secondResolvedValueText),
|
||||
).toBeVisible();
|
||||
await expect(submitButton).toBeEnabled();
|
||||
|
||||
await submitButton.click();
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
|
||||
).toBeEnabled();
|
||||
};
|
||||
|
||||
const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => {
|
||||
const tokenSegments = tokenName.split(".");
|
||||
const tokenFolderTree = tokenSegments.slice(0, -1);
|
||||
const tokenLeafName = tokenSegments.pop();
|
||||
|
||||
const typeParentWrapper = tokensTabPanel.getByTestId(`section-${type}`);
|
||||
const typeSectionButton = typeParentWrapper
|
||||
.getByRole("button", {
|
||||
name: type,
|
||||
})
|
||||
.first();
|
||||
|
||||
const isSectionExpanded =
|
||||
await typeSectionButton.getAttribute("aria-expanded");
|
||||
|
||||
if (isSectionExpanded === "false") {
|
||||
await typeSectionButton.click();
|
||||
}
|
||||
|
||||
for (const segment of tokenFolderTree) {
|
||||
const segmentButton = typeParentWrapper
|
||||
.getByRole("listitem")
|
||||
.getByRole("button", { name: segment })
|
||||
.first();
|
||||
|
||||
const isExpanded = await segmentButton.getAttribute("aria-expanded");
|
||||
if (isExpanded === "false") {
|
||||
await segmentButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
await expect(
|
||||
typeParentWrapper.getByRole("button", {
|
||||
name: tokenLeafName,
|
||||
}),
|
||||
).toBeEnabled();
|
||||
};
|
||||
|
||||
export {
|
||||
setupEmptyTokensFile,
|
||||
setupTokensFile,
|
||||
setupTypographyTokensFile,
|
||||
testTokenCreationFlow,
|
||||
unfoldTokenTree,
|
||||
};
|
||||
651
frontend/playwright/ui/specs/tokens/remapping.spec.js
Normal file
@@ -0,0 +1,651 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../../pages/WorkspacePage";
|
||||
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
|
||||
import {
|
||||
setupEmptyTokensFile,
|
||||
setupTokensFile,
|
||||
setupTypographyTokensFile,
|
||||
} from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
|
||||
});
|
||||
|
||||
test.describe("Tokens: Remapping Feature", () => {
|
||||
test.describe("Box Shadow Token Remapping", () => {
|
||||
test("User renames box shadow token with alias references", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base shadow token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Shadow" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("base-shadow");
|
||||
|
||||
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
await colorField.fill("#000000");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived shadow token that references base-shadow
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Shadow" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Name",
|
||||
});
|
||||
await nameField.fill("derived-shadow");
|
||||
|
||||
const referenceToggle =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{base-shadow}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Rename base-shadow token
|
||||
const baseToken = tokensSidebar.getByRole("button", {
|
||||
name: "base-shadow",
|
||||
});
|
||||
await baseToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("foundation-shadow");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Check for remapping modal
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
await expect(remappingModal).toContainText("1");
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "foundation-shadow" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "derived-shadow" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User renames and updates shadow token - referenced token and applied shapes update", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
workspacePage,
|
||||
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base shadow token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Shadow" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("primary-shadow");
|
||||
|
||||
let colorField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
await colorField.fill("#000000");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived shadow token that references base
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Shadow" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("card-shadow");
|
||||
|
||||
const referenceToggle =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{primary-shadow}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Apply the referenced token to a shape
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Button" })
|
||||
.click();
|
||||
|
||||
await page.getByRole("tab", { name: "Tokens" }).click();
|
||||
const cardShadowToken = tokensSidebar.getByRole("button", {
|
||||
name: "card-shadow",
|
||||
});
|
||||
await cardShadowToken.click();
|
||||
|
||||
// Rename and update value of base token
|
||||
const primaryToken = tokensSidebar.getByRole("button", {
|
||||
name: "primary-shadow",
|
||||
});
|
||||
await primaryToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("main-shadow");
|
||||
|
||||
// Update the color value
|
||||
colorField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
await colorField.fill("#FF0000");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Confirm remapping
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify base token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "main-shadow" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify referenced token still exists
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "card-shadow" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify the shape still has the token applied with the NEW name
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Button" })
|
||||
.click();
|
||||
|
||||
// Verify the shape still has the shadow applied with the UPDATED color value
|
||||
// Expand the shadow section to access the color field
|
||||
const shadowSection =
|
||||
workspacePage.rightSidebar.getByTestId("shadow-section");
|
||||
await expect(shadowSection).toBeVisible();
|
||||
|
||||
// Click to expand the shadow options (the menu button)
|
||||
const shadowMenuButton = shadowSection
|
||||
.getByRole("button", { name: "options" })
|
||||
.first();
|
||||
await shadowMenuButton.click();
|
||||
|
||||
// Wait for the advanced options to appear
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify the color value has updated from #000000 to #FF0000
|
||||
const colorInput = shadowSection.getByRole("textbox", { name: "Color" });
|
||||
expect(colorInput).not.toBeNull();
|
||||
const colorValue = await colorInput.inputValue();
|
||||
expect(colorValue.toUpperCase()).toBe("FF0000");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Typography Token Remapping", () => {
|
||||
test("User renames typography token with alias references", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTypographyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base typography token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("base-text");
|
||||
|
||||
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Font size",
|
||||
});
|
||||
await fontSizeField.fill("16");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived typography token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Name",
|
||||
});
|
||||
await nameField.fill("body-text");
|
||||
|
||||
const referenceToggle =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{base-text}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Rename base token
|
||||
const baseToken = tokensSidebar.getByRole("button", {
|
||||
name: "base-text",
|
||||
});
|
||||
await baseToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("default-text");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Check for remapping modal
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "default-text" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "body-text" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User renames and updates typography token - referenced token and applied shapes update", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
workspacePage,
|
||||
} = await setupTypographyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base typography token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("body-style");
|
||||
|
||||
let fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Font size",
|
||||
});
|
||||
await fontSizeField.fill("16");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived typography token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Name",
|
||||
});
|
||||
await nameField.fill("paragraph-style");
|
||||
|
||||
const referenceToggle =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{body-style}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Apply the referenced token to a text shape
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Some Text" })
|
||||
.click();
|
||||
|
||||
await page.getByRole("tab", { name: "Tokens" }).click();
|
||||
const paragraphToken = tokensSidebar.getByRole("button", {
|
||||
name: "paragraph-style",
|
||||
});
|
||||
await paragraphToken.click();
|
||||
|
||||
// Rename and update value of base token
|
||||
const bodyToken = tokensSidebar.getByRole("button", {
|
||||
name: "body-style",
|
||||
});
|
||||
await bodyToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("text-base");
|
||||
|
||||
// Update the font size value
|
||||
fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Font size",
|
||||
});
|
||||
await fontSizeField.fill("18");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Confirm remapping
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify base token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "text-base" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify referenced token still exists
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "paragraph-style" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify the text shape still has the token applied with NEW name and value
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Some Text" })
|
||||
.click();
|
||||
|
||||
// Verify the shape shows the updated font size value (18)
|
||||
// This proves the remapping worked and the value update propagated through the reference
|
||||
const fontSizeInput = workspacePage.rightSidebar.getByRole("textbox", {
|
||||
name: "Font Size",
|
||||
});
|
||||
await expect(fontSizeInput).toBeVisible();
|
||||
await expect(fontSizeInput).toHaveValue("18");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Border Radius Token Remapping", () => {
|
||||
test("User renames border radius token with alias references", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base border radius token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("base-radius");
|
||||
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField.fill("4");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived border radius token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("card-radius");
|
||||
|
||||
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField2.fill("{base-radius}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Rename base token
|
||||
const baseToken = tokensSidebar.getByRole("button", {
|
||||
name: "base-radius",
|
||||
});
|
||||
await baseToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("primary-radius");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Check for remapping modal
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "primary-radius" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "card-radius" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User renames and updates border radius token - referenced token updates", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base border radius token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("radius-sm");
|
||||
|
||||
let valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField.fill("4");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived border radius token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("button-radius");
|
||||
|
||||
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField2.fill("{radius-sm}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Rename and update value of base token
|
||||
const radiusToken = tokensSidebar.getByRole("button", {
|
||||
name: "radius-sm",
|
||||
});
|
||||
await radiusToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("radius-base");
|
||||
|
||||
// Update the value
|
||||
valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField.fill("8");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Confirm remapping
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify base token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "radius-base" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify referenced token still exists
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "button-radius" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify the referenced token now points to the renamed token
|
||||
// by opening it and checking the reference
|
||||
const buttonRadiusToken = tokensSidebar.getByRole("button", {
|
||||
name: "button-radius",
|
||||
});
|
||||
await buttonRadiusToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
const currentValue = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await expect(currentValue).toHaveValue("{radius-base}");
|
||||
});
|
||||
});
|
||||
});
|
||||
219
frontend/playwright/ui/specs/tokens/sets.spec.js
Normal file
@@ -0,0 +1,219 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
|
||||
import { WorkspacePage } from "../../pages/WorkspacePage";
|
||||
import { setupEmptyTokensFile, setupTokensFile } from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
|
||||
});
|
||||
|
||||
test.describe("Tokens: Sets Tab", () => {
|
||||
const changeSetInput = async (sidebar, setName, finalKey = "Enter") => {
|
||||
const setInput = sidebar.locator("input:focus");
|
||||
await expect(setInput).toBeVisible();
|
||||
await setInput.fill(setName);
|
||||
await setInput.press(finalKey);
|
||||
};
|
||||
|
||||
const createSet = async (sidebar, setName, finalKey = "Enter") => {
|
||||
const tokensTabButton = sidebar
|
||||
.getByRole("button", { name: "Add set" })
|
||||
.click();
|
||||
|
||||
await changeSetInput(sidebar, setName, (finalKey = "Enter"));
|
||||
};
|
||||
|
||||
const assertEmptySetsList = async (el) => {
|
||||
const buttons = await el.getByRole("button").allTextContents();
|
||||
const filteredButtons = buttons.filter((text) => text === "Create one.");
|
||||
await expect(filteredButtons.length).toEqual(2); // We assume there are no themes, so we have two "Create one" buttons.
|
||||
};
|
||||
|
||||
const assertSetsList = async (el, sets) => {
|
||||
const buttons = await el.getByRole("button").allTextContents();
|
||||
const filteredButtons = buttons.filter(
|
||||
(text) => text && text !== "Create one.",
|
||||
);
|
||||
await expect(filteredButtons).toEqual(sets);
|
||||
};
|
||||
|
||||
test("User creates sets tree structure by entering a set path", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokenThemesSetsSidebar, tokenContextMenuForSet } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
const tokensTabButton = tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "Add set" })
|
||||
.click();
|
||||
|
||||
await createSet(tokenThemesSetsSidebar, "core/colors/light");
|
||||
await createSet(tokenThemesSetsSidebar, "core/colors/dark");
|
||||
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
"core",
|
||||
"colors",
|
||||
"light",
|
||||
"dark",
|
||||
]);
|
||||
|
||||
// User renames set
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "light" })
|
||||
.click({ button: "right" });
|
||||
await expect(tokenContextMenuForSet).toBeVisible();
|
||||
await tokenContextMenuForSet.getByText("Rename").click();
|
||||
await changeSetInput(tokenThemesSetsSidebar, "light-renamed");
|
||||
|
||||
// User cancels during editing
|
||||
await createSet(tokenThemesSetsSidebar, "core/colors/dark", "Escape");
|
||||
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
"core",
|
||||
"colors",
|
||||
"light-renamed",
|
||||
"dark",
|
||||
]);
|
||||
|
||||
// Creates nesting by renaming set with double click
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "light-renamed" })
|
||||
.click({ button: "right" });
|
||||
await expect(tokenContextMenuForSet).toBeVisible();
|
||||
await tokenContextMenuForSet.getByText("Rename").click();
|
||||
await changeSetInput(tokenThemesSetsSidebar, "nested/light");
|
||||
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
"core",
|
||||
"colors",
|
||||
"nested",
|
||||
"light",
|
||||
"dark",
|
||||
]);
|
||||
|
||||
// Create set in group
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "core" })
|
||||
.click({ button: "right" });
|
||||
await expect(tokenContextMenuForSet).toBeVisible();
|
||||
await tokenContextMenuForSet.getByText("Add set to this group").click();
|
||||
await changeSetInput(tokenThemesSetsSidebar, "sizes/small");
|
||||
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
"core",
|
||||
"colors",
|
||||
"nested",
|
||||
"light",
|
||||
"dark",
|
||||
"sizes",
|
||||
"small",
|
||||
]);
|
||||
|
||||
// User deletes set
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "nested" })
|
||||
.click({ button: "right" });
|
||||
await expect(tokenContextMenuForSet).toBeVisible();
|
||||
await tokenContextMenuForSet.getByText("Delete").click();
|
||||
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
"core",
|
||||
"colors",
|
||||
"dark",
|
||||
"sizes",
|
||||
"small",
|
||||
]);
|
||||
|
||||
// User deletes all sets
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "core" })
|
||||
.click({ button: "right" });
|
||||
await expect(tokenContextMenuForSet).toBeVisible();
|
||||
await tokenContextMenuForSet.getByText("Delete").click();
|
||||
|
||||
await assertEmptySetsList(tokenThemesSetsSidebar);
|
||||
});
|
||||
|
||||
test("User can create & edit sets and set groups with an identical name", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokenThemesSetsSidebar, tokenContextMenuForSet } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
const tokensTabButton = tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "Add set" })
|
||||
.click();
|
||||
|
||||
await createSet(tokenThemesSetsSidebar, "core/colors");
|
||||
await createSet(tokenThemesSetsSidebar, "core");
|
||||
await assertSetsList(tokenThemesSetsSidebar, ["core", "colors", "core"]);
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "core" })
|
||||
.nth(0)
|
||||
.dblclick();
|
||||
await changeSetInput(tokenThemesSetsSidebar, "core-group-renamed");
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
"core-group-renamed",
|
||||
"colors",
|
||||
"core",
|
||||
]);
|
||||
|
||||
await page.keyboard.press(`ControlOrMeta+z`);
|
||||
await assertSetsList(tokenThemesSetsSidebar, ["core", "colors", "core"]);
|
||||
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "core" })
|
||||
.nth(1)
|
||||
.dblclick();
|
||||
await changeSetInput(tokenThemesSetsSidebar, "core-set-renamed");
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
"core",
|
||||
"colors",
|
||||
"core-set-renamed",
|
||||
]);
|
||||
});
|
||||
|
||||
test("Fold/Unfold set", async ({ page }) => {
|
||||
const { tokenThemesSetsSidebar, tokenSetGroupItems } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await expect(tokenThemesSetsSidebar).toBeVisible();
|
||||
|
||||
const darkSet = tokenThemesSetsSidebar.getByRole("button", {
|
||||
name: "dark",
|
||||
exact: true,
|
||||
});
|
||||
|
||||
await expect(darkSet).toBeVisible();
|
||||
|
||||
const setGroup = await tokenSetGroupItems
|
||||
.filter({ hasText: "LightDark" })
|
||||
.first();
|
||||
|
||||
const setCollapsable = await setGroup
|
||||
.getByTestId("tokens-set-group-collapse")
|
||||
.first();
|
||||
|
||||
await setCollapsable.click();
|
||||
|
||||
await expect(darkSet).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("Change current theme", async ({ page }) => {
|
||||
const { tokenThemesSetsSidebar, tokenSetItems } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await expect(tokenSetItems.nth(1)).toHaveAttribute("aria-checked", "true");
|
||||
await expect(tokenSetItems.nth(2)).toHaveAttribute("aria-checked", "false");
|
||||
|
||||
await tokenThemesSetsSidebar.getByTestId("theme-select").click();
|
||||
await page
|
||||
.getByTestId("theme-select-dropdown")
|
||||
.getByRole("option", { name: "Dark", exact: true })
|
||||
.click();
|
||||
|
||||
await expect(tokenSetItems.nth(1)).toHaveAttribute("aria-checked", "false");
|
||||
await expect(tokenSetItems.nth(2)).toHaveAttribute("aria-checked", "true");
|
||||
});
|
||||
});
|
||||
285
frontend/playwright/ui/specs/tokens/themes.spec.js
Normal file
@@ -0,0 +1,285 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../../pages/WorkspacePage";
|
||||
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
|
||||
import { setupEmptyTokensFile, setupTokensFile } from "./helpers";
|
||||
|
||||
// THEMES HELPERS
|
||||
|
||||
const checkInputFieldWithError = async (
|
||||
tokenThemeUpdateCreateModal,
|
||||
inputLocator,
|
||||
) => {
|
||||
await expect(inputLocator).toHaveAttribute("aria-invalid", "true");
|
||||
|
||||
const errorMessageId = await inputLocator.getAttribute("aria-describedby");
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.locator(`#${errorMessageId}`),
|
||||
).toBeVisible();
|
||||
};
|
||||
|
||||
const checkInputFieldWithoutError = async (inputLocator) => {
|
||||
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
|
||||
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
|
||||
});
|
||||
|
||||
test.describe("Tokens Themes", () => {
|
||||
test("User edits theme and activates it in the sidebar", async ({ page }) => {
|
||||
const { tokenThemesSetsSidebar, tokenThemeUpdateCreateModal } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await expect(tokenThemesSetsSidebar).toBeVisible();
|
||||
|
||||
await tokenThemesSetsSidebar.getByRole("button", { name: "Edit" }).click();
|
||||
|
||||
await expect(tokenThemeUpdateCreateModal).toBeVisible();
|
||||
await tokenThemeUpdateCreateModal
|
||||
.getByRole("button", { name: "sets" })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await tokenThemeUpdateCreateModal.getByLabel("Theme").fill("Changed");
|
||||
|
||||
const lightDarkSetGroup = tokenThemeUpdateCreateModal.getByTestId(
|
||||
"tokens-set-group-item",
|
||||
{
|
||||
name: "LightDark",
|
||||
exact: true,
|
||||
},
|
||||
);
|
||||
await expect(lightDarkSetGroup).toBeVisible();
|
||||
|
||||
const lightSet = tokenThemeUpdateCreateModal.getByRole("button", {
|
||||
name: "light",
|
||||
exact: true,
|
||||
});
|
||||
const darkSet = tokenThemeUpdateCreateModal.getByRole("button", {
|
||||
name: "dark",
|
||||
exact: true,
|
||||
});
|
||||
|
||||
// Mixed set group
|
||||
await expect(lightSet.getByRole("checkbox")).toBeChecked();
|
||||
await expect(darkSet.getByRole("checkbox")).not.toBeChecked();
|
||||
|
||||
// Disable all
|
||||
await lightDarkSetGroup.getByRole("checkbox").click();
|
||||
await expect(lightSet.getByRole("checkbox")).not.toBeChecked();
|
||||
await expect(darkSet.getByRole("checkbox")).not.toBeChecked();
|
||||
|
||||
// Enable all
|
||||
await lightDarkSetGroup.getByRole("checkbox").click();
|
||||
await expect(lightSet.getByRole("checkbox")).toBeChecked();
|
||||
await expect(darkSet.getByRole("checkbox")).toBeChecked();
|
||||
|
||||
await tokenThemeUpdateCreateModal
|
||||
.getByRole("button", {
|
||||
name: "save theme",
|
||||
})
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByText("Changed" + "4 active sets"),
|
||||
).toBeVisible();
|
||||
|
||||
await tokenThemeUpdateCreateModal
|
||||
.getByRole("button")
|
||||
.getByText("close")
|
||||
.click();
|
||||
await expect(tokenThemeUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
const themeSelect = tokenThemesSetsSidebar.getByRole("combobox");
|
||||
|
||||
await themeSelect.click();
|
||||
await page
|
||||
.getByTestId("theme-select-dropdown")
|
||||
.getByRole("option", { name: "Changed" })
|
||||
.click();
|
||||
|
||||
const sidebarLightSet = tokenThemesSetsSidebar.getByRole("button", {
|
||||
name: "light",
|
||||
exact: true,
|
||||
});
|
||||
const sidebarDarkSet = tokenThemesSetsSidebar.getByRole("button", {
|
||||
name: "dark",
|
||||
exact: true,
|
||||
});
|
||||
|
||||
await expect(sidebarLightSet.getByRole("checkbox")).toBeChecked();
|
||||
await expect(sidebarDarkSet.getByRole("checkbox")).toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Tokens: Themes modal", () => {
|
||||
test("Delete theme", async ({ page }) => {
|
||||
const { tokenThemeUpdateCreateModal, workspacePage } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
workspacePage.openTokenThemesModal();
|
||||
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByRole("button", { name: "Delete theme" }),
|
||||
).toHaveCount(2);
|
||||
|
||||
await tokenThemeUpdateCreateModal
|
||||
.getByRole("button", { name: "Delete theme" })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByRole("button", { name: "Delete theme" }),
|
||||
).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("Add new theme in empty file", async ({ page }) => {
|
||||
const { tokenThemesSetsSidebar, tokenThemeUpdateCreateModal } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "Create one." })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await expect(tokenThemeUpdateCreateModal).toBeVisible();
|
||||
|
||||
const groupInput = tokenThemeUpdateCreateModal.getByLabel("Group");
|
||||
const nameInput = tokenThemeUpdateCreateModal.getByLabel("Theme");
|
||||
const saveButton = tokenThemeUpdateCreateModal.getByRole("button", {
|
||||
name: "Save theme",
|
||||
});
|
||||
|
||||
await groupInput.fill("New Group name");
|
||||
await nameInput.fill("New Theme name");
|
||||
|
||||
await checkInputFieldWithoutError(tokenThemeUpdateCreateModal);
|
||||
await expect(saveButton).not.toBeDisabled();
|
||||
|
||||
await saveButton.click();
|
||||
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByText("New Theme name"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByText("New Group name"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("Add new theme", async ({ page }) => {
|
||||
const { tokenThemeUpdateCreateModal, workspacePage } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
workspacePage.openTokenThemesModal();
|
||||
|
||||
await tokenThemeUpdateCreateModal
|
||||
.getByRole("button", {
|
||||
name: "Add new theme",
|
||||
})
|
||||
.click();
|
||||
|
||||
const groupInput = tokenThemeUpdateCreateModal.getByLabel("Group");
|
||||
const nameInput = tokenThemeUpdateCreateModal.getByLabel("Theme");
|
||||
const saveButton = tokenThemeUpdateCreateModal.getByRole("button", {
|
||||
name: "Save theme",
|
||||
});
|
||||
|
||||
await groupInput.fill("Core"); // Invalid because "Core / Light" theme already exists
|
||||
await nameInput.fill("Light");
|
||||
|
||||
await checkInputFieldWithError(tokenThemeUpdateCreateModal, nameInput);
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
await groupInput.fill("New Group name");
|
||||
await nameInput.fill("New Theme name");
|
||||
|
||||
await checkInputFieldWithoutError(tokenThemeUpdateCreateModal);
|
||||
await expect(saveButton).not.toBeDisabled();
|
||||
|
||||
await saveButton.click();
|
||||
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByText("New Theme name"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByText("New Group name"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("Edit theme", async ({ page }) => {
|
||||
const { tokenThemeUpdateCreateModal, workspacePage } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
workspacePage.openTokenThemesModal();
|
||||
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByText("no sets"),
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByText("3 active sets"),
|
||||
).toHaveCount(2);
|
||||
|
||||
await tokenThemeUpdateCreateModal
|
||||
.getByText("3 active sets")
|
||||
.first()
|
||||
.click();
|
||||
|
||||
const groupInput = tokenThemeUpdateCreateModal.getByLabel("Group");
|
||||
const nameInput = tokenThemeUpdateCreateModal.getByLabel("Theme");
|
||||
const saveButton = tokenThemeUpdateCreateModal.getByRole("button", {
|
||||
name: "Save theme",
|
||||
});
|
||||
|
||||
await groupInput.fill("Core"); // Invalid because "Core / Dark" theme already exists
|
||||
await nameInput.fill("Dark");
|
||||
|
||||
await checkInputFieldWithError(tokenThemeUpdateCreateModal, nameInput);
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
await groupInput.fill("Core"); // Valid because "Core / Light" theme already exists
|
||||
await nameInput.fill("Light"); // but it's the same theme we are editing
|
||||
|
||||
await checkInputFieldWithoutError(tokenThemeUpdateCreateModal);
|
||||
await expect(saveButton).not.toBeDisabled();
|
||||
|
||||
await nameInput.fill("Changed Theme name"); // New names should be also valid
|
||||
await groupInput.fill("Changed Group name");
|
||||
|
||||
await checkInputFieldWithoutError(tokenThemeUpdateCreateModal);
|
||||
await expect(saveButton).not.toBeDisabled();
|
||||
|
||||
expect(await nameInput.getAttribute("aria-invalid")).toBeNull();
|
||||
expect(await nameInput.getAttribute("aria-describedby")).toBeNull();
|
||||
|
||||
const checkboxes = await tokenThemeUpdateCreateModal
|
||||
.locator('[role="checkbox"]')
|
||||
.all();
|
||||
|
||||
for (const checkbox of checkboxes) {
|
||||
const isChecked = await checkbox.getAttribute("aria-checked");
|
||||
|
||||
if (isChecked === "true") {
|
||||
await checkbox.click();
|
||||
}
|
||||
}
|
||||
|
||||
const firstButton = await tokenThemeUpdateCreateModal
|
||||
.getByTestId("tokens-set-item")
|
||||
.first();
|
||||
|
||||
await firstButton.click();
|
||||
|
||||
await expect(saveButton).not.toBeDisabled();
|
||||
|
||||
await saveButton.click();
|
||||
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByText("Changed Theme name"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokenThemeUpdateCreateModal.getByText("Changed Group name"),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
32
frontend/playwright/ui/specs/tokens/tree.spec.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../../pages/WorkspacePage";
|
||||
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
|
||||
import { setupTokensFile, unfoldTokenTree } from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
|
||||
});
|
||||
|
||||
test.describe("Tokens - node tree", () => {
|
||||
test("User fold/unfold color tokens", async ({ page }) => {
|
||||
const { tokensSidebar } = await setupTokensFile(page);
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
||||
name: "Color 92",
|
||||
});
|
||||
await expect(tokensColorGroup).toBeVisible();
|
||||
await tokensColorGroup.click();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
});
|
||||
await expect(colorToken).toBeVisible();
|
||||
await tokensColorGroup.click();
|
||||
await expect(colorToken).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../pages/WorkspacePage";
|
||||
import { presenceFixture } from "../../data/workspace/ws-notifications";
|
||||
import { presenceFixture, joinFixture2, joinFixture3 } from "../../data/workspace/ws-notifications";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
@@ -40,6 +40,28 @@ test("User receives presence notifications updates in the workspace", async ({
|
||||
).toHaveCount(2);
|
||||
});
|
||||
|
||||
test("BUG 13058 - Presence list shows up to 3 user avatars", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
|
||||
await workspacePage.goToWorkspace();
|
||||
await workspacePage.sendPresenceMessage(presenceFixture);
|
||||
await workspacePage.sendPresenceMessage(joinFixture2);
|
||||
|
||||
await expect(
|
||||
page.getByTestId("active-users-list").getByAltText("Princesa Leia"),
|
||||
).toHaveCount(3);
|
||||
|
||||
await workspacePage.sendPresenceMessage(joinFixture3);
|
||||
await expect(
|
||||
page.getByTestId("active-users-list").getByAltText("Princesa Leia"),
|
||||
).toHaveCount(2);
|
||||
|
||||
await expect(page.getByTestId("active-users-list").getByText("+2")).toBeVisible();
|
||||
});
|
||||
|
||||
test("User draws a rect", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
@@ -332,24 +354,33 @@ test("Copy/paste properties", async ({ page, context }) => {
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page.getByText("Rectangle").first().click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page.getByText("Board").nth(2).click({ button: "right" });
|
||||
await page
|
||||
.getByTestId("layer-item")
|
||||
.getByText("Rectangle")
|
||||
.first()
|
||||
.click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page
|
||||
.getByTestId("layer-item")
|
||||
.locator("div")
|
||||
.filter({ hasText: "Path" })
|
||||
.getByText("Board")
|
||||
.nth(1)
|
||||
.click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page.getByText("Ellipse").click({ button: "right" });
|
||||
await page
|
||||
.getByTestId("layer-item")
|
||||
.getByText("Path")
|
||||
.click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page
|
||||
.getByTestId("layer-item")
|
||||
.getByText("Ellipse")
|
||||
.click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
});
|
||||
|
||||
8718
frontend/pnpm-lock.yaml
generated
Normal file
9
frontend/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
patchedDependencies:
|
||||
'@zip.js/zip.js@2.8.11': patches/@zip.js__zip.js@2.8.11.patch
|
||||
|
||||
shamefullyHoist: true
|
||||
|
||||
packages:
|
||||
- "packages/draft-js"
|
||||
- "packages/mousetrap"
|
||||
- "text-editor"
|
||||
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 138 KiB |
@@ -700,19 +700,6 @@
|
||||
background-color: var(--menu-shortcut-background-color);
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
@include flexCenter;
|
||||
@include bodySmallTypography;
|
||||
height: $s-24;
|
||||
width: $s-24;
|
||||
border-radius: $br-circle;
|
||||
margin-left: calc(-1 * $s-4);
|
||||
img {
|
||||
border-radius: $br-circle;
|
||||
border: $s-2 solid var(--user-count-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.mixed-bar {
|
||||
@include bodySmallTypography;
|
||||
display: flex;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
--app-background: var(--color-background-primary);
|
||||
--loader-background: var(--color-background-primary);
|
||||
--panel-title-background-color: var(--color-background-secondary);
|
||||
|
||||
// BUTTONS
|
||||
--button-foreground-hover: var(--color-accent-primary);
|
||||
@@ -245,13 +244,6 @@
|
||||
--assets-component-second-border-selected: var(--color-background-primary);
|
||||
--assets-component-hightlight: var(--color-accent-secondary);
|
||||
|
||||
--radio-btns-background-color: var(--color-background-tertiary);
|
||||
--radio-btn-background-color-selected: var(--color-background-quaternary);
|
||||
--radio-btn-foreground-color: var(--color-foreground-secondary);
|
||||
--radio-btn-foreground-color-selected: var(--color-accent-primary);
|
||||
--radio-btn-border-color: var(--color-background-tertiary);
|
||||
--radio-btn-border-color-selected: var(--color-background-quaternary);
|
||||
|
||||
--library-name-foreground-color: var(--color-foreground-primary);
|
||||
--library-content-foreground-color: var(--color-foreground-secondary);
|
||||
|
||||
@@ -424,13 +416,6 @@
|
||||
--tab-border-color: var(--color-background-tertiary);
|
||||
--tab-border-color-selected: var(--color-background-secondary);
|
||||
|
||||
--radio-btns-background-color: var(--color-background-tertiary);
|
||||
--radio-btn-background-color-selected: var(--color-background-primary);
|
||||
--radio-btn-foreground-color: var(--color-foreground-secondary);
|
||||
--radio-btn-foreground-color-selected: var(--color-accent-primary);
|
||||
--radio-btn-border-color: var(--color-background-tertiary);
|
||||
--radio-btn-border-color-selected: var(--color-background-secondary);
|
||||
|
||||
--button-icon-background-color-selected: var(--color-background-primary);
|
||||
--button-icon-border-color-selected: var(--color-background-secondary);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
import initWasmModule from '/js/render_wasm.js';
|
||||
import initWasmModule from '/js/render-wasm.js';
|
||||
import {
|
||||
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||
getRandomFloat, useShape, setShapeChildren, setupInteraction, addShapeSolidStrokeFill,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
import initWasmModule from '/js/render_wasm.js';
|
||||
import initWasmModule from '/js/render-wasm.js';
|
||||
import {
|
||||
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||
getRandomFloat, useShape, setShapeChildren, setupInteraction, set_parent
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
import initWasmModule from '/js/render_wasm.js';
|
||||
import initWasmModule from '/js/render-wasm.js';
|
||||
import {
|
||||
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||
getRandomFloat, useShape, setShapeChildren, setupInteraction, set_parent, draw_star,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
import initWasmModule from '/js/render_wasm.js';
|
||||
import initWasmModule from '/js/render-wasm.js';
|
||||
import {
|
||||
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||
getRandomFloat, useShape, setShapeChildren, setupInteraction, set_parent, allocBytes,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
import initWasmModule from '/js/render_wasm.js';
|
||||
import initWasmModule from '/js/render-wasm.js';
|
||||
import {
|
||||
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||
getRandomFloat, useShape, setShapeChildren, setupInteraction, addShapeSolidStrokeFill
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
import initWasmModule from '/js/render_wasm.js';
|
||||
import initWasmModule from '/js/render-wasm.js';
|
||||
import {
|
||||
init, assignCanvas, setupInteraction, useShape, setShapeChildren, addTextShape, hexToU32ARGB,getRandomInt, getRandomColor, getRandomFloat, addShapeSolidFill, addShapeSolidStrokeFill
|
||||
} from './js/lib.js';
|
||||
@@ -102,4 +102,4 @@
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -18,10 +18,9 @@ export VERSION_TAG="${VERSION}-${BUILD_TS}";
|
||||
# performant code on macros (example: rumext)
|
||||
export NODE_ENV=production;
|
||||
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
yarn install;
|
||||
pnpm install;
|
||||
|
||||
rm -rf target/dist;
|
||||
rm -rf resources/public;
|
||||
@@ -33,9 +32,9 @@ pushd ../render-wasm;
|
||||
./build
|
||||
popd
|
||||
|
||||
yarn run build:app:main $EXTRA_PARAMS;
|
||||
yarn run build:app:libs;
|
||||
yarn run build:app:assets;
|
||||
pnpm run build:app:main $EXTRA_PARAMS;
|
||||
pnpm run build:app:libs;
|
||||
pnpm run build:app:assets;
|
||||
|
||||
sed -i "s/\.\/render.js/.\/render.js?version=$VERSION_TAG/g" resources/public/js/worker/main*.js
|
||||
|
||||
@@ -43,6 +42,6 @@ rsync -avr resources/public/ target/dist/
|
||||
|
||||
if [ "$INCLUDE_STORYBOOK" = "yes" ]; then
|
||||
# build storybook
|
||||
yarn run build:storybook || exit 1;
|
||||
pnpm run build:storybook || exit 1;
|
||||
rsync -avr storybook-static/ target/dist/storybook-static;
|
||||
fi
|
||||
|
||||
@@ -14,6 +14,6 @@ export NODE_ENV=production;
|
||||
|
||||
corepack enable;
|
||||
corepack install || exit 1;
|
||||
yarn install || exit 1;
|
||||
pnpm install || exit 1;
|
||||
|
||||
yarn run build:storybook || exit 1;
|
||||
pnpm run build:storybook || exit 1;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import express from "express";
|
||||
import compression from "compression";
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
|
||||
const app = express();
|
||||
const port = 3000;
|
||||
|
||||
app.use(compression());
|
||||
|
||||
const staticPath = path.join(
|
||||
fileURLToPath(import.meta.url),
|
||||
"../../resources/public",
|
||||
);
|
||||
app.use(express.static(staticPath));
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Listening at 0.0.0.0:${port}`);
|
||||
});
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
yarn install;
|
||||
yarn playwright install chromium;
|
||||
pnpm install;
|
||||
pnpx playwright install chromium;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
set -ex
|
||||
corepack enable;
|
||||
corepack install;
|
||||
yarn install;
|
||||
pnpm install;
|
||||
|
||||
yarn run lint:scss;
|
||||
yarn run test;
|
||||
pnpm run lint:scss;
|
||||
pnpm run test;
|
||||
|
||||
@@ -5,6 +5,5 @@ SCRIPT_DIR=$(dirname $0);
|
||||
set -ex
|
||||
|
||||
$SCRIPT_DIR/setup;
|
||||
|
||||
yarn run build:storybook
|
||||
yarn run test:storybook
|
||||
pnpm run build:storybook
|
||||
pnpm run test:storybook
|
||||
|
||||
@@ -5,5 +5,4 @@ SCRIPT_DIR=$(dirname $0);
|
||||
set -ex
|
||||
|
||||
$SCRIPT_DIR/setup;
|
||||
|
||||
yarn run test:e2e -x --workers=2 --reporter=list "$@";
|
||||
pnpm run test:e2e -x --workers=2 --reporter=list "$@";
|
||||
|
||||
@@ -4,4 +4,4 @@ TARGET=${1:-app};
|
||||
|
||||
set -ex
|
||||
|
||||
exec yarn run watch:$TARGET
|
||||
exec pnpm run watch:$TARGET
|
||||
|
||||
@@ -61,6 +61,11 @@
|
||||
;; Def micro-benchmark iterations
|
||||
(def micro-benchmark-iterations 1e6)
|
||||
|
||||
;; Performance logs
|
||||
(defonce ^:private longtask-observer* (atom nil))
|
||||
(defonce ^:private stall-timer* (atom nil))
|
||||
(defonce ^:private current-op* (atom nil))
|
||||
|
||||
;; --- CONTEXT
|
||||
|
||||
(defn- collect-context
|
||||
@@ -464,3 +469,72 @@
|
||||
(defn event
|
||||
[props]
|
||||
(ptk/data-event ::event props))
|
||||
|
||||
;; --- DEVTOOLS PERF LOGGING
|
||||
|
||||
(defn install-long-task-observer! []
|
||||
(when (and (some? (.-PerformanceObserver js/window)) (nil? @longtask-observer*))
|
||||
(let [observer (js/PerformanceObserver.
|
||||
(fn [list _]
|
||||
(doseq [entry (.getEntries list)]
|
||||
(let [dur (.-duration entry)
|
||||
start (.-startTime entry)
|
||||
attrib (.-attribution entry)
|
||||
attrib-count (when attrib (.-length attrib))
|
||||
first-attrib (when (and attrib-count (> attrib-count 0)) (aget attrib 0))
|
||||
attrib-name (when first-attrib (.-name first-attrib))
|
||||
attrib-ctype (when first-attrib (.-containerType first-attrib))
|
||||
attrib-cid (when first-attrib (.-containerId first-attrib))
|
||||
attrib-csrc (when first-attrib (.-containerSrc first-attrib))]
|
||||
|
||||
(.warn js/console (str "[perf] long task " (Math/round dur) "ms at " (Math/round start) "ms"
|
||||
(when first-attrib
|
||||
(str " attrib:name=" attrib-name
|
||||
" ctype=" attrib-ctype
|
||||
" cid=" attrib-cid
|
||||
" csrc=" attrib-csrc))))))))]
|
||||
(.observe observer #js{:entryTypes #js["longtask"]})
|
||||
(reset! longtask-observer* observer))))
|
||||
|
||||
(defn start-event-loop-stall-logger!
|
||||
"Log event loop stalls by measuring setInterval drift.
|
||||
interval-ms: base interval
|
||||
threshold-ms: drift over which we report"
|
||||
[interval-ms threshold-ms]
|
||||
(when (nil? @stall-timer*)
|
||||
(let [last (atom (.now js/performance))
|
||||
id (js/setInterval
|
||||
(fn []
|
||||
(let [now (.now js/performance)
|
||||
expected (+ @last interval-ms)
|
||||
drift (- now expected)
|
||||
current-op @current-op*
|
||||
measures (.getEntriesByType js/performance "measure")
|
||||
mlen (.-length measures)
|
||||
last-measure (when (> mlen 0) (aget measures (dec mlen)))
|
||||
meas-name (when last-measure (.-name last-measure))
|
||||
meas-detail (when last-measure (.-detail last-measure))
|
||||
meas-count (when meas-detail (unchecked-get meas-detail "count"))]
|
||||
(reset! last now)
|
||||
(when (> drift threshold-ms)
|
||||
(.warn js/console
|
||||
(str "[perf] event loop stall: " (Math/round drift) "ms"
|
||||
(when current-op (str " op=" current-op))
|
||||
(when meas-name (str " last=" meas-name))
|
||||
(when meas-count (str " count=" meas-count)))))))
|
||||
interval-ms)]
|
||||
(reset! stall-timer* id))))
|
||||
|
||||
(defn init!
|
||||
"Install perf observers in dev builds. Safe to call multiple times."
|
||||
[]
|
||||
(when ^boolean js/goog.DEBUG
|
||||
(install-long-task-observer!)
|
||||
(start-event-loop-stall-logger! 50 100)
|
||||
;; Expose simple API on window for manual control in devtools
|
||||
(let [api #js {:reset (fn []
|
||||
(try
|
||||
(.clearMarks js/performance)
|
||||
(.clearMeasures js/performance)
|
||||
(catch :default _ nil)))}]
|
||||
(aset js/window "PenpotPerf" api))))
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
"Parses shadow spread value (non-negative number)."
|
||||
[value]
|
||||
(let [parsed (parse-sd-token-general-value value)
|
||||
valid? (:value parsed)]
|
||||
valid? (and (:value parsed) (>= (:value parsed) 0))]
|
||||
(cond
|
||||
valid?
|
||||
parsed
|
||||
|
||||
@@ -173,7 +173,8 @@
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/cmd! :delete-team-member params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (fetch-members team-id)
|
||||
(rx/of (dp/refresh-profile)
|
||||
(fetch-members team-id)
|
||||
(fetch-teams)
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "delete-team-member"
|
||||
|
||||
@@ -347,6 +347,12 @@
|
||||
(with-meta {:team-id team-id
|
||||
:file-id file-id}))))))
|
||||
|
||||
;; Install dev perf observers once the workspace is ready
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] (ev/init!))))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dps/persistence-notification))
|
||||
(rx/take 1)
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [expand-fn (fn [expanded]
|
||||
(merge expanded
|
||||
(->> ids
|
||||
(map #(cfh/get-parent-ids objects %))
|
||||
flatten
|
||||
(remove #(= % uuid/zero))
|
||||
(map (fn [id] {id true}))
|
||||
(into {}))))]
|
||||
(let [parents-seqs (map (fn [x] (cfh/get-parent-ids objects x)) ids)
|
||||
flat-parents (apply concat parents-seqs)
|
||||
non-root-parents (remove #(= % uuid/zero) flat-parents)
|
||||
distinct-parents (into #{} non-root-parents)]
|
||||
(merge expanded
|
||||
(into {}
|
||||
(map (fn [id] {id true}) distinct-parents)))))]
|
||||
(update-in state [:workspace-local :expanded] expand-fn)))))
|
||||
|
||||
|
||||
|
||||