Compare commits

..

244 Commits

Author SHA1 Message Date
Andrey Antukh
fb755a861c Restore back node based http server for e2e tests 2026-01-26 18:18:06 +01:00
Andrey Antukh
539a3923e0 🔥 Remove unused svg from frontend resources 2026-01-26 18:18:06 +01:00
David Barragán Merino
d433fd25c1 🔧 Run all the jobs if the workflow is launched manually 2026-01-26 17:12:58 +01:00
David Barragán Merino
c5f03d711a 🔧 Enable secret inheritance 2026-01-26 14:00:09 +01:00
David Barragán Merino
72cc5ee349 🔧 Define deploy plugin packages workflows 2026-01-26 13:23:46 +01:00
Eva Marco
804695b48b ♻️ Replace stroke width numeric inputs (#8137)
*  Replace opacity numeric input

*  Add test

* ♻️ Replace stroke width numeric input

* 🎉 Add tests
2026-01-26 12:50:28 +01:00
Elena Torró
20c8fbf314 🔧 Make render-wasm visual regression tests use gpu (#8189) 2026-01-26 11:53:29 +01:00
Andrey Antukh
e02536f8d4 Merge branch 'staging-render' into develop 2026-01-26 11:02:50 +01:00
Andrey Antukh
3eeaaab17e Merge branch 'staging' into staging-render 2026-01-26 11:02:26 +01:00
Alejandro Alonso
3dc9e28230 Merge pull request #8155 from penpot/elenatorro-13089-improve-page-load-render
🔧 Improve render UX on first load
2026-01-26 10:40:44 +01:00
Andrey Antukh
d9c56da705 ⬆️ Update playwright on frontend module 2026-01-26 10:08:53 +01:00
Andrey Antukh
75248aec4e 🔧 Add missing container on tests.yml 2026-01-26 09:52:47 +01:00
David Barragán Merino
f0d9429775 🔧 Rename wrangle to wrangler 2026-01-26 09:37:33 +01:00
Eva Marco
62ecf48bdb 🐛 Fix glich when applying padding (#8099)
*  Replace opacity numeric input

*  Add test

* 🐛 Fix glich when applying padding
2026-01-26 08:40:32 +01:00
Alejandro Alonso
18de7f1db6 Merge pull request #8041 from penpot/niwinz-subpath-support
 Add several adjustments for make penpot run on subpath
2026-01-26 07:34:24 +01:00
Alejandro Alonso
2b2941bd25 Merge pull request #8049 from penpot/niwinz-virtual-clock-by-user
 Make the virtual clock by profile and not global
2026-01-26 07:22:08 +01:00
Alejandro Alonso
f2d561eff7 Merge pull request #8063 from penpot/niwinz-develop-auditlog-logging
 Add the ability to log using logging subsystem the audit events
2026-01-26 07:17:13 +01:00
Alejandro Alonso
418b65a287 Merge pull request #8143 from penpot/niwinz-develop-bugfix-2
🐛 Fix incorrect handling of numeric values layout padding and gap
2026-01-26 07:11:09 +01:00
Alejandro Alonso
d4e7810eba Merge pull request #8135 from penpot/niwinz-develop-chunking-on-font-upload
🐛 Avoid json decoder line limit exception by chunking
2026-01-26 07:08:23 +01:00
Andrey Antukh
1d1d32ad39 🐛 Avoid json decoder liner limit exception by chunking
Happens only when we send large binary data serialized with transit
(mainly used for upload fonts data).
2026-01-26 06:58:17 +01:00
Alejandro Alonso
fb08dc65c8 Merge pull request #8133 from penpot/niwinz-develop-asset-export-whitespaces
 Add slugify to the filename on assets exportation
2026-01-26 06:55:52 +01:00
Alejandro Alonso
927ac93fa7 Merge pull request #8141 from penpot/niwinz-develop-bugfix-1
🐛 Prevent exception on open-new-window when no window is returned
2026-01-26 06:32:02 +01:00
Andrey Antukh
e546a7c614 🐛 Prevent exception on open-new-window when no window is returned
Fixes https://github.com/penpot/penpot/issues/7787
2026-01-26 06:31:40 +01:00
David Barragán Merino
058c20c2e2 🔧 Add deploy plugin packages workflow placeholder and wrangle config files 2026-01-23 20:39:39 +01:00
Elena Torró
68a77e9cc8 Merge pull request #8179 from penpot/superalex-adding-performance-logs-flag
🎉 Adding performance logs flag
2026-01-23 14:06:57 +01:00
Alejandro Alonso
e3148ea20e 🎉 Adding performance logs flag 2026-01-23 13:34:19 +01:00
Elena Torró
5da9bbea62 Merge pull request #8174 from penpot/superalex-fix-blur-events-text-editor-v2
🐛 Fix blur events for text editor v2 in firefox
2026-01-23 13:08:01 +01:00
Andrey Antukh
5016b2a7bf Merge remote-tracking branch 'origin/staging-render' into develop 2026-01-23 11:18:33 +01:00
Andrey Antukh
089d1667b6 Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-23 11:08:07 +01:00
Alejandro Alonso
4ad5282063 🐛 Fix blur events for text editor v2 in firefox 2026-01-23 10:58:54 +01:00
Elena Torró
d0e79c94b4 Merge pull request #8162 from penpot/superalex-fix-auto-height
🐛 Fix text boxes with auto-height don't update height when resized by dragging side handles
2026-01-23 10:57:54 +01:00
Andrey Antukh
43ae213659 Replace login illustration svg with a correct bitmap (#8170) 2026-01-23 09:59:29 +01:00
Alejandro Alonso
d112c0a33b 🐛 Fix text boxes with auto-height don't update height when resized by dragging side handles 2026-01-23 09:05:20 +01:00
Elena Torró
7b86518afa Merge pull request #8171 from penpot/ladybenko-13152-fix-blur
🐛 Fix blur when clicking on same page
2026-01-22 17:42:39 +01:00
Elena Torró
9991901ed8 Merge pull request #8161 from penpot/superalex-fix-editing-text-doesnt-update-layer-name
🐛 Bug: Editing the text inside a text object doesn’t update the text layer name.
2026-01-22 17:40:32 +01:00
Belén Albeza
3d0c6ad421 Blur board titles and outlines when switching pages 2026-01-22 16:00:24 +01:00
Andrey Antukh
dc973dac36 🔧 Enable link workspace packages on plugins 2026-01-22 13:55:41 +01:00
Andrey Antukh
4467827218 ⬆️ Update react dependency on frontend 2026-01-22 13:55:41 +01:00
Andrey Antukh
6470db8d5f Remove mention of yarn on several files 2026-01-22 13:55:41 +01:00
Andrey Antukh
dc44156b53 🐛 Fix text editor wasm playground 2026-01-22 13:55:41 +01:00
Andrey Antukh
f0e53d70ae 🎉 Migrate to PNPM render-wasm module 2026-01-22 13:55:41 +01:00
Andrey Antukh
ef73a263b2 🎉 Migrate to PNPM docs module 2026-01-22 13:55:41 +01:00
Andrey Antukh
9b1e007a49 Replace e2e node server with caddy
Which is already available in the devenv runtime image
2026-01-22 13:55:41 +01:00
Andrey Antukh
ea8632e56a 🐛 Fix frontend test-components scripts 2026-01-22 13:55:41 +01:00
Andrey Antukh
2d00e64ede Fix e2e scripts related to pnpm change 2026-01-22 13:55:41 +01:00
Andrey Antukh
1246250198 🎉 Migrate to PNPM frontend module 2026-01-22 13:55:41 +01:00
Andrey Antukh
34f2943dcd 🎉 Migrate to PNPM library module 2026-01-22 13:55:41 +01:00
Andrey Antukh
3fb78116b8 🎉 Migrate to PNPM backend module 2026-01-22 13:55:41 +01:00
Andrey Antukh
072e415b9e 🎉 Migrate to PNPM common module 2026-01-22 13:55:41 +01:00
Andrey Antukh
67a904824c 🎉 Migrate to PNPM exporter module 2026-01-22 13:55:41 +01:00
Belén Albeza
835ea97be7 🐛 Fix blur applied when clicking in the active page 2026-01-22 13:27:05 +01:00
Andrey Antukh
68184209be Add the ability to make json/->clj non-recursive 2026-01-22 13:07:05 +01:00
Andrey Antukh
d2295862b4 Add the ability to customize decoder and schema on obj/reify 2026-01-22 13:07:05 +01:00
Andrey Antukh
23cbf33d1b Merge remote-tracking branch 'origin/staging' into develop 2026-01-22 12:34:49 +01:00
David Barragán Merino
b4ff0ccf3a 🔧 Fixes to the API documentation deployer 2026-01-22 12:08:09 +01:00
Andrey Antukh
6c6666a39a Merge pull request #8158 from penpot/ladybenko-13058-hide-avatar
🐛 Do not hide active users avatar when there are 3 of them
2026-01-22 12:02:53 +01:00
Elena Torro
f94c9cdb02 🐛 Fix objects sorting for thumbnail generation 2026-01-22 09:29:33 +01:00
Elena Torro
8637c46ba1 🐛 Fix empty pool state 2026-01-22 08:52:26 +01:00
Elena Torro
5d7d23a2c7 🔧 Keep clear cached canvas 2026-01-22 08:51:58 +01:00
Alejandro Alonso
a1a3966d7b 🐛 Editing the text inside a text object doesn’t update the text layer name 2026-01-22 08:24:13 +01:00
David Barragán Merino
c1335961b4 🔧 Deploy penpot api documentation 2026-01-21 18:52:07 +01:00
Belén Albeza
eaf64b6e16 ♻️ Make the CSS of presence widgets to adhere to our guidelines 2026-01-21 17:33:18 +01:00
Belén Albeza
560a0d09d5 🐛 Fix hiding avatar when we have 3 active users 2026-01-21 16:01:20 +01:00
Elena Torro
aab1d97c4c 🔧 Clean up and use proper imports 2026-01-21 16:01:06 +01:00
Elena Torro
499aac31a4 🔧 Improve tile invalidation to prevent visual flickering
When tiles are invalidated (during shape updates or page loading), the old tile
content is now kept visible until new content is rendered to replace it. This
provides a smoother visual experience during updates.
2026-01-21 15:42:52 +01:00
Elena Torro
962d7839a2 🔧 Add progressive rendering support for improved page load experience
When loading large pages with many shapes, the UI now remains responsive by
processing shapes in chunks (100 shapes at a time) and yielding to the browser
between chunks. Preview renders are triggered at 25%, 50%, and 75% progress to
give users visual feedback during loading.
2026-01-21 14:55:53 +01:00
Elena Torro
83387701a0 🔧 Add batched shape base properties serialization for improved WASM performance 2026-01-21 14:55:07 +01:00
Elena Torro
5775fa61ba 🔧 Refactor ShapesPool to use index-based storage instead of unsafe lifetime references
Replace `HashMap<&'a Uuid, ...>` with `HashMap<usize, ...>` for all auxiliary maps
(modifiers, structure, scale_content, modified_shape_cache)
2026-01-21 14:53:56 +01:00
Andrey Antukh
b70eb768e0 Merge remote-tracking branch 'origin/staging' into develop 2026-01-21 13:51:41 +01:00
Belén Albeza
5b1766835f 🐛 Fix broken selection on duplicated shapes on new pages 2026-01-21 10:32:13 +01:00
Andrey Antukh
4397ede5c1 Merge branch 'staging-render' into develop 2026-01-21 10:18:15 +01:00
Andrey Antukh
ff25df0457 Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-21 10:17:22 +01:00
Andrey Antukh
e0910db99e 🐛 Fix incorrect handling of numeric values layout padding and gap
Fixes https://github.com/penpot/penpot/issues/8113
2026-01-21 09:38:44 +01:00
Luis de Dios
079b3fbfad ♻️ Extract and create panel title component (#8090) 2026-01-20 18:56:25 +01:00
Andrey Antukh
299f628951 Merge pull request #8123 from penpot/GlobalStar117-fix/token-validation-crash
🐛 Fix Penpot crash when setting some name in Design tokens
2026-01-20 18:53:05 +01:00
David Barragán Merino
32d0fe6463 🔧 Use selfhosted runner 01 to generate the bundle 2026-01-20 18:09:18 +01:00
Andrey Antukh
cecd3d4a90 📎 Update changelog 2026-01-20 16:00:57 +01:00
Eva Marco
1c2c0987f5 🐛 Fix schema validation for references from other sets 2026-01-20 15:51:43 +01:00
Globalstar117
0418147e74 🐛 Add error handler to token form validation to prevent crash
When creating a token with a name that conflicts with existing
hierarchical token names (e.g., 'accent-color' when 'accent-color.blue.dark'
exists), the validation throws an error via rx/throw. However, the
rx/subs! subscriber in generic_form.cljs had no error handler, causing
an unhandled exception that resulted in an 'Internal Error' crash.

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

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

Fixes #8110

---
This is a Gittensor contribution.
gittensor:user:GlobalStar117
2026-01-20 15:51:25 +01:00
Andrey Antukh
9e0ba4429a Add slugify to the filename on assets exportation
Fixes https://github.com/penpot/penpot/issues/8017
2026-01-20 15:27:24 +01:00
Eva Marco
7499a5bca6 ♻️ Replace opacity input (#8072)
*  Replace opacity numeric input

*  Add test
2026-01-20 14:30:35 +01:00
Xaviju
6cd5bc76d7 💄 Replace current themes switch with DS switch (#8131) 2026-01-20 14:17:25 +01:00
David Barragán Merino
bbe6ee2e19 🔧 Define a different temporary config file for each execution 2026-01-20 12:59:56 +01:00
David Barragán Merino
fb6d8309b6 🔧 Prevent error 429 downloading docker images from dockerhub 2026-01-20 12:59:56 +01:00
Alejandro Alonso
b7c2d9a079 Merge pull request #8130 from penpot/superalex-improve-zoom-pan-performance-7
🐛 Fix some tiles disappear after fast zoom and pan
2026-01-20 12:56:02 +01:00
Alejandro Alonso
aeb34a6f64 Merge pull request #8109 from penpot/superalex-fix-text-selrect-calculation
🐛 Render wasm typography token issues
2026-01-20 12:54:45 +01:00
Alejandro Alonso
6fa0c3af0c 🐛 Fix some tiles disappear after fast zoom and pan 2026-01-20 12:40:01 +01:00
Alejandro Alonso
260b9fb040 🐛 Fix texts with auto size updated via tokens with render wasm
activated
2026-01-20 12:39:17 +01:00
Alejandro Alonso
884954f4ff 🐛 Fix text selrect calculation 2026-01-20 12:37:57 +01:00
Andrey Antukh
689467bcf9 📎 Update changelog 2026-01-20 12:25:43 +01:00
Andrey Antukh
7724450037 Merge remote-tracking branch 'origin/staging-render' into develop 2026-01-20 12:18:04 +01:00
Xaviju
368fa954ce Remove tokens tree node (#8042) 2026-01-20 11:00:13 +01:00
Andrey Antukh
6fd0f5377c Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-20 10:08:58 +01:00
Elena Torró
eb54bc485e Merge pull request #8120 from penpot/alotor-fix-flex-layout
🐛 Fix problems with layout
2026-01-20 10:00:24 +01:00
Elena Torró
12c24a36b4 Merge pull request #8122 from penpot/fix-thumbnail-generation
🐛 Fix problem with thumbnail generation
2026-01-20 09:59:34 +01:00
Alejandro Alonso
324d54ad28 🐛 Fix set all rounded corners to 0 2026-01-20 09:34:06 +01:00
alonso.torres
f42ff27f3d 🐛 Fix problem with bools 2026-01-19 17:05:04 +01:00
Andrey Antukh
0ecb2bc838 Merge remote-tracking branch 'origin/staging' into develop 2026-01-19 13:42:05 +01:00
alonso.torres
2c1cc89f53 🐛 Fix problem with thumbnail generation 2026-01-19 12:54:15 +01:00
alonso.torres
498b0b30fe 🐛 Fix problems with layout 2026-01-19 12:17:58 +01:00
Elena Torró
89f40dcda2 🔧 Move WebGL context error message to 'errors' namespace (#8117) 2026-01-19 11:24:19 +01:00
Andrey Antukh
e92f3fb3cb Use pseudo-names on release builds of frontend (#8105) 2026-01-19 11:23:35 +01:00
Andrey Antukh
5193cfd56e :paerclip: Update changelog 2026-01-19 11:15:39 +01:00
Dalai Felinto
813d5d8e69 🐛 Fix import tokens dialog default option (#8051)
This was intended to be changed on 13fcf3a9bb. However only the menu
order changed, not the default option.

Signed-off-by: Dalai Felinto <dalai@blender.org>
Co-authored-by: Dalai Felinto <dalai@blender.org>
2026-01-19 11:13:55 +01:00
Andrey Antukh
84f1ff092d Merge remote-tracking branch 'origin/staging' into develop 2026-01-19 11:10:37 +01:00
Elena Torró
ccac7bd510 Merge pull request #8108 from penpot/ladybenko-13022-blur-page
🎉 Apply blur effect when switching pages
2026-01-19 11:04:31 +01:00
Andrey Antukh
f2b082b93e Merge branch 'staging-render' into develop 2026-01-19 10:54:37 +01:00
Andrey Antukh
d73197625d Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-19 10:43:43 +01:00
Andrey Antukh
1f41bef4a9 Add several adjustments for make penpot run on subpath 2026-01-18 10:12:23 +01:00
Marina López
fdf5bb250b 🐛 Fix profile not updating after deleting a team member (#8075) 2026-01-18 10:08:18 +01:00
Yamila Moreno
786736fadd Improve default nginx config (#8104) 2026-01-18 10:07:44 +01:00
Pablo Alba
1ff6e00398 🐛 Fix error message on components doesn't close automatically (#8081) 2026-01-18 10:07:11 +01:00
David Barragán Merino
25455523ad 🔧 Revert use of selfhosted runner 02 to generate the bundle 2026-01-16 17:51:02 +01:00
David Barragán Merino
8dfeb21978 🔧 Use selfhosted runner 02 to generate the bundle and the docker images 2026-01-16 17:44:15 +01:00
Belén Albeza
43d1d127dc 🎉 Apply blur effect to previous canvas pixels while setting wasm objects 2026-01-16 13:04:59 +01:00
Belén Albeza
8bd3ef717c 🎉 Apply blur to canvas when switching pages 2026-01-16 13:04:59 +01:00
Elena Torro
53bc647783 🔧 Fix shape selection from canvas to sidebar 2026-01-16 13:02:25 +01:00
Alejandro Alonso
ad2833bb7a Merge pull request #8107 from penpot/elenatorro-13077-improve-shape-selection-performance
🔧 Fix shape selection from canvas to sidebar
2026-01-16 13:01:58 +01:00
Elena Torro
21911e898f 🔧 Fix shape selection from canvas to sidebar 2026-01-16 12:45:45 +01:00
Andrey Antukh
538073debf Merge remote-tracking branch 'origin/staging' into develop 2026-01-16 10:38:20 +01:00
Elena Torró
6029f9bb51 Merge pull request #8089 from penpot/superalex-improve-zoom-pan-performance-5
🎉 Performance improvements
2026-01-15 16:46:07 +01:00
Elena Torro
e0fd8bac81 🔧 Optimize sidebar performance for deeply nested shapes
- Batch hover highlights using RAF to avoid long tasks from rapid events
- Run parent expansion asynchronously to not block selection
- Lazy-load children in layer items using IntersectionObserver
- Clarify expand-all-parents logic with explicit bindings
2026-01-15 13:41:54 +01:00
Elena Torro
34737ddfc9 🔧 Always lookup over a set 2026-01-15 13:41:10 +01:00
Elena Torro
a8dfd19338 🔧 Add performance debugging logs 2026-01-15 13:40:58 +01:00
Elena Torro
e33e8a8c3b 🔧 Lookup page objects only when value changes 2026-01-15 13:40:53 +01:00
Alejandro Alonso
214b0efa02 Merge pull request #8074 from penpot/elenatorro-13017-improve-sidebar-performance
🔧 Optimize sidebar performance for deeply nested shapes
2026-01-15 13:28:14 +01:00
David Barragán Merino
661436ecae 🔧 Use selfhosted runner 02 2026-01-15 13:26:49 +01:00
Alejandro Alonso
c411aefc6c 🐛 Fix rotated shapes extrect calculation 2026-01-15 12:53:21 +01:00
Alejandro Alonso
311e124658 🎉 Reduce extrect work in tile traversal
Avoid repeated extrect calculations and simplify root ordering per tile.
2026-01-15 12:53:21 +01:00
Alejandro Alonso
afc914f486 🎉 Render simple shapes directly on Current
Bypass intermediate surfaces for simple shapes without effects.
2026-01-15 12:53:21 +01:00
Pablo Alba
0d5fe6e527 🐛 Fix wrong register image 2026-01-15 11:25:12 +01:00
Pablo Alba
e7230d9da4 🐛 Fix wrong image in the onboarding invitation block 2026-01-15 11:25:12 +01:00
Elena Torro
5054f6bc38 🔧 Optimize sidebar performance for deeply nested shapes
- Batch hover highlights using RAF to avoid long tasks from rapid events
- Run parent expansion asynchronously to not block selection
- Lazy-load children in layer items using IntersectionObserver
- Clarify expand-all-parents logic with explicit bindings
2026-01-15 09:15:32 +01:00
Alejandro Alonso
84f750da0d 🎉 Skip heavy effects in fast mode
Avoid blur and shadow passes for text and shapes when FAST_MODE is enabled.
2026-01-15 08:45:21 +01:00
Xaviju
38396ba299 ♻️ Divide token integrations tests into multiple files (#8076) 2026-01-14 16:12:36 +01:00
Pablo Alba
b1997a83b3 🐛 Fix prototype connections lost when switching between variants 2026-01-14 15:30:54 +01:00
Elena Torro
68f5671eab 🔧 Always lookup over a set 2026-01-14 13:49:32 +01:00
Elena Torro
92976143bb 🔧 Add performance debugging logs 2026-01-14 13:49:32 +01:00
Eva Marco
dd2d03e6a0 ♻️ Replace border radius inputs (#7953)
*  Replace border radius numeric input

*  Add border radius token inputs on multiple selection
2026-01-14 12:45:40 +01:00
Elena Torro
fd675e0194 🔧 Lookup page objects only when value changes 2026-01-14 12:15:01 +01:00
Elena Torro
a3119bef5e 🔧 Show message and button to reload the page when WebGL context is lost 2026-01-14 11:10:03 +01:00
Alejandro Alonso
c60d74df62 🐛 Fix nested frames border clipping 2026-01-14 11:10:03 +01:00
Alejandro Alonso
d593e299e3 🐛 Fix mask erros on save/restore optimizations 2026-01-14 11:10:03 +01:00
Alejandro Alonso
4a8e02987f 🐛 Fix mask erros on save/restore optimizations 2026-01-14 11:10:03 +01:00
Alejandro Alonso
ee766e85a0 🎉 Wasm render dirty surfaces 2026-01-14 11:10:03 +01:00
Alejandro Alonso
35e3b7f19a 🎉 Root ids refactor 2026-01-14 11:10:03 +01:00
Alejandro Alonso
1810df232b 🎉 Ignore frames and groups when they have no visual extra information 2026-01-14 11:10:03 +01:00
Alejandro Alonso
3e99ad036c 🎉 Avoid unnecesary saves and restores 2026-01-14 11:10:03 +01:00
Alejandro Alonso
042a3a4080 🐛 Fix wasm playgrounds 2026-01-14 11:10:03 +01:00
Belén Albeza
f0687fd1f7 🎉 Make workspace loader to wait for first render 2026-01-14 11:10:03 +01:00
Aitor Moreno
2c9159288f 🐛 Fix previous styles lost when changing selected text 2026-01-14 11:10:01 +01:00
Andrey Antukh
c98373658e Revert "🔧 Use selfhosted runners (#8057)"
This reverts commit 01e42b0458.
2026-01-13 15:45:28 +01:00
Andrey Antukh
2e400768b7 Add the ability to log using logging subsystem the audit events 2026-01-13 14:22:18 +01:00
David Barragán Merino
01e42b0458 🔧 Use selfhosted runners (#8057) 2026-01-13 14:20:33 +01:00
Madalena Melo
7529673812 📎 Create an issue template for reporting bugs on the new render engine (#8059)
This template will be part of the open beta test for the new render, so that users can report issues for us to review.
2026-01-13 13:27:27 +01:00
Andrey Antukh
d2dad35d7a Merge branch 'staging' into develop 2026-01-13 10:36:22 +01:00
Eva Marco
f7b5266304 🐛 Fix sticky-buttons-on-layers (#7962) 2026-01-12 14:28:19 +01:00
Eva Marco
09c23256b7 🐛 Fix multiselection with tokens on stroke color (#7977) 2026-01-12 12:54:24 +01:00
Andrey Antukh
1ae1c0460e 🐛 Fix translation related to dashboard deleted page (#8056)
* 🐛 Fix translation related to dashboard deleted page

*  Rehash and validate translation files
2026-01-12 12:42:51 +01:00
Andrey Antukh
291c7349db Merge remote-tracking branch 'origin/staging' into develop 2026-01-12 11:53:02 +01:00
Andrey Antukh
b605a3b53d Merge branch 'staging' into develop 2026-01-12 11:44:51 +01:00
Nicola Bortoletto
cc2dab2756 🌐 Add translations for: Italian
Currently translated at 98.4% (2032 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-01-12 09:36:34 +01:00
Alexis Morin
d0c0664338 🌐 Add translations for: French (Canada)
Currently translated at 16.5% (342 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-12 09:36:34 +01:00
Alexis Morin
2240f25143 🌐 Add translations for: French (Canada)
Currently translated at 15.3% (317 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-12 09:36:34 +01:00
Oğuz Ersen
93a5ec2f5d 🌐 Add translations for: Turkish
Currently translated at 99.8% (2061 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-01-12 09:36:33 +01:00
Alexis Morin
d6784771a8 🌐 Add translations for: French (Canada)
Currently translated at 13.5% (279 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-12 09:36:33 +01:00
Edgars Andersons
930c814ded 🌐 Add translations for: Latvian
Currently translated at 91.1% (1883 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-01-12 09:36:33 +01:00
Stephan Paternotte
1a5a69bca2 🌐 Add translations for: Dutch
Currently translated at 99.8% (2061 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-01-12 09:36:33 +01:00
VKing9
9ad323a220 🌐 Add translations for: Hindi
Currently translated at 96.9% (2001 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-01-12 09:36:33 +01:00
Andrey Antukh
bcaf76d055 Make the virtual clock by profile and not global
And make it affect only RPC/HTTP requests and not worker
tasks.
2026-01-09 12:58:44 +01:00
Andrés Moya
5fa4368d70 🔧 Refactor integration test to be cleaner (#8044) 2026-01-09 12:52:50 +01:00
Elena Torró
b8efd2518d 🐛 Fix invite members UI modal (#8032) 2026-01-09 11:12:15 +01:00
Andrés Moya
7b2271ec38 🐛 Fix remapping of tokens with the same name and update tests (#8043) 2026-01-09 10:53:19 +01:00
Xaviju
2240d93069 Save unfolded tokens path (#7949) 2026-01-09 09:56:18 +01:00
Edgars Andersons
3f4506284b 🌐 Add translations for: Latvian
Currently translated at 91.3% (1869 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-01-08 18:06:51 +01:00
Valentina Chapellu
af1dfd91aa 🌐 Add translations for: Italian
Currently translated at 97.0% (1984 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-01-08 18:06:51 +01:00
Mikel Larreategi
24feebd73b 🌐 Add translations for: Basque
Currently translated at 56.4% (1155 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2026-01-08 18:06:51 +01:00
Aryiu
33e5a9a538 🌐 Add translations for: Catalan
Currently translated at 52.2% (1068 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2026-01-08 18:06:50 +01:00
Linerly
9c69b07a62 🌐 Add translations for: Indonesian
Currently translated at 82.9% (1697 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2026-01-08 18:06:50 +01:00
Црнобог
56f5be4f37 🌐 Add translations for: Serbian
Currently translated at 67.0% (1371 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sr/
2026-01-08 18:06:50 +01:00
ascarida
8a70204d41 🌐 Add translations for: Galician
Currently translated at 18.0% (370 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/gl/
2026-01-08 18:06:50 +01:00
Henrik Allberg
57a27f7e7f 🌐 Add translations for: Swedish
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-01-08 18:06:50 +01:00
Eranot
3b0b2a78d6 🌐 Add translations for: Portuguese (Brazil)
Currently translated at 68.1% (1394 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2026-01-08 18:06:50 +01:00
Alejandro Alonso
10bf4610df 🌐 Add translations for: Hausa
Currently translated at 60.6% (1241 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ha/
2026-01-08 18:06:50 +01:00
Andy Li
77e8414aea 🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 78.1% (1599 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2026-01-08 18:06:50 +01:00
bingling_sama
20ecf3b066 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 88.2% (1804 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2026-01-08 18:06:50 +01:00
Amerey.eu
49b1032973 🌐 Add translations for: Czech
Currently translated at 77.9% (1594 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/cs/
2026-01-08 18:06:49 +01:00
Radek Sawicki
5ba7dd8c56 🌐 Add translations for: Polish
Currently translated at 55.2% (1130 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2026-01-08 18:06:49 +01:00
Ingrid Pigueron
38b5125186 🌐 Add translations for: French
Currently translated at 94.5% (1934 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-01-08 18:06:49 +01:00
Vint Prox
6677ae83d4 🌐 Add translations for: Russian
Currently translated at 77.3% (1582 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2026-01-08 18:06:49 +01:00
Marius
0737c055f0 🌐 Add translations for: German
Currently translated at 93.2% (1906 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2026-01-08 18:06:49 +01:00
Dário
4b88748fe3 🌐 Add translations for: Portuguese (Portugal)
Currently translated at 76.8% (1571 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/
2026-01-08 18:06:49 +01:00
Denys Kisil
92107e5b1e 🌐 Add translations for: Ukrainian (ukr_UA)
Currently translated at 88.8% (1818 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/
2026-01-08 18:06:49 +01:00
Shuaib Zahda
ebc0e3a23c 🌐 Add translations for: Arabic
Currently translated at 55.0% (1126 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2026-01-08 18:06:49 +01:00
VKing9
ebe4f2da50 🌐 Add translations for: Hindi
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-01-08 18:06:48 +01:00
Vincas Dundzys
a07c1d6eaa 🌐 Add translations for: Lithuanian
Currently translated at 5.7% (118 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lt/
2026-01-08 18:06:48 +01:00
Ahmad HosseinBor
613bfda955 🌐 Add translations for: Persian
Currently translated at 38.2% (782 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2026-01-08 18:06:48 +01:00
AlexTECPlayz
f7ef6618e5 🌐 Add translations for: Romanian
Currently translated at 94.8% (1940 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2026-01-08 18:06:48 +01:00
Sebastiaan Pasma
fe334d9cbe 🌐 Add translations for: Dutch
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-01-08 18:06:48 +01:00
Revenant
268b883c73 🌐 Add translations for: Malay
Currently translated at 32.8% (672 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ms/
2026-01-08 18:06:48 +01:00
Zvonimir Juranko
f6a4effa29 🌐 Add translations for: Croatian
Currently translated at 78.1% (1599 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/
2026-01-08 18:06:48 +01:00
Yessenia Villarte Vaca
ced848077e 🌐 Add translations for: Spanish (Latin America)
Currently translated at 6.4% (131 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es_419/
2026-01-08 18:06:48 +01:00
Alexis Morin
7d9d318539 🌐 Add translations for: French (Canada)
Currently translated at 12.5% (257 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 18:06:48 +01:00
Oğuz Ersen
9781fceadb 🌐 Add translations for: Turkish
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-01-08 18:06:48 +01:00
Yaron Shahrabani
3178bd9a27 🌐 Add translations for: Hebrew
Currently translated at 97.0% (1984 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-01-08 18:06:47 +01:00
Andrey Antukh
e5d677f449 🌐 Validate and rehash translation files 2026-01-08 18:05:51 +01:00
Andrey Antukh
6bf928893c Merge pull request #8000 from penpot/luis-radio-buttons-ds
♻️ Replace some components with DS ones
2026-01-08 18:04:20 +01:00
Andrés Moya
53dd90aa24 🔥 Remove unused css (#8039) 2026-01-08 16:37:27 +01:00
Andrey Antukh
9fd0f6a8f3 📎 Fix integration tests 2026-01-08 16:02:52 +01:00
Andrey Antukh
638c3356d3 📎 Use correct casing on translation strings 2026-01-08 14:58:17 +01:00
Luis de Dios
6879f54e5d ♻️ Replace some components with DS ones 2026-01-08 14:52:25 +01:00
Andrey Antukh
a71baa5a78 🌐 Rehash and validate translation files 2026-01-08 14:46:18 +01:00
Andrey Antukh
8e4a89bd1c Merge branch 'staging' into develop 2026-01-08 14:43:43 +01:00
Hosted Weblate
7aad9da285 🌐 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2026-01-08 14:04:56 +01:00
Alexis Morin
ab57a4ae52 🌐 Add translations for: French (Canada)
Currently translated at 12.9% (259 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Alexis Morin
266ee29bb9 🌐 Add translations for: French (Canada)
Currently translated at 9.2% (184 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Alexis Morin
69ca86bb6c 🌐 Add translations for: French (Canada)
Currently translated at 7.3% (147 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Alexis Morin
ee14a845fc 🌐 Add translations for: French (Canada)
Currently translated at 3.1% (62 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Yaron Shahrabani
73639f5d16 🌐 Add translations for: Hebrew
Currently translated at 99.7% (1992 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-01-08 14:04:48 +01:00
Yaron Shahrabani
9bd106b2bc 🌐 Add translations for: Hebrew
Currently translated at 99.4% (1986 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-01-08 14:04:47 +01:00
Alexis Morin
59c75afc7b 🌐 Add translations for: French (Canada)
Currently translated at 1.0% (21 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:47 +01:00
Nicola Bortoletto
bbc81586e3 🌐 Add translations for: Italian
Currently translated at 99.7% (1992 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-01-08 14:04:47 +01:00
Anton Palmqvist
c9c30eab75 🌐 Add translations for: Swedish
Currently translated at 99.8% (1994 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-01-08 14:04:47 +01:00
Alexis Morin
86ba9280db 🌐 Add translations for: French (Canada)
Currently translated at 0.3% (6 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:47 +01:00
Vin
5800cc4bb2 🌐 Add translations for: Russian
Currently translated at 79.2% (1583 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2026-01-08 14:04:47 +01:00
andy
aa29a34c4c 🌐 Added translation for: French (Canada) 2026-01-08 14:04:47 +01:00
Edgars Andersons
3276129cc7 🌐 Add translations for: Latvian
Currently translated at 93.9% (1876 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-01-08 14:04:47 +01:00
VKing9
67a96de475 🌐 Add translations for: Hindi
Currently translated at 100.0% (1997 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-01-08 14:04:47 +01:00
Stephan Paternotte
48785b4846 🌐 Add translations for: Dutch
Currently translated at 99.8% (1994 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-01-08 14:04:46 +01:00
Oğuz Ersen
3f0573f95d 🌐 Add translations for: Turkish
Currently translated at 99.8% (1994 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-01-08 14:04:46 +01:00
Andrey Antukh
d94a2a8881 Merge branch 'staging-render' into develop 2026-01-08 13:59:01 +01:00
Andrey Antukh
1c237a0968 Merge branch 'staging' into staging-render 2026-01-08 13:58:48 +01:00
Elena Torró
b7eaeffa88 Merge pull request #8024 from penpot/azazeln28-issue-12835-fix-previous-styles-lost
🐛 Fix previous styles lost when changing selected text
2026-01-08 13:49:06 +01:00
Andrey Antukh
722fcc1f82 Merge remote-tracking branch 'origin/staging' into develop 2026-01-08 13:48:21 +01:00
Andrés Moya
2ad42cfd9b Add ability to remap tokens when renamed ones are referenced by other child tokens (#8035)
* 🎉 Add ability to remap tokens when renamed ones are referenced by other child tokens

Signed-off-by: Akshay Gupta <gravity.akshay@gmail.com>

* 🐛 Fix remap skipping tokens with same name in different sets

* 📚 Update CHANGES.md

* 🔧 Fix css styles

---------

Signed-off-by: Akshay Gupta <gravity.akshay@gmail.com>
Co-authored-by: Akshay Gupta <gravity.akshay@gmail.com>
2026-01-08 13:42:06 +01:00
Andrey Antukh
795f65632a 🐛 Fix wasm-playground on devenv 2026-01-08 10:42:37 +01:00
Aitor Moreno
7819e6c440 🐛 Fix previous styles lost when changing selected text 2026-01-07 12:41:39 +01:00
Andrey Antukh
952f622ce9 🔧 Add 'Reapply` prefix to valid commit checker prefixes 2026-01-07 11:56:38 +01:00
Andrey Antukh
a6c6f97f47 Reapply "💄 Group tokens by name path (#7775)"
This reverts commit eff572d3bb.
2026-01-07 11:55:56 +01:00
Andrey Antukh
88424eb54a Merge branch 'staging' into develop 2026-01-07 11:55:40 +01:00
Alejandro Alonso
de9a21121a Merge remote-tracking branch 'origin/staging' into develop 2026-01-05 13:22:14 +01:00
Alejandro Alonso
cea10308b7 Merge remote-tracking branch 'origin/staging' into develop 2026-01-05 11:52:15 +01:00
David Barragán Merino
5223c9c881 🔧 Fix a typo in an interpolation 2026-01-05 09:13:14 +01:00
Alejandro Alonso
be62fa10c4 📎 Bump new version on changelog 2026-01-05 08:42:57 +01:00
393 changed files with 53391 additions and 82491 deletions

View 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.

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -11,7 +11,7 @@ on:
- "plugins/libs/plugin-types/REAME.md"
- "plugins/tools/typedoc.css"
- "plugins/CHANGELOG.md"
- "plugins/wrangle-penpot-plugins-api-doc.toml"
- "plugins/wrangler-penpot-plugins-api-doc.toml"
workflow_dispatch:
inputs:
gh_ref:
@@ -98,4 +98,16 @@ jobs:
workingDirectory: plugins
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config wrangle-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
command: deploy --config wrangler-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
❌ 🧩📚 *[PENPOT PLUGINS] Error deploying API documentation.*
📄 Triggered from ref: `${{ inputs.gh_ref }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra

View File

@@ -0,0 +1,127 @@
name: Plugins/package deployer
on:
# Deploy package from manual action
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
plugin_name:
description: 'Pluging name (like plugins/apps/<plugin_name>-plugin)'
type: string
required: true
workflow_call:
inputs:
gh_ref:
description: 'Name of the branch'
type: string
required: true
default: 'develop'
plugin_name:
description: 'Publig name (from plugins/apps/<plugin_name>-plugin)'
type: string
required: true
permissions:
contents: read
jobs:
deploy:
runs-on: penpot-runner-01
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.gh_ref }}
# START: Setup Node and PNPM enabling cache
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Enable PNPM
working-directory: ./plugins
shell: bash
run: |
corepack enable;
corepack install;
- name: Get pnpm store path
id: pnpm-store
working-directory: ./plugins
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
# END: Setup Node and PNPM enabling cache
- name: Install deps
working-directory: ./plugins
shell: bash
run: |
pnpm install --no-frozen-lockfile;
pnpm add -D -w wrangler@latest;
- name: "Build package for ${{ inputs.plugin_name }}-plugin"
working-directory: plugins
shell: bash
run: npx nx build ${{ inputs.plugin_name }}-plugin
- name: Select Worker name
run: |
REF="${{ inputs.gh_ref }}"
case "$REF" in
main)
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pro" >> $GITHUB_ENV
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.app" >> $GITHUB_ENV ;;
staging)
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pre" >> $GITHUB_ENV
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.dev" >> $GITHUB_ENV ;;
develop)
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-hourly" >> $GITHUB_ENV
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
*) echo "Unsupported branch ${REF}" && exit 1 ;;
esac
- name: Set the custom url
working-directory: plugins
shell: bash
run: |
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" apps/${{ inputs.plugin_name }}-plugin/wrangler.toml
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
workingDirectory: plugins
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config apps/${{ inputs.plugin_name }}-plugin/wrangler.toml --name ${{ env.WORKER_NAME }}
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
❌ 🧩📦 *[PENPOT PLUGINS] Error deploying ${{ env.WORKER_NAME }}.*
📄 Triggered from ref: `${{ inputs.gh_ref }}`
Plugin name: `${{ inputs.plugin_name }}-plugin`
Cloudflare worker name: `${{ env.WORKER_NAME }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra

View File

@@ -0,0 +1,143 @@
name: Plugins/packages deployer
on:
push:
branches:
- develop
- staging
- main
paths:
- 'plugins/apps/*-plugin/**'
- 'libs/plugins-styles/**'
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
colors_to_tokens: ${{ steps.filter.outputs.colors_to_tokens }}
create_palette: ${{ steps.filter.outputs.create_palette }}
lorem_ipsum: ${{ steps.filter.outputs.lorem_ipsum }}
rename_layers: ${{ steps.filter.outputs.rename_layers }}
contrast: ${{ steps.filter.outputs.contrast }}
icons: ${{ steps.filter.outputs.icons }}
poc_state: ${{ steps.filter.outputs.poc_state }}
table: ${{ steps.filter.outputs.table }}
# [For new plugins]
# Add more outputs here
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
colors_to_tokens:
- 'plugins/apps/colors-to-tokens-plugin/**'
- 'libs/plugins-styles/**'
contrast:
- 'plugins/apps/contrast-plugin/**'
- 'libs/plugins-styles/**'
create_palette:
- 'plugins/apps/create-palette-plugin/**'
- 'libs/plugins-styles/**'
icons:
- 'plugins/apps/icons-plugin/**'
- 'libs/plugins-styles/**'
lorem_ipsum:
- 'plugins/apps/lorem-ipsum-plugin/**'
- 'libs/plugins-styles/**'
rename_layers:
- 'plugins/apps/rename-layers-plugin/**'
- 'libs/plugins-styles/**'
table:
- 'plugins/apps/table-plugin/**'
- 'libs/plugins-styles/**'
# [For new plugins]
# Add more plugin filters here
# another_plugin:
# - 'plugins/apps/another-plugin/**'
# - 'libs/plugins-styles/**'
colors-to-tokens-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.colors_to_tokens == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: colors-to-tokens
contrast-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.contrast == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: contrast
create-palette-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.create_palette == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: create-palette
icons-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.icons == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: icons
lorem-ipsum-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.lorem_ipsum == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: lorem-ipsum
rename-layers-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.rename_layers == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: rename-layers
table-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.table == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: table
# [For new plugins]
# Add more jobs for other plugins below, following the same pattern
# another-plugin:
# needs: detect-changes
# if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.another_plugin == 'true'
# uses: ./.github/workflows/plugins-deploy-package.yml
# secrets: inherit
# with:
# gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
# plugin_name: another

2
.gitignore vendored
View File

@@ -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/

View File

@@ -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

View File

@@ -1,11 +0,0 @@
enableGlobalCache: true
enableImmutableCache: false
enableImmutableInstalls: false
enableTelemetry: false
httpTimeout: 600000
nodeLinker: node-modules

View File

@@ -1,5 +1,36 @@
# 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
- Remove whitespaces from asset export filename [Github #8133](https://github.com/penpot/penpot/pull/8133)
- 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 handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
- 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)
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
## 2.13.0 (Unreleased)
### :boom: Breaking changes & Deprecations
@@ -23,12 +54,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 problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
- 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 spanish translations on import export token modal [Taiga #13171](https://tree.taiga.io/project/penpot/issue/13171)
@@ -46,7 +79,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
@@ -58,7 +90,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
@@ -91,7 +122,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
@@ -155,7 +185,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`

View File

@@ -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
View File

@@ -1,7 +0,0 @@
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

View File

@@ -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
View 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: {}

View File

View File

@@ -12,43 +12,22 @@ Debug Main Page
</nav>
<main class="dashboard">
<section class="widget">
<fieldset>
<legend>Error reports</legend>
<desc><a href="/dbg/error">CLICK HERE TO SEE THE ERROR REPORTS</a> </desc>
</fieldset>
<fieldset>
<legend>Profile Management</legend>
<form method="post" action="/dbg/actions/resend-email-verification">
<div class="row">
<input type="email" name="email" placeholder="example@example.com" value="" />
</div>
<div class="row">
<label for="force-verify">Are you sure?</label>
<input id="force-verify" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" name="resend" value="Resend Verification" />
<input type="submit" name="verify" value="Verify" />
</div>
<div class="row">
<input type="submit" class="danger" name="block" value="Block" />
<input type="submit" class="danger" name="unblock" value="Unblock" />
</div>
</form>
<legend>CURRENT PROFILE</legend>
<desc>
<p>
Name: <b>{{profile.fullname}}</b> <br />
Email: <b>{{profile.email}}</b>
</p>
</desc>
</fieldset>
<fieldset>
<legend>VIRTUAL CLOCK</legend>
<desc>
<p><b>IMPORTANT:</b> The virtual clock is profile based and only affects the currently logged-in profile.</p>
<p>
CURRENT CLOCK: <b>{{current-clock}}</b>
<br />
@@ -81,8 +60,93 @@ Debug Main Page
</form>
</fieldset>
<fieldset>
<legend>ERROR REPORTS</legend>
<desc><a href="/dbg/error">CLICK HERE TO SEE THE ERROR REPORTS</a> </desc>
</fieldset>
</section>
<section class="widget">
<fieldset>
<legend>Profile Management</legend>
<form method="post" action="/dbg/actions/resend-email-verification">
<div class="row">
<input type="email" name="email" placeholder="example@example.com" value="" />
</div>
<div class="row">
<label for="force-verify">Are you sure?</label>
<input id="force-verify" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" name="resend" value="Resend Verification" />
<input type="submit" name="verify" value="Verify" />
</div>
<div class="row">
<input type="submit" class="danger" name="block" value="Block" />
<input type="submit" class="danger" name="unblock" value="Unblock" />
</div>
</form>
</fieldset>
<fieldset>
<legend>Feature Flags for Team</legend>
<desc>Add a feature flag to a team</desc>
<form method="post" action="/dbg/actions/handle-team-features">
<div class="row">
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
</div>
<div class="row">
<select type="text" style="width:100px" name="feature">
{% for feature in supported-features %}
<option value="{{feature}}">{{feature}}</option>
{% endfor %}
</select>
</div>
<div class="row">
<select style="width:100px" name="action">
<option value="">Action...</option>
<option value="show">Show</option>
<option value="enable">Enable</option>
<option value="disable">Disable</option>
</select>
</div>
<div class="row">
<label for="check-feature">Skip feature check</label>
<input id="check-feature" type="checkbox" name="skip-check" />
<br />
<small>
Do not check if the feature is supported
</small>
</div>
<div class="row">
<label for="force-version">Are you sure?</label>
<input id="force-version" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>
</form>
</fieldset>
</section>
<section class="widget">
<fieldset>
@@ -173,55 +237,5 @@ Debug Main Page
</form>
</fieldset>
</section>
<section class="widget">
<fieldset>
<legend>Feature Flags for Team</legend>
<desc>Add a feature flag to a team</desc>
<form method="post" action="/dbg/actions/handle-team-features">
<div class="row">
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
</div>
<div class="row">
<select type="text" style="width:100px" name="feature">
{% for feature in supported-features %}
<option value="{{feature}}">{{feature}}</option>
{% endfor %}
</select>
</div>
<div class="row">
<select style="width:100px" name="action">
<option value="">Action...</option>
<option value="show">Show</option>
<option value="enable">Enable</option>
<option value="disable">Disable</option>
</select>
</div>
<div class="row">
<label for="check-feature">Skip feature check</label>
<input id="check-feature" type="checkbox" name="skip-check" />
<br />
<small>
Do not check if the feature is supported
</small>
</div>
<div class="row">
<label for="force-version">Are you sure?</label>
<input id="force-version" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>
</form>
</fieldset>
</section>
</main>
{% endblock %}

View File

@@ -49,13 +49,16 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn index-handler
[_cfg _request]
(let [{:keys [clock offset]} @clock/current]
[cfg request]
(let [profile-id (::session/profile-id request)
offset (clock/get-offset profile-id)
profile (profile/get-profile cfg profile-id)]
{::yres/status 200
::yres/headers {"content-type" "text/html"}
::yres/body (-> (io/resource "app/templates/debug.tmpl")
(tmpl/render {:version (:full cf/version)
:current-clock (str clock)
:profile profile
:current-clock ct/*clock*
:current-offset (if offset
(ct/format-duration offset)
"NO OFFSET")
@@ -447,15 +450,16 @@
(defn- set-virtual-clock
[_ {:keys [params] :as request}]
(let [offset (some-> params :offset str/trim not-empty ct/duration)
reset? (contains? params :reset)]
(let [offset (some-> params :offset str/trim not-empty ct/duration)
profile-id (::session/profile-id request)
reset? (contains? params :reset)]
(if (= "production" (cf/get :tenant))
{::yres/status 501
::yres/body "OPERATION NOT ALLOWED"}
(do
(if (or reset? (zero? (inst-ms offset)))
(clock/set-offset! nil)
(clock/set-offset! offset))
(clock/assign-offset profile-id nil)
(clock/assign-offset profile-id offset))
{::yres/status 302
::yres/headers {"location" "/dbg"}}))))
@@ -495,7 +499,7 @@
(defn authorized?
[pool {:keys [::session/profile-id]}]
(or (= "devenv" (cf/get :host))
(or (and (= "devenv" (cf/get :host)) profile-id)
(let [profile (ex/ignoring (profile/get-profile pool profile-id))
admins (or (cf/get :admins) #{})]
(contains? admins (:email profile)))))

View File

@@ -20,6 +20,7 @@
[app.http.session.tasks :as-alias tasks]
[app.main :as-alias main]
[app.setup :as-alias setup]
[app.setup.clock :as clock]
[app.tokens :as tokens]
[integrant.core :as ig]
[yetti.request :as yreq]
@@ -229,18 +230,22 @@
(let [{:keys [type token claims metadata]} (get request ::http/auth-data)]
(cond
(= type :cookie)
(let [session (case (:ver metadata)
;; BACKWARD COMPATIBILITY WITH OLD TOKENS
0 (read-session manager token)
1 (some->> (:sid claims) (read-session manager))
nil)
(let [session
(case (:ver metadata)
;; BACKWARD COMPATIBILITY WITH OLD TOKENS
0 (read-session manager token)
1 (some->> (:sid claims) (read-session manager))
nil)
request (cond-> request
(some? session)
(-> (assoc ::profile-id (:profile-id session))
(assoc ::session session)))
request
(cond-> request
(some? session)
(-> (assoc ::profile-id (:profile-id session))
(assoc ::session session)))
response (handler request)]
response
(binding [ct/*clock* (clock/get-clock (:profile-id session))]
(handler request))]
(if (and session (renew-session? session))
(let [session (->> session

View File

@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.json :as json]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
@@ -223,7 +224,7 @@
(some? tnow)
(assoc :tracked-at tnow))))
(defn- append-audit-entry!
(defn- append-audit-entry
[cfg params]
(let [params (-> params
(update :props db/tjson)
@@ -236,6 +237,16 @@
(let [params (event->params event)
tnow (ct/now)]
(when (contains? cf/flags :audit-log-logger)
(l/log! ::l/logger "app.audit"
::l/level :info
:profile-id (str (::profile-id event))
:ip-addr (str (::ip-addr event))
:type (::type event)
:name (::name event)
:props (json/encode (::props event) :key-fn json/write-camel-key)
:context (json/encode (::context event) :key-fn json/write-camel-key)))
(when (contains? cf/flags :audit-log)
;; NOTE: this operation may cause primary key conflicts on inserts
;; because of the timestamp precission (two concurrent requests), in
@@ -243,7 +254,7 @@
(let [params (-> params
(assoc :created-at tnow)
(update :tracked-at #(or % tnow)))]
(append-audit-entry! cfg params)))
(append-audit-entry cfg params)))
(when (and (or (contains? cf/flags :telemetry)
(cf/get :telemetry-enabled))
@@ -258,7 +269,7 @@
(update :tracked-at #(or % tnow))
(assoc :props {})
(assoc :context {}))]
(append-audit-entry! cfg params)))
(append-audit-entry cfg params)))
(when (and (contains? cf/flags :webhooks)
(::webhooks/event? event))
@@ -312,4 +323,4 @@
params (-> (event->params event)
(assoc :created-at tnow)
(update :tracked-at #(or % tnow)))]
(append-audit-entry! cfg params)))))))
(append-audit-entry cfg params)))))))

View File

@@ -9,48 +9,35 @@
modification of time offset (useful for testing and time adjustments)."
(:require
[app.common.logging :as l]
[app.common.time :as ct]
[app.setup :as-alias setup]
[integrant.core :as ig])
(:import
java.time.Clock
java.time.Duration
java.time.Instant
java.time.ZoneId))
[app.common.time :as ct]))
(defonce current
(atom {:clock (Clock/systemDefaultZone)
:offset nil}))
(defonce state
(atom {}))
(defmethod ig/init-key ::setup/clock
[_ _]
(add-watch current ::common
(fn [_ _ _ {:keys [clock offset]}]
(let [clock (if (ct/duration? offset)
(Clock/offset ^Clock clock
^Duration offset)
clock)]
(l/wrn :hint "altering clock" :clock (str clock))
(alter-var-root #'ct/*clock* (constantly clock))))))
(defn assign-offset
"Assign virtual clock offset to a specific user. Is the responsability
of RPC module to properly bind the correct clock to the user
request."
[profile-id duration]
(swap! state (fn [state]
(if (nil? duration)
(dissoc state profile-id)
(assoc state profile-id duration)))))
(defn get-offset
[profile-id]
(get @state profile-id))
(defmethod ig/halt-key! ::setup/clock
[_ _]
(remove-watch current ::common))
(defn get-clock
[profile-id]
(if-let [offset (get-offset profile-id)]
(ct/offset-clock offset)
(ct/get-system-clock)))
(defn fixed
"Get fixed clock, mainly used in tests"
[instant]
(Clock/fixed ^Instant (ct/inst instant)
^ZoneId (ZoneId/of "Z")))
(defn set-offset!
[duration]
(swap! current assoc :offset (some-> duration ct/duration)))
(defn set-clock!
(defn set-global-clock
([]
(swap! current assoc :clock (Clock/systemDefaultZone)))
(set-global-clock (ct/get-system-clock)))
([clock]
(when (instance? Clock clock)
(swap! current assoc :clock clock))))
(assert (ct/clock? clock) "expected valid clock instance")
(l/wrn :hint "altering clock" :clock (str clock))
(alter-var-root #'ct/*clock* (constantly clock))))

View File

@@ -17,7 +17,6 @@
[app.db.sql :as sql]
[app.http :as http]
[app.rpc :as-alias rpc]
[app.setup.clock :as clock]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
@@ -134,7 +133,7 @@
;; this will run pending task triggered by deleting user snapshot
(th/run-pending-tasks!)
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :objects-gc {})]
;; delete 2 snapshots and 2 file data entries
(t/is (= 4 (:processed res)))))))))

View File

@@ -19,7 +19,6 @@
[app.http :as http]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.setup.clock :as clock]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
@@ -922,7 +921,7 @@
(t/is (= 0 (:processed result))))
;; run permanent deletion
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [result (th/run-task! :objects-gc {})]
(t/is (= 3 (:processed result)))))
@@ -1875,7 +1874,7 @@
file-id (uuid/next)
now (ct/inst "2025-10-31T00:00:00Z")]
(binding [ct/*clock* (clock/fixed now)]
(binding [ct/*clock* (ct/fixed-clock now)]
(let [data {::th/type :create-file
::rpc/profile-id (:id prof)
:project-id proj-id
@@ -1937,7 +1936,7 @@
file-id (uuid/next)
now (ct/inst "2025-10-31T00:00:00Z")]
(binding [ct/*clock* (clock/fixed now)]
(binding [ct/*clock* (ct/fixed-clock now)]
(let [data {::th/type :create-file
::rpc/profile-id (:id prof)
:project-id proj-id
@@ -2000,7 +1999,7 @@
team-id (:default-team-id profile)
now (ct/inst "2025-10-31T00:00:00Z")]
(binding [ct/*clock* (clock/fixed now)]
(binding [ct/*clock* (ct/fixed-clock now)]
(let [project (th/create-project* 1 {:profile-id (:id profile)
:team-id team-id})
file (th/create-file* 1 {:profile-id (:id profile)

View File

@@ -85,7 +85,7 @@
(t/is (map? (:result out))))
;; run the task again
(let [res (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 31}))]
(let [res (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 31}))]
(th/run-task! "storage-gc-touched" {}))]
(t/is (= 2 (:freeze res))))
@@ -136,7 +136,7 @@
(t/is (some? (sto/get-object storage (:media-id row2))))
;; run the task again
(let [res (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 31}))]
(let [res (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 31}))]
(th/run-task! :storage-gc-touched {}))]
(t/is (= 1 (:delete res)))
(t/is (= 0 (:freeze res))))
@@ -147,7 +147,7 @@
;; Run the storage gc deleted task, it should permanently delete
;; all storage objects related to the deleted thumbnails
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 1 (:deleted res)))))
@@ -247,7 +247,7 @@
;; Run the storage gc deleted task, it should permanently delete
;; all storage objects related to the deleted thumbnails
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [result (th/run-task! :storage-gc-deleted {})]
(t/is (= 1 (:deleted result)))))

View File

@@ -12,7 +12,6 @@
[app.db :as db]
[app.http :as http]
[app.rpc :as-alias rpc]
[app.setup.clock :as clock]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
@@ -147,7 +146,7 @@
(t/is (= 0 (:freeze res)))
(t/is (= 0 (:delete res))))
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :objects-gc {})]
(t/is (= 2 (:processed res))))
@@ -208,7 +207,7 @@
(t/is (= 0 (:freeze res)))
(t/is (= 0 (:delete res))))
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :objects-gc {})]
(t/is (= 1 (:processed res))))
@@ -268,7 +267,7 @@
(t/is (= 0 (:freeze res)))
(t/is (= 0 (:delete res))))
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :objects-gc {})]
(t/is (= 1 (:processed res))))

View File

@@ -12,7 +12,6 @@
[app.db :as db]
[app.http :as http]
[app.rpc :as-alias rpc]
[app.setup.clock :as clock]
[backend-tests.helpers :as th]
[clojure.test :as t]))
@@ -228,7 +227,7 @@
(t/is (= 0 (count result)))))
;; run permanent deletion
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [result (th/run-task! :objects-gc {})]
(t/is (= 1 (:processed result)))))

View File

@@ -13,7 +13,6 @@
[app.db :as db]
[app.http :as http]
[app.rpc :as-alias rpc]
[app.setup.clock :as clock]
[app.storage :as sto]
[app.tokens :as tokens]
[backend-tests.helpers :as th]
@@ -526,7 +525,7 @@
(t/is (= :not-found (:type edata)))))
;; run permanent deletion
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [result (th/run-task! :objects-gc {})]
(t/is (= 2 (:processed result)))))
@@ -583,7 +582,7 @@
(t/is (= 1 (count rows)))
(t/is (ct/inst? (:deleted-at (first rows)))))
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [result (th/run-task! :objects-gc {})]
(t/is (= 7 (:processed result)))))))

View File

@@ -11,7 +11,6 @@
[app.common.uuid :as uuid]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.setup.clock :as clock]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
@@ -99,14 +98,14 @@
::sto/expired-at (ct/in-future {:hours 1})
:content-type "text/plain"})]
(binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 0}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 0}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 1 (:deleted res)))))
(let [res (th/db-exec-one! ["select count(*) from storage_object;"])]
(t/is (= 2 (:count res))))
(binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 61}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 61}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 1 (:deleted res)))))
@@ -331,22 +330,22 @@
:content-type "text/plain"})]
(binding [ct/*clock* (clock/fixed now)]
(binding [ct/*clock* (ct/fixed-clock now)]
(let [res (th/run-task! :storage-gc-touched {})]
(t/is (= 0 (:freeze res)))
(t/is (= 0 (:delete res)))))
(binding [ct/*clock* (clock/fixed (ct/plus now {:minutes 1}))]
(binding [ct/*clock* (ct/fixed-clock (ct/plus now {:minutes 1}))]
(let [res (th/run-task! :storage-gc-touched {})]
(t/is (= 0 (:freeze res)))
(t/is (= 1 (:delete res)))))
(binding [ct/*clock* (clock/fixed (ct/plus now {:hours 1}))]
(binding [ct/*clock* (ct/fixed-clock (ct/plus now {:hours 1}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 0 (:deleted res)))))
(binding [ct/*clock* (clock/fixed (ct/plus now {:hours 2}))]
(binding [ct/*clock* (ct/fixed-clock (ct/plus now {:hours 2}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 0 (:deleted res)))))))

View File

File diff suppressed because it is too large Load Diff

7
common/.gitignore vendored
View File

@@ -1,7 +0,0 @@
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

View File

@@ -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"]}

View File

@@ -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
View 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

View File

View File

@@ -3,5 +3,5 @@
set -ex
corepack enable;
corepack install;
yarn install;
yarn run test;
pnpm install;
pnpm run test;

View File

@@ -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

View File

@@ -62,6 +62,7 @@
#{:audit-log
:audit-log-archive
:audit-log-gc
:audit-log-logger
:auto-file-snapshot
;; enables the `/api/doc` endpoint that lists all the rpc methods available.
:backend-api-doc
@@ -134,6 +135,8 @@
:subscriptions
:subscriptions-old
:inspect-styles
;; Enable performance logs in devconsole (disabled by default)
:perf-logs
;; Security layer middleware that filters request by fetch
;; metadata headers

View File

@@ -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

View File

@@ -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))]

View File

@@ -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))

View File

@@ -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)))

View File

@@ -64,8 +64,33 @@
java.time.temporal.TemporalAmount
java.time.temporal.TemporalUnit)))
(declare inst)
#?(:clj (def ^:dynamic *clock* (Clock/systemDefaultZone)))
#?(:clj
(defn clock?
[o]
(instance? Clock o)))
#?(:clj
(defn get-system-clock
[]
(Clock/systemDefaultZone)))
#?(:clj
(defn offset-clock
[offset]
(Clock/offset ^Clock (Clock/systemDefaultZone) ^Duration offset)))
#?(:clj
(defn fixed-clock
[instant]
(Clock/fixed ^Instant (inst instant)
^ZoneId (ZoneId/of "Z"))))
(defn now
[]
#?(:clj (Instant/now *clock*)

View File

@@ -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.

View File

@@ -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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -462,8 +474,8 @@
:height #{:sizing :dimensions}
:max-width #{:sizing :dimensions}
:max-height #{:sizing :dimensions}
:x #{:spacing :dimensions}
:y #{:spacing :dimensions}
:x #{:dimensions}
:y #{:dimensions}
:rotation #{:number :rotation}
:border-radius #{:border-radius :dimensions}
:row-gap #{:spacing :dimensions}
@@ -475,6 +487,8 @@
:vertical-margin #{:spacing :dimensions}
:sided-margins #{:spacing :dimensions}
:line-height #{:line-height :number}
:opacity #{:opacity}
:stroke-width #{:stroke-width :dimensions}
:font-size #{:font-size}
:letter-spacing #{:letter-spacing}
:fill #{:color}
@@ -558,3 +572,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))

View File

@@ -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)))))

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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"]

View File

@@ -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;
}
}
}

View File

@@ -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).

View File

@@ -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
View File

File diff suppressed because it is too large Load Diff

View File

@@ -6,5 +6,5 @@ corepack enable;
corepack install;
rm -rf ./_dist
yarn install
yarn run build
pnpm install
pnpm run build

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

File diff suppressed because it is too large Load Diff

7
exporter/.gitignore vendored
View File

@@ -1,7 +0,0 @@
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

View File

@@ -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
View File

File diff suppressed because it is too large Load Diff

View File

View 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;

View File

@@ -4,5 +4,5 @@ set -e;
corepack enable;
corepack install;
yarn install;
yarn playwright install chromium
pnpm install;
pnpx playwright install chromium

View File

@@ -4,4 +4,4 @@ TARGET=${1:-app};
set -ex
exec yarn run watch:$TARGET
exec pnpm run watch:$TARGET

View File

File diff suppressed because it is too large Load Diff

14
frontend/.gitignore vendored
View File

@@ -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

View File

@@ -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",
"@playwright/test": "1.57.0",
"@penpot/text-editor": "workspace:./text-editor",
"@playwright/test": "1.58.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",
"@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",
"@types/node": "^25.0.3",
"@vitest/browser": "4.0.18",
"@vitest/browser-playwright": "^4.0.18",
"@vitest/coverage-v8": "4.0.18",
"@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.58.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,9 +108,9 @@
"tdigest": "^0.1.2",
"tinycolor2": "^1.6.0",
"typescript": "^5.9.2",
"ua-parser-js": "2.0.5",
"vite": "^7.3.0",
"vitest": "^4.0.16",
"ua-parser-js": "2.0.7",
"vite": "^7.3.1",
"vitest": "^4.0.18",
"wait-on": "^9.0.3",
"wasm-pack": "^0.13.1",
"watcher": "^2.3.1",

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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

View 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 {

View File

@@ -64,6 +64,11 @@ export default defineConfig({
...devices["Desktop Chrome"],
viewport: { width: 1920, height: 1080 }, // Add custom viewport size
deviceScaleFactor: 2,
launchOptions: {
args: [
'--enable-gpu',
],
}
},
testDir: "./playwright/ui/render-wasm-specs",
snapshotPathTemplate: "{testDir}/{testFilePath}-snapshots/{arg}.png",
@@ -80,7 +85,7 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
timeout: 2 * 60 * 1000,
command: "yarn run e2e:server",
command: "node ./scripts/e2e-server.js",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},

View File

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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",
};

View File

@@ -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",
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -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();

View File

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

View File

@@ -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/);
});
});

View 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,
};

View 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}");
});
});
});

View 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");
});
});

View 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();
});
});

View 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();
});
});

View File

@@ -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();
});

9010
frontend/pnpm-lock.yaml generated Normal file
View File

File diff suppressed because it is too large Load Diff

View 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"

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 279 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 279 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

View File

@@ -13,7 +13,7 @@
$weight: unquote("normal"),
$style: string.unquote("normal")
) {
$filepath: "/fonts/" + $file;
$filepath: "../fonts/" + $file;
@font-face {
font-family: "#{$style-name}";
@@ -29,7 +29,7 @@
}
@mixin font-face-variable($style-name, $file, $unicode-range) {
$filepath: "/fonts/" + $file;
$filepath: "../fonts/" + $file;
@font-face {
font-family: "#{$style-name}";

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,

Some files were not shown because too many files have changed in this diff Show More