Compare commits

...

310 Commits

Author SHA1 Message Date
Andrey Antukh
d4b7f231c7 🔧 Add missing config for on commit checker 2025-09-29 12:05:09 +02:00
Eva Marco
e184a9a8b9 🐛 Fix context menu on spacing tokens (#7382) 2025-09-25 17:28:46 +02:00
Eva Marco
a4ada6dc8a 🐛 Add default flags for tokens (#7367) 2025-09-25 08:47:04 +02:00
Andrey Antukh
11b75408fe 🐛 Fix regression on importing binfile-v1 files (#7359) 2025-09-23 11:38:33 +02:00
Andrey Antukh
59f7ede4ff 🐛 Add migration for properly decode all position data on text shapes 2025-09-23 11:34:24 +02:00
Andrés Moya
8954b05d76 🐛 Fix error exporting a file with deleted tokens (#7356) 2025-09-22 17:41:31 +02:00
Andrey Antukh
cb4c155b32 📎 Uncomment previously commented migrations 2025-09-22 11:38:52 +02:00
Andrey Antukh
0b346e02ff 🐛 Fix incorrect options pass on decode-file 2025-09-22 11:30:42 +02:00
Andrey Antukh
946f641917 📎 Disable possible problematic migrations 2025-09-22 11:12:43 +02:00
Andrey Antukh
e88039e46a 🐛 Fix future linter issues on wasm shape impl 2025-09-17 16:53:02 +02:00
Andrey Antukh
3c45a8d0b4 Allow delete subscriptions on profile deletion request 2025-09-17 16:53:02 +02:00
Aitor Moreno
c9d71f3b2d 🐛 Fix conflicting shortcuts (text alignment) (#7339) 2025-09-17 16:52:44 +02:00
Andrey Antukh
9f37175775 🐛 Fix incorrect path data content initialization on pluings api 2025-09-17 15:19:41 +02:00
andrés gonzález
5ed870cc6e 📚 Update shortcuts docs (#7341) 2025-09-17 14:13:00 +02:00
Pablo Alba
2a3d7e470d 📚 Update changelog with variants info (#7335) 2025-09-17 13:45:59 +02:00
Eva Marco
f654eb2dcd 🐛 Fix font weight input placehoder (#7338) 2025-09-17 13:44:11 +02:00
Belén Albeza
c21d705143 🐛 Remove shortcuts for inc/dec line height and letter spacing (#7337) 2025-09-17 12:35:39 +02:00
Andrey Antukh
85c1750706 🐛 Fix backend last migration naming (#7333) 2025-09-17 10:47:14 +02:00
Luis de Dios
e2151409bf 🐛 Fix wrong number of components in the library modal (#7332) 2025-09-17 09:25:23 +02:00
Luis de Dios
4fe6cfc57a 🐛 Fix focus the first property value when creating a variant (#7329) 2025-09-16 23:25:18 +02:00
Andrés Moya
2eed7444b7 🔧 Add migration to automatically fix validation errors 2025-09-16 16:11:58 +02:00
Luis de Dios
ef376fbb7b Add shortcut for creating variant to the shortcuts panel (#7319)
*  Add shortcut for creating variant to the shortcuts panel

* ♻️ Update components to new rumext syntax

* 🐛 Fix unique "key" prop error for each child in a list

* ♻️ Remove deprecated icons and CSS cleanup

* 📎 PR changes
2025-09-16 14:06:47 +02:00
Pablo Alba
18d5b84b00 🐛 Fix variants events (#7320)
* 🐛 Add missing event add-component-to-variant

* 🐛 Fix event apply-tokens, param applied-to-variant

* 🐛 Fix missing case on event "add new variant"

* 🐛 Fix event combine-as-variants

* 🐛 Fix event variant-edit-property-name

* 🐛 On variants events, change trigger for origin

* 🐛 Split combine-as-variants to not have an optional first parameter
2025-09-16 13:09:23 +02:00
Aitor Moreno
c62fadac47 🐛 Fix fast move with distance (#7302)
* 🐛 Fix fast move with distance

* 📎 Remove duplicated shourtcuts

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-09-16 11:36:21 +02:00
Andrés Moya
a264f84e6c 🔧 Deactivate validation temporarily 2025-09-16 11:34:38 +02:00
Luis de Dios
9311ee4c87 🐛 Fix show in assets panel option for component of variants (#7311) 2025-09-15 13:13:12 +02:00
Andrey Antukh
b8c8579ff5 Merge pull request #7310 from penpot/niwinz-staging-update-jdk
⬆️ Update node and jdk (patch)
2025-09-12 16:54:49 +02:00
Andrey Antukh
82295c79d4 ⬆️ Update jdk to 24.0.2+12 2025-09-12 16:47:25 +02:00
Andrey Antukh
5174591058 ⬆️ Update nodejs to v22.19.0 2025-09-12 16:44:26 +02:00
Juan de la Cruz
658303fa36 🎉 Add 2.10 release slides (#7293) 2025-09-12 15:06:18 +02:00
Andrey Antukh
84013c21fa Merge pull request #7308 from penpot/niwinz-staging-update-deps
⬆️ Update dependencies
2025-09-12 14:47:53 +02:00
Andrey Antukh
f3062ade39 ⬆️ Update jakarta.mail dependency 2025-09-12 14:28:14 +02:00
Andrey Antukh
ca19d4deeb ⬆️ Update postgresql jdbc dependency 2025-09-12 14:28:14 +02:00
Andrey Antukh
dfceccca3d ⬆️ Update aws s3 sdk version
This transitivelly updates the netty library version
that comes with fixes for several security issues
2025-09-12 14:28:14 +02:00
Luis de Dios
9e2d3b1fa1 🐛 Fix position of annotation for variants (#7306) 2025-09-12 14:05:36 +02:00
Florian Schroedl
4dc0f3b4eb 🐛 Fix make-token throwing because of error in name, keep preview value 2025-09-12 13:49:25 +02:00
andrés gonzález
215288b6b4 📚 Update Design Tokens doc (#7265) 2025-09-12 11:15:23 +02:00
andrés gonzález
2e2c3e7bac 📚 Add doc for variants (#7258) 2025-09-12 11:15:03 +02:00
Alejandro Alonso
7ac44009d5 Merge pull request #7288 from penpot/luis-12042-context-menu-variant
🐛 Fix create a variant using the contextual menu
2025-09-09 16:27:35 +02:00
Luis de Dios
f22eef5bf6 🐛 Fix create a variant using the contextual menu 2025-09-09 15:59:04 +02:00
Alejandro Alonso
7c61049103 Merge pull request #7257 from penpot/azazeln28-fix-issue-11992-cannot-move-elements-up-or-down
🐛 Fix moving elements up or down while pressing alt
2025-09-09 11:07:58 +02:00
Alejandro Alonso
fe819c6ec4 Merge pull request #7286 from penpot/azazeln28-fix-text-editor-v1-paste
🐛 Fix text editor v1 paste HTML
2025-09-09 11:05:17 +02:00
Aitor Moreno
1a4594a615 🐛 Fix text editor v1 paste HTML 2025-09-09 10:48:15 +02:00
David Barragán Merino
d2bff2853f Merge pull request #7283 from penpot/bameda-upgrade-docker-images-dependencies
🐳 Update the version of node and nginx-unprivileged
2025-09-09 09:29:36 +02:00
Aitor Moreno
ff96f7be85 🐛 Fix moving elements up or down while pressing alt 2025-09-09 09:19:00 +02:00
Alejandro Alonso
a403af7ebd 🐛 Fix plugin installation link 2025-09-09 08:47:09 +02:00
Alejandro Alonso
e3c9588c1c Merge pull request #7279 from penpot/palba-variants-events2
🎉 Add "advanced" events to variants
2025-09-09 07:03:22 +02:00
Alejandro Alonso
25b63e5675 Merge pull request #7280 from penpot/palba-fix-variants-duplicate
🐛 Fix bad selection after variant duplicate
2025-09-09 06:49:34 +02:00
David Barragán Merino
6c59d633cd 🐳 Update the version of node and nginx-unprivileged 2025-09-08 18:36:31 +02:00
Andrés Moya
bb0a891638 📚 Update changelog 2025-09-08 16:49:27 +02:00
Pablo Alba
c5bd183f73 🐛 Fix bad selection after variant duplicate 2025-09-08 16:33:38 +02:00
Pablo Alba
06441063f2 Add "advanced" events to variants 2025-09-08 15:33:14 +02:00
Alejandro Alonso
9f11a2cb32 🐛 Fix context menu shape ids (#7277)
This reverts commit 1929ee36ed.
2025-09-08 11:59:54 +02:00
Alejandro Alonso
053b2c6248 Merge pull request #7253 from penpot/marina-payments-incorrect-date-plan
🐛 Fix incorrect date displayed for support plan
2025-09-08 11:22:48 +02:00
Marina López
fad058ee59 🐛 Fix incorrect date displayed for support plan 2025-09-08 11:06:57 +02:00
Alejandro Alonso
568c2fd9d7 Merge pull request #7271 from penpot/eva-bugfixing-release
🐛 Fix several bugs
2025-09-08 10:23:05 +02:00
Luis de Dios
794eb78aca ♻️ Refactor icon namespaces (#7262)
* ♻️ Rename old icons as deprecated

* ♻️ Take icons from the namespace for the icon component

* ♻️ Take icons from the namespace for the icon-button component
2025-09-08 10:02:33 +02:00
Florian Schrödl
e761bcac85 🐛 Fix text-transform not unapplyng text-case token (#7272) 2025-09-08 09:34:58 +02:00
Eva Marco
1929ee36ed 🐛 Fix ungroup option missing 2025-09-08 09:18:43 +02:00
Eva Marco
84cccd1b79 🐛 Fix alignment row on single shape selection 2025-09-05 13:54:46 +02:00
Eva Marco
e66d44ca81 🐛 Fix wrong icon on path layer 2025-09-05 13:54:19 +02:00
Eva Marco
2f3b464715 🎉 Add base font size flag (#7270) 2025-09-05 13:36:32 +02:00
Pablo Alba
286e477ad5 🐛 Change variants nesting error texts 2025-09-05 12:34:16 +02:00
Andrés Moya
6e6749f42e 🔧 Add unit tests to apply layout tokens 2025-09-05 11:11:48 +02:00
Andrés Moya
7b6aa0c12a 🐛 Unapply layout item tokens when moving out of a layout 2025-09-05 11:11:48 +02:00
Pablo Alba
409f95ac17 Add basic variants events (#7249)
*  Add basic variants events

*  MR changes
2025-09-04 17:00:09 +02:00
Florian Schroedl
25950be077 🐛 Fix when font-weight is a computed int (math resolver) 2025-09-04 13:06:57 +02:00
Alejandro Alonso
9eda1d0d78 Merge pull request #7256 from penpot/ladybenko-8371-fix-iconsistent-naming
🐛 Fix inconsistent naming for Flatten
2025-09-04 07:34:10 +02:00
Belén Albeza
f6c4f800c4 📚 Update changelog 2025-09-04 07:13:30 +02:00
Belén Albeza
f363d6a801 Add integration test for Flatten menu option 2025-09-04 07:13:16 +02:00
Belén Albeza
e88ce0d52f 🐛 Unify flatten naming 2025-09-04 07:13:16 +02:00
Alejandro Alonso
fe5fe7a933 Merge pull request #7252 from penpot/mavalroot-typos
🐛 Fix typos
2025-09-04 07:05:37 +02:00
María Valderrama
699cc147b5 🐛 Fix typos 2025-09-03 11:20:12 +02:00
Luis de Dios
b1d792a757 🐛 Fix icons do not appear in swap panel and annotations (#7240)
* 🐛 Fix icons do not appear in swap panel and annotations

* 📎 PR changes
2025-09-03 10:57:47 +02:00
Florian Schroedl
18e6842e35 ♻️ Revert trigger interactive via actionize and propagation 2025-09-03 10:10:34 +02:00
Andrés Moya
0df420d353 🐛 Fix setting shape size to zero 2025-09-03 08:57:26 +02:00
Pablo Alba
dac2d31b35 🐛 Don't allow a variant switch when that will provoke a components loop 2025-09-02 15:03:38 +02:00
Pablo Alba
1d3a1a094a 🐛 Missing component copy options on the context menu 2025-09-02 12:49:53 +02:00
Andrés Moya
9652996f07 🐛 Add validation for text shapes with wrong register of overrides 2025-09-02 12:49:41 +02:00
Luis de Dios
2a2735cd67 💄 Adjust design details of some components (#7225) 2025-09-01 11:02:36 +02:00
Elena Torró
0552b6e713 Merge pull request #7189 from penpot/azazeln28-feat-text-playground-fonts
🎉 Add multiple fonts to text editor WASM playground
2025-09-01 10:54:40 +02:00
Alejandro Alonso
30e655b1da Merge pull request #7236 from penpot/eva-fix-padding-sidebar
🐛 Fix wrong spacing
2025-09-01 10:35:21 +02:00
Alejandro Alonso
0aeecc6268 Merge pull request #7234 from penpot/palba-variants-activate-for-everyone
🎉 Activate variants for everyone
2025-09-01 10:27:44 +02:00
Eva Marco
5cec006a76 🐛 Fix wrong spacing 2025-09-01 10:15:52 +02:00
Pablo Alba
307e06372b 🎉 Activate variants for everyone 2025-09-01 09:58:14 +02:00
Xaviju
9f24e76c27 🌐 Convert translation single word to label (#7220) 2025-09-01 08:44:06 +02:00
Xavier Julian
7954eaf529 🎉 Inspect styles tab attributes container box 2025-08-31 14:58:11 +02:00
Eva Marco
fe406b577e 📚 Add comment to not translated string 2025-08-29 14:26:52 +02:00
Eva Marco
76c03af024 ♻️ Fix how files are used on scss files (#7208) 2025-08-29 13:55:50 +02:00
Andrey Antukh
fe5cdcbdc7 🐛 Do not render assets sidebar context menu if it is not open (#7222)
This commit is a workaround to an issue that happens when you performing
multiple selection of several shapes (including components) and an
exception is raised of max depth of updates. The issue is still not
solved, we justo do not render the context menu when user performs
the selection on the workspace.

That issue/exception happens only we have context-menu* component
rendered independently of its visibility and dev-tools open
2025-08-29 13:53:55 +02:00
Florian Schroedl
2479a06f9a 🐛 Fix token create not disabled when creating token without value 2025-08-29 13:41:38 +02:00
Florian Schroedl
fc5e4a821b ♻️ Use interactive update functions only on user actions 2025-08-29 13:38:41 +02:00
Florian Schroedl
3cdbc27de9 Unapply font-weight token when changing font-family 2025-08-29 13:38:41 +02:00
Florian Schroedl
a8ed1371d4 Find closest variant when updating font family 2025-08-29 13:38:41 +02:00
Andrey Antukh
e7bac41c37 Merge pull request #7202 from penpot/niwinz-develop-sidebar-refactor-2
♻️ Refactor right sidebar state management
2025-08-29 12:10:51 +02:00
Andrey Antukh
dede2a8f8e 💄 Fix JS files formatting issues 2025-08-29 11:25:58 +02:00
Andrey Antukh
e94abad3eb Add efficiency refactor for layer-menu* 2025-08-29 11:25:58 +02:00
Andrey Antukh
f8bc6e12a9 Improve efficiency of border radius menu 2025-08-29 11:25:58 +02:00
Andrey Antukh
a2c3208af9 🐛 Fix regression on not updating measures ui on moving frames 2025-08-29 11:25:58 +02:00
Andrey Antukh
a303df9c34 ♻️ Refactor right sidebar state management
Also removing duplicated refs and improve efficiency of
several other refs used on sidebar.
2025-08-29 11:25:58 +02:00
Kelp
bda24f3829 📎 Update Changelog to add font weight token (#7216)
Add font weight to changes.md

Signed-off-by: Kelp <5446186+NatachaMenjibar@users.noreply.github.com>
2025-08-29 11:04:36 +02:00
Elena Torró
94fd3119e8 Merge pull request #7175 from penpot/superalex-fix-clipping
🐛 Fix clipping
2025-08-29 11:03:07 +02:00
María Valderrama
c8091b42a7 🐛 Fix navigation arrows in Libraries & Templates carousel (#7217) 2025-08-29 10:51:00 +02:00
Xaviju
41a859b444 🌐 Recover lost variant translation (#7215) 2025-08-29 09:50:33 +02:00
Aitor Moreno
1cf20f7604 Merge pull request #7203 from penpot/elenatorro-fix-text-decoration-leaf-position
🐛 Fix line height on texts and improve text decoration rendering
2025-08-28 16:21:40 +02:00
Andrey Antukh
629541bc9d 🐛 Fix incorrect recursion on looking boolean parent (#7212) 2025-08-28 16:13:14 +02:00
Alejandro Alonso
44245d1b5f 🐛 Fix position-data validator (#7194) 2025-08-28 15:46:23 +02:00
Luis de Dios
a8692c72c6 Show create variant shortcut also for stand-alone components (#7195) 2025-08-28 15:45:53 +02:00
Elena Torró
7b7da59ca9 Merge pull request #7200 from penpot/ladybenko-11552-fix-font-styles
🐛 Fix new inline text styles not being applied correctly
2025-08-28 15:34:54 +02:00
Elena Torro
b78e3159d1 📚 Add wasm playground internal docs 2025-08-28 15:10:56 +02:00
Luis de Dios
2106028350 🐛 Fix color of variant property names in the design tab (#7204) 2025-08-28 12:51:04 +02:00
Andrey Antukh
c2e7f9dc42 ⬆️ Update base deps (#7207) 2025-08-28 12:50:39 +02:00
Andrey Antukh
8568098c5e 🌐 Validate and rehash translation files 2025-08-28 12:49:35 +02:00
VKing9
c8bc1ef1d2 🌐 Add translations for: Hindi
Currently translated at 94.8% (1816 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2025-08-28 12:46:50 +02:00
Stephan Paternotte
59bd434d05 🌐 Add translations for: Dutch
Currently translated at 97.5% (1869 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-08-28 12:46:50 +02:00
Edgars Andersons
3cf1e53340 🌐 Add translations for: Latvian
Currently translated at 96.0% (1839 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2025-08-28 12:46:50 +02:00
Denys Kisil
24f0956630 🌐 Add translations for: Ukrainian (ukr_UA)
Currently translated at 95.8% (1835 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/
2025-08-28 12:46:49 +02:00
Nicola Bortoletto
91991d5b32 🌐 Add translations for: Italian
Currently translated at 95.3% (1826 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-08-28 12:46:49 +02:00
Yaron Shahrabani
447b6ed1ab 🌐 Add translations for: Hebrew
Currently translated at 97.3% (1864 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-08-28 12:46:49 +02:00
Stas Haas
336486fecd 🌐 Add translations for: German
Currently translated at 89.2% (1710 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-08-28 12:46:48 +02:00
Jun Fang
bbd417c119 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 91.8% (1758 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-28 12:46:48 +02:00
Ingrid Pigueron
e3ffd45a18 🌐 Add translations for: French
Currently translated at 96.4% (1847 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-08-28 12:46:48 +02:00
Unreal Vision
52a4a46ebd 🌐 Add translations for: French
Currently translated at 96.4% (1847 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-08-28 12:46:48 +02:00
Elena Torro
025423a75e 🐛 Fix line height on texts and improve text decoration rendering 2025-08-28 12:40:23 +02:00
Hosted Weblate
a6f17e35dd 🌐 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2025-08-28 12:37:27 +02:00
Andrey Antukh
9a8cac3cac Merge remote-tracking branch 'weblate/develop' into develop 2025-08-28 12:36:51 +02:00
Aitor Moreno
884b857d17 🐛 Fix paste RTF crashes text editor (#7196) 2025-08-28 11:53:39 +02:00
Florian Schroedl
a20bbeff79 🔧 Add ff for typography composite token 2025-08-28 10:42:58 +02:00
Eva Marco
2a5f1f870b 🐛 Fix padding of input field component (#7198) 2025-08-28 10:30:33 +02:00
Luis de Dios
cf5303a39c ♻️ Title bar refactor (#7201) 2025-08-28 09:51:53 +02:00
Andrey Antukh
bf1e26c4e6 Merge pull request #7182 from penpot/niwinz-measures-menu-changes
♻️ Add efficiency refactor for sidebar
2025-08-28 08:40:44 +02:00
Andrey Antukh
4713d943d1 ♻️ Add efficiency refactor for workspace sidebars
The main changes are for right sidebar but left sidebar is also
slightly affected beacuse of the move where the active tokes are
resolved.
2025-08-27 17:56:35 +02:00
Eva Marco
df083cb315 🐛 Fix corner case on tooltip positioning 2025-08-27 17:56:35 +02:00
Eva Marco
6401b25964 💄 Format tab-switcher stories jsx file 2025-08-27 17:56:35 +02:00
Andrey Antukh
65f4adc68e Add minor efficiency enhancements to numeric-input* 2025-08-27 17:56:35 +02:00
Andrey Antukh
dfab472522 💄 Add minor cosmetic change to shape layout type helper 2025-08-27 17:56:35 +02:00
Belén Albeza
9759adf747 🐛 Fix new inline text styles not being applied correctly 2025-08-27 16:52:57 +02:00
Andrey Antukh
9ae1a08573 🐛 Make the app.common.time/inst nil safe 2025-08-27 14:17:11 +02:00
Ingrid Pigueron
ddab2cab14 🌐 Add translations for: French
Currently translated at 98.1% (1867 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-08-27 14:15:14 +02:00
Andrey Antukh
cf1a8fff65 Merge remote-tracking branch 'origin/develop' into develop 2025-08-27 13:19:53 +02:00
Andrey Antukh
45be4769d7 Merge remote-tracking branch 'origin/staging' into develop 2025-08-27 13:19:06 +02:00
María Valderrama
a68c41709a 🐛 Fix misleading affordance in saved versions (#7192) 2025-08-27 13:01:54 +02:00
Xaviju
4290bce718 🎉 Inspect tab layout switcher and computed feature (#7166) 2025-08-27 13:01:01 +02:00
Pablo Alba
3dd237002b 🐛 Fix combine as variants from assets tab selects wrong components (#7190) 2025-08-27 13:00:28 +02:00
Andrey Antukh
e0fb112bfb 📎 Update changelog 2025-08-27 12:52:24 +02:00
Andrey Antukh
2629537fd3 Merge branch 'subscriptions-enhancements' into staging 2025-08-27 12:51:34 +02:00
Marina López
cb7711f637 🐛 Fix integration tests 2025-08-27 12:30:48 +02:00
Andrey Antukh
a114e9adb0 Add logging for management update-customer method 2025-08-27 12:02:39 +02:00
Andrey Antukh
5fed5fa158 Add transactions support on management api 2025-08-27 12:00:03 +02:00
Andrey Antukh
6c8873c7f5 🐛 Ensure for-update locking is used on updating profile props 2025-08-27 11:59:35 +02:00
Andrey Antukh
37b0c4adc0 🐛 Add support fror ::db/for-update on sql ns 2025-08-27 11:58:42 +02:00
Florian Schroedl
9106617436 🎉 Add composite typography token 2025-08-27 11:35:56 +02:00
Yamila Moreno
68a95cf0d0 📎 Fix CI inputs 2025-08-27 11:21:34 +02:00
Yamila Moreno
1438632dde 📎 Fix CI inputs 2025-08-27 11:19:42 +02:00
Florian Schroedl
112fa46896 🐛 Fix case-sensitivity and multi word italic in font weight parsing 2025-08-27 11:15:21 +02:00
Yamila Moreno
6da5bbf33a 📎 Fix CI inputs 2025-08-27 11:14:56 +02:00
Yamila Moreno
95faf340c4 📎 Fix CI inputs 2025-08-27 11:13:13 +02:00
Marina López
723ea508df 🐛 Fix missing link for enterprise trial 2025-08-27 10:56:17 +02:00
Yamila Moreno
cbe538261c 📎 Fix CI inputs 2025-08-27 10:42:13 +02:00
Yamila Moreno
1925e6782f 📎 Fix CI inputs 2025-08-27 10:33:17 +02:00
Yamila Moreno
bcd950c141 📎 Fix CI inputs 2025-08-27 10:11:56 +02:00
Aitor Moreno
b215689566 🎉 Add multiple fonts to text editor WASM playground 2025-08-27 09:57:55 +02:00
Pablo Alba
dfe0f64c7c Add variants advanced retrieve tests (#7183) 2025-08-26 16:40:27 +02:00
Andrey Antukh
9de3910526 Add missing impl for Associative on LoadableWeakValueMap (#7188) 2025-08-26 16:39:48 +02:00
Luis de Dios
59eb75d8c3 💄 Style improvements in the component list & grid (#7185) 2025-08-26 16:35:09 +02:00
Florian Schroedl
6670b76ccc Show warning when applying token with non-matching font variant 2025-08-26 16:12:07 +02:00
Florian Schroedl
09b9383a0b Choose closest font weight for token weight when no matching weight is found 2025-08-26 16:12:07 +02:00
Eva Marco
31e37f352d 🐛 Fix token option schema (#7186)
* 🐛 Fix token option schema

* 🐛 Fix numeric-input schema
2025-08-26 14:06:59 +02:00
Yamila Moreno
c5958e4d61 📎 Add storybook to CI bundle 2025-08-26 10:45:08 +02:00
Luis de Dios
f1e7149e88 Add shortcuts for creating variants and properties (#7181)
*  Add shortcuts for creating variants and properties

* 📎 PR changes
2025-08-26 09:32:41 +02:00
Pablo Alba
d80ef17623 🐛 Fix cut pasting a variant into its own parent (#7179) 2025-08-26 09:25:52 +02:00
Andrey Antukh
ffe469ce71 Merge pull request #7159 from penpot/niwinz-develop-migrations-fixes
♻️ Add a better approach for load libraries on file validation and migrations
2025-08-26 09:25:23 +02:00
Andrey Antukh
c35bb6e09a 🎉 Add loadable weak map impl for libraries loading on validation and migration 2025-08-26 09:03:25 +02:00
Alejandro Alonso
8d404d97a1 🐛 Fix clipping 2025-08-26 08:52:42 +02:00
Andrey Antukh
fa2b0bd67c Don't migrate libraries on accessing them on file data migrations
We don't migrate the libraries for avoid cascade migration; it is not ideal
but it reduces the total of the required memory needed for process a single
file migration that requires libraries to be loaded.
2025-08-26 08:15:45 +02:00
Andrey Antukh
9563d1b1f6 Merge pull request #6635 from penpot/eva-add-numeric-input
 Add numeric input component
2025-08-25 19:42:47 +02:00
Eva Marco
33fc578f96 🎉 Add numeric-input component to DS
A new numeric-input impl compatible with tokens
2025-08-25 18:52:39 +02:00
Andrey Antukh
79786dde16 🎉 Add helpers for work with weak references and weak data structs 2025-08-25 18:52:39 +02:00
Marina López
926b2c9cfb 🐛 Fix doble click to submit subscription 2025-08-25 15:42:47 +02:00
Andrés Moya
c1b2aa7628 🐛 Add handler to correctly encode cljs dates to json 2025-08-25 13:52:38 +02:00
Andrey Antukh
991b26b73f 🐛 Fix undo transaction handling on creating a variant from group of components 2025-08-25 11:48:46 +02:00
Andrey Antukh
254a7461b2 Simplify commit-undo-transaction impl 2025-08-25 11:48:46 +02:00
Andrey Antukh
1384ed8aba Remove duplicated execution of check-open-transactions 2025-08-25 11:48:46 +02:00
Andrey Antukh
c9393c0cfb Remove repeated/duplucated lookups on start-undo-transaction 2025-08-25 11:48:46 +02:00
Pablo Alba
6eeb55fb88 🐛 Fix after variant switch children layouts aren't updated (#7177) 2025-08-25 11:35:33 +02:00
Andrey Antukh
c759afc10d 🔥 Remove unnecessary and broken unique-editors field
From the get-teams rpc method response
2025-08-25 11:33:34 +02:00
Marina López
090cb63568 🐛 Fix condition for members warning 2025-08-25 11:07:19 +02:00
Andrey Antukh
f223831766 Refresh subscription info when member role is updated 2025-08-25 10:53:48 +02:00
Marina López
854f286364 ♻️ Fix subscriptions inconsistencies 2025-08-25 10:53:48 +02:00
Andrey Antukh
2846b80cf7 Add rpc methods for obtain editors 2025-08-25 10:53:12 +02:00
Andrey Antukh
ad0ef82ffc 🎉 Add management http api 2025-08-25 10:53:12 +02:00
Elena Torró
3bb547fc45 🐛 Parse rx and ry SVG values correctly (#7176) 2025-08-25 10:44:11 +02:00
Pablo Alba
c3b326d95e 🐛 Fix duplicating a page with variants should duplicate them (#7172)
* 🐛 Fix duplicating a page with variants should duplicate them

*  MR changes
2025-08-25 10:41:03 +02:00
Luis de Dios
8c1a97dac5 Make some improvements to the swap panel (#7174)
* 💄 Visual improvements in swap panel

* ♻️ Refactor search-bar component to use DS icons

* 📎 PR changes
2025-08-25 10:40:20 +02:00
Alejandro Alonso
4053e8c8db Merge pull request #7173 from penpot/elenatorro-11735-fix-text-auto-width
🐛 Fix paragraph layout width on autowidth
2025-08-22 15:16:40 +02:00
Xaviju
ee86a3943d Update code to use design system icon (#7145) 2025-08-22 14:27:11 +02:00
Elena Torro
46b3e174ed 🐛 Fix paragraph layout width on autowidth 2025-08-22 13:55:24 +02:00
Alejandro Alonso
c0c2c9489c Merge pull request #7170 from penpot/elenatorro-fix-text-tests
🔧 Update and fix text tests
2025-08-22 13:34:00 +02:00
Elena Torró
c05c179d67 Merge pull request #7167 from penpot/azazeln28-feat-text-editor-wasm-playground
🎉 Add Text Editor WASM Playground
2025-08-22 12:47:41 +02:00
Aitor Moreno
596193d34d 🎉 Add missing styles on text leaves 2025-08-22 12:22:51 +02:00
Aitor Moreno
15eee0d8d8 🎉 Add Text Editor WASM Playground 2025-08-22 12:22:51 +02:00
Elena Torro
0b7444e8ff 🐛 Use SrcIn only when there is only one inner stroke, otherwise use erode filter 2025-08-22 09:54:15 +02:00
Elena Torro
96a91dc710 🔧 Add missing emoji ranges 2025-08-22 09:54:03 +02:00
Elena Torro
503d431d8e 🔧 Fix and update text use cases 2025-08-22 09:53:50 +02:00
Alejandro Alonso
ed5875f29a Merge pull request #7154 from penpot/niwinz-staging-bug-1
🐛 Fix incorrect show request-access dialog on not-found on viewer
2025-08-22 09:19:47 +02:00
Yamila Moreno
b12d44150b 📎 Fix and improve ci 2025-08-21 17:42:37 +02:00
Andrey Antukh
66f5eb57b9 🐛 Fix incorrect fills coerce to binary type when flag is disabled (#7169) 2025-08-21 15:11:14 +02:00
Pablo Alba
7023880e67 🐛 On a variants switch, keep the value of the rotation and its transformations (#7165) 2025-08-21 13:55:16 +02:00
Andrey Antukh
0e4cf23a93 🐛 Fix incorrect type coerce operations (#7168)
A regression introduced in previous commits that causes
a browser tab totally killed by memory usage.
2025-08-21 13:52:47 +02:00
Elena Torró
3f93b0d44b Merge pull request #7098 from penpot/superalex-fix-big-blur-2
🐛 Fix big blur rendering for wasm render
2025-08-21 09:23:26 +02:00
Elena Torró
4a6c3d6ad3 Merge pull request #7152 from penpot/superalex-update-skia-binaries
🎉 Update skia binares to 0.87.0
2025-08-21 09:10:37 +02:00
Alejandro Alonso
478439f055 🐛 Fix big blur rendering for wasm render 2025-08-21 08:47:25 +02:00
Alejandro Alonso
9ef2454210 🎉 Update skia binares to 0.87.0 2025-08-21 07:38:46 +02:00
Andrey Antukh
a702fee3cd Merge pull request #7143 from penpot/luis-11856-adjust-component-title
 Adjust the appearance of the variant-related elements in the design tab
2025-08-20 17:41:50 +02:00
Pablo Alba
cac639d267 🐛 Fix deleting a variant from assets panel (#7147) 2025-08-20 17:38:11 +02:00
Elena Torró
0daf548773 Merge pull request #7146 from penpot/superlaex-fix-open-path-calculation
🐛 Fix open path calculation
2025-08-20 16:38:17 +02:00
Alejandro Alonso
9249a5d4ea Merge pull request #7155 from penpot/elenatorro-use-erode-image-filter-for-inner-strokes-with-opacity
🐛 Fix inner strokes with opacity using erode instead of multiple blending modes
2025-08-20 15:55:50 +02:00
Andrey Antukh
4c83feaa31 Merge remote-tracking branch 'origin/staging' into develop 2025-08-20 13:56:58 +02:00
Luis de Dios
4d8ad19eea 💄 Improve the alignment, spaces and sizes of the variant elements 2025-08-20 13:51:04 +02:00
Luis de Dios
e7e7d576b2 ♻️ Adapt title-bar component 2025-08-20 13:36:10 +02:00
Luis de Dios
7f2af1c355 Adjust component title 2025-08-20 13:36:10 +02:00
Andrey Antukh
ad38a21053 🐛 Fix incorrect show request-access dialog on not-found on viewer
When a user is not-authenticated
2025-08-20 13:35:20 +02:00
Elena Torro
9bb92277e4 🐛 Fix inner strokes with opacity using erode instead of multiple blending modes 2025-08-20 13:32:20 +02:00
Alejandro Alonso
643621a389 Merge pull request #7119 from penpot/niwinz-develop-type-hints
 Add several performance enhancements
2025-08-20 12:58:43 +02:00
Alejandro Alonso
ef8d2e7418 Merge pull request #7082 from penpot/niwinz-staging-changes-fix
♻️ Refactor schema references and openapi.json output
2025-08-20 12:55:16 +02:00
Andrey Antukh
adffac4eec Merge remote-tracking branch 'origin/main' into staging 2025-08-20 12:49:31 +02:00
Andrey Antukh
575342b3bb ♻️ Use direct schemas instead of references
Only a very common use, basic types schemas should be used as
reference (with namespaced keywords)
2025-08-20 12:33:07 +02:00
Alejandro Alonso
b6ecb4368e Merge pull request #7044 from penpot/niwinz-develop-refactor-versions-sidebar
♻️ Refactor versions sidebar
2025-08-20 12:00:28 +02:00
Pablo Alba
081df7fc03 🐛 Fix combine variants move items 2025-08-20 11:07:51 +02:00
Yamila Moreno
358343b4df 📎 Add build tag workflow and minor changes 2025-08-20 10:51:05 +02:00
Andrey Antukh
d7c19325cc Add better type references naming on openapi output 2025-08-20 10:47:56 +02:00
Andrey Antukh
b472a8ab19 Add general improvement to openapi and js-like doc output 2025-08-20 09:41:01 +02:00
Andrey Antukh
2b83d0d0e9 Add generative test case for openapi json serialization
This will prevent possible regression on introducing schemas without
generators or schema with generators that can't be serialized to json.
2025-08-20 09:38:57 +02:00
Andrey Antukh
911ac263fa 🐛 Use ::sm/any instead of any for get-file-fragment rpc method schema
The usage of `any?` predicate as-is uses the standard any generator that
causes to generate java.lang.Character instances created that are not
properly serialiable to JSON. The `::sm/any` schema delimits the
generator to a commonly known serializable types on json.
2025-08-20 09:38:55 +02:00
Andrey Antukh
545c78eb74 🐛 Add missing generators 2025-08-20 09:38:13 +02:00
Andrey Antukh
124b098c92 🔥 Remove already deprecated change spec 2025-08-20 09:38:13 +02:00
Andrey Antukh
43ed430475 📎 Update .gitignore file 2025-08-20 09:38:13 +02:00
Andrey Antukh
edd3b1512e 🐛 Add missing attrs to add-component change schema 2025-08-20 09:38:13 +02:00
Andrey Antukh
d9623c3c88 🐛 Add proper schema for decoding :obj on add-obj change 2025-08-20 09:38:13 +02:00
Andrey Antukh
f052e31ff0 🐛 Fix incorrect handling of assign operation on changes protocol 2025-08-20 09:38:13 +02:00
Yamila Moreno
73dfe12ec9 📚 Update k8s documentation 2025-08-20 09:04:25 +02:00
Alejandro Alonso
0c3d73745e 🐛 Fix open path calculation 2025-08-20 09:00:04 +02:00
Aitor Moreno
a6ecc4fb3c Merge pull request #7106 from penpot/niwinz-develop-modifiers-enhacements
 Several enhancements on how shape data is written on memory
2025-08-19 17:05:25 +02:00
Denys Kisil
625d9ab188 🌐 Add translations for: Ukrainian (ukr_UA)
Currently translated at 97.9% (1863 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/
2025-08-19 11:03:09 +00:00
Jun Fang
e6cc15e19b 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:07 +00:00
王世阳
0af2cd6413 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:05 +00:00
IsCycleBai
443e0b0206 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:04 +00:00
Jun Fang
a4fa5e9304 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:02 +00:00
IsCycleBai
d94c311a1e 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:01 +00:00
Pablo Alba
c0c8390a7d 🐛 Fix restoring component inside flex 2025-08-19 12:15:30 +02:00
Andrey Antukh
f4be117219 🔥 Remove app.common.time/duration usage on frontend (#7139)
Is broken and not necessary; duration class is no longer
available on frontend code.
2025-08-19 10:36:34 +02:00
Andrey Antukh
7eb590e9fd ♻️ Refactor versions sidebar 2025-08-18 21:57:47 +02:00
Andrey Antukh
6588913141 ♻️ Remove several level of unnecesary allocation on writing text 2025-08-18 21:54:36 +02:00
Andrey Antukh
5c4a60aee7 Make mem write helpers receive offset as first arg 2025-08-18 21:54:36 +02:00
Andrey Antukh
af02e12685 🎉 Add missing write-u32 helper to mem ns 2025-08-18 21:54:36 +02:00
Andrey Antukh
675864ce0b Remove incorrect usage of dm/get-prop 2025-08-18 21:54:36 +02:00
Andrey Antukh
c55f3182d8 💄 Rename text-dimensions to get-text-dimensions 2025-08-18 21:49:00 +02:00
Andrey Antukh
0d6eac7656 💄 Add mainly cosmetic changes to set-shape-shadows
Mainly replace a loop with run! that used reduce as impl.
After measuring there are no real difference between using
the more complex loop and more simplier run!; in parity of
performance we prefer simplier approach.
2025-08-18 21:49:00 +02:00
Andrey Antukh
7acfd199aa 💄 Add mainly cosmetic changes to set-layout-child 2025-08-18 21:49:00 +02:00
Andrey Antukh
33d6f543a1 Remove several not necessary allocations from set-grid-layout-cells 2025-08-18 21:49:00 +02:00
Luis de Dios
4237ef572e 🐛 Fix use ellipsis when property names are too long (#7135) 2025-08-18 21:36:56 +02:00
Pablo Alba
6babea8b12 🐛 Fix alert for bad formula not showing in copies of variants (#7126)
* 🐛 Fix alert for bad formula not showing in copies of variants

*  MR changes
2025-08-18 21:36:29 +02:00
Pablo Alba
6b7f91c671 🐛 Fix weird resizing on combine variants with constraints scale (#7134) 2025-08-18 21:35:04 +02:00
Pablo Alba
b3b183c151 🐛 Fix duplicate a variant when all have bad formulae crashes 2025-08-18 17:09:20 +02:00
Yamila Moreno
59f2ee87e6 📎 Improve github actions 2025-08-18 15:28:15 +02:00
Pablo Alba
6af8940a46 🐛 On variant create do not set sizing 2025-08-18 15:14:36 +02:00
Xavier Julian
e1a1110f06 📎 Update changelog with new typography tokens 2025-08-18 13:50:14 +02:00
Pablo Alba
1dcf1e0b0f 🐛 Fix :show-content wasn't on components sync-attrs 2025-08-18 13:17:09 +02:00
Yamila Moreno
4c3e345c9c 📎 Fix github actions 2025-08-18 13:05:45 +02:00
Andrey Antukh
a3b9a9f07b Remove reflection calls from buffer macros 2025-08-18 13:03:10 +02:00
Andrey Antukh
17ec360720 Add several missing type hints for avoid reflection and autoboxing 2025-08-18 13:03:10 +02:00
Andrey Antukh
89b67d59d5 Fix autoboxing on path type impl 2025-08-18 13:03:10 +02:00
Andrey Antukh
77be00014e Remove reflection on geom rect code 2025-08-18 13:03:10 +02:00
Andrey Antukh
e336f287b6 Remove reflection on geom matrix code 2025-08-18 13:03:10 +02:00
Andrey Antukh
50aa6ff306 Remove reflection calls on binfile v3 code 2025-08-18 13:03:10 +02:00
Alejandro Alonso
56f162f219 Merge pull request #7133 from penpot/superalex-fix-create-variant-doesnt-work-2
🐛 Create variant doesn't work
2025-08-18 13:02:33 +02:00
Pablo Alba
f7a0c4139a 🐛 Fix you can add a rect into a variant container (#7137) 2025-08-18 13:02:27 +02:00
Alejandro Alonso
7c39e321c4 Merge pull request #7136 from penpot/niwinz-develop-fix-inconsistencies-on-text-shortcuts
🐛 Fix several inconsistencies and duplicated shortcuts
2025-08-18 12:57:18 +02:00
Andrey Antukh
ce6a863599 🐛 Fix several inconsistencies and duplicated shortcuts 2025-08-18 12:31:09 +02:00
Alejandro Alonso
832690e71e 🐛 Create variant doesn't work 2025-08-18 11:58:24 +02:00
Pablo Alba
7526cb0d71 🐛 Fix on variants rotation override is not preserving properly (#7120) 2025-08-18 11:57:58 +02:00
andrés gonzález
3292109ab0 🐛 Fix typos in modal about variant connections (#7122) 2025-08-18 11:47:23 +02:00
Elena Torró
be376d2030 Merge pull request #7124 from penpot/ladybenko-11799-fix-remove-layout
🐛 Fix removing layout (wasm)
2025-08-18 09:52:22 +02:00
Alejandro Alonso
4d455b5e9f Merge pull request #7125 from penpot/elenatorro-11842-fix-groups-rendering-on-drag
🐛 Fix Group extrect calculation
2025-08-18 09:36:58 +02:00
Edgars Andersons
50ce28e378 🌐 Add translations for: Latvian
Currently translated at 98.4% (1872 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2025-08-15 21:02:02 +02:00
Yaron Shahrabani
1eee8e2ce8 🌐 Add translations for: Hebrew
Currently translated at 99.7% (1897 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-08-15 21:02:00 +02:00
Elena Torro
5e6ce9172f 🔧 Return always a shader on merge_fills for consistency 2025-08-14 16:08:03 +02:00
Elena Torró
03adbc2ae8 🔧 Update multiple emoji test and increase resulting image (#7113) 2025-08-14 15:47:55 +02:00
Belén Albeza
e63a3f76f7 🐛 Fix open/close path detection in wasm (#7110)
* 🐛 Fix open/close path detection in wasm

* 💄 Remove leftover code
2025-08-14 15:45:09 +02:00
Elena Torro
6d42d456fb 🐛 Fix Group extrect calculation 2025-08-14 15:39:45 +02:00
Belén Albeza
c818b6f88f 🐛 Fix layout and constraints not being cleared 2025-08-14 15:38:23 +02:00
Belén Albeza
3f3c7905b4 ♻️ Refactor wasm layout functions to their own submodule 2025-08-14 14:17:49 +02:00
Elena Torró
e8dd13beb2 Merge pull request #7118 from penpot/superalex-fix-extrect-invalidation-for-texts
🐛 Fix extrect invalidation for texts
2025-08-14 13:43:42 +02:00
Elena Torró
fc6b64aa68 Merge pull request #7121 from penpot/superalex-fix-set-corners
🐛 Fix set corners
2025-08-14 13:34:43 +02:00
Alejandro Alonso
5e0a2f66e3 🐛 Fix set corners 2025-08-14 11:50:40 +02:00
Pablo Alba
108d4fabba 🐛 Fix menu entry not showing on assets tab (#7115)
* 🐛 Fix menu entry not showing on assets tab

*  MR changes
2025-08-14 10:39:47 +02:00
Pablo Alba
2e277a39ca 🐛 Fix flick on design tab after variant switch (#7116) 2025-08-14 08:14:06 +02:00
Pablo Alba
814ec43714 🐛 Fix variants nesting loops (#7112)
* 🐛 Fix variants nesting loops

*  MR changes
2025-08-14 08:08:31 +02:00
Andrey Antukh
54bb9ea755 Merge remote-tracking branch 'origin/staging' into develop 2025-08-14 08:06:15 +02:00
Pablo Alba
374e921672 🐛 Fix variants change property name multiple selection 2025-08-13 17:29:03 +02:00
Alejandro Alonso
64e5ea93a0 🐛 Fix extrect invalidation for texts 2025-08-13 14:32:23 +02:00
Pablo Alba
2562d70880 🐛 Fix crash dragging external component into a variant without props (#7111) 2025-08-13 14:29:56 +02:00
Alejandro Alonso
d99ef29152 Merge pull request #7029 from penpot/elenatorro-11691-fix-default-text-fill
🔧 Fix text default color and inner stroke opacity
2025-08-13 12:52:14 +02:00
Edgars Andersons
d5a2cd9cd2 🌐 Add translations for: Latvian
Currently translated at 97.8% (1862 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2025-08-13 12:02:14 +02:00
Elena Torro
82d2889e96 🔧 Improve text strokes blending 2025-08-13 11:50:09 +02:00
Eva Marco
ff2e845f2c 🐛 Fix double click on set name input (#7096) 2025-08-13 09:23:53 +02:00
Stephan Paternotte
aa94671002 🌐 Add translations for: Dutch
Currently translated at 100.0% (1902 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-08-12 10:02:02 +02:00
Nicola Bortoletto
52cf136f84 🌐 Add translations for: Italian
Currently translated at 96.7% (1841 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-08-12 10:02:01 +02:00
Yaron Shahrabani
808427795c 🌐 Add translations for: Hebrew
Currently translated at 99.6% (1895 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-08-12 10:01:59 +02:00
Elena Torro
172c6ad4b8 🔧 Set fill paint as transparent when there are no fills 2025-08-11 13:52:49 +02:00
560 changed files with 53732 additions and 35097 deletions

View File

@@ -1,18 +1,45 @@
name: Build and Upload Penpot Bundles
name: Build and Upload Penpot Bundle
on:
# Create bundle from manual action
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: string
required: true
default: 'develop'
build_wasm:
description: 'BUILD_WASM. Valid values: yes, no'
type: string
required: false
default: 'yes'
build_storybook:
description: 'BUILD_STORYBOOK. Valid values: yes, no'
type: string
required: false
default: 'yes'
workflow_call:
inputs:
gh_ref:
description: 'Name of the branch'
type: string
required: true
default: 'develop'
build_wasm:
description: 'BUILD_WASM. Valid values: yes, no'
type: string
required: false
default: 'yes'
build_storybook:
description: 'BUILD_STORYBOOK. Valid values: yes, no'
type: string
required: false
default: 'yes'
jobs:
build-bundles:
name: Build and Upload Penpot Bundles
build-bundle:
name: Build and Upload Penpot Bundle
runs-on: ubuntu-24.04
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
@@ -30,12 +57,12 @@ jobs:
id: vars
run: |
echo "commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "gh_branch=${{ github.base_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx for multi-arch build
uses: docker/setup-buildx-action@v3
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Run manage.sh build-bundle from host
env:
BUILD_WASM: ${{ inputs.build_wasm }}
BUILD_STORYBOOK: ${{ inputs.build_storybook }}
run: ./manage.sh build-bundle
- name: Prepare directories for zipping
@@ -43,16 +70,22 @@ jobs:
mkdir zips
mv bundles penpot
- name: Create zip bundles
- name: Create zip bundle
run: |
echo "📦 Packaging Penpot bundles..."
echo "📦 Packaging Penpot bundle..."
zip -r zips/penpot.zip penpot
- name: Upload Penpot bundle to S3
if: github.ref_type == 'branch'
run: |
aws s3 cp zips/penpot.zip s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.gh_branch}}-latest.zip
aws s3 cp zips/penpot.zip s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.gh_ref }}-latest.zip
aws s3 cp zips/penpot.zip s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.commit_hash }}.zip
- name: Upload Penpot bundle to S3
if: github.ref_type == 'tag'
run: |
aws s3 cp zips/penpot.zip s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.gh_ref }}.zip
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
@@ -60,5 +93,5 @@ jobs:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
❌ *[PENPOT] Error during the execution of the job*
📄 Triggered from ref: `${{ steps.vars.outputs.gh_branch}}`
📄 Triggered from ref: `${{ steps.vars.outputs.gh_ref }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}

View File

@@ -1,4 +1,4 @@
name: Build and Upload Penpot DEVELOP Bundles
name: DEVELOP - Build and Upload Penpot Bundle
on:
schedule:
@@ -6,7 +6,9 @@ on:
jobs:
build-develop-bundle:
uses: ./.github/workflows/build-bundles.yml
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: "develop"
build_wasm: "yes"
build_storybook: "yes"

View File

@@ -1,12 +1,14 @@
name: Build and Upload Penpot STAGING Bundles
name: STAGING - Build and Upload Penpot Bundle
on:
schedule:
- cron: '0 5 * * 1-5'
- cron: '36 5-20 * * 1-5'
jobs:
build-staging-bundle:
uses: ./.github/workflows/build-bundles.yml
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: "staging"
build_wasm: "yes"
build_storybook: "yes"

15
.github/workflows/build-tag.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: TAG - Build and Upload Penpot Bundle
on:
push:
tags:
- '*'
jobs:
build-tag-bundle:
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: ${{ github.ref_name }}
build_wasm: "no"
build_storybook: "yes"

View File

@@ -26,7 +26,7 @@ jobs:
- name: Check Commit Type
uses: gsactions/commit-message-checker@v2
with:
pattern: '^(Merge|Revert|:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle):)\s[A-Z].*[^.]$'
pattern: '^(Merge|Revert|:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind):)\s[A-Z].*[^.]$'
flags: 'gm'
error: 'Commit should match CONTRIBUTING.md guideline'
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request

1
.gitignore vendored
View File

@@ -31,6 +31,7 @@
/.clj-kondo/.cache
/_dump
/notes
/playground/
/backend/*.md
/backend/*.sql
/backend/*.txt

2
.nvmrc
View File

@@ -1 +1 @@
v22.13.1
v22.19.0

View File

@@ -4,13 +4,37 @@
### :rocket: Epics and highlights
- Variants
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
### :sparkles: New features & Enhancements
- Add efficiency enhancements to right sidebar [Github #7182](https://github.com/penpot/penpot/pull/7182)
- Add defaults for artboard drawing [Taiga #494](https://tree.taiga.io/project/penpot/us/494?milestone=465047)
- Continuous display of distances between elements when moving a layer with the keyboard [Taiga #1780](https://tree.taiga.io/project/penpot/us/1780)
- New Number token - unitless values [Taiga #10936](https://tree.taiga.io/project/penpot/us/10936)
- New font-family token [Taiga #10937](https://tree.taiga.io/project/penpot/us/10937)
- New text case token [Taiga #10942](https://tree.taiga.io/project/penpot/us/10942)
- New text-decoration token [Taiga #10941](https://tree.taiga.io/project/penpot/us/10941)
- New letter spacing token [Taiga #10940](https://tree.taiga.io/project/penpot/us/10940)
- New font weight token [Taiga #10939](https://tree.taiga.io/project/penpot/us/10939)
- Upgrade Node to v22.18.0 [Github #7283](https://github.com/penpot/penpot/pull/7283)
- Upgrade the base docker image for penpot frontend to v1.29.1 [Github #7283](https://github.com/penpot/penpot/pull/7283)
- Create variant from an existing component [Taiga #2088](https://tree.taiga.io/project/penpot/us/2088)
- Create variant from an existing variant [Taiga #8282](https://tree.taiga.io/project/penpot/us/8282)
- Actions over a component with variants [Taiga #10503](https://tree.taiga.io/project/penpot/us/10503)
- Create a variant by dragging a component into a component with variants [Taiga #8134](https://tree.taiga.io/project/penpot/us/8134)
- Transform a variant into an individual component [Taiga #8141](https://tree.taiga.io/project/penpot/us/8141)
- Delete variant [Taiga #6890](https://tree.taiga.io/project/penpot/us/6890)
- Restore an orphaned copy of a variant [Taiga #10446](https://tree.taiga.io/project/penpot/us/10446)
- Add, Edit & Delete variant properties name and value [Taiga #6892](https://tree.taiga.io/project/penpot/us/6892)
- Retrieve variants [Taiga #6888](https://tree.taiga.io/project/penpot/us/6888)
- Retrieve variants with nested components [Taiga #10277](https://tree.taiga.io/project/penpot/us/10277)
- Create variants in bulk from existing components [Taiga #7926](https://tree.taiga.io/project/penpot/us/7926)
- Alternative ways of creating variants - Button Design Tab [Taiga #10316](https://tree.taiga.io/project/penpot/us/10316)
### :bug: Bugs fixed
@@ -23,8 +47,25 @@
- Fix font size/variant not updated when editing a text [Taiga #11552](https://tree.taiga.io/project/penpot/issue/11552)
- Fix issue where Alt + arrow keys shortcut interferes with letter-spacing when moving text layers [Taiga #11552](https://tree.taiga.io/project/penpot/issue/11771)
- Fix consistency issues on how font variants are visualized [Taiga #11499](https://tree.taiga.io/project/penpot/us/11499)
- Fix parsing rx and ry SVG values for rect radius [Taiga #11861](https://tree.taiga.io/project/penpot/issue/11861)
- Misleading affordance in saved versions [Taiga #11887](https://tree.taiga.io/project/penpot/issue/11887)
- Fix pasting RTF text crashes penpot [Taiga #11717](https://tree.taiga.io/project/penpot/issue/11717)
- Fix navigation arrows in Libraries & Templates carousel [Taiga #10609](https://tree.taiga.io/project/penpot/issue/10609)
- Fix applying tokens with zero value to size [Taiga #11618](https://tree.taiga.io/project/penpot/issue/11618)
- Fix typo [Taiga #11969](https://tree.taiga.io/project/penpot/issue/11969)
- Fix typo [Taiga #11970](https://tree.taiga.io/project/penpot/issue/11970)
- Fix typos [Taiga #11971](https://tree.taiga.io/project/penpot/issue/11971)
- Fix inconsistent naming for "Flatten" [Taiga #8371](https://tree.taiga.io/project/penpot/issue/8371)
- Layout item tokens should be unapplied when moving out of a layout [Taiga #11012](https://tree.taiga.io/project/penpot/issue/11012)
- Fix incorrect date displayed for support plan [Taiga #11986](https://tree.taiga.io/project/penpot/issue/11986)
- Fix can't import 'borderWidth' type token [#132](https://github.com/tokens-studio/penpot/issues/132)
- Fix moving elements up or down while pressing alt [Taiga Issue #11992](https://tree.taiga.io/project/penpot/issue/11992)
- Fix conflicting shortcuts (remove dec/inc line height and letter spacing) [Taiga #12102](https://tree.taiga.io/project/penpot/issue/12102)
- Fix conflicting shortcuts (remove text-align shortcuts) [Taiga #12047](https://tree.taiga.io/project/penpot/issue/12047)
- Fix export file with empty tokens library [Taiga #12137](https://tree.taiga.io/project/penpot/issue/12137)
- Fix context menu on spacing tokens [Taiga #12141](https://tree.taiga.io/project/penpot/issue/12141)
## 2.9.0 (Unreleased)
## 2.9.0
### :rocket: Epics and highlights
@@ -52,6 +93,8 @@
- Add the ability to show login dialog on profile settings [Github #6871](https://github.com/penpot/penpot/pull/6871)
- Improve the application of tokens with object specific tokens [Taiga #10209](https://tree.taiga.io/project/penpot/us/10209)
- Add info to apply-token event [Taiga #11710](https://tree.taiga.io/project/penpot/task/11710)
- Fix double click on set name input [Taiga #11747](https://tree.taiga.io/project/penpot/issue/11747)
### :bug: Bugs fixed

View File

@@ -3,7 +3,7 @@
:deps
{penpot/common {:local/root "../common"}
org.clojure/clojure {:mvn/version "1.12.1"}
org.clojure/clojure {:mvn/version "1.12.2"}
org.clojure/tools.namespace {:mvn/version "1.5.0"}
com.github.luben/zstd-jni {:mvn/version "1.5.7-3"}
@@ -38,7 +38,7 @@
metosin/reitit-core {:mvn/version "0.9.1"}
nrepl/nrepl {:mvn/version "1.3.1"}
org.postgresql/postgresql {:mvn/version "42.7.6"}
org.postgresql/postgresql {:mvn/version "42.7.7"}
org.xerial/sqlite-jdbc {:mvn/version "3.49.1.0"}
com.zaxxer/HikariCP {:mvn/version "6.3.0"}
@@ -65,7 +65,7 @@
;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.31.55"}}
software.amazon.awssdk/s3 {:mvn/version "2.33.8"}}
:paths ["src" "resources" "target/classes"]
:aliases

View File

@@ -6,12 +6,14 @@
(ns user
(:require
[app.binfile.common :as bfc]
[app.common.data :as d]
[app.common.debug :as debug]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cfh]
[app.common.fressian :as fres]
[app.common.geom.matrix :as gmt]
[app.common.json :as json]
[app.common.logging :as l]
[app.common.perf :as perf]
[app.common.pprint :as pp]
@@ -19,8 +21,9 @@
[app.common.schema.desc-js-like :as smdj]
[app.common.schema.desc-native :as smdn]
[app.common.schema.generators :as sg]
[app.common.schema.openapi :as oapi]
[app.common.spec :as us]
[app.common.json :as json]
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
@@ -30,7 +33,6 @@
[app.srepl.helpers :as srepl.helpers]
[app.srepl.main :as srepl]
[app.util.blob :as blob]
[app.common.time :as ct]
[clj-async-profiler.core :as prof]
[clojure.contrib.humanize :as hum]
[clojure.java.io :as io]

View File

@@ -19,6 +19,7 @@
[app.common.time :as ct]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.common.weak :as weak]
[app.config :as cf]
[app.db :as db]
[app.db.sql :as sql]
@@ -187,9 +188,9 @@
and decoding."
[cfg file-id & {:as opts}]
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
(some->> (db/get* conn :file {:id file-id}
(assoc opts ::db/remove-deleted false))
(decode-file cfg)))))
(when-let [row (db/get* conn :file {:id file-id}
(assoc opts ::db/remove-deleted false))]
(decode-file cfg row opts)))))
(defn clean-file-features
[file]
@@ -606,11 +607,19 @@
(map decode-row))
(db/exec! conn [sql:get-file-libraries file-id])))
;; FIXME: this will use a lot of memory if file uses too many big
;; libraries, we should load required libraries on demand
(defn get-resolved-file-libraries
"A helper for preload file libraries"
[{:keys [::db/conn] :as cfg} file]
(->> (get-file-libraries conn (:id file))
(into [file] (map #(get-file cfg (:id %))))
(d/index-by :id)))
"Get all file libraries including itself. Returns an instance of
LoadableWeakValueMap that allows do not have strong references to
the loaded libraries and reduce possible memory pressure on having
all this libraries loaded at same time on processing file validation
or file migration.
This still requires at least one library at time to be loaded while
access to it is performed, but it improves considerable not having
the need of loading all the libraries at the same time."
[{:keys [::db/conn] :as cfg} {:keys [id] :as file}]
(let [library-ids (->> (get-file-libraries conn (:id file))
(map :id)
(cons (:id file)))
load-fn #(get-file cfg % :migrate? false)]
(weak/loadable-weak-value-map library-ids load-fn {id file})))

View File

@@ -36,11 +36,6 @@
"fdata/shape-data-type"
nil
;; There is no migration needed, but we don't want to allow
;; copy paste nor import of variant files into no-variant teams
"variants/v1"
nil
(ex/raise :type :internal
:code :no-migration-defined
:hint (str/ffmt "no migation for feature '%' on file importation" feature)

View File

@@ -27,7 +27,7 @@
[app.common.types.page :as ctp]
[app.common.types.plugins :as ctpg]
[app.common.types.shape :as cts]
[app.common.types.tokens-lib :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.typography :as cty]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -41,8 +41,10 @@
[datoteka.fs :as fs]
[datoteka.io :as io])
(:import
java.io.File
java.io.InputStream
java.io.OutputStreamWriter
java.lang.AutoCloseable
java.util.zip.ZipEntry
java.util.zip.ZipFile
java.util.zip.ZipOutputStream))
@@ -103,25 +105,25 @@
(sm/encoder ctp/schema:page sm/json-transformer))
(def encode-shape
(sm/encoder ::cts/shape sm/json-transformer))
(sm/encoder cts/schema:shape sm/json-transformer))
(def encode-media
(sm/encoder ::ctf/media sm/json-transformer))
(sm/encoder ctf/schema:media sm/json-transformer))
(def encode-component
(sm/encoder ::ctc/component sm/json-transformer))
(sm/encoder ctc/schema:component sm/json-transformer))
(def encode-color
(sm/encoder ctcl/schema:library-color sm/json-transformer))
(def encode-typography
(sm/encoder ::cty/typography sm/json-transformer))
(sm/encoder cty/schema:typography sm/json-transformer))
(def encode-tokens-lib
(sm/encoder ::cto/tokens-lib sm/json-transformer))
(sm/encoder ctob/schema:tokens-lib sm/json-transformer))
(def encode-plugin-data
(sm/encoder ::ctpg/plugin-data sm/json-transformer))
(sm/encoder ctpg/schema:plugin-data sm/json-transformer))
(def encode-storage-object
(sm/encoder schema:storage-object sm/json-transformer))
@@ -138,7 +140,7 @@
(sm/decoder ctf/schema:media sm/json-transformer))
(def decode-component
(sm/decoder ::ctc/component sm/json-transformer))
(sm/decoder ctc/schema:component sm/json-transformer))
(def decode-color
(sm/decoder ctcl/schema:library-color sm/json-transformer))
@@ -147,19 +149,19 @@
(sm/decoder schema:file sm/json-transformer))
(def decode-page
(sm/decoder ::ctp/page sm/json-transformer))
(sm/decoder ctp/schema:page sm/json-transformer))
(def decode-shape
(sm/decoder ::cts/shape sm/json-transformer))
(sm/decoder cts/schema:shape sm/json-transformer))
(def decode-typography
(sm/decoder ::cty/typography sm/json-transformer))
(sm/decoder cty/schema:typography sm/json-transformer))
(def decode-tokens-lib
(sm/decoder cto/schema:tokens-lib sm/json-transformer))
(sm/decoder ctob/schema:tokens-lib sm/json-transformer))
(def decode-plugin-data
(sm/decoder ::ctpg/plugin-data sm/json-transformer))
(sm/decoder ctpg/schema:plugin-data sm/json-transformer))
(def decode-storage-object
(sm/decoder schema:storage-object sm/json-transformer))
@@ -173,31 +175,31 @@
(sm/check-fn schema:manifest))
(def validate-file
(sm/check-fn ::ctf/file))
(sm/check-fn ctf/schema:file))
(def validate-page
(sm/check-fn ::ctp/page))
(sm/check-fn ctp/schema:page))
(def validate-shape
(sm/check-fn ::cts/shape))
(sm/check-fn cts/schema:shape))
(def validate-media
(sm/check-fn ::ctf/media))
(sm/check-fn ctf/schema:media))
(def validate-color
(sm/check-fn ctcl/schema:library-color))
(def validate-component
(sm/check-fn ::ctc/component))
(sm/check-fn ctc/schema:component))
(def validate-typography
(sm/check-fn ::cty/typography))
(sm/check-fn cty/schema:typography))
(def validate-tokens-lib
(sm/check-fn ::cto/tokens-lib))
(sm/check-fn ctob/schema:tokens-lib))
(def validate-plugin-data
(sm/check-fn ::ctpg/plugin-data))
(sm/check-fn ctpg/schema:plugin-data))
(def validate-storage-object
(sm/check-fn schema:storage-object))
@@ -251,9 +253,9 @@
(write-entry! output path params)
(with-open [input (sto/get-object-data storage sobject)]
(.putNextEntry output (ZipEntry. (str "objects/" id ext)))
(.putNextEntry ^ZipOutputStream output (ZipEntry. (str "objects/" id ext)))
(io/copy input output :size (:size sobject))
(.closeEntry output))))))
(.closeEntry ^ZipOutputStream output))))))
(defn- export-file
[{:keys [::file-id ::output] :as cfg}]
@@ -347,7 +349,8 @@
typography (encode-typography object)]
(write-entry! output path typography)))
(when tokens-lib
(when (and tokens-lib
(not (ctob/empty-lib? tokens-lib)))
(let [path (str "files/" file-id "/tokens.json")
encoded-tokens (encode-tokens-lib tokens-lib)]
(write-entry! output path encoded-tokens)))))
@@ -447,7 +450,7 @@
(defn- read-manifest
[^ZipFile input]
(let [entry (get-zip-entry input "manifest.json")]
(with-open [reader (zip-entry-reader input entry)]
(with-open [^AutoCloseable reader (zip-entry-reader input entry)]
(let [manifest (json/read reader :key-fn json/read-kebab-key)]
(decode-manifest manifest)))))
@@ -537,12 +540,12 @@
(defn- read-entry
[^ZipFile input entry]
(with-open [reader (zip-entry-reader input entry)]
(with-open [^AutoCloseable reader (zip-entry-reader input entry)]
(json/read reader :key-fn json/read-kebab-key)))
(defn- read-plain-entry
[^ZipFile input entry]
(with-open [reader (zip-entry-reader input entry)]
(with-open [^AutoCloseable reader (zip-entry-reader input entry)]
(json/read reader)))
(defn- read-file
@@ -1006,8 +1009,8 @@
(try
(l/info :hint "start exportation" :export-id (str id))
(binding [bfc/*state* (volatile! (bfc/initial-state))]
(with-open [output (io/output-stream output)]
(with-open [output (ZipOutputStream. output)]
(with-open [^AutoCloseable output (io/output-stream output)]
(with-open [^AutoCloseable output (ZipOutputStream. output)]
(let [cfg (assoc cfg ::output output)]
(export-files cfg)
(export-storage-objects cfg)))))
@@ -1051,7 +1054,7 @@
(l/info :hint "import: started" :id (str id))
(try
(with-open [input (ZipFile. (fs/file input))]
(with-open [input (ZipFile. ^File (fs/file input))]
(import-files (assoc cfg ::bfc/input input)))
(catch Throwable cause
@@ -1066,6 +1069,6 @@
(defn get-manifest
[path]
(with-open [input (ZipFile. (fs/file path))]
(with-open [^AutoCloseable input (ZipFile. ^File (fs/file path))]
(-> (read-manifest input)
(validate-manifest))))

View File

@@ -53,8 +53,15 @@
opts (cond-> opts
(::order-by opts) (assoc :order-by (::order-by opts))
(::columns opts) (assoc :columns (::columns opts))
(::for-update opts) (assoc :suffix "FOR UPDATE")
(::for-share opts) (assoc :suffix "FOR SHARE"))]
(or (::db/for-update opts)
(::for-update opts))
(assoc :suffix "FOR UPDATE")
(or (::db/for-share opts)
(::for-share opts))
(assoc :suffix "FOR SHARE"))]
(sql/for-query table where-params opts))))
(defn update

View File

@@ -17,6 +17,7 @@
[app.http.awsns :as-alias awsns]
[app.http.debug :as-alias debug]
[app.http.errors :as errors]
[app.http.management :as mgmt]
[app.http.middleware :as mw]
[app.http.session :as session]
[app.http.websocket :as-alias ws]
@@ -143,6 +144,7 @@
[::debug/routes schema:routes]
[::mtx/routes schema:routes]
[::awsns/routes schema:routes]
[::mgmt/routes schema:routes]
::session/manager
::setup/props
::db/pool])
@@ -170,6 +172,9 @@
["/webhooks"
(::awsns/routes cfg)]
["/management"
(::mgmt/routes cfg)]
(::ws/routes cfg)
["/api" {:middleware [[mw/cors]]}

View File

@@ -0,0 +1,234 @@
;; 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.http.management
"Internal mangement HTTP API"
(:require
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.time :as ct]
[app.db :as db]
[app.main :as-alias main]
[app.rpc.commands.profile :as cmd.profile]
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.worker :as-alias wrk]
[integrant.core :as ig]
[yetti.response :as-alias yres]))
;; ---- ROUTES
(declare ^:private authenticate)
(declare ^:private get-customer)
(declare ^:private update-customer)
(defmethod ig/assert-key ::routes
[_ params]
(assert (db/pool? (::db/pool params)) "expect valid database pool"))
(def ^:private default-system
{:name ::default-system
:compile
(fn [_ _]
(fn [handler cfg]
(fn [request]
(handler cfg request))))})
(def ^:private transaction
{:name ::transaction
:compile
(fn [data _]
(when (:transaction data)
(fn [handler]
(fn [cfg request]
(db/tx-run! cfg handler request)))))})
(defmethod ig/init-key ::routes
[_ cfg]
["" {:middleware [[default-system cfg]
[transaction]]}
["/authenticate"
{:handler authenticate
:allowed-methods #{:post}}]
["/get-customer"
{:handler get-customer
:transaction true
:allowed-methods #{:post}}]
["/update-customer"
{:handler update-customer
:allowed-methods #{:post}
:transaction true}]])
;; ---- HELPERS
(defn- coercer
[schema & {:as opts}]
(let [decode-fn (sm/decoder schema sm/json-transformer)
check-fn (sm/check-fn schema opts)]
(fn [data]
(-> data decode-fn check-fn))))
;; ---- API: AUTHENTICATE
(defn- authenticate
[cfg request]
(let [token (-> request :params :token)
props (get cfg ::setup/props)
result (tokens/verify props {:token token :iss "authentication"})]
{::yres/status 200
::yres/body result}))
;; ---- API: GET-CUSTOMER
(def ^:private schema:get-customer
[:map [:id ::sm/uuid]])
(def ^:private coerce-get-customer-params
(coercer schema:get-customer
:type :validation
:hint "invalid data provided for `get-customer` rpc call"))
(def ^:private sql:get-customer-slots
"WITH teams AS (
SELECT tpr.team_id AS id,
tpr.profile_id AS profile_id
FROM team_profile_rel AS tpr
WHERE tpr.is_owner IS true
AND tpr.profile_id = ?
), teams_with_slots AS (
SELECT tpr.team_id AS id,
count(*) AS total
FROM team_profile_rel AS tpr
WHERE tpr.team_id IN (SELECT id FROM teams)
AND tpr.can_edit IS true
GROUP BY 1
ORDER BY 2
)
SELECT max(total) AS total FROM teams_with_slots;")
(defn- get-customer-slots
[cfg profile-id]
(let [result (db/exec-one! cfg [sql:get-customer-slots profile-id])]
(:total result)))
(defn- get-customer
[cfg request]
(let [profile-id (-> request :params coerce-get-customer-params :id)
profile (cmd.profile/get-profile cfg profile-id)
result {:id (get profile :id)
:name (get profile :fullname)
:email (get profile :email)
:num-editors (get-customer-slots cfg profile-id)
:subscription (-> profile :props :subscription)}]
{::yres/status 200
::yres/body result}))
;; ---- API: UPDATE-CUSTOMER
(def ^:private schema:timestamp
(sm/type-schema
{:type ::timestamp
:pred ct/inst?
:type-properties
{:title "inst"
:description "The same as :app.common.time/inst but encodes to epoch"
:error/message "should be an instant"
:gen/gen (->> (sg/small-int)
(sg/fmap (fn [v] (ct/inst v))))
:decode/string ct/inst
:encode/string inst-ms
:decode/json ct/inst
:encode/json inst-ms}}))
(def ^:private schema:subscription
[:map {:title "Subscription"}
[:id ::sm/text]
[:customer-id ::sm/text]
[:type [:enum
"unlimited"
"professional"
"enterprise"]]
[:status [:enum
"active"
"canceled"
"incomplete"
"incomplete_expired"
"past_due"
"paused"
"trialing"
"unpaid"]]
[:billing-period [:enum
"month"
"day"
"week"
"year"]]
[:quantity :int]
[:description [:maybe ::sm/text]]
[:created-at schema:timestamp]
[:start-date [:maybe schema:timestamp]]
[:ended-at [:maybe schema:timestamp]]
[:trial-end [:maybe schema:timestamp]]
[:trial-start [:maybe schema:timestamp]]
[:cancel-at [:maybe schema:timestamp]]
[:canceled-at [:maybe schema:timestamp]]
[:current-period-end [:maybe schema:timestamp]]
[:current-period-start [:maybe schema:timestamp]]
[:cancel-at-period-end :boolean]
[:cancellation-details
[:map {:title "CancellationDetails"}
[:comment [:maybe ::sm/text]]
[:reason [:maybe ::sm/text]]
[:feedback [:maybe
[:enum
"customer_service"
"low_quality"
"missing_feature"
"other"
"switched_service"
"too_complex"
"too_expensive"
"unused"]]]]]])
(def ^:private schema:update-customer
[:map
[:id ::sm/uuid]
[:subscription [:maybe schema:subscription]]])
(def ^:private coerce-update-customer-params
(coercer schema:update-customer
:type :validation
:hint "invalid data provided for `update-customer` rpc call"))
(defn- update-customer
[cfg request]
(let [{:keys [id subscription]}
(-> request :params coerce-update-customer-params)
{:keys [props] :as profile}
(cmd.profile/get-profile cfg id ::db/for-update true)
props
(assoc props :subscription subscription)]
(l/dbg :hint "update customer"
:profile-id (str id)
:subscription-type (get subscription :type)
:subscription-status (get subscription :status)
:subscription-quantity (get subscription :quantity))
(db/update! cfg :profile
{:props (db/tjson props)}
{:id id}
{::db/return-keys false})
{::yres/status 201
::yres/body nil}))

View File

@@ -33,7 +33,7 @@
(println "event:" (d/name name))
(println "data:" (t/encode-str data {:type :json-verbose}))
(println))]
(.getBytes data "UTF-8"))
(.getBytes ^String data "UTF-8"))
(catch Throwable cause
(l/err :hint "unexpected error on encoding value on sse stream"
:cause cause)

View File

@@ -20,6 +20,7 @@
[app.http.awsns :as http.awsns]
[app.http.client :as-alias http.client]
[app.http.debug :as-alias http.debug]
[app.http.management :as mgmt]
[app.http.session :as-alias session]
[app.http.session.tasks :as-alias session.tasks]
[app.http.websocket :as http.ws]
@@ -273,6 +274,10 @@
::email/blacklist (ig/ref ::email/blacklist)
::email/whitelist (ig/ref ::email/whitelist)}
::mgmt/routes
{::db/pool (ig/ref ::db/pool)
::setup/props (ig/ref ::setup/props)}
:app.http/router
{::session/manager (ig/ref ::session/manager)
::db/pool (ig/ref ::db/pool)
@@ -281,6 +286,7 @@
::setup/props (ig/ref ::setup/props)
::mtx/routes (ig/ref ::mtx/routes)
::oidc/routes (ig/ref ::oidc/routes)
::mgmt/routes (ig/ref ::mgmt/routes)
::http.debug/routes (ig/ref ::http.debug/routes)
::http.assets/routes (ig/ref ::http.assets/routes)
::http.ws/routes (ig/ref ::http.ws/routes)

View File

@@ -38,15 +38,13 @@
org.im4java.core.Info))
(def schema:upload
(sm/register!
^{::sm/type ::upload}
[:map {:title "Upload"}
[:filename :string]
[:size ::sm/int]
[:path ::fs/path]
[:mtype {:optional true} :string]
[:headers {:optional true}
[:map-of :string :string]]]))
[:map {:title "Upload"}
[:filename :string]
[:size ::sm/int]
[:path ::fs/path]
[:mtype {:optional true} :string]
[:headers {:optional true}
[:map-of :string :string]]])
(def ^:private schema:input
[:map {:title "Input"}
@@ -118,7 +116,7 @@
(defn- parse-svg
[text]
(let [text (strip-doctype text)]
(dm/with-open [istream (IOUtils/toInputStream text "UTF-8")]
(dm/with-open [istream (IOUtils/toInputStream ^String text "UTF-8")]
(xml/parse istream secure-parser-factory))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -444,7 +444,10 @@
:fn (mg/resource "app/migrations/sql/0140-mod-file-change-table.sql")}
{:name "0140-add-locked-by-column-to-file-change-table"
:fn (mg/resource "app/migrations/sql/0140-add-locked-by-column-to-file-change-table.sql")}])
:fn (mg/resource "app/migrations/sql/0140-add-locked-by-column-to-file-change-table.sql")}
{:name "0141-add-idx-to-file-library-rel"
:fn (mg/resource "app/migrations/sql/0141-add-idx-to-file-library-rel.sql")}])
(defn apply-migrations!
[pool name migrations]

View File

@@ -0,0 +1,2 @@
CREATE INDEX IF NOT EXISTS file_library_rel__library_file_id__idx
ON file_library_rel (library_file_id);

View File

@@ -127,7 +127,7 @@
[:project-id ::sm/uuid]
[:file-id {:optional true} ::sm/uuid]
[:version {:optional true} ::sm/int]
[:file ::media/upload]])
[:file media/schema:upload]])
(sv/defmethod ::import-binfile
"Import a penpot file in a binary format. If `file-id` is provided,

View File

@@ -78,6 +78,7 @@
;; --- FILE PERMISSIONS
(def ^:private sql:file-permissions
"select fpr.is_owner,
fpr.is_admin,
@@ -195,7 +196,7 @@
(def schema:permissions-mixin
[:map {:title "PermissionsMixin"}
[:permissions ::perms/permissions]])
[:permissions perms/schema:permissions]])
(def schema:file-with-permissions
[:merge {:title "FileWithPermissions"}
@@ -357,7 +358,7 @@
[:id ::sm/uuid]
[:file-id ::sm/uuid]
[:created-at ::ct/inst]
[:content any?]])
[:content ::sm/any]])
(def schema:get-file-fragment
[:map {:title "get-file-fragment"}
@@ -460,8 +461,42 @@
(:has-libraries row)))
;; --- COMMAND QUERY: get-library-usage
(declare get-library-usage)
(def schema:get-library-usage
[:map {:title "get-library-usage"}
[:file-id ::sm/uuid]])
:sample
(sv/defmethod ::get-library-usage
"Gets the number of files that use the specified library."
{::doc/added "2.10.0"
::sm/params schema:get-library-usage
::sm/result ::sm/int}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id]}]
(dm/with-open [conn (db/open pool)]
(check-read-permissions! pool profile-id file-id)
(get-library-usage conn file-id)))
(def ^:private sql:get-library-usage
"SELECT COUNT(*) AS used
FROM file_library_rel AS flr
JOIN file AS fl ON (flr.library_file_id = fl.id)
WHERE flr.library_file_id = ?::uuid
AND (fl.deleted_at IS NULL OR
fl.deleted_at > now())")
(defn- get-library-usage
[conn file-id]
(let [row (db/exec-one! conn [sql:get-library-usage file-id])]
{:used-in (:used row)}))
;; --- QUERY COMMAND: get-page
(defn- prune-objects
"Given the page data and the object-id returns the page data with all
other not needed objects removed from the `:objects` data
@@ -551,6 +586,24 @@
;; --- COMMAND QUERY: get-team-shared-files
(defn- components-and-variants
"Return a set with all the variant-ids, and a list of components, but with
only one component by variant"
[components]
(let [{:keys [variant-ids components]}
(reduce (fn [{:keys [variant-ids components] :as acc} {:keys [variant-id] :as component}]
(cond
(nil? variant-id)
{:variant-ids variant-ids :components (conj components component)}
(contains? variant-ids variant-id)
acc
:else
{:variant-ids (conj variant-ids variant-id) :components (conj components component)}))
{:variant-ids #{} :components []}
components)]
{:components components
:variant-ids variant-ids}))
(def ^:private sql:team-shared-files
"select f.id,
f.revn,
@@ -584,10 +637,13 @@
:sample (into [] (take limit sorted-assets))}))]
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
(let [load-objects (fn [component]
(ctf/load-component-objects data component))
components-sample (-> (assets-sample (ctkl/components data) 4)
(update :sample #(mapv load-objects %)))]
(let [load-objects (fn [component]
(ctf/load-component-objects data component))
comps-and-variants (components-and-variants (ctkl/components-seq data))
components (into {} (map (juxt :id identity) (:components comps-and-variants)))
components-sample (-> (assets-sample components 4)
(update :sample #(mapv load-objects %))
(assoc :variants-count (-> comps-and-variants :variant-ids count)))]
{:components components-sample
:media (assets-sample (:media data) 3)
:colors (assets-sample (:colors data) 3)
@@ -641,6 +697,7 @@
;; --- COMMAND QUERY: Files that use this File library
(def ^:private sql:library-using-files
"SELECT f.id,
f.name
@@ -713,6 +770,7 @@
;; --- COMMAND QUERY: get-file-summary
(defn- get-file-summary
[{:keys [::db/conn] :as cfg} {:keys [profile-id id project-id] :as params}]
(check-read-permissions! conn profile-id id)
@@ -730,11 +788,13 @@
(cfeat/check-file-features! (:features file)))
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
{:name (:name file)
:components-count (count (ctkl/components-seq (:data file)))
:graphics-count (count (get-in file [:data :media] []))
:colors-count (count (get-in file [:data :colors] []))
:typography-count (count (get-in file [:data :typographies] []))})))
(let [components-and-variants (components-and-variants (ctkl/components-seq (:data file)))]
{:name (:name file)
:components-count (-> components-and-variants :components count)
:variants-count (-> components-and-variants :variant-ids count)
:graphics-count (count (get-in file [:data :media] []))
:colors-count (count (get-in file [:data :colors] []))
:typography-count (count (get-in file [:data :typographies] []))}))))
(sv/defmethod ::get-file-summary
"Retrieve a file summary by its ID. Only authenticated users."
@@ -746,6 +806,7 @@
;; --- COMMAND QUERY: get-file-info
(defn- get-file-info
[{:keys [::db/conn] :as cfg} {:keys [id] :as params}]
(db/get* conn :file

View File

@@ -79,10 +79,9 @@
;; --- MUTATION COMMAND: update-temp-file
(def ^:private schema:update-temp-file
[:map {:title "update-temp-file"}
[:changes [:vector ::cpc/change]]
[:changes [:vector cpc/schema:change]]
[:revn [::sm/int {:min 0}]]
[:session-id ::sm/uuid]
[:id ::sm/uuid]])

View File

@@ -271,7 +271,7 @@
[:map {:title "create-file-object-thumbnail"}
[:file-id ::sm/uuid]
[:object-id [:string {:max 250}]]
[:media ::media/upload]
[:media media/schema:upload]
[:tag {:optional true} [:string {:max 50}]]])
(sv/defmethod ::create-file-object-thumbnail
@@ -381,7 +381,7 @@
[:map {:title "create-file-thumbnail"}
[:file-id ::sm/uuid]
[:revn ::sm/int]
[:media ::media/upload]])
[:media media/schema:upload]])
(sv/defmethod ::create-file-thumbnail
"Creates or updates the file thumbnail. Mainly used for paint the

View File

@@ -64,10 +64,10 @@
[:revn {:min 0} ::sm/int]
[:vern {:min 0} ::sm/int]
[:features {:optional true} ::cfeat/features]
[:changes {:optional true} [:vector ::cpc/change]]
[:changes {:optional true} [:vector cpc/schema:change]]
[:changes-with-metadata {:optional true}
[:vector [:map
[:changes [:vector ::cpc/change]]
[:changes [:vector cpc/schema:change]]
[:hint-origin {:optional true} :keyword]
[:hint-events {:optional true} [:vector [:string {:max 250}]]]]]]
[:skip-validate {:optional true} ::sm/boolean]])
@@ -76,7 +76,7 @@
schema:update-file-result
[:vector {:title "update-file-result"}
[:map
[:changes [:vector ::cpc/change]]
[:changes [:vector cpc/schema:change]]
[:file-id ::sm/uuid]
[:id ::sm/uuid]
[:revn {:min 0} ::sm/int]
@@ -408,7 +408,6 @@
(not skip-validate))
(bfc/get-resolved-file-libraries cfg file))
;; The main purpose of this atom is provide a contextual state
;; for the changes subsystem where optionally some hints can
;; be provided for the changes processing. Right now we are

View File

@@ -37,14 +37,13 @@
(def ^:private
schema:get-font-variants
[:schema {:title "get-font-variants"}
[:and
[:map
[:team-id {:optional true} ::sm/uuid]
[:file-id {:optional true} ::sm/uuid]
[:project-id {:optional true} ::sm/uuid]
[:share-id {:optional true} ::sm/uuid]]
[::sm/contains-any #{:team-id :file-id :project-id}]]])
[:and
[:map {:title "get-font-variants"}
[:team-id {:optional true} ::sm/uuid]
[:file-id {:optional true} ::sm/uuid]
[:project-id {:optional true} ::sm/uuid]
[:share-id {:optional true} ::sm/uuid]]
[::sm/contains-any #{:team-id :file-id :project-id}]])
(sv/defmethod ::get-font-variants
{::doc/added "1.18"

View File

@@ -48,7 +48,7 @@
[:file-id ::sm/uuid]
[:is-local ::sm/boolean]
[:name [:string {:max 250}]]
[:content ::media/upload]])
[:content media/schema:upload]])
(sv/defmethod ::upload-file-media-object
{::doc/added "1.17"

View File

@@ -131,9 +131,7 @@
;; NOTE: we need to retrieve the profile independently if we use
;; it or not for explicit locking and avoid concurrent updates of
;; the same row/object.
(let [profile (-> (db/get-by-id conn :profile profile-id ::sql/for-update true)
(decode-row))
(let [profile (get-profile conn profile-id ::db/for-update true)
;; Update the profile map with direct params
profile (-> profile
(assoc :fullname fullname)
@@ -143,9 +141,9 @@
(db/update! conn :profile
{:fullname fullname
:lang lang
:theme theme
:props (db/tjson (:props profile))}
{:id profile-id})
:theme theme}
{:id profile-id}
{::db/return-keys false})
(-> profile
(strip-private-attrs)
@@ -228,21 +226,22 @@
(defn- update-notifications!
[{:keys [::db/conn] :as cfg} {:keys [profile-id dashboard-comments email-comments email-invites]}]
(let [profile (get-profile conn profile-id)
(let [profile
(get-profile conn profile-id ::db/for-update true)
notifications
{:dashboard-comments dashboard-comments
:email-comments email-comments
:email-invites email-invites}]
:email-invites email-invites}
(db/update!
conn :profile
{:props
(-> (:props profile)
(assoc :notifications notifications)
(db/tjson))}
{:id (:id profile)})
props
(-> (get profile :props)
(assoc :notifications notifications))]
(db/update! conn :profile
{:props (db/tjson props)}
{:id profile-id}
{::db/return-keys false})
nil))
;; --- MUTATION: Update Photo
@@ -253,7 +252,7 @@
(def ^:private
schema:update-profile-photo
[:map {:title "update-profile-photo"}
[:file ::media/upload]])
[:file media/schema:upload]])
(sv/defmethod ::update-profile-photo
{:doc/added "1.1"
@@ -411,7 +410,7 @@
(defn update-profile-props
[{:keys [::db/conn] :as cfg} profile-id props]
(let [profile (get-profile conn profile-id ::sql/for-update true)
(let [profile (get-profile conn profile-id ::db/for-update true)
props (reduce-kv (fn [props k v]
;; We don't accept namespaced keys
(if (simple-ident? k)
@@ -424,16 +423,17 @@
(db/update! conn :profile
{:props (db/tjson props)}
{:id profile-id})
{:id profile-id}
{::db/return-keys false})
(filter-props props)))
(sv/defmethod ::update-profile-props
{::doc/added "1.0"
::sm/params schema:update-profile-props}
::sm/params schema:update-profile-props
::db/transaction true}
[cfg {:keys [::rpc/profile-id props]}]
(db/tx-run! cfg (fn [cfg]
(update-profile-props cfg profile-id props))))
(update-profile-props cfg profile-id props))
;; --- MUTATION: Delete Profile
@@ -471,6 +471,26 @@
(-> (rph/wrap nil)
(rph/with-transform (session/delete-fn cfg)))))
(def sql:get-subscription-editors
"SELECT DISTINCT
p.id,
p.fullname AS name,
p.email AS email
FROM team_profile_rel AS tpr1
JOIN team_profile_rel AS tpr2
ON (tpr1.team_id = tpr2.team_id)
JOIN profile AS p
ON (tpr2.profile_id = p.id)
WHERE tpr1.profile_id = ?
AND tpr1.is_owner IS true
AND tpr2.can_edit IS true")
(sv/defmethod ::get-subscription-usage
{::doc/added "2.9"}
[cfg {:keys [::rpc/profile-id]}]
(let [editors (db/exec! cfg [sql:get-subscription-editors profile-id])]
{:editors editors}))
;; --- HELPERS
(def sql:owned-teams

View File

@@ -12,7 +12,7 @@
[app.common.features :as cfeat]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.team :as tt]
[app.common.types.team :as types.team]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -629,7 +629,7 @@
;; assign owner role to new profile
(db/update! conn :team-profile-rel
(get tt/permissions-for-role :owner)
(get types.team/permissions-for-role :owner)
{:team-id id :profile-id reassign-to}))
;; and finally, if all other conditions does not match and the
@@ -742,7 +742,7 @@
:team-id team-id
:role role})
(let [params (get tt/permissions-for-role role)]
(let [params (get types.team/permissions-for-role role)]
;; Only allow single owner on team
(when (= role :owner)
(db/update! conn :team-profile-rel
@@ -760,7 +760,7 @@
[:map {:title "update-team-member-role"}
[:team-id ::sm/uuid]
[:member-id ::sm/uuid]
[:role ::tt/role]])
[:role types.team/schema:role]])
(sv/defmethod ::update-team-member-role
{::doc/added "1.17"
@@ -810,7 +810,7 @@
(def ^:private schema:update-team-photo
[:map {:title "update-team-photo"}
[:team-id ::sm/uuid]
[:file ::media/upload]])
[:file media/schema:upload]])
(sv/defmethod ::update-team-photo
{::doc/added "1.17"

View File

@@ -75,7 +75,7 @@
[:map
[:id ::sm/uuid]
[:fullname :string]]]
[:role ::types.team/role]
[:role types.team/schema:role]
[:email ::sm/email]])
(def ^:private check-create-invitation-params
@@ -257,7 +257,7 @@
(def ^:private schema:create-team-invitations
[:map {:title "create-team-invitations"}
[:team-id ::sm/uuid]
[:role ::types.team/role]
[:role types.team/schema:role]
[:emails [::sm/set ::sm/email]]])
(def ^:private max-invitations-by-request-threshold
@@ -318,7 +318,7 @@
[:features {:optional true} ::cfeat/features]
[:id {:optional true} ::sm/uuid]
[:emails [::sm/set ::sm/email]]
[:role ::types.team/role]])
[:role types.team/schema:role]])
(sv/defmethod ::create-team-with-invitations
{::doc/added "1.17"
@@ -403,7 +403,7 @@
[:map {:title "update-team-invitation-role"}
[:team-id ::sm/uuid]
[:email ::sm/email]
[:role ::types.team/role]])
[:role types.team/schema:role]])
(sv/defmethod ::update-team-invitation-role
{::doc/added "1.17"

View File

@@ -128,7 +128,7 @@
[:iss :keyword]
[:exp ::ct/inst]
[:profile-id ::sm/uuid]
[:role ::types.team/role]
[:role types.team/schema:role]
[:team-id ::sm/uuid]
[:member-email ::sm/email]
[:member-id {:optional true} ::sm/uuid]])

View File

@@ -166,9 +166,6 @@
:servers [{:url (str/ffmt "%/api/rpc" (cf/get :public-uri))
;; :description "penpot backend"
}]
:security
{:api_key []}
:paths paths
:components {:schemas @definitions}}))

View File

@@ -10,15 +10,14 @@
[app.common.exceptions :as ex]
[app.common.schema :as sm]))
(sm/register!
^{::sm/type ::permissions}
[:map {:title "Permissions"}
[:type {:gen/elements [:membership :share-link]} :keyword]
[:is-owner ::sm/boolean]
[:is-admin ::sm/boolean]
[:can-edit ::sm/boolean]
[:can-read ::sm/boolean]
[:is-logged ::sm/boolean]])
(def schema:permissions
[:map {:title "Permissions"}
[:type {:gen/elements [:membership :share-link]} :keyword]
[:is-owner ::sm/boolean]
[:is-admin ::sm/boolean]
[:can-edit ::sm/boolean]
[:can-read ::sm/boolean]
[:is-logged ::sm/boolean]])
(def valid-roles
#{:admin :owner :editor :viewer})

View File

@@ -212,8 +212,8 @@
deleted 0]
(if-let [chunk (get-chunk pool)]
(let [[nfo ndo] (db/tx-run! cfg process-chunk! chunk)]
(recur (+ freezed nfo)
(+ deleted ndo)))
(recur (long (+ freezed nfo))
(long (+ deleted ndo))))
(do
(l/inf :hint "task finished"
:to-freeze freezed

View File

@@ -313,7 +313,7 @@
(db/exec-one! conn ["SET LOCAL rules.deletion_protection TO off"])
(proc-fn cfg)))]
(if (pos? result)
(recur (+ total result))
(recur (long (+ total result)))
total))))
(defmethod ig/assert-key ::handler
@@ -335,7 +335,7 @@
(if-let [proc-fn (first procs)]
(let [result (execute-proc! cfg proc-fn)]
(recur (rest procs)
(+ total result)))
(long (+ total result))))
(do
(l/inf :hint "task finished" :deleted total)
{:processed total}))))))

View File

@@ -0,0 +1,96 @@
;; 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 backend-tests.http-management-test
(:require
[app.common.data :as d]
[app.common.time :as ct]
[app.db :as db]
[app.http.access-token]
[app.http.management :as mgmt]
[app.http.session :as sess]
[app.main :as-alias main]
[app.rpc :as-alias rpc]
[backend-tests.helpers :as th]
[clojure.test :as t]
[mockery.core :refer [with-mocks]]
[yetti.response :as-alias yres]))
(t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset)
(t/deftest authenticate-method
(let [profile (th/create-profile* 1)
props (get th/*system* :app.setup/props)
token (#'sess/gen-token props {:profile-id (:id profile)})
request {:params {:token token}}
response (#'mgmt/authenticate th/*system* request)]
(t/is (= 200 (::yres/status response)))
(t/is (= "authentication" (-> response ::yres/body :iss)))
(t/is (= (:id profile) (-> response ::yres/body :uid)))))
(t/deftest get-customer-method
(let [profile (th/create-profile* 1)
request {:params {:id (:id profile)}}
response (#'mgmt/get-customer th/*system* request)]
(t/is (= 200 (::yres/status response)))
(t/is (= (:id profile) (-> response ::yres/body :id)))
(t/is (= (:fullname profile) (-> response ::yres/body :name)))
(t/is (= (:email profile) (-> response ::yres/body :email)))
(t/is (= 1 (-> response ::yres/body :num-editors)))
(t/is (nil? (-> response ::yres/body :subscription)))))
(t/deftest update-customer-method
(let [profile (th/create-profile* 1)
subs {:type "unlimited"
:description nil
:id "foobar"
:customer-id (str (:id profile))
:status "past_due"
:billing-period "week"
:quantity 1
:created-at (ct/truncate (ct/now) :day)
:cancel-at-period-end true
:start-date nil
:ended-at nil
:trial-end nil
:trial-start nil
:cancel-at nil
:canceled-at nil
:current-period-end nil
:current-period-start nil
:cancellation-details
{:comment "other"
:reason "other"
:feedback "other"}}
request {:params {:id (:id profile)
:subscription subs}}
response (#'mgmt/update-customer th/*system* request)]
(t/is (= 201 (::yres/status response)))
(t/is (nil? (::yres/body response)))
(let [request {:params {:id (:id profile)}}
response (#'mgmt/get-customer th/*system* request)]
(t/is (= 200 (::yres/status response)))
(t/is (= (:id profile) (-> response ::yres/body :id)))
(t/is (= (:fullname profile) (-> response ::yres/body :name)))
(t/is (= (:email profile) (-> response ::yres/body :email)))
(t/is (= 1 (-> response ::yres/body :num-editors)))
(let [subs' (-> response ::yres/body :subscription)]
(t/is (= subs' subs))))))

View File

@@ -0,0 +1,36 @@
;; 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 backend-tests.rpc-doc-test
"Internal binfile test, no RPC involved"
(:require
[app.common.json :as json]
[app.common.pprint :as pp]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.schema.test :as smt]
[app.rpc :as-alias rpc]
[app.rpc.doc :as rpc.doc]
[backend-tests.helpers :as th]
[clojure.test :as t]))
(t/use-fixtures :once th/state-init)
(t/deftest openapi-context-json-encode
(smt/check!
(smt/for [context (->> sg/int
(sg/fmap (fn [_]
(rpc.doc/prepare-openapi-context (::rpc/methods th/*system*)))))]
(try
(json/encode context)
true
(catch Throwable _cause
false)))
{:num 30}))

View File

@@ -86,7 +86,7 @@
(t/deftest internal-encode-decode
(smt/check!
(smt/for [data (->> (cg/map cg/uuid (sg/generator ::cts/shape))
(smt/for [data (->> (cg/map cg/uuid (sg/generator cts/schema:shape))
(cg/not-empty))]
(let [obj1 (omap/wrap data)
obj2 (omap/create (deref obj1))
@@ -103,7 +103,7 @@
(t/deftest fressian-encode-decode
(smt/check!
(smt/for [data (->> (cg/map cg/uuid (sg/generator ::cts/shape))
(smt/for [data (->> (cg/map cg/uuid (sg/generator cts/schema:shape))
(cg/not-empty)
(cg/fmap omap/wrap)
(cg/fmap (fn [o] {:objects o})))]
@@ -119,7 +119,7 @@
(t/deftest transit-encode-decode
(smt/check!
(smt/for [data (->> (cg/map cg/uuid (sg/generator ::cts/shape))
(smt/for [data (->> (cg/map cg/uuid (sg/generator cts/schema:shape))
(cg/not-empty)
(cg/fmap omap/wrap)
(cg/fmap (fn [o] {:objects o})))]

View File

@@ -1,5 +1,5 @@
{:deps
{org.clojure/clojure {:mvn/version "1.12.1"}
{org.clojure/clojure {:mvn/version "1.12.2"}
org.clojure/data.json {:mvn/version "2.5.1"}
org.clojure/tools.cli {:mvn/version "1.1.230"}
org.clojure/test.check {:mvn/version "1.1.1"}
@@ -43,7 +43,7 @@
frankiesardo/linked {:mvn/version "1.3.0"}
com.sun.mail/jakarta.mail {:mvn/version "2.0.1"}
com.sun.mail/jakarta.mail {:mvn/version "2.0.2"}
org.la4j/la4j {:mvn/version "0.6.0"}
;; exception printing

View File

@@ -5,7 +5,8 @@
;; Copyright (c) KALEIDOS INC
(ns app.common.buffer
"A collection of helpers and macros for work with byte buffers"
"A collection of helpers and macros for work with byte
buffer (ByteBuffer on JVM and DataView on JS)."
(:refer-clojure :exclude [clone])
(:require
[app.common.uuid :as uuid])
@@ -19,42 +20,42 @@
(if (:ns &env)
`(.getInt8 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(long (.get ~target ~offset)))))
`(long (.get ~target (unchecked-int ~offset))))))
(defmacro read-unsigned-byte
[target offset]
(if (:ns &env)
`(.getUint8 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(bit-and (long (.get ~target ~offset)) 0xff))))
`(bit-and (long (.get ~target (unchecked-int ~offset))) 0xff))))
(defmacro read-bool
[target offset]
(if (:ns &env)
`(== 1 (.getInt8 ~target ~offset true))
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(== 1 (.get ~target ~offset)))))
`(== 1 (.get ~target (unchecked-int ~offset))))))
(defmacro read-short
[target offset]
(if (:ns &env)
`(.getInt16 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.getShort ~target ~offset))))
`(.getShort ~target (unchecked-int ~offset)))))
(defmacro read-int
[target offset]
(if (:ns &env)
`(.getInt32 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(long (.getInt ~target ~offset)))))
`(long (.getInt ~target (unchecked-int ~offset))))))
(defmacro read-float
[target offset]
(if (:ns &env)
`(.getFloat32 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(double (.getFloat ~target ~offset)))))
`(double (.getFloat ~target (unchecked-int ~offset))))))
(defmacro read-uuid
[target offset]
@@ -68,8 +69,8 @@
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(try
(.order ~target ByteOrder/BIG_ENDIAN)
(let [msb# (.getLong ~target (+ ~offset 0))
lsb# (.getLong ~target (+ ~offset 8))]
(let [msb# (.getLong ~target (unchecked-int (+ ~offset 0)))
lsb# (.getLong ~target (unchecked-int (+ ~offset 8)))]
(java.util.UUID. (long msb#) (long lsb#)))
(finally
(.order ~target ByteOrder/LITTLE_ENDIAN))))))
@@ -78,6 +79,13 @@
[target offset value]
(if (:ns &env)
`(.setInt8 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.put ~target (unchecked-int ~offset) (unchecked-byte ~value)))))
(defmacro write-u8
[target offset value]
(if (:ns &env)
`(.setUint8 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.put ~target ~offset (unchecked-byte ~value)))))
@@ -86,28 +94,45 @@
(if (:ns &env)
`(.setInt8 ~target ~offset (if ~value 0x01 0x00) true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.put ~target ~offset (unchecked-byte (if ~value 0x01 0x00))))))
`(.put ~target (unchecked-int ~offset) (unchecked-byte (if ~value 0x01 0x00))))))
(defmacro write-short
[target offset value]
(if (:ns &env)
`(.setInt16 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putShort ~target ~offset (unchecked-short ~value)))))
`(.putShort ~target (unchecked-int ~offset) (unchecked-short ~value)))))
(defmacro write-int
[target offset value]
(if (:ns &env)
`(.setInt32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putInt ~target (unchecked-int ~offset) (unchecked-int ~value)))))
(defmacro write-u32
[target offset value]
(if (:ns &env)
`(.setUint32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putInt ~target ~offset (unchecked-int ~value)))))
(defmacro write-i32
"Idiomatic alias for `write-int`"
[target offset value]
`(write-int ~target ~offset ~value))
(defmacro write-float
[target offset value]
(if (:ns &env)
`(.setFloat32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putFloat ~target ~offset (unchecked-float ~value)))))
`(.putFloat ~target (unchecked-int ~offset) (unchecked-float ~value)))))
(defmacro write-f32
"Idiomatic alias for `write-float`."
[target offset value]
`(write-float ~target ~offset ~value))
(defmacro write-uuid
[target offset value]
@@ -122,8 +147,8 @@
value (with-meta value {:tag 'java.util.UUID})]
`(try
(.order ~target ByteOrder/BIG_ENDIAN)
(.putLong ~target (+ ~offset 0) (.getMostSignificantBits ~value))
(.putLong ~target (+ ~offset 8) (.getLeastSignificantBits ~value))
(.putLong ~target (unchecked-int (+ ~offset 0)) (.getMostSignificantBits ~value))
(.putLong ~target (unchecked-int (+ ~offset 8)) (.getLeastSignificantBits ~value))
(finally
(.order ~target ByteOrder/LITTLE_ENDIAN))))))

View File

@@ -64,7 +64,8 @@
"layout/grid"
"components/v2"
"plugins/runtime"
"design-tokens/v1"})
"design-tokens/v1"
"variants/v1"})
;; A set of features that should not be propagated to team on creating
;; or modifying a file
@@ -95,7 +96,8 @@
(-> #{"layout/grid"
"design-tokens/v1"
"fdata/shape-data-type"
"fdata/path-data"}
"fdata/path-data"
"variants/v1"}
(into frontend-only-features)
(into backend-only-features)))

View File

@@ -83,24 +83,25 @@
[:multi {:decode/json #(update % :grid-type keyword)
:gen/gen gen
:title "SetDefaultGridChange"
:dispatch :grid-type
::smd/simplified true}
[:square
[:map
[:map {:title "SetDefautSquareGridAttrs"}
[:type [:= :set-default-grid]]
[:page-id ::sm/uuid]
[:grid-type [:= :square]]
[:params [:maybe ctg/schema:square-params]]]]
[:column
[:map
[:map {:title "SetDefaultColumnGridAttrs"}
[:type [:= :set-default-grid]]
[:page-id ::sm/uuid]
[:grid-type [:= :column]]
[:params [:maybe ctg/schema:column-params]]]]
[:row
[:map
[:map {:title "SetDefaultRowGridAttrs"}
[:type [:= :set-default-grid]]
[:page-id ::sm/uuid]
[:grid-type [:= :row]]
@@ -111,20 +112,20 @@
[:type [:= :set-guide]]
[:page-id ::sm/uuid]
[:id ::sm/uuid]
[:params [:maybe ::ctp/guide]]]
[:params [:maybe ctp/schema:guide]]]
gen (->> (sg/generator schema)
(sg/fmap (fn [change]
(if (some? (:params change))
(update change :params assoc :id (:id change))
change))))]
[:schema {:gen/gen gen} schema]))
(sm/update-properties schema assoc :gen/gen gen)))
(def schema:set-flow-change
(let [schema [:map {:title "SetFlowChange"}
[:type [:= :set-flow]]
[:page-id ::sm/uuid]
[:id ::sm/uuid]
[:params [:maybe ::ctp/flow]]]
[:params [:maybe ctp/schema:flow]]]
gen (->> (sg/generator schema)
(sg/fmap (fn [change]
@@ -132,7 +133,7 @@
(update change :params assoc :id (:id change))
change))))]
[:schema {:gen/gen gen} schema]))
(sm/update-properties schema assoc :gen/gen gen)))
(def schema:set-plugin-data-change
(let [types #{:file :page :shape :color :typography :component}
@@ -169,287 +170,274 @@
:else
(dissoc change :page-id)))))]
[:and {:gen/gen gen} schema check1]))
[:and (sm/update-properties schema assoc :gen/gen gen) check1]))
(def schema:change
[:schema
[:multi {:dispatch :type
:title "Change"
:decode/json #(update % :type keyword)
::smd/simplified true}
[:set-option
[:multi {:dispatch :type
:title "Change"
:decode/json #(update % :type keyword)
::smd/simplified true}
;; DEPRECATED: remove before 2.3 release
;;
;; Is still there for not cause error when event is received
[:map {:title "SetOptionChange"}]]
[:set-comment-thread-position
[:map {:title "SetCommentThreadPositionChange"}
[:comment-thread-id ::sm/uuid]
[:page-id ::sm/uuid]
[:frame-id [:maybe ::sm/uuid]]
[:position [:maybe ::gpt/point]]]]
[:set-comment-thread-position
[:map
[:comment-thread-id ::sm/uuid]
[:page-id ::sm/uuid]
[:frame-id [:maybe ::sm/uuid]]
[:position [:maybe ::gpt/point]]]]
[:add-obj
[:map {:title "AddObjChange"}
[:type [:= :add-obj]]
[:id ::sm/uuid]
[:obj cts/schema:shape]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:frame-id ::sm/uuid]
[:parent-id {:optional true} [:maybe ::sm/uuid]]
[:index {:optional true} [:maybe :int]]
[:ignore-touched {:optional true} :boolean]]]
[:add-obj
[:map {:title "AddObjChange"}
[:type [:= :add-obj]]
[:id ::sm/uuid]
[:obj :map]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:frame-id ::sm/uuid]
[:parent-id {:optional true} [:maybe ::sm/uuid]]
[:index {:optional true} [:maybe :int]]
[:ignore-touched {:optional true} :boolean]]]
[:mod-obj
[:map {:title "ModObjChange"}
[:type [:= :mod-obj]]
[:id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:operations [:vector {:gen/max 5} schema:operation]]]]
[:mod-obj
[:map {:title "ModObjChange"}
[:type [:= :mod-obj]]
[:id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:operations [:vector {:gen/max 5} schema:operation]]]]
[:del-obj
[:map {:title "DelObjChange"}
[:type [:= :del-obj]]
[:id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean]]]
[:del-obj
[:map {:title "DelObjChange"}
[:type [:= :del-obj]]
[:id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean]]]
[:set-guide schema:set-guide-change]
[:set-flow schema:set-flow-change]
[:set-default-grid schema:set-default-grid-change]
[:set-guide schema:set-guide-change]
[:set-flow schema:set-flow-change]
[:set-default-grid schema:set-default-grid-change]
[:fix-obj
[:map {:title "FixObjChange"}
[:type [:= :fix-obj]]
[:id ::sm/uuid]
[:fix {:optional true} :keyword]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]]]
[:fix-obj
[:map {:title "FixObjChange"}
[:type [:= :fix-obj]]
[:id ::sm/uuid]
[:fix {:optional true} :keyword]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]]]
[:mov-objects
[:map {:title "MovObjectsChange"}
[:type [:= :mov-objects]]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean]
[:parent-id ::sm/uuid]
[:shapes ::sm/any]
[:index {:optional true} [:maybe :int]]
[:after-shape {:optional true} ::sm/any]
[:allow-altering-copies {:optional true} :boolean]]]
[:mov-objects
[:map {:title "MovObjectsChange"}
[:type [:= :mov-objects]]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean]
[:parent-id ::sm/uuid]
[:shapes ::sm/any]
[:index {:optional true} [:maybe :int]]
[:after-shape {:optional true} ::sm/any]
[:allow-altering-copies {:optional true} :boolean]]]
[:reorder-children
[:map {:title "ReorderChildrenChange"}
[:type [:= :reorder-children]]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean]
[:parent-id ::sm/uuid]
[:shapes ::sm/any]]]
[:reorder-children
[:map {:title "ReorderChildrenChange"}
[:type [:= :reorder-children]]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean]
[:parent-id ::sm/uuid]
[:shapes ::sm/any]]]
[:add-page
[:map {:title "AddPageChange"}
[:type [:= :add-page]]
[:id {:optional true} ::sm/uuid]
[:name {:optional true} :string]
[:page {:optional true} ::sm/any]]]
[:add-page
[:map {:title "AddPageChange"}
[:type [:= :add-page]]
[:id {:optional true} ::sm/uuid]
[:name {:optional true} :string]
[:page {:optional true} ::sm/any]]]
[:mod-page
[:map {:title "ModPageChange"}
[:type [:= :mod-page]]
[:id ::sm/uuid]
;; All props are optional, background can be nil because is the
;; way to remove already set background
[:background {:optional true} [:maybe ctc/schema:hex-color]]
[:name {:optional true} :string]]]
[:mod-page
[:map {:title "ModPageChange"}
[:type [:= :mod-page]]
[:id ::sm/uuid]
;; All props are optional, background can be nil because is the
;; way to remove already set background
[:background {:optional true} [:maybe ctc/schema:hex-color]]
[:name {:optional true} :string]]]
[:set-plugin-data schema:set-plugin-data-change]
[:set-plugin-data schema:set-plugin-data-change]
[:del-page
[:map {:title "DelPageChange"}
[:type [:= :del-page]]
[:id ::sm/uuid]]]
[:del-page
[:map {:title "DelPageChange"}
[:type [:= :del-page]]
[:id ::sm/uuid]]]
[:mov-page
[:map {:title "MovPageChange"}
[:type [:= :mov-page]]
[:id ::sm/uuid]
[:index :int]]]
[:mov-page
[:map {:title "MovPageChange"}
[:type [:= :mov-page]]
[:id ::sm/uuid]
[:index :int]]]
[:reg-objects
[:map {:title "RegObjectsChange"}
[:type [:= :reg-objects]]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:shapes [:vector {:gen/max 5} ::sm/uuid]]]]
[:reg-objects
[:map {:title "RegObjectsChange"}
[:type [:= :reg-objects]]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:shapes [:vector {:gen/max 5} ::sm/uuid]]]]
[:add-color
[:map {:title "AddColorChange"}
[:type [:= :add-color]]
[:color ctc/schema:library-color]]]
[:add-color
[:map {:title "AddColorChange"}
[:type [:= :add-color]]
[:color ctc/schema:library-color]]]
[:mod-color
[:map {:title "ModColorChange"}
[:type [:= :mod-color]]
[:color ctc/schema:library-color]]]
[:mod-color
[:map {:title "ModColorChange"}
[:type [:= :mod-color]]
[:color ctc/schema:library-color]]]
[:del-color
[:map {:title "DelColorChange"}
[:type [:= :del-color]]
[:id ::sm/uuid]]]
[:del-color
[:map {:title "DelColorChange"}
[:type [:= :del-color]]
[:id ::sm/uuid]]]
[:add-media
[:map {:title "AddMediaChange"}
[:type [:= :add-media]]
[:object ctf/schema:media]]]
;; DEPRECATED: remove before 2.3
[:add-recent-color
[:map {:title "AddRecentColorChange"}]]
[:mod-media
[:map {:title "ModMediaChange"}
[:type [:= :mod-media]]
[:object ctf/schema:media]]]
[:add-media
[:map {:title "AddMediaChange"}
[:type [:= :add-media]]
[:object ctf/schema:media]]]
[:del-media
[:map {:title "DelMediaChange"}
[:type [:= :del-media]]
[:id ::sm/uuid]]]
[:mod-media
[:map {:title "ModMediaChange"}
[:type [:= :mod-media]]
[:object ctf/schema:media]]]
[:add-component
[:map {:title "AddComponentChange"}
[:type [:= :add-component]]
[:id ::sm/uuid]
[:name :string]
[:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
[:path {:optional true} :string]
[:main-instance-id ::sm/uuid]
[:main-instance-page ::sm/uuid]]]
[:del-media
[:map {:title "DelMediaChange"}
[:type [:= :del-media]]
[:id ::sm/uuid]]]
[:mod-component
[:map {:title "ModCompoenentChange"}
[:type [:= :mod-component]]
[:id ::sm/uuid]
[:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
[:name {:optional true} :string]
[:variant-id {:optional true} ::sm/uuid]
[:variant-properties {:optional true} [:vector ctv/schema:variant-property]]]]
[:add-component
[:map {:title "AddComponentChange"}
[:type [:= :add-component]]
[:id ::sm/uuid]
[:name :string]
[:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
[:path {:optional true} :string]]]
[:del-component
[:map {:title "DelComponentChange"}
[:type [:= :del-component]]
[:id ::sm/uuid]
;; when it is an undo of a cut-paste, we need to undo the movement
;; of the shapes so we need to move them delta
[:delta {:optional true} ::gpt/point]
[:skip-undelete? {:optional true} :boolean]]]
[:mod-component
[:map {:title "ModCompoenentChange"}
[:type [:= :mod-component]]
[:id ::sm/uuid]
[:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
[:name {:optional true} :string]
[:variant-id {:optional true} ::sm/uuid]
[:variant-properties {:optional true} [:vector ::ctv/variant-property]]]]
[:restore-component
[:map {:title "RestoreComponentChange"}
[:type [:= :restore-component]]
[:id ::sm/uuid]
[:page-id ::sm/uuid]]]
[:del-component
[:map {:title "DelComponentChange"}
[:type [:= :del-component]]
[:id ::sm/uuid]
;; when it is an undo of a cut-paste, we need to undo the movement
;; of the shapes so we need to move them delta
[:delta {:optional true} ::gpt/point]
[:skip-undelete? {:optional true} :boolean]]]
[:purge-component
[:map {:title "PurgeComponentChange"}
[:type [:= :purge-component]]
[:id ::sm/uuid]]]
[:restore-component
[:map {:title "RestoreComponentChange"}
[:type [:= :restore-component]]
[:id ::sm/uuid]
[:page-id ::sm/uuid]]]
[:add-typography
[:map {:title "AddTypogrphyChange"}
[:type [:= :add-typography]]
[:typography ctt/schema:typography]]]
[:purge-component
[:map {:title "PurgeComponentChange"}
[:type [:= :purge-component]]
[:id ::sm/uuid]]]
[:mod-typography
[:map {:title "ModTypogrphyChange"}
[:type [:= :mod-typography]]
[:typography ctt/schema:typography]]]
[:add-typography
[:map {:title "AddTypogrphyChange"}
[:type [:= :add-typography]]
[:typography ::ctt/typography]]]
[:del-typography
[:map {:title "DelTypogrphyChange"}
[:type [:= :del-typography]]
[:id ::sm/uuid]]]
[:mod-typography
[:map {:title "ModTypogrphyChange"}
[:type [:= :mod-typography]]
[:typography ::ctt/typography]]]
[:update-active-token-themes
[:map {:title "UpdateActiveTokenThemes"}
[:type [:= :update-active-token-themes]]
[:theme-paths [:set :string]]]]
[:del-typography
[:map {:title "DelTypogrphyChange"}
[:type [:= :del-typography]]
[:id ::sm/uuid]]]
[:rename-token-set-group
[:map {:title "RenameTokenSetGroup"}
[:type [:= :rename-token-set-group]]
[:set-group-path [:vector :string]]
[:set-group-fname :string]]]
[:update-active-token-themes
[:map {:title "UpdateActiveTokenThemes"}
[:type [:= :update-active-token-themes]]
[:theme-paths [:set :string]]]]
[:move-token-set
[:map {:title "MoveTokenSet"}
[:type [:= :move-token-set]]
[:from-path [:vector :string]]
[:to-path [:vector :string]]
[:before-path [:maybe [:vector :string]]]
[:before-group [:maybe :boolean]]]]
[:rename-token-set-group
[:map {:title "RenameTokenSetGroup"}
[:type [:= :rename-token-set-group]]
[:set-group-path [:vector :string]]
[:set-group-fname :string]]]
[:move-token-set-group
[:map {:title "MoveTokenSetGroup"}
[:type [:= :move-token-set-group]]
[:from-path [:vector :string]]
[:to-path [:vector :string]]
[:before-path [:maybe [:vector :string]]]
[:before-group [:maybe :boolean]]]]
[:move-token-set
[:map {:title "MoveTokenSet"}
[:type [:= :move-token-set]]
[:from-path [:vector :string]]
[:to-path [:vector :string]]
[:before-path [:maybe [:vector :string]]]
[:before-group [:maybe :boolean]]]]
[:set-token-theme
[:map {:title "SetTokenThemeChange"}
[:type [:= :set-token-theme]]
[:theme-name :string]
[:group :string]
[:theme [:maybe ctob/schema:token-theme-attrs]]]]
[:move-token-set-group
[:map {:title "MoveTokenSetGroup"}
[:type [:= :move-token-set-group]]
[:from-path [:vector :string]]
[:to-path [:vector :string]]
[:before-path [:maybe [:vector :string]]]
[:before-group [:maybe :boolean]]]]
[:set-tokens-lib
[:map {:title "SetTokensLib"}
[:type [:= :set-tokens-lib]]
[:tokens-lib ::sm/any]]]
[:set-token-theme
[:map {:title "SetTokenThemeChange"}
[:type [:= :set-token-theme]]
[:theme-name :string]
[:group :string]
[:theme [:maybe ctob/schema:token-theme-attrs]]]]
[:set-token-set
[:map {:title "SetTokenSetChange"}
[:type [:= :set-token-set]]
[:set-name :string]
[:group? :boolean]
[:set-tokens-lib
[:map {:title "SetTokensLib"}
[:type [:= :set-tokens-lib]]
[:tokens-lib ::sm/any]]]
;; FIXME: we should not pass private types as part of changes
;; protocol, the changes protocol should reflect a
;; method/protocol for perform surgical operations on file data,
;; this has nothing todo with internal types of a file data
;; structure.
[:token-set {:gen/gen (sg/generator ctob/schema:token-set)}
[:maybe [:fn ctob/token-set?]]]]]
[:set-token-set
[:map {:title "SetTokenSetChange"}
[:type [:= :set-token-set]]
[:set-name :string]
[:group? :boolean]
[:set-token
[:map {:title "SetTokenChange"}
[:type [:= :set-token]]
[:set-name :string]
[:token-id ::sm/uuid]
[:token [:maybe ctob/schema:token-attrs]]]]
;; FIXME: we should not pass private types as part of changes
;; protocol, the changes protocol should reflect a
;; method/protocol for perform surgical operations on file data,
;; this has nothing todo with internal types of a file data
;; structure.
[:token-set {:gen/gen (sg/generator ctob/schema:token-set)}
[:maybe [:fn ctob/token-set?]]]]]
[:set-token
[:map {:title "SetTokenChange"}
[:type [:= :set-token]]
[:set-name :string]
[:token-id ::sm/uuid]
[:token [:maybe ctob/schema:token-attrs]]]]
[:set-base-font-size
[:map {:title "ModBaseFontSize"}
[:type [:= :set-base-font-size]]
[:base-font-size :string]]]]])
[:set-base-font-size
[:map {:title "ModBaseFontSize"}
[:type [:= :set-base-font-size]]
[:base-font-size :string]]]])
(def schema:changes
[:sequential {:gen/max 5 :gen/min 1} schema:change])
(sm/register! ::change schema:change)
(sm/register! ::changes schema:changes)
(def valid-change?
(sm/lazy-validator schema:change))
(def check-changes!
(def check-changes
(sm/check-fn schema:changes))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -534,7 +522,7 @@
;; When verify? false we spec the schema validation. Currently used
;; to make just 1 validation even if the changes are applied twice
(when verify?
(check-changes! items))
(check-changes items))
(binding [*touched-changes* (volatile! #{})
cts/*wasm-sync* true]
@@ -547,11 +535,6 @@
#?(:clj (validate-shapes! data result items))
result))))
;; DEPRECATED: remove after 2.3 release
(defmethod process-change :set-option
[data _]
data)
;; --- Comment Threads
(defmethod process-change :set-comment-thread-position
@@ -945,12 +928,6 @@
[data {:keys [id]}]
(ctl/delete-color data id))
;; DEPRECATED: remove before 2.3
(defmethod process-change :add-recent-color
[data _]
data)
;; -- Media
(defmethod process-change :add-media
@@ -1091,21 +1068,23 @@
;; === Operations
(def ^:private decode-shape
(sm/decoder cts/schema:shape sm/json-transformer))
(def decode-shape-attrs
(sm/decoder cts/schema:shape-attrs sm/json-transformer))
(defmethod process-operation :assign
[{:keys [type] :as shape} {:keys [value] :as op}]
(let [modifications (assoc value :type type)
modifications (decode-shape modifications)]
modifications (decode-shape-attrs modifications)]
(reduce-kv (fn [shape k v]
(process-operation shape {:type :set
:attr k
:val v
:ignore-touched (:ignore-touched op)
:ignore-geometry (:ignore-geometry op)}))
(if (not= v (get shape k))
(process-operation shape {:type :set
:attr k
:val v
:ignore-touched (:ignore-touched op)
:ignore-geometry (:ignore-geometry op)})
shape))
shape
modifications)))
(dissoc modifications :type))))
(defmethod process-operation :set
[shape op]

View File

@@ -24,7 +24,7 @@
[app.common.uuid :as uuid]))
;; Auxiliary functions to help create a set of changes (undo + redo)
;; TODO: this is a duplicate schema
(def schema:changes
(sm/register!
^{::sm/type ::changes}
@@ -36,7 +36,7 @@
[:stack-undo? {:optional true} boolean?]
[:undo-group {:optional true} ::sm/any]]))
(def check-changes!
(def check-changes
(sm/check-fn schema:changes))
(defn empty-changes
@@ -168,9 +168,8 @@
(defn apply-changes-local
[changes & {:keys [apply-to-library?]}]
(assert
(check-changes! changes)
"expected valid changes")
(assert (check-changes changes)
"expected valid changes")
(if-let [file-data (::file-data (meta changes))]
(let [library-data (::library-data (meta changes))

View File

@@ -426,15 +426,15 @@
(defn components-nesting-loop?
"Check if a nesting loop would be created if the given shape is moved below the given parent"
[objects shape-id parent-id]
(let [xf-get-component-id (keep :component-id)
children (get-children-with-self objects shape-id)
child-components (into #{} xf-get-component-id children)
parents (get-parents-with-self objects parent-id)
parent-components (into #{} xf-get-component-id parents)]
(seq (set/intersection child-components parent-components))))
([objects shape-id parent-id]
(let [children (get-children-with-self objects shape-id)
parents (get-parents-with-self objects parent-id)]
(components-nesting-loop? children parents)))
([children parents]
(let [xf-get-component-id (keep :component-id)
child-components (into #{} xf-get-component-id children)
parent-components (into #{} xf-get-component-id parents)]
(seq (set/intersection child-components parent-components)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ALGORITHMS & TRANSFORMATIONS FOR SHAPES
@@ -798,6 +798,13 @@
(let [path-split (split-path path)]
(merge-path-item (first path-split) name)))
(defn inside-path? [child parent]
(let [child-path (split-path child)
parent-path (split-path parent)]
(and (<= (count parent-path) (count child-path))
(= parent-path (take (count parent-path) child-path)))))
(defn split-by-last-period
"Splits a string into two parts:

View File

@@ -31,6 +31,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.shadow :as ctss]
[app.common.types.shape.text :as ctst]
[app.common.types.text :as types.text]
[app.common.uuid :as uuid]
[clojure.set :as set]
@@ -1568,6 +1569,41 @@
(-> data
(update :pages-index d/update-vals update-page))))
(defmethod migrate-data "0011-fix-invalid-text-touched-flags"
[data _]
(letfn [(fix-shape [shape]
(let [touched-groups (ctk/normal-touched-groups shape)
content-touched? (touched-groups :content-group)
text-touched? (or (touched-groups :text-content-text)
(touched-groups :text-content-attribute)
(touched-groups :text-content-structure))]
(if (and text-touched? (not content-touched?))
(update shape :touched ctk/set-touched-group :content-group)
shape)))
(update-page [page]
(d/update-when page :objects d/update-vals fix-shape))]
(-> data
(update :pages-index d/update-vals update-page))))
(defmethod migrate-data "0012-fix-position-data"
[data _]
(let [decode-fn
(sm/decoder ctst/schema:position-data sm/json-transformer)
update-object
(fn [object]
(if (cfh/text-shape? object)
(d/update-when object :position-data decode-fn)
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))))
(def available-migrations
(into (d/ordered-set)
@@ -1635,4 +1671,6 @@
"0008-fix-library-colors-v4"
"0009-clean-library-colors"
"0009-add-partial-text-touched-flags"
"0010-fix-swap-slots-pointing-non-existent-shapes"]))
"0010-fix-swap-slots-pointing-non-existent-shapes"
"0011-fix-invalid-text-touched-flags"
"0012-fix-position-data"]))

View File

@@ -491,6 +491,19 @@
(pcb/with-library-data file-data)
(pcb/update-component (:id shape) repair-component))))
(defmethod repair-error :invalid-text-touched
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
;; Add content group
(log/debug :hint " -> add :content-group to :touched-groups")
(update shape :touched ctk/set-touched-group :content-group))]
(log/dbg :hint "repairing shape :invalid-text-touched" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/update-shapes [(:id shape)] repair-shape))))
(defmethod repair-error :misplaced-slot
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape

View File

@@ -269,6 +269,22 @@
(d/parse-double width 1)
(d/parse-double height 1)))
(defn- parse-radius-attrs
[attrs]
(if (or (contains? attrs :rx) (contains? attrs :ry))
(let [rx-val (d/parse-double (:rx attrs) 0)
ry-val (d/parse-double (:ry attrs) 0)
radius (cond
(and (contains? attrs :rx) (contains? attrs :ry))
(min rx-val ry-val)
(contains? attrs :rx)
rx-val
(contains? attrs :ry)
ry-val
:else 0)]
{:r1 radius :r2 radius :r3 radius :r4 radius})
{}))
(defn create-rect-shape [name frame-id svg-data {:keys [attrs] :as data}]
(let [transform (->> (csvg/parse-transform (:transform attrs))
(gmt/transform-in (gpt/point svg-data)))
@@ -280,7 +296,9 @@
(update :y - (:y origin)))
props (-> (dissoc attrs :x :y :width :height :rx :ry :transform)
(csvg/attrs->props))]
(csvg/attrs->props))
radius-attrs (parse-radius-attrs attrs)]
(cts/setup-shape
(-> (calculate-rect-metadata rect transform)
(assoc :type :rect)
@@ -288,13 +306,10 @@
(assoc :frame-id frame-id)
(assoc :svg-viewbox vbox)
(assoc :svg-attrs props)
;; We need to ensure fills are empty on import process
;; because setup-shape assings one by default.
;; We need to ensure fills are empty on import process
;; because setup-shape assings one by default.
(assoc :fills [])
(cond-> (contains? attrs :rx)
(assoc :rx (d/parse-double (:rx attrs) 0)))
(cond-> (contains? attrs :ry)
(assoc :ry (d/parse-double (:ry attrs) 0)))))))
(merge radius-attrs)))))
(defn- parse-circle-attrs
[attrs]
@@ -508,6 +523,7 @@
:else (dm/str tag))]
(dm/str "svg-" suffix)))
(defn parse-svg-element
[frame-id svg-data {:keys [tag attrs hidden] :as element} unames]

View File

@@ -57,6 +57,7 @@
:not-component-not-allowed
:component-nil-objects-not-allowed
:instance-head-not-frame
:invalid-text-touched
:misplaced-slot
:missing-slot
:shape-ref-cycle
@@ -328,6 +329,20 @@
"This shape has children with the same swap slot"
shape file page)))
(defn- check-valid-touched
"Validate that the text touched flags are coherent."
[shape file page]
(let [touched-groups (ctk/normal-touched-groups shape)
content-touched? (touched-groups :content-group)
text-touched? (or (touched-groups :text-content-text)
(touched-groups :text-content-attribute)
(touched-groups :text-content-structure))]
;; For now we only check this combination, that has been reported in some bugs
(when (and text-touched? (not content-touched?))
(report-error :invalid-text-touched
"This thape has text type touched but not content touched"
shape file page))))
(defn- check-shape-main-root-top
"Root shape of a top main instance:
@@ -369,6 +384,7 @@
(check-component-ref shape file page libraries)
(check-empty-swap-slot shape file page)
(check-duplicate-swap-slot shape file page)
(check-valid-touched shape file page)
(run! #(check-shape % file page libraries :context :copy-top :library-exists library-exists) (:shapes shape))))
(defn- check-shape-copy-root-nested
@@ -379,6 +395,7 @@
[shape file page libraries library-exists]
(check-component-not-main-head shape file page libraries)
(check-component-not-root shape file page)
(check-valid-touched shape file page)
;; We can have situations where the nested copy and the ancestor copy come from different libraries and some of them have been dettached
;; so we only validate the shape-ref if the ancestor is from a valid library
(when library-exists
@@ -401,6 +418,7 @@
(check-component-not-root shape file page)
(check-component-ref shape file page libraries)
(check-empty-swap-slot shape file page)
(check-valid-touched shape file page)
(run! #(check-shape % file page libraries :context :copy-any) (:shapes shape)))
(defn- check-shape-not-component

View File

@@ -119,7 +119,9 @@
;; Only for developtment.
:tiered-file-data-storage
:token-units
:token-base-font-size
:token-typography-types
:token-typography-composite
:transit-readable-response
:user-feedback
;; TODO: remove this flag.
@@ -131,7 +133,8 @@
:hide-release-modal
:subscriptions
:subscriptions-old
:frontend-binary-fills})
:frontend-binary-fills
:inspect-styles})
(def all-flags
(set/union email login varia))
@@ -153,7 +156,9 @@
:enable-dashboard-templates-section
:enable-google-fonts-provider
:enable-component-thumbnails
:enable-render-wasm-dpr])
:enable-render-wasm-dpr
:enable-token-units
:enable-token-typography-types])
(defn parse
[& flags]

View File

@@ -25,16 +25,7 @@
;; --- Matrix Impl
(defn format-precision
[mtx precision]
(when mtx
(dm/fmt "matrix(%, %, %, %, %, %)"
(mth/to-fixed (.-a mtx) precision)
(mth/to-fixed (.-b mtx) precision)
(mth/to-fixed (.-c mtx) precision)
(mth/to-fixed (.-d mtx) precision)
(mth/to-fixed (.-e mtx) precision)
(mth/to-fixed (.-f mtx) precision))))
(declare format-precision)
(cr/defrecord Matrix [^double a
^double b
@@ -46,6 +37,17 @@
(toString [this]
(format-precision this precision)))
(defn format-precision
[mtx precision]
(when mtx
(dm/fmt "matrix(%, %, %, %, %, %)"
(mth/to-fixed (.-a ^Matrix mtx) precision)
(mth/to-fixed (.-b ^Matrix mtx) precision)
(mth/to-fixed (.-c ^Matrix mtx) precision)
(mth/to-fixed (.-d ^Matrix mtx) precision)
(mth/to-fixed (.-e ^Matrix mtx) precision)
(mth/to-fixed (.-f ^Matrix mtx) precision))))
(defn matrix?
"Return true if `v` is Matrix instance."
[v]

View File

@@ -96,7 +96,6 @@
(if (and (some? layout-line) (<= from-idx max-idx))
(let [to-idx (+ from-idx (:num-children layout-line))
children (subvec children from-idx to-idx)
[_ modif-tree]
(reduce set-child-modifiers [layout-line modif-tree] children)]
(recur modif-tree (first pending) (rest pending) to-idx))

View File

@@ -255,10 +255,10 @@
(if-let [pt (first pts)]
(let [x (dm/get-prop pt :x)
y (dm/get-prop pt :y)]
(recur (mth/min minx x)
(mth/min miny y)
(mth/max maxx x)
(mth/max maxy y)
(recur (double (mth/min minx x))
(double (mth/min miny y))
(double (mth/max maxx x))
(double (mth/max maxy y))
(rest pts)))
(when (d/num? minx miny maxx maxy)
(make-rect minx miny (- maxx minx) (- maxy miny)))))))

View File

@@ -393,7 +393,7 @@
min-fr
(let [{:keys [size type value]} (first tracks)
min-fr (if (= type :flex) (max min-fr (/ size value)) min-fr)]
(recur (rest tracks) min-fr)))))
(recur (rest tracks) (double min-fr))))))
(defn calc-layout-data
([parent transformed-parent-bounds children bounds objects]

View File

@@ -2020,7 +2020,6 @@
skip-operations? (or skip-operations?
(= attr-val (get current-shape attr)))
;; On a text-change, we want to force a position-data reset
;; so it's calculated again
[roperations uoperations]
@@ -2028,6 +2027,16 @@
(add-update-attr-operations :position-data current-shape roperations uoperations nil)
[roperations uoperations])
;; On a rotation operation we need to keep also the transformation matrixes
[roperations uoperations]
(if (and (not skip-operations?) (= attr :rotation))
(let [[roperations uoperations]
(add-update-attr-operations
:transform current-shape roperations uoperations (:transform previous-shape))]
(add-update-attr-operations
:transform-inverse current-shape roperations uoperations (:transform-inverse previous-shape)))
[roperations uoperations])
[roperations' uoperations']
(if skip-operations?
[roperations uoperations]

View File

@@ -17,12 +17,13 @@
[app.common.types.pages-list :as ctpl]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.types.shape.token :as ctst]
[app.common.types.text :as ctt]
[app.common.types.token :as cto]
[app.common.uuid :as uuid]
[clojure.set :as set]))
(def text-typography-attrs (set ctt/text-typography-attrs))
(def text-typography-style-attrs (set ctt/text-typography-attrs))
(defn- generate-unapply-tokens
"When updating attributes that have a token applied, we must unapply it, because the value
@@ -38,10 +39,14 @@
(let [new-shape (get new-objects (:id shape))
attrs (ctt/get-diff-attrs (:content shape) (:content new-shape))
;; Unapply token when applying typography asset style
attrs (if (seq (set/intersection text-typography-attrs attrs))
(into attrs cto/typography-keys)
attrs)]
attrs (cond-> attrs
;; Unapply token when applying typography asset style
(seq (set/intersection text-typography-style-attrs attrs))
(into cto/typography-keys)
;; Unapply font-weight when changing the font-family attribute
(and (:font-id attrs) (ctst/font-weight-applied? shape))
(conj :font-weight))]
(apply set/union (map cto/shape-attr->token-attrs attrs))))
check-attr
@@ -400,9 +405,10 @@
(remove #(= % parent-id) all-parents))]
(-> changes
;; Remove layout-item properties when moving a shape outside a layout
;; Remove layout-item properties and tokens when moving a shape outside a layout
(cond-> (not (ctl/any-layout? parent))
(pcb/update-shapes ids ctl/remove-layout-item-data))
(-> (pcb/update-shapes ids ctl/remove-layout-item-data)
(pcb/update-shapes ids cto/unapply-layout-item-tokens)))
;; Remove the hide in viewer flag
(cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent))

View File

@@ -172,6 +172,10 @@
objects (pcb/get-objects changes)
variant-id (:id variant-container)
num-shapes (->> variant-container
:shapes
count)
;; If we are cut-pasting a variant-container, this will be null
;; because it hasn't any shapes yet
first-comp-id (->> variant-container
@@ -198,7 +202,7 @@
0
shapes)
num-new-props (if (or (zero? num-base-props)
num-new-props (if (or (zero? num-shapes)
(< total-props num-base-props))
0
(- total-props num-base-props))
@@ -213,7 +217,7 @@
(reduce
(fn [changes shape]
(let [component (ctcl/get-component data (:component-id shape) true)]
(if (or (zero? num-base-props) ;; do nothing if there are no base props
(if (or (zero? num-shapes) ;; do nothing if there are no shapes
(and (= variant-id (:variant-id shape)) ;; or we are only moving the shape inside its parent (it is
(not (:deleted component)))) ;; the same parent and the component isn't deleted)
changes

View File

@@ -31,9 +31,11 @@
component-id
new-component-id
{:new-shape-id new-shape-id :apply-changes-local-library? true}))]
(-> changes
(clvp/generate-update-property-value new-component-id prop-num value)
(pcb/change-parent (:parent-id shape) [new-shape] 0))))
(cond-> changes
(>= prop-num 0)
(clvp/generate-update-property-value new-component-id prop-num value)
:always
(pcb/change-parent (:parent-id shape) [new-shape] 0))))
(defn- generate-path
[path objects base-id shape]

View File

@@ -131,34 +131,10 @@
(->> (entries schema)
(into #{} xf:map-key)))
;; (defn key-transformer
;; [& {:as opts}]
;; (mt/key-transformer opts))
;; (defn- transform-map-keys
;; [f o]
;; (cond
;; (record? o)
;; (reduce-kv (fn [res k v]
;; (let [k' (f k)]
;; (if (= k k')
;; res
;; (-> res
;; (assoc k' v)
;; (dissoc k)))))
;; o
;; o)
;; (map? o)
;; (persistent!
;; (reduce-kv (fn [res k v]
;; (assoc! res (f k) v))
;; (transient {})
;; o))
;; :else
;; o))
(defn update-properties
[s f & args]
(let [s (schema s)]
(apply m/-update-properties s f args)))
(defn -transform-map-keys
([f]
@@ -679,8 +655,7 @@
identity)]
{:pred #(contains? options %)
:type-properties
{:title "one-of"
:description "One of the Set"
{:title "enum"
:gen/gen (sg/elements options)
:decode/string decode
:decode/json decode
@@ -723,15 +698,14 @@
{:pred pred
:type-properties
{:title "int"
:description "int"
{:title "integer"
:description "integer"
:error/message "expected to be int/long"
:error/code "errors.invalid-integer"
:gen/gen gen
:decode/string parse-long
:decode/json parse-long
::oapi/type "integer"
::oapi/format "int64"}}))})
::oapi/type "integer"}}))})
(defn parse-double
[v]
@@ -793,8 +767,8 @@
{:pred pred
:type-properties
{:title "int"
:description "int"
{:title "number"
:description "number"
:error/message "expected to be number"
:error/code "errors.invalid-number"
:gen/gen gen
@@ -844,10 +818,7 @@
#(some (fn [prop]
(contains? % prop))
choices))]
{:pred pred
:type-properties
{:title "contains any"
:description "contains predicate"}}))})
{:pred pred}))})
;; (register!
;; {:type ::inst
@@ -968,6 +939,7 @@
:type-properties
{:title "string"
:description "not whitespace string"
::oapi/type "string"
:gen/gen (sg/word-string)
:error/fn
(fn [{:keys [value schema]}]

View File

@@ -91,11 +91,15 @@
(defmethod visit :int [_ schema _ _] (str "integer" (-titled schema) (-min-max-suffix-number schema)))
(defmethod visit :double [_ schema _ _] (str "double" (-titled schema) (-min-max-suffix-number schema)))
(defmethod visit :select-keys [_ schema _ options] (describe* (m/deref schema) options))
(defmethod visit :and [_ s children _] (str (str/join " && " children) (-titled s)))
(defmethod visit :and [_ s children _]
(str (str/join " && " (filter some? children)) (-titled s)))
(defmethod visit :enum [_ s children _options] (str "enum" (-titled s) " of " (str/join ", " children)))
(defmethod visit :maybe [_ _ children _] (str (first children) " nullable"))
(defmethod visit :tuple [_ _ children _] (str "(" (str/join ", " children) ")"))
(defmethod visit :re [_ s _ options] (str "regex pattern " (-titled s) "matching " (pr-str (first (m/children s options)))))
(defmethod visit :re [_ _ children _]
(let [pattern (first children)]
(str "string & regex pattern /" (str pattern) "/")))
(defmethod visit :any [_ s _ _] (str "anything" (-titled s)))
(defmethod visit :some [_ _ _ _] "anything but null")
(defmethod visit :nil [_ _ _ _] "null")
@@ -108,10 +112,11 @@
(defmethod visit :uuid [_ _ _ _] "uuid")
(defmethod visit :boolean [_ _ _ _] "boolean")
(defmethod visit :keyword [_ _ _ _] "string")
(defmethod visit :fn [_ _ _ _] "FN")
(defmethod visit :fn [_ _ _ _]
nil)
(defmethod visit :vector [_ _ children _]
(str "[" (last children) "]"))
(str "[" (str/trim (last children)) "]"))
(defn -tagged [children] (map (fn [[tag _ c]] (str c " (tag: " tag ")")) children))
@@ -137,8 +142,15 @@
(some? suffix)
(str suffix))))
(defmethod visit :map-of [_ _ children _]
(str "map[" (first children) "," (second children) "]"))
(defmethod visit :map-of
[_ schema children _]
(let [props (m/properties schema)
title (some->> (:title props) str/camel str/capital)]
(str (if title
(str "type " title ": ")
"")
"map[" (first children) "," (second children) "]")))
(defmethod visit :union [_ _ children _]
(str/join " | " children))
@@ -156,61 +168,104 @@
(or (:title props)
"*")))
(defn- format-map
[schema children]
(let [props (m/properties schema)
closed? (get props :closed)
title (some->> (:title props) str/camel str/capital)
optional (into #{} (comp (filter (m/-comp :optional second))
(map first))
children)
entries (->> children
(map (fn [[k _ s]]
;; NOTE: maybe we can detect multiple lines
;; and then just insert a break line
(str " " (str/camel k)
(when (contains? optional k) "?")
": " (str/trim s))))
(str/join ",\n"))
header (cond-> (str "type " title)
closed? (str "!")
(some? title) (str " "))]
(str header "{\n" entries "\n}")))
(defmethod visit :map
[_ schema children {:keys [::level ::max-level] :as options}]
(let [props (m/properties schema)
closed? (:closed props)
title (some->> (:title props) str/camel str/capital)]
[_ schema children {:keys [::level] :as options}]
(let [props (m/properties schema)
extracted? (get props ::extracted false)]
(if (>= level max-level)
(or (some-> title str)
"<untitled>")
(let [optional (into #{} (comp (filter (m/-comp :optional second))
(map first))
children)
entries (->> children
(map (fn [[k _ s]]
(str (pad " " level) (str/camel k)
(when (contains? optional k) "?")
": " s)))
(str/join ",\n"))
(cond
(or (= level 0) extracted?)
(format-map schema children)
header (cond-> (str "type " title)
closed? (str "!")
(some? title) (str " "))]
:else
(let [schema (mu/update-properties schema assoc ::extracted true)
title (or (some->> (:title props) str/camel str/capital) "<untitled>")]
(swap! *definitions* conj (format-map schema children))
title))))
(str (pad header level) "{\n" entries "\n" (pad "}\n" level))))))
(defn format-multi
[s children]
(let [props (m/properties s)
title (or (some-> (:title props) str/camel str/capital) "<untitled>")
dispatcher (or (-> s m/properties :dispatch-description)
(-> s m/properties :dispatch))
entries (->> children
(map (fn [[_ _ entry]]
(pad entry 1)))
(str/join ",\n"))
header (str "type " title " [dispatch=" (d/name dispatcher) "]")]
(str header " {\n" entries "\n}")))
(defmethod visit :multi
[_ s children {:keys [::level ::max-level] :as options}]
(let [props (m/properties s)
title (some-> (:title props) str/camel str/capital)]
(if (>= level max-level)
title
(let [dispatcher (or (-> s m/properties :dispatch-description)
(-> s m/properties :dispatch))
[_ schema children {:keys [::level] :as options}]
(let [props (m/properties schema)
title (or (some-> (:title props) str/camel str/capital) "<untitled>")
extracted? (get props ::extracted false)]
prefix (apply str (take (inc level) (repeat " ")))
(cond
(or (zero? level) extracted?)
(format-multi schema children)
entries (->> children
(map (fn [[_ _ shape]]
(str prefix shape)))
(str/join ",\n"))
:else
(let [schema (mu/update-properties schema assoc ::extracted true)]
(swap! *definitions* conj (format-multi schema children))
title))))
header (cond-> "multi"
(some? title) (str " " title)
:always (str " [dispatch=" (d/name dispatcher) "]"))]
(defn- format-merge
[schema children]
(str header " {\n" entries "\n" (pad "}" level))))))
(let [props (m/properties schema)
entries (->> children
(map (fn [shape]
(pad shape 1)))
(str/join ",\n"))
title (some-> (:title props) str/camel str/capital)
header (str "merge type " title)]
(str header " {\n" entries "\n}")))
(defmethod visit :merge
[_ schema children _]
(let [entries (str/join ",\n" children)
props (m/properties schema)
title (or (some-> (:title props) str/camel str/capital)
"<untitled>")]
(str "merge type " title " { \n" entries "\n}\n")))
[_ schema children {:keys [::level] :as options}]
(let [props (m/properties schema)
title (some-> (:title props) str/camel str/capital)
extracted? (get props ::extracted false)]
(cond
(or (zero? level) extracted?)
(format-merge schema children)
:else
(let [schema (mu/update-properties schema assoc ::extracted true)]
(swap! *definitions* conj
(format-merge schema children))
title))))
(defmethod visit ::sm/one-of
[_ _ children _]
@@ -219,45 +274,37 @@
(map d/name)
(str/join "|")) ")")))
(defmethod visit :schema [_ schema children options]
(visit ::m/schema schema children options))
(defmethod visit ::m/schema
[_ schema _ {:keys [::level ::limit ::max-level] :as options}]
(let [schema' (m/deref schema)
props (merge
(m/properties schema)
(m/properties schema'))
ref (m/-ref schema)
title (:title props)]
(defmethod visit :schema
[_ schema children options]
(let [props (m/properties schema)
title (some-> (:title props) str/camel str/capital)
extracted? (get props ::extracted false)]
(cond
(::inline props)
(do
(if (>= limit max-level)
title
(describe* schema' options)))
(not title)
(visit ::m/schema schema children options)
(and ref title)
(do
(when (<= limit max-level)
(swap! *definitions* conj (describe* schema' (assoc options ::base-limit limit))))
title)
(>= limit max-level)
(or title
(some-> ref d/name str/camel str/capital)
"<untitled>")
extracted?
(let [title (or title "<untitled>")]
(str "type " title ": "
(visit ::m/schema schema children options)))
:else
(describe* schema' (assoc options ::base-level level ::base-limit limit)))))
(let [schema (mu/update-properties schema assoc ::extracted true)
title (or title "<untitled>")]
(swap! *definitions* conj
(str "type " title ": "
(visit ::m/schema schema children (update options ::level inc))))
title))))
(defmethod visit ::m/schema
[_ schema _ {:keys [::level] :as options}]
(let [schema' (m/deref schema)]
(describe* schema' (assoc options ::base-level level))))
(defn describe* [s options]
(letfn [(walk-fn [schema path children {:keys [::base-level ::base-limit] :or {base-level 0 base-limit 0} :as options}]
(let [options (assoc options
::limit (+ base-limit (count path))
::level (+ base-level (count path)))]
(letfn [(walk-fn [schema path children {:keys [::base-level] :or {base-level 0} :as options}]
(let [options (assoc options ::level (+ base-level (count path)))]
(visit (m/type schema) schema children options)))]
(m/walk s walk-fn options)))
@@ -275,8 +322,7 @@
(mu/update-properties assoc ::root true))
options (into {::m/walk-entry-vals true
::level 0
::max-level 300}
::level 0}
options)]
(binding [*definitions* defs]

View File

@@ -6,6 +6,8 @@
(ns app.common.schema.openapi
(:require
[app.common.data :as d]
[app.common.schema :as-alias sm]
[clojure.set :as set]
[cuerdas.core :as str]
[malli.core :as m]))
@@ -15,16 +17,44 @@
(declare transform*)
(defmulti visit (fn [name _schema _children _options] name) :default ::default)
(defmethod visit ::default [_ _ _ _] {})
(defmethod visit ::default [_ schema _ _]
(let [props (m/type-properties schema)]
(d/without-nils
{:type (get props ::type)
:format (get props ::format)
:title (get props :title)
:description (get props :description)})))
(defmethod visit :> [_ _ [value] _] {:type "number" :exclusiveMinimum value})
(defmethod visit :>= [_ _ [value] _] {:type "number" :minimum value})
(defmethod visit :< [_ _ [value] _] {:type "number" :exclusiveMaximum value})
(defmethod visit :<= [_ _ [value] _] {:type "number" :maximum value})
(defmethod visit := [_ _ [value] _] {:const value})
(defmethod visit := [_ schema children _]
(let [props (m/properties schema)
type (get props :type :string)]
(d/without-nils
{:type (or (get props ::type)
(d/name type))
:enum (if (= :string type)
(mapv d/name children)
(vec children))})))
(defmethod visit :not= [_ _ _ _] {})
(defmethod visit :fn [_ _ _ _]
nil)
(defmethod visit ::sm/contains-any [_ _ _ _]
nil)
(defmethod visit :not [_ _ children _] {:not (last children)})
(defmethod visit :and [_ _ children _] {:allOf children})
(defmethod visit :and [_ _ children _]
{:allOf (keep not-empty children)})
(defmethod visit :or [_ _ children _] {:anyOf children})
(defmethod visit :orn [_ _ children _] {:anyOf (map last children)})
@@ -71,14 +101,28 @@
:minProperties
:maxProperties))
(defmethod visit :vector [_ schema children _]
(let [child (-> schema m/children first)
props (m/properties (m/deref child))]
(minmax-properties
{:type "array", :items (first children) :title (:title props)}
schema
:minItems
:maxItems)))
(defmethod visit :any [_ _ _ _]
{:description "Any Value"})
(defmethod visit ::sm/set [_ schema children _]
(minmax-properties
{:type "array", :items (first children), :uniqueItems true}
schema
:minItems
:maxItems))
(defmethod visit ::sm/vec [_ schema children _]
(minmax-properties
{:type "array", :items (first children)}
schema
:minItems
:maxItems))
(defmethod visit :vector [_ schema children options]
(visit ::sm/vec schema children options))
(defmethod visit :set [_ schema children options]
(visit ::sm/set schema children options))
(defmethod visit :sequential [_ schema children _]
(minmax-properties
@@ -87,36 +131,64 @@
:minItems
:maxItems))
(defmethod visit :set [_ schema children _]
(minmax-properties
{:type "array", :items (first children), :uniqueItems true}
schema
:minItems
:maxItems))
(defmethod visit :enum [_ _ children options]
(merge (some-> (m/-infer children) (transform* options)) {:enum children}))
(defmethod visit :maybe [_ _ children _]
(let [children (first children)]
(assoc children :nullable true)))
(defmethod visit :tuple [_ _ children _]
{:type "array", :items children, :additionalItems false})
(defmethod visit :enum [_ _ children options] (merge (some-> (m/-infer children) (transform* options)) {:enum children}))
(defmethod visit :maybe [_ _ children _] {:oneOf (conj children {:type "null"})})
(defmethod visit :tuple [_ _ children _] {:type "array", :items children, :additionalItems false})
(defmethod visit :re [_ schema _ options]
{:type "string", :pattern (str (first (m/children schema options)))})
(defmethod visit :nil [_ _ _ _] {:type "null"})
(defmethod visit :string [_ schema _ _]
(merge {:type "string"} (-> schema m/properties (select-keys [:min :max]) (set/rename-keys {:min :minLength, :max :maxLength}))))
(defmethod visit ::sm/one-of [_ _ children _]
(let [options (->> (first children)
(mapv d/name))]
{:type "string"
:enum options}))
(defmethod visit :int [_ schema _ _]
(merge {:type "integer"} (-> schema m/properties (select-keys [:min :max]) (set/rename-keys {:min :minimum, :max :maximum}))))
(minmax-properties
{:type "integer"}
schema
:minimum
:maximum))
(defmethod visit :double [_ schema _ _]
(merge {:type "number"}
(-> schema m/properties (select-keys [:min :max]) (set/rename-keys {:min :minimum, :max :maximum}))))
(minmax-properties
{:type "number"
:format "double"}
schema
:minimum
:maximum))
(defmethod visit ::sm/int
[_ schema children options]
(visit :int schema children options))
(defmethod visit ::sm/double
[_ schema children options]
(visit :double schema children options))
(defmethod visit :boolean [_ _ _ _] {:type "boolean"})
(defmethod visit ::sm/boolean [_ _ _ _] {:type "boolean"})
(defmethod visit :keyword [_ _ _ _] {:type "string"})
(defmethod visit :qualified-keyword [_ _ _ _] {:type "string"})
(defmethod visit :symbol [_ _ _ _] {:type "string"})
(defmethod visit :qualified-symbol [_ _ _ _] {:type "string"})
(defmethod visit :uuid [_ _ _ _] {:type "string" :format "uuid"})
(defmethod visit ::sm/uuid [_ _ _ _] {:type "string" :format "uuid"})
(defmethod visit :schema [_ schema children options]
(visit ::m/schema schema children options))
@@ -124,11 +196,41 @@
(defmethod visit ::m/schema [_ schema _ options]
(let [result (transform* (m/deref schema) options)
defpath (::definitions-path options "#/definitions/")]
(if-let [ref (m/-ref schema)]
(let [rkey (str/concat (str/camel (namespace ref)) "$" (name ref))]
(some-> *definitions* (swap! assoc rkey result))
{"$ref" (str/concat defpath rkey)})
result)))
(if (::embed options)
result
(if-let [ref (m/-ref schema)]
(let [nname (namespace ref)
tname (name ref)
tname (str/capital (str/camel tname))
nname (cond
(or (= nname "app.common.schema")
(= nname "app.common.time")
(= nname "app.common.features"))
""
(= nname "datoteka.fs")
"Filesystem"
(str/starts-with? nname "app.common.geom")
(-> (str/replace nname #"app\.common\.geom\.\w+" "geom")
(str/camel)
(str/capital))
(str/starts-with? nname "app.")
(-> (subs nname 4)
(str/camel)
(str/capital))
:else
(str/capital (str/camel nname)))
rkey (str nname tname)]
(some-> *definitions* (swap! assoc rkey result))
{"$ref" (str/concat defpath rkey)})
result))))
(defmethod visit :merge [_ schema _ options] (transform* (m/deref schema) options))
(defmethod visit :union [_ schema _ options] (transform* (m/deref schema) options))

View File

@@ -307,6 +307,7 @@
file' (thf/apply-changes file changes)]
(when new-shape-label
(thi/rm-id! (:id new-shape))
(thi/set-id! new-shape-label (:id new-shape)))
(if propagate-fn
(propagate-fn file')

View File

@@ -21,6 +21,10 @@
[label id]
(swap! idmap assoc label id))
(defn rm-id!
[id]
(swap! idmap #(into {} (remove (comp #{id} val) %))))
(defn new-id!
[label]
(let [id (uuid/next)]

View File

@@ -77,22 +77,25 @@
[file shape-label token-name token-attrs shape-attrs resolved-value]
(let [page (thf/current-page file)
shape (ths/get-shape file shape-label)
shape' (as-> shape $
(cto/apply-token-to-shape {:shape $
:token {:name token-name}
:attributes token-attrs})
(reduce (fn [shape attr]
(case attr
:stroke-width (set-stroke-width shape resolved-value)
:stroke-color (set-stroke-color shape resolved-value)
:fill (set-fill-color shape resolved-value)
(ctn/set-shape-attr shape attr resolved-value {:ignore-touched true})))
$
shape-attrs))]
shape' (when shape
(as-> shape $
(cto/apply-token-to-shape {:shape $
:token {:name token-name}
:attributes token-attrs})
(reduce (fn [shape attr]
(case attr
:stroke-width (set-stroke-width shape resolved-value)
:stroke-color (set-stroke-color shape resolved-value)
:fill (set-fill-color shape resolved-value)
(ctn/set-shape-attr shape attr resolved-value {:ignore-touched true})))
$
shape-attrs)))]
(ctf/update-file-data
file
(fn [file-data]
(ctpl/update-page file-data
(:id page)
#(ctst/set-shape % shape'))))))
(if shape'
(ctf/update-file-data
file
(fn [file-data]
(ctpl/update-page file-data
(:id page)
#(ctst/set-shape % shape'))))
file)))

View File

@@ -56,6 +56,22 @@
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property 1" :value "Value2"}]}))))
(defn add-variant-with-copy
[file variant-label component1-label root1-label component2-label root2-label child1-label child2-label component-copy-label]
(let [file (ths/add-sample-shape file variant-label :type :frame :is-variant-container true)
variant-id (thi/id variant-label)]
(-> file
(ths/add-sample-shape root2-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value2")
(ths/add-sample-shape root1-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value1")
(thc/instantiate-component component-copy-label child1-label :parent-label root1-label)
(thc/instantiate-component component-copy-label child2-label :parent-label root2-label)
(thc/make-component component1-label root1-label)
(thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property 1" :value "Value1"}]})
(thc/make-component component2-label root2-label)
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property 1" :value "Value2"}]}))))
(defn add-variant-with-text
[file variant-label component1-label root1-label component2-label root2-label child1-label child2-label text1 text2
& {:keys [text1-params text2-params]}]

View File

@@ -119,9 +119,9 @@
[o]
(instance? Duration o)))
(defn duration
[ms-or-obj]
#?(:clj
#?(:clj
(defn duration
[ms-or-obj]
(cond
(string? ms-or-obj)
(Duration/parse (str "PT" ms-or-obj))
@@ -134,10 +134,7 @@
(Duration/ofMillis ms-or-obj)
:else
(obj->duration ms-or-obj))
:cljs
(clj->js ms-or-obj)))
(obj->duration ms-or-obj))))
#?(:clj
(defn parse-duration
@@ -262,6 +259,9 @@
(defn inst
[s]
(cond
(nil? s)
s
(inst? s)
s
@@ -292,7 +292,7 @@
(defn plus
[d ta]
(let [ta (duration ta)]
(let [ta #?(:clj (duration ta) :cljs ta)]
(cond
#?@(:clj [(duration? d) (.plus ^Duration d ^TemporalAmount ta)])
@@ -307,7 +307,7 @@
(defn minus
[d ta]
(let [^TemporalAmount ta (duration ta)]
(let [ta #?(:clj (duration ta) :cljs ta)]
(cond
#?@(:clj [(duration? d) (.minus ^Duration d ^TemporalAmount ta)])
@@ -429,3 +429,8 @@
:encode/json format-duration
::oapi/type "string"
::oapi/format "duration"}})))
#?(:cljs
(extend-protocol cljs.core/IEncodeJS
js/Date
(-clj->js [x] x)))

View File

@@ -60,16 +60,17 @@
{:type ::hex-color
:pred hex-color-string?
:type-properties
{:title "hex-color"
{:title "HexColor"
:description "HEX Color String"
:error/message "expected a valid HEX color"
:error/code "errors.invalid-hex-color"
:gen/gen hex-color-generator
::oapi/type "integer"
::oapi/format "int64"}}))
::oapi/type "string"
::oapi/format "rgb"}}))
(def schema:plain-color
[:map [:color schema:hex-color]])
[:map {:title "PlainColorAttrs"}
[:color schema:hex-color]])
(def schema:image
[:map {:title "ImageColor" :closed true}
@@ -85,7 +86,8 @@
(sm/keys schema:image))
(def schema:image-color
[:map [:image schema:image]])
[:map {:title "ImageColorAttrs"}
[:image schema:image]])
(def gradient-types
#{:linear :radial})
@@ -110,10 +112,11 @@
(sm/keys schema:gradient))
(def schema:gradient-color
[:map [:gradient schema:gradient]])
[:map {:title "GradientColorAttrs"}
[:gradient schema:gradient]])
(def schema:color-attrs
[:map {:title "ColorAttrs" :closed true}
[:map {:title "GenericColorAttrs" :closed true}
[:opacity {:optional true} [::sm/number {:min 0 :max 1}]]
[:ref-id {:optional true} ::sm/uuid]
[:ref-file {:optional true} ::sm/uuid]])
@@ -132,13 +135,13 @@
(into required-color-attrs (sm/keys schema:color-attrs)))
(def schema:library-color-attrs
[:map {:title "ColorAttrs" :closed true}
[:map {:title "LibraryColorAttrs" :closed true}
[:id ::sm/uuid]
[:name ::sm/text]
[:path {:optional true} :string]
[:opacity {:optional true} [::sm/number {:min 0 :max 1}]]
[:modified-at {:optional true} ::ct/inst]
[:plugin-data {:optional true} ::ctpg/plugin-data]])
[:plugin-data {:optional true} ctpg/schema:plugin-data]])
(def schema:library-color
"Used for in-transit representation of a color (per example when user

View File

@@ -19,19 +19,17 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def schema:component
(sm/register!
^{::sm/type ::component}
[:merge
[:map
[:id ::sm/uuid]
[:name :string]
[:path {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::ct/inst]
[:objects {:gen/max 10 :optional true} ctp/schema:objects]
[:main-instance-id ::sm/uuid]
[:main-instance-page ::sm/uuid]
[:plugin-data {:optional true} ctpg/schema:plugin-data]]
ctv/schema:variant-component]))
[:merge
[:map
[:id ::sm/uuid]
[:name :string]
[:path {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::ct/inst]
[:objects {:gen/max 10 :optional true} ctp/schema:objects]
[:main-instance-id ::sm/uuid]
[:main-instance-page ::sm/uuid]
[:plugin-data {:optional true} ctpg/schema:plugin-data]]
ctv/schema:variant-component])
(def check-component
(sm/check-fn schema:component))
@@ -99,6 +97,8 @@
:exports :exports-group
:grids :grids-group
:show-content :show-content
:layout :layout-container
:layout-align-content :layout-align-content

View File

@@ -389,12 +389,13 @@
[(remap-ids new-shape)
(map remap-ids new-shapes)])))
(defn get-first-not-copy-parent
"Go trough the parents until we find a shape that is not a copy of a component."
(defn get-first-valid-parent
"Go trough the parents until we find a shape that is not a copy of a component nor
a variant container."
[objects id]
(let [shape (get objects id)]
(if (ctk/in-component-copy? shape)
(get-first-not-copy-parent objects (:parent-id shape))
(if (or (ctk/in-component-copy? shape) (ctk/is-variant-container? shape))
(get-first-valid-parent objects (:parent-id shape))
shape)))
(defn has-any-copy-parent?
@@ -425,7 +426,6 @@
(not (has-any-main? objects shape))
(not (has-any-copy-parent? objects shape))))
(defn collect-main-shapes [shape objects]
(if (ctk/main-instance? shape)
[shape]
@@ -433,7 +433,11 @@
(mapcat collect-main-shapes children objects)
[])))
(defn- invalid-structure-for-component?
(defn get-component-from-shape
[shape libraries]
(get-in libraries [(:component-file shape) :data :components (:component-id shape)]))
(defn invalid-structure-for-component?
"Check if the structure generated nesting children in parent is invalid in terms of nested components"
[objects parent children pasting? libraries]
(let [; If the original shapes had been cutted, and we are pasting them now, they aren't
@@ -445,7 +449,7 @@
; original component doesn't exist or is deleted. So for this function purposes, they
; are removed from the list
remove? (fn [shape]
(let [component (get-in libraries [(:component-file shape) :data :components (:component-id shape)])]
(let [component (get-component-from-shape shape libraries)]
(and component (not (:deleted component)))))
selected-components (cond->> (mapcat collect-main-shapes children objects)
@@ -475,17 +479,17 @@
(letfn [(get-frame [parent-id]
(if (cfh/frame-shape? objects parent-id) parent-id (get-in objects [parent-id :frame-id])))]
(let [parent (get objects parent-id)
;; We can always move the children to the parent they already have.
;; But if we are pasting, those are new items, so it is considered a change
no-changes?
(and (every? #(= parent-id (:parent-id %)) children)
(not pasting?))
;; When pasting frames, children have the frames and their children
;; We need to check only the top shapes
children-ids (set (map :id children))
top-children (remove #(contains? children-ids (:parent-id %)) children)
;; We can always move the children to the parent they already have.
;; But if we are pasting, those are new items, so it is considered a change
no-changes?
(and (every? #(= parent-id (:parent-id %)) top-children)
(not pasting?))
;; Are all the top-children a main-instance of a component?
all-main?
(every? ctk/main-instance? top-children)

View File

@@ -110,7 +110,6 @@
(sm/register! ::data schema:data)
(sm/register! ::file schema:file)
(sm/register! ::media schema:media)
(sm/register! ::colors schema:colors)
(sm/register! ::typographies schema:typographies)

View File

@@ -119,7 +119,7 @@
(c/assoc position fill)))
(if (nil? fills)
[fill]
(-> (coerce fills)
(-> fills
(c/assoc position fill)))))
(defn update

View File

@@ -7,7 +7,7 @@
(ns app.common.types.fills.impl
(:require
#?(:clj [clojure.data.json :as json])
#?(:cljs [app.common.weak-map :as weak-map])
#?(:cljs [app.common.weak :as weak])
[app.common.buffer :as buf]
[app.common.data :as d]
[app.common.data.macros :as dm]
@@ -443,7 +443,7 @@
:code :invalid-fill
:hint "found invalid fill on encoding fills to binary format")))))
#?(:cljs (Fills. total dbuffer mbuffer image-ids (weak-map/create) nil)
#?(:cljs (Fills. total dbuffer mbuffer image-ids (weak/weak-value-map) nil)
:clj (Fills. total dbuffer mbuffer nil))))))
(defn fills?

View File

@@ -14,12 +14,12 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def schema:grid-color
[:map {:title "PageGridColor"}
[:map {:title "GridColor"}
[:color clr/schema:hex-color]
[:opacity ::sm/safe-number]])
(def schema:column-params
[:map
[:map {:title "ColumnGridParams"}
[:color schema:grid-color]
[:type {:optional true} [::sm/one-of #{:stretch :left :center :right}]]
[:size {:optional true} [:maybe ::sm/safe-number]]
@@ -28,7 +28,7 @@
[:gutter {:optional true} [:maybe ::sm/safe-number]]])
(def schema:square-params
[:map
[:map {:title "SquareGridParams"}
[:size {:optional true} [:maybe ::sm/safe-number]]
[:color schema:grid-color]])
@@ -37,33 +37,28 @@
:dispatch :type
:decode/json #(update % :type keyword)}
[:column
[:map
[:map {:title "ColumnGridAttrs"}
[:type [:= :column]]
[:display :boolean]
[:params schema:column-params]]]
[:row
[:map
[:map {:title "RowGridAttrs"}
[:type [:= :row]]
[:display :boolean]
[:params schema:column-params]]]
[:square
[:map
[:map {:title "SquareGridAttrs"}
[:type [:= :square]]
[:display :boolean]
[:params schema:square-params]]]])
(def schema:default-grids
[:map {:title "PageGrid"}
[:square {:optional true} ::square-params]
[:row {:optional true} ::column-params]
[:column {:optional true} ::column-params]])
(sm/register! ::square-params schema:square-params)
(sm/register! ::column-params schema:column-params)
(sm/register! ::grid schema:grid)
(sm/register! ::default-grids schema:default-grids)
[:square {:optional true} schema:square-params]
[:row {:optional true} schema:column-params]
[:column {:optional true} schema:column-params]])
(def ^:private default-square-params
{:size 16

View File

@@ -466,7 +466,12 @@
(dm/assert! (#{:width :height} attr))
(dm/assert! (number? value))
(let [{:keys [proportion proportion-lock]} shape
(let [;; Avoid havig shapes with zero size
value (if (< (mth/abs value) 0.01)
0.01
value)
{:keys [proportion proportion-lock]} shape
size (select-keys (:selrect shape) [:width :height])
new-size (if-not (and (not ignore-lock?) proportion-lock)
(assoc size attr value)

View File

@@ -40,7 +40,7 @@
[:map-of {:gen/max 2} ::sm/uuid schema:guide])
(def schema:objects
[:map-of {:gen/max 5} ::sm/uuid ::cts/shape])
[:map-of {:gen/max 5} ::sm/uuid cts/schema:shape])
(def schema:comment-thread-position
[:map {:title "CommentThreadPosition"}
@@ -62,11 +62,6 @@
[:comment-thread-positions {:optional true}
[:map-of ::sm/uuid schema:comment-thread-position]]])
(sm/register! ::objects schema:objects)
(sm/register! ::page schema:page)
(sm/register! ::guide schema:guide)
(sm/register! ::flow schema:flow)
(def valid-guide?
(sm/lazy-validator schema:guide))

View File

@@ -34,7 +34,7 @@
(def schema:segments impl/schema:segments)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TRANSFORMATIONS
;; CONSTRUCTORS & TYPE METHODS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn content?
@@ -55,6 +55,10 @@
[data]
(impl/from-string data))
(defn from-plain
[data]
(impl/from-plain data))
(defn check-content
[content]
(impl/check-content content))
@@ -189,6 +193,12 @@
[content]
(some-> content segment/get-points))
(defn calc-selrect
"Calculate selrect from a content. The content can be in a PathData
instance or plain vector of segments."
[content]
(segment/content->selrect content))
(defn- calc-bool-content*
"Calculate the boolean content from shape and objects. Returns plain
vector of segments"

View File

@@ -393,17 +393,15 @@
defined by the constant num-segments"
[start end h1 h2]
(let [offset (/ 1 num-segments)
tp (fn [t] (curve-values start end h1 h2 t))]
(loop [from 0
tp (fn [t] (curve-values start end h1 h2 t))]
(loop [from 0.0
result []]
(let [to (min 1 (+ from offset))
line [(tp from) (tp to)]
(let [to (mth/min 1.0 (+ from offset))
line [(tp from) (tp to)]
result (conj result line)]
(if (>= to 1)
(if (>= to 1.0)
result
(recur to result))))))
(recur (double to) result))))))
(defn curve-split
"Splits a curve into two at the given parametric value `t`.

View File

@@ -12,12 +12,13 @@
(:require
#?(:clj [app.common.fressian :as fres])
#?(:clj [clojure.data.json :as json])
#?(:cljs [app.common.weak-map :as weak-map])
#?(:cljs [app.common.weak :as weak])
[app.common.buffer :as buf]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.schema.openapi :as oapi]
[app.common.svg.path :as svg.path]
[app.common.transit :as t]
[app.common.types.path :as-alias path]
@@ -378,7 +379,7 @@
(-transform [this m]
(let [buffer (buf/clone buffer)]
(impl-transform buffer m size)
(PathData. size buffer (weak-map/create) nil)))
(PathData. size buffer (weak/weak-value-map) nil)))
(-walk [_ f initial]
(impl-walk buffer f initial size))
@@ -537,7 +538,8 @@
(sg/fmap from-plain))]
{:pred path-data?
:type-properties
{:gen/gen generator
{::oapi/type "string"
:gen/gen generator
:encode/json identity
:decode/json (fn [s]
(cond
@@ -598,14 +600,14 @@
count (long (/ size SEGMENT-U8-SIZE))]
(PathData. count
(js/DataView. buffer)
(weak-map/create)
(weak/weak-value-map)
nil))
(instance? js/DataView buffer)
(let [buffer' (.-buffer ^js/DataView buffer)
size (.-byteLength ^js/ArrayBuffer buffer')
count (long (/ size SEGMENT-U8-SIZE))]
(PathData. count buffer (weak-map/create) nil))
(PathData. count buffer (weak/weak-value-map) nil))
(instance? js/Uint8Array buffer)
(from-bytes (.-buffer buffer))

View File

@@ -6,7 +6,6 @@
(ns app.common.types.plugins
(:require
[app.common.schema :as sm]
[app.common.schema.generators :as sg]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -22,15 +21,13 @@
:keyword])
(def schema:plugin-data
(sm/register!
^{::sm/type ::plugin-data}
[:map-of {:gen/max 5 :title "PluginsData"}
schema:keyword
[:map-of {:gen/max 5}
schema:keyword
[:map-of {:gen/max 5}
schema:string
schema:string]]))
schema:string
schema:string]])
(def ^:private schema:registry-entry
(def schema:registry-entry
[:map
[:plugin-id :string]
[:name :string]
@@ -47,6 +44,3 @@
[:map-of {:gen/max 5}
:string
schema:registry-entry]]])
(sm/register! ::plugin-registry schema:plugin-registry)
(sm/register! ::registry-entry schema:registry-entry)

View File

@@ -0,0 +1,23 @@
;; 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.common.types.profile
(:require
[app.common.schema :as sm]
[app.common.time :as cm]))
(def schema:profile
[:map {:title "Profile"}
[:id ::sm/uuid]
[:created-at {:optional true} ::cm/inst]
[:fullname {:optional true} :string]
[:email {:optional true} :string]
[:lang {:optional true} :string]
[:theme {:optional true} :string]
[:photo-id {:optional true} ::sm/uuid]
;; Only present on resolved profile objects, the resolve process
;; takes the photo-id or geneates an image from the name
[:photo-url {:optional true} :string]])

View File

@@ -22,7 +22,6 @@
[app.common.types.fills :refer [schema:fill fill->color]]
[app.common.types.grid :as ctg]
[app.common.types.path :as path]
[app.common.types.path.segment :as path.segment]
[app.common.types.plugins :as ctpg]
[app.common.types.shape.attrs :refer [default-color]]
[app.common.types.shape.blur :as ctsb]
@@ -119,8 +118,6 @@
(def schema:points
[:vector {:gen/max 4 :gen/min 4} ::gpt/point])
;; FIXME: the register is necessary until this is moved to a separated
;; ns because it is used on shapes.text
(def valid-stroke-attrs
"A set used for proper check if color should contain only one of the
attrs listed in this set."
@@ -156,10 +153,8 @@
(sm/keys schema:stroke-attrs))
(def schema:stroke
(sm/register!
^{::sm/type ::stroke}
[:and schema:stroke-attrs
[:fn has-valid-stroke-attrs?]]))
[:and schema:stroke-attrs
[:fn has-valid-stroke-attrs?]])
(def check-stroke
(sm/check-fn schema:stroke))
@@ -184,7 +179,7 @@
[:height ::sm/safe-number]])
(def schema:shape-generic-attrs
[:map {:title "ShapeAttrs"}
[:map {:title "ShapeGenericAttrs"}
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:component-file {:optional true} ::sm/uuid]
@@ -213,22 +208,22 @@
[:r4 {:optional true} ::sm/safe-number]
[:opacity {:optional true} ::sm/safe-number]
[:grids {:optional true}
[:vector {:gen/max 2} ::ctg/grid]]
[:vector {:gen/max 2} ctg/schema:grid]]
[:exports {:optional true}
[:vector {:gen/max 2} ::ctse/export]]
[:vector {:gen/max 2} ctse/schema:export]]
[:strokes {:optional true}
[:vector {:gen/max 2} schema:stroke]]
[:blend-mode {:optional true}
[::sm/one-of blend-modes]]
[:interactions {:optional true}
[:vector {:gen/max 2} ::ctsi/interaction]]
[:vector {:gen/max 2} ctsi/schema:interaction]]
[:shadow {:optional true}
[:vector {:gen/max 1} ctss/schema:shadow]]
[:blur {:optional true} ::ctsb/blur]
[:blur {:optional true} ctsb/schema:blur]
[:grow-type {:optional true}
[::sm/one-of grow-types]]
[:applied-tokens {:optional true} cto/schema:applied-tokens]
[:plugin-data {:optional true} ::ctpg/plugin-data]])
[:plugin-data {:optional true} ctpg/schema:plugin-data]])
(def schema:group-attrs
[:map {:title "GroupAttrs"}
@@ -274,7 +269,8 @@
(def ^:private schema:text-attrs
[:map {:title "TextAttrs"}
[:content {:optional true} [:maybe ::ctsx/content]]])
[:position-data {:optional true} [:maybe ctsx/schema:position-data]]
[:content {:optional true} [:maybe ctsx/schema:content]]])
(defn- decode-shape
[o]
@@ -326,8 +322,8 @@
schema:shape-generic-attrs
schema:shape-geom-attrs
schema:shape-base-attrs
::ctv/variant-shape
::ctv/variant-container]]
ctv/schema:variant-shape
ctv/schema:variant-container]]
[:bool
[:merge {:title "BoolShape"}
@@ -384,13 +380,11 @@
schema:shape-base-attrs]]])
(def schema:shape
(sm/register!
^{::sm/type ::shape}
[:and {:title "Shape"
:gen/gen (shape-generator)
:decode/json {:leave decode-shape}}
[:fn shape?]
schema:shape-attrs]))
[:and {:title "Shape"
:gen/gen (shape-generator)
:decode/json {:leave decode-shape}}
[:fn shape?]
schema:shape-attrs])
(def check-shape-generic-attrs
(sm/check-fn schema:shape-generic-attrs))
@@ -418,7 +412,7 @@
#{:page-id :component-id :component-file :component-root :main-instance
:remote-synced :shape-ref :touched :blocked :collapsed :locked
:hidden :masked-group :fills :proportion :proportion-lock :constraints-h
:constraints-v :fixed-scroll :r1 :r2 :r3 :r4 :opacity :grids :exports
:constraints-v :fixed-scroll :r1 :r2 :r3 :r4 :rotation :opacity :grids :exports
:strokes :blend-mode :interactions :shadow :blur :grow-type :applied-tokens
:plugin-data})
@@ -593,12 +587,16 @@
(defn setup-path
[{:keys [content selrect points] :as shape}]
(let [selrect (or selrect
(path.segment/content->selrect content)
(path/calc-selrect content)
(grc/make-rect))
points (or points (grc/rect->points selrect))]
points (or points
(grc/rect->points selrect))
;; Ensure we hace correct type here for Path Data
content (path/content content)]
(-> shape
(assoc :selrect selrect)
(assoc :points points))))
(assoc :points points)
(assoc :content content))))
(defn- setup-image
[{:keys [metadata] :as shape}]

View File

@@ -6,30 +6,11 @@
(ns app.common.types.shape.blur
(:require
[app.common.schema :as sm]
[app.common.spec :as us]
[clojure.spec.alpha :as s]))
[app.common.schema :as sm]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SPEC
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::id uuid?)
(s/def ::type #{:layer-blur})
(s/def ::value ::us/safe-number)
(s/def ::hidden boolean?)
(s/def ::blur
(s/keys :req-un [::id ::type ::value ::hidden]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(sm/register!
^{::sm/type ::blur}
[:map {:title "Blur"}
[:id ::sm/uuid]
[:type [:= :layer-blur]]
[:value ::sm/safe-number]
[:hidden :boolean]])
(def schema:blur
[:map {:title "Blur"}
[:id ::sm/uuid]
[:type [:= :layer-blur]]
[:value ::sm/safe-number]
[:hidden :boolean]])

View File

@@ -15,5 +15,3 @@
[:type [::sm/one-of types]]
[:scale ::sm/safe-number]
[:suffix :string]])
(sm/register! ::export schema:export)

View File

@@ -109,8 +109,8 @@
(def check-animation!
(sm/check-fn schema:animation))
(def schema:interaction-attrs
[:map {:title "InteractionAttrs"}
(def schema:generic-interaction-attrs
[:map {:title "GenericInteractionAttrs"}
[:action-type {:optional true} [::sm/one-of action-types]]
[:event-type {:optional true} [::sm/one-of event-types]]
[:destination {:optional true} [:maybe ::sm/uuid]]
@@ -124,7 +124,7 @@
[:url {:optional true} :string]])
(def schema:navigate-interaction
[:map
[:map {:title "NavigateInteraction"}
[:action-type [:= :navigate]]
[:event-type [::sm/one-of event-types]]
[:destination {:optional true} [:maybe ::sm/uuid]]
@@ -132,7 +132,7 @@
[:animation {:optional true} schema:animation]])
(def schema:open-overlay-interaction
[:map
[:map {:title "OpenOverlayInteraction"}
[:action-type [:= :open-overlay]]
[:event-type [::sm/one-of event-types]]
[:overlay-position ::gpt/point]
@@ -144,7 +144,7 @@
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]])
(def schema:toggle-overlay-interaction
[:map
[:map {:title "ToggleOverlayInteraction"}
[:action-type [:= :toggle-overlay]]
[:event-type [::sm/one-of event-types]]
[:overlay-position ::gpt/point]
@@ -156,7 +156,7 @@
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]])
(def schema:close-overlay-interaction
[:map
[:map {:title "CloseOverlayInteraction"}
[:action-type [:= :close-overlay]]
[:event-type [::sm/one-of event-types]]
[:destination {:optional true} [:maybe ::sm/uuid]]
@@ -164,34 +164,33 @@
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]])
(def schema:prev-scren-interaction
[:map
[:map {:title "PrevScreenInteraction"}
[:action-type [:= :prev-screen]]
[:event-type [::sm/one-of event-types]]])
(def schema:open-url-interaction
[:map
[:map {:title "OpenUrlInteraction"}
[:action-type [:= :open-url]]
[:event-type [::sm/one-of event-types]]
[:url :string]])
(def schema:interaction
[:and {:title "Interaction"
:gen/gen (sg/one-of (sg/generator schema:navigate-interaction)
(sg/generator schema:open-overlay-interaction)
(sg/generator schema:close-overlay-interaction)
(sg/generator schema:toggle-overlay-interaction)
(sg/generator schema:prev-scren-interaction)
(sg/generator schema:open-url-interaction))}
schema:interaction-attrs
[:multi {:dispatch :action-type}
[:navigate schema:navigate-interaction]
[:open-overlay schema:open-overlay-interaction]
[:toggle-overlay schema:toggle-overlay-interaction]
[:close-overlay schema:close-overlay-interaction]
[:prev-screen schema:prev-scren-interaction]
[:open-url schema:open-url-interaction]]])
(sm/register! ::interaction schema:interaction)
[:schema {:title "Interaction"
:gen/gen (sg/one-of (sg/generator schema:navigate-interaction)
(sg/generator schema:open-overlay-interaction)
(sg/generator schema:close-overlay-interaction)
(sg/generator schema:toggle-overlay-interaction)
(sg/generator schema:prev-scren-interaction)
(sg/generator schema:open-url-interaction))}
[:and
schema:generic-interaction-attrs
[:multi {:dispatch :action-type :title "InteractionAttrs"}
[:navigate schema:navigate-interaction]
[:open-overlay schema:open-overlay-interaction]
[:toggle-overlay schema:toggle-overlay-interaction]
[:close-overlay schema:close-overlay-interaction]
[:prev-screen schema:prev-scren-interaction]
[:open-url schema:open-url-interaction]]]])
(def check-interaction
(sm/check-fn schema:interaction))

View File

@@ -14,22 +14,22 @@
[app.common.schema :as sm]
[app.common.uuid :as uuid]))
;; :layout ;; :flex, :grid in the future
;; :layout-flex-dir ;; :row, :row-reverse, :column, :column-reverse
;; :layout-gap-type ;; :simple, :multiple
;; :layout-gap ;; {:row-gap number , :column-gap number}
;; :layout ;; :flex, :grid in the future
;; :layout-flex-dir ;; :row, :row-reverse, :column, :column-reverse
;; :layout-gap-type ;; :simple, :multiple
;; :layout-gap ;; {:row-gap number , :column-gap number}
;; :layout-align-items ;; :start :end :center :stretch
;; :layout-align-content ;; :start :center :end :space-between :space-around :space-evenly :stretch (by default)
;; :layout-align-items ;; :start :end :center :stretch
;; :layout-align-content ;; :start :center :end :space-between :space-around :space-evenly :stretch (by default)
;; :layout-justify-items ;; :start :center :end :space-between :space-around :space-evenly
;; :layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly
;; :layout-wrap-type ;; :wrap, :nowrap
;; :layout-padding-type ;; :simple, :multiple
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
;; :layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly
;; :layout-wrap-type ;; :wrap, :nowrap
;; :layout-padding-type ;; :simple, :multiple
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
;; layout-grid-rows ;; vector of grid-track
;; layout-grid-columns ;; vector of grid-track
;; layout-grid-cells ;; map of id->grid-cell
;; layout-grid-rows ;; vector of grid-track
;; layout-grid-columns ;; vector of grid-track
;; layout-grid-cells ;; map of id->grid-cell
;; ITEMS
;; :layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0}
@@ -517,7 +517,7 @@
([objects id]
(item-absolute? (get objects id)))
([shape]
(true? (:layout-item-absolute shape))))
(true? (get shape :layout-item-absolute))))
(defn position-absolute?
([objects id]

View File

@@ -7,7 +7,6 @@
(ns app.common.types.shape.shadow
(:require
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.types.color :as ctc]))
(def styles #{:drop-shadow :inner-shadow})
@@ -15,10 +14,7 @@
(def schema:shadow
[:map {:title "Shadow"}
[:id [:maybe ::sm/uuid]]
[:style
[:and {:gen/gen (sg/elements styles)}
:keyword
[::sm/one-of styles]]]
[:style [::sm/one-of styles]]
[:offset-x ::sm/safe-number]
[:offset-y ::sm/safe-number]
[:blur ::sm/safe-number]

View File

@@ -7,9 +7,7 @@
(ns app.common.types.shape.text
(:require
[app.common.schema :as sm]
[app.common.types.fills :refer [schema:fill]]
[app.common.types.shape :as-alias shape]
[app.common.types.shape.text.position-data :as-alias position-data]))
[app.common.types.fills :refer [schema:fill]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA
@@ -63,26 +61,22 @@
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])
(sm/register! ::content schema:content)
(def valid-content?
(sm/lazy-validator schema:content))
(sm/register!
^{::sm/type ::position-data}
[:vector {:min 1 :gen/max 2}
[:map
[:x ::sm/safe-number]
[:y ::sm/safe-number]
[:width ::sm/safe-number]
[:height ::sm/safe-number]
[:fills [:vector {:gen/max 2} schema:fill]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:rtl {:optional true} :boolean]
[:text {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]]])
(def schema:position-data
[:vector {:min 0 :gen/max 2}
[:map
[:x ::sm/safe-number]
[:y ::sm/safe-number]
[:width ::sm/safe-number]
[:height ::sm/safe-number]
[:fills [:vector {:gen/max 2} schema:fill]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:rtl {:optional true} :boolean]
[:text {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]]])

View File

@@ -0,0 +1,7 @@
(ns app.common.types.shape.token)
(defn font-weight-applied?
[shape]
(or
(get-in shape [:applied-tokens :font-weight])
(get-in shape [:applied-tokens :typography :font-weight])))

View File

@@ -17,4 +17,5 @@
:admin {:can-edit true :is-admin true :is-owner false}
:owner {:can-edit true :is-admin true :is-owner true}})
(sm/register! ::role [::sm/one-of valid-roles])
(def schema:role
[::sm/one-of {:title "TeamRole"} valid-roles])

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[clojure.data :as data]
[clojure.set :as set]
[cuerdas.core :as str]
@@ -47,7 +48,8 @@
:stroke-width "borderWidth"
:text-case "textCase"
:text-decoration "textDecoration"
:font-weight "fontWeights"})
:font-weight "fontWeights"
:typography "typography"})
(def dtcg-token-type->token-type
(set/map-invert token-type->dtcg-token-type))
@@ -56,7 +58,8 @@
(into #{} (keys token-type->dtcg-token-type)))
(def token-name-ref
[:and :string [:re #"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?<!\.)$"]])
[:re {:title "TokenNameRef" :gen/gen sg/text}
#"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?<!\.)$"])
(def ^:private schema:color
[:map
@@ -66,7 +69,7 @@
(def color-keys (schema-keys schema:color))
(def ^:private schema:border-radius
[:map
[:map {:title "BorderRadiusTokenAttrs"}
[:r1 {:optional true} token-name-ref]
[:r2 {:optional true} token-name-ref]
[:r3 {:optional true} token-name-ref]
@@ -80,56 +83,75 @@
(def stroke-width-keys (schema-keys schema:stroke-width))
(def ^:private schema:sizing
[:map
(def ^:private schema:sizing-base
[:map {:title "SizingBaseTokenAttrs"}
[:width {:optional true} token-name-ref]
[:height {:optional true} token-name-ref]
[:height {:optional true} token-name-ref]])
(def ^:private schema:sizing-layout-item
[:map {:title "SizingLayoutItemTokenAttrs"}
[:layout-item-min-w {:optional true} token-name-ref]
[:layout-item-max-w {:optional true} token-name-ref]
[:layout-item-min-h {:optional true} token-name-ref]
[:layout-item-max-h {:optional true} token-name-ref]])
(def ^:private schema:sizing
(-> (reduce mu/union [schema:sizing-base
schema:sizing-layout-item])
(mu/update-properties assoc :title "SizingTokenAttrs")))
(def sizing-layout-item-keys (schema-keys schema:sizing-layout-item))
(def sizing-keys (schema-keys schema:sizing))
(def ^:private schema:opacity
[:map
[:map {:title "OpacityTokenAttrs"}
[:opacity {:optional true} token-name-ref]])
(def opacity-keys (schema-keys schema:opacity))
(def ^:private schema:spacing-gap
[:map
[:map {:title "SpacingGapTokenAttrs"}
[:row-gap {:optional true} token-name-ref]
[:column-gap {:optional true} token-name-ref]])
(def ^:private schema:spacing-padding
[:map
[:map {:title "SpacingPaddingTokenAttrs"}
[:p1 {:optional true} token-name-ref]
[:p2 {:optional true} token-name-ref]
[:p3 {:optional true} token-name-ref]
[:p4 {:optional true} token-name-ref]])
(def ^:private schema:spacing-margin
[:map
[:map {:title "SpacingMarginTokenAttrs"}
[:m1 {:optional true} token-name-ref]
[:m2 {:optional true} token-name-ref]
[:m3 {:optional true} token-name-ref]
[:m4 {:optional true} token-name-ref]])
(def ^:private schema:spacing
(reduce mu/union [schema:spacing-gap
schema:spacing-padding
schema:spacing-margin]))
(-> (reduce mu/union [schema:spacing-gap
schema:spacing-padding
schema:spacing-margin])
(mu/update-properties assoc :title "SpacingTokenAttrs")))
(def spacing-margin-keys (schema-keys schema:spacing-margin))
(def spacing-keys (schema-keys schema:spacing))
(def ^:private schema:spacing-gap-padding
(-> (reduce mu/union [schema:spacing-gap
schema:spacing-padding])
(mu/update-properties assoc :title "SpacingGapPaddingTokenAttrs")))
(def spacing-gap-padding-keys (schema-keys schema:spacing-gap-padding))
(def ^:private schema:dimensions
(reduce mu/union [schema:sizing
schema:spacing
schema:stroke-width
schema:border-radius]))
(-> (reduce mu/union [schema:sizing
schema:spacing
schema:stroke-width
schema:border-radius])
(mu/update-properties assoc :title "DimensionsTokenAttrs")))
(def dimensions-keys (schema-keys schema:dimensions))
@@ -140,22 +162,20 @@
(def axis-keys (schema-keys schema:axis))
(def ^:private schema:rotation
[:map
[:map {:title "RotationTokenAttrs"}
[:rotation {:optional true} token-name-ref]])
(def rotation-keys (schema-keys schema:rotation))
(def ^:private schema:font-size
[:map
[:map {:title "FontSizeTokenAttrs"}
[:font-size {:optional true} token-name-ref]])
(def font-size-keys (schema-keys schema:font-size))
(def ^:private schema:letter-spacing
[:map
[:map {:title "LetterSpacingTokenAttrs"}
[:letter-spacing {:optional true} token-name-ref]])
(def letter-spacing-keys (schema-keys schema:letter-spacing))
@@ -178,6 +198,12 @@
(def font-weight-keys (schema-keys schema:font-weight))
(def ^:private schema:typography
[:map
[:typography {:optional true} token-name-ref]])
(def typography-token-keys (schema-keys schema:typography))
(def ^:private schema:text-decoration
[:map
[:text-decoration {:optional true} token-name-ref]])
@@ -190,15 +216,17 @@
font-weight-keys
text-case-keys
text-decoration-keys
font-weight-keys))
font-weight-keys
typography-token-keys))
;; TODO: Created to extract the font-size feature from the typography feature flag.
;; Delete this once the typography feature flag is removed.
(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]))
(-> (reduce mu/union [[:map [:line-height {:optional true} token-name-ref]]
schema:rotation])
(mu/update-properties assoc :title "NumberTokenAttrs")))
(def number-keys (schema-keys schema:number))
@@ -212,13 +240,14 @@
axis-keys
rotation-keys
typography-keys
typography-token-keys
number-keys))
(def ^:private schema:tokens
[:map {:title "Applied Tokens"}])
[:map {:title "GenericTokenAttrs"}])
(def schema:applied-tokens
[:merge
[:merge {:title "AppliedTokens"}
schema:tokens
schema:border-radius
schema:sizing
@@ -257,12 +286,13 @@
changed-sub-attr
#{:m1 :m2 :m3 :m4})
(font-size-keys shape-attr) #{shape-attr}
(letter-spacing-keys shape-attr) #{shape-attr}
(font-family-keys shape-attr) #{shape-attr}
(text-case-keys shape-attr) #{shape-attr}
(text-decoration-keys shape-attr) #{shape-attr}
(font-weight-keys shape-attr) #{shape-attr}
(font-size-keys shape-attr) #{shape-attr :typography}
(letter-spacing-keys shape-attr) #{shape-attr :typography}
(font-family-keys shape-attr) #{shape-attr :typography}
(= :text-transform shape-attr) #{:text-case :typography}
(text-decoration-keys shape-attr) #{shape-attr :typography}
(font-weight-keys shape-attr) #{shape-attr :typography}
(border-radius-keys shape-attr) #{shape-attr}
(sizing-keys shape-attr) #{shape-attr}
(opacity-keys shape-attr) #{shape-attr}
@@ -297,9 +327,9 @@
(set/union generic-attributes
border-radius-keys))
(def frame-attributes
(def frame-with-layout-attributes
(set/union rect-attributes
spacing-keys))
spacing-gap-padding-keys))
(def text-attributes
(set/union generic-attributes
@@ -307,12 +337,14 @@
number-keys))
(defn shape-type->attributes
[type]
[type is-layout]
(case type
:bool generic-attributes
:circle generic-attributes
:rect rect-attributes
:frame frame-attributes
:frame (if is-layout
frame-with-layout-attributes
rect-attributes)
:image rect-attributes
:path generic-attributes
:svg-raw generic-attributes
@@ -320,14 +352,14 @@
nil))
(defn appliable-attrs
"Returns intersection of shape `attributes` for `token-type`."
[attributes token-type]
(set/intersection attributes (shape-type->attributes token-type)))
"Returns intersection of shape `attributes` for `shape-type`."
[attributes shape-type is-layout]
(set/intersection attributes (shape-type->attributes shape-type is-layout)))
(defn any-appliable-attr?
"Checks if `token-type` supports given shape `attributes`."
[attributes token-type]
(seq (appliable-attrs attributes token-type)))
[attributes token-type is-layout]
(seq (appliable-attrs attributes token-type is-layout)))
;; Token attrs that are set inside content blocks of text shapes, instead
;; at the shape level.
@@ -365,6 +397,13 @@
(defn unapply-token-id [shape attributes]
(update shape :applied-tokens d/without-keys attributes))
(defn unapply-layout-item-tokens
"Unapplies all layout item related tokens from shape."
[shape]
(let [layout-item-attrs (set/union sizing-layout-item-keys
spacing-margin-keys)]
(unapply-token-id shape layout-item-attrs)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TYPOGRAPHY
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -412,14 +451,20 @@
(reduce (fn [acc [k vs]]
(into acc (zipmap vs (repeat k)))) {})))
(defn parse-font-weight [font-weight]
(let [[_ variant italic] (->> (str font-weight)
(str/lower)
(re-find #"^(.+?)\s*(italic)?$"))]
{:variant variant
:italic? (some? italic)}))
(defn valid-font-weight-variant
"Converts font-weight token value to a map like `{:weight \"100\" :style \"italic\"}`.
Converts a weight alias like `regular` to a number, needs to be a regular number.
Adds `italic` style when found in the `value` string."
[value]
(let [[weight style] (->> (str/split value #"\s+")
(map str/lower))
weight (get font-weight-map weight weight)]
(let [{:keys [variant italic?]} (parse-font-weight value)
weight (get font-weight-map variant variant)]
(when (font-weight-values weight)
(cond-> {:weight weight}
(= style "italic") (assoc :style "italic")))))
italic? (assoc :style "italic")))))

View File

@@ -543,18 +543,17 @@
(get-ordered-set-names [_] "get an ordered sequence of all sets names in the library")
(get-set [_ set-name] "get one set looking for name"))
(def schema:token-set-node
[:schema {:registry {::node [:or [:fn token-set?]
[:and
[:map-of {:gen/max 5} :string [:ref ::node]]
[:fn d/ordered-map?]]]}}
(def ^:private schema:token-set-node
[:schema {:registry {::node
[:or [:fn token-set?]
[:and
[:map-of {:gen/max 5} :string [:ref ::node]]
[:fn d/ordered-map?]]]}}
[:ref ::node]])
(def schema:token-sets
[:and
[:map-of {:title "TokenSets"}
:string
schema:token-set-node]
(def ^:private schema:token-sets
[:and {:title "TokenSets"}
[:map-of :string schema:token-set-node]
[:fn d/ordered-map?]])
(def ^:private check-token-sets
@@ -769,7 +768,8 @@
(theme-active? [_ group name] "predicate if token theme is active")
(activate-theme [_ group name] "adds theme from the active-themes")
(deactivate-theme [_ group name] "removes theme from the active-themes")
(toggle-theme-active? [_ group name] "toggles theme in the active-themes"))
(toggle-theme-active? [_ group name] "toggles theme in the active-themes")
(get-hidden-theme [_] "get the hidden temporary theme"))
(def schema:token-themes
[:and
@@ -907,6 +907,7 @@
(defprotocol ITokensLib
"A library of tokens, sets and themes."
(empty-lib? [_] "True if the lib does not contain any token, set or theme")
(set-path-exists? [_ path] "if a set at `path` exists")
(set-group-path-exists? [_ path] "if a set group at `path` exists")
(add-token-in-set [_ set-name token] "add token to a set")
@@ -1236,7 +1237,16 @@ Will return a value that matches this schema:
(filter #(theme-active? this (:group %) (:name %))))
(tree-seq d/ordered-map? vals themes)))
(get-hidden-theme [this]
(get-theme this hidden-theme-group hidden-theme-name))
ITokensLib
(empty-lib? [this]
(and (empty? sets)
(or (empty? themes)
(and (= (theme-count this) 1)
(get-hidden-theme this)))))
(set-path-exists? [_ set-path]
(some? (get-in sets (set-full-path->set-prefixed-full-path set-path))))
@@ -1339,10 +1349,6 @@ Will return a value that matches this schema:
cljs.core/IEncodeJS
(-clj->js [this] (clj->js (datafy this)))))
(defn get-hidden-theme
[tokens-lib]
(get-theme tokens-lib hidden-theme-group hidden-theme-name))
(defn valid-tokens-lib?
[o]
(and (instance? TokensLib o)
@@ -1375,7 +1381,7 @@ Will return a value that matches this schema:
(or tokens-lib (make-tokens-lib)))
(def schema:tokens-lib
(sm/register!
(sm/type-schema
{:type ::tokens-lib
:pred valid-tokens-lib?
:type-properties
@@ -1753,11 +1759,12 @@ Will return a value that matches this schema:
active-set-names
(get-active-themes-set-names tokens-lib)]
(-> sets
(assoc "$themes" themes)
(assoc "$metadata" {"tokenSetOrder" ordered-set-names
"activeThemes" active-themes
"activeSets" active-set-names}))))
(when-not (empty-lib? tokens-lib)
(-> sets
(assoc "$themes" themes)
(assoc "$metadata" {"tokenSetOrder" ordered-set-names
"activeThemes" active-themes
"activeSets" active-set-names})))))
(defn get-tokens-of-unknown-type
"Search for all tokens in the decoded json file that have a type that is not currently

View File

@@ -18,26 +18,24 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def schema:typography
(sm/register!
^{::sm/type ::typography}
[:map {:title "Typography"}
[:id ::sm/uuid]
[:name :string]
[:font-id :string]
[:font-family :string]
[:font-variant-id :string]
[:font-size :string]
[:font-weight :string]
[:font-style :string]
[:line-height :string]
[:letter-spacing :string]
[:text-transform :string]
[:modified-at {:optional true} ::ct/inst]
[:path {:optional true} [:maybe :string]]
[:plugin-data {:optional true} ::ctpg/plugin-data]]))
[:map {:title "Typography"}
[:id ::sm/uuid]
[:name :string]
[:font-id :string]
[:font-family :string]
[:font-variant-id :string]
[:font-size :string]
[:font-weight :string]
[:font-style :string]
[:line-height :string]
[:letter-spacing :string]
[:text-transform :string]
[:modified-at {:optional true} ::ct/inst]
[:path {:optional true} [:maybe :string]]
[:plugin-data {:optional true} ctpg/schema:plugin-data]])
(def check-typography
(sm/check-fn ::typography))
(sm/check-fn schema:typography))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS

View File

@@ -22,34 +22,27 @@
[:value :string]])
(def schema:variant-component
;; A component that is part of a variant set.
(sm/register!
^{::sm/type ::variant-component}
[:map
[:variant-id {:optional true} ::sm/uuid]
[:variant-properties {:optional true} [:vector schema:variant-property]]]))
"A component that is part of a variant set"
[:map
[:variant-id {:optional true} ::sm/uuid]
[:variant-properties {:optional true} [:vector schema:variant-property]]])
(def schema:variant-shape
;; The root shape of the main instance of a variant component.
"The root shape of the main instance of a variant component"
[:map
[:variant-id {:optional true} ::sm/uuid]
[:variant-name {:optional true} :string]
[:variant-error {:optional true} :string]])
(def schema:variant-container
;; is a board that contains all variant components of a variant set,
;; for grouping them visually in the workspace.
"Is a board that contains all variant components of a variant set,
for grouping them visually in the workspace"
[:map
[:is-variant-container {:optional true} :boolean]])
(sm/register! ::variant-property schema:variant-property)
(sm/register! ::variant-shape schema:variant-shape)
(sm/register! ::variant-container schema:variant-container)
(def valid-variant-component?
(sm/check-fn schema:variant-component))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def property-prefix "Property ")

View File

@@ -0,0 +1,79 @@
;; 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.common.weak
"A collection of helpers for work with weak references and weak
data structures on JS runtime."
(:refer-clojure :exclude [memoize])
(:require
#?@(:cljs [["./weak/impl_weak_map.js" :as wm]
["./weak/impl_weak_value_map.js" :as wvm]]
:clj [[app.common.weak.impl-loadable-weak-value-map :as lwvm]])))
#?(:cljs
(defn weak-value-map
"Creates a WeakMap instance where values are held by soft
references and keys are held by hard references."
[]
(new wvm/WeakValueMap.)))
#?(:cljs
(defn weak-map
"Create a WeakMap like instance what uses clojure equality
semantics."
[]
(new wm/WeakEqMap #js {:hash hash :equals =})))
#?(:clj
(defn loadable-weak-value-map
"Creates an instance of a LoadableWeakValueMap. It gives you a clojure-like,
map instance with fixed number of keys and fixed preload data (for
the provided keys) where not preload data is lazy loadable. It
internally uses soft-like references, leaving the runtime to collect
values that are not in use (no hard references keeps on the runtime)."
([keys load-fn]
(lwvm/loadable-weak-value-map keys load-fn {}))
([keys load-fn preload-data]
(lwvm/loadable-weak-value-map keys load-fn preload-data))))
#?(:cljs (def ^:private state (new js/WeakMap)))
#?(:cljs (def ^:private global-counter 0))
#?(:cljs
(defn weak-key
"A simple helper that returns a stable key string for an object while
that object remains in memory and is not collected by the GC.
Mainly used for assign temporal IDs/keys for react children
elements when the element has no specific id."
[o]
(let [key (.get ^js/WeakMap state o)]
(if (some? key)
key
(let [key (str "weak-key" (js* "~{}++" global-counter))]
(.set ^js/WeakMap state o key)
key)))))
#?(:cljs
(defn memoize
"Returns a memoized version of a referentially transparent
function. The memoized version of the function keeps a cache of the
mapping from arguments to results and, when calls with the same
arguments are repeated often, has higher performance at the expense
of higher memory use.
The main difference with clojure.core/memoize, is that this function
uses weak-map, so cache is cleared once GC is passed and cached keys
are collected"
[f]
(let [mem (weak-map)]
(fn [& args]
(let [v (.get mem args)]
(if (undefined? v)
(let [ret (apply f args)]
(.set ^js mem args ret)
ret)
v))))))

View File

@@ -0,0 +1,100 @@
;; 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.common.weak.impl-loadable-weak-value-map
(:import
clojure.lang.Associative
clojure.lang.Counted
clojure.lang.IPersistentVector
clojure.lang.MapEntry
clojure.lang.Seqable
java.lang.ref.SoftReference
java.util.HashMap
java.util.Map$Entry))
(deftype LoadableWeakValueMap [^HashMap data load-fn]
clojure.lang.ILookup
(valAt [_ key]
(when-let [reference (.get data key)]
(if (instance? SoftReference reference)
(or (.get ^SoftReference reference)
(let [value (load-fn key)]
(.put data key (SoftReference. value))
value))
reference)))
(valAt [_ key default]
(if-let [reference (.get data key)]
(if (instance? SoftReference reference)
(or (.get ^SoftReference reference)
(let [value (load-fn key)]
(.put data key (SoftReference. value))
value))
reference)
default))
Associative
(containsKey [_ key]
(.containsKey data key))
(entryAt [_ key]
(when-let [reference (.get data key)]
(if (instance? SoftReference reference)
(let [val (or (.get ^SoftReference reference)
(let [value (load-fn key)]
(.put data key (SoftReference. value))
value))]
(MapEntry/create key val))
(MapEntry/create key reference))))
(assoc [_ key val]
(let [data' (HashMap. data)]
(.put data' key (SoftReference. val))
(LoadableWeakValueMap. data' load-fn)))
(cons [this other]
(cond
(instance? Map$Entry other)
(.assoc ^Associative this
(.getKey ^Map$Entry other)
(.getValue ^Map$Entry other))
(vector? other)
(do
(when (not= 2 (count other))
(throw (IllegalArgumentException. "Vector arg to map conj must be a pair")))
(.assoc ^Associative this
(.nth ^IPersistentVector other 0)
(.nth ^IPersistentVector other 1)))
:else
(throw (IllegalArgumentException. "only MapEntry or Vectors supported on cons"))))
(empty [_]
(LoadableWeakValueMap. (HashMap.) load-fn))
(equiv [_ _other]
(throw (UnsupportedOperationException. "equiv not implemented")))
Counted
(count [_]
(.size data))
Seqable
(seq [this]
(->> (seq (.keySet data))
(map (fn [key]
(MapEntry/create key (.valAt this key)))))))
(defn loadable-weak-value-map
[keys load-fn preload-data]
(let [^HashMap hmap (HashMap. (count keys))]
(run! (fn [key]
(if-let [value (get preload-data key)]
(.put hmap key value)
(.put hmap key (SoftReference. nil))))
keys)
(LoadableWeakValueMap. hmap load-fn)))

View File

@@ -0,0 +1,130 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright (c) KALEIDOS INC
*/
"use strict";
export class WeakEqMap {
constructor({ equals, hash }) {
this._equals = equals;
this._hash = hash;
// buckets: Map<hash, Array<Entry>>
this._buckets = new Map();
// Token -> (hash) so the FR cleanup can find & remove dead entries
// We store {hash, token} as heldValue for FinalizationRegistry
this._fr = new FinalizationRegistry(({ hash, token }) => {
const bucket = this._buckets.get(hash);
if (!bucket) return;
// Remove the entry whose token matches or whose key has been collected
let i = 0;
while (i < bucket.length) {
const e = bucket[i];
const dead = e.keyRef.deref() === undefined;
if (dead || e.token === token) {
// swap-remove for O(1)
bucket[i] = bucket[bucket.length - 1];
bucket.pop();
continue;
}
i++;
}
if (bucket.length === 0) this._buckets.delete(hash);
});
}
_getBucket(hash) {
let b = this._buckets.get(hash);
if (!b) {
b = [];
this._buckets.set(hash, b);
}
return b;
}
_findEntry(bucket, key) {
// Sweep dead entries opportunistically
let i = 0;
let found = null;
while (i < bucket.length) {
const e = bucket[i];
const k = e.keyRef.deref();
if (k === undefined) {
bucket[i] = bucket[bucket.length - 1];
bucket.pop();
continue;
}
if (found === null && this._equals(k, key)) {
found = e;
}
i++;
}
return found;
}
set(key, value) {
if (key === null || (typeof key !== 'object' && typeof key !== 'function')) {
throw new TypeError('WeakEqMap keys must be objects (like WeakMap).');
}
const hash = this._hash(key);
const bucket = this._getBucket(hash);
const existing = this._findEntry(bucket, key);
if (existing) {
existing.value = value;
return this;
}
const token = Object.create(null); // unique identity
const entry = { keyRef: new WeakRef(key), value, token };
bucket.push(entry);
// Register for cleanup when key is GCd
this._fr.register(key, { hash, token }, entry);
return this;
}
get(key) {
const hash = this._hash(key);
const bucket = this._buckets.get(hash);
if (!bucket) return undefined;
const e = this._findEntry(bucket, key);
return e ? e.value : undefined;
}
has(key) {
const hash = this._hash(key);
const bucket = this._buckets.get(hash);
if (!bucket) return false;
return !!this._findEntry(bucket, key);
}
delete(key) {
const hash = this._hash(key);
const bucket = this._buckets.get(hash);
if (!bucket) return false;
let i = 0;
while (i < bucket.length) {
const e = bucket[i];
const k = e.keyRef.deref();
if (k === undefined) {
// clean dead
bucket[i] = bucket[bucket.length - 1];
bucket.pop();
continue;
}
if (this._equals(k, key)) {
// Unregister and remove
this._fr.unregister(e); // unregister via the registration "unregisterToken" = entry
bucket[i] = bucket[bucket.length - 1];
bucket.pop();
if (bucket.length === 0) this._buckets.delete(hash);
return true;
}
i++;
}
if (bucket.length === 0) this._buckets.delete(hash);
return false;
}
}

View File

@@ -0,0 +1,54 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright (c) KALEIDOS INC
*/
"use strict";
export class WeakValueMap {
constructor() {
this._map = new Map(); // key -> {ref, token}
this._registry = new FinalizationRegistry((token) => {
this._map.delete(token.key);
});
}
set(key, value) {
const ref = new WeakRef(value);
const token = { key };
this._map.set(key, { ref, token });
this._registry.register(value, token, token);
return this;
}
get(key) {
const entry = this._map.get(key);
if (!entry) return undefined;
const value = entry.ref.deref();
if (value === undefined) {
// Value was GCd, clean up
this._map.delete(key);
return undefined;
}
return value;
}
has(key) {
const entry = this._map.get(key);
if (!entry) return false;
if (entry.ref.deref() === undefined) {
this._map.delete(key);
return false;
}
return true;
}
delete(key) {
const entry = this._map.get(key);
if (!entry) return false;
this._registry.unregister(entry.token);
return this._map.delete(key);
}
}

View File

@@ -1,29 +0,0 @@
;; 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.common.weak-map
"A value based weak-map implementation (CLJS/JS)")
(deftype ValueWeakMap [^js/Map data ^js/FinalizationRegistry registry]
Object
(clear [_]
(.clear data))
(delete [_ key]
(.delete data key))
(get [_ key]
(if-let [ref (.get data key)]
(.deref ^WeakRef ref)
nil))
(set [_ key val]
(.set data key (js/WeakRef. val))
(.register registry val key)
nil))
(defn create
[]
(let [data (js/Map.)
registry (js/FinalizationRegistry. #(.delete data %))]
(ValueWeakMap. data registry)))

View File

@@ -168,7 +168,7 @@
(gpt/point 20 65.2)
(gpt/point 12 -10)]
result (grc/points->rect points)
expect {:x -1, :y -10, :width 21, :height 75.2}]
expect {:x -1.0, :y -10.0, :width 21.0, :height 75.2}]
(t/is (= (:x expect) (:x result)))
(t/is (= (:y expect) (:y result)))

View File

@@ -12,6 +12,8 @@
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.tokens :as tht]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
[clojure.test :as t]))
@@ -47,7 +49,6 @@
(t/is (= (:parent-id frame-to-move) uuid/zero))
(t/is (= (:parent-id frame-to-move') (:id frame-parent')))))
(t/deftest test-relocate-shape-out-of-group
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
@@ -68,7 +69,6 @@
0 ;; to-index
#{(:id circle)}) ;; ids
file' (thf/apply-changes file changes)
;; ==== Get
@@ -81,4 +81,134 @@
(t/is (= (:parent-id circle) (:id group)))
(t/is (= (:parent-id circle') uuid/zero))
(t/is group)
(t/is (nil? group'))))
(t/is (nil? group'))))
(t/deftest test-relocate-shape-out-of-layout-manual
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-frame :frame-1
:layout :flex ;; TODO: those values come from main.data.workspace.shape_layout/default-layout-params
:layout-flex-dir :row ;; it should be good to use it directly, but first it should be moved to common.logic
:layout-gap-type :multiple
:layout-gap {:row-gap 0 :column-gap 0}
:layout-align-items :start
:layout-justify-content :start
:layout-align-content :stretch
:layout-wrap-type :nowrap
:layout-padding-type :simple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0})
(ths/add-sample-shape :circle-1 :parent-label :frame-1
:layout-item-margin {:m1 10 :m2 10 :m3 10 :m4 10}
:layout-item-margin-type :multiple
:layout-item-h-sizing :auto
:layout-item-v-sizing :auto
:layout-item-max-h 1000
:layout-item-min-h 100
:layout-item-max-w 2000
:layout-item-min-w 200
:layout-item-absolute false
:layout-item-z-index 10))
page (thf/current-page file)
circle (ths/get-shape file :circle-1)
;; ==== Action
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
uuid/zero ;; parent-id
0 ;; to-index
#{(:id circle)}) ;; ids
;; ==== Get
file' (thf/apply-changes file changes)
circle' (ths/get-shape file' :circle-1)]
;; ==== Check
;; the layout item attributes are removed
(t/is (nil? (:layout-item-margin circle')))
(t/is (nil? (:layout-item-margin-type circle')))
(t/is (nil? (:layout-item-h-sizing circle')))
(t/is (nil? (:layout-item-v-sizing circle')))
(t/is (nil? (:layout-item-max-h circle')))
(t/is (nil? (:layout-item-min-h circle')))
(t/is (nil? (:layout-item-max-w circle')))
(t/is (nil? (:layout-item-min-w circle')))
(t/is (nil? (:layout-item-absolute circle')))
(t/is (nil? (:layout-item-z-index circle')))))
(t/deftest test-relocate-shape-out-of-layout-with-tokens
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tht/add-tokens-lib)
(tht/update-tokens-lib #(-> %
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :name "test-theme"
:sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"})
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :id (thi/new-id! :token-sizing)
:name "token-sizing"
:type :sizing
:value 10))
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :id (thi/new-id! :token-spacing)
:name "token-spacing"
:type :spacing
:value 30))))
(tho/add-frame :frame-1
:layout :flex ;; TODO: those values come from main.data.workspace.shape_layout/default-layout-params
:layout-flex-dir :row ;; it should be good to use it directly, but first it should be moved to common.logic
:layout-gap-type :multiple
:layout-gap {:row-gap 0 :column-gap 0}
:layout-align-items :start
:layout-justify-content :start
:layout-align-content :stretch
:layout-wrap-type :nowrap
:layout-padding-type :simple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0})
(ths/add-sample-shape :circle-1 :parent-label :frame-1)
(tht/apply-token-to-shape :circle-1
"token-sizing"
[:layout-item-max-h :layout-item-max-w :layout-item-min-h :layout-item-min-w]
[:layout-item-max-h :layout-item-max-w :layout-item-min-h :layout-item-min-w]
10)
(tht/apply-token-to-shape :circle-1
"token-spacing"
[:m1 :m2 :m3 :m4]
[:layout-item-margin]
{:m1 30 :m2 30 :m3 30 :m4 30}))
page (thf/current-page file)
circle (ths/get-shape file :circle-1)
;; ==== Action
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
uuid/zero ;; parent-id
0 ;; to-index
#{(:id circle)}) ;; ids
;; ==== Get
file' (thf/apply-changes file changes)
circle' (ths/get-shape file' :circle-1)]
;; ==== Check
;; the layout item attributes and tokens are removed
(t/is (empty? (:applied-tokens circle')))
(t/is (nil? (:layout-item-margin circle')))
(t/is (nil? (:layout-item-margin-type circle')))
(t/is (nil? (:layout-item-h-sizing circle')))
(t/is (nil? (:layout-item-v-sizing circle')))
(t/is (nil? (:layout-item-max-h circle')))
(t/is (nil? (:layout-item-min-h circle')))
(t/is (nil? (:layout-item-max-w circle')))
(t/is (nil? (:layout-item-min-w circle')))
(t/is (nil? (:layout-item-absolute circle')))
(t/is (nil? (:layout-item-z-index circle')))))

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