Compare commits

...

358 Commits

Author SHA1 Message Date
Andrey Antukh
e2c5a1378e Merge pull request #6724 from penpot/elenatorro-improve-create-profile-command
🔧 Add option to skip tutorial/walkthrough when creating a profile from the script
2025-07-11 14:15:49 +02:00
Xavier Julian
81f99458e5 📎 Add new font-size token type to CHANGELOG 2025-07-11 14:09:39 +02:00
Florian Schroedl
9c5a13c4ac Enable font-size token 2025-07-11 10:37:17 +02:00
Xavier Julian
02ae934e25 📎 Add import tokens from zip file to CHANGELOG 2025-07-10 13:40:42 +02:00
Andrey Antukh
95cfb26b38 Merge pull request #6882 from penpot/xaviju-11283-info-tab-visibility-attrs-review
♻️ Fix tab info not updating and suggested code refactor
2025-07-10 11:56:38 +02:00
Andrey Antukh
935c22d124 Merge pull request #6885 from penpot/marina-change-text-capitalize
🐛 Fix title button from Title case to Capitalize
2025-07-10 11:55:50 +02:00
Marina López
ba6a02d1d9 🐛 Add fixes from subscription design review (#6870)
* 🐛 Fixes from subscription design review

* 🐛 Fix to consider professional plan the unpaid and canceled status

* 📎 Fixes PR feedback
2025-07-10 11:55:16 +02:00
Xavier Julian
0b681effe7 ♻️ Fix tab info not updating and suggested code refactor 2025-07-10 11:38:53 +02:00
Marina López
6826db8498 🐛 Fix title button from Title case to Capitalize 2025-07-10 11:29:48 +02:00
Andrey Antukh
66c5841d48 Merge pull request #6886 from penpot/alotor-fix-create-layout
🐛 Fix problem when creating a layout from an existing layout
2025-07-10 11:28:19 +02:00
Xavier Julian
af10705b4c ♻️ Review import message text 2025-07-10 10:18:59 +02:00
Pablo Alba
41146ef71d 🐛 Fix text overrides when there are structure changes 2025-07-09 21:58:01 +02:00
Pablo Alba
abb6aee57d 🐛 On texts overrides, keep also vertical-align property 2025-07-09 21:58:01 +02:00
alonso.torres
aa01d3b707 🐛 Fix problem when creating a layout from an existing layout 2025-07-09 15:44:15 +02:00
alonso.torres
a003687256 🐛 Fix problem with grid assignments 2025-07-09 14:55:14 +02:00
Andrey Antukh
51a6d61be6 Merge pull request #6865 from penpot/xaviju-11283-info-tab-visibility-attrs
 Add visibility group and attributes to info tab
2025-07-09 12:18:10 +02:00
Xavier Julian
0daa8be0b5 Add visibility group and attributes to info tab 2025-07-09 11:19:30 +02:00
Andrey Antukh
00599f76d0 Merge pull request #6875 from penpot/ladybenko-fix-devenv-mac-ubuntu
🔧 Fix building and running devenv (Mac / Linux)
2025-07-09 08:28:49 +02:00
Belén Albeza
cb8aae4d5f 🔧 Drop the -R in chown (dockerfile mac) 2025-07-08 15:45:34 +02:00
Belén Albeza
927228fc8f 🔧 Remove COPY of apt.sources (linux issue) 2025-07-08 15:44:42 +02:00
Andrés Moya
88bb9bfe52 🐛 Detach styles from assets when applying tokens 2025-07-08 13:15:45 +02:00
Andrey Antukh
e554b9fcb7 Merge remote-tracking branch 'origin/staging' into develop 2025-07-08 11:04:29 +02:00
Aitor Moreno
4548310235 Merge pull request #6867 from penpot/azazeln28-fix-missing-solid-color
🐛 Fix missing required SolidColor
2025-07-08 09:11:12 +02:00
Aitor Moreno
ea9261b0b2 🐛 Fix missing required SolidColor 2025-07-08 08:45:03 +02:00
Aitor Moreno
6ffcd58368 Merge pull request #6846 from penpot/alotor-wasm-refactor-mut-2
♻️ Refactor wasm shapes state management
2025-07-08 08:31:15 +02:00
alonso.torres
69135ef8c7 ♻️ Refactor wasm shapes state management 2025-07-08 08:30:40 +02:00
Aitor Moreno
747427daa4 Merge pull request #6841 from penpot/superalex-fix-frame-clipping
🐛 Fix frame clipping
2025-07-08 08:26:48 +02:00
Alejandro Alonso
5b704faf79 Merge pull request #6862 from penpot/niwinz-develop-minor-fixes
 Add several improvements
2025-07-07 15:40:20 +02:00
Andrey Antukh
d24eab7241 📎 Update changelog 2025-07-07 15:07:07 +02:00
Andrey Antukh
c8fef97598 Add missing field on get-teams rpc method 2025-07-07 13:36:00 +02:00
Andrey Antukh
44e3e4a641 🔥 Remove image shape inspect pannel
Image shapes are long ago deprecated not used
on penpot
2025-07-07 13:36:00 +02:00
Andrey Antukh
f3616c68a0 Improve decode empty string for path content 2025-07-07 13:36:00 +02:00
Andrey Antukh
9ea3f81bc4 🐛 Clean invalid library colors 2025-07-07 13:35:59 +02:00
Pablo Alba
cfec023585 ♻️ Rename flag :component-swap to :allow-altering-copies 2025-07-07 12:07:36 +02:00
Pablo Alba
469d47eaf3 🐛 Fix variants combobox and select to new options format 2025-07-07 11:46:50 +02:00
Alejandro Alonso
51bb6583d2 🐛 Fix frame clipping 2025-07-07 11:09:29 +02:00
Pablo Alba
a44c70ef69 Keep the swapped childs if the copies when doing a variant switch 2025-07-07 10:50:49 +02:00
Andrés Moya
4fddf34a73 🐛 Fix error when there exists a tokens lib with no sets 2025-07-07 10:02:49 +02:00
Xavier Julian
8f840daa91 Improve token import error copy 2025-07-07 09:59:57 +02:00
Juanfran
0a7d6d98e1 Integrate plugin runtime as npm library (#6852) 2025-07-07 09:46:07 +02:00
Álvaro Tejero-Cantero
bcb69b6227 🐛 Restore viewport and selection when exiting focus mode (#6827)
* 📚 Provide guidance on how to exit focus mode

* 🐛 Restore viewport & selection post focus mode

* 📎 Update changelog
2025-07-07 09:44:06 +02:00
Andrey Antukh
92d708d52c Merge remote-tracking branch 'origin/staging' into develop 2025-07-07 09:37:55 +02:00
Alonso Torres
accd5226d7 🐛 Fix sidebar width in localhost (#6732) 2025-07-07 09:28:52 +02:00
Andrey Antukh
16a1fd14e5 🐛 Fix media translation on text nodes on paste (#6845)
Fix incorrect media translation on paste text with fill images
2025-07-07 09:03:35 +02:00
Aitor Moreno
824bb19c7e Merge pull request #6848 from penpot/niwinz-staging-library-referer
 Add referer field to binfile v3
2025-07-07 09:02:13 +02:00
Aitor Moreno
d0f3e0f0b0 Merge pull request #6853 from penpot/niwinz-staging-path-bool-fixes
🐛 Fix exception on a corner case of creating bool shape
2025-07-07 09:00:57 +02:00
Andrey Antukh
43ba2b05e8 📎 Change current config values for error report explain 2025-07-04 14:51:08 +02:00
Andrey Antukh
d5ccb704b2 🐛 Fix unexpected exception on creating bool shapes 2025-07-04 14:42:09 +02:00
Andrey Antukh
0374e4f3eb Merge remote-tracking branch 'origin/staging' into develop 2025-07-04 12:02:12 +02:00
David Barragán Merino
6d21fcc9de 🔧 Fix condition for automatic events (#6849) 2025-07-04 11:57:57 +02:00
Andrey Antukh
77741b49a7 Add tracking for referer on the import-binfile 2025-07-04 11:02:36 +02:00
Andrey Antukh
a7e0cfc609 🎉 Bump 1.0.7 release of the penpot library
Includes the ability to pass referer
2025-07-04 11:02:36 +02:00
Andrey Antukh
50a6355537 🎉 Add options for creating library build context
With the ability to pass referer.
2025-07-04 11:02:36 +02:00
Alejandro Alonso
264aef277d Merge pull request #6847 from penpot/niwinz-staging-error-reports-2
 Add minor improvements to error report on calc bool content
2025-07-04 10:53:02 +02:00
Andrey Antukh
78d0e6d059 Add minor improvements to error report on calc bool content 2025-07-04 10:13:24 +02:00
Alonso Torres
6d41d36b3a 🐛 Fix problem when double click on hidden shapes (#6833) 2025-07-04 09:01:20 +02:00
David Barragán Merino
bb97df373e 🔧 Add Github Action to build and upload artifact (#6840)
Co-authored-by: Francis Santiago <francis.santiago@kaleidos.net>
2025-07-04 08:41:46 +02:00
David Barragán Merino
528c819323 🔧 Add Github Action to build and upload artifact (#6840)
Co-authored-by: Francis Santiago <francis.santiago@kaleidos.net>
2025-07-04 08:25:23 +02:00
Florian Schrödl
21746144b7 Add letter spacing token (#6814)
* 🐛 Fix merge schema not working with key generation

*  Add letter-spacing token

* ♻️ Remove comments

* ♻️ Inline line-height for now
2025-07-03 16:00:58 +02:00
Andrey Antukh
3165761bac Merge remote-tracking branch 'origin/staging' into develop 2025-07-03 15:32:30 +02:00
Andrés Moya
c09f72c3d5 🐛 Sanitize wrong ids in token themes (#6843) 2025-07-03 15:31:45 +02:00
Alejandro Alonso
a41af032cd Merge pull request #6844 from penpot/niwinz-staging-enhancements
 Improve error reporting
2025-07-03 15:31:20 +02:00
Andrey Antukh
86ee4f55c5 📚 Update docstring 2025-07-03 14:54:00 +02:00
Andrey Antukh
63cd3ae025 Add better error handling for bool creation 2025-07-03 14:54:00 +02:00
Andrey Antukh
cafb7abb53 🎉 Add better syntax facility for ex/try! macro 2025-07-03 14:54:00 +02:00
Andrey Antukh
e5b6c4a9e0 Add minor improvement to error reporter logger 2025-07-03 14:54:00 +02:00
Andrey Antukh
1d5bad5523 💄 Report file-id on file changes exception 2025-07-03 14:54:00 +02:00
Andrey Antukh
96d6868b45 🐛 Add missing fields on get-team-shared-files query 2025-07-03 14:54:00 +02:00
Andrey Antukh
b739d8bd0c 💄 Change default depth on params for error reports 2025-07-03 14:54:00 +02:00
Alejandro Alonso
dd803dc1de Merge pull request #6839 from penpot/niwinz-staging-fix-broken-path
🐛 Add migration for fix undecoded path content
2025-07-03 13:43:59 +02:00
Florian Schrödl
7dd61968b5 Implement object type specific tokens (#6816)
*  Allow token applying for supported shape types only

* 🐛 Remove x/y attribute keys from spacing token

*  Shape specific context-menu

*  Only apply tokens to supported shapes when doing multi selection apply

*  Handle groups not supported by tokens yet

* 🐛 Fix outdated tests

* ♻️ Commentary

*  Add helper functions for attribute applicability checks

* ♻️ Groups don't have own attributes

* ♻️ Remove unused function

* ♻️ Move attribute logic to common.types.token
2025-07-03 12:22:04 +02:00
Juanfran
669d6d9ae2 Merge pull request #6837 from penpot/juanfran-us-11186-rules-help
 Add in-app help to guide users about variant rules
2025-07-03 11:30:16 +02:00
Andrey Antukh
b627c10737 🔥 Remove duplicated check-fn 2025-07-03 10:50:09 +02:00
María Valderrama
95f4a9bd29 Add missing start-plugin event (#6809)
*  Add missing start-plugin event

* 📎 Correct event origin
2025-07-03 10:29:43 +02:00
Miguel de Benito Delgado
b931547300 🐳 Add "postgres" network alias to default docker network in devenv (#6823) 2025-07-03 10:28:53 +02:00
Andrey Antukh
a2b8f19ff3 🐛 Add migration for fix undecoded path content 2025-07-03 08:40:23 +02:00
Belén Albeza
30274c4f5c 🔧 Restore arm64 build of devenv (#6826) 2025-07-03 08:28:07 +02:00
Andrés Moya
0a71134652 🔧 Sanitize and check tokens when deserializing from db (#6838) 2025-07-02 17:01:10 +02:00
Juanfran
72b1919e29 Add in-app help to guide users about variant rules 2025-07-02 14:46:36 +02:00
Andrey Antukh
898182e3d5 Add minor events props normalization (#6836) 2025-07-02 14:41:48 +02:00
Xavier Julian
be43365909 🐛 Fix broken import file type drodown options 2025-07-02 14:35:01 +02:00
Andrey Antukh
41994703a9 ♻️ Refactor tab-switcher* component (#6815)
* 💄 Add minor style adjustments to workspace sidebar

* 💄 Add style enhacement to sitemap component

* ♻️ Refactor tab-switcher* component
2025-07-02 14:08:47 +02:00
Marina López
3d45080e3c 🐛 Fixes from subscription design review (#6812) 2025-07-02 10:49:16 +02:00
Miguel de Benito Delgado
28c055e3f9 📚 Fix and extend backend repl doc (#6819) 2025-07-02 10:38:35 +02:00
Prithvi Tharun
4f993bf4ae 💄 Replace 'Verify new email' label with 'Confirm new email' (#6831)
Improves clarity by using more accurate and familiar terminology.

Signed-off-by: Prithvi Tharun <ptrithu8@gmail.com>
2025-07-02 10:32:09 +02:00
Alejandro Alonso
3cb0e1b6ee 🐛 Fix exif rotation detection when auto-rotation isn't supported (#6818) 2025-07-02 10:31:05 +02:00
Miguel de Benito Delgado
e03c822b51 🐛 Fix internal error on missing theme setting in profile (#6822) 2025-07-02 10:17:22 +02:00
Andrey Antukh
1432b211a6 Merge remote-tracking branch 'origin/staging' into develop 2025-07-02 10:13:30 +02:00
Miguel de Benito Delgado
3e45e4fb25 🐛 Fix internal error on missing theme setting in profile (#6822) 2025-07-02 09:57:56 +02:00
Eva Marco
a3aabf3b7d 🐛 Fix tooltip position after click (#6830) 2025-07-02 09:56:14 +02:00
Andrés Moya
953287ea33 🐛 Avoid crash in combobox with empty options 2025-07-02 08:57:25 +02:00
Elena Torró
493831f110 Merge pull request #6821 from penpot/alotor-refactor-mutability
♻️ Refactor mutability modifiers in wasm
2025-07-01 13:52:39 +02:00
alonso.torres
3d374e8e97 ♻️ Refactor mutability modifiers in wasm 2025-07-01 12:47:31 +02:00
Andrés Moya
f0f01af55c 🔧 Make TokenSet an abstract data type 2025-06-30 16:59:00 +02:00
Xavier Julian
6de9de9e38 Add new metric for token update and provide token type 2025-06-30 13:21:49 +02:00
Kelp
b893a62e40 Add new typography icon to the DS (#6808)
Signed-off-by: Kelp <5446186+NatachaMenjibar@users.noreply.github.com>
2025-06-30 11:06:54 +02:00
alonso.torres
8dcb376b18 Add drop grid cells in wasm 2025-06-30 10:28:59 +02:00
alonso.torres
52a4fc6030 🐛 Fix drop index on flex layout wasm 2025-06-30 10:28:59 +02:00
Andrey Antukh
403d92838a ♻️ Add minor refactor to options dropdown options handling and validation (#6739)
* ♻️ Refactor options-dropdown* and related components

* 🐛 Fix props error

* 🐛 Fix test

* 📎 Update rumext

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
2025-06-29 11:52:29 +02:00
Xavier Julian
6bd3253e5e ♻️ Restructure UI files for tokens editor 2025-06-27 13:23:42 +02:00
Pablo Alba
20b5b7f6e4 🐛 Fix variant switch in another page (#6802) 2025-06-27 12:23:54 +02:00
Andrés Moya
5c4fd97541 🐛 Allow importing file without any token but with themes or sets (#6796) 2025-06-27 11:32:14 +02:00
Pablo Alba
804146ae9a 🐛 Fix text partial change doesn't show up on another page (#6799) 2025-06-27 10:21:21 +02:00
Juanfran
24e78e6a10 🐛 Display error message on register form (#6797) 2025-06-27 10:01:54 +02:00
Pablo Alba
daca26e54f 🐛 On variants override use the component name instead of the copy name 2025-06-26 17:37:21 +02:00
Aitor Moreno
29016cef49 Merge pull request #6794 from penpot/alotor-wasm-fix-grid-fr
🐛 Fix problem with fr allocation
2025-06-26 14:39:42 +02:00
alonso.torres
fb07788e8f 🐛 Fix problem with fr allocation 2025-06-26 13:17:26 +02:00
Alejandro Alonso
3010abbf64 Merge pull request #6793 from penpot/alotor-fix-plugins-system-theme
🐛 Fix problem with plugins on system theme
2025-06-26 12:30:51 +02:00
alonso.torres
e6a7eed7a9 🐛 Fix problem with plugins on system theme 2025-06-26 12:15:06 +02:00
Andrey Antukh
c75a617d26 Merge remote-tracking branch 'origin/staging' into develop 2025-06-26 11:19:29 +02:00
Andrey Antukh
f2c4a1eb1f Merge pull request #6674 from penpot/niwinz-develop-enhacements-3
 Refactor fills-menu and related components
2025-06-26 11:09:30 +02:00
Marina López
62371fded0 🐛 Fix libraries position in dashboard sidebar (#6791) 2025-06-26 11:08:18 +02:00
Alejandro Alonso
daf3b5caa8 🐛 Fix slow color picker (#6780) 2025-06-26 11:07:35 +02:00
Andrey Antukh
e72d31a082 🔥 Remove unused and commented code 2025-06-26 10:50:38 +02:00
Andrey Antukh
6b4a85cd15 🐛 Fix issue on changing from gradient to solid color on colorpicker 2025-06-26 10:50:38 +02:00
Andrey Antukh
027a7a457d Add minor style improvements for reorder-handler component 2025-06-26 10:50:38 +02:00
Andrey Antukh
20d2d22f39 Add performance oriented refactor to fill-menu component 2025-06-26 10:50:36 +02:00
Andrey Antukh
34d65ed1c8 Merge pull request #6775 from penpot/superalex-fix-entering-long-project-name
🐛 Fix entering long project name
2025-06-26 10:48:54 +02:00
Andrey Antukh
a191fe63a1 Merge remote-tracking branch 'origin/staging' into develop 2025-06-26 09:18:23 +02:00
luisδμ
2de0c90fc7 🐛 Remove empty properties starting with the last one (#6757)
* 🐛 Remove empty properties starting with the last one

*  MR changes

---------

Co-authored-by: Pablo Alba <pablo.alba@kaleidos.net>
2025-06-26 09:16:59 +02:00
Alejandro Alonso
27c624ae0f Merge pull request #6787 from penpot/niwinz-staging-hotfix-4
🐛 Several fixes
2025-06-26 09:13:49 +02:00
Andrey Antukh
3831b3034e 🐛 Fix boolean shape migration that causes issues on import 2025-06-26 08:55:09 +02:00
Luis de Dios
7cd0e28c3b Allow variants with no properties 2025-06-26 08:42:34 +02:00
Andrey Antukh
00390a1349 🐛 Add correct is-text-node? predicate to text processing methods 2025-06-26 08:32:11 +02:00
Andrey Antukh
17bfed137c 📎 Add better defaults for text processing on old migrations 2025-06-26 08:32:11 +02:00
Andrey Antukh
77ef26b207 📎 Add srepl script for validate file schema 2025-06-26 08:32:11 +02:00
Andrey Antukh
26239a15f2 📎 Add missing changes on lost-colors fix script 2025-06-25 20:13:35 +02:00
Andrey Antukh
25ef1800d0 Merge remote-tracking branch 'origin/staging' into develop 2025-06-25 19:30:57 +02:00
Andrey Antukh
207974fe6c Add minor improvement to color cleaning migration 2025-06-25 19:26:43 +02:00
Andrey Antukh
b52e2fa681 🐛 Add missing version field on get-team-shared-files internal query 2025-06-25 19:24:18 +02:00
Andrey Antukh
bf719b587f Add better shadow cleaning migration 2025-06-25 19:17:58 +02:00
Alejandro Alonso
61109c91e3 Merge pull request #6784 from penpot/niwinz-staging-hotfix-3
🐛 Fix incorrect library color cleaning mechanism
2025-06-25 16:21:58 +02:00
Andrey Antukh
4915a97c2c 📎 Add script for restoring lost colors 2025-06-25 16:10:35 +02:00
Andrey Antukh
903aba5642 🐛 Fix incorrect library color cleaning mechanism 2025-06-25 14:36:33 +02:00
Andrey Antukh
9760911fce Merge remote-tracking branch 'origin/staging' into develop 2025-06-25 14:24:26 +02:00
Alejandro Alonso
82583f5079 🐛 Fix entering long project name 2025-06-25 14:21:52 +02:00
Alejandro Alonso
4561392791 🐛 Fix shortcut error pressing G+W from the View Mode (#6772) 2025-06-25 14:14:44 +02:00
Marina López
f81a973a4d 🐛 Fix text decoration line through value in inspect tab (#6778) 2025-06-25 14:11:58 +02:00
Alejandro Alonso
ca99671d3c 📚 Update CHANGES with support for exif rotated images (#6782) 2025-06-25 14:10:13 +02:00
Alejandro Alonso
8e8b2acddd Merge pull request #6781 from penpot/niwinz-staging-hotfix-2
🐛 Hot fixes
2025-06-25 13:53:58 +02:00
Marina López
1f42f032fc 🐛 Add fixes for subscription design review (#6751)
* 🐛 Fix from subscription design review

* 📎 Fixes PR feedback
2025-06-25 13:41:45 +02:00
Andrey Antukh
93cbd99932 🐛 Clear invalid keys from color libraries 2025-06-25 13:38:35 +02:00
Andrey Antukh
15c91a5de5 Make the bool-content normalize migration idempotent 2025-06-25 13:26:32 +02:00
Andrey Antukh
7f2e819789 🐛 Fix migration persistence ordering issue
When migrations are applied to old files
2025-06-25 13:26:25 +02:00
Alejandro Alonso
4947bf480b Merge pull request #6779 from penpot/niwinz-staging-hotfix-2
🐛 Fix regression on changing color type on fills
2025-06-25 12:27:34 +02:00
Marina López
67ca8ccb22 🐛 Fix copy font-size doesn't copy the unit (#6776) 2025-06-25 12:14:33 +02:00
Alejandro Alonso
c51ae35fc5 Merge pull request #6777 from penpot/niwinz-staging-hotfix-1
🐛 Remove qualified keyword keys from colors
2025-06-25 12:13:53 +02:00
Andrey Antukh
81564dbfa9 🐛 Fix regression on changing color type on fills 2025-06-25 12:12:04 +02:00
Andrey Antukh
56472a95de 🐛 Add missing file migration to get-team-shared-files rpc method 2025-06-25 11:56:11 +02:00
Andrey Antukh
9e5bc3675c 🐛 Remove cider nrepl handler from default nrepl server 2025-06-25 11:41:24 +02:00
Xavier Julian
ce59070fd1 ♻️ Restructure UI files for token sets 2025-06-25 11:27:13 +02:00
Andrey Antukh
787c066357 🐛 Remove qualified keyword keys from colors 2025-06-25 11:23:39 +02:00
Marina López
82bedda604 Add tokens library to dashboard carousel (#6769) 2025-06-25 10:22:19 +02:00
Marina López
e258030bc0 💄 Change 'save color' button (#6774) 2025-06-25 10:21:22 +02:00
Alejandro Alonso
8f00292f8f 🎉 Support for exim rotated images (#6767) 2025-06-25 10:20:37 +02:00
Alejandro Alonso
1b67be2f36 Merge pull request #6773 from penpot/niwinz-staging-paste-regression
🐛 Fix minor regression on paste shapes with fill-image
2025-06-25 10:02:36 +02:00
Andrey Antukh
1a8a9df2b7 🐛 Fix minor regression on paste shapes with fill-image 2025-06-25 09:40:17 +02:00
Alejandro Alonso
e1ce7ec787 Merge pull request #6752 from penpot/niwinz-staging-path-editor-fixes
🐛 Fix incorrect path tool handling on shapes not coerced to path
2025-06-25 07:25:13 +02:00
Andrey Antukh
546b7d5f60 🐛 Fix incorrect path tool handling on shapes not coerced to path 2025-06-24 19:09:38 +02:00
Florian Schroedl
fe91201431 Keep warning for unsupported token types when FF is disabled 2025-06-24 15:41:24 +02:00
Florian Schroedl
00c7411f92 🐛 Fix dtcg token type name 2025-06-24 15:41:24 +02:00
Xavier Julian
e585cbd673 ♻️ Restructure UI files for import/export and common files 2025-06-24 13:58:54 +02:00
Alejandro Alonso
bdc10ac173 Merge pull request #6754 from penpot/azazeln28-issue-11401-fix-wrong-aspect-ratio
🐛 Fix image aspect ratio rendering on oriented images
2025-06-24 13:23:35 +02:00
Elena Torró
9f5cb61a19 Merge pull request #6766 from penpot/elenatorro-fix-text-auto-height
🐛 Fix text auto height
2025-06-24 13:18:28 +02:00
Alejandro Alonso
fb6121bf92 Merge pull request #6765 from penpot/alotor-update-plugins-runtime
⬆️ Update plugins runtime
2025-06-24 13:14:53 +02:00
Alejandro Alonso
e442d8adad Add tests for exif rotated images 2025-06-24 13:08:18 +02:00
Elena Torro
925af4e1e9 🐛 Fix text auto height 2025-06-24 12:36:12 +02:00
alonso.torres
a45886c86c Small cosmetic change 2025-06-24 10:26:37 +02:00
alonso.torres
36b6f6323a ♻️ Refactor modifiers methods 2025-06-24 10:26:37 +02:00
alonso.torres
afec3b9bc1 🐛 Fix problem with margin in flex layout 2025-06-24 10:26:37 +02:00
alonso.torres
ac6a814026 🐛 Fix problem with flex layout in wasm 2025-06-24 10:26:37 +02:00
Alejandro Alonso
89fb802362 Merge pull request #6764 from penpot/alotor-fix-problem-lines
🐛 Fix wasm problem with horizontal/vertical lines
2025-06-24 09:43:11 +02:00
alonso.torres
8cdcfb70e2 ⬆️ Update plugins runtime 2025-06-24 09:25:12 +02:00
alonso.torres
b0d858df2b 🐛 Fix wasm problem with horizontal/vertical lines 2025-06-24 09:24:00 +02:00
Aitor Moreno
f54497194a Merge pull request #6762 from penpot/elenatorro-10901-add-text-vertical-alignment
🔧 Add vertical alignment for text shapes
2025-06-23 17:05:47 +02:00
Elena Torro
134fb1ab4c 🔧 Add vertical alignment for text shapes 2025-06-23 16:45:25 +02:00
andrés gonzález
8c2dc1f22d 📚 Add DT import/export options to the docs (#6753) 2025-06-23 15:37:57 +02:00
Aitor Moreno
833546d754 🐛 Fix wrong aspect ratio on oriented image 2025-06-23 15:30:01 +02:00
Elena Torró
0010d61ae2 Merge pull request #6758 from penpot/elenatorro-text-rendering-fixes-and-tests
🔧 Add tests to cover text styles
2025-06-23 14:06:19 +02:00
Elena Torro
747f72be47 🔧 Add tests to cover text styles 2025-06-23 13:43:09 +02:00
Alejandro Alonso
1882efe3f7 🐛 Fix paths rendered initially ony in tile 0 0 2025-06-23 12:23:49 +02:00
Florian Schrödl
580bb46a05 Implement font-size token type (#6708)
*  Implement font-size token type

*  Hide typography tokens behind FF

* 💄 Update icon

* 🔧 Add font-size to unapply check

* ♻️ Generalize status-icon logic and remove icon for font-size
2025-06-23 12:12:40 +02:00
Alejandro Alonso
9ea0875e65 Merge pull request #6742 from penpot/ladybenko-fix-wasm-debug-text-hi-dpr
 Fix size of 'wasm renderer' debug text on dpr > 1
2025-06-23 11:47:20 +02:00
Alejandro Alonso
43b19ba33e Merge pull request #6738 from penpot/ladybenko-11247-enable-dpr-when-render-wasm
🔧 Enable render-wasm-dpr by default
2025-06-23 11:46:24 +02:00
Andrey Fedorov
130cd52f79 Notify user if imported file or directory doesn't contain token files 2025-06-23 11:44:00 +02:00
Aitor Moreno
21fd56076c Merge pull request #6756 from penpot/superalex-fix-empty-fills
🐛 Fix empty fills
2025-06-23 11:31:34 +02:00
Alejandro Alonso
c97314ddb5 🐛 Fix empty fills 2025-06-23 11:14:58 +02:00
Andrey Antukh
34bbce5089 Merge remote-tracking branch 'origin/staging' into develop 2025-06-23 10:06:05 +02:00
ºelhombretecla
9a0538e5e3 Add visual indicator for new comments in the workspace (#6728)
* 🎉 Add comment notification to workspace

* 💄 Add info to changelog

*  Add minor efficiency improvements

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-06-23 09:51:41 +02:00
ºelhombretecla
1b041d949c 💄 Add new content for 2.8 release slides (#6729)
* 💄 Add new content for 2.8 release slides

* 📎 Fix linter issues

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-06-23 09:43:55 +02:00
Belén Albeza
56d96aaf07 🔧 Enable render-wasm-dpr by default 2025-06-20 12:48:49 +02:00
Eva Marco
1a8f36562b 🐛 Fix export button width on design tab (#6744) 2025-06-20 12:04:49 +02:00
luisδμ
70f3988046 Show warning when selecting a copy of conflicted variant (#6743) 2025-06-20 11:52:07 +02:00
Andrey Antukh
ec021d944d Merge remote-tracking branch 'origin/staging' into develop 2025-06-20 11:37:50 +02:00
Alejandro Alonso
3d0c3013e5 Merge pull request #6746 from penpot/niwinz-staging-path-editor-fixes
 Add missing fixes to the path edition mode
2025-06-20 11:31:17 +02:00
Elena Torró
f1a6b46165 Merge pull request #6745 from penpot/superalex-bug-fix-text-image-strokes
🐛 Fix image fill strokes are not rendered correctly for texts
2025-06-20 11:14:24 +02:00
Andrey Antukh
3274a74611 Add missing fixes to the path edition mode 2025-06-20 11:06:48 +02:00
Belén Albeza
2412402a23 Fix size of 'wasm renderer' debug text on dpr > 1 2025-06-20 10:55:33 +02:00
Alejandro Alonso
5375029497 🐛 Fix image fill strokes are not rendered correctly for texts 2025-06-20 10:52:26 +02:00
Alejandro Alonso
3a0ac577f8 Merge pull request #6720 from penpot/niwinz-staging-path-editor-fixes
 Fix path edition/drawing state on workspace
2025-06-20 10:34:19 +02:00
Andrés Moya
cf78e68787 🐛 Fix token unapply for text shapes 2025-06-20 10:15:49 +02:00
Aitor Moreno
8bfc314b17 Merge pull request #6700 from penpot/superalex-fix-async-content-rendering
🐛 Fix asynchronous content rendering
2025-06-20 10:11:28 +02:00
Elena Torró
38112e287a Merge pull request #6734 from penpot/ladybenko-11392-no-ui-in-tests
🔧 Make visual regression tests to hide the UI when taking a screenshot (render-wasm tests only)
2025-06-20 09:05:30 +02:00
Alejandro Alonso
43ba30c694 Merge pull request #6741 from penpot/niwinz-staging-join-nodes
🔥 Remove incorrect assert on path segment helper
2025-06-20 08:22:27 +02:00
Andrey Antukh
520ea0e094 🔥 Remove incorrect assert on path segment helper 2025-06-20 08:01:59 +02:00
Alejandro Alonso
5c7a4ce5b7 🐛 Fix fill images for text 2025-06-20 07:45:28 +02:00
Andrey Antukh
0c260c626b Make the path creation flow consistent 2025-06-20 07:41:06 +02:00
Andrey Antukh
db4721f692 Forward path edition state from vieweport to editor 2025-06-20 07:41:06 +02:00
Andrey Antukh
f8d63f5d9d 🐛 Fix incorrect path drawing/edition state management 2025-06-20 07:41:06 +02:00
Andrey Antukh
7e909dfbe8 Merge remote-tracking branch 'origin/staging' into develop 2025-06-19 15:35:19 +02:00
Alonso Torres
7f7f0893d0 🐛 Fix sidebar width in localhost (#6732) 2025-06-19 15:31:27 +02:00
Andrey Antukh
0f46efc117 Merge pull request #6733 from penpot/eva-bugfixing
🐛 Fix some bugs
2025-06-19 15:29:34 +02:00
María Valderrama
22fbc3fa5f 💄 Improve dashboard's sidebar (#6736) 2025-06-19 15:28:32 +02:00
Alejandro Alonso
d71fa659d5 🐛 Fix asynchronous content rendering 2025-06-19 14:03:40 +02:00
Alejandro Alonso
d0425cabda Merge pull request #6721 from penpot/ladybenko-11276-fix-modifiers-dpr
🐛 Fix panning and scroll when dpr > 1 (render wasm)
2025-06-19 14:01:47 +02:00
Eva Marco
909838c8c4 🐛 Fix snap nodes shortcut 2025-06-19 14:01:15 +02:00
Eva Marco
86e36061fb 🐛 Fix multiple values on text 2025-06-19 13:41:35 +02:00
Belén Albeza
9852d24b83 🔧 Make visual regression tests to hide the UI when taking a screenshot (render-wasm tests only) 2025-06-19 13:37:52 +02:00
Alejandro Alonso
2239482049 Merge pull request #6717 from penpot/alotor-grid-editor
 Support grid editor with wasm modifiers
2025-06-19 13:32:27 +02:00
Xavier Julian
4ea4a1e130 ♻️ Restructure UI files for token settings 2025-06-19 13:10:09 +02:00
alonso.torres
11467e26a2 🐛 Fix problem with flex wrap in wasm 2025-06-19 13:03:25 +02:00
alonso.torres
b997d5a320 🐛 Fix problem with grid layout wasm 2025-06-19 13:03:25 +02:00
alonso.torres
5b4cd9f4f1 🐛 Fix problem when moving masks, bools, groups with wasm 2025-06-19 13:03:25 +02:00
alonso.torres
58e5748b4f 🐛 Fix wasm layout problems 2025-06-19 13:03:25 +02:00
alonso.torres
b2647f30c2 Support grid editor with wasm modifiers 2025-06-19 13:03:25 +02:00
Eva Marco
fbdabcd913 🐛 Fix button width 2025-06-19 12:51:53 +02:00
luisδμ
72f2a409f9 Show warning when duplicated variant prop name and value (#6639)
*  Show warning when duplicated variant prop name and value

* 📎 PR changes
2025-06-19 12:34:28 +02:00
Alejandro Alonso
191ea3d02a Merge pull request #6637 from penpot/mavalroot-missing-events
 Add missing user experience events
2025-06-19 12:11:07 +02:00
Xavier Julian
62a6f2c2f1 ♻️ Restructure UI files for theme creation modal 2025-06-19 11:59:25 +02:00
Andrey Antukh
b747ccc382 🎉 Add shortcut helper for creating events 2025-06-19 11:38:08 +02:00
Andrey Antukh
892c9ab12c Add minor code consistency fixes 2025-06-19 11:38:08 +02:00
Eva Marco
b595d5abf8 🐛 Fix radio buttons gap 2025-06-19 11:23:32 +02:00
María Valderrama
cb46d643ac Add missing user experience events 2025-06-19 11:22:58 +02:00
Xavier Julian
105e0ba75f ♻️ Create themes folder and themes root file 2025-06-19 10:53:31 +02:00
Belén Albeza
4a9f6ea04e 🐛 Fix panning and scroll when dpr > 1 (render wasm) 2025-06-19 10:42:19 +02:00
Andrey Antukh
03a82c18cb Merge pull request #6726 from penpot/eva-fix-comment-icon
🐛 Fix comment icon fill
2025-06-19 09:43:02 +02:00
Andrey Antukh
a559547f97 Merge pull request #6727 from penpot/bameda-fix-typo
🐛 Fix a typo
2025-06-19 09:42:35 +02:00
David Barragán Merino
b91d703060 🐛 Fix a typo 2025-06-19 09:34:53 +02:00
luisδμ
e7e39a5521 Avoid duplicated property names adding a number (#6681)
*  Avoid repeated property names appending a number

* 📎 PR changes

* 🐛 Adjust rules for incrementing numbers in prop names
2025-06-19 09:11:41 +02:00
Belén Albeza
70a29c43ec 🔧 Fix race condition in colorpicket gradient tests (#6722) 2025-06-19 08:57:38 +02:00
Eva Marco
7bde3d0ec1 🐛 Fix comment icon fill 2025-06-19 08:51:29 +02:00
Andrey Antukh
386c729507 Merge remote-tracking branch 'origin/staging' into develop 2025-06-19 08:49:11 +02:00
Alejandro Alonso
219dca3ab8 Merge pull request #6723 from penpot/elenatorro-11385-fix-text-gradients
🐛 Fix text fill gradients and add visual regression test for text…
2025-06-19 07:03:19 +02:00
Elena Torro
5c120b601c 🐛 Fix text fill gradients and add visual regression test for text styles 2025-06-18 18:02:28 +02:00
Andrey Antukh
54643b79f6 🐛 Fix exception on changing hex color on colorpicker gradient editor (#6718) 2025-06-18 17:59:14 +02:00
Elena Torro
cf8006ce9c 🔧 Add option to skip tutorial/walkthrough when creating profiles for dev purposes 2025-06-18 17:00:46 +02:00
Kelp
71afccbeb5 Adds new font-size icon to the DS
Signed-off-by: Kelp <5446186+NatachaMenjibar@users.noreply.github.com>
2025-06-18 15:55:13 +02:00
Andrey Antukh
f0b82864dd Merge pull request #6719 from penpot/andy-changelog-update
📚 Update changelog
2025-06-18 15:04:28 +02:00
Andres Gonzalez
29244776f0 📚 Update changelog 2025-06-18 14:11:35 +02:00
Andrey Antukh
bbb9713f97 Merge remote-tracking branch 'origin/staging' into develop 2025-06-18 14:09:49 +02:00
Andrey Antukh
40c33c7dcc Merge pull request #6715 from penpot/alotor-bug-fix-workaround
🐛 Fix problem fetching images
2025-06-18 14:09:29 +02:00
Andrey Antukh
063c6e7771 Merge remote-tracking branch 'origin/staging' into develop 2025-06-18 13:34:47 +02:00
alonso.torres
1a6fcb5daf 🐛 Fix problem fetching images 2025-06-18 13:26:15 +02:00
Andrey Antukh
34febfc833 📎 Update indirect dependencies 2025-06-18 13:20:45 +02:00
Andrey Antukh
2c0abea254 📎 Add missing deps for build wasm tests 2025-06-18 12:45:25 +02:00
Andrey Antukh
4524782282 📎 Adapt backend test to devenv changes 2025-06-18 12:35:58 +02:00
Andrey Antukh
b8b56d5aa4 Merge remote-tracking branch 'origin/staging' into develop 2025-06-18 10:54:17 +02:00
Alejandro Alonso
2a5b087aa4 Merge pull request #6714 from penpot/niwinz-staging-fix-path-edition
🐛 Fix unexpected exception on selectiong node on non path shape
2025-06-18 10:45:36 +02:00
Andrey Antukh
402508a710 Merge remote-tracking branch 'origin/staging' into develop 2025-06-18 10:41:30 +02:00
Alejandro Alonso
1f034654a0 Merge pull request #6705 from penpot/niwinz-staging-docker-improvements
 Major improvement and update to docker images and devenv
2025-06-18 10:39:51 +02:00
Andrey Antukh
88ed08916e Merge remote-tracking branch 'origin/staging' into develop 2025-06-18 10:39:23 +02:00
Andrey Antukh
a9a0970001 Merge pull request #6679 from penpot/niwinz-develop-enhacements-4
 Add editors count to get-owned-teams rpc method response
2025-06-18 10:38:48 +02:00
Andrey Antukh
1576016999 Merge pull request #6711 from penpot/elenatorro-11212-fix-right-sidebar-overflow
🐛 Fix right-sidebar width overflow
2025-06-18 10:38:18 +02:00
Andrey Antukh
5ea515cc9f Merge pull request #6713 from mdbenito/doc/undo-ns
📚 Document app.main.data.workspace.undo
2025-06-18 10:37:42 +02:00
Andrey Antukh
e3cce104e1 🐛 Fix unexpected exception on selectiong node on non path shape 2025-06-18 10:12:24 +02:00
Alejandro Alonso
a24631ac66 Merge pull request #6710 from penpot/niwinz-staging-path-make-curve-point
🐛 Fix incorrect behavior of `make-curve-point` fn
2025-06-18 10:06:28 +02:00
Miguel de Benito Delgado
c0df527b3d 📚 Document app.main.data.workspace.undo 2025-06-18 09:52:15 +02:00
Andrey Antukh
fd81ea6a84 🔥 Remove commented code 2025-06-18 09:46:15 +02:00
Andrey Antukh
a3c7151157 ⬆️ Update indirect exporter module dependencies 2025-06-18 09:46:15 +02:00
Andrey Antukh
2d4fc3e05f ♻️ Refactor devenv build mechanism
This introduces multistage build process for devenv making
different dependencies build depend on its own (per example, when
jvm version is changed, only the jvm stage is rebuild)

This commit also introduces imagemagick 7.x custom build
in the same way as we have on public docker images, so on
devenv we use the same version.
2025-06-18 09:46:15 +02:00
Andrey Antukh
b01dea20d6 Add imagemagick custom build to backend docker image
This allows us be in control of the imagemagick version and not
depend on the version available on the distro repository, which
right now only ships the legacy 6.x version
2025-06-18 09:46:15 +02:00
Andrey Antukh
3f40a830fd Revisit exporter docker image dependencies
And remove cache for not include unnecessary files on
the docker image.
2025-06-18 09:46:15 +02:00
Andrey Antukh
50e9816526 Use custom jre for backend image
Mainly for reduce the size of the image and not include
not necessary modules. It also starts using multistage
dockerfiles for fetch and setup jdk and node and then
only copy the required files.
2025-06-18 09:46:15 +02:00
Andrey Antukh
4c0165da62 ⬆️ Update node dependency on devenv 2025-06-18 09:46:15 +02:00
Andrey Antukh
42d36bae0a Update several node based dependencies 2025-06-18 09:46:15 +02:00
Andrey Antukh
6dd0f4f164 🔥 Remove unused jvm options from backend start-dev script 2025-06-18 09:46:15 +02:00
Elena Torro
f7c4bd77be 🐛 Fix right-sidebar width overflow 2025-06-18 09:10:01 +02:00
Alejandro Alonso
5d72954611 Merge pull request #6707 from penpot/niwinz-staging-fix-path-join-nodes
🐛 Fix incorrect type handling on path join nodes operation
2025-06-18 07:37:58 +02:00
Alejandro Alonso
9930f54558 Merge pull request #6706 from penpot/niwinz-staging-opacity-fix
🐛 Fix incorrect handling of opacity change on gradient stop
2025-06-18 07:32:34 +02:00
Alejandro Alonso
667c9ddbb9 Merge pull request #6698 from penpot/niwinz-staging-fix-exporter
🐛 Fix incorrect params handling on exporter
2025-06-18 07:29:36 +02:00
Alejandro Alonso
4f09005586 Merge pull request #6694 from penpot/niwinz-staging-fix-profile-lang
🐛 Fix bad initial state on profile settings
2025-06-18 07:26:42 +02:00
Alejandro Alonso
cbc98a761f Merge pull request #6693 from penpot/niwinz-staging-register-regression
🐛 Fix regression on singup flow
2025-06-18 07:23:37 +02:00
Andrey Antukh
c3b306201d 🐛 Fix incorrect behavior of make-curve-point fn 2025-06-17 23:18:34 +02:00
Andrey Antukh
a772b442c8 🐛 Fix incorrect type handling on path join nodes operation 2025-06-17 17:34:37 +02:00
Alejandro Alonso
6a46110f80 Merge pull request #6672 from penpot/superalex-fix-focus-mode-wasm
🐛 Fix focus mode for wasm render
2025-06-17 16:55:47 +02:00
Alejandro Alonso
1c7aea4b84 🐛 Fix focus mode for wasm render 2025-06-17 16:42:45 +02:00
Andrey Antukh
90116c207f Merge remote-tracking branch 'origin/staging' into develop 2025-06-17 16:23:35 +02:00
Pablo Alba
46fe3a6239 📚 Add comments on convoluted variants code (#6704) 2025-06-17 16:17:56 +02:00
Andrey Antukh
332bbc71c3 🐛 Fix incorrect handling of opacity change on gradient stop 2025-06-17 16:14:10 +02:00
Andrey Antukh
c5b0206bf0 🐛 Add workaround for webkit issue on fileReader (#6697)
On reading blob as data uri
2025-06-17 15:25:58 +02:00
Elena Torró
01311225c7 Merge pull request #6695 from penpot/superalex-fix-allocate-0-bytes-path-attrs
🐛 Fix wasm render path issues
2025-06-17 14:44:15 +02:00
Elena Torró
717f3e1b32 Merge pull request #6703 from penpot/elenatorro-update-render-wasm-docs
📚 Add schemas and links to render-wasm README
2025-06-17 14:24:47 +02:00
Elena Torro
9a44bd0967 📚 Add schemas and links to render-wasm README 2025-06-17 14:06:46 +02:00
Belén Albeza
b92e108205 Are more visual regression tests for wasm (#6702) 2025-06-17 12:39:38 +02:00
Alejandro Alonso
8c6a80829f Merge pull request #6671 from penpot/azazeln28-refactor-minor-perf-issues
♻️ Refactor some minor perf issues
2025-06-17 11:30:43 +02:00
Elena Torró
039a544990 Merge pull request #6675 from penpot/alotor-grid-helpers
 Add grid helpers to wasm
2025-06-17 11:14:18 +02:00
Alejandro Alonso
60dbf02896 Merge pull request #6701 from penpot/elenatorro-fix-custom-font-load
🐛 Fix storing custom fonts
2025-06-17 10:06:02 +02:00
Elena Torro
d248dd09bc 🐛 Fix storing custom fonts 2025-06-17 09:38:17 +02:00
Alejandro Alonso
81d2b9a82e 🐛 Fix group fills propagation when fill is none 2025-06-17 09:17:54 +02:00
Alejandro Alonso
1bb6f2754c 🐛 Fix allocate 0 bytes for path attrs 2025-06-17 08:43:00 +02:00
Andrey Antukh
22ca1ab5f9 🐛 Fix incorrect params handling on exporter
that caused unexpected exception on multiple exports
2025-06-16 16:58:03 +02:00
Andrey Antukh
df84396fea Merge remote-tracking branch 'origin/staging' into develop 2025-06-16 16:51:14 +02:00
Andrey Antukh
acf0d02c5d Merge pull request #6696 from penpot/eva-add-export-metrics
🎉 Add token export event tag
2025-06-16 16:50:49 +02:00
Eva Marco
5ccf34fdae 🐛 Fix reposition on tooltip 2025-06-16 16:34:53 +02:00
Eva Marco
41c8bba1df ♻️ Remove anonym fn 2025-06-16 16:23:53 +02:00
Eva Marco
325a78a967 🎉 Add token export event tag 2025-06-16 16:20:14 +02:00
mirakernel
409ff31c30 🐛 Avoid crash on empty string in interaction prototype (#6687)
Fixes #6469

Using `uuid/parse` caused a crash when "Relative to" field was set to "auto",
producing an empty string. This change uses `uuid/parse*` instead, which safely
returns nil for invalid or empty inputs, preventing the crash.

Signed-off-by: Dmitriy Mikheev <mirakernel.disroot.org>
Co-authored-by: kira <kira@kira.kira>
2025-06-16 15:19:45 +02:00
mirakernel
a56822ad99 🐛 Avoid crash on empty string in interaction prototype (#6687)
Fixes #6469

Using `uuid/parse` caused a crash when "Relative to" field was set to "auto",
producing an empty string. This change uses `uuid/parse*` instead, which safely
returns nil for invalid or empty inputs, preventing the crash.

Signed-off-by: Dmitriy Mikheev <mirakernel.disroot.org>
Co-authored-by: kira <kira@kira.kira>
2025-06-16 15:18:59 +02:00
Andrey Antukh
8cb42a63e5 🐛 Fix bad initial state on profile settings 2025-06-16 15:05:29 +02:00
Andrey Antukh
2af1feafb6 Merge pull request #6667 from penpot/niwinz-develop-enhacements-2
 Add internal changes to tooltip* ds component
2025-06-16 15:03:42 +02:00
Eva Marco
691a67b595 🐛 Fix tooltip height on safari 2025-06-16 14:45:12 +02:00
Andrey Antukh
f7e94accc3 Add internal performance oriented changes to tooltip* 2025-06-16 14:45:12 +02:00
Andrey Antukh
fc655224af Add memorization to icon-button* ds component 2025-06-16 14:45:12 +02:00
Andrey Antukh
8b0ead6832 🐛 Fix regression on singup flow
Move all params handling to prepare-register, just for consistency
2025-06-16 14:31:30 +02:00
Alejandro Alonso
10ae4dd3f7 Merge pull request #6689 from penpot/niwinz-staging-add-sr-lang
 Add several fixes & enhancements
2025-06-16 13:43:24 +02:00
Andrey Antukh
34d6e86e42 🐛 Fix incorrect theem selection on export progress bar color 2025-06-16 13:42:43 +02:00
Andrey Antukh
481d1ec53a Normalize layout schemas 2025-06-16 13:42:43 +02:00
Andrey Antukh
c6f4ee1974 ⬆️ Update cuerdas dependency
Fixes a corner case with camel->kebab casing
2025-06-16 13:42:41 +02:00
Andrey Antukh
f20032199a 🎉 Add Serbian lang 2025-06-16 13:42:22 +02:00
Elena Torró
4869373a43 🔧 Add methods to render text as path (#6624)
* 🔧 Refactor text strokes drawing

* 🔧 Add text to path methods for future usage

* 📚 Add text as paths internal documentation
2025-06-16 13:37:29 +02:00
María Valderrama
d0aac65c76 🐛 Fix misalignments at create account (#6692) 2025-06-16 13:26:33 +02:00
Pablo Alba
2d36a1f3e0 🐛 Fix when retrieving a variant from several with same props, it get the last one 2025-06-16 12:23:40 +02:00
Alejandro Alonso
38941d4811 Merge pull request #6676 from penpot/elenatorro-fix-load-pending-single-attr
🐛 Fix parsing pending callback on setting single shape attr
2025-06-16 11:50:57 +02:00
María Valderrama
f21e546bc1 Add import error event (#6690) 2025-06-16 10:39:56 +02:00
alonso.torres
0be8a6e0e6 Add grid helpers to wasm 2025-06-16 09:55:35 +02:00
Marina López
3624a14141 Subscription tests (#6669)
*  Subscription tests

*  Subscription tests
2025-06-16 09:31:50 +02:00
Peter Kahoun
141431bb9e Update cs.po - inflection fixes (#6677)
Signed-off-by: Peter Kahoun <kahi.cz@gmail.com>
2025-06-16 09:29:47 +02:00
Alonso Torres
874a658369 Adds generateFontFaces method to the plugins api (#6682)
*  Adds generateFontFaces method to the plugins api

* ⬆️ Update plugin runtime
2025-06-16 09:28:04 +02:00
Alonso Torres
c254ebd7c3 🐛 Fix problem with import modal style (#6683) 2025-06-16 09:26:35 +02:00
Elena Torro
f58ee2c89f 🔧 Add visual regression tests for font load 2025-06-11 13:22:23 +02:00
Pablo Alba
925b6c02d6 🎉 Separate the content of the text of the rest of properties on variants 2025-06-11 11:22:43 +02:00
Pablo Alba
9761cba337 ♻️ Restore separate the content of the text of the rest of properties on components updates
This reverts commit b2aaa5f0df.
2025-06-11 11:21:54 +02:00
Florian Schrödl
267a3af1e5 💄 Fixes export dialog styling issues (#6673)
* 🐛 Fix text overflow in file list
* 💄 Set monospace font for code-block component
* 💄 Reduce padding
2025-06-11 10:43:02 +02:00
Andrey Antukh
71f5806e23 Add editors count to get-owned-teams rpc method response 2025-06-11 08:40:58 +02:00
Elena Torro
330bee7839 🐛 Fix parsing pending callback on setting single shape attr 2025-06-10 21:34:41 +02:00
Andrey Fedorov
d44e4e5275 Add zip file format import for tokens 2025-06-10 17:32:06 +02:00
Aitor Moreno
369e134bed ♻️ Refactor some minor perf issues 2025-06-10 16:00:10 +02:00
Andrey Antukh
b8ee7cad26 Merge pull request #6663 from penpot/alotor-plugins-bugfixes
 Plugins fixes and enhancements
2025-06-10 15:30:32 +02:00
alonso.torres
26efc9f0c8 📚 Add documentation for new plugin permission 2025-06-10 15:09:22 +02:00
alonso.torres
1d593e1287 ⬆️ Updates plugins runtime 2025-06-10 15:09:22 +02:00
alonso.torres
b34c161fc3 Adds local store proxy in plugins 2025-06-10 15:09:22 +02:00
alonso.torres
ed0c84a069 Exposes board clipContent and showInViewMode properties in plugins 2025-06-10 15:06:31 +02:00
alonso.torres
29466b47fe Adds skipChildren to export parameters 2025-06-10 15:06:30 +02:00
Elena Torró
f02dd9f8dc Merge pull request #6651 from penpot/superalex-path-fixes
🐛 Path fixes
2025-06-10 12:11:25 +02:00
David Barragán Merino
b385f055e0 🐳 Migrate from Redis to Valkey (#6666) 2025-06-10 10:08:10 +02:00
Alejandro Alonso
e91550cd9d Merge pull request #6646 from penpot/ladybenko-10904-playwright-wasm
🔧 Set up visual regression tests for wasm renderer
2025-06-10 09:24:59 +02:00
Alejandro Alonso
5faa619bc4 Merge pull request #6664 from penpot/niwinz-hotfix-1
🐛 Add better fix for path transformation
2025-06-10 08:51:51 +02:00
Alejandro Alonso
ed76b1b1ee 🎉 Support for webp images (#6665) 2025-06-10 08:40:30 +02:00
Belén Albeza
afdbb5cf2f 📚 Add documentation specific for wasm visual regression tests 2025-06-09 17:46:18 +02:00
Belén Albeza
971b92a75b 🔧 Make mockAsset to accept an array of asset ids too 2025-06-09 17:46:18 +02:00
Belén Albeza
479406b884 🔧 Add initial snapshots 2025-06-09 17:46:18 +02:00
Belén Albeza
1a10b7ebfd 🔧 Wait for first render using a custom event (visual regression tests) 2025-06-09 17:46:18 +02:00
Belén Albeza
59a4b51d2c 🔧 Set up playwright project for render wasm 2025-06-09 17:01:29 +02:00
Andrey Antukh
74430aad21 📎 Add missing emsd loading to devenv bashrc 2025-06-09 15:57:31 +02:00
Alejandro Alonso
78d6166bac 🐛 Fix caps for rounded paths 2025-06-09 14:40:54 +02:00
Alejandro Alonso
8db910baee 🐛 Fix rendering vertical and horizontal paths 2025-06-09 13:04:18 +02:00
Alejandro Alonso
a9702f104d 🐛 Fix shapes without fills contained in a group with fills 2025-06-09 13:04:18 +02:00
Andrey Antukh
01dda6dd6b 🐛 Add better fix for path transformation 2025-06-09 12:46:53 +02:00
481 changed files with 38800 additions and 17328 deletions

129
.github/workflows/build-bundles.yml vendored Normal file
View File

@@ -0,0 +1,129 @@
name: Build and Upload Penpot Bundles non-prod
on:
# Create bundler for every tag
push:
tags:
- '**' # Pattern matched against refs/tags
# Create bundler every hour between 5:00 and 20:00 on working days
schedule:
- cron: '0 5-20 * * 1-5'
# Create bundler from manual action
workflow_dispatch:
inputs:
zip_mode:
# zip_mode defines how the build artifacts are packaged:
# - 'individual': creates one ZIP file per component (frontend, backend, exporter)
# - 'all': creates a single ZIP containing all components
# - null: for the rest of cases (non-manual events)
description: 'Bundle packaging mode'
required: false
default: 'individual'
type: choice
options:
- individual
- all
jobs:
build-bundles:
name: Build and Upload Penpot Bundles
runs-on: ubuntu-24.04
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract somer useful variables
id: vars
run: |
echo "commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "gh_branch=${{ github.base_ref || github.ref_name }}" >> $GITHUB_OUTPUT
# Set up Docker Buildx for multi-arch build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Run manage.sh build-bundle from host
run: ./manage.sh build-bundle
- name: Prepare directories for zipping
run: |
mkdir zips
mv bundles penpot
- name: Create zip bundles for zip_mode == 'all'
if: ${{ github.event.inputs.zip_mode == 'all' }}
run: |
echo "📦 Packaging Penpot 'all' bundles..."
zip -r zips/penpot-all-bundles.zip penpot
- name: Create zip bundles for zip_mode != 'all'
if: ${{ github.event.inputs.zip_mode != 'all' }}
run: |
echo "📦 Packaging Penpot 'individual' bundles..."
zip -r zips/penpot-frontend.zip penpot/frontend
zip -r zips/penpot-backend.zip penpot/backend
zip -r zips/penpot-exporter.zip penpot/exporter
- name: Upload unified 'all' bundle
if: ${{ github.event.inputs.zip_mode == 'all' }}
uses: actions/upload-artifact@v4
with:
name: penpot-all-bundles
path: zips/penpot-all-bundles.zip
- name: Upload individual bundles
if: ${{ github.event.inputs.zip_mode != 'all' }}
uses: actions/upload-artifact@v4
with:
name: penpot-individual-bundles
path: |
zips/penpot-frontend.zip
zips/penpot-backend.zip
zips/penpot-exporter.zip
- name: Upload unified 'all' bundle to S3
if: ${{ github.event.inputs.zip_mode == 'all' }}
run: |
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.gh_branch}}.zip
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.commit_hash }}.zip
- name: Upload 'individual' bundles to S3
if: ${{ github.event.inputs.zip_mode != 'all' }}
run: |
for name in penpot-frontend penpot-backend penpot-exporter; do
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.gh_branch }}-latest.zip
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.commit_hash }}.zip
done
- name: Notify Mattermost about automatic bundles
if: github.event_name == 'pull_request'
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
📦 *Penpot bundle automatically generated*
📄 PR: ${{ github.event.pull_request.title }}
🔁 From: \`${{ github.head_ref }}\` to \`{{ github.base_ref }}\`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Notify Mattermost about manual bundles
if: github.event_name == 'workflow_dispatch'
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
📦 *Penpot bundle manually generated*
📄 Triggered from branch: `${{ github.ref_name}}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Print artifact summary URL
run: |
echo "📦 Artifacts available at:"
echo "🔗 https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"

View File

@@ -1,6 +1,32 @@
# CHANGELOG
## 2.8.0 (Next / Unreleased)
## 2.9.0 (Unreleased)
### :rocket: Epics and highlights
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
### :sparkles: New features & Enhancements
- Add visual indicator for new comments in the workspace [Taiga #11328](https://tree.taiga.io/project/penpot/issue/11328)
- On components overrides, separate the content of the text from the rest of properties [Taiga #7434](https://tree.taiga.io/project/penpot/us/7434)
- Improve dashboard's sidebar [Taiga #10700](https://tree.taiga.io/project/penpot/us/10700)
- Change "Save color" button to primary button [Taiga #9410](https://tree.taiga.io/project/penpot/issue/9410)
- Support for exif rotated images [GitHub #6767](https://github.com/penpot/penpot/issues/6767)
- Display Blend Mode and Layer Opacity properties in the Inspect tab [Taiga #11283](https://tree.taiga.io/project/penpot/issue/11283)
- Add the option to import tokens in a .zip file. [Taiga #11378](https://tree.taiga.io/project/penpot/us/11378)
- New typography token type - font size token [Taiga #10938](https://tree.taiga.io/project/penpot/us/10938)
### :bug: Bugs fixed
- Copying font size does not copy the unit [Taiga #11143](https://tree.taiga.io/project/penpot/issue/11143)
- Fix text-decoration line-through that displays a wrong property value [Taiga #11145](https://tree.taiga.io/project/penpot/issue/11145)
- Fix display error message on register form [Taiga #11444](https://tree.taiga.io/project/penpot/issue/11444)
- Fix toggle focus mode did not restore viewport and selection upon exit [GitHub #6280](https://github.com/penpot/penpot/issues/6820)
- Fix problem when creating a layout from an existing layout [Taiga #11554](https://tree.taiga.io/project/penpot/issue/11554)
- Fix title button from Title Case to Capitalize [Taiga #11476](https://tree.taiga.io/project/penpot/issue/11476)
## 2.8.0
### :rocket: Epics and highlights
@@ -12,7 +38,15 @@ The initial prototype is completly reworked for provide a more consistent API
and to have proper validation and params decoding. All the details can be found
on [its own changelog](library/CHANGES.md)
**Penpot migrate from Redis to Valkey**
As [Valkey](https://valkey.io/) is an opne-souce fork of [Redis](https://redis.io/)
version 7.2.4, this version of Penpot will be compatible with Redis but may diverge
in future versions. Therefore, **migration from Redis to ValKey is recommended for all
on-premises instances** that want to keep up to date.
### :heart: Community contributions (Thank you!)
- Add Serbian language [GitHub #5002](https://github.com/penpot/penpot/issues/5002) by [crnobog69](https://github.com/crnobog69)
### :sparkles: New features & Enhancements
@@ -31,6 +65,7 @@ on [its own changelog](library/CHANGES.md)
- Update google fonts (at 2025/05/19) [Taiga 10792](https://tree.taiga.io/project/penpot/us/10792)
- Add tooltip component to DS [Taiga 9220](https://tree.taiga.io/project/penpot/us/9220)
- Allow multi file token export [Taiga #10144](https://tree.taiga.io/project/penpot/us/10144)
- Fix problem when double click on hidden shapes [Taiga #11314](https://tree.taiga.io/project/penpot/issue/11314)
### :bug: Bugs fixed
@@ -40,7 +75,21 @@ on [its own changelog](library/CHANGES.md)
- Fix element positioning on the right side to adjust to grid [#11073](https://tree.taiga.io/project/penpot/issue/11073)
- Fix palette is over sidebar [#11160](https://tree.taiga.io/project/penpot/issue/11160)
- Fix font size input not displaying "mixed" when multiple texts are selected [Taiga #11177](https://tree.taiga.io/project/penpot/issue/11177)
- Misalignments at Create account [Taiga #11315](https://tree.taiga.io/project/penpot/issue/11315)
- Fix issue with importing files where flex/grid is used [Taiga #11334](https://tree.taiga.io/project/penpot/issue/11334)
- Fix wrong color in the export progress bar [Taiga #11299](https://tree.taiga.io/project/penpot/issue/11299)
- Fix right sidebar width overflow on long layer names [Taiga #11212](https://tree.taiga.io/project/penpot/issue/11212)
- Fix comment icon fill [Taiga #11388](https://tree.taiga.io/project/penpot/issue/11388)
- Fix gap on radio-buttons component [Taiga #11360](https://tree.taiga.io/project/penpot/issue/11360)
- Fix button width [Taiga #11394](https://tree.taiga.io/project/penpot/issue/11394)
- Fix mixed letter spacing and line height [Taiga #11178](https://tree.taiga.io/project/penpot/issue/11178)
- Fix snap nodes shortcut [Taiga #11054](https://tree.taiga.io/project/penpot/issue/11054)
- Fix changing a text property in a text layer does not unapply the previously applied token in the same property [Taiga #11337](https://tree.taiga.io/project/penpot/issue/11337)
- Fix shortcut error pressing G+W from the View Mode [Taiga #11061](https://tree.taiga.io/project/penpot/issue/11061)
- Fix entering long project name [Taiga #11417](https://tree.taiga.io/project/penpot/issue/11417)
- Fix slow color picker [Taiga #11019](https://tree.taiga.io/project/penpot/issue/11019)
- Fix tooltip position after click [Taiga #11405](https://tree.taiga.io/project/penpot/issue/11405)
- Fix incorrect media translation on paste text with fill images [Github #6845](https://github.com/penpot/penpot/pull/6845)
## 2.7.2

View File

@@ -37,7 +37,6 @@
{:mvn/version "1.3.1002"}
metosin/reitit-core {:mvn/version "0.9.1"}
nrepl/nrepl {:mvn/version "1.3.1"}
cider/cider-nrepl {:mvn/version "0.56.0"}
org.postgresql/postgresql {:mvn/version "42.7.6"}
org.xerial/sqlite-jdbc {:mvn/version "3.49.1.0"}

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"

View File

@@ -1,4 +1,7 @@
[{:id "wireframing-kit"
[{:id "tokens-starter-kit"
:name "Design tokens starter kit"
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Tokens%20starter%20kit.penpot"},
{:id "wireframing-kit"
:name "Wireframe library"
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Wireframing%20kit%20v1.1.penpot"}
{:id "prototype-examples"

View File

@@ -71,19 +71,27 @@ def run_cmd(params):
print("EXC:", str(cause))
sys.exit(-2)
def create_profile(fullname, email, password):
def create_profile(fullname, email, password, skip_tutorial=False, skip_walkthrough=False):
props = {}
if skip_tutorial:
props["viewed-tutorial?"] = True
if skip_walkthrough:
props["viewed-walkthrough?"] = True
params = {
"cmd": "create-profile",
"params": {
"fullname": fullname,
"email": email,
"password": password
"password": password,
**props
}
}
res = run_cmd(params)
print(f"Created: {res['email']} / {res['id']}")
def update_profile(email, fullname, password, is_active):
params = {
"cmd": "update-profile",
@@ -170,6 +178,8 @@ parser.add_argument("-n", "--fullname", help="fullname", action="store")
parser.add_argument("-e", "--email", help="email", action="store")
parser.add_argument("-p", "--password", help="password", action="store")
parser.add_argument("-c", "--connect", help="connect to PREPL", action="store", default="tcp://localhost:6063")
parser.add_argument("--skip-tutorial", help="mark tutorial as viewed", action="store_true")
parser.add_argument("--skip-walkthrough", help="mark walkthrough as viewed", action="store_true")
args = parser.parse_args()

View File

@@ -77,8 +77,9 @@ export JAVA_OPTS="\
-Djdk.attach.allowAttachSelf \
-Dlog4j2.configurationFile=log4j2-devenv-repl.xml \
-Djdk.tracePinnedThreads=full \
-Dim4java.useV7=true \
-XX:+EnableDynamicAgentLoading \
-XX:-OmitStackTraceInFastThrow \
-XX:-OmitStackTraceInFastThrow \
-XX:+UnlockDiagnosticVMOptions \
-XX:+DebugNonSafepoints \
--sun-misc-unsafe-memory-access=allow \
@@ -106,9 +107,6 @@ export OPTIONS="-A:jmx-remote -A:dev"
# Setup GC
# export OPTIONS="$OPTIONS -J-XX:+UseZGC"
# Enable ImageMagick v7.x support
# export OPTIONS="-J-Dim4java.useV7=true $OPTIONS";
export OPTIONS_EVAL="nil"
# export OPTIONS_EVAL="(set! *warn-on-reflection* true)"

View File

@@ -18,9 +18,9 @@ if [ -f ./environ ]; then
source ./environ
fi
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --enable-native-access=ALL-UNNAMED --enable-preview $JVM_OPTS"
export JAVA_OPTS="-Dim4java.useV7=true -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --sun-misc-unsafe-memory-access=allow --enable-native-access=ALL-UNNAMED --enable-preview $JVM_OPTS $JAVA_OPTS"
ENTRYPOINT=${1:-app.main};
set -ex
exec $JAVA_CMD $JVM_OPTS -jar penpot.jar -m $ENTRYPOINT
exec $JAVA_CMD $JAVA_OPTS -jar penpot.jar -m $ENTRYPOINT

View File

@@ -36,9 +36,6 @@ export PENPOT_MEDIA_MAX_FILE_SIZE=104857600
# Setup default multipart upload size to 300MiB
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=314572800
# Enable ImageMagick v7.x support
# export OPTIONS="-J-Dim4java.useV7=true $OPTIONS";
# Initialize MINIO config
mc alias set penpot-s3/ http://minio:9000 minioadmin minioadmin -q
mc admin user add penpot-s3 penpot-devenv penpot-devenv -q
@@ -61,10 +58,8 @@ export JAVA_OPTS="\
-Djdk.attach.allowAttachSelf \
-Dlog4j2.configurationFile=log4j2-devenv.xml \
-Djdk.tracePinnedThreads=full \
-XX:+EnableDynamicAgentLoading \
-XX:-OmitStackTraceInFastThrow \
-XX:+UnlockDiagnosticVMOptions \
-XX:+DebugNonSafepoints \
-Dim4java.useV7=true \
-XX:-OmitStackTraceInFastThrow \
--sun-misc-unsafe-memory-access=allow \
--enable-preview \
--enable-native-access=ALL-UNNAMED";

View File

@@ -12,7 +12,6 @@
[app.binfile.common :as bfc]
[app.binfile.migrations :as bfm]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
[app.common.files.migrations :as-alias fmg]
@@ -54,7 +53,7 @@
[:map {:title "Manifest"}
[:version ::sm/int]
[:type :string]
[:referer {:optional true} :string]
[:generated-by {:optional true} :string]
[:files
@@ -373,6 +372,7 @@
params {:type "penpot/export-files"
:version 1
:generated-by (str "penpot/" (:full cf/version))
:refer "penpot"
:files (vec (vals files))
:relations rels}]
(write-entry! output "manifest.json" params))))
@@ -878,13 +878,8 @@
(defn- import-files
[{:keys [::bfc/timestamp ::bfc/input ::bfc/name] :or {timestamp (dt/now)} :as cfg}]
(dm/assert!
"expected zip file"
(instance? ZipFile input))
(dm/assert!
"expected valid instant"
(dt/instant? timestamp))
(assert (instance? ZipFile input) "expected zip file")
(assert (dt/instant? timestamp) "expected valid instant")
(let [manifest (-> (read-manifest input)
(validate-manifest))
@@ -896,6 +891,7 @@
:hint "unexpected type on manifest"
:manifest manifest))
;; Check if all files referenced on manifest are present
(doseq [{file-id :id features :features} (:files manifest)]
(let [path (str "files/" file-id ".json")]
@@ -956,14 +952,13 @@
[{:keys [::bfc/ids] :as cfg} output]
(dm/assert!
"expected a set of uuid's for `::bfc/ids` parameter"
(and (set? ids)
(every? uuid? ids)))
(assert
(and (set? ids) (every? uuid? ids))
"expected a set of uuid's for `::bfc/ids` parameter")
(dm/assert!
"expected instance of jio/IOFactory for `input`"
(satisfies? jio/IOFactory output))
(assert
(satisfies? jio/IOFactory output)
"expected instance of jio/IOFactory for `input`")
(let [id (uuid/next)
tp (dt/tpoint)
@@ -1002,14 +997,14 @@
(defn import-files!
[{:keys [::bfc/input] :as cfg}]
(dm/assert!
"expected valid profile-id and project-id on `cfg`"
(assert
(and (uuid? (::bfc/profile-id cfg))
(uuid? (::bfc/project-id cfg))))
(uuid? (::bfc/project-id cfg)))
"expected valid profile-id and project-id on `cfg`")
(dm/assert!
"expected instance of jio/IOFactory for `input`"
(io/coercible? input))
(assert
(io/coercible? input)
"expected instance of jio/IOFactory for `input`")
(let [id (uuid/next)
tp (dt/tpoint)
@@ -1029,3 +1024,9 @@
:id (str id)
:elapsed (dt/format-duration (tp))
:error? (some? @cs))))))
(defn get-manifest
[path]
(with-open [input (ZipFile. (fs/file path))]
(-> (read-manifest input)
(validate-manifest))))

View File

@@ -41,7 +41,7 @@
(if (or (instance? java.util.concurrent.CompletionException cause)
(instance? java.util.concurrent.ExecutionException cause))
(-> record
(assoc ::trace (ex/format-throwable cause :data? false :explain? false :header? false :summary? false))
(assoc ::trace (ex/format-throwable cause :data? true :explain? false :header? false :summary? false))
(assoc ::l/cause (ex-cause cause))
(record->report))
@@ -64,18 +64,18 @@
message))
@message)
:trace (or (::trace record)
(some-> cause (ex/format-throwable :data? false :explain? false :header? false :summary? false)))}
(some-> cause (ex/format-throwable :data? true :explain? false :header? false :summary? false)))}
(when-let [params (or (:request/params context) (:params context))]
{:params (pp/pprint-str params :length 30 :level 13)})
{:params (pp/pprint-str params :length 20 :level 20)})
(when-let [value (:value context)]
{:value (pp/pprint-str value :length 30 :level 12)})
{:value (pp/pprint-str value :length 30 :level 13)})
(when-let [data (some-> data (dissoc ::s/problems ::s/value ::s/spec ::sm/explain :hint))]
{:data (pp/pprint-str data :length 30 :level 12)})
{:data (pp/pprint-str data :length 30 :level 13)})
(when-let [explain (ex/explain data :length 30 :level 12)]
(when-let [explain (ex/explain data :length 30 :level 13)]
{:explain explain})))))
(defn error-record?

View File

@@ -40,7 +40,6 @@
[app.svgo :as-alias svgo]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[cider.nrepl :refer [cider-nrepl-handler]]
[clojure.test :as test]
[clojure.tools.namespace.repl :as repl]
[cuerdas.core :as str]
@@ -605,7 +604,7 @@
(let [p (promise)]
(when (contains? cf/flags :nrepl-server)
(l/inf :hint "start nrepl server" :port 6064)
(nrepl/start-server :bind "0.0.0.0" :port 6064 :handler cider-nrepl-handler))
(nrepl/start-server :bind "0.0.0.0" :port 6064))
(start)
(deref p))

View File

@@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.media :as cm]
[app.common.schema :as sm]
[app.common.schema.openapi :as-alias oapi]
@@ -21,6 +22,7 @@
[buddy.core.bytes :as bb]
[buddy.core.codecs :as bc]
[clojure.java.shell :as sh]
[clojure.string]
[clojure.xml :as xml]
[cuerdas.core :as str]
[datoteka.fs :as fs]
@@ -215,6 +217,23 @@
{:width (int width)
:height (int height)})))]))
(defn- get-dimensions-with-orientation [^String path]
;; Image magick doesn't give info about exif rotation so we use the identify command
;; If we are processing an animated gif we use the first frame with -scene 0
(let [dim-result (sh/sh "identify" "-format" "%w %h\n" path)
orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)]
(if (and (= 0 (:exit dim-result))
(= 0 (:exit orient-result)))
(let [[w h] (-> (:out dim-result)
str/trim
(clojure.string/split #"\s+")
(->> (mapv #(Integer/parseInt %))))
orientation (-> orient-result :out str/trim)]
(case orientation
("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees
{:width w :height h})) ; Normal or unknown orientation
nil)))
(defmethod process :info
[{:keys [input] :as params}]
(let [{:keys [path mtype] :as input} (check-input input)]
@@ -234,13 +253,17 @@
:code :media-type-mismatch
:hint (str "Seems like you are uploading a file whose content does not match the extension."
"Expected: " mtype ". Got: " mtype')))
;; For an animated GIF, getImageWidth/Height returns the delta size of one frame (if no frame given
;; it returns size of the last one), whereas getPageWidth/Height always return the full size of
;; any frame.
(assoc input
:width (.getPageWidth instance)
:height (.getPageHeight instance)
:ts (dt/now))))))
(let [{:keys [width height]}
(or (get-dimensions-with-orientation (str path))
(do
(l/warn "Failed to read image dimensions with orientation; falling back to im4java"
{:path path})
{:width (.getPageWidth instance)
:height (.getPageHeight instance)}))]
(assoc input
:width width
:height height
:ts (dt/now)))))))
(defmethod process-error org.im4java.core.InfoException
[error]

View File

@@ -231,13 +231,14 @@
:hint "email has complaint reports")))
(defn prepare-register
[{:keys [::db/pool] :as cfg} {:keys [email accept-newsletter-updates] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [fullname email accept-newsletter-updates] :as params}]
(validate-register-attempt! cfg params)
(let [email (profile/clean-email email)
profile (profile/get-profile-by-email pool email)
params {:email email
:fullname fullname
:password (:password params)
:invitation-token (:invitation-token params)
:backend "penpot"
@@ -254,8 +255,10 @@
(def schema:prepare-register-profile
[:map {:title "prepare-register-profile"}
[:fullname ::sm/text]
[:email ::sm/email]
[:password schema:password]
[:create-welcome-file {:optional true} :boolean]
[:invitation-token {:optional true} schema:token]])
(sv/defmethod ::prepare-register-profile
@@ -359,13 +362,9 @@
:extra-data ptoken})))
(defn register-profile
[{:keys [::db/conn ::wrk/executor] :as cfg} {:keys [token fullname theme] :as params}]
(let [theme (when (= theme "light") theme)
claims (tokens/verify (::setup/props cfg) {:token token :iss :prepared-register})
params (-> claims
(into params)
(assoc :fullname fullname)
(assoc :theme theme))
[{:keys [::db/conn ::wrk/executor] :as cfg} {:keys [token] :as params}]
(let [claims (tokens/verify (::setup/props cfg) {:token token :iss :prepared-register})
params (into claims params)
profile (if-let [profile-id (:profile-id claims)]
(profile/get-profile conn profile-id)
@@ -479,10 +478,7 @@
(def schema:register-profile
[:map {:title "register-profile"}
[:token schema:token]
[:fullname [::sm/word-string {:max 100}]]
[:theme {:optional true} [:string {:max 10}]]
[:create-welcome-file {:optional true} :boolean]])
[:token schema:token]])
(sv/defmethod ::register-profile
{::rpc/auth false

View File

@@ -134,11 +134,18 @@
::webhooks/event? true
::sse/stream? true
::sm/params schema:import-binfile}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id project-id version] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id project-id version file] :as params}]
(projects/check-edition-permissions! pool profile-id project-id)
(let [params (-> params
(assoc :profile-id profile-id)
(assoc :version (or version 1)))]
(let [version (or version 1)
params (-> params
(assoc :profile-id profile-id)
(assoc :version version))
manifest (case (int version)
1 nil
3 (bf.v3/get-manifest (:path file)))]
(with-meta
(sse/response (partial import-binfile cfg params))
{::audit/props {:file nil}})))
{::audit/props {:file nil
:generated-by (:generated-by manifest)
:referer (:referer manifest)}})))

View File

@@ -559,7 +559,10 @@
f.project_id,
f.created_at,
f.modified_at,
f.data_backend,
f.data_ref_id,
f.name,
f.version,
f.is_shared,
ft.media_id,
p.team_id
@@ -595,7 +598,11 @@
(teams/check-read-permissions! conn profile-id team-id)
(->> (db/exec! conn [sql:team-shared-files team-id])
(into #{} (comp
(map decode-row)
;; NOTE: this decode operation is a workaround for a
;; fast fix, this should be approached with a more
;; efficient implementation, for now it loads all
;; the files in memory.
(map (partial bfc/decode-file cfg))
(map (fn [row]
(if-let [media-id (:media-id row)]
(-> row

View File

@@ -139,7 +139,8 @@
'~:status', CASE COALESCE(p.props->'~:subscription'->>'~:type', 'professional')
WHEN 'professional' THEN 'active'
ELSE COALESCE(p.props->'~:subscription'->>'~:status', 'incomplete')
END
END,
'~:seats', p.props->'~:subscription'->'~:quantity'
) AS subscription
FROM team_profile_rel AS tp
JOIN team AS t ON (t.id = tp.team_id)
@@ -192,7 +193,8 @@
(def ^:private sql:get-owned-teams
"SELECT t.id, t.name,
(SELECT count(*) FROM team_profile_rel WHERE team_id=t.id) AS total_members
(SELECT count(*) FROM team_profile_rel WHERE team_id=t.id) AS total_members,
(SELECT count(*) FROM team_profile_rel WHERE team_id=t.id AND can_edit=true) AS total_editors
FROM team AS t
JOIN team_profile_rel AS tpr ON (tpr.team_id = t.id)
WHERE t.is_default IS false

View File

@@ -0,0 +1,88 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.srepl.fixes.lost-colors
"A collection of adhoc fixes scripts."
(:require
[app.binfile.common :as bfc]
[app.common.logging :as l]
[app.common.types.color :as types.color]
[app.db :as db]
[app.srepl.helpers :as h]))
(def sql:get-affected-files
"SELECT fm.file_id AS id FROM file_migration AS fm WHERE fm.name = '0008-fix-library-colors-v2'")
(def sql:get-matching-snapshot
"SELECT * FROM file_change
WHERE file_id = ?
AND created_at <= ?
AND label IS NOT NULL
AND data IS NOT NULL
ORDER BY created_at DESC
LIMIT 2")
(defn get-affected-migration
[conn file-id]
(db/get* conn :file-migration
{:name "0008-fix-library-colors-v2"
:file-id file-id}))
(defn get-last-valid-snapshot
[conn migration]
(let [[snapshot] (db/exec! conn [sql:get-matching-snapshot
(:file-id migration)
(:created-at migration)])]
(when snapshot
(let [snapshot (assoc snapshot :id (:file-id snapshot))]
(bfc/decode-file h/*system* snapshot)))))
(defn restore-color
[{:keys [data] :as snapshot} color]
(when-let [scolor (get-in data [:colors (:id color)])]
(-> (select-keys scolor types.color/library-color-attrs)
(types.color/check-library-color))))
(defn restore-missing-colors
[{:keys [id] :as file} & _opts]
(l/inf :hint "process file" :file-id (str id) :name (:name file) :has-colors (-> file :data :colors not-empty boolean))
(if-let [colors (-> file :data :colors not-empty)]
(let [migration (get-affected-migration h/*system* id)]
(if-let [snapshot (get-last-valid-snapshot h/*system* migration)]
(do
(l/inf :hint "using snapshot" :snapshot (:label snapshot))
(let [colors (reduce-kv (fn [colors color-id color]
(if-let [result (restore-color snapshot color)]
(do
(l/inf :hint "restored color" :file-id (str id) :color-id (str color-id))
(assoc colors color-id result))
(do
(l/wrn :hint "ignoring color" :file-id (str id) :color (pr-str color))
colors)))
colors
colors)
file (-> file
(update :data assoc :colors colors)
(update :migrations disj "0008-fix-library-colors-v2"))]
(db/delete! h/*system* :file-migration
{:name "0008-fix-library-colors-v2"
:file-id (:id file)})
file))
(do
(db/delete! h/*system* :file-migration
{:name "0008-fix-library-colors-v2"
:file-id (:id file)})
nil)))
(do
(db/delete! h/*system* :file-migration
{:name "0008-fix-library-colors-v2"
:file-id (:id file)})
nil)))

View File

@@ -17,6 +17,7 @@
[app.common.files.validate :as cfv]
[app.common.logging :as l]
[app.common.pprint :as p]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -395,6 +396,22 @@
libs (bfc/get-resolved-file-libraries system file)]
(cfv/validate-file file libs))))))
(defn validate-file-schema
"Validate structure, referencial integrity and semantic coherence of
all contents of a file. Returns a list of errors."
[file-id]
(let [file-id (h/parse-uuid file-id)]
(db/tx-run! (assoc main/system ::db/rollback true)
(fn [system]
(try
(let [file (bfc/get-file system file-id)]
(cfv/validate-file-schema! file)
(println "OK"))
(catch Exception cause
(if-let [explain (-> cause ex-data ::sm/explain)]
(println (sm/humanize-explain explain))
(ex/print-throwable cause))))))))
(defn repair-file!
"Repair the list of errors detected by validation."
[file-id & {:keys [rollback?] :or {rollback? true} :as opts}]
@@ -474,7 +491,8 @@
:index idx)
(let [system (assoc main/system ::db/rollback rollback?)]
(db/tx-run! system (fn [system]
(binding [h/*system* system]
(binding [h/*system* system
db/*conn* (db/get-connection system)]
(h/process-file! system file-id update-fn opts)))))
(catch Throwable cause

View File

@@ -48,7 +48,7 @@
(t/is (sto/object? mobj1))
(t/is (sto/object? mobj2))
(t/is (= 122785 (:size mobj1)))
(t/is (= 3299 (:size mobj2)))))))
(t/is (= 3297 (:size mobj2)))))))
(t/deftest media-object-upload
(let [prof (th/create-profile* 1)
@@ -85,7 +85,7 @@
(t/is (sto/object? mobj1))
(t/is (sto/object? mobj2))
(t/is (= 312043 (:size mobj1)))
(t/is (= 3901 (:size mobj2)))))))
(t/is (= 3890 (:size mobj2)))))))
(t/deftest media-object-upload-idempotency
@@ -163,7 +163,7 @@
(t/is (sto/object? mobj1))
(t/is (sto/object? mobj2))
(t/is (= 122785 (:size mobj1)))
(t/is (= 3299 (:size mobj2)))))))
(t/is (= 3297 (:size mobj2)))))))
(t/deftest media-object-upload-command
(let [prof (th/create-profile* 1)
@@ -200,7 +200,7 @@
(t/is (sto/object? mobj1))
(t/is (sto/object? mobj2))
(t/is (= 312043 (:size mobj1)))
(t/is (= 3901 (:size mobj2)))))))
(t/is (= 3890 (:size mobj2)))))))
(t/deftest media-object-upload-idempotency-command

View File

@@ -379,15 +379,14 @@
(t/deftest prepare-register-and-register-profile-1
(let [data {::th/type :prepare-register-profile
:email "user@example.com"
:fullname "foobar"
:password "foobar"}
out (th/command! data)
token (get-in out [:result :token])]
(t/is (string? token))
;; try register without token
(let [data {::th/type :register-profile
:fullname "foobar"
:accept-terms-and-privacy true}
(let [data {::th/type :register-profile}
out (th/command! data)]
;; (th/print-result! out)
(let [error (:error out)]
@@ -398,11 +397,8 @@
;; try correct register
(let [data {::th/type :register-profile
:token token
:fullname "foobar"
:utm_campaign "utma"
:mtm_campaign "mtma"
:accept-terms-and-privacy true
:accept-newsletter-subscription true}]
:mtm_campaign "mtma"}]
(let [{:keys [result error]} (th/command! data)]
(t/is (nil? error))))
@@ -424,6 +420,7 @@
;; PREPARE REGISTER
(let [data {::th/type :prepare-register-profile
:email "hello@example.com"
:fullname "foobar"
:password "foobar"}
out (th/command! data)
token (get-in out [:result :token])]
@@ -432,10 +429,7 @@
;; DO REGISTRATION
(let [data {::th/type :register-profile
:token @current-token
:fullname "foobar"
:accept-terms-and-privacy true
:accept-newsletter-subscription true}
:token @current-token}
out (th/command! data)]
(t/is (nil? (:error out)))
(t/is (= 1 (:call-count @mock))))
@@ -445,6 +439,7 @@
;; PREPARE REGISTER: second attempt
(let [data {::th/type :prepare-register-profile
:email "hello@example.com"
:fullname "foobar"
:password "foobar"}
out (th/command! data)
token (get-in out [:result :token])]
@@ -479,6 +474,7 @@
;; PREPARE REGISTER
(let [data {::th/type :prepare-register-profile
:email "hello@example.com"
:fullname "foobar"
:password "foobar"}
out (th/command! data)
token (get-in out [:result :token])]
@@ -487,10 +483,7 @@
;; DO REGISTRATION
(let [data {::th/type :register-profile
:token @current-token
:fullname "foobar"
:accept-terms-and-privacy true
:accept-newsletter-subscription true}
:token @current-token}
out (th/command! data)]
(t/is (nil? (:error out)))
(t/is (= 1 (:call-count @mock))))
@@ -504,6 +497,7 @@
;; PREPARE REGISTER: second attempt
(let [data {::th/type :prepare-register-profile
:email "hello@example.com"
:fullname "foobar"
:password "foobar"}
out (th/command! data)
token (get-in out [:result :token])]
@@ -514,10 +508,7 @@
:return true}]
;; DO REGISTRATION: second attempt
(let [data {::th/type :register-profile
:token @current-token
:fullname "foobar"
:accept-terms-and-privacy true
:accept-newsletter-subscription true}
:token @current-token}
out (th/command! data)]
(t/is (nil? (:error out)))
(t/is (= 0 (:call-count @mock))))))))
@@ -532,6 +523,7 @@
:member-email "user@example.com"})
data {::th/type :prepare-register-profile
:invitation-token itoken
:fullname "foobar"
:email "user@example.com"
:password "foobar"}
@@ -542,8 +534,7 @@
(let [rtoken (:token result)
data {::th/type :register-profile
:token rtoken
:fullname "foobar"}
:token rtoken}
{:keys [result error] :as out} (th/command! data)]
;; (th/print-result! out)
@@ -563,6 +554,7 @@
data {::th/type :prepare-register-profile
:invitation-token itoken
:email "user@example.com"
:fullname "foobar"
:password "foobar"}
out (th/command! data)]
@@ -582,6 +574,7 @@
:member-email "user@example.com"})
data {::th/type :prepare-register-profile
:invitation-token itoken
:fullname "foobar"
:email "user@example.com"
:password "foobar"}
out (th/command! data)]
@@ -604,6 +597,7 @@
data {::th/type :prepare-register-profile
:invitation-token itoken
:email "user@example.com"
:fullname "foobar"
:password "foobar"}
out (th/command! data)]
@@ -624,6 +618,7 @@
data {::th/type :prepare-register-profile
:invitation-token itoken
:fullname "foobar"
:email "user@example.com"
:password "foobar"}
out (th/command! data)]
@@ -636,6 +631,7 @@
(t/deftest prepare-register-with-registration-disabled
(with-redefs [app.config/flags #{}]
(let [data {::th/type :prepare-register-profile
:fullname "foobar"
:email "user@example.com"
:password "foobar"}
out (th/command! data)]
@@ -648,6 +644,7 @@
(t/deftest prepare-register-with-existing-user
(let [profile (th/create-profile* 1)
data {::th/type :prepare-register-profile
:fullname "foobar"
:email (:email profile)
:password "foobar"}
out (th/command! data)]
@@ -660,6 +657,7 @@
(let [pool (:app.db/pool th/*system*)
data {::th/type :prepare-register-profile
:fullname "foobar"
:email "user@example.com"
:password "foobar"}]
@@ -674,6 +672,7 @@
(t/deftest register-profile-with-complained-email
(let [pool (:app.db/pool th/*system*)
data {::th/type :prepare-register-profile
:fullname "foobar"
:email "user@example.com"
:password "foobar"}]
@@ -688,6 +687,7 @@
(t/deftest register-profile-with-email-as-password
(let [data {::th/type :prepare-register-profile
:fullname "foobar"
:email "user@example.com"
:password "USER@example.com"}
out (th/command! data)]

View File

@@ -460,11 +460,14 @@
::rpc/profile-id (:id profile1)}
out (th/command! params)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(let [[item1 :as result] (:result out)]
(t/is (= 1 (count result)))
(t/is (= (:id team1) (-> result first :id)))
(t/is (not= (:default-team-id profile1) (-> result first :id))))))
(t/is (= (:id team1) (:id item1)))
(t/is (= 1 (:total-members item1)))
(t/is (= 1 (:total-editors item1)))
(t/is (not= (:default-team-id profile1) (:id item1))))))
(t/deftest team-deletion-1

View File

@@ -19,25 +19,34 @@ __metadata:
languageName: node
linkType: hard
"@npmcli/agent@npm:^2.0.0":
version: 2.2.2
resolution: "@npmcli/agent@npm:2.2.2"
"@isaacs/fs-minipass@npm:^4.0.0":
version: 4.0.1
resolution: "@isaacs/fs-minipass@npm:4.0.1"
dependencies:
minipass: "npm:^7.0.4"
checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2
languageName: node
linkType: hard
"@npmcli/agent@npm:^3.0.0":
version: 3.0.0
resolution: "@npmcli/agent@npm:3.0.0"
dependencies:
agent-base: "npm:^7.1.0"
http-proxy-agent: "npm:^7.0.0"
https-proxy-agent: "npm:^7.0.1"
lru-cache: "npm:^10.0.1"
socks-proxy-agent: "npm:^8.0.3"
checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae
checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271
languageName: node
linkType: hard
"@npmcli/fs@npm:^3.1.0":
version: 3.1.1
resolution: "@npmcli/fs@npm:3.1.1"
"@npmcli/fs@npm:^4.0.0":
version: 4.0.0
resolution: "@npmcli/fs@npm:4.0.0"
dependencies:
semver: "npm:^7.3.5"
checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99
checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5
languageName: node
linkType: hard
@@ -48,29 +57,17 @@ __metadata:
languageName: node
linkType: hard
"abbrev@npm:^2.0.0":
version: 2.0.0
resolution: "abbrev@npm:2.0.0"
checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372
"abbrev@npm:^3.0.0":
version: 3.0.1
resolution: "abbrev@npm:3.0.1"
checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf
languageName: node
linkType: hard
"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1":
version: 7.1.1
resolution: "agent-base@npm:7.1.1"
dependencies:
debug: "npm:^4.3.4"
checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50
languageName: node
linkType: hard
"aggregate-error@npm:^3.0.0":
version: 3.1.0
resolution: "aggregate-error@npm:3.1.0"
dependencies:
clean-stack: "npm:^2.0.0"
indent-string: "npm:^4.0.0"
checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039
"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2":
version: 7.1.3
resolution: "agent-base@npm:7.1.3"
checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11
languageName: node
linkType: hard
@@ -82,9 +79,9 @@ __metadata:
linkType: hard
"ansi-regex@npm:^6.0.1":
version: 6.0.1
resolution: "ansi-regex@npm:6.0.1"
checksum: 10c0/cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08
version: 6.1.0
resolution: "ansi-regex@npm:6.1.0"
checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc
languageName: node
linkType: hard
@@ -141,21 +138,21 @@ __metadata:
linkType: hard
"brace-expansion@npm:^1.1.7":
version: 1.1.11
resolution: "brace-expansion@npm:1.1.11"
version: 1.1.12
resolution: "brace-expansion@npm:1.1.12"
dependencies:
balanced-match: "npm:^1.0.0"
concat-map: "npm:0.0.1"
checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668
checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73
languageName: node
linkType: hard
"brace-expansion@npm:^2.0.1":
version: 2.0.1
resolution: "brace-expansion@npm:2.0.1"
version: 2.0.2
resolution: "brace-expansion@npm:2.0.2"
dependencies:
balanced-match: "npm:^1.0.0"
checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f
checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf
languageName: node
linkType: hard
@@ -175,11 +172,11 @@ __metadata:
languageName: node
linkType: hard
"cacache@npm:^18.0.0":
version: 18.0.3
resolution: "cacache@npm:18.0.3"
"cacache@npm:^19.0.1":
version: 19.0.1
resolution: "cacache@npm:19.0.1"
dependencies:
"@npmcli/fs": "npm:^3.1.0"
"@npmcli/fs": "npm:^4.0.0"
fs-minipass: "npm:^3.0.0"
glob: "npm:^10.2.2"
lru-cache: "npm:^10.0.1"
@@ -187,11 +184,11 @@ __metadata:
minipass-collect: "npm:^2.0.1"
minipass-flush: "npm:^1.0.5"
minipass-pipeline: "npm:^1.2.4"
p-map: "npm:^4.0.0"
ssri: "npm:^10.0.0"
tar: "npm:^6.1.11"
unique-filename: "npm:^3.0.0"
checksum: 10c0/dfda92840bb371fb66b88c087c61a74544363b37a265023223a99965b16a16bbb87661fe4948718d79df6e0cc04e85e62784fbcf1832b2a5e54ff4c46fbb45b7
p-map: "npm:^7.0.2"
ssri: "npm:^12.0.0"
tar: "npm:^7.4.3"
unique-filename: "npm:^4.0.0"
checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c
languageName: node
linkType: hard
@@ -214,17 +211,10 @@ __metadata:
languageName: node
linkType: hard
"chownr@npm:^2.0.0":
version: 2.0.0
resolution: "chownr@npm:2.0.0"
checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6
languageName: node
linkType: hard
"clean-stack@npm:^2.0.0":
version: 2.2.0
resolution: "clean-stack@npm:2.2.0"
checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1
"chownr@npm:^3.0.0":
version: 3.0.0
resolution: "chownr@npm:3.0.0"
checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10
languageName: node
linkType: hard
@@ -251,26 +241,26 @@ __metadata:
languageName: node
linkType: hard
"cross-spawn@npm:^7.0.0":
version: 7.0.3
resolution: "cross-spawn@npm:7.0.3"
"cross-spawn@npm:^7.0.6":
version: 7.0.6
resolution: "cross-spawn@npm:7.0.6"
dependencies:
path-key: "npm:^3.1.0"
shebang-command: "npm:^2.0.0"
which: "npm:^2.0.1"
checksum: 10c0/5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750
checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1
languageName: node
linkType: hard
"debug@npm:4, debug@npm:^4, debug@npm:^4.3.4":
version: 4.3.4
resolution: "debug@npm:4.3.4"
version: 4.4.1
resolution: "debug@npm:4.4.1"
dependencies:
ms: "npm:2.1.2"
ms: "npm:^2.1.3"
peerDependenciesMeta:
supports-color:
optional: true
checksum: 10c0/cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736
checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55
languageName: node
linkType: hard
@@ -319,9 +309,21 @@ __metadata:
linkType: hard
"exponential-backoff@npm:^3.1.1":
version: 3.1.1
resolution: "exponential-backoff@npm:3.1.1"
checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579
version: 3.1.2
resolution: "exponential-backoff@npm:3.1.2"
checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844
languageName: node
linkType: hard
"fdir@npm:^6.4.4":
version: 6.4.6
resolution: "fdir@npm:6.4.6"
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
checksum: 10c0/45b559cff889934ebb8bc498351e5acba40750ada7e7d6bde197768d2fa67c149be8ae7f8ff34d03f4e1eb20f2764116e56440aaa2f6689e9a4aa7ef06acafe9
languageName: node
linkType: hard
@@ -335,21 +337,12 @@ __metadata:
linkType: hard
"foreground-child@npm:^3.1.0":
version: 3.1.1
resolution: "foreground-child@npm:3.1.1"
version: 3.3.1
resolution: "foreground-child@npm:3.3.1"
dependencies:
cross-spawn: "npm:^7.0.0"
cross-spawn: "npm:^7.0.6"
signal-exit: "npm:^4.0.1"
checksum: 10c0/9700a0285628abaeb37007c9a4d92bd49f67210f09067638774338e146c8e9c825c5c877f072b2f75f41dc6a2d0be8664f79ffc03f6576649f54a84fb9b47de0
languageName: node
linkType: hard
"fs-minipass@npm:^2.0.0":
version: 2.1.0
resolution: "fs-minipass@npm:2.1.0"
dependencies:
minipass: "npm:^3.0.0"
checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004
checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3
languageName: node
linkType: hard
@@ -390,18 +383,19 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^10.2.2, glob@npm:^10.3.10":
version: 10.3.16
resolution: "glob@npm:10.3.16"
"glob@npm:^10.2.2":
version: 10.4.5
resolution: "glob@npm:10.4.5"
dependencies:
foreground-child: "npm:^3.1.0"
jackspeak: "npm:^3.1.2"
minimatch: "npm:^9.0.1"
minipass: "npm:^7.0.4"
path-scurry: "npm:^1.11.0"
minimatch: "npm:^9.0.4"
minipass: "npm:^7.1.2"
package-json-from-dist: "npm:^1.0.0"
path-scurry: "npm:^1.11.1"
bin:
glob: dist/esm/bin.mjs
checksum: 10c0/f7eb4c3e66f221f0be3967c02527047167967549bdf8ed1bd5f6277d43a35191af4e2bb8c89f07a79664958bae088fd06659e69a0f1de462972f1eab52a715e8
checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e
languageName: node
linkType: hard
@@ -420,9 +414,9 @@ __metadata:
linkType: hard
"http-cache-semantics@npm:^4.1.1":
version: 4.1.1
resolution: "http-cache-semantics@npm:4.1.1"
checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc
version: 4.2.0
resolution: "http-cache-semantics@npm:4.2.0"
checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37
languageName: node
linkType: hard
@@ -437,12 +431,12 @@ __metadata:
linkType: hard
"https-proxy-agent@npm:^7.0.1":
version: 7.0.4
resolution: "https-proxy-agent@npm:7.0.4"
version: 7.0.6
resolution: "https-proxy-agent@npm:7.0.6"
dependencies:
agent-base: "npm:^7.0.2"
agent-base: "npm:^7.1.2"
debug: "npm:4"
checksum: 10c0/bc4f7c38da32a5fc622450b6cb49a24ff596f9bd48dcedb52d2da3fa1c1a80e100fb506bd59b326c012f21c863c69b275c23de1a01d0b84db396822fdf25e52b
checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac
languageName: node
linkType: hard
@@ -469,13 +463,6 @@ __metadata:
languageName: node
linkType: hard
"indent-string@npm:^4.0.0":
version: 4.0.0
resolution: "indent-string@npm:4.0.0"
checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f
languageName: node
linkType: hard
"ip-address@npm:^9.0.5":
version: 9.0.5
resolution: "ip-address@npm:9.0.5"
@@ -518,13 +505,6 @@ __metadata:
languageName: node
linkType: hard
"is-lambda@npm:^1.0.1":
version: 1.0.1
resolution: "is-lambda@npm:1.0.1"
checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d
languageName: node
linkType: hard
"is-number@npm:^7.0.0":
version: 7.0.0
resolution: "is-number@npm:7.0.0"
@@ -547,15 +527,15 @@ __metadata:
linkType: hard
"jackspeak@npm:^3.1.2":
version: 3.1.2
resolution: "jackspeak@npm:3.1.2"
version: 3.4.3
resolution: "jackspeak@npm:3.4.3"
dependencies:
"@isaacs/cliui": "npm:^8.0.2"
"@pkgjs/parseargs": "npm:^0.11.0"
dependenciesMeta:
"@pkgjs/parseargs":
optional: true
checksum: 10c0/5f1922a1ca0f19869e23f0dc4374c60d36e922f7926c76fecf8080cc6f7f798d6a9caac1b9428327d14c67731fd551bb3454cb270a5e13a0718f3b3660ec3d5d
checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9
languageName: node
linkType: hard
@@ -567,36 +547,35 @@ __metadata:
linkType: hard
"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0":
version: 10.2.2
resolution: "lru-cache@npm:10.2.2"
checksum: 10c0/402d31094335851220d0b00985084288136136992979d0e015f0f1697e15d1c86052d7d53ae86b614e5b058425606efffc6969a31a091085d7a2b80a8a1e26d6
version: 10.4.3
resolution: "lru-cache@npm:10.4.3"
checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb
languageName: node
linkType: hard
"luxon@npm:^3.4.4":
version: 3.4.4
resolution: "luxon@npm:3.4.4"
checksum: 10c0/02e26a0b039c11fd5b75e1d734c8f0332c95510f6a514a9a0991023e43fb233884da02d7f966823ffb230632a733fc86d4a4b1e63c3fbe00058b8ee0f8c728af
version: 3.6.1
resolution: "luxon@npm:3.6.1"
checksum: 10c0/906d57a9dc4d1de9383f2e9223e378c298607c1b4d17b6657b836a3cd120feb1c1de3b5d06d846a3417e1ca764de8476e8c23b3cd4083b5cdb870adcb06a99d5
languageName: node
linkType: hard
"make-fetch-happen@npm:^13.0.0":
version: 13.0.1
resolution: "make-fetch-happen@npm:13.0.1"
"make-fetch-happen@npm:^14.0.3":
version: 14.0.3
resolution: "make-fetch-happen@npm:14.0.3"
dependencies:
"@npmcli/agent": "npm:^2.0.0"
cacache: "npm:^18.0.0"
"@npmcli/agent": "npm:^3.0.0"
cacache: "npm:^19.0.1"
http-cache-semantics: "npm:^4.1.1"
is-lambda: "npm:^1.0.1"
minipass: "npm:^7.0.2"
minipass-fetch: "npm:^3.0.0"
minipass-fetch: "npm:^4.0.0"
minipass-flush: "npm:^1.0.5"
minipass-pipeline: "npm:^1.2.4"
negotiator: "npm:^0.6.3"
proc-log: "npm:^4.2.0"
negotiator: "npm:^1.0.0"
proc-log: "npm:^5.0.0"
promise-retry: "npm:^2.0.1"
ssri: "npm:^10.0.0"
checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e
ssri: "npm:^12.0.0"
checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0
languageName: node
linkType: hard
@@ -609,12 +588,12 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^9.0.1":
version: 9.0.4
resolution: "minimatch@npm:9.0.4"
"minimatch@npm:^9.0.4":
version: 9.0.5
resolution: "minimatch@npm:9.0.5"
dependencies:
brace-expansion: "npm:^2.0.1"
checksum: 10c0/2c16f21f50e64922864e560ff97c587d15fd491f65d92a677a344e970fe62aafdbeafe648965fa96d33c061b4d0eabfe0213466203dd793367e7f28658cf6414
checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed
languageName: node
linkType: hard
@@ -627,18 +606,18 @@ __metadata:
languageName: node
linkType: hard
"minipass-fetch@npm:^3.0.0":
version: 3.0.5
resolution: "minipass-fetch@npm:3.0.5"
"minipass-fetch@npm:^4.0.0":
version: 4.0.1
resolution: "minipass-fetch@npm:4.0.1"
dependencies:
encoding: "npm:^0.1.13"
minipass: "npm:^7.0.3"
minipass-sized: "npm:^1.0.3"
minizlib: "npm:^2.1.2"
minizlib: "npm:^3.0.1"
dependenciesMeta:
encoding:
optional: true
checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b
checksum: 10c0/a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c
languageName: node
linkType: hard
@@ -678,76 +657,68 @@ __metadata:
languageName: node
linkType: hard
"minipass@npm:^5.0.0":
version: 5.0.0
resolution: "minipass@npm:5.0.0"
checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462
"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2":
version: 7.1.2
resolution: "minipass@npm:7.1.2"
checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557
languageName: node
linkType: hard
"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4":
version: 7.1.1
resolution: "minipass@npm:7.1.1"
checksum: 10c0/fdccc2f99c31083f45f881fd1e6971d798e333e078ab3c8988fb818c470fbd5e935388ad9adb286397eba50baebf46ef8ff487c8d3f455a69c6f3efc327bdff9
languageName: node
linkType: hard
"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2":
version: 2.1.2
resolution: "minizlib@npm:2.1.2"
"minizlib@npm:^3.0.1":
version: 3.0.2
resolution: "minizlib@npm:3.0.2"
dependencies:
minipass: "npm:^3.0.0"
yallist: "npm:^4.0.0"
checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78
minipass: "npm:^7.1.2"
checksum: 10c0/9f3bd35e41d40d02469cb30470c55ccc21cae0db40e08d1d0b1dff01cc8cc89a6f78e9c5d2b7c844e485ec0a8abc2238111213fdc5b2038e6d1012eacf316f78
languageName: node
linkType: hard
"mkdirp@npm:^1.0.3":
version: 1.0.4
resolution: "mkdirp@npm:1.0.4"
"mkdirp@npm:^3.0.1":
version: 3.0.1
resolution: "mkdirp@npm:3.0.1"
bin:
mkdirp: bin/cmd.js
checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf
mkdirp: dist/cjs/src/bin.js
checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d
languageName: node
linkType: hard
"ms@npm:2.1.2":
version: 2.1.2
resolution: "ms@npm:2.1.2"
checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc
"ms@npm:^2.1.3":
version: 2.1.3
resolution: "ms@npm:2.1.3"
checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48
languageName: node
linkType: hard
"negotiator@npm:^0.6.3":
version: 0.6.3
resolution: "negotiator@npm:0.6.3"
checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2
"negotiator@npm:^1.0.0":
version: 1.0.0
resolution: "negotiator@npm:1.0.0"
checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b
languageName: node
linkType: hard
"node-gyp@npm:latest":
version: 10.1.0
resolution: "node-gyp@npm:10.1.0"
version: 11.2.0
resolution: "node-gyp@npm:11.2.0"
dependencies:
env-paths: "npm:^2.2.0"
exponential-backoff: "npm:^3.1.1"
glob: "npm:^10.3.10"
graceful-fs: "npm:^4.2.6"
make-fetch-happen: "npm:^13.0.0"
nopt: "npm:^7.0.0"
proc-log: "npm:^3.0.0"
make-fetch-happen: "npm:^14.0.3"
nopt: "npm:^8.0.0"
proc-log: "npm:^5.0.0"
semver: "npm:^7.3.5"
tar: "npm:^6.1.2"
which: "npm:^4.0.0"
tar: "npm:^7.4.3"
tinyglobby: "npm:^0.2.12"
which: "npm:^5.0.0"
bin:
node-gyp: bin/node-gyp.js
checksum: 10c0/9cc821111ca244a01fb7f054db7523ab0a0cd837f665267eb962eb87695d71fb1e681f9e21464cc2fd7c05530dc4c81b810bca1a88f7d7186909b74477491a3c
checksum: 10c0/bd8d8c76b06be761239b0c8680f655f6a6e90b48e44d43415b11c16f7e8c15be346fba0cbf71588c7cdfb52c419d928a7d3db353afc1d952d19756237d8f10b9
languageName: node
linkType: hard
"nodemon@npm:^3.1.2":
version: 3.1.2
resolution: "nodemon@npm:3.1.2"
version: 3.1.10
resolution: "nodemon@npm:3.1.10"
dependencies:
chokidar: "npm:^3.5.2"
debug: "npm:^4"
@@ -761,18 +732,18 @@ __metadata:
undefsafe: "npm:^2.0.5"
bin:
nodemon: bin/nodemon.js
checksum: 10c0/7a091067d766768fb6660b796194b01748bba5dc3f1e3ed3dd5f804bfa305e207d24635755078ee5e7cc53848cea35204901e0a6e51ac64483bb8e9ecb237c95
checksum: 10c0/95b64d647f2c22e85e375b250517b0a4b32c2d2392ad898444e331f70d6b1ab43b17f53a8a1d68d5879ab8401fc6cd6e26f0d2a8736240984f6b5a8435b407c0
languageName: node
linkType: hard
"nopt@npm:^7.0.0":
version: 7.2.1
resolution: "nopt@npm:7.2.1"
"nopt@npm:^8.0.0":
version: 8.1.0
resolution: "nopt@npm:8.1.0"
dependencies:
abbrev: "npm:^2.0.0"
abbrev: "npm:^3.0.0"
bin:
nopt: bin/nopt.js
checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81
checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef
languageName: node
linkType: hard
@@ -783,12 +754,17 @@ __metadata:
languageName: node
linkType: hard
"p-map@npm:^4.0.0":
version: 4.0.0
resolution: "p-map@npm:4.0.0"
dependencies:
aggregate-error: "npm:^3.0.0"
checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75
"p-map@npm:^7.0.2":
version: 7.0.3
resolution: "p-map@npm:7.0.3"
checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c
languageName: node
linkType: hard
"package-json-from-dist@npm:^1.0.0":
version: 1.0.1
resolution: "package-json-from-dist@npm:1.0.1"
checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b
languageName: node
linkType: hard
@@ -799,7 +775,7 @@ __metadata:
languageName: node
linkType: hard
"path-scurry@npm:^1.11.0":
"path-scurry@npm:^1.11.1":
version: 1.11.1
resolution: "path-scurry@npm:1.11.1"
dependencies:
@@ -816,17 +792,17 @@ __metadata:
languageName: node
linkType: hard
"proc-log@npm:^3.0.0":
version: 3.0.0
resolution: "proc-log@npm:3.0.0"
checksum: 10c0/f66430e4ff947dbb996058f6fd22de2c66612ae1a89b097744e17fb18a4e8e7a86db99eda52ccf15e53f00b63f4ec0b0911581ff2aac0355b625c8eac509b0dc
"picomatch@npm:^4.0.2":
version: 4.0.2
resolution: "picomatch@npm:4.0.2"
checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc
languageName: node
linkType: hard
"proc-log@npm:^4.2.0":
version: 4.2.0
resolution: "proc-log@npm:4.2.0"
checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9
"proc-log@npm:^5.0.0":
version: 5.0.0
resolution: "proc-log@npm:5.0.0"
checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3
languageName: node
linkType: hard
@@ -878,11 +854,11 @@ __metadata:
linkType: hard
"semver@npm:^7.3.5, semver@npm:^7.5.3":
version: 7.6.2
resolution: "semver@npm:7.6.2"
version: 7.7.2
resolution: "semver@npm:7.7.2"
bin:
semver: bin/semver.js
checksum: 10c0/97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c
checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea
languageName: node
linkType: hard
@@ -926,23 +902,23 @@ __metadata:
linkType: hard
"socks-proxy-agent@npm:^8.0.3":
version: 8.0.3
resolution: "socks-proxy-agent@npm:8.0.3"
version: 8.0.5
resolution: "socks-proxy-agent@npm:8.0.5"
dependencies:
agent-base: "npm:^7.1.1"
agent-base: "npm:^7.1.2"
debug: "npm:^4.3.4"
socks: "npm:^2.7.1"
checksum: 10c0/4950529affd8ccd6951575e21c1b7be8531b24d924aa4df3ee32df506af34b618c4e50d261f4cc603f1bfd8d426915b7d629966c8ce45b05fb5ad8c8b9a6459d
socks: "npm:^2.8.3"
checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6
languageName: node
linkType: hard
"socks@npm:^2.7.1":
version: 2.8.3
resolution: "socks@npm:2.8.3"
"socks@npm:^2.8.3":
version: 2.8.5
resolution: "socks@npm:2.8.5"
dependencies:
ip-address: "npm:^9.0.5"
smart-buffer: "npm:^4.2.0"
checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7
checksum: 10c0/e427d0eb0451cfd04e20b9156ea8c0e9b5e38a8d70f21e55c30fbe4214eda37cfc25d782c63f9adc5fbdad6d062a0f127ef2cefc9a44b6fee2b9ea5d1ed10827
languageName: node
linkType: hard
@@ -970,12 +946,12 @@ __metadata:
languageName: node
linkType: hard
"ssri@npm:^10.0.0":
version: 10.0.6
resolution: "ssri@npm:10.0.6"
"ssri@npm:^12.0.0":
version: 12.0.0
resolution: "ssri@npm:12.0.0"
dependencies:
minipass: "npm:^7.0.3"
checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227
checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d
languageName: node
linkType: hard
@@ -1028,17 +1004,27 @@ __metadata:
languageName: node
linkType: hard
"tar@npm:^6.1.11, tar@npm:^6.1.2":
version: 6.2.1
resolution: "tar@npm:6.2.1"
"tar@npm:^7.4.3":
version: 7.4.3
resolution: "tar@npm:7.4.3"
dependencies:
chownr: "npm:^2.0.0"
fs-minipass: "npm:^2.0.0"
minipass: "npm:^5.0.0"
minizlib: "npm:^2.1.1"
mkdirp: "npm:^1.0.3"
yallist: "npm:^4.0.0"
checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537
"@isaacs/fs-minipass": "npm:^4.0.0"
chownr: "npm:^3.0.0"
minipass: "npm:^7.1.2"
minizlib: "npm:^3.0.1"
mkdirp: "npm:^3.0.1"
yallist: "npm:^5.0.0"
checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d
languageName: node
linkType: hard
"tinyglobby@npm:^0.2.12":
version: 0.2.14
resolution: "tinyglobby@npm:0.2.14"
dependencies:
fdir: "npm:^6.4.4"
picomatch: "npm:^4.0.2"
checksum: 10c0/f789ed6c924287a9b7d3612056ed0cda67306cd2c80c249fd280cf1504742b12583a2089b61f4abbd24605f390809017240e250241f09938054c9b363e51c0a6
languageName: node
linkType: hard
@@ -1067,21 +1053,21 @@ __metadata:
languageName: node
linkType: hard
"unique-filename@npm:^3.0.0":
version: 3.0.0
resolution: "unique-filename@npm:3.0.0"
"unique-filename@npm:^4.0.0":
version: 4.0.0
resolution: "unique-filename@npm:4.0.0"
dependencies:
unique-slug: "npm:^4.0.0"
checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f
unique-slug: "npm:^5.0.0"
checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc
languageName: node
linkType: hard
"unique-slug@npm:^4.0.0":
version: 4.0.0
resolution: "unique-slug@npm:4.0.0"
"unique-slug@npm:^5.0.0":
version: 5.0.0
resolution: "unique-slug@npm:5.0.0"
dependencies:
imurmurhash: "npm:^0.1.4"
checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635
checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293
languageName: node
linkType: hard
@@ -1096,14 +1082,14 @@ __metadata:
languageName: node
linkType: hard
"which@npm:^4.0.0":
version: 4.0.0
resolution: "which@npm:4.0.0"
"which@npm:^5.0.0":
version: 5.0.0
resolution: "which@npm:5.0.0"
dependencies:
isexe: "npm:^3.1.1"
bin:
node-which: bin/which.js
checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a
checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b
languageName: node
linkType: hard
@@ -1130,8 +1116,8 @@ __metadata:
linkType: hard
"ws@npm:^8.17.0":
version: 8.17.0
resolution: "ws@npm:8.17.0"
version: 8.18.2
resolution: "ws@npm:8.18.2"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2"
@@ -1140,7 +1126,7 @@ __metadata:
optional: true
utf-8-validate:
optional: true
checksum: 10c0/55241ec93a66fdfc4bf4f8bc66c8eb038fda2c7a4ee8f6f157f2ca7dc7aa76aea0c0da0bf3adb2af390074a70a0e45456a2eaf80e581e630b75df10a64b0a990
checksum: 10c0/4b50f67931b8c6943c893f59c524f0e4905bbd183016cfb0f2b8653aa7f28dad4e456b9d99d285bbb67cca4fedd9ce90dfdfaa82b898a11414ebd66ee99141e4
languageName: node
linkType: hard
@@ -1150,3 +1136,10 @@ __metadata:
checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a
languageName: node
linkType: hard
"yallist@npm:^5.0.0":
version: 5.0.0
resolution: "yallist@npm:5.0.0"
checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416
languageName: node
linkType: hard

View File

@@ -28,7 +28,7 @@
integrant/integrant {:mvn/version "0.13.1"}
funcool/tubax {:mvn/version "2021.05.20-0"}
funcool/cuerdas {:mvn/version "2025.05.26-411"}
funcool/cuerdas {:mvn/version "2025.06.16-414"}
funcool/promesa
{:git/sha "f52f58cfacf62f59eab717e2637f37729d0cc383"
:git/url "https://github.com/funcool/promesa"}

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c",
"type": "module",
"repository": {
"type": "git",
@@ -16,7 +16,6 @@
"devDependencies": {
"concurrently": "^9.1.2",
"nodemon": "^3.1.10",
"shadow-cljs": "3.1.5",
"source-map-support": "^0.5.21",
"ws": "^8.18.2"
},

View File

@@ -47,10 +47,26 @@
`(try ~@exprs (catch Throwable e# nil))))
(defmacro try!
[& exprs]
(if (:ns &env)
`(try ~@exprs (catch :default e# e#))
`(try ~@exprs (catch Throwable e# e#))))
[expr & {:keys [reraise-with on-exception]}]
(let [ex-sym
(gensym "exc")
generate-catch
(fn []
(cond
(map? reraise-with)
`(ex/raise ~@(mapcat identity reraise-with) :cause ~ex-sym)
on-exception
`(let [handler# ~on-exception]
(handler# ~ex-sym))
:else
ex-sym))]
(if (:ns &env)
`(try ~expr (catch :default ~ex-sym ~(generate-catch)))
`(try ~expr (catch Throwable ~ex-sym ~(generate-catch))))))
(defn ex-info?
[v]

View File

@@ -241,7 +241,7 @@
[:shapes ::sm/any]
[:index {:optional true} [:maybe :int]]
[:after-shape {:optional true} ::sm/any]
[:component-swap {:optional true} :boolean]]]
[:allow-altering-copies {:optional true} :boolean]]]
[:reorder-children
[:map {:title "ReorderChildrenChange"}
@@ -418,7 +418,7 @@
[:type [:= :set-token-set]]
[:set-name :string]
[:group? :boolean]
[:token-set [:maybe ctob/schema:token-set-attrs]]]]
[:token-set [:maybe [:fn ctob/token-set?]]]]]
[:set-token
[:map {:title "SetTokenChange"}
@@ -487,7 +487,9 @@
(cts/shape? shape-new))
(ex/raise :type :assertion
:code :data-validation
:hint "invalid shape found after applying changes"
:hint (str "invalid shape found after applying changes on file "
(:id data-new))
:file-id (:id data-new)
::sm/explain (cts/explain-shape shape-new))))))]
(->> (into #{} (map :page-id) items)
@@ -759,7 +761,7 @@
(d/update-in-when data [:components component-id :objects] reg-objects))))
(defmethod process-change :mov-objects
[data {:keys [parent-id shapes index page-id component-id ignore-touched after-shape component-swap syncing]}]
[data {:keys [parent-id shapes index page-id component-id ignore-touched after-shape allow-altering-copies syncing]}]
(letfn [(calculate-invalid-targets [objects shape-id]
(let [reduce-fn #(into %1 (calculate-invalid-targets objects %2))]
(->> (get-in objects [shape-id :shapes])
@@ -774,7 +776,7 @@
(and shape
(not (invalid-targets parent-id))
(not (cfh/components-nesting-loop? objects shape-id parent-id))
(or component-swap ;; On a component swap it's allowed to change the structure of a copy
(or allow-altering-copies ;; In some cases (like a component swap) it's allowed to change the structure of a copy
syncing ;; If we are syncing the changes of a main component, it's allowed to change the structure of a copy
(and
(not (ctk/in-component-copy? (get objects (:parent-id shape)))) ;; We don't want to change the structure of component copies
@@ -1025,11 +1027,10 @@
(ctob/delete-set lib' set-name))
(not (ctob/get-set lib' set-name))
(ctob/add-set lib' (ctob/make-token-set token-set))
(ctob/add-set lib' token-set)
:else
(ctob/update-set lib' set-name (fn [prev-token-set]
(ctob/make-token-set (merge prev-token-set token-set)))))))))
(ctob/update-set lib' set-name (fn [_] token-set)))))))
(defmethod process-change :set-token-theme
[data {:keys [group theme-name theme]}]

View File

@@ -161,7 +161,6 @@
(contains? (meta changes) ::file-data)
"Call (with-file-data) before using this function"))
(defn- lookup-objects
[changes]
(let [data (::file-data (meta changes))]
@@ -465,8 +464,8 @@
(some? index)
(assoc :index index)
(:component-swap options)
(assoc :component-swap true)
(:allow-altering-copies options)
(assoc :allow-altering-copies true)
(:ignore-touched options)
(assoc :ignore-touched true))
@@ -474,12 +473,14 @@
(fn [undo-changes shape]
(let [prev-sibling (cfh/get-prev-sibling objects (:id shape))]
(conj undo-changes
{:type :mov-objects
:page-id (::page-id (meta changes))
:parent-id (:parent-id shape)
:shapes [(:id shape)]
:after-shape prev-sibling
:index 0}))) ; index is used in case there is no after-shape (moving bottom shapes)
(cond-> {:type :mov-objects
:page-id (::page-id (meta changes))
:parent-id (:parent-id shape)
:shapes [(:id shape)]
:after-shape prev-sibling
:index 0} ; index is used in case there is no after-shape (moving bottom shapes)
(:allow-altering-copies options)
(assoc :allow-altering-copies true)))))
restore-touched-change
{:type :mod-obj
@@ -917,7 +918,7 @@
(-> changes
(update :redo-changes conj {:type :set-token-set
:set-name name
:token-set (assoc prev-token-set :name new-name)
:token-set (ctob/rename prev-token-set new-name)
:group? false})
(update :undo-changes conj {:type :set-token-set
:set-name new-name
@@ -938,11 +939,11 @@
:group? group?})
(update :undo-changes conj (if prev-token-set
{:type :set-token-set
:set-name (or
;; Undo of edit
(:name token-set)
;; Undo of delete
set-name)
:set-name (if token-set
;; Undo of edit
(ctob/get-name token-set)
;; Undo of delete
set-name)
:token-set prev-token-set
:group? group?}
;; Undo of create

View File

@@ -117,6 +117,12 @@
([shape]
(d/not-empty? (:shapes shape))))
(defn has-layout?
"Returns true if the provided shape has a layout assigned"
[objects id]
(let [shape (get objects id)]
(boolean (and shape (:layout shape)))))
(defn group-like-shape?
([objects id]
(group-like-shape? (get objects id)))
@@ -127,13 +133,41 @@
;; ---- ACCESSORS
(defn get-children-ids
(defn get-selected-type
"Returns the type of the shape if only one, or :multiple if more
than one"
[objects selected]
(if (= 1 (count selected))
(let [shape (get objects (first selected))]
(:type shape))
:multiple))
(defn get-shape-type
"Returns the type of the shape, or 'root' if it's Root Frame, always
as string"
[objects id]
(letfn [(get-children-ids-rec [id processed]
(when (not (contains? processed id))
(when-let [shapes (-> (get objects id) :shapes (some-> vec))]
(into shapes (mapcat #(get-children-ids-rec % (conj processed id))) shapes))))]
(get-children-ids-rec id #{})))
(let [shape (get objects id)]
(if (root? shape)
:root
(dm/get-prop shape :type))))
(defn get-children-ids
"Returns the ids of all the descendants of the shape identified
by the id. Optionally, you can pass an ignore function to indicate
when to ignore a descendant (and all its descendants)"
([objects id]
(get-children-ids objects id {}))
([objects id {:keys [ignore-children-fn]
;;ignore-children-fn should receive a shape and return a boolean
:or {ignore-children-fn (constantly false)}}]
(letfn [(get-children-ids-rec [id processed]
(when-not (contains? processed id)
(when-let [shapes (as-> (get objects id) $
(:shapes $)
(remove ignore-children-fn $)
(some-> $ vec))]
(into shapes (mapcat #(get-children-ids-rec % (conj processed id))) shapes))))]
(get-children-ids-rec id #{}))))
(defn get-children-ids-with-self
[objects id]

View File

@@ -32,6 +32,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.shadow :as ctss]
[app.common.types.text :as cttx]
[app.common.uuid :as uuid]
[clojure.set :as set]
[cuerdas.core :as str]))
@@ -94,22 +95,33 @@
(defn migrate-file
[file libs]
(binding [cfeat/*new* (atom #{})]
(let [version (or (:version file)
(-> file :data :version))]
(-> file
(assoc :version cfd/version)
(update :migrations
(fn [migrations]
(if (nil? migrations)
(generate-migrations-from-version version)
migrations)))
;; NOTE: in some future we can consider to apply
;; a migration to the whole database and remove
;; this code from this function that executes on
;; each file migration operation
(update :features cfeat/migrate-legacy-features)
(migrate libs)
(update :features (fnil into #{}) (deref cfeat/*new*))))))
(let [version
(or (:version file) (-> file :data :version))
migrations
(not-empty (get file :migrations))
file
(-> file
(assoc :version cfd/version)
(assoc :migrations
(if migrations
migrations
(generate-migrations-from-version version)))
;; NOTE: in some future we can consider to apply a
;; migration to the whole database and remove this code
;; from this function that executes on each file
;; migration operation
(update :features cfeat/migrate-legacy-features)
(migrate libs)
(update :features (fnil into #{}) (deref cfeat/*new*)))]
;; NOTE: When we have no previous migrations, we report all
;; migrations as migrated in order to correctly persist them all
;; and not only the really applied migrations
(if (not migrations)
(vary-meta file assoc ::migrated (:migrations file))
file))))
(defn migrated?
[file]
@@ -844,7 +856,7 @@
(update-object [object]
(if (cfh/text-shape? object)
(update object :content #(txt/transform-nodes identity update-text-node %))
(update object :content #(txt/transform-nodes txt/is-content-node? update-text-node %))
object))
(update-container [container]
@@ -1008,8 +1020,8 @@
[data _]
(let [update-colors
(fn [colors]
(into {} (filter #(-> % val types.color/valid-color?) colors)))]
(update data :colors update-colors)))
(into {} (filter #(-> % val types.color/valid-library-color?) colors)))]
(d/update-when data :colors update-colors)))
(defmethod migrate-data "legacy-52"
[data _]
@@ -1023,7 +1035,6 @@
(update data :pages-index d/update-vals update-page)))
(defmethod migrate-data "legacy-53"
[data _]
(migrate-data data "legacy-26"))
@@ -1094,7 +1105,7 @@
;; The text shape also can has fills on the text
;; fragments so we need to fix fills there
(cond-> (cfh/text-shape? object)
(update :content (partial txt/transform-nodes identity fix-fills)))))
(update :content (partial txt/transform-nodes txt/is-content-node? fix-fills)))))
(update-container [container]
(d/update-when container :objects d/update-vals update-object))]
@@ -1266,21 +1277,21 @@
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0002-normalize-bool-content"
(defmethod migrate-data "0002-normalize-bool-content-v2"
[data _]
(letfn [(update-object [object]
;; NOTE: we still preserve the previous value for possible
;; rollback, we still need to perform an other migration
;; for properly delete the bool-content prop from shapes
;; once the know the migration was OK
(if (cfh/bool-shape? object)
(if-let [content (:bool-content object)]
(assoc object :content content)
object)
(if (contains? object :content)
(dissoc object :bool-content)
(let [content (:bool-content object)]
(-> object
(assoc :content content)
(dissoc :bool-content))))
(dissoc object :bool-content :bool-type)))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
@@ -1308,49 +1319,71 @@
(d/update-when :components d/update-vals update-container)
(d/without-nils))))
(defmethod migrate-data "0003-convert-path-content"
(defmethod migrate-data "0003-convert-path-content-v2"
[data _]
(some-> cfeat/*new* (swap! conj "fdata/path-data"))
(letfn [(update-object [object]
(if (or (cfh/bool-shape? object)
(cfh/path-shape? object))
(update object :content path/content)
object))
(let [decode-segments
(sm/decoder path/schema:segments sm/json-transformer)
(update-container [container]
(d/update-when container :objects update-vals update-object))]
update-object
(fn [object]
(if (or (cfh/bool-shape? object)
(cfh/path-shape? object))
(let [content (get object :content)
content (cond
(path/content? content)
content
(nil? content)
(path/content [])
:else
(-> content
(decode-segments)
(path/content)))]
(assoc object :content content))
object))
update-container
(fn [container]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0004-clean-shadow-and-colors"
(defmethod migrate-data "0004-clean-shadow-color"
[data _]
(letfn [(clean-shadow [shadow]
(update shadow :color (fn [color]
(let [ref-id (get color :id)
ref-file (get color :file-id)]
(-> (d/without-qualified color)
(select-keys [:opacity :color :gradient :image :ref-id :ref-file])
(cond-> ref-id
(assoc :ref-id ref-id))
(cond-> ref-file
(assoc :ref-file ref-file)))))))
(let [decode-color (sm/decoder types.color/schema:color sm/json-transformer)
(update-object [object]
(d/update-when object :shadow #(mapv clean-shadow %)))
clean-shadow-color
(fn [color]
(let [ref-id (get color :id)
ref-file (get color :file-id)]
(-> (d/without-qualified color)
(select-keys [:opacity :color :gradient :image :ref-id :ref-file])
(cond-> ref-id
(assoc :ref-id ref-id))
(cond-> ref-file
(assoc :ref-file ref-file))
(decode-color))))
(update-container [container]
(d/update-when container :objects d/update-vals update-object))
clean-shadow
(fn [shadow]
(update shadow :color clean-shadow-color))
(clean-library-color [color]
(dissoc color :file-id))]
update-object
(fn [object]
(d/update-when object :shadow #(mapv clean-shadow %)))
update-container
(fn [container]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container)
(d/update-when :colors d/update-vals clean-library-color))))
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0005-deprecate-image-type"
[data _]
@@ -1390,7 +1423,7 @@
(update-object [object]
(if (cfh/text-shape? object)
(update object :content (partial txt/transform-nodes identity fix-fills))
(update object :content (partial txt/transform-nodes txt/is-content-node? fix-fills))
object))
(update-container [container]
@@ -1468,14 +1501,57 @@
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0008-fix-library-colors-opacity"
(defmethod migrate-data "0008-fix-library-colors-v4"
[data _]
(letfn [(update-color [color]
(letfn [(clear-color-opacity [color]
(if (and (contains? color :opacity)
(nil? (get color :opacity)))
(assoc color :opacity 1)
color))]
(d/update-when data :colors d/update-vals update-color)))
color))
(clear-color [color]
(-> color
(select-keys types.color/library-color-attrs)
(clear-color-opacity)
(d/without-nils)))]
(d/update-when data :colors d/update-vals clear-color)))
(defmethod migrate-data "0009-clean-library-colors"
[data _]
(d/update-when data :colors
(fn [colors]
(reduce-kv (fn [colors id color]
(if (types.color/valid-library-color? color)
colors
(dissoc colors id)))
colors
colors))))
(defmethod migrate-data "0009-add-partial-text-touched-flags"
[data _]
(letfn [(update-object [page object]
(if (and (cfh/text-shape? object)
(ctk/in-component-copy? object))
(let [file {:id (:id data) :data data}
libs (when (:libs data)
(deref (:libs data)))
ref-shape (ctf/find-ref-shape file page libs object
{:include-deleted? true :with-context? true})
partial-touched (when ref-shape
(cttx/get-diff-type (:content object) (:content ref-shape)))]
(if (seq partial-touched)
(update object :touched (fn [touched]
(reduce #(ctk/set-touched-group %1 %2)
touched
partial-touched)))
object))
object))
(update-page [page]
(d/update-when page :objects d/update-vals (partial update-object page)))]
(update data :pages-index d/update-vals update-page)))
(def available-migrations
(into (d/ordered-set)
@@ -1532,12 +1608,14 @@
"legacy-66"
"legacy-67"
"0001-remove-tokens-from-groups"
"0002-normalize-bool-content"
"0002-normalize-bool-content-v2"
"0002-clean-shape-interactions"
"0003-fix-root-shape"
"0003-convert-path-content"
"0004-clean-shadow-and-colors"
"0003-convert-path-content-v2"
"0004-clean-shadow-color"
"0005-deprecate-image-type"
"0006-fix-old-texts-fills"
"0007-clear-invalid-strokes-and-fills-v2"
"0008-fix-library-colors-opacity"]))
"0008-fix-library-colors-v4"
"0009-clean-library-colors"
"0009-add-partial-text-touched-flags"]))

View File

@@ -96,7 +96,7 @@
(log/dbg :hint "repairing shape :invalid-parent" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/change-parent (:parent-id args) [shape] nil {:component-swap true})))
(pcb/change-parent (:parent-id args) [shape] nil {:allow-altering-copies true})))
(defmethod repair-error :frame-not-found
[_ {:keys [shape page-id] :as error} file-data _]
@@ -387,7 +387,7 @@
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/update-shapes [(:id shape)] repair-shape)
(pcb/change-parent uuid/zero [shape] nil {:component-swap true}))))
(pcb/change-parent uuid/zero [shape] nil {:allow-altering-copies true}))))
(defmethod repair-error :root-copy-not-allowed
[_ {:keys [shape page-id] :as error} file-data _]
@@ -602,11 +602,6 @@
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :variant-no-properties
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :variant-bad-variant-name
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))

View File

@@ -91,10 +91,10 @@
parent-id (or parent-id (get selected-obj :parent-id))
base-parent (get objects parent-id)
layout-props
layout-attrs
(when (and (= 1 (count selected))
(ctl/any-layout? base-parent))
(select-keys selected-obj ctl/layout-item-props))
(select-keys selected-obj ctl/layout-child-attrs))
target-cell-id
(if (and (nil? target-cell-id)
@@ -129,8 +129,8 @@
:parent-id parent-id
:shapes (into [] selected))
(some? layout-props)
(d/patch-object layout-props)
(some? layout-attrs)
(d/patch-object layout-attrs)
;; Frames from shapes will not be displayed in viewer and no clipped
(or (not= frame-id uuid/zero) without-fill?)

View File

@@ -68,7 +68,6 @@
:variant-bad-name
:variant-bad-variant-name
:variant-component-bad-name
:variant-no-properties
:variant-component-bad-id})
(def ^:private schema:error
@@ -589,11 +588,7 @@
(when-not (ctk/is-variant? main-component)
(report-error :not-a-variant
(str/ffmt "Shape % should be a variant" (:id main-component))
main-component file component-page))
(when (< (count (:variant-properties component)) 1)
(report-error :variant-no-properties
(str/ffmt "Component variant % should have properties" (:id main-component))
main-component file nil))))
main-component file component-page))))
(defn- check-component
"Validate semantic coherence of a component. Report all errors found."

View File

@@ -117,6 +117,7 @@
;; Only for developtment.
:tiered-file-data-storage
:token-units
:token-typography-types
:transit-readable-response
:user-feedback
;; TODO: remove this flag.
@@ -149,7 +150,8 @@
:enable-onboarding
:enable-dashboard-templates-section
:enable-google-fonts-provider
:enable-component-thumbnails])
:enable-component-thumbnails
:enable-render-wasm-dpr])
(defn parse
[& flags]

View File

@@ -467,15 +467,15 @@
row-tracks (set-flex-multi-span parent row-tracks children-map shape-cells bounds objects :row)
;; Once auto sizes have been calculated we get calculate the `fr` unit with the remainining size and adjust the size
free-column-space (max 0 (- bound-width (+ column-total-size-nofr column-total-gap)))
free-row-space (max 0 (- bound-height (+ row-total-size-nofr row-total-gap)))
fr-column-space (max 0 (- bound-width (+ column-total-size-nofr column-total-gap)))
fr-row-space (max 0 (- bound-height (+ row-total-size-nofr row-total-gap)))
;; Get the minimum values for fr's
min-column-fr (min-fr-value column-tracks)
min-row-fr (min-fr-value row-tracks)
column-fr (if auto-width? min-column-fr (mth/finite (/ free-column-space column-frs) 0))
row-fr (if auto-height? min-row-fr (mth/finite (/ free-row-space row-frs) 0))
column-fr (if auto-width? min-column-fr (mth/finite (/ fr-column-space column-frs) 0))
row-fr (if auto-height? min-row-fr (mth/finite (/ fr-row-space row-frs) 0))
column-tracks (set-fr-value column-tracks column-fr auto-width?)
row-tracks (set-fr-value row-tracks row-fr auto-height?)
@@ -484,13 +484,13 @@
column-total-size (tracks-total-size column-tracks)
row-total-size (tracks-total-size row-tracks)
free-column-space (max 0 (if auto-width? 0 (- bound-width (+ column-total-size column-total-gap))))
free-row-space (max 0 (if auto-height? 0 (- bound-height (+ row-total-size row-total-gap))))
auto-column-space (max 0 (if auto-width? 0 (- bound-width (+ column-total-size column-total-gap))))
auto-row-space (max 0 (if auto-height? 0 (- bound-height (+ row-total-size row-total-gap))))
column-autos (tracks-total-autos column-tracks)
row-autos (tracks-total-autos row-tracks)
column-add-auto (/ free-column-space column-autos)
row-add-auto (/ free-row-space row-autos)
column-add-auto (/ auto-column-space column-autos)
row-add-auto (/ auto-row-space row-autos)
column-tracks (cond-> column-tracks
(= :stretch (:layout-justify-content parent))

View File

@@ -29,6 +29,7 @@
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.types.text :as cttx]
[app.common.types.token :as cto]
[app.common.types.typography :as cty]
[app.common.types.variant :as ctv]
@@ -1664,28 +1665,33 @@
:shapes all-parents})]))))
(defn- text-partial-change-value
[touched-content untouched-content touched]
(cond
(touched :text-content-structure-same-attrs)
(if (touched :text-content-attribute)
;; Both structure and attrs has been touched, keep the
;; touched-content
touched-content
;; Keep the touched-content structure and texts, update
;; its attrs to make them like the untouched-content
(cttx/copy-attrs-keys touched-content (cttx/get-first-paragraph-text-attrs untouched-content)))
(touched :text-content-text)
;; Keep the texts touched in touched-content, so copy the
;; texts from touched-content into untouched-content
(cttx/copy-text-keys touched-content untouched-content)
(touched :text-content-attribute)
;; Keep the attrs touched in touched-content, so copy the
;; texts from untouched-content into touched-content
(cttx/copy-text-keys untouched-content touched-content)))
(defn- add-update-attr-operations
[attr dest-shape origin-shape roperations uoperations touched]
(let [orig-value (get origin-shape attr)
dest-value (get dest-shape attr)
;; position-data is a special case because can be affected by :geometry-group and :content-group
;; so, if the position-data changes but the geometry is touched we need to reset the position-data
;; so it's calculated again
reset-pos-data?
(and (cfh/text-shape? origin-shape)
(= attr :position-data)
(not= orig-value dest-value)
(touched :geometry-group))
val (cond
;; If position data changes and the geometry group is touched
;; we need to put to nil so we can regenerate it
reset-pos-data? nil
:else orig-value)
roperation {:type :set
[attr dest-shape roperations uoperations attr-val]
(let [roperation {:type :set
:attr attr
:val val
:val attr-val
:ignore-touched true}
uoperation {:type :set
:attr attr
@@ -1694,6 +1700,33 @@
[(conj roperations roperation)
(conj uoperations uoperation)]))
(defn- is-text-partial-change?
"Check if the attr update is a text partial change"
[untouched-shape touched-shape]
(let [touched (get touched-shape :touched #{})
partial-text-keys [:text-content-attribute :text-content-text]
active-keys (filter touched partial-text-keys)
untouched-content (:content untouched-shape)
untouched-attrs (cttx/get-first-paragraph-text-attrs untouched-content)
eq-untouched-attrs? (cttx/equal-attrs? untouched-content untouched-attrs)]
(and
(or
;; One and only one of the keys is pressent
(= 1 (count active-keys))
(and
(not (touched :text-content-attribute))
(touched :text-content-structure-same-attrs)))
(or
;; Both has the same structure
(cttx/equal-structure? untouched-content (:content touched-shape))
;; The origin and destiny have different structures, but each have the same attrs
;; for all the items on its content tree
(and
eq-untouched-attrs?
(touched :text-content-structure-same-attrs))))))
(defn- update-attrs
"The main function that implements the attribute sync algorithm. Copy
attributes that have changed in the origin shape to the dest shape.
@@ -1737,58 +1770,166 @@
(generate-update-tokens container dest-shape origin-shape touched omit-touched?))
(let [attr-group (get ctk/sync-attrs attr)
skip-operations? (or (= (get origin-shape attr) (get dest-shape attr))
(and (touched attr-group)
omit-touched?))
;; position-data is a special case because can be affected by
;; :geometry-group and :content-group so, if the position-data
;; changes but the geometry is touched we need to reset the position-data
;; so it's calculated again
reset-pos-data? (and (cfh/text-shape? origin-shape)
(= attr :position-data)
(not= (:position-data origin-shape) (:position-data dest-shape))
(touched :geometry-group))
;; On texts, when we want to omit the touched attrs, both text (the actual letters)
;; and attrs (bold, font, etc) are in the same attr :content.
;; If only one of them is touched, we want to adress this case and
;; only update the untouched one
text-partial-change?
(when (and
omit-touched?
(cfh/text-shape? origin-shape)
(= :content attr)
(touched attr-group))
(is-text-partial-change? origin-shape dest-shape))
skip-operations?
(or (= (get origin-shape attr) (get dest-shape attr))
(and (touched attr-group)
omit-touched?
;; When it is a text-partial-change, we should generate operations
;; even when omit-touched? is true, but updating only the text or
;; the attributes, omiting the other part
(not text-partial-change?)))
attr-val (when-not skip-operations?
(cond
;; If position data changes and the geometry group is touched
;; we need to put to nil so we can regenerate it
reset-pos-data?
nil
text-partial-change?
(text-partial-change-value (:content dest-shape)
(:content origin-shape)
touched)
:else
(get origin-shape attr)))
;; On a text-partial-change, we want to force a position-data reset
;; so it's calculated again
[roperations uoperations]
(if text-partial-change?
(add-update-attr-operations :position-data dest-shape roperations uoperations nil)
[roperations uoperations])
[roperations' uoperations']
(if skip-operations?
[roperations uoperations]
(add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched))]
(add-update-attr-operations attr dest-shape roperations uoperations attr-val))]
(recur (next attrs)
roperations'
uoperations')))))))
(defn update-attrs-on-switch
"Copy attributes that have changed in the origin shape to the dest shape. Used on variants switch"
[changes dest-shape origin-shape dest-root origin-root origin-ref-shape container]
"Copy attributes that have changed in the shape previous to the switch
to the current shape (post switch). Used only on variants switch"
;; NOTE: This function have similitudes but is very different to
;; update-attrs:
;; In components (update-attrs), the source shape is "clean", and the destination
;; shape may have touched elements that shouldn't be overwritten.
;; In variants (update-attrs-on-switch), the destination shape is "clean",
;; and it's the source shape that may have touched elements, and we only want
;; to copy those touched elements.
[changes current-shape previous-shape current-root prev-root origin-ref-shape container]
(let [;; We need to sync only the position relative to the origin of the component.
;; (see update-attrs for a full explanation)
origin-shape (reposition-shape origin-shape origin-root dest-root)
touched (get dest-shape :touched #{})
touched-origin (get origin-shape :touched #{})]
previous-shape (reposition-shape previous-shape prev-root current-root)
touched (get previous-shape :touched #{})]
(loop [attrs updatable-attrs
roperations [{:type :set-touched :touched (:touched origin-shape)}]
uoperations (list {:type :set-touched :touched (:touched dest-shape)})]
roperations [{:type :set-touched :touched (:touched previous-shape)}]
uoperations (list {:type :set-touched :touched (:touched current-shape)})]
(if-let [attr (first attrs)]
(let [attr-group (get ctk/sync-attrs attr)
skip-operations?
(or
;; If the attribute is not valid for the destiny, don't copy it
(not (cts/is-allowed-attr? attr (:type current-shape)))
;; If the values are already equal, don't copy them
(= (get previous-shape attr) (get current-shape attr))
;; If both variants (origin and destiny) don't have the same value
;; for that attribute, don't copy it.
;; Exceptions: :points :selrect and :content can be different
;;
;; Sample:
;; 1. We have a variant with C1 (bg red) and C2 (bg blue).
;; 2. We make a copy of C1 called Copy.
;; 3. We set Copys bg to green (so it it has an override on the bg).
;; 4. We switch Copy to use C2 as base.
;; 5. The bg of Copy now is blue (we ignore the override)
(and
(not (contains? #{:points :selrect :content} attr))
(not= (get origin-ref-shape attr) (get current-shape attr)))
;; The :content attr cant't be copied to elements of different type
(and (= attr :content) (not= (:type previous-shape) (:type current-shape)))
;; If the attr is not touched, don't copy it
(not (touched attr-group)))
;; On texts, both text (the actual letters)
;; and attrs (bold, font, etc) are in the same attr :content.
;; If only one of them is touched, we want to adress this case and
;; only update the untouched one
text-partial-change?
(when (and
(not skip-operations?)
(cfh/text-shape? current-shape)
(cfh/text-shape? previous-shape)
(= :content attr)
(touched attr-group))
(is-text-partial-change? current-shape previous-shape))
;; position-data is a special case because can be affected by :geometry-group and :content-group
;; so, if the position-data changes but the geometry is touched we need to reset the position-data
;; so it's calculated again
reset-pos-data? (and
(not skip-operations?)
(cfh/text-shape? previous-shape)
(= attr :position-data)
(not= (:position-data previous-shape) (:position-data current-shape))
(touched :geometry-group))
attr-val (when-not skip-operations?
(cond
;; If position data changes and the geometry group is touched
;; we need to put to nil so we can regenerate it
reset-pos-data?
nil
text-partial-change?
(text-partial-change-value (:content previous-shape)
(:content current-shape)
touched)
:else
(get previous-shape attr)))
[roperations' uoperations']
(if (or
;; If the attribute is not valid for the destiny, don't copy it
(not (cts/is-allowed-attr? attr (:type dest-shape)))
;; If the values are already equal, don't copy it
(= (get origin-shape attr) (get dest-shape attr))
;; If the referenced shape on the original component doesn't have the same value, don't copy it
;; Exceptions: :points :selrect and :content can be different
(and
(not (contains? #{:points :selrect :content} attr))
(not= (get origin-ref-shape attr) (get dest-shape attr)))
;; The :content attr cant't be copied to elements of different type
(and (= attr :content) (not= (:type origin-shape) (:type dest-shape)))
;; If the attr is not touched in the origin shape, don't copy it
(not (touched-origin attr-group)))
(if skip-operations?
[roperations uoperations]
(add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched))]
(add-update-attr-operations attr current-shape roperations uoperations attr-val))]
(recur (next attrs)
roperations'
uoperations'))
(cond-> changes
(> (count roperations) 1)
(add-update-attr-changes dest-shape container roperations uoperations)
(add-update-attr-changes current-shape container roperations uoperations)
:always
(generate-update-tokens container dest-shape origin-shape touched false))))))
(generate-update-tokens container current-shape previous-shape touched false))))))
(defn- propagate-attrs
"Helper that puts the origin attributes (attrs) into dest but only if
@@ -2115,7 +2256,7 @@
(pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values))
;; We need to set the same index as the original shape
(pcb/change-parent (:parent-id shape) [new-shape] index {:component-swap true
(pcb/change-parent (:parent-id shape) [new-shape] index {:allow-altering-copies true
:ignore-touched true})
(change-touched new-shape
shape
@@ -2123,10 +2264,21 @@
{}))]))
(defn generate-component-swap
[changes objects shape file page libraries id-new-component index target-cell keep-props-values]
(let [[all-parents changes]
[changes objects shape file page libraries id-new-component
index target-cell keep-props-values ignore-swapped?]
(let [;; When we keep the touched properties, we can't delete the
;; swapped children (we will keep them too)
ignore-swapped-fn
(if ignore-swapped?
#(-> (get objects %)
(ctk/get-swap-slot))
(constantly false))
[all-parents changes]
(-> changes
(cls/generate-delete-shapes file page objects (d/ordered-set (:id shape)) {:component-swap true}))
(cls/generate-delete-shapes
file page objects (d/ordered-set (:id shape))
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn}))
[new-shape changes]
(-> changes
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]

View File

@@ -16,19 +16,30 @@
[app.common.types.pages-list :as ctpl]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.types.text :as ctt]
[app.common.types.token :as cto]
[app.common.uuid :as uuid]))
[app.common.uuid :as uuid]
[clojure.set :as set]))
(defn- generate-unapply-tokens
"When updating attributes that have a token applied, we must unapply it, because the value
of the attribute now has been given directly, and does not come from the token."
[changes objects changed-sub-attr]
(let [mod-obj-changes (->> (:redo-changes changes)
(let [new-objects (pcb/get-objects changes)
mod-obj-changes (->> (:redo-changes changes)
(filter #(= (:type %) :mod-obj)))
text-changed-attrs
(fn [shape]
(let [new-shape (get new-objects (:id shape))
attrs (ctt/get-diff-attrs (:content shape) (:content new-shape))]
(apply set/union (map cto/shape-attr->token-attrs attrs))))
check-attr (fn [shape changes attr]
(let [tokens (get shape :applied-tokens {})
token-attrs (cto/shape-attr->token-attrs attr changed-sub-attr)]
token-attrs (if (or (not= (:type shape) :text) (not= attr :content))
(cto/shape-attr->token-attrs attr changed-sub-attr)
(text-changed-attrs shape))]
(if (some #(contains? tokens %) token-attrs)
(pcb/update-shapes changes [(:id shape)] #(cto/unapply-token-id % token-attrs))
changes)))
@@ -88,7 +99,14 @@
(pcb/with-library-data file))
ids
options))
([changes ids {:keys [ignore-touched component-swap]}]
([changes ids {:keys [ignore-touched
allow-altering-copies
;; We will delete the shapes and its descendants.
;; ignore-children-fn is used to ignore some descendants
;; on the deletion process. It should receive a shape and
;; return a boolean
ignore-children-fn]
:or {ignore-children-fn (constantly false)}}]
(let [objects (pcb/get-objects changes)
data (pcb/get-library-data changes)
page-id (pcb/get-page-id changes)
@@ -101,11 +119,12 @@
;; Look for shapes that are inside a component copy, but are
;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily).
;; Unless we are doing a component swap, in which case we want
;; If we want to specifically allow altering the copies, this is
;; a special case, like a component swap, in which case we want
;; to delete the old shape
(let [shape (get objects shape-id)]
(and (ctn/has-any-copy-parent? objects shape)
(not component-swap))))
(not allow-altering-copies))))
[ids-to-delete ids-to-hide]
(loop [ids-seq (seq ids)
@@ -166,10 +185,15 @@
(d/ordered-set)
(concat ids-to-delete ids-to-hide))
all-children
(->> ids-to-delete ;; Children of deleted shapes must be also deleted.
;; Descendants of deleted shapes must be also deleted,
;; except the ignored ones by the function ignore-children-fn
descendants-to-delete
(->> ids-to-delete
(reduce (fn [res id]
(into res (cfh/get-children-ids objects id)))
(into res (cfh/get-children-ids
objects
id
{:ignore-children-fn ignore-children-fn})))
[])
(reverse)
(into (d/ordered-set)))
@@ -189,9 +213,10 @@
empty-parents
;; Any parent whose children are all deleted, must be deleted too.
;; Unless we are during a component swap: in this case we are replacing a shape by
;; If we want to specifically allow altering the copies, this is a special case,
;; for example during a component swap. in this case we are replacing a shape by
;; other one, so must not delete empty parents.
(if-not component-swap
(if-not allow-altering-copies
(into (d/ordered-set) (find-all-empty-parents #{}))
#{})
@@ -203,7 +228,7 @@
(conj components (:component-id shape))
components)))
[]
(into ids-to-delete all-children))
(into ids-to-delete descendants-to-delete))
ids-set (set ids-to-delete)
@@ -230,7 +255,7 @@
changes (-> changes
(generate-update-shape-flags ids-to-hide objects {:hidden true})
(pcb/remove-objects all-children {:ignore-touched true})
(pcb/remove-objects descendants-to-delete {:ignore-touched true})
(pcb/remove-objects ids-to-delete {:ignore-touched ignore-touched})
(pcb/remove-objects empty-parents)
(pcb/resize-parents all-parents)

View File

@@ -41,7 +41,7 @@
[group-path tokens-lib tokens-lib-theme]
(let [deactivate? (contains? #{:all :partial} (ctob/sets-at-path-all-active? tokens-lib group-path))
sets-names (->> (ctob/get-sets-at-path tokens-lib group-path)
(map :name)
(map ctob/get-name)
(into #{}))]
(if deactivate?
(ctob/disable-sets tokens-lib-theme sets-names)

View File

@@ -18,7 +18,12 @@
[changes variant-id pos new-name]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)]
related-components (cfv/find-variant-components data objects variant-id)
props (-> related-components last :variant-properties)
prop-names (mapv :name props)
prop-names (concat (subvec prop-names 0 pos) (subvec prop-names (inc pos)))
new-name (ctv/update-number-in-repeated-item prop-names new-name)]
(reduce (fn [changes component]
(pcb/update-component
changes (:id component)
@@ -81,6 +86,9 @@
next-prop-num (ctv/next-property-number props)
property-name (or property-name (str ctv/property-prefix next-prop-num))
prop-names (mapv :name props)
property-name (ctv/update-number-in-repeated-item prop-names property-name)
[_ changes]
(reduce (fn [[num changes] component]
(let [main-id (:main-instance-id component)

View File

@@ -1,13 +1,17 @@
(ns app.common.logic.variants
(:require
[app.common.data :as d]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.variant :as cfv]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.logic.variant-properties :as clvp]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.variant :as ctv]))
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]))
(defn generate-add-new-variant
[changes shape variant-id new-component-id new-shape-id prop-num]
@@ -62,29 +66,142 @@
shapes))))
(defn- keep-swapped-item
"As part of the keep-touched process on a switch, given a child on the original
copy that was swapped (orig-swapped-child), and its related shape on the new copy
(related-shape-in-new), move the orig-swapped-child into the parent of
related-shape-in-new, fix its swap-slot if needed, and then delete
related-shape-in-new"
[changes related-shape-in-new orig-swapped-child ldata page swap-ref-id]
(let [;; Before to the swap, temporary move the previous
;; shape to the root panel to avoid problems when
;; the previous parent is deleted.
before-changes (-> (pcb/empty-changes)
(pcb/with-page page)
(pcb/with-objects (:objects page))
(pcb/change-parent uuid/zero [orig-swapped-child] 0 {:allow-altering-copies true}))
objects (pcb/get-objects changes)
prev-swap-slot (ctk/get-swap-slot orig-swapped-child)
current-parent (get objects (:parent-id related-shape-in-new))
pos (d/index-of (:shapes current-parent) (:id related-shape-in-new))]
(-> (pcb/concat-changes before-changes changes)
;; Move the previous shape to the new parent
(pcb/change-parent (:parent-id related-shape-in-new) [orig-swapped-child] pos {:allow-altering-copies true})
;; We need to update the swap slot only when it pointed
;; to the swap-ref-id. Oterwise this is a swapped item
;; inside a nested copy, so we need to keep it.
(cond->
(= prev-swap-slot swap-ref-id)
(pcb/update-shapes
[(:id orig-swapped-child)]
#(ctk/set-swap-slot % (:shape-ref related-shape-in-new))))
;; Delete new non-swapped item
(cls/generate-delete-shapes ldata page objects (d/ordered-set (:id related-shape-in-new)) {:allow-altering-copies true})
second)))
(defn- child-of-swapped?
"Check if any ancestor of a shape (between base-parent-id and shape) was swapped"
[shape objects base-parent-id]
(let [ancestors (->> (ctn/get-parent-heads objects shape)
;; Ignore ancestors ahead of base-parent
(drop-while #(not= base-parent-id (:id %)))
seq)
num-ancestors (count ancestors)
;; Ignore first and last (base-parent and shape)
ancestors (when (and ancestors (<= 3 num-ancestors))
(subvec (vec ancestors) 1 (dec num-ancestors)))]
(some ctk/get-swap-slot ancestors)))
(defn generate-keep-touched
[changes new-shape original-shape original-shapes page libraries]
"This is used as part of the switch process, when you switch from
an original-shape to a new-shape. It generate changes to
copy the touched attributes on the shapes children of the original-shape
into the related children of the new-shape.
This relation is tricky. The shapes are related if:
* On the main components, both have the same name (the name on the copies are ignored)
* Both has the same type of ancestors, on the same order (see generate-path for the
translation of the types)"
[changes new-shape original-shape original-shapes page libraries ldata]
(let [objects (pcb/get-objects changes)
orig-objects (into {} (map (juxt :id identity) original-shapes))
orig-shapes-w-path (add-unique-path
(reverse original-shapes)
orig-objects
(:id original-shape))
container (ctn/make-container page :page)
page-objects (:objects page)
;; Get the touched children of the original-shape
;; Ignore children of swapped items, because
;; they will be moved without change when
;; managing their swapped ancestor
orig-touched (->> (filter (comp seq :touched) original-shapes)
(remove
#(child-of-swapped? %
page-objects
(:id original-shape))))
;; Adds a :shape-path attribute to the children of the new-shape,
;; that contains the type of its ancestors and its name
new-shapes-w-path (add-unique-path
(reverse (cfh/get-children-with-self objects (:id new-shape)))
objects
(:id new-shape))
new-shapes-map (into {} (map (juxt :shape-path identity) new-shapes-w-path))
orig-touched (filter (comp seq :touched) orig-shapes-w-path)
;; Creates a map to quickly find a child of the new-shape by its shape-path
new-shapes-map (into {} (map (juxt :shape-path identity)) new-shapes-w-path)
container (ctn/make-container page :page)]
;; The original-shape is in a copy. For the relation rules, we need the referenced
;; shape on the main component
orig-ref-shape (ctf/find-ref-shape nil container libraries original-shape)
orig-ref-objects (-> (ctf/get-component-container-from-head orig-ref-shape libraries)
:objects)
;; Adds a :shape-path attribute to the children of the orig-ref-shape,
;; that contains the type of its ancestors and its name
o-ref-shapes-wp (add-unique-path
(reverse (cfh/get-children-with-self orig-ref-objects (:id orig-ref-shape)))
orig-ref-objects
(:id orig-ref-shape))
;; Creates a map to quickly find a child of the orig-ref-shape by its shape-path
o-ref-shapes-p-map (into {} (map (juxt :id :shape-path)) o-ref-shapes-wp)]
;; Process each touched children of the original-shape
(reduce
(fn [changes touched-shape]
(let [related-shape (get new-shapes-map (:shape-path touched-shape))
orig-ref-shape (ctf/find-ref-shape nil container libraries touched-shape)]
(if related-shape
(cll/update-attrs-on-switch
changes related-shape touched-shape new-shape original-shape orig-ref-shape container)
(fn [changes orig-child-touched]
(let [;; If the orig-child-touched was swapped, get its swap-slot
swap-slot (ctk/get-swap-slot orig-child-touched)
;; orig-child-touched is in a copy. Get the referenced shape on the main component
;; If there is a swap slot, we will get the referenced shape in another way
orig-ref-shape (when-not swap-slot
;; TODO Maybe just get it from o-ref-shapes-wp
(ctf/find-ref-shape nil container libraries orig-child-touched))
orig-ref-id (if swap-slot
;; If there is a swap slot, find the referenced shape id
(ctf/find-ref-id-for-swapped orig-child-touched container libraries)
;; If there is not a swap slot, get the id from the orig-ref-shape
(:id orig-ref-shape))
;; Get the shape path of the referenced main
shape-path (get o-ref-shapes-p-map orig-ref-id)
;; Get its related shape in the children of new-shape: the one that
;; has the same shape-path
related-shape-in-new (get new-shapes-map shape-path)]
;; If there is a related shape, keep its data
(if related-shape-in-new
(if swap-slot
;; If the orig-child-touched was swapped, keep it
(keep-swapped-item changes related-shape-in-new orig-child-touched
ldata page orig-ref-id)
;; If the orig-child-touched wasn't swapped, copy
;; the touched attributes into it
(cll/update-attrs-on-switch
changes related-shape-in-new orig-child-touched
new-shape original-shape orig-ref-shape container))
changes)))
changes
orig-touched)))

View File

@@ -156,7 +156,7 @@
[new_shape _ changes]
(-> (pcb/empty-changes nil (:id page))
(cll/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values))
(cll/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values false))
file' (thf/apply-changes file changes)]

View File

@@ -37,8 +37,6 @@
(merge shape
text-params))))
(defn add-frame
[file frame-label & {:keys [] :as params}]
;; Generated shape tree:
@@ -293,7 +291,8 @@
:id)
0
nil
{})
{}
false)
file' (thf/apply-changes file changes)]
(if propagate-fn

View File

@@ -11,6 +11,7 @@
[app.common.files.helpers :as cfh]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.text :as txt]
[app.common.types.color :as ctc]
[app.common.types.container :as ctn]
[app.common.types.pages-list :as ctpl]
@@ -81,6 +82,21 @@
(:id page)
#(ctst/set-shape % (ctn/set-shape-attr shape attr val)))))))
(defn update-shape-text
[file shape-label attr val & {:keys [page-label]}]
(let [page (if page-label
(thf/get-page file page-label)
(thf/current-page file))
shape (ctst/get-shape page (thi/id shape-label))]
(update file :data
(fn [file-data]
(ctpl/update-page file-data
(:id page)
#(ctst/set-shape % (txt/update-text-content shape
txt/is-content-node?
d/txt-merge
{attr val})))))))
(defn sample-library-color
[label & {:keys [name path color opacity gradient image]}]
(-> {:id (thi/new-id! label)

View File

@@ -130,8 +130,8 @@
(defn is-text-node?
[node]
(and (string? (:text node))
(not= (:text node) "")))
(and (nil? (:type node))
(string? (:text node))))
(defn is-paragraph-set-node?
[node]
@@ -170,19 +170,6 @@
item))
root)))
(defn xform-nodes
"The same as transform but instead of receiving a funcion, receives
a transducer."
[xf root]
(let [rf (fn [_ v] v)]
(walk/postwalk
(fn [item]
(let [rf (xf rf)]
(if (is-node? item)
(d/nilv (rf nil item) item)
item)))
root)))
(defn update-text-content
[shape pred-fn update-fn attrs]
(let [update-attrs-fn #(update-fn % attrs)

View File

@@ -22,7 +22,7 @@
;; SCHEMAS & TYPES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def valid-color-attrs
(def ^:private required-color-attrs
"A set used for proper check if color should contain only one of the
attrs listed in this set."
#{:image :gradient :color})
@@ -31,7 +31,7 @@
"Check if color has correct color attrs"
[color]
(let [attrs (set (keys color))
result (set/intersection attrs valid-color-attrs)]
result (set/intersection attrs required-color-attrs)]
(= 1 (count result))))
(def ^:private hex-color-rx
@@ -117,6 +117,7 @@
[:ref-id {:optional true} ::sm/uuid]
[:ref-file {:optional true} ::sm/uuid]])
;; This schema represent an "applied color"
(def schema:color
[:and
[:merge {:title "Color"}
@@ -126,6 +127,9 @@
(sm/optional-keys schema:image-color)]
[:fn has-valid-color-attrs?]])
(def color-attrs
(into required-color-attrs (sm/keys schema:color-attrs)))
(def schema:library-color-attrs
[:map {:title "ColorAttrs" :closed true}
[:id ::sm/uuid]
@@ -147,6 +151,9 @@
(sm/optional-keys schema:image-color)]
[:fn has-valid-color-attrs?]])
(def library-color-attrs
(into required-color-attrs (sm/keys schema:library-color-attrs)))
(def valid-color?
(sm/lazy-validator schema:color))

View File

@@ -18,6 +18,7 @@
[app.common.types.plugins :as ctpg]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.common.types.text :as cttx]
[app.common.types.token :as ctt]
[app.common.uuid :as uuid]
[clojure.set :as set]))
@@ -569,13 +570,16 @@
(not equal?)
(not (and ignore-geometry is-geometry?)))
content-diff-type (when (and (= (:type shape) :text) (= attr :content))
(cttx/get-diff-type (:content shape) val))
token-groups (if (= attr :applied-tokens)
(get-token-groups shape val)
#{})
groups (cond-> token-groups
(and group (not equal?))
(set/union #{group}))]
(set/union #{group} content-diff-type))]
(cond-> shape
;; Depending on the origin of the attribute change, we need or not to
;; set the "touched" flag for the group the attribute belongs to.

View File

@@ -242,6 +242,13 @@
(cfh/make-container component-page :page))
(cfh/make-container component :component)))
(defn get-component-container-from-head
[instance-head libraries & {:keys [include-deleted?] :or {include-deleted? true}}]
(let [library-data (-> (get-component-library libraries instance-head)
:data)
component (ctkl/get-component library-data (:component-id instance-head) include-deleted?)]
(get-component-container library-data component)))
(defn get-component-root
"Retrieve the root shape of the component."
[file-data component]
@@ -390,6 +397,47 @@
(or (= slot-main slot-inst)
(= (:id shape-main) slot-inst)))))
(defn- find-next-related-swap-shape-id
"Go up from the chain of references shapes that will eventually lead to the shape
with swap-slot-id as id. Returns the next shape on the chain"
[parent swap-slot-id libraries]
(let [container (get-component-container-from-head parent libraries)
objects (:objects container)
children (cfh/get-children objects (:id parent))
original-shape-id (->> children
(filter #(= swap-slot-id (:id %)))
first
:id)]
(if original-shape-id
;; Return the children which id is the swap-slot-id
original-shape-id
;; No children with swap-slot-id as id, go up
(let [referenced-shape (find-ref-shape nil container libraries parent)
;; Recursive call that will get the id of the next shape on
;; the chain that ends on a shape with swap-slot-id as id
next-shape-id (when referenced-shape
(find-next-related-swap-shape-id referenced-shape swap-slot-id libraries))]
;; Return the children which shape-ref points to the next-shape-id
(->> children
(filter #(= next-shape-id (:shape-ref %)))
first
:id)))))
(defn find-ref-id-for-swapped
"When a shape has been swapped, find the original ref-id that the shape had
before the swap"
[shape container libraries]
(let [swap-slot (ctk/get-swap-slot shape)
objects (:objects container)
parent (get objects (:parent-id shape))
parent-head (ctn/get-head-shape objects parent)
parent-ref (find-ref-shape nil container libraries parent-head)]
(when (and swap-slot parent-ref)
(find-next-related-swap-shape-id parent-ref swap-slot libraries))))
(defn get-component-shapes
"Retrieve all shapes of the component"
[file-data component]

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cpf]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
@@ -26,6 +27,9 @@
(def ^:const bool-style-properties bool/style-properties)
(def ^:const default-bool-fills bool/default-fills)
(def schema:content impl/schema:content)
(def schema:segments impl/schema:segments)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TRANSFORMATIONS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -48,9 +52,9 @@
[data]
(impl/from-string data))
(defn check-path-content
(defn check-content
[content]
(impl/check-content-like content))
(impl/check-content content))
(defn get-byte-size
"Get byte size of a path content"
@@ -76,7 +80,7 @@
(defn apply-content-modifiers
"Apply delta modifiers over the path content"
[content modifiers]
(assert (impl/check-content-like content))
(assert (impl/check-content content))
(letfn [(apply-to-index [content [index params]]
(if (contains? content index)
@@ -104,36 +108,11 @@
(impl/path-data
(reduce apply-to-index (vec content) modifiers))))
(defn- transform-content-legacy
[content transform]
(if (some? transform)
(let [set-tr
(fn [params px py]
(let [tr-point (-> (gpt/point (get params px) (get params py))
(gpt/transform transform))]
(assoc params
px (:x tr-point)
py (:y tr-point))))
transform-params
(fn [{:keys [x c1x c2x] :as params}]
(cond-> params
(some? x) (set-tr :x :y)
(some? c1x) (set-tr :c1x :c1y)
(some? c2x) (set-tr :c2x :c2y)))]
(into []
(map #(update % :params transform-params))
content))
content))
(defn transform-content
"Applies a transformation matrix over content and returns a new
content as PathData instance."
[content transform]
#_(segment/transform-content content transform)
(some-> (transform-content-legacy (vec content) transform)
(impl/from-plain)))
(segment/transform-content content transform))
(defn move-content
[content move-vec]
@@ -221,7 +200,18 @@
contents
(sequence extract-content-xf (:shapes shape))]
(bool/calculate-content (:bool-type shape) contents)))
(ex/try!
(bool/calculate-content (:bool-type shape) contents)
:on-exception
(fn [cause]
(ex/raise :type :internal
:code :invalid-path-content
:hint (str "unable to calculate bool content for shape " (:id shape))
:shapes (:shapes shape)
:type (:bool-type shape)
:content (vec contents)
:cause cause)))))
(defn calc-bool-content
"Calculate the boolean content from shape and objects. Returns a

View File

@@ -412,7 +412,7 @@
(defn calculate-content
"Create a bool content from a collection of contents and specified
type."
type. Returns plain segments"
[bool-type contents]
;; We apply the boolean operation in to each pair and the result to the next
;; element

View File

@@ -27,13 +27,11 @@
(defn make-move-to [to]
{:command :move-to
:relative false
:params {:x (:x to)
:y (:y to)}})
(defn make-line-to [to]
{:command :line-to
:relative false
:params {:x (:x to)
:y (:y to)}})
@@ -65,7 +63,6 @@
(defn make-curve-to
[to h1 h2]
{:command :curve-to
:relative false
:params (make-curve-params to h1 h2)})
(defn prefix->coords [prefix]
@@ -98,7 +95,7 @@
(defn segment->point
([segment] (segment->point segment :x))
([segment coord]
(let [params (get segment :params)]
(when-let [params (not-empty (get segment :params))]
(case coord
:c1 (gpt/point (get params :c1x)
(get params :c1y))

View File

@@ -20,7 +20,8 @@
[app.common.schema.generators :as sg]
[app.common.svg.path :as svg.path]
[app.common.transit :as t]
[app.common.types.path :as-alias path])
[app.common.types.path :as-alias path]
[cuerdas.core :as str])
(:import
#?(:cljs [goog.string StringBuffer]
:clj [java.nio ByteBuffer ByteOrder])))
@@ -65,34 +66,34 @@
(let [t (buf/read-short buffer offset)]
(case t
(1 2)
(let [x (buf/read-float buffer (+ offset 20))
y (buf/read-float buffer (+ offset 24))
x (+ (* x a) (* y c) e)
y (+ (* x b) (* y d) f)]
(buf/write-float buffer (+ offset 20) x)
(buf/write-float buffer (+ offset 24) y))
(let [x (buf/read-float buffer (+ offset 20))
y (buf/read-float buffer (+ offset 24))
x' (+ (* x a) (* y c) e)
y' (+ (* x b) (* y d) f)]
(buf/write-float buffer (+ offset 20) x')
(buf/write-float buffer (+ offset 24) y'))
3
(let [c1x (buf/read-float buffer (+ offset 4))
c1y (buf/read-float buffer (+ offset 8))
c2x (buf/read-float buffer (+ offset 12))
c2y (buf/read-float buffer (+ offset 16))
x (buf/read-float buffer (+ offset 20))
y (buf/read-float buffer (+ offset 24))
(let [c1x (buf/read-float buffer (+ offset 4))
c1y (buf/read-float buffer (+ offset 8))
c2x (buf/read-float buffer (+ offset 12))
c2y (buf/read-float buffer (+ offset 16))
x (buf/read-float buffer (+ offset 20))
y (buf/read-float buffer (+ offset 24))
c1x (+ (* c1x a) (* c1y c) e)
c1y (+ (* c1x b) (* c1y d) f)
c2x (+ (* c2x a) (* c2y c) e)
c2y (+ (* c2x b) (* c2y d) f)
x (+ (* x a) (* y c) e)
y (+ (* x b) (* y d) f)]
c1x' (+ (* c1x a) (* c1y c) e)
c1y' (+ (* c1x b) (* c1y d) f)
c2x' (+ (* c2x a) (* c2y c) e)
c2y' (+ (* c2x b) (* c2y d) f)
x' (+ (* x a) (* y c) e)
y' (+ (* x b) (* y d) f)]
(buf/write-float buffer (+ offset 4) c1x)
(buf/write-float buffer (+ offset 8) c1y)
(buf/write-float buffer (+ offset 12) c2x)
(buf/write-float buffer (+ offset 16) c2y)
(buf/write-float buffer (+ offset 20) x)
(buf/write-float buffer (+ offset 24) y))
(buf/write-float buffer (+ offset 4) c1x')
(buf/write-float buffer (+ offset 8) c1y')
(buf/write-float buffer (+ offset 12) c2x')
(buf/write-float buffer (+ offset 16) c2y')
(buf/write-float buffer (+ offset 20) x')
(buf/write-float buffer (+ offset 24) y'))
nil)))
@@ -507,18 +508,6 @@
(= (:command e1) :move-to))))}
schema:segment])
(def schema:content-like
[:sequential schema:segment])
(def check-content-like
(sm/check-fn schema:content-like))
(def check-segment
(sm/check-fn schema:segment))
(def ^:private check-segments
(sm/check-fn schema:segments))
(defn path-data?
[o]
(instance? PathData o))
@@ -526,37 +515,42 @@
(declare from-string)
(declare from-plain)
;; Mainly used on backend: features/components_v2.clj
(sm/register! ::path/segment schema:segment)
(sm/register! ::path/segments schema:segments)
(def schema:content
(sm/type-schema
{:type ::path/content
:compile
(fn [_ _ _]
(let [decoder (delay (sm/decoder schema:segments sm/json-transformer))
generator (->> (sg/generator schema:segments)
(sg/filter not-empty)
(sg/fmap from-plain))]
{:pred path-data?
:type-properties
{:gen/gen generator
:encode/json identity
:decode/json (fn [s]
(cond
(string? s)
(if (str/empty? s)
(from-plain [])
(from-string s))
(sm/register!
{:type ::path/content
:compile
(fn [_ _ _]
(let [decoder (delay (sm/decoder schema:segments sm/json-transformer))
generator (->> (sg/generator schema:segments)
(sg/filter not-empty)
(sg/fmap from-plain))]
{:pred path-data?
:type-properties
{:gen/gen generator
:encode/json identity
:decode/json (fn [s]
(cond
(string? s)
(from-string s)
(vector? s)
(let [decode-fn (deref decoder)]
(-> (decode-fn s)
(from-plain)))
(vector? s)
(let [decode-fn (deref decoder)]
(-> (decode-fn s)
(from-plain)))
:else
s))}}))}))
:else
s))}}))})
(def check-plain-content
(sm/check-fn schema:segments))
(def check-path-content
(sm/check-fn ::path/content))
(def check-segment
(sm/check-fn schema:segment))
(def check-content
(sm/check-fn schema:content))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CONSTRUCTORS & PREDICATES
@@ -617,7 +611,7 @@
(defn from-plain
"Create a PathData instance from plain data structures"
[segments]
(assert (check-segments segments))
(assert (check-plain-content segments))
(let [total (count segments)
buffer (buf/allocate (* total SEGMENT-BYTE-SIZE))]

View File

@@ -363,7 +363,7 @@
(defn make-curve-point
"Changes the content to make the point a 'curve'. The handlers will be
positioned in the same vector that results from the previous->next
points but with fixed length."
points but with fixed length; return a plain segments vector"
[content point]
(let [;; We perform this operation before because it can be
@@ -372,17 +372,21 @@
indices
(point-indices content point)
;; We transform content to a plain format for execute the
;; algorithm because right now is the only way to execute it
content
(vec content)
vectors
(map (fn [index]
(let [segment (nth content index)
prev-i (dec index)
prev (when (not (= :move-to (:command segment)))
(get content prev-i))
next-i (inc index)
next (get content next-i)
next (when (not (= :move-to (:command next)))
next)]
(let [segment (get content index)
prev-i (dec index)
prev (when (not (= :move-to (:command segment)))
(get content prev-i))
next-i (inc index)
next (get content next-i)
next (when (not (= :move-to (:command next)))
next)]
{:index index
:prev-i (when (some? prev) prev-i)
:prev-c prev
@@ -394,81 +398,73 @@
indices)
points
(into #{} xf:mapcat-points vectors)
(into #{} xf:mapcat-points vectors)]
;; We transform content to a plain format for execute the
;; algorithm because right now is the only way to execute it
content
(vec content)
(if (= (count points) 2)
(let [[fpoint spoint] (vec points)
v1 (gpt/to-vec fpoint point)
v2 (gpt/to-vec fpoint spoint)
vp (gpt/project v1 v2)
vh (gpt/subtract v1 vp)
content
(if (= (count points) 2)
(let [[fpoint spoint] (vec points)
v1 (gpt/to-vec fpoint point)
v2 (gpt/to-vec fpoint spoint)
vp (gpt/project v1 v2)
vh (gpt/subtract v1 vp)
add-curve
(fn [content {:keys [index prev-p next-p next-i]}]
(let [curr-segment (get content index)
curr-command (get curr-segment :command)
add-curve
(fn [content {:keys [index prev-p next-p next-i]}]
(let [curr-segment (get content index)
curr-command (get curr-segment :command)
next-segment (get content next-i)
next-command (get next-segment :command)
next-segment (get content next-i)
next-command (get next-segment :command)
;; New handlers for prev-point and next-point
prev-h
(when (some? prev-p) (gpt/add prev-p vh))
;; New handlers for prev-point and next-point
prev-h
(when (some? prev-p) (gpt/add prev-p vh))
next-h
(when (some? next-p) (gpt/add next-p vh))
next-h
(when (some? next-p) (gpt/add next-p vh))
;; Correct 1/3 to the point improves the curve
prev-correction
(when (some? prev-h) (gpt/scale (gpt/to-vec prev-h point) (/ 1 3)))
;; Correct 1/3 to the point improves the curve
prev-correction
(when (some? prev-h) (gpt/scale (gpt/to-vec prev-h point) (/ 1 3)))
next-correction
(when (some? next-h) (gpt/scale (gpt/to-vec next-h point) (/ 1 3)))
next-correction
(when (some? next-h) (gpt/scale (gpt/to-vec next-h point) (/ 1 3)))
prev-h
(when (some? prev-h) (gpt/add prev-h prev-correction))
prev-h
(when (some? prev-h) (gpt/add prev-h prev-correction))
next-h
(when (some? next-h) (gpt/add next-h next-correction))]
next-h
(when (some? next-h) (gpt/add next-h next-correction))]
(cond-> content
(and (= :line-to curr-command) (some? prev-p))
(update index helpers/update-curve-to prev-p prev-h)
(cond-> content
(and (= :line-to curr-command) (some? prev-p))
(update index helpers/update-curve-to prev-p prev-h)
(and (= :line-to next-command) (some? next-p))
(update next-i helpers/update-curve-to next-h next-p)
(and (= :line-to next-command) (some? next-p))
(update next-i helpers/update-curve-to next-h next-p)
(and (= :curve-to curr-command) (some? prev-p))
(update index update-handler :c2 prev-h)
(and (= :curve-to curr-command) (some? prev-p))
(update index update-handler :c2 prev-h)
(and (= :curve-to next-command) (some? next-p))
(update next-i update-handler :c1 next-h))))]
(and (= :curve-to next-command) (some? next-p))
(update next-i update-handler :c1 next-h))))]
(reduce add-curve content vectors))
(reduce add-curve content vectors))
(let [add-curve
(fn [content {:keys [index segment prev-p next-c next-i]}]
(cond-> content
(= :line-to (:command segment))
(update index #(line->curve prev-p %))
(let [add-curve
(fn [content {:keys [index segment prev-p next-c next-i]}]
(cond-> content
(= :line-to (:command segment))
(update index #(line->curve prev-p %))
(= :curve-to (:command segment))
(update index #(line->curve prev-p %))
(= :curve-to (:command segment))
(update index #(line->curve prev-p %))
(= :line-to (:command next-c))
(update next-i #(line->curve point %))
(= :line-to (:command next-c))
(update next-i #(line->curve point %))
(= :curve-to (:command next-c))
(update next-i #(line->curve point %))))]
(reduce add-curve content vectors)))]
(impl/from-plain content)))
(= :curve-to (:command next-c))
(update next-i #(line->curve point %))))]
(reduce add-curve content vectors)))))
(defn get-segments-with-points
"Given a content and a set of points return all the segments in the path
@@ -628,27 +624,38 @@
(rest content))))))))
(defn join-nodes
"Creates new segments between points that weren't previously"
"Creates new segments between points that weren't previously.
Returns plain segments vector."
[content points]
(let [segments-set (into #{}
(map (juxt :start :end))
(get-segments-with-points content points))
(let [;; Materialize the content to a vector (plain format)
content
(vec content)
create-line-command (fn [point other]
[(helpers/make-move-to point)
(helpers/make-line-to other)])
segments-set
(into #{}
(map (juxt :start :end))
(get-segments-with-points content points))
not-segment? (fn [point other] (and (not (contains? segments-set [point other]))
(not (contains? segments-set [other point]))))
create-line-segment
(fn [point other]
[(helpers/make-move-to point)
(helpers/make-line-to other)])
new-content (->> (d/map-perm create-line-command not-segment? points)
(flatten)
(into []))]
not-segment?
(fn [point other]
(and (not (contains? segments-set [point other]))
(not (contains? segments-set [other point]))))
;; FIXME: implement map-perm in terms of transducer, will
;; improve performance and remove the need to use flatten
new-content
(->> (d/map-perm create-line-segment not-segment? points)
(flatten)
(into []))]
(into content new-content)))
(defn separate-nodes
"Removes the segments between the points given"
[content points]

View File

@@ -246,7 +246,7 @@
[:map {:title "BoolAttrs"}
[:shapes [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]
[:bool-type [::sm/one-of bool-types]]
[:content ::path/content]])
[:content path/schema:content]])
(def ^:private schema:rect-attrs
[:map {:title "RectAttrs"}])
@@ -271,7 +271,7 @@
(def ^:private schema:path-attrs
[:map {:title "PathAttrs"}
[:content ::path/content]])
[:content path/schema:content]])
(def ^:private schema:text-attrs
[:map {:title "TextAttrs"}
@@ -313,7 +313,7 @@
:title "Shape"}
[:group
[:merge {:title "GroupShape"}
ctsl/schema:layout-attrs
ctsl/schema:layout-child-attrs
schema:group-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
@@ -321,8 +321,8 @@
[:frame
[:merge {:title "FrameShape"}
ctsl/schema:layout-child-attrs
ctsl/schema:layout-attrs
::ctsl/layout-attrs
schema:frame-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
@@ -332,14 +332,14 @@
[:bool
[:merge {:title "BoolShape"}
ctsl/schema:layout-attrs
ctsl/schema:layout-child-attrs
schema:bool-attrs
schema:shape-generic-attrs
schema:shape-base-attrs]]
[:rect
[:merge {:title "RectShape"}
ctsl/schema:layout-attrs
ctsl/schema:layout-child-attrs
schema:rect-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
@@ -347,7 +347,7 @@
[:circle
[:merge {:title "CircleShape"}
ctsl/schema:layout-attrs
ctsl/schema:layout-child-attrs
schema:circle-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
@@ -355,7 +355,7 @@
[:image
[:merge {:title "ImageShape"}
ctsl/schema:layout-attrs
ctsl/schema:layout-child-attrs
schema:image-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
@@ -363,7 +363,7 @@
[:svg-raw
[:merge {:title "SvgRawShape"}
ctsl/schema:layout-attrs
ctsl/schema:layout-child-attrs
schema:svg-raw-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
@@ -371,14 +371,14 @@
[:path
[:merge {:title "PathShape"}
ctsl/schema:layout-attrs
ctsl/schema:layout-child-attrs
schema:path-attrs
schema:shape-generic-attrs
schema:shape-base-attrs]]
[:text
[:merge {:title "TextShape"}
ctsl/schema:layout-attrs
ctsl/schema:layout-child-attrs
schema:text-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
@@ -682,23 +682,6 @@
:r3
:r4})
(def ^:private layout-extract-props
#{:layout
:layout-flex-dir
:layout-gap-type
:layout-gap
:layout-wrap-type
:layout-align-items
:layout-align-content
:layout-justify-items
:layout-justify-content
:layout-padding-type
:layout-padding
:layout-grid-dir
:layout-grid-rows
:layout-grid-columns
:layout-grid-cells})
(defn extract-props
"Retrieves an object with the 'pasteable' properties for a shape."
[shape]
@@ -729,9 +712,8 @@
(assoc-props node txt/text-node-attrs)))
props)))
(extract-layout-props
[props shape]
(d/patch-object props (select-keys shape layout-extract-props)))]
(extract-layout-attrs [props shape]
(d/patch-object props (select-keys shape ctsl/layout-attrs)))]
(let [;; For texts we don't extract the fill
extract-props
@@ -739,7 +721,7 @@
(-> shape
(select-keys extract-props)
(cond-> (cfh/text-shape? shape) (extract-text-props shape))
(cond-> (ctsl/any-layout? shape) (extract-layout-props shape))))))
(cond-> (ctsl/any-layout? shape) (extract-layout-attrs shape))))))
(defn patch-props
"Given the object of `extract-props` applies it to a shape. Adapt the shape if necesary"
@@ -764,7 +746,7 @@
(d/patch-object (select-keys props txt/text-node-attrs))))))))))
(patch-layout-props [shape props]
(let [shape (d/patch-object shape (select-keys props layout-extract-props))]
(let [shape (d/patch-object shape (select-keys props ctsl/layout-attrs))]
(cond-> shape
(ctsl/grid-layout? shape)
(ctsl/assign-cells objects))))]

View File

@@ -43,7 +43,6 @@
;; :layout-item-absolute ;; boolean
;; :layout-item-z-index ;; int
(def layout-types
#{:flex :grid})
@@ -74,49 +73,6 @@
(def justify-items-types
#{:start :end :center :stretch})
(def layout-item-props
[:layout-item-margin
:layout-item-margin-type
:layout-item-h-sizing
:layout-item-v-sizing
:layout-item-max-h
:layout-item-min-h
:layout-item-max-w
:layout-item-min-w
:layout-item-absolute
:layout-item-z-index])
(sm/register!
^{::sm/type ::layout-attrs}
[:map {:title "LayoutAttrs"}
[:layout {:optional true} [::sm/one-of layout-types]]
[:layout-flex-dir {:optional true} [::sm/one-of flex-direction-types]]
[:layout-gap {:optional true}
[:map
[:row-gap {:optional true} ::sm/safe-number]
[:column-gap {:optional true} ::sm/safe-number]]]
[:layout-gap-type {:optional true} [::sm/one-of gap-types]]
[:layout-wrap-type {:optional true} [::sm/one-of wrap-types]]
[:layout-padding-type {:optional true} [::sm/one-of padding-type]]
[:layout-padding {:optional true}
[:map
[:p1 ::sm/safe-number]
[:p2 ::sm/safe-number]
[:p3 ::sm/safe-number]
[:p4 ::sm/safe-number]]]
[:layout-justify-content {:optional true} [::sm/one-of justify-content-types]]
[:layout-justify-items {:optional true} [::sm/one-of justify-items-types]]
[:layout-align-content {:optional true} [::sm/one-of align-content-types]]
[:layout-align-items {:optional true} [::sm/one-of align-items-types]]
[:layout-grid-dir {:optional true} [::sm/one-of grid-direction-types]]
[:layout-grid-rows {:optional true}
[:vector {:gen/max 2} ::grid-track]]
[:layout-grid-columns {:optional true}
[:vector {:gen/max 2} ::grid-track]]
[:layout-grid-cells {:optional true}
[:map-of {:gen/max 5} ::sm/uuid ::grid-cell]]])
;; Grid types
(def grid-track-types
#{:percent :flex :auto :fixed})
@@ -130,29 +86,59 @@
(def grid-cell-justify-self-types
#{:auto :start :center :end :stretch})
(sm/register!
^{::sm/type ::grid-cell}
[:map {:title "GridCell"}
[:id ::sm/uuid]
[:area-name {:optional true} :string]
[:row ::sm/safe-int]
[:row-span ::sm/safe-int]
[:column ::sm/safe-int]
[:column-span ::sm/safe-int]
[:position {:optional true} [::sm/one-of grid-position-types]]
[:align-self {:optional true} [::sm/one-of grid-cell-align-self-types]]
[:justify-self {:optional true} [::sm/one-of grid-cell-justify-self-types]]
[:shapes
[:vector {:gen/max 1} ::sm/uuid]]])
(def ^:private schema:grid-cell
[:map {:title "GridCell"}
[:id ::sm/uuid]
[:area-name {:optional true} :string]
[:row ::sm/safe-int]
[:row-span ::sm/safe-int]
[:column ::sm/safe-int]
[:column-span ::sm/safe-int]
[:position {:optional true} [::sm/one-of grid-position-types]]
[:align-self {:optional true} [::sm/one-of grid-cell-align-self-types]]
[:justify-self {:optional true} [::sm/one-of grid-cell-justify-self-types]]
[:shapes
[:vector {:gen/max 1} ::sm/uuid]]])
(sm/register!
^{::sm/type ::grid-track}
[:map {:title "GridTrack"}
[:type [::sm/one-of grid-track-types]]
[:value {:optional true} [:maybe ::sm/safe-number]]])
(def ^:private schema:grid-track
[:map {:title "GridTrack"}
[:type [::sm/one-of grid-track-types]]
[:value {:optional true} [:maybe ::sm/safe-number]]])
(def check-grid-track!
(sm/check-fn ::grid-track))
(def schema:layout-attrs
[:map {:title "LayoutAttrs"}
[:layout {:optional true} [::sm/one-of layout-types]]
[:layout-flex-dir {:optional true} [::sm/one-of flex-direction-types]]
[:layout-gap {:optional true}
[:map
[:row-gap {:optional true} ::sm/safe-number]
[:column-gap {:optional true} ::sm/safe-number]]]
[:layout-gap-type {:optional true} [::sm/one-of gap-types]]
[:layout-wrap-type {:optional true} [::sm/one-of wrap-types]]
[:layout-padding-type {:optional true} [::sm/one-of padding-type]]
[:layout-padding {:optional true}
[:map
[:p1 ::sm/safe-number]
[:p2 ::sm/safe-number]
[:p3 ::sm/safe-number]
[:p4 ::sm/safe-number]]]
[:layout-justify-content {:optional true} [::sm/one-of justify-content-types]]
[:layout-justify-items {:optional true} [::sm/one-of justify-items-types]]
[:layout-align-content {:optional true} [::sm/one-of align-content-types]]
[:layout-align-items {:optional true} [::sm/one-of align-items-types]]
[:layout-grid-dir {:optional true} [::sm/one-of grid-direction-types]]
[:layout-grid-rows {:optional true}
[:vector {:gen/max 2} schema:grid-track]]
[:layout-grid-columns {:optional true}
[:vector {:gen/max 2} schema:grid-track]]
[:layout-grid-cells {:optional true}
[:map-of {:gen/max 5} ::sm/uuid schema:grid-cell]]])
(def ^:private check-grid-track
(sm/check-fn schema:grid-track))
(def layout-attrs
(sm/keys schema:layout-attrs))
;; LAYOUT CHILDREN
@@ -168,7 +154,7 @@
(def item-align-self-types
#{:start :end :center :stretch})
(def schema:layout-attrs
(def schema:layout-child-attrs
[:map {:title "LayoutChildAttrs"}
[:layout-item-margin-type {:optional true} [::sm/one-of item-margin-types]]
[:layout-item-margin {:optional true}
@@ -187,6 +173,9 @@
[:layout-item-absolute {:optional true} :boolean]
[:layout-item-z-index {:optional true} ::sm/safe-number]])
(def layout-child-attrs
(sm/keys schema:layout-child-attrs))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMAS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -194,8 +183,6 @@
(def valid-layouts
#{:flex :grid})
(sm/register! ::layout [::sm/one-of valid-layouts])
(defn flex-layout?
([objects id]
(flex-layout? (get objects id)))
@@ -754,9 +741,7 @@
([type parent value]
(add-grid-track type parent value nil))
([type parent value index]
(dm/assert!
"expected a valid grid definition for `value`"
(check-grid-track! value))
(assert (check-grid-track value))
(let [[tracks-prop tracks-prop-other prop prop-other prop-span prop-span-other]
(if (= type :column)

View File

@@ -11,9 +11,12 @@
(defn- compare-text-content
"Given two content text structures, conformed by maps and vectors,
compare them, and returns a set with the type of differences.
The possibilities are :text-content-text :text-content-attribute and :text-content-structure."
[a b]
compare them, and returns a set with the differences info.
If the structures are equal, it returns an empty set. If the structure
has changed, it returns :text-content-structure. There are two
callbacks to specify what to return when there is a text change with
the same structure, and when attributes change."
[a b {:keys [text-cb attribute-cb] :as callbacks}]
(cond
;; If a and b are equal, there is no diff
(= a b)
@@ -38,18 +41,18 @@
#{:text-content-structure}
(into acc
(apply set/union
(map #(compare-text-content %1 %2) v1 v2))))
(map #(compare-text-content %1 %2 callbacks) v1 v2))))
;; If the key is :text, and they are different, it is a text differece
(= k :text)
(if (not= v1 v2)
(conj acc :text-content-text)
(text-cb acc)
acc)
:else
;; If the key is not :text, and they are different, it is an attribute differece
(if (not= v1 v2)
(conj acc :text-content-attribute)
(attribute-cb acc k)
acc))))
#{}
keys))
@@ -57,7 +60,6 @@
:else
#{:text-content-structure}))
(defn equal-attrs?
"Given a text structure, and a map of attrs, check that all the internal attrs in
paragraphs and sentences have the same attrs"
@@ -79,10 +81,15 @@
(defn get-diff-type
"Given two content text structures, conformed by maps and vectors,
compare them, and returns a set with the type of differences.
The possibilities are :text-content-text :text-content-attribute,
:text-content-structure and :text-content-structure-same-attrs."
The possibilities are
:text-content-text
:text-content-attribute,
:text-content-structure
:text-content-structure-same-attrs."
[a b]
(let [diff-type (compare-text-content a b)]
(let [diff-type (compare-text-content a b
{:text-cb (fn [acc] (conj acc :text-content-text))
:attribute-cb (fn [acc _] (conj acc :text-content-attribute))})]
(if-not (contains? diff-type :text-content-structure)
diff-type
(let [;; get attrs of the first paragraph of the first paragraph-set
@@ -92,6 +99,24 @@
#{:text-content-structure :text-content-structure-same-attrs}
diff-type)))))
(defn get-diff-attrs
"Given two content text structures, conformed by maps and vectors,
compare them, and returns a set with the attributes that have changed.
This is independent of the text structure, so if the structure changes
but the attributes are the same, it will return an empty set."
[a b]
(let [diff-attrs (compare-text-content a b
{:text-cb identity
:attribute-cb (fn [acc attr] (conj acc attr))})]
(if-not (contains? diff-attrs :text-content-structure)
diff-attrs
(let [;; get attrs of the first paragraph of the first paragraph-set
attrs (get-first-paragraph-text-attrs a)]
(if (and (equal-attrs? a attrs)
(equal-attrs? b attrs))
#{}
(disj diff-attrs :text-content-structure))))))
;; TODO We know that there are cases that the blocks of texts are separated
;; differently: ["one" " " "two"], ["one " "two"], ["one" " two"]
;; so this won't work for 100% of the situations. But it's good enough for now,
@@ -116,7 +141,6 @@
:else
true))
(defn copy-text-keys
"Given two equal content text structures, deep copy all the keys :text
from origin to destiny"
@@ -124,7 +148,7 @@
(cond
(map? origin)
(into {}
(for [k (keys origin) :when (not= k :key)] ;; We ignore :key because it is a draft artifact
(for [k (keys destiny) :when (not= k :key)] ;; We ignore :key because it is a draft artifact
(cond
(= :children k)
[k (vec (map #(copy-text-keys %1 %2) (get origin k) (get destiny k)))]

View File

@@ -33,6 +33,8 @@
:border-radius "borderRadius"
:color "color"
:dimensions "dimension"
:font-size "fontSizes"
:letter-spacing "letterSpacing"
:number "number"
:opacity "opacity"
:other "other"
@@ -101,18 +103,15 @@
[:m1 {:optional true} token-name-ref]
[:m2 {:optional true} token-name-ref]
[:m3 {:optional true} token-name-ref]
[:m4 {:optional true} token-name-ref]
[:x {:optional true} token-name-ref]
[:y {:optional true} token-name-ref]])
[:m4 {:optional true} token-name-ref]])
(def spacing-keys (schema-keys schema:spacing))
(def ^:private schema:dimensions
[:merge
schema:sizing
schema:spacing
schema:stroke-width
schema:border-radius])
(reduce mu/union [schema:sizing
schema:spacing
schema:stroke-width
schema:border-radius]))
(def dimensions-keys (schema-keys schema:dimensions))
@@ -122,10 +121,25 @@
(def rotation-keys (schema-keys schema:rotation))
(def ^:private schema:number
(def ^:private schema:font-size
[:map
[:rotation {:optional true} token-name-ref]
[:line-height {:optional true} token-name-ref]])
[:font-size {:optional true} token-name-ref]])
(def font-size-keys (schema-keys schema:font-size))
(def ^:private schema:letter-spacing
[:map
[:letter-spacing {:optional true} token-name-ref]])
(def letter-spacing-keys (schema-keys schema:letter-spacing))
(def typography-keys (set/union font-size-keys letter-spacing-keys))
(def ff-typography-keys (set/difference typography-keys font-size-keys))
(def ^:private schema:number
(reduce mu/union [[:map [:line-height {:optional true} token-name-ref]]
schema:rotation]))
(def number-keys (schema-keys schema:number))
@@ -137,6 +151,7 @@
spacing-keys
dimensions-keys
rotation-keys
typography-keys
number-keys))
(def ^:private schema:tokens
@@ -150,6 +165,8 @@
schema:spacing
schema:rotation
schema:number
schema:font-size
schema:letter-spacing
schema:dimensions])
(defn shape-attr->token-attrs
@@ -177,6 +194,8 @@
changed-sub-attr
#{:m1 :m2 :m3 :m4})
(font-size-keys shape-attr) #{shape-attr}
(letter-spacing-keys shape-attr) #{shape-attr}
(border-radius-keys shape-attr) #{shape-attr}
(sizing-keys shape-attr) #{shape-attr}
(opacity-keys shape-attr) #{shape-attr}
@@ -192,6 +211,56 @@
:stroke-width :strokes
token-attr))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKEN SHAPE ATTRIBUTES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def position-attributes #{:x :y})
(def generic-attributes
(set/union color-keys
stroke-width-keys
rotation-keys
sizing-keys
opacity-keys
position-attributes))
(def rect-attributes
(set/union generic-attributes
border-radius-keys))
(def frame-attributes
(set/union rect-attributes
spacing-keys))
(def text-attributes
(set/union generic-attributes
typography-keys
number-keys))
(defn shape-type->attributes
[type]
(case type
:bool generic-attributes
:circle generic-attributes
:rect rect-attributes
:frame frame-attributes
:image rect-attributes
:path generic-attributes
:svg-raw generic-attributes
:text text-attributes
nil))
(defn appliable-attrs
"Returns intersection of shape `attributes` for `token-type`."
[attributes token-type]
(set/intersection attributes (shape-type->attributes token-type)))
(defn any-appliable-attr?
"Checks if `token-type` supports given shape `attributes`."
[attributes token-type]
(seq (appliable-attrs attributes token-type)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKENS IN SHAPES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -218,13 +287,5 @@
:attributes attributes})]
(update shape :applied-tokens #(merge % applied-tokens))))
(defn maybe-apply-token-to-shape
"When the passed `:token` is non-nil apply it to the `:applied-tokens` on a shape."
[{:keys [shape token _attributes] :as props}]
(if token
(apply-token-to-shape props)
shape))
(defn unapply-token-id [shape attributes]
(update shape :applied-tokens d/without-keys attributes))

View File

@@ -17,6 +17,7 @@
[app.common.transit :as t]
[app.common.types.token :as cto]
[app.common.uuid :as uuid]
[clojure.core.protocols :as protocols]
[clojure.set :as set]
[clojure.walk :as walk]
[cuerdas.core :as str]))
@@ -25,13 +26,6 @@
;; TODO: add again the removed functions and refactor the rest of the module to use them
(def ^:private schema:groupable-item
[:map {:title "Groupable item"}
[:name :string]])
(def ^:private valid-groupable-item?
(sm/validator schema:groupable-item))
(def ^:private xf-map-trim
(comp
(map str/trim)
@@ -60,14 +54,38 @@
(defn get-path
"Get the path of object by specified separator (E.g. with '.' separator, the
'group.subgroup.name' -> ['group' 'subgroup'])"
[item separator]
(assert (valid-groupable-item? item) "expected groupable item")
(->> (split-path (:name item) separator)
[name separator]
(->> (split-path name separator)
(not-empty)))
;; === Common
(defprotocol INamedItem
"Protocol for items that have a name, a description and a modified date."
(get-name [_] "Get the name of the item.")
(get-description [_] "Get the description of the item.")
(get-modified-at [_] "Get the description of the item.")
(rename [_ new-name] "Set the name of the item.")
(set-description [_ new-description] "Set the description of the item."))
;; === Token
(defrecord Token [id name type value description modified-at])
(defrecord Token [id name type value description modified-at]
INamedItem
(get-name [_]
name)
(get-description [_]
description)
(get-modified-at [_]
modified-at)
(rename [this new-name]
(assoc this :name new-name))
(set-description [this new-description]
(assoc this :description new-description)))
(defn token?
[o]
@@ -109,7 +127,7 @@
(defn get-token-path
[token]
(get-path token token-separator))
(get-path (:name token) token-separator))
(defn find-token-value-references
"Returns set of token references found in `token-value`.
@@ -146,9 +164,53 @@
(update-token [_ token-name f] "update a token in the list")
(delete-token [_ token-name] "delete a token from the list")
(get-token [_ token-name] "return token by token-name")
(get-tokens [_] "return an ordered sequence of all tokens in the set"))
(get-tokens [_] "return an ordered sequence of all tokens in the set")
(get-tokens-map [_] "return a map of tokens in the set, indexed by token-name"))
(deftype TokenSet [id name description modified-at tokens]
#?@(:clj [clojure.lang.IDeref
(deref [_] {:id id
:name name
:description description
:modified-at modified-at
:tokens tokens})]
:cljs [cljs.core/IDeref
(-deref [_] {:id id
:name name
:description description
:modified-at modified-at
:tokens tokens})])
#?@(:cljs [cljs.core/IEncodeJS
(-clj->js [_] (js-obj "id" (clj->js id)
"name" (clj->js name)
"description" (clj->js description)
"modified-at" (clj->js modified-at)
"tokens" (clj->js tokens)))])
INamedItem
(get-name [_]
name)
(get-description [_]
description)
(get-modified-at [_]
modified-at)
(rename [_ new-name]
(TokenSet. id
new-name
description
(dt/now)
tokens))
(set-description [_ new-description]
(TokenSet. id
name
(d/nilv new-description "")
(dt/now)
tokens))
(defrecord TokenSet [id name description modified-at tokens]
ITokenSet
(add-token [_ token]
(let [token (check-token token)]
@@ -184,7 +246,10 @@
(get tokens token-name))
(get-tokens [_]
(vals tokens)))
(vals tokens))
(get-tokens-map [_]
tokens))
(defn token-set?
[o]
@@ -218,10 +283,7 @@
(declare make-token-set)
(def schema:token-set
[:and {:gen/gen (->> (sg/generator schema:token-set-attrs)
(sg/fmap #(make-token-set %)))}
(sm/required-keys schema:token-set-attrs)
[:fn token-set?]])
(sm/required-keys schema:token-set-attrs))
(sm/register! ::token-set schema:token-set) ;; need to register for the recursive schema of token-sets
@@ -233,13 +295,17 @@
(defn make-token-set
[& {:as attrs}]
(-> attrs
(update :id #(or % (uuid/next)))
(update :modified-at #(or % (dt/now)))
(update :tokens #(into (d/ordered-map) %))
(update :description d/nilv "")
(check-token-set-attrs)
(map->TokenSet)))
(let [attrs (-> attrs
(update :id #(or % (uuid/next)))
(update :modified-at #(or % (dt/now)))
(update :tokens #(into (d/ordered-map) %))
(update :description d/nilv "")
(check-token-set-attrs))]
(TokenSet. (:id attrs)
(:name attrs)
(:description attrs)
(:modified-at attrs)
(:tokens attrs))))
(def ^:private set-prefix "S-")
@@ -291,7 +357,7 @@
(defn get-set-path
[token-set]
(get-path token-set set-separator))
(get-path (get-name token-set) set-separator))
(defn split-set-name
[name]
@@ -315,7 +381,7 @@
(set-full-path->set-prefixed-full-path)))
(defn get-set-prefixed-path [token-set]
(let [path (get-path token-set set-separator)]
(let [path (get-path (get-name token-set) set-separator)]
(set-full-path->set-prefixed-full-path path)))
(defn prefixed-set-path-string->set-name-string [path-str]
@@ -333,7 +399,7 @@
(conj name)))
(defn tokens-tree
"Convert tokens into a nested tree with their `:name` as the path.
"Convert tokens into a nested tree with their name as the path.
Optionally use `update-token-fn` option to transform the token."
[tokens & {:keys [update-token-fn]
:or {update-token-fn identity}}]
@@ -343,7 +409,7 @@
{} tokens))
(defn backtrace-tokens-tree
"Convert tokens into a nested tree with their `:name` as the path.
"Convert tokens into a nested tree with their name as the path.
Generates a uuid per token to backtrace a token from an external source (StyleDictionary).
The backtrace can't be the name as the name might not exist when the user is creating a token."
[tokens]
@@ -392,7 +458,7 @@
(get-set [_ set-name] "get one set looking for name"))
(def schema:token-set-node
[:schema {:registry {::node [:or ::token-set
[:schema {:registry {::node [:or [:fn token-set?]
[:and
[:map-of {:gen/max 5} :string [:ref ::node]]
[:fn d/ordered-map?]]]}}
@@ -443,6 +509,22 @@
(hidden-theme? [_] "if a theme is the (from the user ui) hidden temporary theme"))
(defrecord TokenTheme [id name group description is-source external-id modified-at sets]
INamedItem
(get-name [_]
name)
(get-description [_]
description)
(get-modified-at [_]
modified-at)
(rename [this new-name]
(assoc this :name new-name))
(set-description [this new-description]
(assoc this :description new-description))
ITokenTheme
(set-sets [_ set-names]
(TokenTheme. id
@@ -528,13 +610,17 @@
(defn make-token-theme
[& {:as attrs}]
(let [id (uuid/next)]
(let [new-id (uuid/next)]
(-> attrs
(update :id d/nilv id)
(update :id (fn [id]
(-> (if (string? id) ;; TODO: probably this may be deleted in some time, when we may be sure
(uuid/parse* id) ;; that no file exists that has not been correctly migrated to
id) ;; convert :id into :external-id
(d/nilv new-id))))
(update :group d/nilv top-level-theme-group-name)
(update :description d/nilv "")
(update :is-source d/nilv false)
(update :external-id #(or % (str id)))
(update :external-id #(or % (str new-id)))
(update :modified-at #(or % (dt/now)))
(update :sets set)
(check-token-theme-attrs)
@@ -618,7 +704,7 @@
;; Set
(and v (instance? TokenSet v))
[{:group? false
:path (split-set-name (:name v))
:path (split-set-name (get-name v))
:parent-path parent
:depth depth
:set v}]
@@ -664,7 +750,7 @@
;; Set
(and v (instance? TokenSet v))
(let [name (:name v)]
(let [name (get-name v)]
[{:is-group false
:path (split-set-name name)
:id name
@@ -725,8 +811,14 @@ Will return a value that matches this schema:
(declare export-dtcg-json)
(deftype TokensLib [sets themes active-themes]
;; NOTE: This is only for debug purposes, pending to properly
;; implement the toString and alternative printing.
;; This is to convert the TokensLib to a plain map, for debugging or unit tests.
protocols/Datafiable
(datafy [_]
{:sets (d/update-vals sets deref)
:themes themes
:active-themes active-themes})
;; TODO: this is used in serialization, but there should be a better way to do it
#?@(:clj [clojure.lang.IDeref
(deref [_] {:sets sets
:themes themes
@@ -746,8 +838,8 @@ Will return a value that matches this schema:
ITokenSets
(add-set [_ token-set]
(let [path (get-set-prefixed-path token-set)
token-set (check-token-set token-set)]
(assert (token-set? token-set) "expected valid token-set")
(let [path (get-set-prefixed-path token-set)]
(TokensLib. (d/oassoc-in sets path token-set)
themes
active-themes)))
@@ -756,10 +848,9 @@ Will return a value that matches this schema:
(let [prefixed-full-path (set-name->prefixed-full-path set-name)
set (get-in sets prefixed-full-path)]
(if set
(let [set' (-> (make-token-set (f set))
(assoc :modified-at (dt/now)))
(let [set' (f set)
prefixed-full-path' (get-set-prefixed-path set')
name-changed? (not= (:name set) (:name set'))]
name-changed? (not= (get-name set) (get-name set'))]
(if name-changed?
(TokensLib. (-> sets
(d/oassoc-in-before prefixed-full-path prefixed-full-path' set')
@@ -767,7 +858,7 @@ Will return a value that matches this schema:
(walk/postwalk
(fn [form]
(if (instance? TokenTheme form)
(update-set-name form (:name set) (:name set'))
(update-set-name form (get-name set) (get-name set'))
form))
themes)
active-themes)
@@ -791,7 +882,7 @@ Will return a value that matches this schema:
(let [path (split-set-name set-group-name)
prefixed-path (map add-set-group-prefix path)
child-set-names (->> (get-sets-at-path this path)
(map :name)
(map get-name)
(into #{}))]
(TokensLib. (d/dissoc-in sets prefixed-path)
(walk/postwalk
@@ -833,7 +924,7 @@ Will return a value that matches this schema:
(set-full-path->set-prefixed-full-path before-path)))
set
(assoc prev-set :name (join-set-path to-path))
(rename prev-set (join-set-path to-path))
reorder?
(= prefixed-from-path prefixed-to-path)
@@ -856,7 +947,7 @@ Will return a value that matches this schema:
(walk/postwalk
(fn [form]
(if (instance? TokenTheme form)
(update-set-name form (:name prev-set) (:name set))
(update-set-name form (get-name prev-set) (get-name set))
form))
themes))
active-themes))
@@ -888,15 +979,15 @@ Will return a value that matches this schema:
(d/oupdate-in prefixed-to-path (fn [sets]
(walk/prewalk
(fn [form]
(if (instance? TokenSet form)
(update form :name #(str to-path-str (str/strip-prefix % from-path-str)))
(if (token-set? form)
(rename form (str to-path-str (str/strip-prefix (get-name form) from-path-str)))
form))
sets)))))
themes' (if reorder?
themes
(let [rename-sets-map (->> (get-sets-at-path this from-path)
(map (fn [set]
[(:name set) (str to-path-str (str/strip-prefix (:name set) from-path-str))]))
[(get-name set) (str to-path-str (str/strip-prefix (get-name set) from-path-str))]))
(into {}))]
(walk/postwalk
(fn [form]
@@ -934,12 +1025,12 @@ Will return a value that matches this schema:
sets (get-sets-at-path this path)]
(reduce
(fn [lib set]
(update-set lib (:name set) (fn [set']
(update set' :name #(str to-path-str (str/strip-prefix % from-path-str))))))
(update-set lib (get-name set) (fn [set']
(rename set' (str to-path-str (str/strip-prefix (get-name set') from-path-str))))))
this sets)))
(get-ordered-set-names [this]
(map :name (get-sets this)))
(map get-name (get-sets this)))
(set-count [this]
(count (get-sets this)))
@@ -1080,7 +1171,7 @@ Will return a value that matches this schema:
prefixed-path-str (set-group-path->set-group-prefixed-path-str group-path)]
(if (seq active-set-names)
(let [path-active-set-names (->> (get-sets-at-prefix-path this prefixed-path-str)
(map :name)
(map get-name)
(into #{}))
difference (set/difference path-active-set-names active-set-names)]
(cond
@@ -1095,7 +1186,7 @@ Will return a value that matches this schema:
active-set-names (filter theme-set-names all-set-names)
tokens (reduce (fn [tokens set-name]
(let [set (get-set this set-name)]
(merge tokens (:tokens set))))
(merge tokens (get-tokens-map set))))
(d/ordered-map)
active-set-names)]
tokens))
@@ -1160,11 +1251,10 @@ Will return a value that matches this schema:
(defn duplicate-set [set-name lib & {:keys [suffix]}]
(let [sets (get-sets lib)
unames (map :name sets)
unames (map get-name sets)
copy-name (cfh/generate-unique-name set-name unames :suffix suffix)]
(some-> (get-set lib set-name)
(assoc :name copy-name)
(assoc :modified-at (dt/now)))))
(rename copy-name))))
;; === Import / Export from JSON format
@@ -1212,7 +1302,8 @@ Will return a value that matches this schema:
"Searches through decoded token file and returns:
- `:json-format/legacy` when first node satisfies `legacy-node?` predicate
- `:json-format/dtcg` when first node satisfies `dtcg-node?` predicate
- `nil` if neither combination is found"
- If neither combination is found, return dtcg format by default (we assume that
the file does not contain any token, so the format is irrelevan)."
([decoded-json]
(get-json-format decoded-json legacy-node? dtcg-node?))
([decoded-json legacy-node? dtcg-node?]
@@ -1230,9 +1321,10 @@ Will return a value that matches this schema:
(check-node node)
(when (branch? node)
(mapcat walk (children node))))))]
(->> (walk decoded-json)
(filter some?)
first)))) ;; TODO: throw error if format cannot be determined
(d/nilv (->> (walk decoded-json)
(filter some?)
first)
:json-format/dtcg))))
(defn- legacy-json->dtcg-json
"Converts a decoded json file in legacy format into DTCG format."
@@ -1291,9 +1383,17 @@ Will return a value that matches this schema:
[set-name decoded-json-tokens]
(assert (map? decoded-json-tokens) "expected a plain clojure map for `decoded-json-tokens`")
(assert (= (get-json-format decoded-json-tokens) :json-format/dtcg) "expected a dtcg format for `decoded-json-tokens`")
(-> (make-tokens-lib)
(add-set (make-token-set :name (normalize-set-name set-name)
:tokens (flatten-nested-tokens-json decoded-json-tokens "")))))
(let [set-name (normalize-set-name set-name)
tokens (flatten-nested-tokens-json decoded-json-tokens "")]
(when (empty? tokens)
(throw (ex-info "the file doesn't contain any tokens"
{:error/code :error.import/invalid-json-data})))
(-> (make-tokens-lib)
(add-set (make-token-set :name set-name
:tokens tokens)))))
(defn- parse-single-set-legacy-json
"Parse a decoded json file with a single set of tokens in legacy format into a TokensLib."
@@ -1385,6 +1485,10 @@ Will return a value that matches this schema:
library
active-theme-names)]
(when (and (empty? sets) (empty? themes))
(throw (ex-info "the file doesn't contain any tokens"
{:error/code :error.import/invalid-json-data})))
library))
(defn- parse-multi-set-legacy-json
@@ -1459,8 +1563,10 @@ Will return a value that matches this schema:
[tokens-lib]
(let [{:keys [themes active-themes]} (dtcg-export-themes tokens-lib)
sets (->> (get-sets tokens-lib)
(map (fn [{:keys [name tokens]}]
[(str name ".json") (tokens-tree tokens :update-token-fn token->dtcg-token)]))
(map (fn [token-set]
(let [name (get-name token-set)
tokens (get-tokens-map token-set)]
[(str name ".json") (tokens-tree tokens :update-token-fn token->dtcg-token)])))
(into {}))]
(-> sets
(assoc "$themes.json" themes)
@@ -1477,8 +1583,9 @@ Will return a value that matches this schema:
(->> (get-set-tree tokens-lib)
(tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet))
(map (fn [{:keys [name tokens]}]
[name (tokens-tree tokens :update-token-fn token->dtcg-token)])))
(map (fn [set]
[(get-name set)
(tokens-tree (get-tokens-map set) :update-token-fn token->dtcg-token)])))
ordered-set-names
(mapv first name-set-tuples)
@@ -1498,28 +1605,31 @@ Will return a value that matches this schema:
(defn get-tokens-of-unknown-type
"Search for all tokens in the decoded json file that have a type that is not currently
supported by Penpot. Returns a map token-path -> token type."
([decoded-json]
(get-tokens-of-unknown-type decoded-json "" (get-json-format decoded-json)))
([decoded-json parent-path json-format]
(let [type-key (if (= json-format :json-format/dtcg) "$type" "type")]
(reduce-kv
(fn [unknown-tokens k v]
(let [child-path (if (empty? parent-path)
(name k)
(str parent-path "." k))]
(if (and (map? v)
(not (contains? v type-key)))
(let [nested-unknown-tokens (get-tokens-of-unknown-type v child-path json-format)]
(merge unknown-tokens nested-unknown-tokens))
(let [token-type-str (get v type-key)
token-type (cto/dtcg-token-type->token-type token-type-str)]
(if (and (not (some? token-type)) (some? token-type-str))
(assoc unknown-tokens child-path token-type-str)
unknown-tokens)))))
nil
decoded-json))))
[decoded-json {:keys [json-format parent-path process-token-type]
:or {json-format (get-json-format decoded-json)
parent-path ""
process-token-type identity}
:as opts}]
(let [type-key (if (= json-format :json-format/dtcg) "$type" "type")]
(reduce-kv
(fn [unknown-tokens k v]
(let [child-path (if (empty? parent-path)
(name k)
(str parent-path "." k))]
(if (and (map? v)
(not (contains? v type-key)))
(let [nested-unknown-tokens (get-tokens-of-unknown-type v (assoc opts :parent-path child-path))]
(merge unknown-tokens nested-unknown-tokens))
(let [token-type-str (get v type-key)
token-type (-> (cto/dtcg-token-type->token-type token-type-str)
(process-token-type))]
(if (and (not (some? token-type)) (some? token-type-str))
(assoc unknown-tokens child-path token-type-str)
unknown-tokens)))))
nil
decoded-json)))
;; === Serialization handlers for RPC API (transit) and database (fressian)
;; === Serialization handlers for RPC API (transit)
(t/add-handlers!
{:id "penpot/tokens-lib"
@@ -1529,8 +1639,8 @@ Will return a value that matches this schema:
{:id "penpot/token-set"
:class TokenSet
:wfn #(into {} %)
:rfn #(map->TokenSet %)}
:wfn deref
:rfn #(make-token-set %)}
{:id "penpot/token-theme"
:class TokenTheme
@@ -1542,6 +1652,8 @@ Will return a value that matches this schema:
:wfn #(into {} %)
:rfn #(map->Token %)})
;; === Serialization handlers for database (fressian)
#?(:clj
(defn- read-tokens-lib-v1-0
"Reads the first version of tokens lib, now completly obsolete"
@@ -1661,16 +1773,16 @@ Will return a value that matches this schema:
(fres/write-object! w (into {} o)))
:rfn (fn [r]
(let [obj (fres/read-object! r)]
(map->Token obj)))}
(make-token obj)))}
{:name "penpot/token-set/v1"
:class TokenSet
:wfn (fn [n w o]
(fres/write-tag! w n 1)
(fres/write-object! w (into {} o)))
(fres/write-object! w (into {} (deref o))))
:rfn (fn [r]
(let [obj (fres/read-object! r)]
(map->TokenSet obj)))}
(make-token-set obj)))}
{:name "penpot/token-theme/v1"
:class TokenTheme
@@ -1679,7 +1791,7 @@ Will return a value that matches this schema:
(fres/write-object! w (into {} o)))
:rfn (fn [r]
(let [obj (fres/read-object! r)]
(map->TokenTheme obj)))}
(make-token-theme obj)))}
;; LEGACY TOKENS LIB READERS (with migrations)
{:name "penpot/tokens-lib/v1"

View File

@@ -93,13 +93,16 @@
remap-typography
content)))))
(defn remove-typography-from-node
"Remove the typography reference from a node."
[node]
(dissoc node :typography-ref-file :typography-ref-id))
(defn remove-external-typographies
"Change the shape so that any use of an external typography now is removed"
[shape file-id]
(let [remove-ref-file #(dissoc % :typography-ref-file :typography-ref-id)]
(update shape :content
(fn [content]
(txt/transform-nodes #(not= (:typography-ref-file %) file-id)
remove-ref-file
content)))))
(update shape :content
(fn [content]
(txt/transform-nodes #(not= (:typography-ref-file %) file-id)
remove-typography-from-node
content))))

View File

@@ -139,7 +139,6 @@
(< (count (first %)) property-max-length)
(< (count (second %)) property-max-length)))))
(defn find-properties-to-remove
"Compares two property maps to find which properties should be removed"
[prev-props upd-props]
@@ -161,6 +160,46 @@
(filterv #(not (contains? prev-names (:name %))) upd-props)))
(defn- split-base-name-and-number
"Extract the number in parentheses from an item, if present, and return both the base name and the number"
[item]
(let [pattern-num-parens #"\(\d+\)$"
pattern-num #"\d+"
base (-> item (str/replace pattern-num-parens "") (str/trim))
num (some->> item (re-find pattern-num-parens) (re-find pattern-num) (d/parse-integer))]
[base (d/nilv num 0)]))
(defn- group-numbers-by-base-name
"Return a map with a set of numbers associated to each base name"
[items]
(reduce (fn [acc item]
(let [[base num] (split-base-name-and-number item)]
(update acc base (fnil conj #{}) num)))
{}
items))
(defn update-number-in-repeated-item
"Add, keep or update a number in parentheses for a given item, if necessary, depending on the items
already present in a list, to avoid repetitions"
[items item]
(let [names (group-numbers-by-base-name items)
[base num] (split-base-name-and-number item)
nums-taken (get names base #{})]
(loop [n num]
(if (nums-taken n)
(recur (inc n))
(str base (when (pos? n) (str " (" n ")")))))))
(defn update-number-in-repeated-prop-names
"Add, keep or update a number for each prop name depending on the previous ones"
[props]
(->> props
(reduce (fn [acc prop]
(conj acc {:name (update-number-in-repeated-item (mapv :name acc) (:name prop))
:value (:value prop)}))
[])))
(defn find-index-for-property-name
"Finds the index of a name in a property map"
[props name]

View File

@@ -0,0 +1,881 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.logic.text-sync-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(t/deftest test-sync-unchanged-copy-when-changed-attribute
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page (thf/current-page file)
main-child (ths/get-shape file :main-child)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
(t/is (= "32" (:font-size line)))
(t/is (= "hello world" (:text line)))))
(t/deftest test-sync-unchanged-copy-when-changed-text
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page (thf/current-page file)
main-child (ths/get-shape file :main-child)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
(t/is (= "14" (:font-size line)))
(t/is (= "Bye" (:text line)))))
(t/deftest test-sync-unchanged-copy-when-changed-both
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page (thf/current-page file)
main-child (ths/get-shape file :main-child)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
(t/is (= "32" (:font-size line)))
(t/is (= "Bye" (:text line)))))
(t/deftest test-sync-updated-attr-copy-when-changed-attribute
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :font-weight] "700"))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr doesn't change, because it was touched
(t/is (= "14" (:font-size line)))
(t/is (= "hello world" (:text line)))))
(t/deftest test-sync-updated-attr-copy-when-changed-text
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :font-weight] "700"))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
(t/is (= "14" (:font-size line)))
;; The text is updated because only attrs were touched
(t/is (= "Bye" (:text line)))))
(t/deftest test-sync-updated-attr-copy-when-changed-both
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :font-weight] "700"))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr doesn't change, because it was touched
(t/is (= "14" (:font-size line)))
;; The text is updated because only attrs were touched
(t/is (= "Bye" (:text line)))))
(t/deftest test-sync-updated-text-copy-when-changed-attribute
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Hi"))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr is updated because only text were touched
(t/is (= "32" (:font-size line)))
(t/is (= "Hi" (:text line)))))
(t/deftest test-sync-updated-text-copy-when-changed-text
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Hi"))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
(t/is (= "14" (:font-size line)))
;; The text doesn't change, because it was touched
(t/is (= "Hi" (:text line)))))
(t/deftest test-sync-updated-text-copy-when-changed-both
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Hi"))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr is updated because only text were touched
(t/is (= "32" (:font-size line)))
;; The text doesn't change, because it was touched
(t/is (= "Hi" (:text line)))))
(t/deftest test-sync-updated-both-copy-when-changed-attribute
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-weight] "700")
(assoc-in [:content :children 0 :children 0 :children 0 :text] "Hi")))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr doesn't change, because it was touched
(t/is (= "14" (:font-size line)))
(t/is (= "Hi" (:text line)))))
(t/deftest test-sync-updated-both-copy-when-changed-text
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-weight] "700")
(assoc-in [:content :children 0 :children 0 :children 0 :text] "Hi")))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
(t/is (= "14" (:font-size line)))
;; The text doesn't change, because it was touched
(t/is (= "Hi" (:text line)))))
(t/deftest test-sync-updated-both-copy-when-changed-both
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-weight] "700")
(assoc-in [:content :children 0 :children 0 :children 0 :text] "Hi")))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr doesn't change, because it was touched
(t/is (= "14" (:font-size line)))
;; The text doesn't change, because it was touched
(t/is (= "Hi" (:text line)))))
(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-attribute
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(let [line (get-in shape [:content :children 0 :children 0 :children 0])]
(update-in shape [:content :children 0 :children 0 :children]
#(conj % line))))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
;; Update the attrs on all the content tree
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :font-size] "32")))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr is updated because all the attrs on the structure are equal
(t/is (= "32" (:font-size line)))
(t/is (= "hello world" (:text line)))))
(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-text
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(let [line (get-in shape [:content :children 0 :children 0 :children 0])]
(update-in shape [:content :children 0 :children 0 :children]
#(conj % line))))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
(t/is (= "14" (:font-size line)))
;; The text doesn't change, because the structure was touched
(t/is (= "hello world" (:text line)))))
(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-both
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(let [line (get-in shape [:content :children 0 :children 0 :children 0])]
(update-in shape [:content :children 0 :children 0 :children]
#(conj % line))))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
;; Update the attrs on all the content tree
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr is updated because all the attrs on the structure are equal
(t/is (= "32" (:font-size line)))
;; The text doesn't change, because the structure was touched
(t/is (= "hello world" (:text line)))))
(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-attribute
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(let [line (-> (get-in shape [:content :children 0 :children 0 :children 0])
(assoc :font-weight "700"))]
(update-in shape [:content :children 0 :children 0 :children]
#(conj % line))))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
;; Update the attrs on all the content tree
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :font-size] "32")))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr doesn't change, because not all the attrs on the structure are equal
(t/is (= "14" (:font-size line)))
(t/is (= "hello world" (:text line)))))
(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-text
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(let [line (-> (get-in shape [:content :children 0 :children 0 :children 0])
(assoc :font-weight "700"))]
(update-in shape [:content :children 0 :children 0 :children]
#(conj % line))))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
(t/is (= "14" (:font-size line)))
;; The text doesn't change, because the structure was touched
(t/is (= "hello world" (:text line)))))
(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-both
(let [;; ==== Setup
file0 (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page0 (thf/current-page file0)
copy-child (ths/get-shape file0 :copy-child)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
#{(:id copy-child)}
(fn [shape]
(let [line (-> (get-in shape [:content :children 0 :children 0 :children 0])
(assoc :font-weight "700"))]
(update-in shape [:content :children 0 :children 0 :children]
#(conj % line))))
(:objects (thf/current-page file0))
{})
file (thf/apply-changes file0 changes)
main-child (ths/get-shape file :main-child)
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
;; Update the attrs on all the content tree
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
(:objects page)
{})
updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
nil
:components
(:id updated-file)
(thi/id :component1)
(:id updated-file)
{(:id updated-file) updated-file}
(:id updated-file))
file' (thf/apply-changes updated-file changes2)
;; ==== Get
copy-child' (ths/get-shape file' :copy-child)
line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
;; The attr doesn't change, because not all the attrs on the structure are equal
(t/is (= "14" (:font-size line)))
;; The text doesn't change, because the structure was touched
(t/is (= "hello world" (:text line)))))

View File

@@ -0,0 +1,132 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.logic.text-touched-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.shapes :as cls]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(t/deftest test-text-copy-changed-attribute
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page (thf/current-page file)
copy-child (ths/get-shape file :copy-child)
;; ==== Action
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id copy-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :font-size] "32"))
(:objects page)
{})
file' (thf/apply-changes file changes)
copy-child' (ths/get-shape file' :copy-child)]
(t/is (= #{:content-group :text-content-attribute} (:touched copy-child')))))
(t/deftest test-text-copy-changed-text
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page (thf/current-page file)
copy-child (ths/get-shape file :copy-child)
;; ==== Action
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id copy-child)}
(fn [shape]
(assoc-in shape [:content :children 0 :children 0 :text] "Bye"))
(:objects page)
{})
file' (thf/apply-changes file changes)
copy-child' (ths/get-shape file' :copy-child)]
(t/is (= #{:content-group :text-content-text} (:touched copy-child')))))
(t/deftest test-text-copy-changed-both
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page (thf/current-page file)
copy-child (ths/get-shape file :copy-child)
;; ==== Action
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id copy-child)}
(fn [shape]
(-> shape
(assoc-in [:content :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :text] "Bye")))
(:objects page)
{})
file' (thf/apply-changes file changes)
copy-child' (ths/get-shape file' :copy-child)]
(t/is (= #{:content-group :text-content-attribute :text-content-text} (:touched copy-child')))))
(t/deftest test-text-copy-changed-structure-same-attrs
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page (thf/current-page file)
copy-child (ths/get-shape file :copy-child)
;; ==== Action
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id copy-child)}
(fn [shape]
(let [line (get-in shape [:content :children 0 :children 0])]
(update-in shape [:content :children 0 :children]
#(conj % line))))
(:objects page)
{})
file' (thf/apply-changes file changes)
copy-child' (ths/get-shape file' :copy-child)]
(t/is (= #{:content-group :text-content-structure :text-content-structure-same-attrs} (:touched copy-child')))))
(t/deftest test-text-copy-changed-structure-diff-attrs
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-frame-with-text :main-root :main-child "hello world")
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
page (thf/current-page file)
copy-child (ths/get-shape file :copy-child)
;; ==== Action
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id copy-child)}
(fn [shape]
(let [line (-> shape
(get-in [:content :children 0 :children 0])
(assoc :font-size "32"))]
(update-in shape [:content :children 0 :children]
#(conj % line))))
(:objects page)
{})
file' (thf/apply-changes file changes)
copy-child' (ths/get-shape file' :copy-child)]
(t/is (= #{:content-group :text-content-structure} (:touched copy-child')))))

View File

@@ -6,6 +6,7 @@
(ns common-tests.logic.token-apply-test
(:require
[app.common.data :as d]
[app.common.files.changes-builder :as pcb]
[app.common.logic.shapes :as cls]
[app.common.test-helpers.compositions :as tho]
@@ -13,6 +14,7 @@
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.tokens :as tht]
[app.common.text :as txt]
[app.common.types.container :as ctn]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
@@ -52,8 +54,17 @@
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-dimensions"
:type :dimensions
:value 100))))
(tho/add-frame :frame1)))
:value 100))
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-font-size"
:type :font-size
:value 24))
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-letter-spacing"
:type :letter-spacing
:value 2))))
(tho/add-frame :frame1)
(tho/add-text :text1 "Hello World!")))
(defn- apply-all-tokens
[file]
@@ -64,19 +75,24 @@
(tht/apply-token-to-shape :frame1 "token-stroke-width" [:stroke-width] [:stroke-width] 2)
(tht/apply-token-to-shape :frame1 "token-color" [:stroke-color] [:stroke-color] "#00ff00")
(tht/apply-token-to-shape :frame1 "token-color" [:fill] [:fill] "#00ff00")
(tht/apply-token-to-shape :frame1 "token-dimensions" [:width :height] [:width :height] 100)))
(tht/apply-token-to-shape :frame1 "token-dimensions" [:width :height] [:width :height] 100)
(tht/apply-token-to-shape :text1 "token-font-size" [:font-size] [:font-size] 24)
(tht/apply-token-to-shape :text1 "token-letter-spacing" [:letter-spacing] [:letter-spacing] 2)))
(t/deftest apply-tokens-to-shape
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
frame1 (ths/get-shape file :frame1)
text1 (ths/get-shape file :text1)
token-radius (tht/get-token file "test-token-set" "token-radius")
token-rotation (tht/get-token file "test-token-set" "token-rotation")
token-opacity (tht/get-token file "test-token-set" "token-opacity")
token-stroke-width (tht/get-token file "test-token-set" "token-stroke-width")
token-color (tht/get-token file "test-token-set" "token-color")
token-dimensions (tht/get-token file "test-token-set" "token-dimensions")
token-font-size (tht/get-token file "test-token-set" "token-font-size")
token-letter-spacing (tht/get-token file "test-token-set" "token-letter-spacing")
;; ==== Action
changes (-> (-> (pcb/empty-changes nil)
@@ -85,38 +101,48 @@
(cls/generate-update-shapes [(:id frame1)]
(fn [shape]
(as-> shape $
(cto/maybe-apply-token-to-shape {:token nil ; test nil case
:shape $
:attributes []})
(cto/maybe-apply-token-to-shape {:token token-radius
:shape $
:attributes [:r1 :r2 :r3 :r4]})
(cto/maybe-apply-token-to-shape {:token token-rotation
:shape $
:attributes [:rotation]})
(cto/maybe-apply-token-to-shape {:token token-opacity
:shape $
:attributes [:opacity]})
(cto/maybe-apply-token-to-shape {:token token-stroke-width
:shape $
:attributes [:stroke-width]})
(cto/maybe-apply-token-to-shape {:token token-color
:shape $
:attributes [:stroke-color]})
(cto/maybe-apply-token-to-shape {:token token-color
:shape $
:attributes [:fill]})
(cto/maybe-apply-token-to-shape {:token token-dimensions
:shape $
:attributes [:width :height]})))
(cto/apply-token-to-shape {:token token-radius
:shape $
:attributes [:r1 :r2 :r3 :r4]})
(cto/apply-token-to-shape {:token token-rotation
:shape $
:attributes [:rotation]})
(cto/apply-token-to-shape {:token token-opacity
:shape $
:attributes [:opacity]})
(cto/apply-token-to-shape {:token token-stroke-width
:shape $
:attributes [:stroke-width]})
(cto/apply-token-to-shape {:token token-color
:shape $
:attributes [:stroke-color]})
(cto/apply-token-to-shape {:token token-color
:shape $
:attributes [:fill]})
(cto/apply-token-to-shape {:token token-dimensions
:shape $
:attributes [:width :height]})))
(:objects page)
{})
(cls/generate-update-shapes [(:id text1)]
(fn [shape]
(as-> shape $
(cto/apply-token-to-shape {:token token-font-size
:shape $
:attributes [:font-size]})
(cto/apply-token-to-shape {:token token-letter-spacing
:shape $
:attributes [:letter-spacing]})))
(:objects page)
{}))
file' (thf/apply-changes file changes)
;; ==== Get
frame1' (ths/get-shape file' :frame1)
applied-tokens' (:applied-tokens frame1')]
frame1' (ths/get-shape file' :frame1)
applied-tokens' (:applied-tokens frame1')
text1' (ths/get-shape file' :text1)
text1-applied-tokens (:applied-tokens text1')]
;; ==== Check
(t/is (= (count applied-tokens') 11))
@@ -130,7 +156,10 @@
(t/is (= (:stroke-color applied-tokens') "token-color"))
(t/is (= (:fill applied-tokens') "token-color"))
(t/is (= (:width applied-tokens') "token-dimensions"))
(t/is (= (:height applied-tokens') "token-dimensions"))))
(t/is (= (:height applied-tokens') "token-dimensions"))
(t/is (= (count text1-applied-tokens) 2))
(t/is (= (:font-size text1-applied-tokens) "token-font-size"))
(t/is (= (:letter-spacing text1-applied-tokens) "token-letter-spacing"))))
(t/deftest unapply-tokens-from-shape
(let [;; ==== Setup
@@ -138,6 +167,7 @@
(apply-all-tokens))
page (thf/current-page file)
frame1 (ths/get-shape file :frame1)
text1 (ths/get-shape file :text1)
;; ==== Action
changes (-> (-> (pcb/empty-changes nil)
@@ -154,16 +184,26 @@
(cto/unapply-token-id [:fill])
(cto/unapply-token-id [:width :height])))
(:objects page)
{})
(cls/generate-update-shapes [(:id text1)]
(fn [shape]
(-> shape
(cto/unapply-token-id [:font-size])
(cto/unapply-token-id [:letter-spacing])))
(:objects page)
{}))
file' (thf/apply-changes file changes)
;; ==== Get
frame1' (ths/get-shape file' :frame1)
applied-tokens' (:applied-tokens frame1')]
frame1' (ths/get-shape file' :frame1)
applied-tokens' (:applied-tokens frame1')
text1' (ths/get-shape file' :text1)
text1-applied-tokens (:applied-tokens text1')]
;; ==== Check
(t/is (= (count applied-tokens') 0))))
(t/is (= (count applied-tokens') 0))
(t/is (= (count text1-applied-tokens) 0))))
(t/deftest unapply-tokens-automatic
(let [;; ==== Setup
@@ -171,6 +211,7 @@
(apply-all-tokens))
page (thf/current-page file)
frame1 (ths/get-shape file :frame1)
text1 (ths/get-shape file :text1)
;; ==== Action
changes (-> (-> (pcb/empty-changes nil)
@@ -190,13 +231,27 @@
(ctn/set-shape-attr :width 0)
(ctn/set-shape-attr :height 0)))
(:objects page)
{})
(cls/generate-update-shapes [(:id text1)]
(fn [shape]
(txt/update-text-content
shape
txt/is-content-node?
d/txt-merge
{:fills (ths/sample-fills-color :fill-color "#fabada")
:font-size "1"
:letter-spacing "0"}))
(:objects page)
{}))
file' (thf/apply-changes file changes)
;; ==== Get
frame1' (ths/get-shape file' :frame1)
applied-tokens' (:applied-tokens frame1')]
frame1' (ths/get-shape file' :frame1)
text1' (ths/get-shape file' :text1)
applied-tokens-frame' (:applied-tokens frame1')
applied-tokens-text' (:applied-tokens text1')]
;; ==== Check
(t/is (= (count applied-tokens') 0))))
(t/is (= (count applied-tokens-frame') 0))
(t/is (= (count applied-tokens-text') 0))))

View File

@@ -269,8 +269,7 @@
new-set-name "foo1"
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-set set-name false (assoc prev-token-set
:name new-set-name)))
(pcb/set-token-set set-name false (ctob/rename prev-token-set new-set-name)))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)

View File

@@ -14,6 +14,7 @@
[app.common.pprint :as pp]
[app.common.transit :as trans]
[app.common.types.path :as path]
[app.common.types.path.bool :as path.bool]
[app.common.types.path.helpers :as path.helpers]
[app.common.types.path.impl :as path.impl]
[app.common.types.path.segment :as path.segment]
@@ -25,6 +26,15 @@
{:command :curve-to :params {:c1x 368.0 :c1y 737.0 :c2x 310.0 :c2y 681.0 :x 264.0 :y 634.0}}
{:command :close-path :params {}}])
(def sample-content-square
[{:command :move-to, :params {:x 0, :y 0}}
{:command :line-to, :params {:x 10, :y 0}}
{:command :line-to, :params {:x 10, :y 10}}
{:command :line-to, :params {:x 10, :y 0}}
{:command :line-to, :params {:x 0, :y 10}}
{:command :line-to, :params {:x 0, :y 0}}
{:command :close-path :params {}}])
(def sample-content-large
[{:command :move-to :params {:x 480.0 :y 839.0}}
{:command :line-to :params {:x 439.0 :y 802.0}}
@@ -179,6 +189,42 @@
(t/is (= (vec result1) result2))
(t/is (= result2 result3))))
(t/deftest path-transform-3
(let [matrix (gmt/rotate-matrix 42 (gpt/point 0 0))
content (path/content sample-content-square)
result1 (path/transform-content content matrix)
result2 (transform-plain-content sample-content-square matrix)
result3 (transform-plain-content content matrix)]
(t/is (= (count result1) (count result2)))
(doseq [[seg-a seg-b] (map vector result1 result2)]
(t/is (= (:command seg-a)
(:command seg-b)))
(let [params-a (get seg-a :params)
params-b (get seg-b :params)]
(t/is (mth/close? (get params-a :x 0)
(get params-b :x 0)))
(t/is (mth/close? (get params-a :y 0)
(get params-b :y 0)))))
(doseq [[seg-a seg-b] (map vector result1 result3)]
(t/is (= (:command seg-a)
(:command seg-b)))
(let [params-a (get seg-a :params)
params-b (get seg-b :params)]
(t/is (mth/close? (get params-a :x 0)
(get params-b :x 0)))
(t/is (mth/close? (get params-a :y 0)
(get params-b :y 0)))))))
(defn- content->points
"Given a content return all points.
@@ -278,15 +324,6 @@
(t/is (= result2 expect))
(t/is (= result3 expect))))
(def sample-content-square
[{:command :move-to, :params {:x 0, :y 0}}
{:command :line-to, :params {:x 10, :y 0}}
{:command :line-to, :params {:x 10, :y 10}}
{:command :line-to, :params {:x 10, :y 0}}
{:command :line-to, :params {:x 0, :y 10}}
{:command :line-to, :params {:x 0, :y 0}}
{:command :close-path :params {}}])
(t/deftest points-to-content
(let [initial [(gpt/point 0.0 0.0)
(gpt/point 10.0 10.0)
@@ -378,3 +415,89 @@
result1 (get-handlers sample-content-large)
result2 (path.segment/get-handlers content)]
(t/is (= result1 result2))))
(def contents-for-bool
[[{:command :move-to, :params {:x 1682.9000244140625, :y 48.0}}
{:command :line-to, :params {:x 1682.9000244140625, :y 44.0}}
{:command :curve-to, :params {:x 1683.9000244140625, :y 43.0, :c1x 1682.9000244140625, :c1y 43.400001525878906, :c2x 1683.300048828125, :c2y 43.0}}
{:command :line-to, :params {:x 1687.9000244140625, :y 43.0}}
{:command :curve-to, :params {:x 1688.9000244140625, :y 44.0, :c1x 1688.5, :c1y 43.0, :c2x 1688.9000244140625, :c2y 43.400001525878906}}
{:command :line-to, :params {:x 1688.9000244140625, :y 48.0}}
{:command :curve-to, :params {:x 1687.9000244140625, :y 49.0, :c1x 1688.9000244140625, :c1y 48.599998474121094, :c2x 1688.5, :c2y 49.0}}
{:command :line-to, :params {:x 1683.9000244140625, :y 49.0}}
{:command :curve-to, :params {:x 1682.9000244140625, :y 48.0, :c1x 1683.300048828125, :c1y 49.0, :c2x 1682.9000244140625, :c2y 48.599998474121094}}
{:command :close-path, :params {}}
{:command :close-path, :params {}}
{:command :move-to, :params {:x 1684.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1684.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1686.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1686.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1684.9000244140625, :y 45.0}}
{:command :close-path, :params {}}
{:command :close-path, :params {}}]
[{:command :move-to, :params {:x 1672.9000244140625, :y 48.0}}
{:command :line-to, :params {:x 1672.9000244140625, :y 44.0}}
{:command :curve-to, :params {:x 1673.9000244140625, :y 43.0, :c1x 1672.9000244140625, :c1y 43.400001525878906, :c2x 1673.300048828125, :c2y 43.0}}
{:command :line-to, :params {:x 1677.9000244140625, :y 43.0}}
{:command :curve-to, :params {:x 1678.9000244140625, :y 44.0, :c1x 1678.5, :c1y 43.0, :c2x 1678.9000244140625, :c2y 43.400001525878906}}
{:command :line-to, :params {:x 1678.9000244140625, :y 48.0}}
{:command :curve-to, :params {:x 1677.9000244140625, :y 49.0, :c1x 1678.9000244140625, :c1y 48.599998474121094, :c2x 1678.5, :c2y 49.0}}
{:command :line-to, :params {:x 1673.9000244140625, :y 49.0}}
{:command :curve-to, :params {:x 1672.9000244140625, :y 48.0, :c1x 1673.300048828125, :c1y 49.0, :c2x 1672.9000244140625, :c2y 48.599998474121094}}
{:command :close-path, :params {}}
{:command :close-path, :params {}}
{:command :move-to, :params {:x 1674.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1674.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1676.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1676.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1674.9000244140625, :y 45.0}}
{:command :close-path, :params {}}
{:command :close-path, :params {}}]])
(def bool-result
[{:command :move-to, :params {:x 1682.9000244140625, :y 48.0}}
{:command :line-to, :params {:x 1682.9000244140625, :y 44.0}}
{:command :curve-to,
:params
{:x 1683.9000244140625, :y 43.0, :c1x 1682.9000244140625, :c1y 43.400001525878906, :c2x 1683.300048828125, :c2y 43.0}}
{:command :line-to, :params {:x 1687.9000244140625, :y 43.0}}
{:command :curve-to,
:params {:x 1688.9000244140625, :y 44.0, :c1x 1688.5, :c1y 43.0, :c2x 1688.9000244140625, :c2y 43.400001525878906}}
{:command :line-to, :params {:x 1688.9000244140625, :y 48.0}}
{:command :curve-to,
:params {:x 1687.9000244140625, :y 49.0, :c1x 1688.9000244140625, :c1y 48.599998474121094, :c2x 1688.5, :c2y 49.0}}
{:command :line-to, :params {:x 1683.9000244140625, :y 49.0}}
{:command :curve-to,
:params
{:x 1682.9000244140625, :y 48.0, :c1x 1683.300048828125, :c1y 49.0, :c2x 1682.9000244140625, :c2y 48.599998474121094}}
{:command :move-to, :params {:x 1684.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1684.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1686.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1686.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1684.9000244140625, :y 45.0}}
{:command :move-to, :params {:x 1672.9000244140625, :y 48.0}}
{:command :line-to, :params {:x 1672.9000244140625, :y 44.0}}
{:command :curve-to,
:params
{:x 1673.9000244140625, :y 43.0, :c1x 1672.9000244140625, :c1y 43.400001525878906, :c2x 1673.300048828125, :c2y 43.0}}
{:command :line-to, :params {:x 1677.9000244140625, :y 43.0}}
{:command :curve-to,
:params {:x 1678.9000244140625, :y 44.0, :c1x 1678.5, :c1y 43.0, :c2x 1678.9000244140625, :c2y 43.400001525878906}}
{:command :line-to, :params {:x 1678.9000244140625, :y 48.0}}
{:command :curve-to,
:params {:x 1677.9000244140625, :y 49.0, :c1x 1678.9000244140625, :c1y 48.599998474121094, :c2x 1678.5, :c2y 49.0}}
{:command :line-to, :params {:x 1673.9000244140625, :y 49.0}}
{:command :curve-to,
:params
{:x 1672.9000244140625, :y 48.0, :c1x 1673.300048828125, :c1y 49.0, :c2x 1672.9000244140625, :c2y 48.599998474121094}}
{:command :move-to, :params {:x 1674.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1674.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1676.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1676.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1674.9000244140625, :y 45.0}}])
(t/deftest calculate-bool-content
(let [result (path.bool/calculate-content :union contents-for-bool)]
(t/is (= result bool-result))))

View File

@@ -113,10 +113,10 @@
{:num 500})))
(t/deftest shape-path-content-json-roundtrip
(let [encode (sm/encoder ::path/content (sm/json-transformer))
decode (sm/decoder ::path/content (sm/json-transformer))]
(let [encode (sm/encoder path/schema:content (sm/json-transformer))
decode (sm/decoder path/schema:content (sm/json-transformer))]
(smt/check!
(smt/for [path-content (sg/generator ::path/content)]
(smt/for [path-content (sg/generator path/schema:content)]
(let [path-content-1 (encode path-content)
path-content-2 (json-roundtrip path-content-1)
path-content-3 (decode path-content-2)]

View File

@@ -29,11 +29,12 @@
#(conj % line)))
(t/deftest test-get-diff-type
(let [diff-text (cttx/get-diff-type content-base content-changed-text)
diff-attr (cttx/get-diff-type content-base content-changed-attr)
diff-both (cttx/get-diff-type content-base content-changed-both)
diff-structure (cttx/get-diff-type content-base content-changed-structure)
(let [diff-text (cttx/get-diff-type content-base content-changed-text)
diff-attr (cttx/get-diff-type content-base content-changed-attr)
diff-both (cttx/get-diff-type content-base content-changed-both)
diff-structure (cttx/get-diff-type content-base content-changed-structure)
diff-structure-same-attrs (cttx/get-diff-type content-base content-changed-structure-same-attrs)]
(t/is (= #{:text-content-text} diff-text))
(t/is (= #{:text-content-attribute} diff-attr))
(t/is (= #{:text-content-text :text-content-attribute} diff-both))
@@ -41,6 +42,20 @@
(t/is (= #{:text-content-structure :text-content-structure-same-attrs} diff-structure-same-attrs))))
(t/deftest test-get-diff-attrs
(let [attrs-text (cttx/get-diff-attrs content-base content-changed-text)
attrs-attr (cttx/get-diff-attrs content-base content-changed-attr)
attrs-both (cttx/get-diff-attrs content-base content-changed-both)
attrs-structure (cttx/get-diff-attrs content-base content-changed-structure)
attrs-structure-same-attrs (cttx/get-diff-attrs content-base content-changed-structure-same-attrs)]
(t/is (= #{} attrs-text))
(t/is (= #{:font-size} attrs-attr))
(t/is (= #{:font-size} attrs-both))
(t/is (= #{} attrs-structure))
(t/is (= #{} attrs-structure-same-attrs))))
(t/deftest test-equal-structure
(t/is (true? (cttx/equal-structure? content-base content-changed-text)))
(t/is (true? (cttx/equal-structure? content-base content-changed-attr)))

View File

@@ -14,6 +14,7 @@
[app.common.time :as dt]
[app.common.transit :as tr]
[app.common.types.tokens-lib :as ctob]
[clojure.datafy :refer [datafy]]
[clojure.test :as t]))
(defn setup-virtual-time
@@ -72,14 +73,14 @@
:modified-at now
:tokens [])]
(t/is (= (:name token-set1) "test-token-set-1"))
(t/is (= (:description token-set1) ""))
(t/is (some? (:modified-at token-set1)))
(t/is (empty? (:tokens token-set1)))
(t/is (= (:name token-set2) "test-token-set-2"))
(t/is (= (:description token-set2) "test description"))
(t/is (= (:modified-at token-set2) now))
(t/is (empty? (:tokens token-set2)))))
(t/is (= (ctob/get-name token-set1) "test-token-set-1"))
(t/is (= (ctob/get-description token-set1) ""))
(t/is (some? (ctob/get-modified-at token-set1)))
(t/is (empty? (ctob/get-tokens-map token-set1)))
(t/is (= (ctob/get-name token-set2) "test-token-set-2"))
(t/is (= (ctob/get-description token-set2) "test description"))
(t/is (= (ctob/get-modified-at token-set2) now))
(t/is (empty? (ctob/get-tokens-map token-set2)))))
(t/deftest make-invalid-token-set
(let [params {:name 777 :description 999}]
@@ -183,7 +184,7 @@
:type :boolean
:value true)})))
expected (-> (ctob/get-set tokens-lib "A")
(get :tokens)
(ctob/get-tokens-map)
(ctob/tokens-tree))]
(t/is (= (get-in expected ["foo" "bar" "baz" :name]) "foo.bar.baz"))
(t/is (= (get-in expected ["foo" "bar" "bam" :name]) "foo.bar.bam"))
@@ -249,20 +250,18 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "test-token-set"
(fn [token-set]
(assoc token-set
:description "some description")))
(ctob/set-description token-set "some description")))
(ctob/update-set "not-existing-set"
(fn [token-set]
(assoc token-set
:description "no-effect"))))
(ctob/set-description token-set "no-effect"))))
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (:name token-set') "test-token-set"))
(t/is (= (:description token-set') "some description"))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/is (= (ctob/get-name token-set') "test-token-set"))
(t/is (= (ctob/get-description token-set') "some description"))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest rename-token-set
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -271,15 +270,14 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "test-token-set"
(fn [token-set]
(assoc token-set
:name "updated-name"))))
(ctob/rename token-set "updated-name"))))
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "updated-name")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (:name token-set') "updated-name"))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/is (= (ctob/get-name token-set') "updated-name"))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest rename-token-set-group
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -323,11 +321,11 @@
:type :boolean
:value true)})))
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
token (get-in token-set-copy [:tokens "test-token"])]
token (ctob/get-token token-set-copy "test-token")]
(t/is (some? token-set-copy))
(t/is (= (:name token-set-copy) "test-token-set-copy"))
(t/is (= (count (:tokens token-set-copy)) 1))
(t/is (= (ctob/get-name token-set-copy) "test-token-set-copy"))
(t/is (= (count (ctob/get-tokens-map token-set-copy)) 1))
(t/is (= (:name token) "test-token"))))
(t/deftest duplicate-token-set-twice
@@ -341,11 +339,11 @@
tokens-lib (ctob/add-set tokens-lib (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"}))
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
token (get-in token-set-copy [:tokens "test-token"])]
token (ctob/get-token token-set-copy "test-token")]
(t/is (some? token-set-copy))
(t/is (= (:name token-set-copy) "test-token-set-copy-2"))
(t/is (= (count (:tokens token-set-copy)) 1))
(t/is (= (ctob/get-name token-set-copy) "test-token-set-copy-2"))
(t/is (= (count (ctob/get-tokens-map token-set-copy)) 1))
(t/is (= (:name token) "test-token"))))
(t/deftest duplicate-empty-token-set
@@ -353,11 +351,11 @@
(ctob/add-set (ctob/make-token-set :name "test-token-set")))
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
tokens (get token-set-copy :tokens)]
tokens (ctob/get-tokens-map token-set-copy)]
(t/is (some? token-set-copy))
(t/is (= (:name token-set-copy) "test-token-set-copy"))
(t/is (= (count (:tokens token-set-copy)) 0))
(t/is (= (ctob/get-name token-set-copy) "test-token-set-copy"))
(t/is (= (count (ctob/get-tokens-map token-set-copy)) 0))
(t/is (= (count tokens) 0))))
(t/deftest duplicate-not-existing-token-set
@@ -392,12 +390,12 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
token' (get-in token-set' [:tokens "test-token"])]
token' (ctob/get-token token-set' "test-token")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (count (:tokens token-set')) 1))
(t/is (= (count (ctob/get-tokens-map token-set')) 1))
(t/is (= (:name token') "test-token"))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest update-token
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -428,16 +426,16 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
token (get-in token-set [:tokens "test-token-1"])
token' (get-in token-set' [:tokens "test-token-1"])]
token (ctob/get-token token-set "test-token-1")
token' (ctob/get-token token-set' "test-token-1")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (count (:tokens token-set')) 2))
(t/is (= (d/index-of (keys (:tokens token-set')) "test-token-1") 0))
(t/is (= (count (ctob/get-tokens-map token-set')) 2))
(t/is (= (d/index-of (keys (ctob/get-tokens-map token-set')) "test-token-1") 0))
(t/is (= (:name token') "test-token-1"))
(t/is (= (:description token') "some description"))
(t/is (= (:value token') false))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest rename-token
@@ -460,16 +458,16 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
token (get-in token-set [:tokens "test-token-1"])
token' (get-in token-set' [:tokens "updated-name"])]
token (ctob/get-token token-set "test-token-1")
token' (ctob/get-token token-set' "updated-name")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (count (:tokens token-set')) 2))
(t/is (= (d/index-of (keys (:tokens token-set')) "updated-name") 0))
(t/is (= (count (ctob/get-tokens-map token-set')) 2))
(t/is (= (d/index-of (keys (ctob/get-tokens-map token-set')) "updated-name") 0))
(t/is (= (:name token') "updated-name"))
(t/is (= (:description token') ""))
(t/is (= (:value token') true))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest delete-token
@@ -486,12 +484,12 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
token' (get-in token-set' [:tokens "test-token"])]
token' (ctob/get-token token-set' "test-token")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (count (:tokens token-set')) 0))
(t/is (= (count (ctob/get-tokens-map token-set')) 0))
(t/is (nil? token'))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest get-ordered-sets
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -897,7 +895,7 @@
:value true)))
set (ctob/get-set tokens-lib "test-token-set")
tokens-list (vals (:tokens set))]
tokens-list (ctob/get-tokens set)]
(t/is (= (count tokens-list) 5))
(t/is (= (:name (nth tokens-list 0)) "token1"))
@@ -931,14 +929,14 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
token (get-in token-set [:tokens "group1.test-token-2"])
token' (get-in token-set' [:tokens "group1.test-token-2"])]
token (ctob/get-token token-set "group1.test-token-2")
token' (ctob/get-token token-set' "group1.test-token-2")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (:name token') "group1.test-token-2"))
(t/is (= (:description token') "some description"))
(t/is (= (:value token') false))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest update-token-in-sets-rename
@@ -965,14 +963,14 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
token (get-in token-set [:tokens "group1.test-token-2"])
token' (get-in token-set' [:tokens "group1.updated-name"])]
token (ctob/get-token token-set "group1.test-token-2")
token' (ctob/get-token token-set' "group1.updated-name")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (:name token') "group1.updated-name"))
(t/is (= (:description token') ""))
(t/is (= (:value token') true))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (:ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest move-token-of-group
@@ -999,15 +997,15 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
token (get-in token-set [:tokens "group1.test-token-2"])
token' (get-in token-set' [:tokens "group2.updated-name"])]
token (ctob/get-token token-set "group1.test-token-2")
token' (ctob/get-token token-set' "group2.updated-name")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (d/index-of (keys (:tokens token-set')) "group2.updated-name") 1))
(t/is (= (d/index-of (keys (ctob/get-tokens-map token-set')) "group2.updated-name") 1))
(t/is (= (:name token') "group2.updated-name"))
(t/is (= (:description token') ""))
(t/is (= (:value token') true))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest delete-token-in-group
@@ -1026,12 +1024,12 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
token' (get-in token-set' [:tokens "group1.test-token-2"])]
token' (ctob/get-token token-set' "group1.test-token-2")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (count (:tokens token-set')) 1))
(t/is (= (count (ctob/get-tokens-map token-set')) 1))
(t/is (nil? token'))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest update-token-set-in-groups
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -1044,7 +1042,7 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "group1/token-set-2"
(fn [token-set]
(assoc token-set :description "some description"))))
(ctob/set-description token-set "some description"))))
sets-tree (ctob/get-set-tree tokens-lib)
sets-tree' (ctob/get-set-tree tokens-lib')
@@ -1055,9 +1053,9 @@
(t/is (= (ctob/set-count tokens-lib') 5))
(t/is (= (count group1') 3))
(t/is (= (d/index-of (keys group1') "S-token-set-2") 0))
(t/is (= (:name token-set') "group1/token-set-2"))
(t/is (= (:description token-set') "some description"))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/is (= (ctob/get-name token-set') "group1/token-set-2"))
(t/is (= (ctob/get-description token-set') "some description"))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest rename-token-set-in-groups
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -1070,8 +1068,7 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "group1/token-set-2"
(fn [token-set]
(assoc token-set
:name "group1/updated-name"))))
(ctob/rename token-set "group1/updated-name"))))
sets-tree (ctob/get-set-tree tokens-lib)
sets-tree' (ctob/get-set-tree tokens-lib')
@@ -1082,9 +1079,9 @@
(t/is (= (ctob/set-count tokens-lib') 5))
(t/is (= (count group1') 3))
(t/is (= (d/index-of (keys group1') "S-updated-name") 0))
(t/is (= (:name token-set') "group1/updated-name"))
(t/is (= (:description token-set') ""))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/is (= (ctob/get-name token-set') "group1/updated-name"))
(t/is (= (ctob/get-description token-set') ""))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest move-token-set-of-group
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -1097,8 +1094,7 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "group1/token-set-2"
(fn [token-set]
(assoc token-set
:name "group2/updated-name"))))
(ctob/rename token-set "group2/updated-name"))))
sets-tree (ctob/get-set-tree tokens-lib)
sets-tree' (ctob/get-set-tree tokens-lib')
@@ -1111,9 +1107,9 @@
(t/is (= (count group1') 2))
(t/is (= (count group2') 1))
(t/is (nil? (get group1' "S-updated-name")))
(t/is (= (:name token-set') "group2/updated-name"))
(t/is (= (:description token-set') ""))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/is (= (ctob/get-name token-set') "group2/updated-name"))
(t/is (= (ctob/get-description token-set') ""))
(t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest delete-token-set-in-group
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -1413,7 +1409,7 @@
tokens-lib' (ctob/parse-decoded-json encoded "")]
(t/testing "library got updated but data is equal"
(t/is (not= tokens-lib' tokens-lib))
(t/is (= @tokens-lib' @tokens-lib)))))))
(t/is (= (datafy tokens-lib') (datafy tokens-lib))))))))
#?(:clj
(t/deftest export-dtcg-json-with-default-theme

View File

@@ -15,13 +15,13 @@
map-with-two-props-dashes [{:name "border" :value "no"} {:name "color" :value "--"}]
map-with-one-prop [{:name "border" :value "no"}]
map-with-equal [{:name "border" :value "yes color=yes"}]
map-with-spaces [{:name "border 1" :value "of course"}
{:name "color 2" :value "dark gray"}
{:name "background 3" :value "anoth€r co-lor"}]
map-with-spaces [{:name "border (1)" :value "of course"}
{:name "color (2)" :value "dark gray"}
{:name "background (3)" :value "anoth€r co-lor"}]
string-valid-with-two-props "border=yes, color=gray"
string-valid-with-one-prop "border=no"
string-valid-with-spaces "border 1=of course, color 2=dark gray, background 3=anoth€r co-lor"
string-valid-with-spaces "border (1)=of course, color (2)=dark gray, background (3)=anoth€r co-lor"
string-valid-with-no-value "border=no, color="
string-valid-with-dashes "border=no, color=--"
string-valid-with-equal "border=yes color=yes"
@@ -131,3 +131,31 @@
(t/is (= (ctv/same-variant? components-2) false))
(t/is (= (ctv/same-variant? components-3) false))
(t/is (= (ctv/same-variant? components-4) false)))))
(t/deftest update-number-in-repeated-item
(let [names ["border" "color" "color 1" "color 2" "color (1)" "color (7)" "area 51"]]
(t/testing "update-number-in-repeated-item"
(t/is (= (ctv/update-number-in-repeated-item names "background") "background"))
(t/is (= (ctv/update-number-in-repeated-item names "border") "border (1)"))
(t/is (= (ctv/update-number-in-repeated-item names "color") "color (2)"))
(t/is (= (ctv/update-number-in-repeated-item names "color 1") "color 1 (1)"))
(t/is (= (ctv/update-number-in-repeated-item names "color (1)") "color (2)"))
(t/is (= (ctv/update-number-in-repeated-item names "area 51") "area 51 (1)")))))
(t/deftest update-number-in-repeated-prop-names
(let [props [{:name "color" :value "yellow"}
{:name "color" :value "blue"}
{:name "color" :value "red"}
{:name "border (1)" :value "no"}
{:name "border (1)" :value "yes"}]
numbered-props [{:name "color" :value "yellow"}
{:name "color (1)" :value "blue"}
{:name "color (2)" :value "red"}
{:name "border (1)" :value "no"}
{:name "border (2)" :value "yes"}]]
(t/testing "update-number-in-repeated-prop-names"
(t/is (= (ctv/update-number-in-repeated-prop-names props) numbered-props)))))

View File

@@ -118,13 +118,6 @@ __metadata:
languageName: node
linkType: hard
"base64-js@npm:^1.3.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf
languageName: node
linkType: hard
"binary-extensions@npm:^2.0.0":
version: 2.3.0
resolution: "binary-extensions@npm:2.3.0"
@@ -133,21 +126,21 @@ __metadata:
linkType: hard
"brace-expansion@npm:^1.1.7":
version: 1.1.11
resolution: "brace-expansion@npm:1.1.11"
version: 1.1.12
resolution: "brace-expansion@npm:1.1.12"
dependencies:
balanced-match: "npm:^1.0.0"
concat-map: "npm:0.0.1"
checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668
checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73
languageName: node
linkType: hard
"brace-expansion@npm:^2.0.1":
version: 2.0.1
resolution: "brace-expansion@npm:2.0.1"
version: 2.0.2
resolution: "brace-expansion@npm:2.0.2"
dependencies:
balanced-match: "npm:^1.0.0"
checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f
checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf
languageName: node
linkType: hard
@@ -167,16 +160,6 @@ __metadata:
languageName: node
linkType: hard
"buffer@npm:^6.0.3":
version: 6.0.3
resolution: "buffer@npm:6.0.3"
dependencies:
base64-js: "npm:^1.3.1"
ieee754: "npm:^1.2.1"
checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0
languageName: node
linkType: hard
"cacache@npm:^19.0.1":
version: 19.0.1
resolution: "cacache@npm:19.0.1"
@@ -267,7 +250,6 @@ __metadata:
concurrently: "npm:^9.1.2"
luxon: "npm:^3.6.1"
nodemon: "npm:^3.1.10"
shadow-cljs: "npm:3.1.5"
source-map-support: "npm:^0.5.21"
ws: "npm:^8.18.2"
languageName: unknown
@@ -380,14 +362,14 @@ __metadata:
linkType: hard
"fdir@npm:^6.4.4":
version: 6.4.5
resolution: "fdir@npm:6.4.5"
version: 6.4.6
resolution: "fdir@npm:6.4.6"
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
checksum: 10c0/5d63330a1b97165e9b0fb20369fafc7cf826bc4b3e374efcb650bc77d7145ac01193b5da1a7591eab89ae6fd6b15cdd414085910b2a2b42296b1480c9f2677af
checksum: 10c0/45b559cff889934ebb8bc498351e5acba40750ada7e7d6bde197768d2fa67c149be8ae7f8ff34d03f4e1eb20f2764116e56440aaa2f6689e9a4aa7ef06acafe9
languageName: node
linkType: hard
@@ -527,13 +509,6 @@ __metadata:
languageName: node
linkType: hard
"ieee754@npm:^1.2.1":
version: 1.2.1
resolution: "ieee754@npm:1.2.1"
checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb
languageName: node
linkType: hard
"ignore-by-default@npm:^1.0.1":
version: 1.0.1
resolution: "ignore-by-default@npm:1.0.1"
@@ -898,13 +873,6 @@ __metadata:
languageName: node
linkType: hard
"process@npm:^0.11.10":
version: 0.11.10
resolution: "process@npm:0.11.10"
checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3
languageName: node
linkType: hard
"promise-retry@npm:^2.0.1":
version: 2.0.1
resolution: "promise-retry@npm:2.0.1"
@@ -931,13 +899,6 @@ __metadata:
languageName: node
linkType: hard
"readline-sync@npm:^1.4.10":
version: 1.4.10
resolution: "readline-sync@npm:1.4.10"
checksum: 10c0/0a4d0fe4ad501f8f005a3c9cbf3cc0ae6ca2ced93e9a1c7c46f226bdfcb6ef5d3f437ae7e9d2e1098ee13524a3739c830e4c8dbc7f543a693eecd293e41093a3
languageName: node
linkType: hard
"require-directory@npm:^2.1.1":
version: 2.1.1
resolution: "require-directory@npm:2.1.1"
@@ -977,30 +938,6 @@ __metadata:
languageName: node
linkType: hard
"shadow-cljs-jar@npm:1.3.4":
version: 1.3.4
resolution: "shadow-cljs-jar@npm:1.3.4"
checksum: 10c0/c5548bb5f2bda5e0a90df6f42e4ec3a07ed4c72cdebb87619e8d9a2167bb3d4b60d6f6a305a3e15cbfb379d5fdbe2a989a0e7059b667cfb3911bc198a4489e94
languageName: node
linkType: hard
"shadow-cljs@npm:3.1.5":
version: 3.1.5
resolution: "shadow-cljs@npm:3.1.5"
dependencies:
buffer: "npm:^6.0.3"
process: "npm:^0.11.10"
readline-sync: "npm:^1.4.10"
shadow-cljs-jar: "npm:1.3.4"
source-map-support: "npm:^0.5.21"
which: "npm:^5.0.0"
ws: "npm:^8.18.1"
bin:
shadow-cljs: cli/runner.js
checksum: 10c0/29da68f7645c258becf4074e4401e5c86dd3af04622c2e10fdac09824e9832290918d90aaf80ef7df0c35731f1b51b84101cbfd0c6819772a493173d4ae69415
languageName: node
linkType: hard
"shebang-command@npm:^2.0.0":
version: 2.0.0
resolution: "shebang-command@npm:2.0.0"
@@ -1059,12 +996,12 @@ __metadata:
linkType: hard
"socks@npm:^2.8.3":
version: 2.8.4
resolution: "socks@npm:2.8.4"
version: 2.8.5
resolution: "socks@npm:2.8.5"
dependencies:
ip-address: "npm:^9.0.5"
smart-buffer: "npm:^4.2.0"
checksum: 10c0/00c3271e233ccf1fb83a3dd2060b94cc37817e0f797a93c560b9a7a86c4a0ec2961fb31263bdd24a3c28945e24868b5f063cd98744171d9e942c513454b50ae5
checksum: 10c0/e427d0eb0451cfd04e20b9156ea8c0e9b5e38a8d70f21e55c30fbe4214eda37cfc25d782c63f9adc5fbdad6d062a0f127ef2cefc9a44b6fee2b9ea5d1ed10827
languageName: node
linkType: hard
@@ -1295,7 +1232,7 @@ __metadata:
languageName: node
linkType: hard
"ws@npm:^8.18.1, ws@npm:^8.18.2":
"ws@npm:^8.18.2":
version: 8.18.2
resolution: "ws@npm:8.18.2"
peerDependencies:

View File

@@ -1,26 +1,16 @@
FROM ubuntu:24.04
LABEL maintainer="Penpot <docker@penpot.app>"
FROM ubuntu:24.04 AS base
ARG DEBIAN_FRONTEND=noninteractive
ENV NODE_VERSION=v22.14.0 \
CLOJURE_VERSION=1.12.0.1501 \
CLJKONDO_VERSION=2025.01.16 \
BABASHKA_VERSION=1.12.196 \
CLJFMT_VERSION=0.13.0 \
RUSTUP_VERSION=1.27.1 \
RUST_VERSION=1.85.0 \
EMSCRIPTEN_VERSION=4.0.6 \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8
ENV LANG='C.UTF-8' \
LC_ALL='C.UTF-8' \
DEBIAN_FRONTEND=noninteractive
RUN set -ex; \
mkdir -p /etc/resolvconf/resolv.conf.d; \
echo "nameserver 8.8.8.8" > /etc/resolvconf/resolv.conf.d/tail; \
apt-get -qq update; \
apt-get -qqy install --no-install-recommends \
locales \
ca-certificates \
apt-get -qq upgrade; \
apt-get -qqy --no-install-recommends install \
python3 \
unzip \
rsync \
wget \
sudo \
tmux \
@@ -28,97 +18,96 @@ RUN set -ex; \
curl \
bash \
git \
; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen; \
rm -rf /var/lib/apt/lists/*;
\
curl \
ca-certificates \
\
binutils \
build-essential autoconf libtool pkg-config
COPY files/apt.sources /etc/apt/sources.list.d/ubuntu.sources
RUN set -ex; \
usermod -l penpot -d /home/penpot -G users -s /bin/bash ubuntu; \
passwd penpot -d; \
echo "penpot ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
################################################################################
## IMAGE MAGICK
################################################################################
FROM base AS build-imagemagick
ENV IMAGEMAGICK_VERSION=7.1.1-47 \
DEBIAN_FRONTEND=noninteractive
RUN set -ex; \
apt-get -qq update; \
apt-get -qqy install --no-install-recommends \
build-essential \
openssh-client \
redis-tools \
gnupg2 \
rlwrap \
unzip \
rsync \
fakeroot \
file \
less \
jq \
nginx \
\
python3 \
python3-tabulate \
imagemagick \
ghostscript \
netpbm \
poppler-utils \
potrace \
webp \
woff-tools \
woff2 \
fontforge \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgcc1 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxshmfence1 \
libxss1 \
libxtst6 \
fonts-liberation \
libnss3 \
libgbm1 \
xvfb \
libfontconfig-dev \
\
fonts-noto-color-emoji \
fonts-unifont \
libfreetype6 \
xfonts-cyrillic \
xfonts-scalable \
fonts-ipafont-gothic \
fonts-wqy-zenhei \
fonts-tlwg-loma-otf \
fonts-freefont-ttf \
libasound2t64 \
libatk-bridge2.0-0t64 \
libatk1.0-0t64 \
libatspi2.0-0t64 \
libcups2t64 \
libdrm2 \
libxkbcommon0 \
apt-get -qq upgrade; \
apt-get -qqy --no-install-recommends install \
libltdl-dev \
libpng-dev \
libjpeg-dev \
libtiff-dev \
libwebp-dev \
libopenexr-dev \
libfftw3-dev \
libzip-dev \
liblcms2-dev \
liblzma-dev \
libzstd-dev \
libheif-dev \
librsvg2-dev \
; \
rm -rf /var/lib/apt/lists/*;
rm -rf /var/lib/apt/lists/*
RUN set -eux; \
curl -LfsSo /tmp/magick.tar.gz https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${IMAGEMAGICK_VERSION}.tar.gz; \
mkdir -p /tmp/magick; \
cd /tmp/magick; \
tar -xf /tmp/magick.tar.gz --strip-components=1; \
./configure --prefix=/opt/imagick; \
make -j 2; \
make install; \
rm -rf /opt/imagick/lib/libMagick++*; \
rm -rf /opt/imagick/include; \
rm -rf /opt/imagick/share;
################################################################################
## NODE SETUP
################################################################################
FROM base AS setup-node
ENV NODE_VERSION=v22.16.0 \
PATH=/opt/node/bin:$PATH
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
OPENSSL_ARCH='linux-aarch64'; \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
;; \
amd64|x86_64) \
OPENSSL_ARCH='linux-x86_64'; \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \
mkdir -p /opt/node; \
cd /opt/node; \
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
chown -R root /opt/node; \
find /opt/node/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; ; \
corepack enable; \
rm -rf /tmp/nodejs.tar.gz;
################################################################################
## JVM SETUP
################################################################################
FROM base AS setup-jvm
ENV CLOJURE_VERSION=1.12.0.1501
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
@@ -138,132 +127,33 @@ RUN set -eux; \
esac; \
curl -LfsSo /tmp/openjdk.tar.gz ${BINARY_URL}; \
echo "${ESUM} */tmp/openjdk.tar.gz" | sha256sum -c -; \
mkdir -p /usr/lib/jvm/openjdk; \
cd /usr/lib/jvm/openjdk; \
mkdir -p /opt/jdk; \
cd /opt/jdk; \
tar -xf /tmp/openjdk.tar.gz --strip-components=1; \
rm -rf /tmp/openjdk.tar.gz;
ENV PATH="/usr/lib/jvm/openjdk/bin:/usr/local/nodejs/bin:$PATH" JAVA_HOME=/usr/lib/jvm/openjdk
RUN set -ex; \
curl -LfsSo /tmp/clojure.sh https://download.clojure.org/install/linux-install-$CLOJURE_VERSION.sh; \
chmod +x /tmp/clojure.sh; \
/tmp/clojure.sh; \
mkdir -p /opt/clojure; \
/tmp/clojure.sh --prefix /opt/clojure; \
rm -rf /tmp/clojure.sh;
RUN set -ex; \
install -d /usr/share/postgresql-common/pgdg; \
curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc; \
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt noble-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \
apt-get -qq update; \
apt-get -qqy install postgresql-client-16; \
rm -rf /var/lib/apt/lists/*;
################################################################################
## RUST SETUP
################################################################################
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \
mkdir -p /usr/local/nodejs; \
cd /usr/local/nodejs; \
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
chown -R root /usr/local/nodejs; \
corepack enable; \
rm -rf /tmp/nodejs.tar.gz;
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://github.com/clj-kondo/clj-kondo/releases/download/v$CLJKONDO_VERSION/clj-kondo-$CLJKONDO_VERSION-linux-aarch64.zip"; \
;; \
amd64|x86_64) \
BINARY_URL="https://github.com/clj-kondo/clj-kondo/releases/download/v$CLJKONDO_VERSION/clj-kondo-$CLJKONDO_VERSION-linux-amd64.zip"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
cd /tmp; \
curl -LfsSo /tmp/clj-kondo.zip ${BINARY_URL}; \
cd /usr/local/bin; \
unzip /tmp/clj-kondo.zip; \
rm -rf /tmp/clj-kondo.zip;
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://github.com/babashka/babashka/releases/download/v$BABASHKA_VERSION/babashka-$BABASHKA_VERSION-linux-aarch64-static.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://github.com/babashka/babashka/releases/download/v$BABASHKA_VERSION/babashka-$BABASHKA_VERSION-linux-amd64-static.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
cd /tmp; \
curl -LfsSo /tmp/babashka.tar.gz ${BINARY_URL}; \
cd /usr/local/bin; \
tar -xf /tmp/babashka.tar.gz; \
rm -rf /tmp/babashka.tar.gz;
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://github.com/weavejester/cljfmt/releases/download/${CLJFMT_VERSION}/cljfmt-${CLJFMT_VERSION}-linux-aarch64.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://github.com/weavejester/cljfmt/releases/download/${CLJFMT_VERSION}/cljfmt-${CLJFMT_VERSION}-linux-amd64.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
cd /tmp; \
curl -LfsSo /tmp/cljfmt.tar.gz ${BINARY_URL}; \
cd /usr/local/bin; \
tar -xf /tmp/cljfmt.tar.gz; \
rm -rf /tmp/cljfmt.tar.gz;
# Install minio client
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://dl.min.io/client/mc/release/linux-arm64/mc"; \
;; \
amd64|x86_64) \
BINARY_URL="https://dl.min.io/client/mc/release/linux-amd64/mc"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
wget -O /tmp/mc ${BINARY_URL}; \
mv /tmp/mc /usr/local/bin/; \
chmod +x /usr/local/bin/mc;
WORKDIR /usr/local
FROM base AS setup-rust
# Install Rust toolchain
ENV PATH=/usr/local/cargo/bin:$PATH RUSTUP_HOME=/usr/local/rustpo CARGO_HOME=/usr/local/cargo
ENV PATH=/opt/cargo/bin:$PATH \
RUSTUP_HOME=/opt/rustup \
CARGO_HOME=/opt/cargo \
RUSTUP_VERSION=1.27.1 \
RUST_VERSION=1.85.0 \
EMSCRIPTEN_VERSION=4.0.6
WORKDIR /opt
RUN set -eux; \
# Same steps as in Rust official Docker image https://github.com/rust-lang/docker-rust/blob/9f287282d513a84cb7c7f38f197838f15d37b6a9/1.81.0/bookworm/Dockerfile
@@ -285,8 +175,217 @@ RUN set -eux; \
./emsdk install $EMSCRIPTEN_VERSION; \
./emsdk activate $EMSCRIPTEN_VERSION; \
rustup target add wasm32-unknown-emscripten; \
cargo install cargo-watch; \
chown -R penpot:users $CARGO_HOME;
cargo install cargo-watch;
################################################################################
## UTILS SETUP
################################################################################
FROM base AS setup-utils
ENV CLJKONDO_VERSION=2025.01.16 \
BABASHKA_VERSION=1.12.196 \
CLJFMT_VERSION=0.13.0
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://github.com/clj-kondo/clj-kondo/releases/download/v$CLJKONDO_VERSION/clj-kondo-$CLJKONDO_VERSION-linux-aarch64.zip"; \
;; \
amd64|x86_64) \
BINARY_URL="https://github.com/clj-kondo/clj-kondo/releases/download/v$CLJKONDO_VERSION/clj-kondo-$CLJKONDO_VERSION-linux-amd64.zip"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
cd /tmp; \
curl -LfsSo /tmp/clj-kondo.zip ${BINARY_URL}; \
mkdir -p /opt/utils/bin; \
cd /opt/utils/bin; \
unzip /tmp/clj-kondo.zip; \
rm -rf /tmp/clj-kondo.zip;
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://github.com/babashka/babashka/releases/download/v$BABASHKA_VERSION/babashka-$BABASHKA_VERSION-linux-aarch64-static.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://github.com/babashka/babashka/releases/download/v$BABASHKA_VERSION/babashka-$BABASHKA_VERSION-linux-amd64-static.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
cd /tmp; \
curl -LfsSo /tmp/babashka.tar.gz ${BINARY_URL}; \
cd /opt/utils/bin; \
tar -xf /tmp/babashka.tar.gz; \
rm -rf /tmp/babashka.tar.gz;
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://github.com/weavejester/cljfmt/releases/download/${CLJFMT_VERSION}/cljfmt-${CLJFMT_VERSION}-linux-aarch64.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://github.com/weavejester/cljfmt/releases/download/${CLJFMT_VERSION}/cljfmt-${CLJFMT_VERSION}-linux-amd64.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
cd /tmp; \
curl -LfsSo /tmp/cljfmt.tar.gz ${BINARY_URL}; \
cd /opt/utils/bin; \
tar -xf /tmp/cljfmt.tar.gz; \
rm -rf /tmp/cljfmt.tar.gz;
# Install minio client
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://dl.min.io/client/mc/release/linux-arm64/mc"; \
;; \
amd64|x86_64) \
BINARY_URL="https://dl.min.io/client/mc/release/linux-amd64/mc"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
wget -O /tmp/mc ${BINARY_URL}; \
mv /tmp/mc /opt/utils/bin/; \
chmod +x /opt/utils/bin/mc;
################################################################################
## DEVENV BASE
################################################################################
FROM base AS devenv-base
RUN set -ex; \
usermod -l penpot -d /home/penpot -G users -s /bin/bash ubuntu; \
passwd penpot -d; \
echo "penpot ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
RUN set -ex; \
apt-get -qq update; \
apt-get -qqy install --no-install-recommends \
redis-tools \
gnupg2 \
rlwrap \
file \
less \
jq \
nginx \
\
fontconfig \
woff-tools \
woff2 \
python3-tabulate \
fontforge \
\
xvfb \
fonts-noto-color-emoji \
fonts-unifont \
libfontconfig1 \
libfontconfig-dev \
libfreetype6 \
libfreetype-dev \
xfonts-cyrillic \
xfonts-scalable \
fonts-liberation \
fonts-ipafont-gothic \
fonts-wqy-zenhei \
fonts-tlwg-loma-otf \
fonts-freefont-ttf \
\
libasound2t64 \
libatk-bridge2.0-0t64 \
libatk1.0-0t64 \
libatspi2.0-0t64 \
libcairo2 \
libcups2t64 \
libdbus-1-3 \
libdrm2 \
libgbm1 \
libglib2.0-0t64 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libx11-6 \
libxcb1 \
libxcomposite1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxkbcommon0 \
libxrandr2 \
\
libpng16-16 \
libjpeg-turbo8 \
libtiff6 \
libwebp7 \
libopenexr-3-1-30 \
libfreetype6 \
libfontconfig1 \
libglib2.0-0 \
libxml2 \
liblcms2-2 \
libheif1 \
libopenjp2-7 \
libzstd1 \
librsvg2-2 \
libgomp1 \
libwebpmux3 \
libwebpdemux2 \
libzip4t64 \
; \
rm -rf /var/lib/apt/lists/*;
RUN set -ex; \
install -d /usr/share/postgresql-common/pgdg; \
curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc; \
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt noble-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \
apt-get -qq update; \
apt-get -qqy install postgresql-client-16; \
rm -rf /var/lib/apt/lists/*;
################################################################################
## DEVENV
################################################################################
FROM devenv-base AS devenv
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG='C.UTF-8' \
LC_ALL='C.UTF-8' \
DEBIAN_FRONTEND="noninteractive" \
JAVA_HOME="/opt/jdk" \
CARGO_HOME="/opt/cargo" \
RUSTUP_HOME="/opt/rustup" \
PATH="/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH"
COPY --from=build-imagemagick /opt/imagick /opt/imagick
COPY --from=setup-jvm /opt/jdk /opt/jdk
COPY --from=setup-jvm /opt/clojure /opt/clojure
COPY --from=setup-node /opt/node /opt/node
COPY --from=setup-utils /opt/utils /opt/utils
COPY --from=setup-rust /opt/cargo /opt/cargo
COPY --from=setup-rust /opt/rustup /opt/rustup
COPY --from=setup-rust /opt/emsdk /opt/emsdk
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/nginx-mime.types /etc/nginx/mime.types
@@ -303,4 +402,4 @@ COPY files/entrypoint.sh /home/entrypoint.sh
COPY files/init.sh /home/init.sh
ENTRYPOINT ["/home/entrypoint.sh"]
CMD ["/home/init.sh"]
CMD ["/home/init.sh"]

View File

@@ -96,6 +96,10 @@ services:
- ./files/postgresql.conf:/etc/postgresql.conf:z
- ./files/postgresql_init.sql:/docker-entrypoint-initdb.d/init.sql:z
- postgres_data_pg16:/var/lib/postgresql/data
networks:
default:
aliases:
- postgres
redis:
image: valkey/valkey:8.1

View File

@@ -1,5 +1,10 @@
#!/usr/bin/env bash
EMSDK_QUIET=1 . /opt/emsdk/emsdk_env.sh;
export PATH="/home/penpot/.cargo/bin:/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH"
export CARGO_HOME="/home/penpot/.cargo"
alias l='ls --color -GFlh'
alias rm='rm -r'
alias ls='ls --color -F'

View File

@@ -2,7 +2,7 @@
set -e
EMSDK_QUIET=1 . /usr/local/emsdk/emsdk_env.sh;
EMSDK_QUIET=1 . /opt/emsdk/emsdk_env.sh;
usermod -u ${EXTERNAL_UID:-1000} penpot;
@@ -10,8 +10,8 @@ cp /root/.bashrc /home/penpot/.bashrc
cp /root/.vimrc /home/penpot/.vimrc
cp /root/.tmux.conf /home/penpot/.tmux.conf
chown -R penpot:users /home/penpot
rsync -ar --chown=penpot:users /usr/local/cargo/ /home/penpot/.cargo/
chown penpot:users /home/penpot
rsync -ar --chown=penpot:users /opt/cargo/ /home/penpot/.cargo/
export PATH="/home/penpot/.cargo/bin:$PATH"
export CARGO_HOME="/home/penpot/.cargo"

View File

@@ -1,49 +1,51 @@
FROM ubuntu:24.04
FROM ubuntu:24.04 AS build
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG='en_US.UTF-8' \
LC_ALL='en_US.UTF-8' \
ENV LANG='C.UTF-8' \
LC_ALL='C.UTF-8' \
JAVA_HOME="/opt/jdk" \
PATH=/opt/jdk/bin:/opt/node/bin:$PATH \
DEBIAN_FRONTEND=noninteractive \
NODE_VERSION=v20.18.0 \
NODE_VERSION=v22.16.0 \
IMAGEMAGICK_VERSION=7.1.1-47 \
TZ=Etc/UTC
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
apt-get -qq update; \
apt-get -qq upgrade; \
apt-get -qqy --no-install-recommends install \
nano \
curl \
tzdata \
locales \
ca-certificates \
imagemagick \
webp \
rlwrap \
fontconfig \
woff-tools \
woff2 \
python3 \
python3-tabulate \
fontforge \
binutils \
build-essential autoconf libtool pkg-config \
libltdl-dev \
libpng-dev libjpeg-dev libtiff-dev libwebp-dev libopenexr-dev libfftw3-dev \
libzip-dev \
liblcms2-dev liblzma-dev libzstd-dev \
libheif-dev librsvg2-dev \
; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen; \
mkdir -p /opt/data/assets; \
mkdir -p /opt/penpot; \
chown -R penpot:penpot /opt/penpot; \
chown -R penpot:penpot /opt/data; \
rm -rf /var/lib/apt/lists/*;
rm -rf /var/lib/apt/lists/*
RUN set -eux; \
curl -LfsSo /tmp/magick.tar.gz https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${IMAGEMAGICK_VERSION}.tar.gz; \
mkdir -p /tmp/magick; \
cd /tmp/magick; \
tar -xf /tmp/magick.tar.gz --strip-components=1; \
./configure --prefix=/opt/imagick; \
make -j 2; \
make install; \
rm -rf /opt/imagick/lib/libMagick++*; \
rm -rf /opt/imagick/include; \
rm -rf /opt/imagick/share;
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
OPENSSL_ARCH='linux-aarch64'; \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
;; \
amd64|x86_64) \
OPENSSL_ARCH='linux-x86_64'; \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
;; \
*) \
@@ -56,18 +58,19 @@ RUN set -eux; \
cd /opt/node; \
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
chown -R root /opt/node; \
find /opt/node/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; ; \
rm -rf /tmp/nodejs.tar.gz;
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
ESUM='3ce6a2b357e2ef45fd6b53d6587aa05bfec7771e7fb982f2c964f6b771b7526a'; \
BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.2_13.tar.gz'; \
ESUM='18071047526ab4b53131f9bb323e8703485ae37fcb2f2c5ef0f1b7bab66d1b94'; \
BINARY_URL='https://github.com/adoptium/temurin24-binaries/releases/download/jdk-24%2B36/OpenJDK24U-jdk_aarch64_linux_hotspot_24_36.tar.gz'; \
;; \
amd64|x86_64) \
ESUM='454bebb2c9fe48d981341461ffb6bf1017c7b7c6e15c6b0c29b959194ba3aaa5'; \
BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_x64_linux_hotspot_21.0.2_13.tar.gz'; \
ESUM='c340dee97b6aa215d248bc196dcac5b56e7be9b5c5d45e691344d40d5d0b171d'; \
BINARY_URL='https://github.com/adoptium/temurin24-binaries/releases/download/jdk-24%2B36/OpenJDK24U-jdk_x64_linux_hotspot_24_36.tar.gz'; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
@@ -79,8 +82,69 @@ RUN set -eux; \
mkdir -p /opt/jdk; \
cd /opt/jdk; \
tar -xf /tmp/openjdk.tar.gz --strip-components=1; \
rm -rf /tmp/openjdk.tar.gz;
rm -rf /tmp/openjdk.tar.gz; \
/opt/jdk/bin/jlink \
--no-header-files \
--no-man-pages \
--strip-debug \
--add-modules java.base,jdk.management.agent,java.se,jdk.compiler,jdk.javadoc,jdk.attach,jdk.unsupported \
--output /opt/jre;
FROM ubuntu:24.04 AS image
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG='C.UTF-8' \
LC_ALL='C.UTF-8' \
JAVA_HOME="/opt/jre" \
PATH=/opt/jre/bin:/opt/node/bin:/opt/imagick/bin:$PATH \
DEBIAN_FRONTEND=noninteractive \
TZ=Etc/UTC
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
apt-get -qq update; \
apt-get -qq upgrade; \
apt-get -qqy --no-install-recommends install \
tzdata \
ca-certificates \
fontconfig \
woff-tools \
woff2 \
python3 \
python3-tabulate \
fontforge \
\
libpng16-16 \
libjpeg-turbo8 \
libtiff6 \
libwebp7 \
libopenexr-3-1-30 \
libfreetype6 \
libfontconfig1 \
libglib2.0-0 \
libxml2 \
liblcms2-2 \
libheif1 \
libopenjp2-7 \
libzstd1 \
librsvg2-2 \
libgomp1 \
libwebpmux3 \
libwebpdemux2 \
libzip4t64 \
; \
find tmp/usr/share/zoneinfo/* -type d ! -name 'Etc' |xargs rm -rf; \
rm -rf /var/lib /var/cache; \
rm -rf /usr/include; \
mkdir -p /opt/data/assets; \
mkdir -p /opt/penpot; \
chown -R penpot:penpot /opt/penpot; \
chown -R penpot:penpot /opt/data; \
rm -rf /var/lib/apt/lists/*;
COPY --from=build /opt/jre /opt/jre
COPY --from=build /opt/node /opt/node
COPY --from=build /opt/imagick /opt/imagick
COPY --chown=penpot:penpot ./bundle-backend/ /opt/penpot/backend/
USER penpot:penpot

View File

@@ -3,7 +3,7 @@ LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 \
NODE_VERSION=v20.11.1 \
NODE_VERSION=v22.16.0 \
DEBIAN_FRONTEND=noninteractive \
PATH=/opt/node/bin:$PATH
@@ -17,56 +17,50 @@ RUN set -ex; \
tzdata \
locales \
ca-certificates \
fontconfig \
xz-utils \
; \
rm -rf /var/lib/apt/lists/*; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen;
locale-gen; \
find /usr/share/i18n/locales/ -type f ! -name "en_US" ! -name "POSIX" ! -name "C" -delete;
RUN set -ex; \
apt-get -qq update; \
apt-get -qqy install \
imagemagick \
ghostscript \
netpbm \
poppler-utils \
potrace \
dconf-service \
libasound2t64 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libatomic1 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
\
xvfb \
fonts-noto-color-emoji \
fonts-unifont \
libfontconfig1 \
libgcc1 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk-3-0 \
libfreetype6 \
xfonts-cyrillic \
xfonts-scalable \
fonts-liberation \
fonts-ipafont-gothic \
fonts-wqy-zenhei \
fonts-tlwg-loma-otf \
fonts-freefont-ttf \
\
libasound2t64 \
libatk-bridge2.0-0t64 \
libatk1.0-0t64 \
libatspi2.0-0t64 \
libcairo2 \
libcups2t64 \
libdbus-1-3 \
libdrm2 \
libgbm1 \
libglib2.0-0t64 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcb-dri3-0 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxkbcommon0 \
libxrandr2 \
libxrender1 \
libxshmfence1 \
libxss1 \
libxtst6 \
fonts-liberation \
libnss3 \
libgbm1 \
; \
rm -rf /var/lib/apt/lists/*;
@@ -89,8 +83,8 @@ RUN set -eux; \
cd /opt/node; \
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
chown -R root /opt/node; \
corepack enable; \
rm -rf /tmp/nodejs.tar.gz; \
corepack enable; \
mkdir -p /opt/penpot; \
chown -R penpot:penpot /opt/penpot;
@@ -100,7 +94,9 @@ WORKDIR /opt/penpot/exporter
USER penpot:penpot
RUN set -ex; \
corepack install; \
yarn install; \
yarn run playwright install chromium;
yarn run playwright install chromium; \
rm -rf /opt/penpot/.yarn
CMD ["node", "app.js"]

View File

@@ -111,7 +111,7 @@ services:
depends_on:
penpot-postgres:
condition: service_healthy
penpot-redis:
penpot-valkey:
condition: service_healthy
networks:
@@ -148,10 +148,10 @@ services:
PENPOT_DATABASE_USERNAME: penpot
PENPOT_DATABASE_PASSWORD: penpot
## Redis is used for the websockets notifications. Don't touch unless the redis
## container has different parameters or different name.
## Valkey (or previously redis) is used for the websockets notifications. Don't touch
## unless the valkey container has different parameters or different name.
PENPOT_REDIS_URI: redis://penpot-redis/0
PENPOT_REDIS_URI: redis://penpot-valkey/0
## Default configuration for assets storage: using filesystem based with all files
## stored in a docker volume.
@@ -195,7 +195,7 @@ services:
restart: always
depends_on:
penpot-redis:
penpot-valkey:
condition: service_healthy
networks:
@@ -206,8 +206,8 @@ services:
# communicate with the frontend.
PENPOT_PUBLIC_URI: http://penpot-frontend:8080
## Redis is used for the websockets notifications.
PENPOT_REDIS_URI: redis://penpot-redis/0
## Valkey (or previously Redis) is used for the websockets notifications.
PENPOT_REDIS_URI: redis://penpot-valkey/0
penpot-postgres:
image: "postgres:15"
@@ -233,12 +233,12 @@ services:
- POSTGRES_USER=penpot
- POSTGRES_PASSWORD=penpot
penpot-redis:
image: redis:7.2
penpot-valkey:
image: valkey/valkey:8.1
restart: always
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
test: ["CMD-SHELL", "valkey-cli ping | grep PONG"]
interval: 1s
timeout: 3s
retries: 5

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -140,7 +140,8 @@ The <code class="language-js">manifest.json</code> file contains the basic infor
"user:read",
"comment:read",
"comment:write",
"allow:downloads"
"allow:downloads",
"allow:localstorage"
]
}
```
@@ -173,6 +174,9 @@ Typical use cases: adding new comments to pages; deleting existing comments; rep
- <code class="language-js">allow:downloads</code>: Allows downloading of the project file. Grants access to endpoints and operations that enable the downloading of the entire project file.
Typical use cases: downloading the full project file for backup or sharing.
- <code class="language-js">allow:localstorage</code>: Allows the access to the local storage proxy to store information. This info is only available for the plugin installation but be aware that a user can see this information in the browser.
Typical use cases: storing authentication tokens for a plugin login
_Note: Write permissions automatically includes its corresponding read permission (e.g., <code class="language-js">content:write</code> includes <code class="language-js">content:read</code>) because reading is required to perform write or modification actions._
### What are plugin.ts and plugin.js files?

View File

@@ -353,9 +353,9 @@ If you are not using SMTP configuration and want to log the emails in the consol
PENPOT_FLAGS: [...] enable-log-emails
```
## Redis
## Valkey
The Redis configuration is very simple, just provide a valid redis URI. Redis is used
The Valkey configuration is very simple, just provide a valid redis URI. Valkey is used
mainly for websocket notifications coordination.
```bash

View File

@@ -4,14 +4,22 @@ title: 3.06. Backend Guide
# Backend guide #
This guide intends to explain the essential details of the backend
application.
This guide collects some basic information on the backend application.
## REPL ##
In the devenv environment you can execute <code class="language-clojure">scripts/repl</code> to open a
Clojure interactive shell ([REPL](https://codewith.mu/en/tutorials/1.0/repl)).
_Note:_ When in development mode, the backend spins up a traditional nREPL socket on port 6064.
If you are experimenting locally, you can connect to it using your Clojure editor or
with `backend/scripts/nrepl`, which starts a [REPLy client](https://github.com/trptcolin/reply),
[see here][1] for more information.
[1]: /technical-guide/developer/devenv/#backend
In the devenv environment you can execute `backend/scripts/repl` to open a
Clojure interactive shell ([REPL](https://codewith.mu/en/tutorials/1.0/repl)) (this is not a socket-based
REPL, but a local, in-process console (over stdin/stdout) with some fancy line-editing and colors). Note
that the backend must be stopped before executing this script, otherwise it will fail with `Port already
in use: 9090`.
Once there, you can execute <code class="language-clojure">(restart)</code> to load and execute the backend
process, or to reload it after making changes to the source code.
@@ -39,11 +47,11 @@ For example:
## Fixtures ##
This is a development feature that allows populate the database with a
good amount of content (usually used for just test the application or
perform performance tweaks on queries).
This is a development feature that allows populating the database with a
good amount of content (typically used to test the application or to run
performance tweaks on queries).
In order to load fixtures, enter to the REPL environment with the <code class="language-clojure">scripts/repl</code>
In order to load fixtures, enter the REPL environment with the <code class="language-clojure">backend/scripts/repl</code>
script, and then execute <code class="language-clojure">(app.cli.fixtures/run {:preset :small})</code>.
You also can execute this as a standalone script with:
@@ -52,11 +60,11 @@ You also can execute this as a standalone script with:
clojure -Adev -X:fn-fixtures
```
NOTE: It is an optional step because the application can start with an
_NOTE:_ This is an optional step because the application can start with an
empty database.
This by default will create a bunch of users that can be used to login
in the application. All users uses the following pattern:
The above will create several users that can be used to login
into the application. All of them follow the pattern:
- Username: <code class="language-text">profileN@example.com</code>
- Password: <code class="language-text">123123</code>

View File

@@ -170,6 +170,23 @@ similar to a webmail client. Simply navigate to:
[http://localhost:1080](http://localhost:1080)
## Create user
You can register a new user manually, or create new users automatically with this script. From your tmux instance, run:
```sh
cd penpot/backend/scripts
python3 manage.py create-profile
```
You can also skip tutorial and walkthrough steps:
```sh
python3 manage.py create-profile --skip-tutorial --skip-walkthrough
python3 manage.py create-profile -n "Jane Doe" -e jane@example.com -p secretpassword --skip-tutorial --skip-walkthrough
```
## Team Feature Flags
To test a Feature Flag, you can enable or disable them by team through the `dbg` page:

View File

@@ -89,6 +89,13 @@ For instance, if the registration is disabled, the only way to create a new use
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile
```
or
```bash
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile --skip-tutorial --skip-walkthrough
```
**NOTE:** the exact container name depends on your docker version and platform.
For example it could be <code class="language-bash">penpot-penpot-backend-1</code> or <code class="language-bash">penpot_penpot-backend-1</code>.
You can check the correct name executing <code class="language-bash">docker ps</code>.

View File

@@ -72,7 +72,7 @@ argument to helm install. For example,
```bash
helm install my-release \
--set global.postgresqlEnabled=true \
--set global.redisEnabled=true \
--set global.valkeyEnabled=true \
--set persistence.assets.enabled=true \
penpot/penpot
```

View File

@@ -367,12 +367,97 @@ title: 10· Design Tokens
<h2 id="design-tokens-import-export">Importing and Exporting Tokens</h2>
<p>You can export Tokens from Penpot and import them from your computer to a Penpot file. Tokens can be imported from the <strong>Tools</strong> option at the bottom of the <strong>Tokens</strong> tab.</p>
<p>The <strong>Import</strong> functionality allows you to upload and replace the global token set using a single file, while the <strong>Export</strong> functionality lets you download the current global token set using a single file to your system.</p>
<p>The <strong>Import</strong> functionality allows you to upload and replace the global token set using a single file or a folder with multiple files in it.</p>
<p>These features support JSON files formatted according to specific guidelines and preserve the ability to undo changes if needed.</p>
<figure>
<img src="/img/design-tokens/21-tokens-import-export.webp" alt="Tokens import export" />
</figure>
<ol>
<li><strong>Import:</strong> At the <strong>Tools</strong> option, select <strong>Import</strong>, then select your <code class="language-js">tokens.json</code> file. </li>
<li><strong>Export:</strong> At the <strong>Tools</strong> option, select <strong>Export</strong>. This will export all the tokens, including token sets and themes.</li>
<li><strong>Import:</strong> Click <strong>Tools</strong>, then select <strong>Import</strong> to view import options. </li>
<li><strong>Export:</strong> Click <strong>Tools</strong>, then select <strong>Export</strong> to view export options.</li>
</ol>
<h3 id="design-tokens-import-options">Import Options</h3>
<h4>Single file</h4>
<p>You can import a JSON file comprising all tokens, token sets and token themes.</p>
<p>When importing a single file, the first-level keys of the json file will be interpreted as the set name.</p>
<pre class="language-json">
<code class="language-json">
{
"Global": {
// first-level key will be interpreted as set name
"color": {
"300": {
"$value": "red",
"$type": "color",
"$description": "my token description"
}
}
},
"Brands/A": {
// first-level key will be interpreted as set name
"color": {
"accent": {
"$value": "{red}",
"$type": "color",
"$description": "my token description"
}
}
},
"Brands/B": {
// first-level key will be interpreted as set name
"color": {
"accent": {
"$value": "#fabada",
"$type": "color",
"$description": "my token description"
}
}
}
}
</code>
</pre>
<h4>Multifile</h4>
<p>Imports a folder containing multiple JSON files (one per Token Set) and additional files like <code class="language-json">$themes.json</code> or <code class="language-json">$metadata.json</code> configurations. When importing multiple files, the name and path of the individual json files inside the folder will be interpreted as set names. These files should only contain tokens.</p>
<p>Multifile folder structure example:</p>
<code>
<pre>
folder/
├── global/
│ ├── colors.json // can only contain tokens
│ └── dimension.json // can only contain tokens
├── mode/
│ ├── dark.json // can only contain tokens
│ └── light.json // can only contain tokens
├── $themes.json // themes config
└── $metadata.json // other metadata config
</code>
</pre>
<figcaption>The main folder name wont be used to build token set names, so in this example, <strong>folder</strong> will be ignored in the set names.</figcaption>
<h3 id="design-tokens-export-options">Export Options</h3>
<p>Just like with importing, you can export tokens, themes and sets either in a single JSON file or in multiple files. There is no difference in the content being exported; the choice depends on your team's preferences for file organization: a single file with all the tokens, sets and themes, or a folder structure with separated JSON files organized by sets.</p>
<p>In both cases you can preview the result of the export options:</p>
<figure>
<img src="/img/design-tokens/22-tokens-export-multiple.webp" alt="Tokens export with multiple files" />
<figcaption>Exporting tokens as multiple files.</figcaption>
</figure>
<figure>
<img src="/img/design-tokens/23-tokens-export-single.webp" alt="Tokens export with single file" />
<figcaption>Exporting tokens as a single file.</figcaption>
</figure>

View File

@@ -281,12 +281,15 @@ press <kbd>Shift/⇧</kbd> + left click over the right arrow of a group or a boa
<h2 id="focus-mode">Focus mode</h2>
<p>Select the elements of a page you want to work with in a specific moment hiding the rest so they dont get in the way of your attention. This option is also useful to improve the performance in cases where the page has a large number of elements.</p>
<p>Focus mode zooms into the elements of a page you want to work with in a specific moment, and hides the rest so that they dont get in the way. When the page has many elements, focus mode can also improve performance.</p>
<p>To activate focus mode:</p>
<ol>
<ol>
<li>Select one or more elements.</li>
<li>Right click to show the menu and select the option "Focus on" or press <kbd>F</kbd>.</li>
<li>Right click on the selection to show the menu and select the option Focus on or press <kbd>F</kbd>.</li>
</ol>
<p>Notice that the layer panel will now only show the focused layers. A focus mode status line will also appear at the top.</p>
<p>To exit focus mode and return to the original viewport and selection, right click anywhere and select “Focus off” or just press <kbd>F</kbd> again. You can also click anywhere on the focus mode status line at the top of the layer panel.
</p>
<figure>
<video title="Focus mode" muted="" playsinline="" controls="" width="100%" poster="/img/layers/layers-focus.webp" height="auto">
<source src="/img/layers/layers-focus.mp4" type="video/mp4">

View File

@@ -14,7 +14,7 @@
:dev
{:extra-deps
{thheller/shadow-cljs {:mvn/version "3.1.4"}}}
{thheller/shadow-cljs {:mvn/version "3.1.5"}}}
:shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"
@@ -16,15 +16,15 @@
"inflation": "^2.1.0",
"ioredis": "^5.6.1",
"luxon": "^3.6.1",
"playwright": "^1.52.0",
"playwright": "^1.53.0",
"raw-body": "^3.0.0",
"svgo": "penpot/svgo#v3.1",
"xml-js": "^1.6.11",
"xregexp": "^5.1.2"
},
"devDependencies": {
"shadow-cljs": "3.0.5",
"source-map-support": "^0.5.21"
"source-map-support": "^0.5.21",
"ws": "^8.18.2"
},
"scripts": {
"fmt:clj:check": "cljfmt check --parallel=false src/",

View File

@@ -6,7 +6,7 @@ export CURRENT_VERSION=$1;
export NODE_ENV=production;
corepack enable;
corepack up || exit 1;
corepack install || exit 1;
yarn install || exit 1;
rm -rf target

View File

@@ -47,7 +47,7 @@
(s/def ::params
(s/keys :req-un [::exports ::profile-id]
:opt-un [::wait ::name]))
:opt-un [::wait ::name ::skip-children]))
(defn handler
[{:keys [:request/auth-token] :as exchange} {:keys [exports] :as params}]
@@ -60,7 +60,7 @@
(handle-multiple-export exchange (assoc params :exports exports)))))
(defn- handle-single-export
[exchange {:keys [export wait profile-id name] :as params}]
[exchange {:keys [export wait profile-id name skip-children] :as params}]
(let [topic (str profile-id)
resource (rsc/create (:type export) (or name (:name export)))
@@ -90,7 +90,7 @@
:resource-id (:id resource)
:status "error"
:cause (ex-message cause)})))
export (assoc export :skip-children skip-children)
proc (-> (rd/render export on-progress)
(p/then (constantly resource))
(p/catch on-error))]
@@ -99,7 +99,7 @@
(assoc exchange :response/body (dissoc resource :path)))))
(defn- handle-multiple-export
[exchange {:keys [exports wait profile-id name] :as params}]
[exchange {:keys [exports wait profile-id name skip-children] :as params}]
(let [resource (rsc/create :zip (or name (-> exports first :name)))
total (count exports)
topic (str profile-id)
@@ -141,7 +141,8 @@
proc (-> (p/do
(p/loop [exports (seq exports)]
(when-let [export (first exports)]
(when-let [export (some-> (first exports)
(assoc :skip-children skip-children))]
(p/do
(rd/render export append)
(p/recur (rest exports)))))

View File

@@ -17,7 +17,7 @@
[promesa.core :as p]))
(defn render
[{:keys [file-id page-id share-id token scale type objects] :as params} on-object]
[{:keys [file-id page-id share-id token scale type objects skip-children] :as params} on-object]
(letfn [(prepare-options [uri]
#js {:screen #js {:width bw/default-viewport-width
:height bw/default-viewport-height}
@@ -56,7 +56,8 @@
:page-id page-id
:share-id share-id
:object-id (mapv :id objects)
:route "objects"}
:route "objects"
:skip-children skip-children}
uri (-> (cf/get :public-uri)
(assoc :path "/render.html")
(assoc :query (u/map->query-string params)))]

View File

File diff suppressed because it is too large Load Diff

1
frontend/.gitignore vendored
View File

@@ -11,3 +11,4 @@ node_modules/
/blob-report/
/playwright/.cache/
/playwright/**/visual-specs/**/*.png

View File

@@ -20,8 +20,8 @@
:git/url "https://github.com/funcool/beicon.git"}
funcool/rumext
{:git/tag "v2.22"
:git/sha "92879b6"
{:git/tag "v2.24"
:git/sha "17a0c94"
:git/url "https://github.com/funcool/rumext.git"}
instaparse/instaparse {:mvn/version "1.5.0"}
@@ -42,7 +42,7 @@
:dev
{:extra-paths ["dev"]
:extra-deps
{thheller/shadow-cljs {:mvn/version "3.1.5"}
{thheller/shadow-cljs {:mvn/version "3.1.7"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c",
"browserslist": [
"defaults"
],
@@ -90,7 +90,6 @@
"rimraf": "^6.0.1",
"sass": "^1.89.0",
"sass-embedded": "^1.89.0",
"shadow-cljs": "3.1.5",
"storybook": "^8.6.14",
"svg-sprite": "^2.0.4",
"typescript": "^5.8.3",
@@ -104,6 +103,7 @@
"@penpot/draft-js": "portal:./vendor/draft-js",
"@penpot/hljs": "portal:./vendor/hljs",
"@penpot/mousetrap": "portal:./vendor/mousetrap",
"@penpot/plugins-runtime": "1.3.2",
"@penpot/svgo": "penpot/svgo#v3.1",
"@penpot/text-editor": "portal:./text-editor",
"@tokens-studio/sd-transforms": "1.2.11",

View File

@@ -53,6 +53,21 @@ export default defineConfig({
toHaveScreenshot: { maxDiffPixelRatio: 0.005 },
},
},
{
name: "render-wasm",
use: {
...devices["Desktop Chrome"],
viewport: { width: 1920, height: 1080 }, // Add custom viewport size
deviceScaleFactor: 2,
},
testDir: "./playwright/ui/render-wasm-specs",
snapshotPathTemplate: "{testDir}/{testFilePath}-snapshots/{arg}.png",
expect: {
toHaveScreenshot: {
maxDiffPixelRatio: 0.001,
},
},
},
],
/* Run your local dev server before starting the tests */

View File

@@ -0,0 +1,5 @@
{
"~:type": "~:restriction",
"~:code": "~:email-does-not-match-invitation",
"~:hint": "email should match the invitation"
}

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

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