Compare commits

...

473 Commits
2.5.3 ... 2.6.2

Author SHA1 Message Date
Andrey Antukh
c2b13a6d5d 📚 Update changelog 2025-04-29 14:46:15 +02:00
Andrey Antukh
6935d54870 Merge branch 'main' into staging 2025-04-28 08:43:54 +02:00
Andrey Antukh
65e8526ee2 Merge tag '2.6.2-RC4' 2025-04-28 08:43:04 +02:00
Andrés Moya
202762027f 🐛 Handle swapped nested instances when detaching 2025-04-25 10:00:14 +02:00
Andrés Moya
d95551e651 🔧 Add debug traces to detach copy operation 2025-04-25 10:00:14 +02:00
Xaviju
c96fbfdcd6 📚 Update tokens changelog for 2.6.2 (#6364)
Co-authored-by: Xavier Julian <xaviju@proton.me>
2025-04-24 13:29:56 +02:00
Alejandro Alonso
6e5d64d403 Merge pull request #6362 from penpot/niwinz-staging-bugfixes-2
🐛 Add migration for fix old broken root shapes (file migration)
2025-04-24 09:28:04 +02:00
Andrey Antukh
3e0c2bf1a1 🐛 Add migration for fix root shape 2025-04-24 09:17:33 +02:00
Andrey Antukh
283cdee5d6 Ensure consistency on using d/update-vals on file migrations 2025-04-24 08:55:54 +02:00
Andrey Antukh
ab5e01e54a Ensure we don't leave :components with nil on file data
after aplying migrations
2025-04-24 08:53:30 +02:00
Alejandro Alonso
373248e304 Merge pull request #6360 from penpot/niwinz-staging-bugfixes-2
🐛 Fix issues on file data migration handling
2025-04-24 07:30:50 +02:00
Andrey Antukh
80308ceafa 🐛 Make http cache aware of missing file data migrations 2025-04-23 18:15:33 +02:00
Andrey Antukh
f65518f865 🐛 Fix incorrect migration application after binfile import 2025-04-23 18:10:52 +02:00
Alejandro Alonso
f155042958 Merge pull request #6345 from penpot/niwinz-staging-add-interaction-cleaning
🐛 Add migration for decoding and cleaning shape interactions
2025-04-23 08:08:31 +02:00
Andrey Antukh
1dd23a3f47 🐛 Invalidate http cache on apply migrations to file on read operation 2025-04-23 07:57:56 +02:00
Andrey Antukh
1194e40222 🐛 Properly dispose rx subscription on grid thumbnail component 2025-04-22 21:39:57 +02:00
Andrey Antukh
05fac41534 🐛 Remove feature checking from get-file-data-for-thumbnail rpc method
The prev code has feature resolution race condition and it
in reallity does not need that check.
2025-04-22 21:38:40 +02:00
Andrey Antukh
3f85e89f62 🐛 Send frontend version on worker http requests 2025-04-22 21:26:51 +02:00
Alonso Torres
ee0f8ad19a 🐛 Fix horizontal scroll in viewer (#6347) 2025-04-22 21:03:45 +02:00
Andrey Antukh
b7d7cf233a Fix shadow colors on import penpot files 2025-04-22 19:58:10 +02:00
Alonso Torres
bd208c31e2 🐛 Fix update layout on component restore (#6348) 2025-04-22 18:46:21 +02:00
Andrey Antukh
151dc352c8 Don't register shadow schema
It is not really necessary, we can use the
schema var directly.
2025-04-22 17:21:52 +02:00
Andrey Antukh
ccbf17106d 🐛 Add migration for decoding and cleaning shape interactions 2025-04-22 15:04:22 +02:00
Andrey Antukh
95c4d95fd3 📎 Use d/update-vals instead of update-vals on migrations 2025-04-22 15:01:33 +02:00
Andrey Antukh
a72c07b657 Merge pull request #6309 from penpot/niwinz-staging-bugfixes-2
🐛 Several bugfixes
2025-04-22 09:15:02 +02:00
Andrey Antukh
708492afeb 💄 Add mainly cosmetic changes to dashboard placeholder components 2025-04-17 09:20:35 +02:00
Andrey Antukh
1305ab3cc6 🐛 Fix issue with empty placeholder on team change 2025-04-17 09:20:34 +02:00
Andrey Antukh
29cc6b4f9c Print the current seed on test.check fail 2025-04-17 09:20:34 +02:00
Andrey Antukh
cc7f0b145c 🐛 Make shape interaction properly decode on binfile import 2025-04-17 09:20:34 +02:00
Andrey Antukh
e69c0c3e27 Make schema uuid parsing fns private 2025-04-17 09:20:34 +02:00
Andrey Antukh
a209966427 🐛 Don't use schema uuid parsing function on websocket ns 2025-04-17 09:20:34 +02:00
Andrey Antukh
d5abbd4220 📎 Add missing entries on the changelog 2025-04-17 09:20:32 +02:00
Pablo Alba
70a23a14c4 🐛 Fix allow moving a main component into another 2025-04-16 22:54:30 +02:00
Marina López
93c81ea49c 🐛 Fix pricing CTA to be under a config flag (#6304) 2025-04-16 17:17:47 +02:00
Alejandro Alonso
ddc41027ab Merge pull request #6316 from penpot/palba-fix-instanciate-component
🐛 Fix error while drag an drop a component to the canvas
2025-04-16 13:15:07 +02:00
Pablo Alba
4f931fbe6a 🐛 Fix error while drag an drop a component to the canvas 2025-04-16 13:05:56 +02:00
Andrey Antukh
b49a4734ff 🐛 Fix srepl helper for restore file snapshots 2025-04-15 11:03:50 +02:00
Alejandro Alonso
2aaa2f3033 🐛 Fix template import (#6299) 2025-04-15 10:39:22 +02:00
Alejandro Alonso
202b9f3075 Merge pull request #6284 from penpot/niwinz-staging-several-bugfixes
🐛 Several bugfixes and enhacements
2025-04-15 10:33:59 +02:00
Andrey Antukh
be0814cdac Improve internal error reporting 2025-04-14 13:26:12 +02:00
Andrey Antukh
80d719353c Make auth data available before request parsing
For properly report profile-id
2025-04-14 09:23:41 +02:00
Andrey Antukh
fa3fc12594 Sanitize uuid on the rest of code 2025-04-14 09:23:29 +02:00
Andrey Antukh
422a9db07b Sanitize uuid parsing on legacy zip import code 2025-04-14 09:13:35 +02:00
Andrey Antukh
a4145a30f5 🐛 Fix uuid encode/decode on schema 2025-04-14 09:13:34 +02:00
andrés gonzález
e004671346 📚 Update Recommended storage info (#6275) 2025-04-11 14:02:35 +02:00
Andrey Antukh
38e5c161e7 Sanitize plugins uuid parsing 2025-04-11 13:21:26 +02:00
Andrey Antukh
a7c1f7ba69 🐛 Fix incorrect undo handling on path edition 2025-04-11 08:54:02 +02:00
Florian Schroedl
e9755d437e 🐛 Fix sets and set groups with same name cannot be renamed 2025-04-10 13:27:49 +02:00
Eva Marco
e5db66351e 🐛 Fix scroll on token themes modal (#6251)
* 🐛 Fix scroll on token themes modal

* 🐛 Fix collapse set group error
2025-04-10 10:25:08 +02:00
ºelhombretecla
89153eef23 🎉 Increase height presets dropdown (#6185)
* 🎉 Add new measures dropdown height

* 🎉 Add enhancement to CHANGES.md
2025-04-10 10:01:52 +02:00
Alejandro
b7a8677036 Merge pull request #6262 from penpot/niwinz-staging-clean-data
🐛 Clean workspace state on exit or url change
2025-04-10 08:38:55 +02:00
Andrey Antukh
9ff2160c77 🐛 Clean workspace state on exit or url change 2025-04-09 16:31:49 +02:00
Alejandro
4c77b32171 Merge pull request #6256 from penpot/niwinz-staging-fix-backend-tests
📎 Fix backend tests
2025-04-09 13:52:30 +02:00
Andrey Antukh
34141ce9af 📎 Fix backend tests
Caused by update of image procesing libraries on the devenv docker
image update from debian to ubuntu
2025-04-09 13:37:52 +02:00
Alejandro
58c867885c Merge pull request #6250 from penpot/alotor-bug-colorpicker
🐛 Fix colorpicker scroll when dropdown displayed
2025-04-09 12:51:17 +02:00
alonso.torres
ccb6e25914 🐛 Fix colorpicker scroll when dropdown displayed 2025-04-09 12:50:55 +02:00
Alejandro
965d2d4036 🐛 Fix webhooks not shown in list (#6254) 2025-04-09 12:46:00 +02:00
Yamila Moreno
9f8d7c9e41 🐳 Improve https documentation 2025-04-09 12:24:43 +02:00
Andrey Antukh
8d352c1f82 Merge branch 'main' into staging 2025-04-09 10:59:37 +02:00
Andrey Antukh
faead09174 Merge tag '2.6.0' 2025-04-09 10:58:39 +02:00
Yamila Moreno
ae3ce1220b 🐳 Improve https documentation 2025-04-09 10:05:18 +02:00
Andrey Antukh
6e3673136a 📎 Update changelog 2025-04-09 09:19:05 +02:00
Yamila Moreno
28caa1d47d 🐛 Fix docker-compose.yaml (#6236) 2025-04-07 16:29:47 +02:00
Andrey Antukh
ea6f0abf7c 🐛 Fix regresion on features calculate method on workspace load 2025-04-07 14:32:48 +02:00
Andrey Antukh
45cdfff128 🐛 Fix backend notifications on dashboard 2025-04-07 14:00:26 +02:00
Marina López
8c38e41261 🎉 Consolidate first state of a project (#6150) 2025-04-07 13:15:32 +02:00
Alejandro
3197dfddd9 Merge pull request #6234 from penpot/niwinz-staging-bugfixes-3
🐛 Several bugfixes and backports
2025-04-07 11:15:32 +02:00
Andrey Antukh
d900516302 Merge branch 'main' into staging 2025-04-07 09:59:27 +02:00
Andrey Antukh
fa68a25bea Merge branch 'warrenjokinen-patch-1' 2025-04-07 09:59:08 +02:00
warrenjokinen
2cc2d34719 📚 Update shortcuts.njk (docs)
minor typo
2025-04-07 09:57:05 +02:00
Andrey Antukh
4640d043e3 ⬆️ Update yarn 2025-04-07 09:21:56 +02:00
Andrey Antukh
bc957893f4 Make feature resolved on team load
That simplifies features retrieval to simple get
2025-04-07 07:50:40 +02:00
Andrey Antukh
b8107ee497 Ensure workspace page loading and intialization process 2025-04-07 07:42:09 +02:00
Andrey Antukh
6b3a988526 Send version and build data to worker configuration 2025-04-07 07:10:40 +02:00
Andrey Antukh
5cb39874a2 Add better error hints on auth ns 2025-04-07 07:10:40 +02:00
Marina López
9fc671cc17 🐛 Fix wrong path to list all icons in storybook 2025-04-04 10:36:51 +02:00
Pablo Alba
3fb3b45fdc Merge pull request #6219 from penpot/niwinz-staging-bugfixes-2
🐛 Several bugfixes and enhancements
2025-04-03 15:49:09 +02:00
Andrey Antukh
0816adbaec Send ws messages in verbose format when on development build 2025-04-03 11:40:40 +02:00
Andrey Antukh
1d69941882 🐛 Fix backend notification dialogs 2025-04-03 11:40:40 +02:00
Andrey Antukh
8f600f334f 🐛 Make accept and cancel handlers optional on actionable* 2025-04-03 11:21:02 +02:00
Andrey Antukh
cf55d12991 📚 Add better docstring for srepl.main/notify! helper 2025-04-03 11:21:02 +02:00
Andrey Antukh
78919df886 🐛 Fix incorrect topic sending on internal srepl notify helper 2025-04-03 10:58:10 +02:00
Andrés Moya
5d600c6715 Change behavior of single set json file import to be coherent (#6211) 2025-04-02 09:41:12 +02:00
Andrey Antukh
ea031a2161 Merge pull request #6210 from penpot/niwinz-staging-bugfixes
🐛 Several bugfixes
2025-04-02 09:19:57 +02:00
Andrey Antukh
4d4a04e9aa Add minor enhacement for error reporting 2025-04-01 20:43:55 +02:00
Andrey Antukh
3ec797f56e 🐛 Validate and decode params on export-binfile 2025-04-01 19:16:35 +02:00
Eva Marco
74f11859e4 🐛 Fix max lenght on assets inputs (#6201) 2025-04-01 13:28:35 +02:00
Andrey Antukh
47f80cf3db 🐛 Make error middleware capture profile-id 2025-04-01 12:30:51 +02:00
Andrey Fedorov
a20dd3f955 Fix single set import 2025-04-01 10:57:17 +02:00
Andrey Antukh
982118c942 🐳 Update devenv corepack setup 2025-04-01 10:47:49 +02:00
Andrey Antukh
a51feb8638 📎 Update changelog 2025-04-01 10:47:13 +02:00
Yamila Moreno
9663964790 🐳 Make traefik example easier (#6198) 2025-04-01 09:34:46 +02:00
Andrés Moya
2c0e18ce1c 🐛 Fix sync of margin and padding tokens in components 2025-03-31 16:19:45 +02:00
Eva Marco
89876ef96f 🐛 Fix UI with long named colors (#6193) 2025-03-31 15:33:30 +02:00
Xavier Julian
c259b8ed46 🐛 Fix overflow on tokens sidebar 2025-03-28 23:45:01 +01:00
Xavier Julian
b1df0ac194 Add a default 256 maxlength value to all input fields 2025-03-28 23:45:01 +01:00
Eva Marco
c1853a71a9 🐛 Fix available resize area (#6186) 2025-03-28 13:15:35 +01:00
Eva Marco
cbb3f6672f 🐛 Fix asset name on inspect tab (#6173)
Signed-off-by: Eva Marco <eva.marco@kaleidos.net>
2025-03-28 10:38:35 +01:00
Alejandro
cc97a8ffcc Merge pull request #6158 from penpot/niwinz-staging-task-result
🐛 Fix incorrect task result handling
2025-03-28 09:43:11 +01:00
ºelhombretecla
535e8653a0 🎉 Add slides for version 2.6 (#6176) 2025-03-28 09:42:09 +01:00
Andrey Antukh
210e5b0023 🐛 Fix incorrect task result handling
That caused that many task rows in a table not properly marked
as completed and leaved just as scheduled.
2025-03-28 09:10:46 +01:00
Andrés Moya
6a87d5eea9 🐛 Rewrite active tokens calculation algorithm (#6165) 2025-03-27 15:53:17 +00:00
Alejandro Alonso
237d9d067d Merge remote-tracking branch 'origin/main' into staging 2025-03-27 12:12:37 +01:00
Alejandro
6519db82d1 Merge pull request #6171 from penpot/mavalroot-install-plugin-bug
🐛 Fix plugin installation error by penpot hub
2025-03-27 12:12:19 +01:00
María Valderrama
0a60cbedb5 🐛 Fix plugin installation error by penpot hub 2025-03-27 11:57:23 +01:00
Eva Marco
d7c709607d 🐛 Fix line height on token pills (#6164) 2025-03-26 13:46:55 +01:00
Andrey Antukh
bb7301fb63 Improve libraries loading on workspace (#6141)
*  Improve libraries loading on workspace

*  Add improvements to CSS

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
2025-03-26 13:19:48 +01:00
Eva Marco
2918c57fb8 🐛 Show broken pills when all sets are disabled (#6161) 2025-03-26 13:13:45 +01:00
Eva Marco
f55e0bf6e3 🐛 Eva Fix context menu for viewer role (#6159) 2025-03-26 12:50:23 +01:00
andrés gonzález
3d16fa6f19 📚 Add Design Tokens documentation (#6026)
* 📚 Add Design Tokens documentation

* 📚 Update docs/user-guide/design-tokens/index.njk

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>

* 📚 Update docs/user-guide/design-tokens/index.njk

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>

* 📚 Update docs/user-guide/design-tokens/index.njk

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>

* 📚 Update docs/user-guide/design-tokens/index.njk

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>

* 📚 Update docs/user-guide/design-tokens/index.njk

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>

* 📚 Changing several things after the PR review

---------

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>
2025-03-26 10:51:17 +01:00
Yamila Moreno
c65c4270c3 Merge pull request #6128 from orhtej2/patch-1
📚 Add YunoHost as a self-hosting option
2025-03-26 10:17:51 +01:00
Andrey Antukh
0099c282b6 🐛 Fix tokens set reordering corner case 2025-03-26 09:06:54 +01:00
Andrés Moya
9115e1a3a3 🐛 Fix resolved value when opening the token edit form 2025-03-25 17:12:04 +01:00
ºelhombretecla
a7044c73ba 🐛 Fix libraries carrousel styles (#6140) 2025-03-25 15:51:34 +01:00
luisδμ
dc84ab3e41 🐛 Fix calculate zoom to avoid bubbles to get outside viewbox (#6138)
* 🐛 Fix calculate zoom to avoid bubbles to get outside vbox

* 📎 PR changes
2025-03-25 15:39:47 +01:00
Eva Marco
e81adb241b 🐛 Add underscore as posible name character (#6135) 2025-03-25 10:57:23 +01:00
Marina López
a6133e9c48 🐛 Fix actions when workspace is visited first time (#6129)
* 🐛 Fix actions when workspace is visited first time

* 📎 Fix linter errors

* 🐛 Fix problem with integration test

* 📎 Fix linter errors

* 📎 Fix linter errors

---------

Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2025-03-24 18:02:05 +01:00
Eva Marco
7bc000517f 🐛 Fix modal position when colopicker is open (#6139) 2025-03-24 16:10:16 +01:00
Xavier Julian
95d9403790 Resize tokens panel on resize 2025-03-24 13:59:51 +01:00
Alejandro
5d66eedcc7 Merge pull request #6134 from penpot/azazeln28-fix-render-wasm-build-env
🐛 Fix _build_env release EMCC_CFLAGS
2025-03-24 10:28:28 +01:00
Aitor Moreno
974d43cb08 🐛 Fix _build_env release EMCC_CFLAGS 2025-03-24 10:17:16 +01:00
Eva Marco
752f74767e 🐛 Fix error copy (#6132) 2025-03-21 10:47:32 +01:00
Andrey Antukh
e5319e04c7 ♻️ Fix naming on token-set group move change operation 2025-03-21 10:23:27 +01:00
Andrey Antukh
e8e9037ef1 🐛 Fix inconsistencies on parsing tokens dtcg json 2025-03-21 10:23:27 +01:00
Andrey Antukh
c6bfae0d63 🐛 Normalize set names on importing themes dtcg json 2025-03-21 10:23:27 +01:00
Andrey Antukh
93bf198073 🐛 Prevent theme replacement on ranaming 2025-03-21 09:22:16 +01:00
orhtej2
4be8d77a79 📚 Update unofficial-options.md
Signed-off-by: Mateusz Szymański <2871798+orhtej2@users.noreply.github.com>
2025-03-20 23:59:30 +01:00
Alejandro
9fb7456b38 Merge pull request #6111 from penpot/superalex-fix-pen-shortcut-multiple-times
🐛 Fix opening pen with shortcut multiple times breaks toolbar
2025-03-20 18:05:35 +01:00
Eva Marco
b3a3cca9fe 🐛 Fix stroke width validation (#6124) 2025-03-20 17:35:19 +01:00
andrés gonzález
f98009ec54 📚 Add Design Tokens to the Changelog (#6112) 2025-03-20 16:39:14 +01:00
Andrey Antukh
669533cae6 Merge pull request #6115 from penpot/niwinz-staging-tokens-1
🐛 Fix incorrect absolute frame positioning with measures sidebar
2025-03-20 13:20:08 +01:00
Andrey Antukh
d6efd469e4 🎉 Make the design tokens feature enabled by default 2025-03-20 12:22:37 +01:00
Andrey Antukh
0d4a6fc75f 🐛 Clear selected token set on leave file on workspace 2025-03-20 12:22:37 +01:00
Andrey Antukh
e403194bba 💄 Remove incorrect use of rx/concat on update-shape-position 2025-03-20 12:22:37 +01:00
Andrey Antukh
b8c5a10551 💄 Add minor cosmetic changes to measures menu 2025-03-20 12:22:37 +01:00
Andrey Antukh
7fdb0873db 🐛 Fix incorrect absolute frame positioning with measures sidebar 2025-03-20 12:22:37 +01:00
Xavier Julian
68a89556d6 🐛 Add tooltip to empty sets button on theme creation modal 2025-03-20 10:18:28 +01:00
Alejandro Alonso
1a77c1fe36 🐛 Fix opening pen with shortcut multiple times breaks toolbar 2025-03-20 09:19:27 +01:00
Aitor Moreno
4aa1bb7246 Merge pull request #6116 from penpot/alotor-fix-group-constraints
🐛 Fix problem with constraints when creating group
2025-03-19 16:43:54 +01:00
alonso.torres
3a80120bf6 🐛 Fix problem with constraints when creating group 2025-03-19 16:33:50 +01:00
Aitor Moreno
d01eccf912 Merge pull request #6114 from penpot/alotor-fix-plugins-shapows
🐛 Fix problem with default shadows in plugins
2025-03-19 16:15:41 +01:00
alonso.torres
25621f8deb 🐛 Fix problem with default shadows in plugins 2025-03-19 14:51:39 +01:00
Yamila Moreno
dc006bd7f2 Merge pull request #6108 from penpot/yms-improve-self-host-documentation
📚 Improve self host documentation
2025-03-19 14:05:22 +01:00
Eva Marco
629f09089b 🐛 Fix tooltip with only one set and no active (#6107) 2025-03-19 13:54:00 +01:00
Andrey Antukh
344ec94a3f Merge pull request #6109 from penpot/superalex-fix-hovering-dashboard-templates
🐛 FIx hovering dashboard templates
2025-03-19 13:42:04 +01:00
Andrey Antukh
62e89258e4 Merge pull request #6101 from penpot/niwinz-develop-token-fixes-4
 Add several improvements to tokens (part 4)
2025-03-19 13:38:46 +01:00
Andrey Antukh
b6bb93f0b6 Improve code convetion related to changes protocol
Partial work, still pending to make changes to other related
changes definitions
2025-03-19 12:52:03 +01:00
Andrey Antukh
39a1d5cc89 🐛 Fix set unexpected deletion on reordering 2025-03-19 12:42:05 +01:00
Andrey Antukh
8fa24de3d4 Merge pull request #6096 from penpot/niwinz-develop-token-fixes-3
 Add several improvements and fixes to tokens (part 3)
2025-03-19 12:30:05 +01:00
Alejandro Alonso
1def5015fb 🐛 FIx hovering dashboard templates 2025-03-19 12:27:13 +01:00
Yamila Moreno
1dbc924d31 📚 Remove docker installation in favour of the official documentation 2025-03-19 12:19:07 +01:00
Yamila Moreno
95da007107 📚 Add a warning about technical knowledge 2025-03-19 12:18:04 +01:00
Alejandro Alonso
633a7eac4e Merge remote-tracking branch 'origin/staging' into develop 2025-03-19 09:47:32 +01:00
Alejandro
357fba5d2b 🐛 Fix selected team not saved (#6104) 2025-03-19 09:46:08 +01:00
Alejandro
b727f2fe1f Merge pull request #6077 from penpot/elenatorro-10516-fix-shadow-rendering
🐛 Fix drop shadows viewport clipping
2025-03-19 08:48:03 +01:00
Andrey Antukh
4453eec687 Persist migrated files on srepl process-file helper 2025-03-18 17:57:52 +01:00
Andrey Antukh
c169eef161 ♻️ Remove tokens lib migrations from file migrations 2025-03-18 17:57:52 +01:00
Eva Marco
17af55d3c8 🐛 Fix lost resolved value on tooltip (#6102) 2025-03-18 17:13:45 +01:00
Andrey Antukh
8df12e5e9c Remove state assignation round-trip on update-dimensions event
Using the lower-level apply-modifiers event, introduced in previous
commit
2025-03-18 16:19:55 +01:00
Andrey Antukh
cd423f23c6 Remove get-hidden-theme from tokens lib protocol 2025-03-18 16:19:55 +01:00
Andrey Antukh
86c2c4cd41 ♻️ Add lower-level impl of apply-modifiers event 2025-03-18 16:19:55 +01:00
Andrey Antukh
d9c4fc3721 Calculate uuid lazily on creating token theme 2025-03-18 16:19:55 +01:00
Andrey Antukh
b91e72d8a1 🐛 Fix typo 2025-03-18 16:19:55 +01:00
Andrey Antukh
6cc96ef679 Add logging for tokens update event operation 2025-03-18 16:19:55 +01:00
Andrey Antukh
28fe951c40 ♻️ Replace usage of dm/assert on several namespaces
And remove the `!` from the name on check functions
2025-03-18 16:19:55 +01:00
Andrey Antukh
22f789e77c Don't put timeout on tokens changes transaction 2025-03-18 16:19:55 +01:00
Eva Marco
2e5138eddc 🐛 Fix error message on invalid json (#6099) 2025-03-18 16:19:17 +01:00
Elena Torro
731c21f082 🐛 Fix drop shadows viewport clipping 2025-03-18 15:56:43 +01:00
Xavier Julian
99d7672284 Validate token name while typing 2025-03-18 14:14:16 +01:00
Andrés Moya
567fdd9619 🐛 Fix initial value of color bullet in form 2025-03-18 12:04:24 +01:00
Yamila Moreno
6067e438a3 📚 Document auto file snapshot (#6085) 2025-03-18 11:40:58 +01:00
Eva Marco
fc17a1742a 🐛 Fix copy on input placeholder (#6097) 2025-03-18 11:30:40 +01:00
Eva Marco
f7f1598e71 🐛 Fix select same set after rename (#6095) 2025-03-18 10:55:44 +01:00
Eva Marco
8caf559a1a 🐛 Fix resolved value on tooltip (#6084) 2025-03-18 10:54:43 +01:00
Elena Torró
e927161ec1 Merge pull request #6066 from penpot/elenatorro-10387-test-emoji-rendering
 Support emoji default font in text rendering
2025-03-18 10:50:20 +01:00
Elena Torro
ba387a892f Support emoji default font in text rendering 2025-03-18 10:33:27 +01:00
Alejandro Alonso
18015bde4f Merge remote-tracking branch 'origin/staging' into develop 2025-03-18 10:14:16 +01:00
Alejandro Alonso
8affefbbab 📎 Update changelog 2025-03-18 10:13:12 +01:00
jdo-odoo
0225919a45 📚 Fix typos in shortcut and insert image section
Signed-off-by: jdo-odoo <108932862+jdo-odoo@users.noreply.github.com>
2025-03-18 10:09:58 +01:00
Pablo Alba
0901807db8 Update inspect-title-bar and copy-button to the new components format 2025-03-18 09:52:32 +01:00
Pablo Alba
625cbfc50a 🎉 Add variants properties to inspect panel 2025-03-18 09:52:32 +01:00
Eva Marco
b2bc5aff68 🐛 Add lost spanish translation (#6089) 2025-03-18 09:25:35 +01:00
Eva Marco
337c61db2c 🐛 Fix not active sets on json import (#6087) 2025-03-18 09:14:30 +01:00
Andrey Antukh
5c2c96fc2e Merge pull request #6086 from penpot/niwinz-develop-token-fixes-2
 Several bugfixes related to tokens (part 2)
2025-03-17 15:50:33 +01:00
Andrey Antukh
04c77a8532 🔥 Remove unused double token resolve operation on sidebar 2025-03-17 14:56:55 +01:00
Andrey Antukh
ebcf5b3177 🐛 Avoid theme overwrite on creating a theme with existing name 2025-03-17 14:56:55 +01:00
Andrey Antukh
9d2117e2ac 📎 Replace use-callback with use-fn on token themes modal 2025-03-17 14:56:55 +01:00
Andrey Antukh
c1c22dc6c6 Merge pull request #6075 from penpot/niwinz-develop-token-fixes-1
 Add several fixes and improvements to tokens
2025-03-17 14:47:21 +01:00
Andrey Antukh
1e10e3818e 🔥 Remove not necessary API from tokens-lib: add-sets 2025-03-17 14:24:55 +01:00
Andrey Antukh
802c67ace4 🔥 Remove unused API from tokens-lib
Removes the protocol method: `get-set-prefixed-path-string`
2025-03-17 14:24:54 +01:00
Andrey Antukh
5c3709b5d8 🔥 Remove unused API from tokens-lib
Removes the protocol method: `get-tokens-tree`
2025-03-17 14:24:54 +01:00
Andrey Antukh
626c65df02 🔥 Remove unnecesary API from tokens lib
Removes the `get-dtcg-tokens-tree` protocol method
2025-03-17 14:24:54 +01:00
Andrey Antukh
f2f492bf3f 🔥 Remove commented code 2025-03-17 14:24:54 +01:00
Andrey Antukh
40f69d320e Show proper toast message on token-set rename error 2025-03-17 14:24:52 +01:00
Andrey Antukh
1893cd306a Improve translation strings for token set drop errors 2025-03-17 14:24:33 +01:00
Andrey Antukh
096b685e2c 🐛 Prevent token-set overwrite on creation and edition 2025-03-17 14:24:02 +01:00
Andrey Antukh
1965490bee 📎 Use proper catch matching on tokens drop operation 2025-03-17 14:24:02 +01:00
Andrey Antukh
559dcabf0e Normalize token name on creation 2025-03-17 14:24:02 +01:00
Andrey Antukh
a9e8d8f8f7 Make the calculate-move-token-set-or-set-group fn private 2025-03-17 14:24:02 +01:00
Alejandro Alonso
dba67eea91 Merge remote-tracking branch 'origin/staging' into develop 2025-03-17 12:52:56 +01:00
Andrey Antukh
70fe6fda83 🔥 Remove unused code 2025-03-17 12:27:20 +01:00
luisδμ
5155cf2b23 🐛 Fix clicking on a comment at the viewer's sidebar is not opening threads (#6083) 2025-03-17 12:25:23 +01:00
elhombretecla
5ca9b95cca 🎉 Adds tips to the file loading screen (#6063)
* 🎉 Add tips to the default loader component

* 📎 Use simplier approach for show tips

This commit also fixes other minor issues

* 📎 Extract to constants the loader path data

* 📎 Use properly tracked translations for loader tips

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-03-17 12:24:44 +01:00
Alonso Torres
fa0da3a695 Flex layout modifiers wasm implementation
*  Flex layout modifiers wasm implementation

*  Flex auto modifiers propagation
2025-03-17 10:46:32 +01:00
Xavier Julian
fa9d8a9b15 Add a tooltip explanation on themes modal sets 2025-03-17 10:21:50 +01:00
Andrey Antukh
7403f60366 Merge pull request #6076 from penpot/alotor-fix-problem-inspect
🐛 Fix problem with readonly and inspect
2025-03-14 15:50:01 +01:00
Alejandro Alonso
66295b0adf Merge remote-tracking branch 'origin/staging' into develop 2025-03-14 12:55:44 +01:00
Belén Albeza
eb6d2fb0eb 🎉 Store custom fonts (ttfs) and use them to write texts (wasm) (#6050) 2025-03-14 12:45:15 +01:00
Alejandro
a8c34ccc1a Merge pull request #6070 from penpot/alotor-bugfix-grid-layout
🐛 Fix problem with grid component propagation
2025-03-14 12:39:54 +01:00
alonso.torres
8c501db2fa 🐛 Fix problem with readonly and inspect 2025-03-14 12:24:34 +01:00
alonso.torres
d2fbb9dfa7 🐛 Fix problem with grid component propagation 2025-03-14 11:50:55 +01:00
Andrey Antukh
e4c9b736f7 Merge remote-tracking branch 'origin/staging' into develop 2025-03-14 11:19:47 +01:00
Andrey Antukh
f02f446015 Merge pull request #6071 from penpot/xaviju-10527-tooltip-import-warning
 Add a warning tooltip over import tokens button
2025-03-14 10:00:18 +01:00
Andrey Antukh
05d6d2fcd4 🐛 Fix several corner cases that causes race conditions on workspace and dashboard loading
* 🐛 Fix several race conditions on workspace and dashboard code

It also fixes a corner case that happens when penpot workspace
is loaded in a background tab on firefox.

* 🐛 Add missing team-id prop to several file returning endpoints
2025-03-14 09:55:41 +01:00
Andrey Antukh
d5492442fb Merge pull request #6068 from penpot/alotor-fix-readonly
🐛 Fix problem with readonly and inspect
2025-03-14 09:34:30 +01:00
Andrey Antukh
61800d8945 Merge pull request #6074 from penpot/dfelinto-export-webp
🎉 Add support for WEBP format for shape export
2025-03-13 16:24:55 +01:00
Dalai Felinto
f450c9dbe3 🎉 Add support for WEBP format on shape export
It is very convenient to be able to export WEBP right from penpot.
Otherwise users have to first download to PNG then convert it locally.

---

Playwright only supports JPEG and PNG. So in order to support WEBP I had
to first generate a PNG and then convert it afterwards.

Signed-off-by: Dalai Felinto <dalai@blender.org>
2025-03-13 16:15:30 +01:00
Xavier Julian
b46574bef6 Add a warning tooltip over import tokens button 2025-03-13 13:26:41 +01:00
Andrey Antukh
e3b3fa3342 📎 Update changelog 2025-03-13 09:32:19 +01:00
Eva Marco
21b2c0c26a 🐛 Fix token unset when flex layout is applied 2025-03-12 20:03:50 +01:00
alonso.torres
dcbf54fae1 🐛 Fix problem with readonly and inspect 2025-03-12 16:39:31 +01:00
Eva Marco
2fe6fb28e4 🐛 Fix tooltip update when set changes (#6058) 2025-03-12 15:49:07 +01:00
luisδμ
86022a967c Replace overlapping bubbles with a bubble group (#6059) 2025-03-12 14:37:39 +01:00
Eva Marco
0efbebd94f 🎉 Avoid setting token to group shapes (#6055)
* 🎉 Avoid setting token to group shapes

* 📎 Fix on update shape when double click
2025-03-12 14:18:59 +01:00
Marina López
2aee2ea79e 📚 Add US in changes.md 2025-03-12 14:03:50 +01:00
elhombretecla
60a20b6984 Add new toggle and translations 2025-03-12 14:03:50 +01:00
elhombretecla
fd753fb262 💄 Add new css styles 2025-03-12 14:03:50 +01:00
elhombretecla
0ae57a017e Add new carrousel layout 2025-03-12 14:03:50 +01:00
Alejandro
88772a9ced Merge pull request #6061 from penpot/alotor-bug-fills
🐛 Fix problem adding fill
2025-03-12 12:31:43 +01:00
Alejandro Alonso
65647f4aae Merge remote-tracking branch 'origin/staging' into develop 2025-03-12 12:30:11 +01:00
alonso.torres
5e6ccc44fc 🐛 Fix problem adding fill 2025-03-12 10:22:04 +01:00
Andrei Fëdorov
b9df8ad038 ♻️ Simplifies RPC pattern for token themes (#6052)
* ♻️ Add set token theme method schema

* ♻️ Add `:set-token-theme` dispatcher for `process-change` multimenthod

* ♻️ Add `set-token-theme` to the changes builder

* ♻️ Use new method on the frontend

* ♻️ Remove unused token theme methods

* ♻️ Add tests

* ♻️ Add library data to changes

* ♻️ Add new test case

* ♻️ Remove unused binding
2025-03-12 09:29:03 +01:00
Eva Marco
474cd1e55a 🐛 Fix migration keyword (#6057) 2025-03-11 16:19:51 +01:00
Andrei Fëdorov
b52e8bc87c ♻️ Simplifies RPC pattern for token sets (#6045)
* ♻️ Add set removal methods to tokens library

* ♻️ Add `set-token-set` method to changes

* ♻️ Add `set-token-set` to changes builder

* ♻️ Use new method in the token set creation

* ♻️ Use `set-token-set` in frontend events

* ♻️ Remove unused binding

* ♻️ Add tests

* ♻️ Remove old API methods

* ♻️ Remove unused parts of schema and multimethods

* ♻️ Make `:tokens` key optional in schema

* ♻️ Add `with-library-data` calls before `set-token-set`

* ♻️ Fix DOM properties error
2025-03-11 16:03:52 +01:00
Elena Torró
f35723e772 Merge pull request #6048 from penpot/elenatorro-10448-fix-clipping-over-groups
🐛 Fix children clip bounds inheritance
2025-03-11 12:06:40 +01:00
Pablo Alba
415d1a2668 Merge pull request #6032 from penpot/palba-variants-create-with-path
 Create variant from component with path
2025-03-11 11:40:31 +01:00
Alejandro Alonso
b3feb9bffd Merge remote-tracking branch 'origin/staging' into develop 2025-03-11 10:00:17 +01:00
Alejandro
dfbf0d34b6 Merge pull request #6036 from penpot/elenatorro-10427-update-rust-last-version
🔧 Upgrade rust to v1.85
2025-03-11 08:40:33 +01:00
Eva Marco
0c3fd8a6d9 🎉 Add id to token theme (#6044)
* 🎉 Add id to token theme

* 📎 Fix tests

* 📎 Fixes from review
2025-03-10 18:38:52 +01:00
elenatorro
5b9dd96e02 🐛 Fix children clip bounds inheritance 2025-03-10 15:55:43 +01:00
Xavier Julian
46c89a1bcf Fix stories and add default state to toast 2025-03-10 14:43:41 +01:00
Xavier Julian
721760d679 Add a default appearance to context notifications 2025-03-10 14:43:41 +01:00
elenatorro
2cdb874484 🔧 Upgrade rust to v1.85 2025-03-10 13:33:12 +01:00
Andrés Moya
e5bccc470b Validate if token values are too large 2025-03-10 13:32:16 +01:00
Andrey Fedorov
ba768f8744 🐛 Fix Ci tests for shape proxy in plugin runtime 2025-03-10 13:29:10 +01:00
Andrey Fedorov
a33828467f ♻️ Rename bindings 2025-03-10 13:29:10 +01:00
Andrey Fedorov
6fed0f3b58 ♻️ Remove unused bidings and requirements 2025-03-10 13:29:10 +01:00
Andrey Fedorov
5f3599eaa7 🐛 Fix token dimension application for all relatively positioned shapes 2025-03-10 13:29:10 +01:00
Andrey Fedorov
44ca01aa27 🐛 Fix relative position application for flex children 2025-03-10 13:29:10 +01:00
Xavier Julian
451306f719 Add a maxlenght to input CRUD tokens 2025-03-10 13:25:07 +01:00
Elena Torró
29518f3ba5 Merge pull request #6042 from penpot/elenatorro-10436-fix-rounded-corners-for-images
🐛 Fix rounded corners in image fill
2025-03-10 12:49:50 +01:00
Elena Torró
d74bfd834d Merge pull request #6035 from penpot/elenatorro-10314-allow-mutable-static-only-on-state
🔧 Use with_state and with_current_state macros allowing static…
2025-03-10 12:42:47 +01:00
elenatorro
ac8b5a7bcc 🐛 Fix rounded corners in image fill 2025-03-10 12:16:41 +01:00
Xavier Julian
390cf6b642 Recalculate token context-menu submenu position 2025-03-07 19:14:07 +01:00
elenatorro
0dbf00a767 🔧 Use with_state and with_current_state macros allowing static_mut_refs only on STATE 2025-03-07 15:24:04 +01:00
Elena Torró
a361e0b990 Merge pull request #5992 from penpot/elenatorro-10314-use-mutex-for-static-mut
🔧 Do not use global static mut variables when possible
2025-03-07 15:20:05 +01:00
elenatorro
3a8ba4cbee 🔧 Avoid using global static mut variables when possible 2025-03-07 15:07:31 +01:00
Elena Torró
d5c9e68a3e 🔧 Update pull request GitHub template (#6022) 2025-03-07 14:45:29 +01:00
Alejandro
253d94c176 Merge pull request #6031 from penpot/fix-problem-flex-reverse
🐛 Fix problem with reverse config in flex
2025-03-07 14:08:25 +01:00
Pablo Alba
fd941e4701 🎉 Update package.json files 2025-03-07 13:59:20 +01:00
Pablo Alba
a99198de48 Filter variants on asset panel 2025-03-07 13:43:04 +01:00
Eva Marco
e729e85c42 ♻️ Create hidden theme on token lib creation (#5991)
* ♻️ Create hidden theme on token lib creation

* 📎 Fix tests

* 🎉 Add migration

* 🎉 Remove comment

* ♻️ Remove unused changes

* 📎 Remove unused fn
2025-03-07 13:17:36 +01:00
Pablo Alba
7eb9325047 Create variant from component with path 2025-03-07 09:54:38 +01:00
alonso.torres
ba4554da79 🐛 Fix problem with reverse config in flex 2025-03-07 09:23:07 +01:00
Alejandro
97fb1e00c2 Merge pull request #6028 from penpot/palba-fix-toolbar-tooltips
🐛 Fix hidden toolbar click event still available
2025-03-07 08:08:06 +01:00
Pablo Alba
3eb332f3d0 🐛 Fix hidden toolbar click event still available 2025-03-07 07:57:04 +01:00
Alejandro Alonso
0d11bafb57 Merge remote-tracking branch 'origin/staging' into develop 2025-03-07 07:54:05 +01:00
alonso.torres
e01dfd76e8 Merge remote-tracking branch 'origin/staging' into develop 2025-03-06 16:05:12 +01:00
Alejandro
854145e435 Merge pull request #6024 from penpot/fix-resize-problem
🐛 Fix problem with new render resize
2025-03-06 12:33:09 +01:00
alonso.torres
707bfd4241 🐛 Fix problem with new render resize 2025-03-06 12:20:56 +01:00
Andrés Moya
02bc6e62e7 🔧 Use a more correct selector in one test 2025-03-06 10:36:10 +01:00
Andrés Moya
9fde4e2121 🐛 Unapply token when manually changing layout margin and padding 2025-03-06 10:07:59 +01:00
Elena Torró
66eb4fb5ad Merge pull request #6003 from penpot/superalex-update-rust-skia-version
 Update rust skia version to 0.81.0
2025-03-06 09:30:57 +01:00
Alejandro Alonso
e362f423c0 Merge remote-tracking branch 'origin/staging' into develop 2025-03-06 07:36:19 +01:00
Alejandro
dc08eb7899 Merge pull request #6014 from penpot/marina-plugins-list-lacks-scrolling
🐛 Add scroll to plugins menu list
2025-03-06 07:27:34 +01:00
Marina López
a1e307b4ce 🐛 Add scroll to plugins menu list 2025-03-06 07:26:36 +01:00
Alejandro
a0f16fc038 Merge pull request #6013 from penpot/palba-fix-duplicate-page
🐛 Fix duplicate page with component over frame
2025-03-06 07:25:54 +01:00
Pablo Alba
7c36c76b0d 🐛 Fix duplicate page with component over frame 2025-03-06 07:17:46 +01:00
Alejandro
8488be311e Merge pull request #6011 from penpot/palba-fix-cut-paste-copy
🐛 Fix cut and paste a copy inside its parent
2025-03-06 07:16:51 +01:00
Pablo Alba
2297862d81 🐛 Fix cut and paste a copy inside its parent 2025-03-06 07:08:37 +01:00
Alejandro
539fdfa016 Merge pull request #6010 from penpot/marina-dashboard-sidebar-horizontal-scroll
🐛 Hide horizontal scroll from dashboard sidebar
2025-03-06 07:07:58 +01:00
Marina López
a7f6797499 🐛 Hide horizontal scroll from dashboard sidebar 2025-03-06 07:00:14 +01:00
Alejandro
cc7f745a0a Merge pull request #5966 from penpot/alotor-bug-click-resize
🐛 Fix problem resizing on click
2025-03-06 06:56:45 +01:00
alonso.torres
265675795e 🐛 Fix problem resizing on click 2025-03-06 06:40:47 +01:00
Alejandro Alonso
2c789e48f3 Merge remote-tracking branch 'origin/staging' into develop 2025-03-05 17:07:23 +01:00
Xavier Julian
9da6c50cbe Display tokens contextual menu in body through portal 2025-03-05 11:53:37 +01:00
Alejandro Alonso
5c53de8e76 Merge remote-tracking branch 'origin/staging' into develop 2025-03-05 11:00:19 +01:00
Alejandro
3cdc826fca Merge pull request #6012 from penpot/hiru-integration-tests
📚 Add some more instructions on running integration tests
2025-03-05 10:48:21 +01:00
Andrés Moya
02292f99ab 📚 Add some more instructions on running integration tests 2025-03-05 10:30:39 +01:00
Xavier Julian
0279e75c4b 🐛 Fix unsetted custom property on notifications 2025-03-04 22:09:11 +01:00
Pablo Alba
44f1798dce 🐛 Fix add variant menu inside a variant 2025-03-04 17:34:04 +01:00
Alejandro Alonso
23468a9908 Update rust skia version to 0.81.0 2025-03-04 16:20:56 +01:00
Pablo Alba
8eb2aaa0a8 🎉 Create a new variant from an existing one 2025-03-04 13:52:40 +01:00
Belén Albeza
aa468e2153 🎉 Render plain text
* 🎉 Serialize text content (wasm)

* ♻️ Refactor functions in main to wasm module

* 🎉 Stub rendering of paragraph text (wasm)

* 📎 Clean up commented code
2025-03-04 11:54:52 +01:00
María Valderrama
9e5de82967 📚 Add mark all as read feature to CHANGES.md 2025-03-04 11:25:16 +01:00
Alonso Torres
856e2be1ca 🐛 Fix problem when creating layouts 2025-03-04 11:04:26 +01:00
elhombretecla
8d4b023d61 💄 Remove underline decoration 2025-03-03 17:12:19 +01:00
Andrés Moya
f06f11ad7a 🐛 Avoid crash when applying a token with no active value 2025-03-03 11:25:00 +01:00
Alejandro Alonso
c807e37525 Merge remote-tracking branch 'origin/staging' into develop 2025-03-03 07:16:31 +01:00
Andrey Antukh
c9a8d2bd23 🚧 Tokens (part4) (#5952)
*  Use reduce-kv for creating tokens tree

Instead of reduce

*  Avoid double iteration on spliting text shapes from shapes

On processing color changes

* ♻️ Move the undo transaction out of transform-fill

The undo transaction is a high level construct and it should
be called from the first level events. Low level impl should
not handle transactions at all

*  Use low-level primitives for update-fill token event

* 📎 Add performance logging for style dictionary resolution

* 📎 Replace inline code with a helper

* ♻️ Refactor how fill and stroke color is applied on tokens

* 💄 Fix call convention for component-item-thumbnail component

*  Clean component thumbnail when frame change received

* 📎 Fix tests

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
2025-02-28 09:47:13 +01:00
Florian Schrödl
65c6c821e7 🐛 Fix set with spaces not being rename-able via context menu (#5956) 2025-02-28 09:46:52 +01:00
Xavier Julian
6cccacaaab 💄 Improve context menu scanability 2025-02-27 22:45:06 +01:00
Elena Torró
7da97d69b0 🐛 Fix viewer style in fullscreen mode (#5964) 2025-02-27 17:57:05 +01:00
Belén Albeza
f7574009b5 🐛 Fix layout context menu width + position 2025-02-27 17:47:31 +01:00
Belén Albeza
0416e883ca 🐛 Fix rendering order of inner shadows when shape has no fills (wasm) 2025-02-27 17:45:39 +01:00
Alejandro Alonso
c0ccb86e3a Merge remote-tracking branch 'origin/staging' into develop 2025-02-27 15:34:39 +01:00
Alejandro Alonso
7885f413b8 Merge remote-tracking branch 'origin/staging' into develop 2025-02-26 18:00:51 +01:00
Eva Marco
dfd5c5b508 🐛 Fix height of combobox label (#5944) 2025-02-26 16:54:28 +01:00
Elena Torró
bffbccac50 Merge pull request #5968 from penpot/ladybenko-fix-sampling-options-merge
🐛 Fix messed up rebase re: sampling options
2025-02-26 15:32:26 +01:00
Belén Albeza
12c2d73846 🐛 Fix messed up rebase re: sampling options 2025-02-26 15:18:26 +01:00
Elena Torró
27d15763f8 🐛 Override default SamplingOptions for ImageFill and set FilterMode (#5961)
* 🐛 Override default SamplingOptions for ImageFill and set FilterMode and MipmapMode to 'Linear' instead of 'Nearest'

* 📎 Use sampling_options from render_state in ImageFill
2025-02-26 14:27:25 +01:00
Alejandro
0eaa43f36b Merge pull request #5930 from penpot/ladybenko-10321-split-fill-strokes
 Split rendering of fills and strokes in separate surfaces
2025-02-26 11:57:37 +01:00
Elena Torró
e30834bb2d Enable WEBGL_debug_renderer_info extension before initializing the render (#5959) 2025-02-26 11:32:27 +01:00
Juanfran
fefb946a25 Merge pull request #5951 from penpot/juanfran-expand-padding-margin-apply-token
🐛 Expand padding/margin values on apply token and show 'Mixed' placeholder
2025-02-26 11:15:33 +01:00
Juanfran
2d857ecf2f 🐛 Expand padding/margin values on apply token and show 'Mixed' placeholder 2025-02-26 10:57:51 +01:00
Belén Albeza
2cf179ccf6 ♻️ Add ShapeStrokes surface 2025-02-26 09:51:30 +01:00
Belén Albeza
5ebfc603e6 ♻️ Refactor surfaces (wasm) 2025-02-26 09:50:17 +01:00
alonso.torres
80d5272248 Serialize layout data 2025-02-25 15:43:12 +01:00
Andrey Antukh
b4f6177be7 🐛 Move team srepl helpers back to main from helpers (#5955) 2025-02-25 14:56:58 +01:00
Andrés Moya
3907a1783a Include active sets and themes in exported tokens json file (#5806)
*  Add active sets to json decode

* 📎 Fix tests

* 📎 Add encode tests

* 📎 Add decode test

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
2025-02-25 14:05:07 +01:00
Andrey Antukh
b676ea7127 Merge remote-tracking branch 'origin/staging' into develop 2025-02-25 12:56:05 +01:00
Xavier Julian
ca65f5ad9a 💄 Improve theme modal with UX enhancements 2025-02-25 12:52:12 +01:00
Elena Torró
3e89b73ca0 📚 Add developer docs about how to test feature flags by team (#5949) 2025-02-25 12:51:00 +01:00
Alejandro
17e9e836f6 Merge pull request #5950 from penpot/alotor-fix-gradient-strokes
🐛 Fix problem with gradient in strokes
2025-02-25 11:43:49 +01:00
alonso.torres
c48d862d0f 🐛 Fix problem with gradient in strokes 2025-02-25 11:29:57 +01:00
alonso.torres
052282cff9 🐛 Fix problem updating layout when duplicating component 2025-02-25 11:21:41 +01:00
Pablo Alba
6d40166de7 Remove menu options and shortcuts actions over variants 2025-02-25 10:11:14 +01:00
Elena Torró
ab7781b4fa Add dbg panel to enable a team feature flag (#5940)
*  Add dbg panel to enable and disable team feature flags

* 📎 Rename debug to skip-check and do not parse & cast the feature value
2025-02-25 10:09:43 +01:00
Eva Marco
88669d2e0f 🎉 Add translations to token errors (#5926) 2025-02-24 15:50:47 +01:00
Andrey Antukh
1187d64f69 Merge remote-tracking branch 'origin/staging' into develop 2025-02-24 12:49:04 +01:00
Andrey Antukh
3074fc9ab5 ♻️ Remove deprecated with-atomic and refactor tx-run! (#5915)
* ♻️ Remove deprecated with-atomic and refactor tx-run!

*  Do not hold open connection for the whole clone-template operation
2025-02-24 11:15:44 +01:00
Elena Torró
bcea19001e Improve readability of RGBA and HSLA values in inspect mode (#5918)
*  Improve readability of RGBA and HSLA values in inspect mode

* 📎 Remove reader conditionals for format common methods
2025-02-24 09:08:42 +01:00
Andrés Moya
1c98c53805 🔧 Add filtering to logs in component sync algorithm 2025-02-21 11:30:02 +01:00
Aitor Moreno
4799f6fe0a ♻️ Refactor rendering surfaces (#5921) 2025-02-21 11:04:12 +01:00
Alejandro
7470fb709f Merge pull request #5920 from penpot/alotor-test-apply-modifiers
 Modify shapes geometry instead of transformation matrix
2025-02-21 07:44:32 +01:00
alonso.torres
8c1e18b1cd 🐛 Fix problem with images 2025-02-20 17:57:36 +01:00
alonso.torres
9143187efd Modify shapes geometry instead of transformation matrix 2025-02-20 17:39:17 +01:00
Aitor Moreno
b661f39422 ♻️ Refactor render shape (#5916) 2025-02-20 16:33:18 +01:00
Aitor Moreno
3a764a9da6 Merge pull request #5919 from penpot/alotor-constraints-2
🐛 Fix problems with constraints resizing
2025-02-20 16:26:46 +01:00
alonso.torres
2341dfb95d 🐛 Fix problems with constraints resizing 2025-02-20 14:20:01 +01:00
Xavier Julian
ff121d2af5 💄 Differentiate empty and not empty token types 2025-02-20 14:12:51 +01:00
Xavier Julian
55d7bab0e6 💄 Spacing on token sets 2025-02-20 13:31:18 +01:00
Juanfran
4a3d951329 Merge pull request #5888 from penpot/juanfran-fix-flaky-add-new-test
 Add more integration tests for tokens
2025-02-20 10:10:00 +01:00
Pablo Alba
014c297458 🎉 Add drag components in or out a variant container 2025-02-20 10:05:01 +01:00
Juanfran
280da72e63 Add more integration tests for tokens 2025-02-20 09:57:31 +01:00
Alejandro Alonso
6277db8d45 🐛 Fix clip for frames with border radius (render wasm) 2025-02-20 09:24:55 +01:00
Andrey Antukh
60530a80d9 Merge remote-tracking branch 'origin/staging' into develop 2025-02-20 09:15:00 +01:00
Andrey Antukh
d79fac7729 Merge remote-tracking branch 'origin/staging' into develop 2025-02-19 17:17:54 +01:00
Pablo Alba
195127b099 Add text selection on focus of an input-with-values (#5909) 2025-02-19 16:51:49 +01:00
María Valderrama
a3e74c55f1 💄 Add cosmetic changes to notifications buttons (#5902) 2025-02-19 16:45:22 +01:00
Juanfran
e408bc9113 Merge pull request #5904 from penpot/juanfran-themes-dropdown-scrollbar-styles
🐛 Fix scrollbar styles in themes dropdown
2025-02-19 15:22:19 +01:00
Juanfran
89dc917cb9 🐛 Fix scrollbar styles in themes dropdown 2025-02-19 14:25:43 +01:00
Elena Torró
f83cdf2f5d 📎 Remove invalid aria label on div (#5898) 2025-02-19 11:42:44 +01:00
Elena Torró
f3040fc10d 🐛 Pass color format to css formatter (#5890)
* 🐛 Pass color format to css formatter

* 📎 Use get method to retrieve format

* 📎 Add UI test to check background color is correctly copied to clipboard when changing the format
2025-02-19 11:23:52 +01:00
Andrey Antukh
44acd79081 Merge remote-tracking branch 'origin/staging' into develop 2025-02-19 10:46:55 +01:00
Andrey Antukh
a3e4da0b3d 📎 Update changelog 2025-02-19 10:46:33 +01:00
Elena Torró
73a52e5395 🐛 Fix opacity in frame containers (#5858)
* 🐛 Fix opacity in frame containers

* 📎 Improve readability of frame containers by using class names
2025-02-19 10:44:24 +01:00
Alonso Torres
6cb1aa24cd Add constraints calculation on WASM (#5894)
*  Add constraints calculation on WASM

*  Fix after review
2025-02-19 10:40:04 +01:00
Andrey Antukh
f5c913d26e Merge remote-tracking branch 'origin/staging' into develop 2025-02-18 17:28:38 +01:00
Andrey Antukh
c86f14e75d 🐛 Fix color slider not updating the hue of the ramp (#5891)
* 🐛 Fix color slider not updating the hue

*  Add minor enhancements for ramp* component

---------

Co-authored-by: Andrey Fedorov <oran9e.red@gmail.com>
2025-02-18 16:42:11 +01:00
Pablo Alba
65a97167de 🐛 Fix bad value on combobox enter keydown (#5859) 2025-02-18 15:55:22 +01:00
Xavier Julian
0530c57d31 Add placeholder to themes modal 2025-02-18 14:51:33 +01:00
Andrey Antukh
adbe29e3d1 Merge remote-tracking branch 'origin/staging' into develop 2025-02-18 10:25:13 +01:00
Alejandro
5fae07af11 Merge pull request #5880 from penpot/alotor-render-wasm-cache
🐛 Fix problem with cache
2025-02-18 10:20:31 +01:00
alonso.torres
400e5f60f2 🐛 Fix problem with cache 2025-02-18 10:00:57 +01:00
Pablo Alba
3268225941 🎉 Add variations POC 2025-02-17 16:47:25 +01:00
Xaviju
91fa39705d ♻️ Switch contextual notification component (#5874) 2025-02-17 16:03:13 +01:00
Andrey Antukh
c41f86f0a4 Merge remote-tracking branch 'origin/staging' into develop 2025-02-17 15:20:33 +01:00
Eva Marco
81b741478a 🐛 Fix a little regression on sidebar resize (#5866) 2025-02-17 12:25:07 +01:00
Juanfran
f6fc2f8808 🐛 Prevent themes dropdown overflow hidden in set sidebar (#5850) 2025-02-17 09:51:10 +01:00
Andrey Fedorov
aa180e9f3f 🐛 Fix max- min- width height not being applied 2025-02-14 15:26:49 +01:00
Florian Schroedl
6b773d6b74 🐛 Dont allow hex values without # prefix 2025-02-14 13:52:54 +01:00
Belén Albeza
6cbaacf1e0 🎉 Implement inner shadows (wasm) (#5767)
* 🎉 Implement inner shadows (wasm)

* 🐛 Fix reset canvas problem

---------

Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2025-02-14 13:46:30 +01:00
Eva Marco
2ffb77cb4d Merge pull request #5840 from penpot/niwinz-tokens-changes-3
♻️ Token changes (part 3)
2025-02-14 12:55:12 +01:00
María Valderrama
59a57d6c3f Mark all notifications as read from dashboard (#5805)
*  Mark all notifications as read from dashboard

* ♻️ Mark all notifications as read code review

* ♻️ Mark all notifications as read code review II
2025-02-14 11:48:24 +01:00
Xaviju
8a332c1402 Add placeholder to set edition (#5848) 2025-02-14 11:46:04 +01:00
Andrey Antukh
37855bfe7f Merge remote-tracking branch 'origin/staging' into develop 2025-02-14 10:35:29 +01:00
Andrey Antukh
f68b0117c4 ♻️ Simplify token creation mechanism 2025-02-14 08:49:31 +01:00
Andrey Antukh
aa867adbd3 ♻️ Add minor refactor on tokenlib object construction and validation 2025-02-14 08:49:30 +01:00
Andrey Antukh
3fd429c72a Merge remote-tracking branch 'origin/staging' into develop 2025-02-13 21:04:52 +01:00
Aitor Moreno
39bbb4c2bd Merge pull request #5827 from penpot/superalex-fix-wasm-glitches
🐛 Fix wasm glitches
2025-02-13 15:25:24 +01:00
Alejandro Alonso
64e6d0b1f8 🐛 Fix wasm glitches 2025-02-13 15:16:45 +01:00
Xavier Julian
26a2ef8fb7 ♻️ Create DS context notification component 2025-02-13 13:36:53 +01:00
Alejandro
a6c46ee55c Merge pull request #5819 from penpot/azazeln28-feat-masks
🎉 Feat masks
2025-02-13 13:26:21 +01:00
AzazelN28
f8d58cb74e 🎉 Feat masks 2025-02-13 12:54:18 +01:00
Andrey Antukh
3ea52a0198 Merge pull request #5784 from penpot/niwinz-tokens-changes-2
 Several performance oriented changes to tokens code (part 2)
2025-02-13 11:47:28 +01:00
Andrey Antukh
054efb3435 Merge pull request #5766 from penpot/niwinz-tokens-changes
 Several changes to tokens
2025-02-13 11:46:58 +01:00
Alejandro
773b4fe02e Merge pull request #5824 from penpot/alotor-propagate-modifiers
 Use skia matrix for internal data
2025-02-13 10:21:58 +01:00
Elena Torró
1a62e5e42d Merge pull request #5830 from penpot/elenatorro-10118-fix-action-button-size
🐛 Fix Action Buttons size and add example to Storybook
2025-02-12 17:30:43 +01:00
elenatorro
f812460158 🐛 Fix Action Buttons size and display, and add example to Storybook 2025-02-12 17:02:20 +01:00
Andrey Antukh
654c070976 Merge remote-tracking branch 'origin/staging' into develop 2025-02-12 14:37:20 +01:00
Florian Schrödl
e776ba1b33 Add margin to spacing token & context menu (#5807) 2025-02-12 14:27:26 +01:00
Andrey Antukh
7497371b32 🐛 Fix issue on renaming group 2025-02-12 10:47:39 +01:00
Andrey Antukh
50afc4c507 Remove selection workaround for undo
Always select the first one when no match is found
2025-02-12 10:47:39 +01:00
Andrey Antukh
064e51d24e 🐛 Clear token set edition state on rename 2025-02-12 10:47:38 +01:00
Andrey Antukh
a029ec18a6 🐛 Fix incorrect handling of selected token set 2025-02-12 10:47:36 +01:00
Andrey Antukh
b0a6e5c946 🐛 Fix regression: close context menu on edit token 2025-02-12 10:45:04 +01:00
Andrey Antukh
e8c85d13ff ♻️ Remove sets context abstraction 2025-02-12 10:45:02 +01:00
Andrey Antukh
b228438127 Refactor the rest fot token sets context menu components 2025-02-12 10:41:24 +01:00
Andrey Antukh
1fb48a1e8a Adapt call style convention for token sets context menu component 2025-02-12 10:41:24 +01:00
Andrey Antukh
91b6d498fe Add minor efficiency improvements to tokens import export 2025-02-12 10:41:24 +01:00
Andrey Antukh
efe204c346 Simplify access to can-edit? using react context 2025-02-12 10:41:24 +01:00
Andrey Antukh
4a4cd9492a ♻️ Refactor token set auto selection mechanism
This is a general purpose change that also allows perform
a best effort on selection sets when the name is changed
(per example by moving it into other group).
2025-02-12 10:41:23 +01:00
Andrey Antukh
5446464d7e 📎 Add fixme comment for confusing name 2025-02-12 10:40:36 +01:00
Andrey Antukh
baa5258a43 Declare transducer statically for path split function 2025-02-12 10:40:36 +01:00
Andrey Antukh
df6a679548 Remove duplicated operations from tokens sidebar 2025-02-12 10:40:36 +01:00
Andrey Antukh
3692f17e55 Adapt call style for set-lists component 2025-02-12 10:40:34 +01:00
Andrey Antukh
2bac94ad5c Use new call convention on all components of tokens sidebar 2025-02-12 10:24:48 +01:00
Andrey Antukh
d46d80bd5c Use value equals on selected shapes set 2025-02-12 10:24:48 +01:00
Andrey Antukh
f0e5196659 Add minor optimization to token-pill component 2025-02-12 10:24:48 +01:00
Andrey Antukh
05a459ea19 📎 Add missing license header 2025-02-12 10:24:48 +01:00
Andrey Antukh
8e85d5a02a Add performance oriented refactor to token sidebar and token-pill 2025-02-12 10:24:48 +01:00
Andrey Antukh
81036b9330 Improve handling open-status and tokens selection 2025-02-12 10:24:47 +01:00
Andrey Antukh
c91b7606a0 Add minor efficiency improvements using new call convention 2025-02-12 10:24:47 +01:00
Andrey Antukh
d6e7a331d5 ♻️ Move token-types to changes ns
And rename it to token-properties
2025-02-12 10:24:47 +01:00
Andrey Antukh
831b0baddd Improve efficiency of grouping and sorting token types 2025-02-12 10:24:47 +01:00
Andrey Antukh
e4bf2bd9ad 💄 Internal function rename on tokens sidebar 2025-02-12 10:24:47 +01:00
Andrey Antukh
7a4d8b824e 💄 Rename token-component* to token-group* 2025-02-12 10:24:47 +01:00
Andrey Antukh
ff34d1d5f9 Don't create an additional sequence on iterating for tokens 2025-02-12 10:24:47 +01:00
Andrey Antukh
c1ce24e5f0 Don't sort tokens on each rerender 2025-02-12 10:24:46 +01:00
Andrey Antukh
1f450c83ec Use new component definition convention for token-component 2025-02-12 10:24:46 +01:00
Andrey Antukh
769000da2d 💄 Add cosmetic changes to token-component component 2025-02-12 10:24:46 +01:00
Andrey Antukh
a53c37bc3c Don't create function references for token-pill callbacks 2025-02-12 10:24:46 +01:00
Andrey Antukh
333cc5996c 💄 Add mainly cosmetic changes to tokens pill
Simplify the component logic removing duplicate token prop handling
2025-02-12 10:24:46 +01:00
Andrey Antukh
bccc90f5a2 Don't create keys seq on each rerender on token-pill component 2025-02-12 10:24:45 +01:00
Andrey Antukh
5575a66b8d 📎 Add many FIXME comments on token-pill component 2025-02-12 10:24:45 +01:00
Andrey Antukh
0fee8143dd Don't use seq operations for string includes operation 2025-02-12 10:24:45 +01:00
Andrey Antukh
b6e26d15e1 💄 Add cosmetic changes to tokens sidebar component 2025-02-12 10:24:44 +01:00
Andrey Antukh
7e0b2702de 💄 Add minor cosmetic changes 2025-02-12 10:24:13 +01:00
Andrey Antukh
e46fb9dba7 📎 Add missing copyright header 2025-02-12 10:24:13 +01:00
Andrey Antukh
f4dee75a17 🔥 Remove unused workspace tokens common ns file 2025-02-12 10:24:13 +01:00
Andrey Antukh
6d1ff0cb49 🔥 Remove unused and incorrect ref passed to dropdown component 2025-02-12 10:24:12 +01:00
alonso.torres
3dcabc9502 Use skia matrix for internal data 2025-02-11 16:49:43 +01:00
Eva Marco
10174aa7bc 🐛 Fix uppercase text and copy (#5821) 2025-02-11 16:46:17 +01:00
Andrey Antukh
cd87cbe44e Merge remote-tracking branch 'origin/staging' into develop 2025-02-11 16:09:32 +01:00
Aitor Moreno
4594c7bf0a Merge pull request #5823 from penpot/superalex-render-wasm-cloning
🎉 Remove extra clonings from render wasm
2025-02-11 15:55:58 +01:00
Alejandro Alonso
d1a1dafcad 🎉 Remove unnecesary clone for cache image 2025-02-11 14:05:37 +01:00
Alejandro Alonso
246463a3ec 🎉 Remove shape cloning from render wasm 2025-02-11 14:05:37 +01:00
Alejandro
4b4541515c Merge pull request #5809 from penpot/perf-transform-wasm
 Add support for WASM transforms
2025-02-11 12:52:55 +01:00
alonso.torres
1bb337c3dd Add support for WASM transforms 2025-02-11 12:36:44 +01:00
Florian Schrödl
8b380a01e6 ♻️ Simplifies RPC pattern for tokens (#5765)
* ♻️ Refactor to RPC pattern

Co-authored-by: andrei <andrei@hyma.io>

* ♻️ Remove unused alias

* ♻️ Change function signature

---------

Co-authored-by: andrei <andrei@hyma.io>
Co-authored-by: Andrey Fedorov <oran9e.red@gmail.com>
2025-02-11 12:32:28 +01:00
Xaviju
5c32ec8cfa ♻️ Create shared notification utility component 2025-02-11 11:42:06 +01:00
Eva Marco
9660307f00 🐛 Fix opacity valid values (#5820) 2025-02-11 11:28:25 +01:00
Andrey Antukh
a3a757f842 Merge remote-tracking branch 'origin/staging' into develop 2025-02-11 11:25:40 +01:00
Eva Marco
a2727a110e Add resize in percentage option (#5816) 2025-02-11 11:25:11 +01:00
Juanfran
dd1aba0d05 Merge pull request #5812 from penpot/juanfran-create-edit-token-modal-enter-key
 Add missing enter key for button activation in edit/create tokens modal
2025-02-10 16:10:25 +01:00
Eva Marco
6692f8dce2 Add validation to token opacity (#5802) 2025-02-10 15:04:22 +01:00
Juanfran
372b3145ea Add missing Enter key for button activation in edit/create tokens modal 2025-02-10 15:02:46 +01:00
Alejandro
8b7a102927 Merge pull request #5804 from penpot/superalex-fix-wasm-frame-clipping
🐛 Fix wasm frame clipping
2025-02-10 14:05:46 +01:00
Juanfran
24e1cf0d7d Merge pull request #5811 from penpot/juanfran-spacing-token-sets
💄 Improve spacing in design token sets
2025-02-10 13:57:02 +01:00
Alejandro Alonso
2ce88283a2 🐛 Fix wasm render frame clipping 2025-02-10 13:54:27 +01:00
Juanfran
2ae2e23b57 💄 Improve spacing in design token sets 2025-02-10 13:37:24 +01:00
Pablo Alba
9c7bb96b1c Add new DS component: InputWithValues (#5777)
*  Add new DS component: InputWithValues

*  Fix MR

*  Fix MR 2
2025-02-10 13:17:30 +01:00
Andrey Antukh
0f49208040 Merge branch 'staging' into develop 2025-02-10 12:01:15 +01:00
Alejandro
8f11a925df 🎉 Non blocking render wasm (#5726) 2025-02-10 11:46:56 +01:00
Andrey Antukh
f65f7d68e6 Merge remote-tracking branch 'origin/staging' into develop 2025-02-10 10:57:04 +01:00
Juanfran
f5b18f953d Enable Enter key for button activation in tokens modal (#5792) 2025-02-07 14:26:09 +01:00
Eva Marco
73ff1b4fe5 🐛 Fix menu shadow color (#5793) 2025-02-06 15:56:45 +01:00
Florian Schrödl
0c275cf490 🐛 Fix token set selection & rename (#5783)
* 🐛 Fix set selection

* 🐛 Fix set name not being updated

* ♻️ Add better list assertion & test set renaming
2025-02-06 14:16:03 +01:00
Andrey Antukh
8b466ef0a3 Merge remote-tracking branch 'origin/staging' into develop 2025-02-06 12:57:33 +01:00
Andrey Antukh
33887711f7 Merge remote-tracking branch 'origin/staging' into develop 2025-02-06 09:38:47 +01:00
Juanfran
947bd547aa 💄 Improve alignment of design token sets (#5782) 2025-02-06 08:58:02 +01:00
Florian Schrödl
86e0f8ad34 🐛 Fix toggling set groups in theme modal not working (#5733) 2025-02-05 12:21:21 +01:00
Andrey Antukh
48acc8715b Merge remote-tracking branch 'origin/staging' into develop 2025-02-04 16:01:14 +01:00
Andrei Fëdorov
9db76f9a15 🎉 Import legacy format tokens (#5695) 2025-02-04 12:50:39 +01:00
Andrey Antukh
df0909483e Merge remote-tracking branch 'origin/staging' into develop 2025-02-04 11:35:03 +01:00
Florian Schrödl
ec8109644b Create sets inside folders 2025-02-04 10:59:28 +01:00
Andrey Antukh
315b389a66 🐛 Fix name generation and handling on creating objects (files, tokens, ...) (#5745)
*  Update function implementation

*  Add tests for a new function implementation

*  Update function usages according to new signature

*  Update docstrings

*  Use simpler assertion

* 💄 Replace concat with cons on name-seq

* 🐛 Fix incorrect error handling on legacy workspace redirect

---------

Co-authored-by: Andrey Fedorov <oran9e.red@gmail.com>
2025-02-03 12:49:56 +01:00
Andrey Antukh
bce30eb522 Merge pull request #5738 from penpot/niwinz-merge-two-prs
 Apply token value changes to all shapes in all pages
2025-02-03 12:31:07 +01:00
Andrey Antukh
d05d1c6a48 📎 Update changelog (add entry for 2.6.0) 2025-02-03 11:03:35 +01:00
Andrés Moya
1803e32322 🐛 Regenerate text shapes after changing token values 2025-02-03 10:16:31 +01:00
Eva Marco
24281b512e 🐛 Fix thumbnail regeration when changing sets of tokens 2025-01-31 16:35:30 +01:00
Andrés Moya
f4f0478975 Apply token value changes to shapes in all pages 2025-01-31 16:35:30 +01:00
444 changed files with 20231 additions and 8140 deletions

View File

@@ -263,6 +263,12 @@ jobs:
command: |
cargo fmt --check
- run:
name: "lint"
working_directory: "./render-wasm"
command: |
./lint
- run:
name: "cargo tests"
working_directory: "./render-wasm"

View File

@@ -1,29 +1,19 @@
<!--
### Related Ticket
Some key notes before you open a PR:
<!-- Reference the related GitHub/Taiga ticket. -->
1. Select which branch should this PR be merged in? By default, you should always merge to the develop branch.
2. PR name follows [convention](http://karma-runner.github.io/4.0/dev/git-commit-msg.html)
3. All tests pass locally, UI and Unit tests
4. All business logic and validations must be on the server-side
5. Update necessary Documentation
6. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes
### Summary
### Steps to reproduce
Also, if you're new here
### Checklist
- Contribution Guide => https://github.com/uxbox/uxbox/blob/develop/CONTRIBUTING.md
- [ ] Choose the correct target branch; use `develop` by default.
- [ ] Provide a brief summary of the changes introduced.
- [ ] Add a detailed explanation of how to reproduce the issue and/or verify the fix, if applicable.
- [ ] Include screenshots or videos, if applicable.
- [ ] Add or modify existing integration tests in case of bugs or new features, if applicable.
- [ ] Check CI passes successfully.
- [ ] Update the `CHANGES.md` file, referencing the related GitHub issue, if applicable.
-->
> Please provide enough information so that others can review your pull request:
<!-- You can skip this if you're fixing a typo or updating existing documentation -->
> Explain the **details** for making this change. What existing problem does the pull request solve?
<!-- Example: When "Adding a function to do X", explain why it is necessary to have a way to do X. -->
> Screenshots/GIFs
<!-- Add images/recordings to better visualize the change: expected/current behviour -->
<!-- For more details, check the contribution guidelines: https://github.com/penpot/penpot/blob/develop/CONTRIBUTING.md -->

View File

@@ -1,28 +1,106 @@
# CHANGELOG
## 2.5.3
## 2.6.2
### :bug: Bugs fixed
- Increase the height of the right sidebar dropdowns [Taiga #10615](https://tree.taiga.io/project/penpot/issue/10615)
- Fix scroll on token themes modal [Taiga #10745](https://tree.taiga.io/project/penpot/issue/10745)
- Fix collapsing grouped sets in "edit Theme" closes the dialog [Taiga #10771](https://tree.taiga.io/project/penpot/issue/10771)
- Fix unexpected exception on path editor on merge segments when undo stack is empty
- Fix pricing CTA to be under a config flag [Taiga #10808](https://tree.taiga.io/project/penpot/issue/10808)
- Fix allow moving a main component into another [Taiga #10818](https://tree.taiga.io/project/penpot/issue/10818)
- Fix several issues with internal srepl helpers
- Fix unexpected exception on template import from libraries
- Fix incorrect uuid parsing from different parts of code
- Fix update layout on component restore [Taiga #10637](https://tree.taiga.io/project/penpot/issue/10637)
- Fix horizontal scroll in viewer [Github #6290](https://github.com/penpot/penpot/issues/6290)
- Fix detach component in a particular case [Taiga #10837](https://tree.taiga.io/project/penpot/issue/10837)
## 2.6.1
### :bug: Bugs fixed
- Fix webhooks not shown in list [Taiga #10763](https://tree.taiga.io/project/penpot/issue/10763)
- Fix colorpicker scroll when dropdown displayed [Taiga #10696](https://tree.taiga.io/project/penpot/issue/10696)
- Clean internal workspace state on exit or url changed [Taiga #10619](https://tree.taiga.io/project/penpot/issue/10619)
## 2.6.0
### :rocket: Epics and highlights
- Design Tokens
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
### :sparkles: New features
- [COMMENTS] "Mark All as Read" Functionality in Dashboard [Taiga #9235](https://tree.taiga.io/project/penpot/us/9235)
- [COMMENTS] Bubble Groups [Taiga #9236](https://tree.taiga.io/project/penpot/us/9236)
- Change templates carrousel [Taiga #9803](https://tree.taiga.io/project/penpot/us/9803)
- [DESIGN TOKENS] Tokens CRUD. Types added: Color, Opacity, Border radius, Dimension, Sizing, Spacing, Rotation and Stroke.
- [DESIGN TOKENS] Create references (alias) that point to other tokens.
- [DESIGN TOKENS] Math operations in token values.
- [DESIGN TOKENS] Sets CRUD, grouping and reordering.
- [DESIGN TOKENS] Multidimensional Themes and Sets management.
- [DESIGN TOKENS] Apply/Remove tokens to/from elements from the Tokens tab.
- [DESIGN TOKENS] Integration with components.
- [DESIGN TOKENS] Import and export tokens from a JSON file.
- [DESIGN TOKENS] Apply Themes and Sets at document level.
- Add more descriptive tooltip to boards for first time users [Taiga #9426](https://tree.taiga.io/project/penpot/us/9426)
- First State of a Project Changes Consolidation [Taia #10605](https://tree.taiga.io/project/penpot/us/10605)
### :bug: Bugs fixed
- Fix opacity in frame containers [Github #5858](https://github.com/penpot/penpot/pull/5858)
- Avoid resizing on click [Taiga #10213](https://tree.taiga.io/project/penpot/issue/10213)
- Hide horizontal scroll from dashboard sidebar [Taiga #10422](https://tree.taiga.io/project/penpot/issue/10422)
- Fix cut and paste a copy a cmponent inside its parent [Taiga #10365](https://tree.taiga.io/project/penpot/us/10365)
- Fix duplicate page with component over frame [Taiga #8151](https://tree.taiga.io/project/penpot/issue/8151) and [Taiga #9698](https://tree.taiga.io/project/penpot/issue/9698)
- The plugin list in the navigation menu lacks scrolling, some plugins are not visible when a large number are installed [Taiga #9360](https://tree.taiga.io/project/penpot/us/9360)
- Fix hidden toolbar click event still available [Taiga #10437](https://tree.taiga.io/project/penpot/us/10437)
- Fix hovering over templates [Taiga #10545](https://tree.taiga.io/project/penpot/issue/10545)
- Fix problem with default shadows value in plugins [Plugins #191](https://github.com/penpot/penpot-plugins/issues/191)
- Fix problem with constraints when creating group [Taiga #10455](https://tree.taiga.io/project/penpot/issue/10455)
- Fix opening pen with shortcut multiple times breaks toolbar [Taiga #10566](https://tree.taiga.io/project/penpot/issue/10566)
- Fix actions when workspace is visited first time [Taiga #10548](https://tree.taiga.io/project/penpot/issue/10548)
- Chat icon overlaps "Show" button in carrousel section [Taiga #10542](https://tree.taiga.io/project/penpot/issue/10542)
- Fix assets name on inspect tab [Taiga #10630](https://tree.taiga.io/project/penpot/issue/10630)
- Fix chat icon overlaps "Show" button in carrousel section [Taiga #10542](https://tree.taiga.io/project/penpot/issue/10542)
- Fix incorrect handling of background task result (now task rows are properly marked as completed)
- Fix available size of resize handler [Taiga #10639](https://tree.taiga.io/project/penpot/issue/10639)
- Internal error when install a plugin by penpothub - Try plugin [Taiga #10542](https://tree.taiga.io/project/penpot/issue/10542)
- Add character limitation to asset inputs [Taiga #10669](https://tree.taiga.io/project/penpot/issue/10669)
- Fix Storybook link 'list of all available icons' wrong path [Taiga #10705](https://tree.taiga.io/project/penpot/issue/10705)
## 2.5.4
### :heart: Community contributions (Thank you!)
- Add support for WEBP format on shape export [Github #6053](https://github.com/penpot/penpot/pull/6053) and [Github #6074](https://github.com/penpot/penpot/pull/6074)
### :bug: Bugs fixed
- Fix feature loading on workspace when opening a file in a background
tab [Taiga #10377](https://tree.taiga.io/project/penpot/issue/10377)
- Fix minor inconsistencies on RPC `get-file-libraries` and `get-file`
methods (add missing team-id prop)
- Fix problem with viewer role and inspect mode [Taiga #9751](https://tree.taiga.io/project/penpot/issue/9751)
- Fix error when clicking on a comment at the viewer's sidebar [Taiga #10465](https://tree.taiga.io/project/penpot/issue/10465)
## 2.5.3
### :bug: Bugs fixed
- Component sync issues with multiple tabs [Taiga #10471](https://tree.taiga.io/project/penpot/issue/10471)
## 2.5.2
### :rocket: Epics and highlights
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
### :sparkles: New features
- When the workspace is empty, set default the board creation tool [Taiga #9425](https://tree.taiga.io/project/penpot/us/9425)
### :bug: Bugs fixed
@@ -34,22 +112,12 @@
## 2.5.1
### :rocket: Epics and highlights
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
### :sparkles: New features
- Improve Nginx entryponit to get the resolvers dinamically by default
### :bug: Bugs fixed
## 2.5.0
### :rocket: Epics and highlights
### :boom: Breaking changes & Deprecations
Although this is not a breaking change, we believe its important to highlight it in this
@@ -78,9 +146,6 @@ If you have a big database and many cores available, you can reduce the time of
all files by increasing paralelizacion changing the `max-jobs` value from 1 to N (where N
is a number of cores)
### :heart: Community contributions (Thank you!)
### :sparkles: New features
- [GRADIENTS] New gradients UI with multi-stop support. [Taiga #3418](https://tree.taiga.io/project/penpot/epic/3418)
@@ -97,6 +162,7 @@ is a number of cores)
### :bug: Bugs fixed
- Fix menu shadow color [Taiga #10102](https://tree.taiga.io/project/penpot/issue/10102)
- Fix missing state refresh on notifications update [Taiga #10253](https://tree.taiga.io/project/penpot/issue/10253)
- Fix icon visualization on select component [Taiga #8889](https://tree.taiga.io/project/penpot/issue/8889)
- Fix typo on integration tests docs [Taiga #10112](https://tree.taiga.io/project/penpot/issue/10112)

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728",
"packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"

View File

@@ -151,6 +151,78 @@ Debug Main Page
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>
</form>
</fieldset>
</section>
<section class="widget">
<h2>Feature Flags</h2>
<fieldset>
<legend>Enable</legend>
<desc>Add a feature flag to a team</desc>
<form method="post" action="/dbg/actions/add-team-feature">
<div class="row">
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
</div>
<div class="row">
<input type="text" style="width:100px" name="feature" placeholder="feature" value="" />
</div>
<div class="row">
<label for="check-feature">Skip feature check</label>
<input id="check-feature" type="checkbox" name="skip-check" />
<br />
<small>
Do not check if the feature is supported
</small>
</div>
<div class="row">
<label for="force-version">Are you sure?</label>
<input id="force-version" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>
</form>
</fieldset>
<fieldset>
<legend>Disable</legend>
<desc>Remove a feature flag from a team</desc>
<form method="post" action="/dbg/actions/remove-team-feature">
<div class="row">
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
</div>
<div class="row">
<input type="text" style="width:100px" name="feature" placeholder="feature" value="" />
</div>
<div class="row">
<label for="check-feature">Skip feature check</label>
<input id="check-feature" type="checkbox" name="skip-check" />
<br />
<small>
Do not check if the feature is supported
</small>
</div>
<div class="row">
<label for="force-version">Are you sure?</label>
<input id="force-version" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>

View File

@@ -30,7 +30,8 @@ export PENPOT_FLAGS="\
enable-access-tokens \
enable-tiered-file-data-storage \
enable-file-validation \
enable-file-schema-validation";
enable-file-schema-validation \
enable-subscriptions-old";
# Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h"

View File

@@ -23,7 +23,8 @@ export PENPOT_FLAGS="\
enable-access-tokens \
enable-tiered-file-data-storage \
enable-file-validation \
enable-file-schema-validation";
enable-file-schema-validation \
enable-subscriptions-old";
export OPTIONS="
-A:jmx-remote -A:dev \

View File

@@ -0,0 +1,44 @@
;; 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.binfile.cleaner
"A collection of helpers for perform cleaning of artifacts; mainly
for recently imported shapes."
(:require
[app.common.data :as d]
[app.common.uuid :as uuid]))
(defn- fix-shape-shadow-color
"Some shapes can come with invalid `id` property on shadow colors
caused by incorrect uuid parsing bug that should be already fixed;
this function removes the invalid id from the data structure."
[shape]
(let [fix-color
(fn [{:keys [id] :as color}]
(if (uuid? id)
color
(if (and (string? id)
(re-matches uuid/regex id))
(assoc color :id (uuid/uuid id))
(dissoc color :id))))
fix-shadow
(fn [shadow]
(d/update-when shadow :color fix-color))
xform
(map fix-shadow)]
(d/update-when shape :shadow
(fn [shadows]
(into [] xform shadows)))))
(defn clean-shape-post-decode
"A shape procesor that expected to be executed after schema decoding
process but before validation."
[shape]
(-> shape
(fix-shape-shadow-color)))

View File

@@ -39,6 +39,11 @@
"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

@@ -8,12 +8,14 @@
"A ZIP based binary file exportation"
(:refer-clojure :exclude [read])
(:require
[app.binfile.cleaner :as bfl]
[app.binfile.common :as bfc]
[app.binfile.migrations :as bfm]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
[app.common.files.migrations :as-alias fmg]
[app.common.json :as json]
[app.common.logging :as l]
[app.common.schema :as sm]
@@ -594,16 +596,25 @@
(defn- read-file-components
[{:keys [::bfc/input ::file-id ::entries]}]
(->> (keep (match-component-entry-fn file-id) entries)
(reduce (fn [result {:keys [id entry]}]
(let [object (->> (read-entry input entry)
(decode-component)
(validate-component))]
(if (= id (:id object))
(assoc result id object)
result)))
{})
(not-empty)))
(let [clean-component-post-decode
(fn [component]
(d/update-when component :objects
(fn [objects]
(reduce-kv (fn [objects id shape]
(assoc objects id (bfl/clean-shape-post-decode shape)))
objects
objects))))]
(->> (keep (match-component-entry-fn file-id) entries)
(reduce (fn [result {:keys [id entry]}]
(let [object (->> (read-entry input entry)
(decode-component)
(clean-component-post-decode)
(validate-component))]
(if (= id (:id object))
(assoc result id object)
result)))
{})
(not-empty))))
(defn- read-file-typographies
[{:keys [::bfc/input ::file-id ::entries]}]
@@ -631,7 +642,9 @@
(reduce (fn [result {:keys [id entry]}]
(let [object (->> (read-entry input entry)
(decode-shape)
(bfl/clean-shape-post-decode)
(validate-shape))]
(if (= id (:id object))
(assoc result id object)
result)))
@@ -733,7 +746,14 @@
(assoc :name file-name)
(assoc :project-id project-id)
(dissoc :options)
(bfc/process-file))]
(bfc/process-file)
;; NOTE: this is necessary because when we just
;; creating a new file from imported artifact,
;; there are no migrations registered on the
;; database, so we need to persist all of them, not
;; only the applied
(vary-meta dissoc ::fmg/migrated))]
(bfm/register-pending-migrations! cfg file)

View File

@@ -22,7 +22,8 @@
[clojure.set :as set]
[integrant.core :as ig]
[next.jdbc :as jdbc]
[next.jdbc.date-time :as jdbc-dt])
[next.jdbc.date-time :as jdbc-dt]
[next.jdbc.transaction])
(:import
com.zaxxer.hikari.HikariConfig
com.zaxxer.hikari.HikariDataSource
@@ -223,16 +224,6 @@
(let [^OutputStream os (.getOutputStream ^LargeObject lobj)]
(io/make-output-stream os opts))))
(defmacro with-atomic
[& args]
(if (symbol? (first args))
(let [cfgs (first args)
body (rest args)]
`(jdbc/with-transaction [conn# (::pool ~cfgs)]
(let [~cfgs (assoc ~cfgs ::conn conn#)]
~@body)))
`(jdbc/with-transaction ~@args)))
(defn open
[system-or-pool]
(if (pool? system-or-pool)
@@ -535,43 +526,31 @@
(l/trc :hint "explicit rollback requested (savepoint)")
(.rollback conn sp))))
(defn transact!
"A lower-level function for executing function in a transaction"
([transactable f] (transact! transactable f {}))
([transactable f opts]
(binding [next.jdbc.transaction/*nested-tx* :ignore]
(jdbc/transact transactable f opts))))
(defn tx-run!
"Run a function in a transaction."
[system f & params]
(cond
(connection? system)
(if (connection? system)
(tx-run! {::conn system} f)
(pool? system)
(tx-run! {::pool system} f)
(::conn system)
(let [conn (::conn system)
sp (savepoint conn)]
(try
(let [system' (-> system
(assoc ::savepoint sp)
(dissoc ::rollback))
result (apply f system' params)]
(if (::rollback system)
(rollback! conn sp)
(release! conn sp))
result)
(catch Throwable cause
(.rollback ^Connection conn ^Savepoint sp)
(throw cause))))
(::pool system)
(with-atomic [conn (::pool system)]
(let [system' (-> system
(assoc ::conn conn)
(dissoc ::rollback))
result (apply f system' params)]
(when (::rollback system)
(rollback! conn))
result))
:else
(throw (IllegalArgumentException. "invalid system/cfg provided"))))
(if (pool? system)
(tx-run! {::pool system} f)
(if-let [conn (or (::conn system)
(::pool system))]
(transact! conn
(fn [conn]
(let [system' (-> system
(dissoc ::rollback)
(assoc ::conn conn))]
(apply f system' params)))
{:rollback-only (::rollback system)
:read-only (::read-only system)})
(throw (IllegalArgumentException. "invalid system/cfg provided"))))))
(defn run!
[system f & params]

View File

@@ -1071,7 +1071,7 @@
groups (d/group-by #(first (cfh/split-path (:path %))) assets)
;; If there is a group called as the generic-name we have to preserve it
unames (into #{} (keep str) (keys groups))
groups (rename-keys groups {generic-name (cfh/generate-unique-name unames generic-name)})
groups (rename-keys groups {generic-name (cfh/generate-unique-name generic-name unames)})
;; Split large groups in chunks of max-group-size elements
groups (loop [groups (seq groups)

View File

@@ -155,10 +155,10 @@
[["" {:middleware [[mw/server-timing]
[mw/params]
[mw/format-response]
[mw/parse-request]
[mw/errors errors/handle]
[session/soft-auth cfg]
[actoken/soft-auth cfg]
[mw/parse-request]
[mw/errors errors/handle]
[mw/restrict-methods]]}
(::mtx/routes cfg)

View File

@@ -24,7 +24,7 @@
[app.rpc.commands.profile :as profile]
[app.rpc.commands.teams :as teams]
[app.setup :as-alias setup]
[app.srepl.helpers :as srepl]
[app.srepl.main :as srepl]
[app.storage :as-alias sto]
[app.storage.tmp :as tmp]
[app.util.blob :as blob]
@@ -430,6 +430,50 @@
::yres/body "OK"}))
(defn- add-team-feature
[{:keys [params] :as request}]
(let [team-id (some-> params :team-id d/parse-uuid)
feature (some-> params :feature str)
skip-check (contains? params :skip-check)]
(when-not (contains? params :force)
(ex/raise :type :validation
:code :missing-force
:hint "missing force checkbox"))
(when (nil? team-id)
(ex/raise :type :validation
:code :invalid-team-id
:hint "provided invalid team id"))
(srepl/enable-team-feature! team-id feature :skip-check skip-check)
{::yres/status 200
::yres/headers {"content-type" "text/plain"}
::yres/body "OK"}))
(defn- remove-team-feature
[{:keys [params] :as request}]
(let [team-id (some-> params :team-id d/parse-uuid)
feature (some-> params :feature str)
skip-check (contains? params :skip-check)]
(when-not (contains? params :force)
(ex/raise :type :validation
:code :missing-force
:hint "missing force checkbox"))
(when (nil? team-id)
(ex/raise :type :validation
:code :invalid-team-id
:hint "provided invalid team id"))
(srepl/disable-team-feature! team-id feature :skip-check skip-check)
{::yres/status 200
::yres/headers {"content-type" "text/plain"}
::yres/body "OK"}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OTHER SMALL VIEWS/HANDLERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -500,6 +544,10 @@
{:handler (partial resend-email-notification cfg)}]
["/actions/reset-file-version"
{:handler (partial reset-file-version cfg)}]
["/actions/add-team-feature"
{:handler (partial add-team-feature)}]
["/actions/remove-team-feature"
{:handler (partial remove-team-feature)}]
["/file/export" {:handler (partial export-handler cfg)}]
["/file/import" {:handler (partial import-handler cfg)}]
["/file/data" {:handler (partial file-data-handler cfg)}]

View File

@@ -25,7 +25,6 @@
(let [claims (-> {}
(into (::session/token-claims request))
(into (::actoken/token-claims request)))]
{:request/path (:path request)
:request/method (:method request)
:request/params (:params request)
@@ -62,7 +61,8 @@
::yres/body data}
(binding [l/*context* (request->context request)]
(l/err :hint "restriction error" :data data)
(l/err :hint "restriction error"
:cause err)
{::yres/status 400
::yres/body data}))))
@@ -102,7 +102,7 @@
(= code :invalid-image)
(binding [l/*context* (request->context request)]
(let [cause (or parent-cause err)]
(l/warn :hint "unexpected error on processing image" :cause cause)
(l/warn :hint "image process error" :cause cause)
{::yres/status 400 ::yres/body data}))
:else
@@ -177,7 +177,7 @@
(let [state (.getSQLState ^java.sql.SQLException error)
cause (or parent-cause error)]
(binding [l/*context* (request->context request)]
(l/error :hint "PSQL error"
(l/error :hint "postgresql error"
:cause cause)
(cond
(= state "57014")

View File

@@ -337,16 +337,17 @@
or (updated_at is null and
created_at < now() - ?::interval)")
(defmethod ig/init-key ::tasks/gc
[_ {:keys [::db/pool ::tasks/max-age] :as cfg}]
(l/debug :hint "initializing session gc task" :max-age max-age)
(fn [_]
(db/with-atomic [conn pool]
(let [interval (db/interval max-age)
result (db/exec-one! conn [sql:delete-expired interval interval])
result (:next.jdbc/update-count result)]
(l/debug :task "gc"
:hint "clean http sessions"
:deleted result)
result))))
(defn- collect-expired-tasks
[{:keys [::db/conn ::tasks/max-age]}]
(let [interval (db/interval max-age)
result (db/exec-one! conn [sql:delete-expired interval interval])
result (:next.jdbc/update-count result)]
(l/debug :task "gc"
:hint "clean http sessions"
:deleted result)
result))
(defmethod ig/init-key ::tasks/gc
[_ {:keys [::tasks/max-age] :as cfg}]
(l/debug :hint "initializing session gc task" :max-age max-age)
(fn [_] (db/tx-run! cfg collect-expired-tasks)))

View File

@@ -273,7 +273,7 @@
(defn- http-handler
[cfg {:keys [params ::session/profile-id] :as request}]
(let [session-id (some-> params :session-id sm/parse-uuid)]
(let [session-id (some-> params :session-id uuid/parse*)]
(when-not (uuid? session-id)
(ex/raise :type :validation
:code :missing-session-id

View File

@@ -53,11 +53,16 @@
(assoc :logger/name logger)
(assoc :logger/level level)
(dissoc :request/params :value :params :data))]
(merge
{:context (-> (into (sorted-map) ctx)
(pp/pprint-str :length 50))
:props (pp/pprint-str props :length 50)
:hint (or (ex-message cause) @message)
:hint (or (when-let [message (ex-message cause)]
(if-let [props-hint (:hint props)]
(str props-hint ": " message)
message))
@message)
:trace (or (::trace record)
(some-> cause (ex/format-throwable :data? false :explain? false :header? false :summary? false)))}

View File

@@ -43,13 +43,8 @@
(decode-row token)))
(defn repl:create-access-token
[{:keys [::db/pool] :as system} profile-id name expiration]
(db/with-atomic [conn pool]
(let [props (:app.setup/props system)]
(create-access-token {::db/conn conn ::setup/props props}
profile-id
name
expiration))))
[cfg profile-id name expiration]
(db/tx-run! cfg create-access-token profile-id name expiration))
(def ^:private schema:create-access-token
[:map {:title "create-access-token"}

View File

@@ -55,7 +55,7 @@
(contains? cf/flags :login-with-password))
(ex/raise :type :restriction
:code :login-disabled
:hint "login is disabled in this instance"))
:hint "login is disabled"))
(letfn [(check-password [cfg profile password]
(if (= (:password profile) "!")
@@ -79,7 +79,8 @@
:code :wrong-credentials))
(when (:is-blocked profile)
(ex/raise :type :restriction
:code :profile-blocked))
:code :profile-blocked
:hint "profile is marked as blocked"))
(when-not (check-password cfg profile password)
(ex/raise :type :validation
:code :wrong-credentials))
@@ -149,7 +150,7 @@
;; ---- COMMAND: Recover Profile
(defn recover-profile
[{:keys [::db/pool] :as cfg} {:keys [token password]}]
[{:keys [::db/conn] :as cfg} {:keys [token password]}]
(letfn [(validate-token [token]
(let [tdata (tokens/verify (::setup/props cfg) {:token token :iss :password-recovery})]
(:profile-id tdata)))
@@ -159,10 +160,10 @@
(db/update! conn :profile {:password pwd :is-active true} {:id profile-id})
nil))]
(db/with-atomic [conn pool]
(->> (validate-token token)
(update-password conn))
nil)))
(->> (validate-token token)
(update-password conn))
nil))
(def schema:recover-profile
[:map {:title "recover-profile"}
@@ -173,7 +174,8 @@
{::rpc/auth false
::doc/added "1.15"
::sm/params schema:recover-profile
::climit/id :auth/global}
::climit/id :auth/global
::db/transaction true}
[cfg params]
(recover-profile cfg params))
@@ -182,11 +184,11 @@
(defn- validate-register-attempt!
[cfg params]
(when (or
(not (contains? cf/flags :registration))
(not (contains? cf/flags :login-with-password)))
(when (or (not (contains? cf/flags :registration))
(not (contains? cf/flags :login-with-password)))
(ex/raise :type :restriction
:code :registration-disabled))
:code :registration-disabled
:hint "registration disabled"))
(when (contains? params :invitation-token)
(let [invitation (tokens/verify (::setup/props cfg)
@@ -200,12 +202,14 @@
(when (and (email.blacklist/enabled? cfg)
(email.blacklist/contains? cfg (:email params)))
(ex/raise :type :restriction
:code :email-domain-is-not-allowed))
:code :email-domain-is-not-allowed
:hint "email domain in blacklist"))
(when (and (email.whitelist/enabled? cfg)
(not (email.whitelist/contains? cfg (:email params))))
(ex/raise :type :restriction
:code :email-domain-is-not-allowed))
:code :email-domain-is-not-allowed
:hint "email domain not in whitelist"))
;; Perform a basic validation of email & password
(when (= (str/lower (:email params))
@@ -218,13 +222,13 @@
(ex/raise :type :restriction
:code :email-has-permanent-bounces
:email (:email params)
:hint "looks like the email has bounce reports"))
:hint "email has bounce reports"))
(when (eml/has-complaint-reports? cfg (:email params))
(ex/raise :type :restriction
:code :email-has-complaints
:email (:email params)
:hint "looks like the email has complaint reports")))
:hint "email has complaint reports")))
(defn prepare-register
[{:keys [::db/pool] :as cfg} {:keys [email] :as params}]

View File

@@ -38,7 +38,6 @@
(def ^:private
schema:export-binfile
[:map {:title "export-binfile"}
[:name [:string {:max 250}]]
[:file-id ::sm/uuid]
[:version {:optional true} ::sm/int]
[:include-libraries ::sm/boolean]
@@ -78,7 +77,7 @@
"Export a penpot file in a binary format."
{::doc/added "1.15"
::webhooks/event? true
::sm/result schema:export-binfile}
::sm/params schema:export-binfile}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id version file-id] :as params}]
(files/check-read-permissions! pool profile-id file-id)
(fn [_]

View File

@@ -797,3 +797,18 @@
{:id id}
{::db/return-keys false})
nil))
(def ^:private
schema:mark-all-threads-as-read
[:map {:title "mark-all-threads-as-read"}
[:threads [:vector ::sm/uuid]]])
(sv/defmethod ::mark-all-threads-as-read
{::doc/added "1.15"
::sm/params schema:mark-all-threads-as-read}
[cfg {:keys [::rpc/profile-id threads] :as params}]
(db/tx-run!
cfg
(fn [{:keys [::db/conn]}]
(doseq [thread-id threads]
(upsert-comment-thread-status! conn profile-id thread-id)))))

View File

@@ -27,7 +27,7 @@
{::rpc/auth false
::doc/added "1.15"
::doc/changes ["1.15" "This method is migrated from mutations to commands."]}
[{:keys [::db/pool] :as cfg} _]
[cfg _]
(when-not (contains? cf/flags :demo-users)
(ex/raise :type :validation
@@ -49,9 +49,11 @@
:password (profile/derive-password cfg password)
:props {}}]
(db/with-atomic [conn pool]
(let [profile (->> (auth/create-profile! conn params)
(auth/create-profile-rels! conn))]
(with-meta {:email email
:password password}
{::audit/profile-id (:id profile)})))))
(let [profile (db/tx-run! cfg (fn [{:keys [::db/conn]}]
(->> (auth/create-profile! conn params)
(auth/create-profile-rels! conn))))]
(with-meta {:email email
:password password}
{::audit/profile-id (:id profile)}))))

View File

@@ -292,7 +292,7 @@
(defn get-file-etag
[{:keys [::rpc/profile-id]} {:keys [modified-at revn vern permissions]}]
(str profile-id "/" revn "/" vern "/"
(str profile-id "/" revn "/" vern "/" (hash fmg/available-migrations) "/"
(dt/format-instant modified-at :iso)
"/"
(uri/map->query-string permissions)))
@@ -323,11 +323,12 @@
file (-> (get-file cfg id :project-id project-id)
(assoc :permissions perms)
(assoc :team-id (:id team))
(check-version!))]
(-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params)))
(cfeat/check-file-features! (:features file)))
;; This operation is needed for backward comapatibility with frontends that
;; does not support pointer-map resolution mechanism; this just resolves the
@@ -489,7 +490,7 @@
_ (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params)))
(cfeat/check-file-features! (:features file)))
page (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
(let [page-id (or page-id (-> file :data :pages first))
@@ -613,6 +614,7 @@
SELECT l.id,
l.features,
l.project_id,
p.team_id,
l.created_at,
l.modified_at,
l.deleted_at,
@@ -622,6 +624,7 @@
l.synced_at,
l.is_shared
FROM libs AS l
INNER JOIN project AS p ON (p.id = l.project_id)
WHERE l.deleted_at IS NULL OR l.deleted_at > now();")
(defn get-file-libraries
@@ -734,7 +737,7 @@
(-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params)))
(cfeat/check-file-features! (:features file)))
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
{:name (:name file)
@@ -803,17 +806,17 @@
[:id ::sm/uuid]
[:name [:string {:max 250}]]
[:created-at ::dt/instant]
[:modified-at ::dt/instant]]}
[:modified-at ::dt/instant]]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id id)
(let [file (rename-file conn params)]
(rph/with-meta
(select-keys file [:id :name :created-at :modified-at])
{::audit/props {:project-id (:project-id file)
:created-at (:created-at file)
:modified-at (:modified-at file)}}))))
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(check-edition-permissions! conn profile-id id)
(let [file (rename-file conn params)]
(rph/with-meta
(select-keys file [:id :name :created-at :modified-at])
{::audit/props {:project-id (:project-id file)
:created-at (:created-at file)
:modified-at (:modified-at file)}})))
;; --- MUTATION COMMAND: set-file-shared
@@ -1005,15 +1008,17 @@
{::doc/added "1.17"
::webhooks/event? true
::sm/params schema:link-file-to-library}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id library-id] :as params}]
[cfg {:keys [::rpc/profile-id file-id library-id] :as params}]
(when (= file-id library-id)
(ex/raise :type :validation
:code :invalid-library
:hint "A file cannot be linked to itself"))
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(check-edition-permissions! conn profile-id library-id)
(link-file-to-library conn params)))
(db/tx-run! cfg
(fn [{:keys [::db/conn]}]
(check-edition-permissions! conn profile-id file-id)
(check-edition-permissions! conn profile-id library-id)
(link-file-to-library conn params))))
;; --- MUTATION COMMAND: unlink-file-from-library
@@ -1031,12 +1036,12 @@
(sv/defmethod ::unlink-file-from-library
{::doc/added "1.17"
::webhooks/event? true
::sm/params schema:unlink-file-to-library}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(unlink-file-from-library conn params)
nil))
::sm/params schema:unlink-file-to-library
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(check-edition-permissions! conn profile-id file-id)
(unlink-file-from-library conn params)
nil)
;; --- MUTATION COMMAND: update-sync
@@ -1056,12 +1061,11 @@
(sv/defmethod ::update-file-library-sync-status
"Update the synchronization status of a file->library link"
{::doc/added "1.17"
::sm/params schema:update-file-library-sync-status}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(update-sync conn params)))
::sm/params schema:update-file-library-sync-status
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id file-id] :as params}]
(check-edition-permissions! conn profile-id file-id)
(update-sync conn params))
;; --- MUTATION COMMAND: ignore-sync
@@ -1082,9 +1086,9 @@
(sv/defmethod ::ignore-file-library-sync-status
"Ignore updates in linked files"
{::doc/added "1.17"
::sm/params schema:ignore-file-library-sync-status}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(-> (ignore-sync conn params)
(update :features db/decode-pgarray #{}))))
::sm/params schema:ignore-file-library-sync-status
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id file-id] :as params}]
(check-edition-permissions! conn profile-id file-id)
(-> (ignore-sync conn params)
(update :features db/decode-pgarray #{})))

View File

@@ -91,9 +91,6 @@
:project-id project-id)
team-id (:id team)
;; When we create files, we only need to respect the team
;; features, because some features can be enabled
;; globally, but the team is still not migrated properly.
features (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params)))
@@ -107,7 +104,7 @@
params (-> params
(assoc :profile-id profile-id)
(assoc :features (set/difference features cfeat/frontend-only-features)))]
(assoc :features features))]
(quotes/check! cfg {::quotes/id ::quotes/files-per-project
::quotes/team-id team-id
@@ -120,7 +117,7 @@
;; to lost team features updating
;; When newly computed features does not match exactly with
;; the features defined on team row, we update it.
;; the features defined on team row, we update it
(when (not= features (:features team))
(let [features (db/create-array conn "text" features)]
(db/update! conn :team

View File

@@ -33,11 +33,11 @@
pages of a file with specific permissions (who-comment and who-inspect)."
{::doc/added "1.18"
::doc/module :files
::sm/params schema:create-share-link}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id file-id)
(create-share-link conn (assoc params :profile-id profile-id))))
::sm/params schema:create-share-link
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id file-id] :as params}]
(files/check-edition-permissions! conn profile-id file-id)
(create-share-link conn (assoc params :profile-id profile-id)))
(defn create-share-link
[conn {:keys [profile-id file-id pages who-comment who-inspect]}]
@@ -61,10 +61,10 @@
(sv/defmethod ::delete-share-link
{::doc/added "1.18"
::doc/module ::files
::sm/params schema:delete-share-link}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(let [slink (db/get-by-id conn :share-link id)]
(files/check-edition-permissions! conn profile-id (:file-id slink))
(db/delete! conn :share-link {:id id})
nil)))
::sm/params schema:delete-share-link
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id id] :as params}]
(let [slink (db/get-by-id conn :share-link id)]
(files/check-edition-permissions! conn profile-id (:file-id slink))
(db/delete! conn :share-link {:id id})
nil))

View File

@@ -180,8 +180,7 @@
(def ^:private
schema:get-file-data-for-thumbnail
[:map {:title "get-file-data-for-thumbnail"}
[:file-id ::sm/uuid]
[:features {:optional true} ::cfeat/features]])
[:file-id ::sm/uuid]])
(def ^:private
schema:partial-file
@@ -211,8 +210,7 @@
(fmg/migrate-file)))]
(-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params)))
(cfeat/check-file-features! (:features file)))
{:file-id file-id
:revn (:revn file)

View File

@@ -142,7 +142,7 @@
features (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params)))
(cfeat/check-file-features! (:features file)))
changes (if changes-with-metadata
(->> changes-with-metadata (mapcat :changes) vec)

View File

@@ -396,45 +396,49 @@
;; --- COMMAND: Clone Template
(defn clone-template
[cfg {:keys [project-id profile-id] :as params} template]
(db/tx-run! cfg (fn [{:keys [::db/conn ::wrk/executor] :as cfg}]
;; NOTE: the importation process performs some operations
;; that are not very friendly with virtual threads, and for
;; avoid unexpected blocking of other concurrent operations
;; we dispatch that operation to a dedicated executor.
(let [template (tmp/tempfile-from template
:prefix "penpot.template."
:suffix ""
:min-age "30m")
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [project-id profile-id] :as params} template]
format (bfc/parse-file-format template)
team (teams/get-team conn
:profile-id profile-id
:project-id project-id)
cfg (-> cfg
(assoc ::bfc/project-id project-id)
(assoc ::bfc/profile-id profile-id)
(assoc ::bfc/input template)
(assoc ::bfc/features (cfeat/get-team-enabled-features cf/flags team)))
;; NOTE: the importation process performs some operations
;; that are not very friendly with virtual threads, and for
;; avoid unexpected blocking of other concurrent operations
;; we dispatch that operation to a dedicated executor.
(let [template (tmp/tempfile-from template
:prefix "penpot.template."
:suffix ""
:min-age "30m")
result (if (= format :binfile-v3)
(px/invoke! executor (partial bf.v3/import-files! cfg))
(px/invoke! executor (partial bf.v1/import-files! cfg)))]
format (bfc/parse-file-format template)
team (teams/get-team pool
:profile-id profile-id
:project-id project-id)
(db/update! conn :project
{:modified-at (dt/now)}
{:id project-id})
cfg (-> cfg
(assoc ::bfc/project-id project-id)
(assoc ::bfc/profile-id profile-id)
(assoc ::bfc/input template)
(assoc ::bfc/features (cfeat/get-team-enabled-features cf/flags team)))
(let [props (audit/clean-props params)]
(doseq [file-id result]
(let [props (assoc props :id file-id)
event (-> (audit/event-from-rpc-params params)
(assoc ::audit/profile-id profile-id)
(assoc ::audit/name "create-file")
(assoc ::audit/props props))]
(audit/submit! cfg event))))
result (if (= format :binfile-v3)
(px/invoke! executor (partial bf.v3/import-files! cfg))
(px/invoke! executor (partial bf.v1/import-files! cfg)))]
result))))
(db/tx-run! cfg
(fn [{:keys [::db/conn] :as cfg}]
(db/update! conn :project
{:modified-at (dt/now)}
{:id project-id}
{::db/return-keys false})
(let [props (audit/clean-props params)]
(doseq [file-id result]
(let [props (assoc props :id file-id)
event (-> (audit/event-from-rpc-params params)
(assoc ::audit/profile-id profile-id)
(assoc ::audit/name "create-file")
(assoc ::audit/props props))]
(audit/submit! cfg event))))))
result))
(def ^:private
schema:clone-template

View File

@@ -273,15 +273,14 @@
(sv/defmethod ::clone-file-media-object
{::doc/added "1.17"
::sm/params schema:clone-file-media-object}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id file-id)
(-> (assoc cfg :conn conn)
(clone-file-media-object params))))
::sm/params schema:clone-file-media-object
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(files/check-edition-permissions! conn profile-id file-id)
(clone-file-media-object cfg params))
(defn clone-file-media-object
[{:keys [conn]} {:keys [id file-id is-local]}]
[{:keys [::db/conn]} {:keys [id file-id is-local]}]
(let [mobj (db/get-by-id conn :file-media-object id)]
(db/insert! conn :file-media-object
{:id (uuid/next)

View File

@@ -125,32 +125,32 @@
(sv/defmethod ::update-profile
{::doc/added "1.0"
::sm/params schema:update-profile
::sm/result schema:profile}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id fullname lang theme] :as params}]
(db/with-atomic [conn pool]
;; 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))
::sm/result schema:profile
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id fullname lang theme] :as params}]
;; 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))
;; Update the profile map with direct params
profile (-> profile
(assoc :fullname fullname)
(assoc :lang lang)
(assoc :theme theme))]
;; Update the profile map with direct params
profile (-> profile
(assoc :fullname fullname)
(assoc :lang lang)
(assoc :theme theme))]
(db/update! conn :profile
{:fullname fullname
:lang lang
:theme theme
:props (db/tjson (:props profile))}
{:id profile-id})
(db/update! conn :profile
{:fullname fullname
:lang lang
:theme theme
:props (db/tjson (:props profile))}
{:id profile-id})
(-> profile
(strip-private-attrs)
(d/without-nils)
(rph/with-meta {::audit/props (audit/profile->props profile)})))))
(-> profile
(strip-private-attrs)
(d/without-nils)
(rph/with-meta {::audit/props (audit/profile->props profile)}))))
;; --- MUTATION: Update Password
@@ -169,21 +169,20 @@
(sv/defmethod ::update-profile-password
{::doc/added "1.0"
::sm/params schema:update-profile-password
::climit/id :auth/global}
::climit/id :auth/global
::db/transaction true}
[cfg {:keys [::rpc/profile-id password] :as params}]
(let [profile (validate-password! cfg (assoc params :profile-id profile-id))
session-id (::session/id params)]
(db/tx-run! cfg (fn [cfg]
(let [profile (validate-password! cfg (assoc params :profile-id profile-id))
session-id (::session/id params)]
(when (= (:email profile) (str/lower (:password params)))
(ex/raise :type :validation
:code :email-as-password
:hint "you can't use your email as password"))
(when (= (:email profile) (str/lower (:password params)))
(ex/raise :type :validation
:code :email-as-password
:hint "you can't use your email as password"))
(update-profile-password! cfg (assoc profile :password password))
(invalidate-profile-session! cfg profile-id session-id)
nil))))
(update-profile-password! cfg (assoc profile :password password))
(invalidate-profile-session! cfg profile-id session-id)
nil))
(defn- invalidate-profile-session!
"Removes all sessions except the current one."
@@ -441,37 +440,36 @@
(declare ^:private get-owned-teams)
(sv/defmethod ::delete-profile
{::doc/added "1.0"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(db/with-atomic [conn pool]
(let [teams (get-owned-teams conn profile-id)
deleted-at (dt/now)]
{::doc/added "1.0"
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id] :as params}]
(let [teams (get-owned-teams conn profile-id)
deleted-at (dt/now)]
;; If we found owned teams with participants, we don't allow
;; delete profile until the user properly transfer ownership or
;; explicitly removes all participants from the team
(when (some pos? (map :participants teams))
(ex/raise :type :validation
:code :owner-teams-with-people
:hint "The user need to transfer ownership of owned teams."
:context {:teams (mapv :id teams)}))
;; If we found owned teams with participants, we don't allow
;; delete profile until the user properly transfer ownership or
;; explicitly removes all participants from the team
(when (some pos? (map :participants teams))
(ex/raise :type :validation
:code :owner-teams-with-people
:hint "The user need to transfer ownership of owned teams."
:context {:teams (mapv :id teams)}))
;; Mark profile deleted immediatelly
(db/update! conn :profile
{:deleted-at deleted-at}
{:id profile-id})
;; Mark profile deleted immediatelly
(db/update! conn :profile
{:deleted-at deleted-at}
{:id profile-id})
;; Schedule cascade deletion to a worker
(wrk/submit! {::db/conn conn
::wrk/task :delete-object
::wrk/params {:object :profile
:deleted-at deleted-at
:id profile-id}})
;; Schedule cascade deletion to a worker
(wrk/submit! {::db/conn conn
::wrk/task :delete-object
::wrk/params {:object :profile
:deleted-at deleted-at
:id profile-id}})
(-> (rph/wrap nil)
(rph/with-transform (session/delete-fn cfg))))))
(-> (rph/wrap nil)
(rph/with-transform (session/delete-fn cfg)))))
;; --- HELPERS

View File

@@ -219,12 +219,12 @@
::sm/params schema:update-project-pin
::webhooks/batch-timeout (dt/duration "5s")
::webhooks/batch-key (webhooks/key-fn ::rpc/profile-id :id)
::webhooks/event? true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id team-id is-pinned] :as params}]
(db/with-atomic [conn pool]
(check-read-permissions! conn profile-id id)
(db/exec-one! conn [sql:update-project-pin team-id id profile-id is-pinned is-pinned])
nil))
::webhooks/event? true
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id id team-id is-pinned] :as params}]
(check-read-permissions! conn profile-id id)
(db/exec-one! conn [sql:update-project-pin team-id id profile-id is-pinned is-pinned])
nil)
;; --- MUTATION: Rename Project
@@ -238,17 +238,17 @@
(sv/defmethod ::rename-project
{::doc/added "1.18"
::sm/params schema:rename-project
::webhooks/event? true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id name] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id id)
(let [project (db/get-by-id conn :project id ::sql/for-update true)]
(db/update! conn :project
{:name name}
{:id id})
(rph/with-meta (rph/wrap)
{::audit/props {:team-id (:team-id project)
:prev-name (:name project)}}))))
::webhooks/event? true
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id id name] :as params}]
(check-edition-permissions! conn profile-id id)
(let [project (db/get-by-id conn :project id ::sql/for-update true)]
(db/update! conn :project
{:name name}
{:id id})
(rph/with-meta (rph/wrap)
{::audit/props {:team-id (:team-id project)
:prev-name (:name project)}})))
;; --- MUTATION: Delete Project
@@ -280,13 +280,13 @@
(sv/defmethod ::delete-project
{::doc/added "1.18"
::sm/params schema:delete-project
::webhooks/event? true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id id)
(let [project (delete-project conn id)]
(rph/with-meta (rph/wrap)
{::audit/props {:team-id (:team-id project)
:name (:name project)
:created-at (:created-at project)
:modified-at (:modified-at project)}}))))
::webhooks/event? true
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id id] :as params}]
(check-edition-permissions! conn profile-id id)
(let [project (delete-project conn id)]
(rph/with-meta (rph/wrap)
{::audit/props {:team-id (:team-id project)
:name (:name project)
:created-at (:created-at project)
:modified-at (:modified-at project)}})))

View File

@@ -527,14 +527,14 @@
(sv/defmethod ::update-team
{::doc/added "1.17"
::sm/params schema:update-team}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id name] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id id)
(db/update! conn :team
{:name name}
{:id id})
nil))
::sm/params schema:update-team
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id id name]}]
(check-edition-permissions! conn profile-id id)
(db/update! conn :team
{:name name}
{:id id})
nil)
;; --- Mutation: Leave Team
@@ -592,10 +592,10 @@
(sv/defmethod ::leave-team
{::doc/added "1.17"
::sm/params schema:leave-team}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(db/with-atomic [conn pool]
(leave-team conn (assoc params :profile-id profile-id))))
::sm/params schema:leave-team
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id] :as params}]
(leave-team conn (assoc params :profile-id profile-id)))
;; --- Mutation: Delete Team
@@ -627,16 +627,16 @@
(sv/defmethod ::delete-team
{::doc/added "1.17"
::sm/params schema:delete-team}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(let [perms (get-permissions conn profile-id id)]
(when-not (:is-owner perms)
(ex/raise :type :validation
:code :only-owner-can-delete-team))
::sm/params schema:delete-team
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(let [perms (get-permissions conn profile-id id)]
(when-not (:is-owner perms)
(ex/raise :type :validation
:code :only-owner-can-delete-team))
(delete-team conn id)
nil)))
(delete-team conn id)
nil))
;; --- Mutation: Team Update Role
@@ -714,31 +714,30 @@
(sv/defmethod ::delete-team-member
{::doc/added "1.17"
::sm/params schema:delete-team-member}
[{:keys [::db/pool ::mbus/msgbus] :as cfg} {:keys [::rpc/profile-id team-id member-id] :as params}]
(db/with-atomic [conn pool]
(let [team (get-team pool :profile-id profile-id :team-id team-id)
perms (get-permissions conn profile-id team-id)]
(when-not (or (:is-owner perms)
(:is-admin perms))
(ex/raise :type :validation
:code :insufficient-permissions))
::sm/params schema:delete-team-member
::db/transaction true}
[{:keys [::db/conn ::mbus/msgbus] :as cfg} {:keys [::rpc/profile-id team-id member-id] :as params}]
(let [team (get-team conn :profile-id profile-id :team-id team-id)
perms (get-permissions conn profile-id team-id)]
(when-not (or (:is-owner perms)
(:is-admin perms))
(ex/raise :type :validation
:code :insufficient-permissions))
(when (= member-id profile-id)
(ex/raise :type :validation
:code :cant-remove-yourself))
(when (= member-id profile-id)
(ex/raise :type :validation
:code :cant-remove-yourself))
(db/delete! conn :team-profile-rel {:profile-id member-id
:team-id team-id})
(db/delete! conn :team-profile-rel {:profile-id member-id
:team-id team-id})
(mbus/pub! msgbus
:topic member-id
:message {:type :team-membership-change
:change :removed
:team-id team-id
:team-name (:name team)})
(mbus/pub! msgbus
:topic member-id
:message {:type :team-membership-change
:change :removed
:team-id team-id
:team-name (:name team)})
nil)))
nil))
;; --- Mutation: Update Team Photo
@@ -764,16 +763,16 @@
(let [team (get-team pool :profile-id profile-id :team-id team-id)
photo (profile/upload-photo cfg params)]
(db/with-atomic [conn pool]
(check-admin-permissions! conn profile-id team-id)
;; Mark object as touched for make it ellegible for tentative
;; garbage collection.
(when-let [id (:photo-id team)]
(sto/touch-object! storage id))
(check-admin-permissions! pool profile-id team-id)
;; Save new photo
(db/update! pool :team
{:photo-id (:id photo)}
{:id team-id})
;; Mark object as touched for make it ellegible for tentative
;; garbage collection.
(when-let [id (:photo-id team)]
(sto/touch-object! storage id))
(assoc team :photo-id (:id photo)))))
;; Save new photo
(db/update! pool :team
{:photo-id (:id photo)}
{:id team-id})
(assoc team :photo-id (:id photo))))

View File

@@ -408,20 +408,20 @@
(sv/defmethod ::update-team-invitation-role
{::doc/added "1.17"
::doc/module :teams
::sm/params schema:update-team-invitation-role}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email role] :as params}]
(db/with-atomic [conn pool]
(let [perms (teams/get-permissions conn profile-id team-id)]
::sm/params schema:update-team-invitation-role
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id team-id email role] :as params}]
(let [perms (teams/get-permissions conn profile-id team-id)]
(when-not (:is-admin perms)
(ex/raise :type :validation
:code :insufficient-permissions))
(when-not (:is-admin perms)
(ex/raise :type :validation
:code :insufficient-permissions))
(db/update! conn :team-invitation
{:role (name role) :updated-at (dt/now)}
{:team-id team-id :email-to (profile/clean-email email)})
(db/update! conn :team-invitation
{:role (name role) :updated-at (dt/now)}
{:team-id team-id :email-to (profile/clean-email email)})
nil)))
nil))
;; --- Mutation: Delete invitation
@@ -432,20 +432,20 @@
(sv/defmethod ::delete-team-invitation
{::doc/added "1.17"
::sm/params schema:delete-team-invition}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email] :as params}]
(db/with-atomic [conn pool]
(let [perms (teams/get-permissions conn profile-id team-id)]
::sm/params schema:delete-team-invition
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id team-id email] :as params}]
(let [perms (teams/get-permissions conn profile-id team-id)]
(when-not (:is-admin perms)
(ex/raise :type :validation
:code :insufficient-permissions))
(when-not (:is-admin perms)
(ex/raise :type :validation
:code :insufficient-permissions))
(let [invitation (db/delete! conn :team-invitation
{:team-id team-id
:email-to (profile/clean-email email)}
{::db/return-keys true})]
(rph/wrap nil {::audit/props {:invitation-id (:id invitation)}})))))
(let [invitation (db/delete! conn :team-invitation
{:team-id team-id
:email-to (profile/clean-email email)}
{::db/return-keys true})]
(rph/wrap nil {::audit/props {:invitation-id (:id invitation)}}))))
;; --- Mutation: Request Team Invitation

View File

@@ -78,7 +78,10 @@
:always
(update :data select-keys [:id :options :pages :pages-index :components]))
libs (files/get-file-libraries conn file-id)
libs (->> (files/get-file-libraries conn file-id)
(mapv (fn [{:keys [id] :as lib}]
(merge lib (files/get-file cfg id)))))
links (->> (db/query conn :share-link {:file-id file-id})
(mapv (fn [row]
(-> row

View File

@@ -144,20 +144,20 @@
(sv/defmethod ::delete-webhook
{::doc/added "1.17"
::sm/params schema:delete-webhook}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id]}]
(db/with-atomic [conn pool]
(let [whook (-> (db/get conn :webhook {:id id}) decode-row)]
(check-webhook-edition-permissions! conn profile-id (:team-id whook) (:profile-id whook))
(db/delete! conn :webhook {:id id})
nil)))
::sm/params schema:delete-webhook
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id id]}]
(let [whook (-> (db/get conn :webhook {:id id}) decode-row)]
(check-webhook-edition-permissions! conn profile-id (:team-id whook) (:profile-id whook))
(db/delete! conn :webhook {:id id})
nil))
;; --- Query: Webhooks
(def sql:get-webhooks
"SELECT id, uri, mtype, is_active, error_code, error_count, profile_id
FROM webhook
WHERE team_id = ?
"SELECT id, uri, mtype, is_active, error_code, error_count, profile_id
FROM webhook
WHERE team_id = ?
ORDER BY uri")
(def ^:private schema:get-webhooks

View File

@@ -78,19 +78,19 @@
(defmethod ig/init-key ::props
[_ {:keys [::db/pool ::key] :as cfg}]
(db/with-atomic [conn pool]
(db/xact-lock! conn 0)
(when-not key
(l/warn :hint (str "using autogenerated secret-key, it will change on each restart and will invalidate "
"all sessions on each restart, it is highly recommended setting up the "
"PENPOT_SECRET_KEY environment variable")))
(let [secret (or key (generate-random-key))]
(-> (get-all-props conn)
(assoc :secret-key secret)
(assoc :tokens-key (keys/derive secret :salt "tokens"))
(update :instance-id handle-instance-id conn (db/read-only? pool))))))
(db/tx-run! cfg (fn [{:keys [::db/conn]}]
(db/xact-lock! conn 0)
(when-not key
(l/warn :hint (str "using autogenerated secret-key, it will change on each restart and will invalidate "
"all sessions on each restart, it is highly recommended setting up the "
"PENPOT_SECRET_KEY environment variable")))
(let [secret (or key (generate-random-key))]
(-> (get-all-props conn)
(assoc :secret-key secret)
(assoc :tokens-key (keys/derive secret :salt "tokens"))
(update :instance-id handle-instance-id conn (db/read-only? pool)))))))
;; FIXME
(sm/register! ::props :any)

View File

@@ -36,37 +36,39 @@
(defmethod exec-command :create-profile
[{:keys [fullname email password is-active]
:or {is-active true}}]
(when-let [system (get-current-system)]
(db/with-atomic [conn (:app.db/pool system)]
(let [password (cmd.profile/derive-password system password)
params {:id (uuid/next)
:email email
:fullname fullname
:is-active is-active
:password password
:props {}}]
(->> (cmd.auth/create-profile! conn params)
(cmd.auth/create-profile-rels! conn))))))
(some-> (get-current-system)
(db/tx-run!
(fn [{:keys [::db/conn] :as system}]
(let [password (cmd.profile/derive-password system password)
params {:id (uuid/next)
:email email
:fullname fullname
:is-active is-active
:password password
:props {}}]
(->> (cmd.auth/create-profile! conn params)
(cmd.auth/create-profile-rels! conn)))))))
(defmethod exec-command :update-profile
[{:keys [fullname email password is-active]}]
(when-let [system (get-current-system)]
(db/with-atomic [conn (:app.db/pool system)]
(let [params (cond-> {}
(some? fullname)
(assoc :fullname fullname)
(some-> (get-current-system)
(db/tx-run!
(fn [{:keys [::db/conn] :as system}]
(let [params (cond-> {}
(some? fullname)
(assoc :fullname fullname)
(some? password)
(assoc :password (auth/derive-password password))
(some? password)
(assoc :password (auth/derive-password password))
(some? is-active)
(assoc :is-active is-active))]
(when (seq params)
(let [res (db/update! conn :profile
params
{:email email
:deleted-at nil})]
(pos? (db/get-update-count res))))))))
(some? is-active)
(assoc :is-active is-active))]
(when (seq params)
(let [res (db/update! conn :profile
params
{:email email
:deleted-at nil})]
(pos? (db/get-update-count res)))))))))
(defmethod exec-command :delete-profile
[{:keys [email soft]}]
@@ -75,16 +77,16 @@
:code :invalid-arguments
:hint "email should be provided"))
(when-let [system (get-current-system)]
(db/with-atomic [conn (:app.db/pool system)]
(let [res (if soft
(db/update! conn :profile
{:deleted-at (dt/now)}
{:email email :deleted-at nil})
(db/delete! conn :profile
{:email email}))]
(pos? (db/get-update-count res))))))
(some-> (get-current-system)
(db/tx-run!
(fn [{:keys [::db/conn] :as system}]
(let [res (if soft
(db/update! conn :profile
{:deleted-at (dt/now)}
{:email email :deleted-at nil})
(db/delete! conn :profile
{:email email}))]
(pos? (db/get-update-count res)))))))
(defmethod exec-command :search-profile
[{:keys [email]}]
@@ -93,12 +95,12 @@
:code :invalid-arguments
:hint "email should be provided"))
(when-let [system (get-current-system)]
(db/with-atomic [conn (:app.db/pool system)]
(let [sql (str "select email, fullname, created_at, deleted_at from profile "
" where email similar to ? order by created_at desc limit 100")]
(db/exec! conn [sql email])))))
(some-> (get-current-system)
(db/tx-run!
(fn [{:keys [::db/conn] :as system}]
(let [sql (str "select email, fullname, created_at, deleted_at from profile "
" where email similar to ? order by created_at desc limit 100")]
(db/exec! conn [sql email]))))))
(defmethod exec-command :derive-password
[{:keys [password]}]

View File

@@ -10,6 +10,7 @@
(:require
[app.binfile.common :as bfc]
[app.common.data :as d]
[app.common.files.migrations :as fmg]
[app.common.files.validate :as cfv]
[app.db :as db]
[app.features.components-v2 :as feat.comp-v2]
@@ -142,7 +143,9 @@
(update-fn file opts)))]
(when (and (some? file')
(not (identical? file file')))
(or (fmg/migrated? file)
(not (identical? file file'))))
(when validate?
(cfv/validate-file-schema! file'))

View File

@@ -101,38 +101,46 @@
"Mark the profile blocked and removes all the http sessiones
associated with the profile-id."
[email]
(db/with-atomic [conn (:app.db/pool main/system)]
(when-let [profile (db/get* conn :profile
{:email (str/lower email)}
{:columns [:id :email]})]
(when-not (:is-blocked profile)
(db/update! conn :profile {:is-active true} {:id (:id profile)})
:activated))))
(some-> main/system
(db/tx-run!
(fn [{:keys [::db/conn] :as system}]
(when-let [profile (db/get* conn :profile
{:email (str/lower email)}
{:columns [:id :email]})]
(when-not (:is-blocked profile)
(db/update! conn :profile {:is-active true} {:id (:id profile)})
:activated))))))
(defn mark-profile-as-blocked!
"Mark the profile blocked and removes all the http sessiones
associated with the profile-id."
[email]
(db/with-atomic [conn (:app.db/pool main/system)]
(when-let [profile (db/get* conn :profile
{:email (str/lower email)}
{:columns [:id :email]})]
(when-not (:is-blocked profile)
(db/update! conn :profile {:is-blocked true} {:id (:id profile)})
(db/delete! conn :http-session {:profile-id (:id profile)})
:blocked))))
(some-> main/system
(db/tx-run!
(fn [{:keys [::db/conn] :as system}]
(when-let [profile (db/get* conn :profile
{:email (str/lower email)}
{:columns [:id :email]})]
(when-not (:is-blocked profile)
(db/update! conn :profile {:is-blocked true} {:id (:id profile)})
(db/delete! conn :http-session {:profile-id (:id profile)})
:blocked))))))
(defn reset-password!
"Reset a password to a specific one for a concrete user or all users
if email is `:all` keyword."
[& {:keys [email password] :or {password "123123"} :as params}]
(us/verify! (contains? params :email) "`email` parameter is mandatory")
(db/with-atomic [conn (:app.db/pool main/system)]
(let [password (derive-password password)]
(if (= email :all)
(db/exec! conn ["update profile set password=?" password])
(let [email (str/lower email)]
(db/exec! conn ["update profile set password=? where email=?" password email]))))))
(when-not email
(throw (IllegalArgumentException. "email is mandatory")))
(some-> main/system
(db/tx-run!
(fn [{:keys [::db/conn] :as system}]
(let [password (derive-password password)]
(if (= email :all)
(db/exec! conn ["update profile set password=?" password])
(let [email (str/lower email)]
(db/exec! conn ["update profile set password=? where email=?" password email]))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FEATURES
@@ -154,8 +162,8 @@
(enable-pointer-map-feature-on-file! file-id opts))
(defn enable-team-feature!
[team-id feature]
(when-not (contains? cfeat/supported-features feature)
[team-id feature & {:keys [skip-check] :or {skip-check false}}]
(when (and (not skip-check) (not (contains? cfeat/supported-features feature)))
(ex/raise :type :assertion
:code :feature-not-supported
:hint (str "feature '" feature "' not supported")))
@@ -173,9 +181,8 @@
:enabled))))))
(defn disable-team-feature!
[team-id feature]
(when-not (contains? cfeat/supported-features feature)
[team-id feature & {:keys [skip-check] :or {skip-check false}}]
(when (and (not skip-check) (not (contains? cfeat/supported-features feature)))
(ex/raise :type :assertion
:code :feature-not-supported
:hint (str "feature '" feature "' not supported")))
@@ -202,100 +209,116 @@
This method allows send flash notifications to specified target destinations.
The message can be a free text or a preconfigured one.
The destination can be: all, profile-id, team-id, or a coll of them."
[{:keys [::mbus/msgbus ::db/pool]} & {:keys [dest code message level]
:or {code :generic level :info}
:as params}]
The destination can be: all, profile-id, team-id, or a coll of them.
It also can be:
{:email \"some@example.com\"}
[[:email \"some@example.com\"], ...]
Command examples:
(notify! :dest :all :code :maintenance)
(notify! :dest :all :code :upgrade-version)
"
[& {:keys [dest code message level]
:or {code :generic level :info}
:as params}]
(when-not (contains? #{:success :error :info :warning} level)
(ex/raise :type :assertion
:code :incorrect-level
:hint (str "level '" level "' not supported")))
(letfn [(send [dest]
(l/inf :hint "sending notification" :dest (str dest))
(let [message {:type :notification
:code code
:level level
:version (:full cf/version)
:subs-id dest
:message message}
message (->> (dissoc params :dest :code :message :level)
(merge message))]
(mbus/pub! msgbus
:topic (str dest)
:message message)))
(let [{:keys [::mbus/msgbus ::db/pool]} main/system
(resolve-profile [email]
(some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector))
send
(fn [dest]
(l/inf :hint "sending notification" :dest (str dest))
(let [message {:type :notification
:code code
:level level
:version (:full cf/version)
:subs-id dest
:message message}
message (->> (dissoc params :dest :code :message :level)
(merge message))]
(mbus/pub! msgbus
:topic dest
:message message)))
(resolve-team [team-id]
(->> (db/query pool :team-profile-rel
{:team-id team-id}
{:columns [:profile-id]})
(map :profile-id)))
resolve-profile
(fn [email]
(some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector))
(resolve-dest [dest]
(cond
(= :all dest)
[uuid/zero]
resolve-team
(fn [team-id]
(->> (db/query pool :team-profile-rel
{:team-id team-id}
{:columns [:profile-id]})
(map :profile-id)))
(uuid? dest)
[dest]
resolve-dest
(fn resolve-dest [dest]
(cond
(= :all dest)
[uuid/zero]
(string? dest)
(some-> dest h/parse-uuid resolve-dest)
(uuid? dest)
[dest]
(nil? dest)
(resolve-dest uuid/zero)
(string? dest)
(some-> dest h/parse-uuid resolve-dest)
(map? dest)
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(nil? dest)
[uuid/zero]
(and (vector? dest)
(every? vector? dest))
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(map? dest)
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(and (vector? dest)
(keyword? (first dest)))
(let [[op param] dest]
(and (vector? dest)
(every? vector? dest))
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(and (vector? dest)
(keyword? (first dest)))
(let [[op param] dest]
(cond
(= op :email)
(cond
(= op :email)
(cond
(and (coll? param)
(every? string? param))
(sequence (comp
(keep resolve-profile)
(mapcat identity))
param)
(and (coll? param)
(every? string? param))
(sequence (comp
(keep resolve-profile)
(mapcat identity))
param)
(string? param)
(resolve-profile param))
(string? param)
(resolve-profile param))
(= op :team-id)
(cond
(coll? param)
(sequence (comp
(mapcat resolve-team)
(keep h/parse-uuid))
param)
(= op :team-id)
(cond
(coll? param)
(sequence (comp
(mapcat resolve-team)
(keep h/parse-uuid))
param)
(uuid? param)
(resolve-team param)
(uuid? param)
(resolve-team param)
(string? param)
(some-> param h/parse-uuid resolve-team))
(string? param)
(some-> param h/parse-uuid resolve-team))
(= op :profile-id)
(if (coll? param)
(sequence (keep h/parse-uuid) param)
(resolve-dest param))))))]
(= op :profile-id)
(if (coll? param)
(sequence (keep h/parse-uuid) param)
(resolve-dest param))))))]
(->> (resolve-dest dest)
(filter some?)
@@ -314,14 +337,23 @@
(db/tx-run! main/system fsnap/create-file-snapshot! {:file-id file-id :label label})))
(defn restore-file-snapshot!
[file-id label]
(let [file-id (h/parse-uuid file-id)]
[file-id & {:keys [label id]}]
(let [file-id (h/parse-uuid file-id)
snapshot-id (some-> id h/parse-uuid)]
(db/tx-run! main/system
(fn [{:keys [::db/conn] :as system}]
(when-let [snapshot (->> (h/search-file-snapshots conn #{file-id} label)
(map :id)
(first))]
(fsnap/restore-file-snapshot! system file-id (:id snapshot)))))))
(cond
(uuid? snapshot-id)
(fsnap/restore-file-snapshot! system file-id snapshot-id)
(string? label)
(->> (h/search-file-snapshots conn #{file-id} label)
(map :id)
(first)
(fsnap/restore-file-snapshot! system file-id))
:else
(throw (ex-info "snapshot id or label should be provided" {})))))))
(defn list-file-snapshots!
[file-id & {:as _}]

View File

@@ -26,18 +26,14 @@
{k (assoc v ::min-age (cf/get-deletion-delay))})
(defmethod ig/init-key ::handler
[_ {:keys [::db/pool ::min-age] :as cfg}]
[_ {:keys [::min-age] :as cfg}]
(fn [{:keys [props] :as task}]
(let [min-age (or (:min-age props) min-age)]
(db/with-atomic [conn pool]
(let [interval (db/interval min-age)
result (db/exec-one! conn [sql:delete-completed-tasks interval])
result (db/get-update-count result)]
(l/debug :hint "task finished" :total result)
(when (:rollback? props)
(db/rollback! conn))
result)))))
(-> cfg
(assoc ::db/rollback (:rollback? props))
(db/tx-run! (fn [{:keys [::db/conn]}]
(let [interval (db/interval min-age)
result (db/exec-one! conn [sql:delete-completed-tasks interval])
result (db/get-update-count result)]
(l/debug :hint "task finished" :total result)
result)))))))

View File

@@ -71,11 +71,12 @@
(run-batch! [rconn]
(try
(db/with-atomic [conn pool]
(if-let [tasks (get-tasks conn)]
(->> (group-by :queue tasks)
(run! (partial push-tasks! conn rconn)))
(px/sleep (::wait-duration cfg))))
(db/tx-run! cfg (fn [{:keys [::db/conn]}]
(if-let [tasks (get-tasks conn)]
(->> (group-by :queue tasks)
(run! (partial push-tasks! conn rconn)))
;; FIXME: this sleep should be outside the transaction
(px/sleep (::wait-duration cfg)))))
(catch InterruptedException cause
(throw cause))
(catch Exception cause

View File

@@ -24,6 +24,33 @@
(set! *warn-on-reflection* true)
(def schema:task
[:map {:title "Task"}
[:id ::sm/uuid]
[:queue :string]
[:name :string]
[:created-at ::sm/inst]
[:modified-at ::sm/inst]
[:scheduled-at {:optional true} ::sm/inst]
[:completed-at {:optional true} ::sm/inst]
[:error {:optional true} :string]
[:max-retries :int]
[:retry-num :int]
[:priority :int]
[:status [:enum "scheduled" "completed" "new" "retry" "failed"]]
[:label {:optional true} :string]
[:props :map]])
(def schema:result
[:map {:title "TaskResult"}
[:status [:enum "retry" "failed" "completed"]]
[:error {:optional true} [:fn ex/exception?]]
[:inc-by {:optional true} :int]
[:delay {:optional true} :int]])
(def valid-task-result?
(sm/validator schema:result))
(defn- decode-task-row
[{:keys [props] :as row}]
(cond-> row
@@ -51,10 +78,11 @@
:retry (:retry-num task))
(let [tpoint (dt/tpoint)
task-fn (wrk/get-task registry (:name task))
result (if task-fn
(task-fn task)
{:status :completed :task task})
elapsed (dt/format-duration (tpoint))]
result (when task-fn (task-fn task))
elapsed (dt/format-duration (tpoint))
result (if (valid-task-result? result)
result
{:status "completed"})]
(when-not task-fn
(l/wrn :hint "no task handler found" :name (:name task)))
@@ -76,7 +104,7 @@
(if (and (< (:retry-num task)
(:max-retries task))
(= ::retry (:type edata)))
(cond-> {:status :retry :task task :error cause}
(cond-> {:status "retry" :error cause}
(dt/duration? (:delay edata))
(assoc :delay (:delay edata))
@@ -87,8 +115,8 @@
::l/context (get-error-context cause task)
:cause cause)
(if (>= (:retry-num task) (:max-retries task))
{:status :failed :task task :error cause}
{:status :retry :task task :error cause})))))))
{:status "failed" :error cause}
{:status "retry" :error cause})))))))
(defn- run-task!
[{:keys [::id ::timeout] :as cfg} task-id]
@@ -116,12 +144,17 @@
:task-id task-id)
:else
(run-task cfg task))))
(let [result (run-task cfg task)]
(with-meta result
{::task task})))))
(defn- run-worker-loop!
[{:keys [::db/pool ::rds/rconn ::timeout ::queue] :as cfg}]
(letfn [(handle-task-retry [{:keys [task error inc-by delay] :or {inc-by 1 delay 1000}}]
(let [explain (ex-message error)
(letfn [(handle-task-retry [{:keys [error inc-by delay] :or {inc-by 1 delay 1000} :as result}]
(let [explain (if (ex/exception? error)
(ex-message error)
(str error))
task (-> result meta ::task)
nretry (+ (:retry-num task) inc-by)
now (dt/now)
delay (->> (iterate #(* % 2) delay) (take nretry) (last))]
@@ -134,8 +167,9 @@
{:id (:id task)})
nil))
(handle-task-failure [{:keys [task error]}]
(let [explain (ex-message error)]
(handle-task-failure [{:keys [error] :as result}]
(let [task (-> result meta ::task)
explain (ex-message error)]
(db/update! pool :task
{:error explain
:modified-at (dt/now)
@@ -143,8 +177,9 @@
{:id (:id task)})
nil))
(handle-task-completion [{:keys [task]}]
(let [now (dt/now)]
(handle-task-completion [result]
(let [task (-> result meta ::task)
now (dt/now)]
(db/update! pool :task
{:completed-at now
:modified-at now
@@ -168,10 +203,11 @@
(process-result [{:keys [status] :as result}]
(ex/try!
(case status
:retry (handle-task-retry result)
:failed (handle-task-failure result)
:completed (handle-task-completion result)
nil)))
"retry" (handle-task-retry result)
"failed" (handle-task-failure result)
"completed" (handle-task-completion result)
(throw (IllegalArgumentException.
(str "invalid status received: " status))))))
(run-task-loop [task-id]
(loop [result (run-task! cfg task-id)]

View File

@@ -138,14 +138,13 @@
" FROM information_schema.tables "
" WHERE table_schema = 'public' "
" AND table_name != 'migrations';")]
(db/with-atomic [conn *pool*]
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
(db/exec-one! conn ["SET LOCAL rules.deletion_protection TO off"])
(let [result (->> (db/exec! conn [sql])
(map :table-name))]
(doseq [table result]
(db/exec! conn [(str "delete from " table ";")]))))
(db/transact! *pool* (fn [conn]
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
(db/exec-one! conn ["SET LOCAL rules.deletion_protection TO off"])
(let [result (->> (db/exec! conn [sql])
(map :table-name))]
(doseq [table result]
(db/exec! conn [(str "delete from " table ";")])))))
(next)))
(defn clean-storage

View File

@@ -20,45 +20,33 @@
(t/use-fixtures :each th/database-reset)
(t/deftest soft-auth-middleware
(db/with-atomic [conn (::db/pool th/*system*)]
(let [profile (th/create-profile* 1)
system (-> th/*system*
(assoc ::db/conn conn)
(assoc ::main/props (:app.setup/props th/*system*)))
(let [profile (th/create-profile* 1)
token (db/tx-run! th/*system* app.rpc.commands.access-token/create-access-token (:id profile) "test" nil)
token (app.rpc.commands.access-token/create-access-token
system (:id profile) "test" nil)
request (volatile! nil)
handler (#'app.http.access-token/wrap-soft-auth
(fn [req] (vreset! request req))
th/*system*)]
request (volatile! nil)
handler (#'app.http.access-token/wrap-soft-auth
(fn [req] (vreset! request req))
system)]
(with-mocks [m1 {:target 'app.http.access-token/get-token
:return nil}]
(handler {})
(t/is (= {} @request)))
(with-mocks [m1 {:target 'app.http.access-token/get-token
:return nil}]
(handler {})
(t/is (= {} @request)))
(with-mocks [m1 {:target 'app.http.access-token/get-token
:return (:token token)}]
(handler {})
(with-mocks [m1 {:target 'app.http.access-token/get-token
:return (:token token)}]
(handler {})
(let [token-id (get @request :app.http.access-token/id)]
(t/is (= token-id (:id token))))))))
(let [token-id (get @request :app.http.access-token/id)]
(t/is (= token-id (:id token)))))))
(t/deftest authz-middleware
(let [profile (th/create-profile* 1)
system (assoc th/*system* ::main/props (:app.setup/props th/*system*))
token (db/with-atomic [conn (::db/pool th/*system*)]
(let [system (assoc system ::db/conn conn)]
(app.rpc.commands.access-token/create-access-token
system (:id profile) "test" nil)))
token (db/tx-run! th/*system* app.rpc.commands.access-token/create-access-token (:id profile) "test" nil)
request (volatile! {})
handler (#'app.http.access-token/wrap-authz
(fn [req] (vreset! request req))
system)]
th/*system*)]
(handler nil)
(t/is (nil? @request))

View File

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

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728",
"packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6",
"type": "module",
"repository": {
"type": "git",

View File

@@ -342,13 +342,20 @@
(-> (hex->hsl data)
(conj opacity)))
#?(:cljs
(defn format-hsla
[[h s l a]]
(let [precision 2
rounded-s (* 100 (parse-double (d/format-precision s precision)))
rounded-l (* 100 (parse-double (d/format-precision l precision)))]
(str/concat "" h ", " rounded-s "%, " rounded-l "%, " a))))
(defn format-hsla
[[h s l a]]
(let [precision 2
rounded-h (int h)
rounded-s (d/format-number (* 100 s) precision)
rounded-l (d/format-number (* 100 l) precision)
rounded-a (d/format-number a precision)]
(str/concat "" rounded-h ", " rounded-s "%, " rounded-l "%, " rounded-a)))
(defn format-rgba
[[r g b a]]
(let [precision 2
rounded-a (d/format-number a precision)]
(str/ffmt "%, %, %, %" r g b rounded-a)))
(defn- hue->rgb
"Helper for hsl->rgb"

View File

@@ -82,15 +82,22 @@
"Assoc a k v pair, in the order position just before the other key."
[o ks k v before-k]
(let [f (fn [o']
(cond-> (reduce
(fn [acc [k' v']]
(cond
(and before-k (= k' before-k)) (assoc acc k v k' v')
(= k k') acc
:else (assoc acc k' v')))
(ordered-map)
o')
(not before-k) (assoc k v)))]
(let [found (volatile! false)
result (reduce
(fn [acc [k' v']]
(cond
(and before-k (= k' before-k))
(do
(vreset! found true)
(assoc acc k v k' v'))
(= k k') acc
:else (assoc acc k' v')))
(ordered-map)
o')]
(if (or (not before-k) (not @found))
(assoc result k v)
result)))]
(if (seq ks)
(oupdate-in o ks f)
(f o))))
@@ -1007,27 +1014,35 @@
(def ^:const trail-zeros-regex-1 #"\.0+$")
(def ^:const trail-zeros-regex-2 #"(\.\d*[^0])0+$")
#?(:cljs
(defn format-precision
"Creates a number with predetermined precision and then removes the trailing 0.
(defn format-precision
"Creates a number with predetermined precision and then removes the trailing 0.
Examples:
12.0123, 0 => 12
12.0123, 1 => 12
12.0123, 2 => 12.01"
[num precision]
[num precision]
(if (number? num)
(try
(let [num-str (mth/to-fixed num precision)
(if (number? num)
(try
(let [num-str (mth/to-fixed num precision)
;; Remove all trailing zeros after the comma 100.00000
num-str (str/replace num-str trail-zeros-regex-1 "")]
num-str (str/replace num-str trail-zeros-regex-1 "")]
;; Remove trailing zeros after a decimal number: 0.001|00|
(if-let [m (re-find trail-zeros-regex-2 num-str)]
(str/replace num-str (first m) (second m))
num-str))
(catch :default _
(str num)))
(str num))))
(if-let [m (re-find trail-zeros-regex-2 num-str)]
(str/replace num-str (first m) (second m))
num-str))
(catch #?(:clj Throwable :cljs :default) _
(str num)))
(str num)))
(defn format-number
([value]
(format-number value nil))
([value {:keys [precision] :or {precision 2}}]
(let [value (if (string? value) (parse-double value) value)]
(when (num? value)
(let [value (format-precision value precision)]
(str value))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Util protocols

View File

@@ -47,7 +47,7 @@
(defn undo
[stack]
(update stack :index dec))
(update stack :index #(max 0 (dec %))))
(defn redo
[{index :index items :items :as stack}]

View File

@@ -52,7 +52,8 @@
"plugins/runtime"
"design-tokens/v1"
"text-editor/v2"
"render-wasm/v1"})
"render-wasm/v1"
"variants/v1"})
;; A set of features enabled by default
(def default-features
@@ -60,7 +61,8 @@
"styles/v2"
"layout/grid"
"components/v2"
"plugins/runtime"})
"plugins/runtime"
"design-tokens/v1"})
;; A set of features which only affects on frontend and can be enabled
;; and disabled freely by the user any time. This features does not
@@ -83,12 +85,11 @@
;; be applied (per example backend can operate in both modes with or
;; without migration applied)
(def no-migration-features
(-> #{"fdata/objects-map"
"fdata/pointer-map"
"layout/grid"
(-> #{"layout/grid"
"fdata/shape-data-type"
"design-tokens/v1"}
(into frontend-only-features)))
(into frontend-only-features)
(into backend-only-features)))
(sm/register!
^{::sm/type ::features}
@@ -111,6 +112,7 @@
:feature-design-tokens "design-tokens/v1"
:feature-text-editor-v2 "text-editor/v2"
:feature-render-wasm "render-wasm/v1"
:feature-variants "variants/v1"
nil))
(defn migrate-legacy-features
@@ -155,7 +157,6 @@
team-features (into #{} xf-remove-ephimeral (:features team))]
(-> enabled-features
(set/intersection no-migration-features)
(set/difference frontend-only-features)
(set/union team-features))))
(defn check-client-features!
@@ -164,6 +165,8 @@
frontend client"
[enabled-features client-features]
(when (set? client-features)
;; Check if client declares support for features enabled on
;; backend side
(let [not-supported (-> enabled-features
(set/difference client-features)
(set/difference frontend-only-features)
@@ -173,14 +176,6 @@
:code :feature-not-supported
:feature (first not-supported)
:hint (str/ffmt "client declares no support for '%' features"
(str/join "," not-supported)))))
(let [not-supported (set/difference client-features supported-features)]
(when (seq not-supported)
(ex/raise :type :restriction
:code :feature-not-supported
:feature (first not-supported)
:hint (str/ffmt "backend does not support '%' features requested by client"
(str/join "," not-supported))))))
enabled-features)
@@ -191,57 +186,49 @@
supported by the current backend"
[enabled-features]
(let [not-supported (set/difference enabled-features supported-features)]
(when (seq not-supported)
(when-let [not-supported (first not-supported)]
(ex/raise :type :restriction
:code :feature-not-supported
:feature (first not-supported)
:hint (str/ffmt "features '%' not supported"
(str/join "," not-supported)))))
enabled-features)
:feature not-supported
:hint (str/ffmt "feature '%' not supported on this backend" not-supported)))
enabled-features))
(defn check-file-features!
"Function used for check feature compability between currently
enabled features set on backend with the provided featured set by
the penpot file"
([enabled-features file-features]
(check-file-features! enabled-features file-features #{}))
([enabled-features file-features client-features]
(let [file-features (into #{} xf-remove-ephimeral file-features)
;; We should ignore all features that does not match with the
;; `no-migration-features` set because we can't enable them
;; as-is, because they probably need migrations
client-features (set/intersection client-features no-migration-features)]
(let [not-supported (-> enabled-features
(set/union client-features)
(set/difference file-features)
;; NOTE: we don't want to raise a feature-mismatch
;; exception for features which don't require an
;; explicit file migration process or has no real
;; effect on file data structure
(set/difference no-migration-features))]
(when (seq not-supported)
(ex/raise :type :restriction
:code :file-feature-mismatch
:feature (first not-supported)
:hint (str/ffmt "enabled features '%' not present in file (missing migration)"
(str/join "," not-supported)))))
[enabled-features file-features]
(let [file-features (into #{} xf-remove-ephimeral file-features)
not-supported (-> enabled-features
(set/difference file-features)
;; NOTE: we don't want to raise a feature-mismatch
;; exception for features which don't require an
;; explicit file migration process or has no real
;; effect on file data structure
(set/difference no-migration-features))]
(check-supported-features! file-features)
(when-let [not-supported (first not-supported)]
(ex/raise :type :restriction
:code :file-feature-mismatch
:feature not-supported
:hint (str/ffmt "enabled feature '%' not present in file (missing migration)"
not-supported)))
(let [not-supported (-> file-features
(set/difference enabled-features)
(set/difference client-features)
(set/difference backend-only-features)
(set/difference frontend-only-features))]
(check-supported-features! file-features)
(when (seq not-supported)
(ex/raise :type :restriction
:code :file-feature-mismatch
:feature (first not-supported)
:hint (str/ffmt "file features '%' not enabled"
(str/join "," not-supported))))))
(let [not-supported (-> file-features
(set/difference enabled-features)
(set/difference backend-only-features)
(set/difference frontend-only-features))]
enabled-features))
;; Check if file has a feature but that feature is not enabled
(when-let [not-supported (first not-supported)]
(ex/raise :type :restriction
:code :file-feature-mismatch
:feature not-supported
:hint (str/ffmt "file feature '%' not enabled" not-supported))))
enabled-features))
(defn check-teams-compatibility!
[{source-features :features} {destination-features :features}]

View File

@@ -371,111 +371,58 @@
[:type [:= :del-typography]]
[:id ::sm/uuid]]]
[:add-temporary-token-theme
[:map {:title "AddTemporaryTokenThemeChange"}
[:type [:= :add-temporary-token-theme]]
[:token-theme ::ctot/token-theme]]]
[:update-active-token-themes
[:map {:title "UpdateActiveTokenThemes"}
[:type [:= :update-active-token-themes]]
[:theme-ids [:set :string]]]]
[:delete-temporary-token-theme
[:map {:title "DeleteTemporaryTokenThemeChange"}
[:type [:= :delete-temporary-token-theme]]
[:id ::sm/uuid]
[:name :string]]]
[:add-token-theme
[:map {:title "AddTokenThemeChange"}
[:type [:= :add-token-theme]]
[:token-theme ::ctot/token-theme]]]
[:mod-token-theme
[:map {:title "ModTokenThemeChange"}
[:type [:= :mod-token-theme]]
[:group :string]
[:name :string]
[:token-theme ::ctot/token-theme]]]
[:del-token-theme
[:map {:title "DelTokenThemeChange"}
[:type [:= :del-token-theme]]
[:group :string]
[:name :string]]]
[:add-token-set
[:map {:title "AddTokenSetChange"}
[:type [:= :add-token-set]]
[:token-set ::ctot/token-set]]]
[:add-token-sets
[:map {:title "AddTokenSetsChange"}
[:type [:= :add-token-sets]]
[:token-sets [:sequential ::ctot/token-set]]]]
[:rename-token-set-group
[:map {:title "RenameTokenSetGroup"}
[:type [:= :rename-token-set-group]]
[:set-group-path [:vector :string]]
[:set-group-fname :string]]]
[:mod-token-set
[:map {:title "ModTokenSetChange"}
[:type [:= :mod-token-set]]
[:name :string]
[:token-set ::ctot/token-set]]]
[:move-token-set-before
[:map {:title "MoveTokenSetBefore"}
[:type [:= :move-token-set-before]]
[: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]]]]
[:before-group [:maybe :boolean]]]]
[:move-token-set-group-before
[:map {:title "MoveTokenSetGroupBefore"}
[:type [:= :move-token-set-group-before]]
[: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]]]]
[:before-group [:maybe :boolean]]]]
[:del-token-set
[:map {:title "DelTokenSetChange"}
[:type [:= :del-token-set]]
[:name :string]]]
[:del-token-set-path
[:map {:title "DelTokenSetPathChange"}
[:type [:= :del-token-set-path]]
[:path :string]]]
[:set-token-theme
[:map {:title "SetTokenThemeChange"}
[:type [:= :set-token-theme]]
[:theme-name :string]
[:group :string]
[:theme [:maybe ::ctot/token-theme]]]]
[:set-tokens-lib
[:map {:title "SetTokensLib"}
[:type [:= :set-tokens-lib]]
[:tokens-lib :any]]]
[:add-token
[:map {:title "AddTokenChange"}
[:type [:= :add-token]]
[:set-token-set
[:map {:title "SetTokenSetChange"}
[:type [:= :set-token-set]]
[:set-name :string]
[:token ::cto/token]]]
[:group? :boolean]
[:token-set [:maybe ::ctot/token-set]]]]
[:mod-token
[:map {:title "ModTokenChange"}
[:type [:= :mod-token]]
[:set-token
[:map {:title "SetTokenChange"}
[:type [:= :set-token]]
[:set-name :string]
[:name :string]
[:token ::cto/token]]]
[:del-token
[:map {:title "DelTokenChange"}
[:type [:= :del-token]]
[:set-name :string]
[:name :string]]]]])
[:token-name :string]
[:token [:maybe ::cto/token]]]]]])
(def schema:changes
[:sequential {:gen/max 5 :gen/min 1} schema:change])
@@ -1040,80 +987,63 @@
[data {:keys [tokens-lib]}]
(assoc data :tokens-lib tokens-lib))
(defmethod process-change :add-token
[data {:keys [set-name token]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/add-token-in-set set-name (ctob/make-token token)))))
(defmethod process-change :set-token
[data {:keys [set-name token-name token]}]
(update data :tokens-lib
(fn [lib]
(let [lib' (ctob/ensure-tokens-lib lib)]
(cond
(not token)
(ctob/delete-token-from-set lib' set-name token-name)
(defmethod process-change :mod-token
[data {:keys [set-name name token]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/update-token-in-set
set-name
name
(fn [old-token]
(ctob/make-token (merge old-token token)))))))
(not (ctob/get-token-in-set lib' set-name token-name))
(ctob/add-token-in-set lib' set-name (ctob/make-token token))
(defmethod process-change :del-token
[data {:keys [set-name name]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/delete-token-from-set
set-name
name))))
:else
(ctob/update-token-in-set lib' set-name token-name (fn [prev-token]
(ctob/make-token (merge prev-token token)))))))))
(defmethod process-change :add-temporary-token-theme
[data {:keys [token-theme]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/add-theme (ctob/make-token-theme token-theme)))))
(defmethod process-change :set-token-set
[data {:keys [set-name group? token-set]}]
(update data :tokens-lib
(fn [lib]
(let [lib' (ctob/ensure-tokens-lib lib)]
(cond
(not token-set)
(if group?
(ctob/delete-set-group lib' set-name)
(ctob/delete-set lib' set-name))
(not (ctob/get-set lib' set-name))
(ctob/add-set lib' (ctob/make-token-set token-set))
:else
(ctob/update-set lib' set-name (fn [prev-token-set]
(ctob/make-token-set (merge prev-token-set token-set)))))))))
(defmethod process-change :set-token-theme
[data {:keys [group theme-name theme]}]
(update data :tokens-lib
(fn [lib]
(let [lib' (ctob/ensure-tokens-lib lib)]
(cond
(not theme)
(ctob/delete-theme lib' group theme-name)
(not (ctob/get-theme lib' group theme-name))
(ctob/add-theme lib' (ctob/make-token-theme theme))
:else
(ctob/update-theme lib'
group theme-name
(fn [prev-token-theme]
(ctob/make-token-theme (merge prev-token-theme theme)))))))))
(defmethod process-change :update-active-token-themes
[data {:keys [theme-ids]}]
(update data :tokens-lib #(-> % (ctob/ensure-tokens-lib)
(ctob/set-active-themes theme-ids))))
(defmethod process-change :delete-temporary-token-theme
[data {:keys [group name]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/delete-theme group name))))
(defmethod process-change :add-token-theme
[data {:keys [token-theme]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/add-theme (-> token-theme
(ctob/make-token-theme))))))
(defmethod process-change :mod-token-theme
[data {:keys [name group token-theme]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/update-theme group name
(fn [prev-theme]
(merge prev-theme token-theme))))))
(defmethod process-change :del-token-theme
[data {:keys [group name]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/delete-theme group name))))
(defmethod process-change :add-token-set
[data {:keys [token-set]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/add-set (ctob/make-token-set token-set)))))
(defmethod process-change :add-token-sets
[data {:keys [token-sets]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/add-sets (map ctob/make-token-set token-sets)))))
(defmethod process-change :rename-token-set-group
[data {:keys [set-group-path set-group-fname]}]
(update data :tokens-lib (fn [lib]
@@ -1121,37 +1051,17 @@
(ctob/ensure-tokens-lib)
(ctob/rename-set-group set-group-path set-group-fname)))))
(defmethod process-change :mod-token-set
[data {:keys [name token-set]}]
(update data :tokens-lib (fn [lib]
(-> lib
(ctob/ensure-tokens-lib)
(ctob/update-set name (fn [prev-set]
(merge prev-set (dissoc token-set :tokens))))))))
(defmethod process-change :move-token-set-before
[data {:keys [from-path to-path before-path before-group?] :as changes}]
(defmethod process-change :move-token-set
[data {:keys [from-path to-path before-path before-group] :as changes}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/move-set from-path to-path before-path before-group?))))
(ctob/move-set from-path to-path before-path before-group))))
(defmethod process-change :move-token-set-group-before
[data {:keys [from-path to-path before-path before-group?]}]
(defmethod process-change :move-token-set-group
[data {:keys [from-path to-path before-path before-group]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/move-set-group from-path to-path before-path before-group?))))
(defmethod process-change :del-token-set
[data {:keys [name]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/delete-set-path name))))
(defmethod process-change :del-token-set-path
[data {:keys [path]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/delete-set-path path))))
(ctob/move-set-group from-path to-path before-path before-group))))
;; === Operations

View File

@@ -155,13 +155,14 @@
(dm/get-in data [:pages-index uuid/zero :objects])))
(defn apply-changes-local
[changes]
[changes & {:keys [apply-to-library?]}]
(dm/assert!
"expected valid changes"
(check-changes! changes))
(if-let [file-data (::file-data (meta changes))]
(let [index (::applied-changes-count (meta changes))
(let [library-data (::library-data (meta changes))
index (::applied-changes-count (meta changes))
redo-changes (:redo-changes changes)
new-changes (if (< index (count redo-changes))
(->> (subvec (:redo-changes changes) index)
@@ -169,8 +170,12 @@
(assoc :page-id uuid/zero)
(dissoc :component-id))))
[])
new-file-data (cfc/process-changes file-data new-changes)]
new-file-data (cfc/process-changes file-data new-changes)
new-library-data (if apply-to-library?
(cfc/process-changes library-data new-changes)
library-data)]
(vary-meta changes assoc ::file-data new-file-data
::library-data new-library-data
::applied-changes-count (count redo-changes)))
changes))
@@ -762,13 +767,6 @@
(update :undo-changes conj {:type :add-typography :typography prev-typography})
(apply-changes-local))))
(defn add-temporary-token-theme
[changes token-theme]
(-> changes
(update :redo-changes conj {:type :add-temporary-token-theme :token-theme token-theme})
(update :undo-changes conj {:type :delete-temporary-token-theme :id (:id token-theme) :name (:name token-theme)})
(apply-changes-local)))
(defn update-active-token-themes
[changes token-active-theme-ids prev-token-active-theme-ids]
(-> changes
@@ -776,42 +774,32 @@
(update :undo-changes conj {:type :update-active-token-themes :theme-ids prev-token-active-theme-ids})
(apply-changes-local)))
(defn add-token-theme
[changes token-theme]
(-> changes
(update :redo-changes conj {:type :add-token-theme :token-theme token-theme})
(update :undo-changes conj {:type :del-token-theme :group (:group token-theme) :name (:name token-theme)})
(apply-changes-local)))
(defn update-token-theme
[changes token-theme prev-token-theme]
(let [name (or (:name prev-token-theme)
(:name token-theme))
group (or (:group prev-token-theme)
(:group token-theme))]
(-> changes
(update :redo-changes conj {:type :mod-token-theme :group group :name name :token-theme token-theme})
(update :undo-changes conj {:type :mod-token-theme :group group :name name :token-theme (or prev-token-theme token-theme)})
(apply-changes-local))))
(defn delete-token-theme
[changes group name]
(defn set-token-theme [changes group theme-name theme]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
prev-token-theme (some-> (get library-data :tokens-lib)
(ctob/get-theme group name))]
prev-theme (some-> (get library-data :tokens-lib)
(ctob/get-theme group theme-name))]
(-> changes
(update :redo-changes conj {:type :del-token-theme :group group :name name})
(update :undo-changes conj {:type :add-token-theme :token-theme prev-token-theme})
(update :redo-changes conj {:type :set-token-theme
:theme-name theme-name
:group group
:theme theme})
(update :undo-changes conj (if prev-theme
{:type :set-token-theme
:group group
:theme-name (or
;; Undo of edit
(:name theme)
;; Undo of delete
theme-name)
:theme prev-theme}
;; Undo of create
{:type :set-token-theme
:group group
:theme-name theme-name
:theme nil}))
(apply-changes-local))))
(defn add-token-set
[changes token-set]
(-> changes
(update :redo-changes conj {:type :add-token-set :token-set token-set})
(update :undo-changes conj {:type :del-token-set :name (:name token-set)})
(apply-changes-local)))
(defn rename-token-set-group
[changes set-group-path set-group-fname]
(let [undo-path (ctob/replace-last-path-name set-group-path set-group-fname)
@@ -821,58 +809,34 @@
(update :undo-changes conj {:type :rename-token-set-group :set-group-path undo-path :set-group-fname undo-fname})
(apply-changes-local))))
(defn update-token-set
[changes token-set prev-token-set]
(-> changes
(update :redo-changes conj {:type :mod-token-set :name (:name prev-token-set) :token-set token-set})
(update :undo-changes conj {:type :mod-token-set :name (:name token-set) :token-set (or prev-token-set token-set)})
(apply-changes-local)))
(defn delete-token-set-path
[changes group? path]
(assert-library! changes)
(let [;; TODO Move leaking prefix to library
prefixed-path (if group?
(ctob/set-group-path->set-group-prefixed-path path)
(ctob/set-full-path->set-prefixed-full-path path))
prefixed-path-str (ctob/join-set-path prefixed-path)
library-data (::library-data (meta changes))
prev-token-sets (some-> (get library-data :tokens-lib)
(ctob/get-path-sets prefixed-path-str))]
(-> changes
(update :redo-changes conj {:type :del-token-set-path :path prefixed-path-str})
(update :undo-changes conj {:type :add-token-sets :token-sets prev-token-sets})
(apply-changes-local))))
(defn move-token-set-before
(defn move-token-set
[changes {:keys [from-path to-path before-path before-group? prev-before-path prev-before-group?] :as opts}]
(-> changes
(update :redo-changes conj {:type :move-token-set-before
(update :redo-changes conj {:type :move-token-set
:from-path from-path
:to-path to-path
:before-path before-path
:before-group? before-group?})
(update :undo-changes conj {:type :move-token-set-before
:before-group before-group?})
(update :undo-changes conj {:type :move-token-set
:from-path to-path
:to-path from-path
:before-path prev-before-path
:before-group? prev-before-group?})
:before-group prev-before-group?})
(apply-changes-local)))
(defn move-token-set-group-before
(defn move-token-set-group
[changes {:keys [from-path to-path before-path before-group? prev-before-path prev-before-group?]}]
(prn prev-before-path prev-before-group?)
(-> changes
(update :redo-changes conj {:type :move-token-set-group-before
(update :redo-changes conj {:type :move-token-set-group
:from-path from-path
:to-path to-path
:before-path before-path
:before-group? before-group?})
(update :undo-changes conj {:type :move-token-set-group-before
:before-group before-group?})
(update :undo-changes conj {:type :move-token-set-group
:from-path to-path
:to-path from-path
:before-path prev-before-path
:before-group? prev-before-group?})
:before-group prev-before-group?})
(apply-changes-local)))
(defn set-tokens-lib
@@ -884,36 +848,84 @@
(update :undo-changes conj {:type :set-tokens-lib :tokens-lib prev-tokens-lib})
(apply-changes-local))))
(defn add-token
[changes set-name token]
(-> changes
(update :redo-changes conj {:type :add-token :set-name set-name :token token})
(update :undo-changes conj {:type :del-token :set-name set-name :name (:name token)})
(apply-changes-local)))
(defn update-token
[changes set-name token prev-token]
(-> changes
(update :redo-changes conj {:type :mod-token :set-name set-name :name (:name prev-token) :token token})
(update :undo-changes conj {:type :mod-token :set-name set-name :name (:name token) :token (or prev-token token)})
(apply-changes-local)))
(defn delete-token
[changes set-name token-name]
(defn set-token [changes set-name token-name token]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
prev-token (some-> (get library-data :tokens-lib)
(ctob/get-set set-name)
(ctob/get-token token-name))]
(-> changes
(update :redo-changes conj {:type :del-token :set-name set-name :name token-name})
(update :undo-changes conj {:type :add-token :set-name set-name :token prev-token})
(update :redo-changes conj {:type :set-token
:set-name set-name
:token-name token-name
:token token})
(update :undo-changes conj (if prev-token
{:type :set-token
:set-name set-name
:token-name (or
;; Undo of edit
(:name token)
;; Undo of delete
token-name)
:token prev-token}
;; Undo of create token
{:type :set-token
:set-name set-name
:token-name token-name
:token nil}))
(apply-changes-local))))
(defn rename-token-set
[changes name new-name]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
prev-token-set (some-> (get library-data :tokens-lib)
(ctob/get-set name))]
(-> changes
(update :redo-changes conj {:type :set-token-set
:set-name name
:token-set (assoc prev-token-set :name new-name)
:group? false})
(update :undo-changes conj {:type :set-token-set
:set-name new-name
:token-set prev-token-set
:group? false})
(apply-changes-local))))
(defn set-token-set
[changes set-name group? token-set]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
prev-token-set (some-> (get library-data :tokens-lib)
(ctob/get-set set-name))]
(-> changes
(update :redo-changes conj {:type :set-token-set
:set-name set-name
:token-set token-set
:group? group?})
(update :undo-changes conj (if prev-token-set
{:type :set-token-set
:set-name (or
;; Undo of edit
(:name token-set)
;; Undo of delete
set-name)
:token-set prev-token-set
:group? group?}
;; Undo of create
{:type :set-token-set
:set-name set-name
:token-set nil
:group? group?}))
(apply-changes-local))))
(defn add-component
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page]
(add-component changes id path name new-shapes updated-shapes main-instance-id main-instance-page nil))
(add-component changes id path name new-shapes updated-shapes main-instance-id main-instance-page nil nil nil))
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation]
(add-component changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation nil nil))
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation variant-id variant-properties & {:keys [apply-changes-local-library?]}]
(assert-page-id! changes)
(assert-objects! changes)
(let [page-id (::page-id (meta changes))
@@ -952,7 +964,9 @@
:name name
:main-instance-id main-instance-id
:main-instance-page main-instance-page
:annotation annotation}
:annotation annotation
:variant-id variant-id
:variant-properties variant-properties}
(some? new-shapes) ;; this will be null in components-v2
(assoc :shapes (vec new-shapes))))
(into (map mk-change) updated-shapes))))
@@ -967,10 +981,10 @@
(map mk-change))
updated-shapes))))
(apply-changes-local)))))
(apply-changes-local {:apply-to-library? apply-changes-local-library?})))))
(defn update-component
[changes id update-fn]
[changes id update-fn & {:keys [apply-changes-local-library?]}]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
prev-component (get-in library-data [:components id])
@@ -984,6 +998,8 @@
:main-instance-id (:main-instance-id new-component)
:main-instance-page (:main-instance-page new-component)
:annotation (:annotation new-component)
:variant-id (:variant-id new-component)
:variant-properties (:variant-properties new-component)
:objects (:objects new-component) ;; this won't exist in components-v2 (except for deleted components)
:modified-at (:modified-at new-component)})
(update :undo-changes conj {:type :mod-component
@@ -993,7 +1009,11 @@
:main-instance-id (:main-instance-id prev-component)
:main-instance-page (:main-instance-page prev-component)
:annotation (:annotation prev-component)
:objects (:objects prev-component)}))
:variant-id (:variant-id prev-component)
:variant-properties (:variant-properties prev-component)
:objects (:objects prev-component)})
(cond-> apply-changes-local-library?
(apply-changes-local {:apply-to-library? true})))
changes)))
(defn delete-component
@@ -1055,3 +1075,11 @@
(reduce reorder-grid changes))]
changes))
(defn get-library-data
[changes]
(::library-data (meta changes)))
(defn get-objects
[changes]
(dm/get-in (::file-data (meta changes)) [:pages-index uuid/zero :objects]))

View File

@@ -9,7 +9,6 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.shapes.common :as gco]
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[clojure.set :as set]
[clojure.walk :as walk]
@@ -201,7 +200,7 @@
result))))
(defn get-parent-seq
"Returns a vector of parents of the specified shape."
"Returns a lazy seq of parents of the specified shape."
([objects shape-id]
(get-parent-seq objects (get objects shape-id) shape-id))
@@ -284,6 +283,22 @@
:else
(get-root-frame objects (:frame-id frame)))))
(defn get-parent-frame
"Similar to `get-frame, but always return the parent frame. When root
frame is provided, then itself is returned."
[objects shape-or-id]
(cond
(map? shape-or-id)
(get objects (dm/get-prop shape-or-id :frame-id))
(= uuid/zero shape-or-id)
(get objects uuid/zero)
:else
(some->> shape-or-id
(get objects)
(get-frame objects))))
(defn valid-frame-target?
[objects parent-id shape-id]
(let [shape (get objects shape-id)]
@@ -400,31 +415,51 @@
elements)]
(into #{} (keep :name) elements)))
(defn- extract-numeric-suffix
[basename]
(if-let [[_ p1 p2] (re-find #"(.*) ([0-9]+)$" basename)]
[p1 (+ 1 (d/parse-integer p2))]
[basename 1]))
(defn- name-seq
"Creates a lazy, infinite sequence of names starting with `base-name`,
followed by variants with suffixes applied. The sequence follows this pattern:
- `base-name`
- `(str base-name (suffix-fn 1))`
- `(str base-name (suffix-fn 2))`
- `(str base-name (suffix-fn 3))`, etc."
[base-name suffix-fn]
(cons base-name
(map #(str/concat base-name (suffix-fn %))
(iterate inc 1))))
(defn ^:private get-suffix
"Default suffix impelemtation"
[copy-count]
(str/concat " " copy-count))
(defn generate-unique-name
"A unique name generator"
[used basename]
"Generates a unique name by selecting the first available name from a generated sequence.
The sequence consists of `base-name` and its variants, avoiding conflicts with `existing-names`.
Parameters:
- `base-name` - string used as the base for name generation.
- `existing-names` - a collection of existing names to check for uniqueness.
- Options:
- `:suffix-fn` - a function that generates suffixes, given an integer (default: `get-suffix`).
- `:immediate-suffix?` - if `true`, the base name is considered taken, and suffixing starts immediately.
Returns:
- A unique name not present in `existing-names`."
[base-name existing-names & {:keys [suffix-fn immediate-suffix?]
:or {suffix-fn get-suffix}}]
(dm/assert!
"expected a set of strings"
(sm/check-set-of-strings! used))
(coll? existing-names))
(dm/assert!
"expected a string for `basename`."
(string? basename))
(if-not (contains? used basename)
basename
(let [[prefix initial] (extract-numeric-suffix basename)]
(loop [counter initial]
(let [candidate (str prefix " " counter)]
(if (contains? used candidate)
(recur (inc counter))
candidate))))))
(string? base-name))
(let [existing-name-set (cond-> (set existing-names)
immediate-suffix? (conj base-name))
names (name-seq base-name suffix-fn)]
(->> names
(remove #(contains? existing-name-set %))
first)))
(defn walk-pages
"Go through all pages of a file and apply a function to each one"

View File

@@ -28,6 +28,7 @@
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.shape :as cts]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.shadow :as ctss]
[app.common.uuid :as uuid]
[clojure.set :as set]
@@ -35,9 +36,7 @@
#?(:cljs (l/set-level! :info))
(declare ^:private available-migrations)
(declare ^:private migration-up-index)
(declare ^:private migration-down-index)
(declare available-migrations)
(def version cfd/version)
@@ -49,7 +48,10 @@
[file]
(or (nil? (:version file))
(not= cfd/version (:version file))
(not= available-migrations (:migrations file))))
(boolean
(->> (:migrations file #{})
(set/difference available-migrations)
(not-empty)))))
(def xf:map-name
(map :name))
@@ -96,13 +98,13 @@
(if (nil? migrations)
(generate-migrations-from-version version)
migrations)))
(migrate)
(update :features (fnil into #{}) (deref cfeat/*new*))
;; NOTE: in some future we can consider to apply
;; a migration to the whole database and remove
;; this code from this function that executes on
;; each file migration operation
(update :features cfeat/migrate-legacy-features)))))
(update :features cfeat/migrate-legacy-features)
(migrate)))))
(defn migrated?
[file]
@@ -119,9 +121,9 @@
(into [] shapes)
shapes))))
(update-page [page]
(update page :objects update-vals update-object))]
(update page :objects d/update-vals update-object))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
(defmethod migrate-data "legacy-3"
[data _]
@@ -172,9 +174,9 @@
(fix-empty-points)))
(update-page [page]
(update page :objects update-vals update-object))]
(update page :objects d/update-vals update-object))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
;; Put the id of the local file in :component-file in instances of
;; local components
@@ -187,9 +189,9 @@
object))
(update-page [page]
(update page :objects update-vals update-object))]
(update page :objects d/update-vals update-object))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
;; Fixes issues with selrect/points for shapes with width/height =
;; 0 (line-like paths)
@@ -212,11 +214,11 @@
shape))
(update-container [container]
(update container :objects update-vals fix-line-paths))]
(update container :objects d/update-vals fix-line-paths))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
;; Remove interactions pointing to deleted frames
(defmethod migrate-data "legacy-7"
@@ -227,9 +229,9 @@
(filterv #(get-in page [:objects (:destination %)]) interactions))))
(update-page [page]
(update page :objects update-vals (partial update-object page)))]
(update page :objects d/update-vals (partial update-object page)))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
;; Remove groups without any shape, both in pages and components
(defmethod migrate-data "legacy-8"
@@ -269,8 +271,8 @@
(assoc container :objects objects)))))]
(-> data
(update :pages-index update-vals clean-container)
(update :components update-vals clean-container))))
(update :pages-index d/update-vals clean-container)
(d/update-when :components d/update-vals clean-container))))
(defmethod migrate-data "legacy-9"
[data _]
@@ -304,7 +306,7 @@
[data _]
(letfn [(update-page [page]
(d/update-in-when page [:objects uuid/zero] dissoc :points :selrect))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
(defmethod migrate-data "legacy-11"
[data _]
@@ -318,7 +320,7 @@
(update page :objects (fn [objects]
(update-vals objects (partial update-object objects)))))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
(defmethod migrate-data "legacy-12"
[data _]
@@ -328,9 +330,9 @@
(assoc :size nil)))
(update-page [page]
(d/update-in-when page [:options :saved-grids] update-vals update-grid))]
(d/update-in-when page [:options :saved-grids] d/update-vals update-grid))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
;; Add rx and ry to images
(defmethod migrate-data "legacy-13"
@@ -348,9 +350,9 @@
(fix-radius)))
(update-page [page]
(update page :objects update-vals update-object))]
(update page :objects d/update-vals update-object))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
(defmethod migrate-data "legacy-14"
[data _]
@@ -380,8 +382,8 @@
container))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-16"
[data _]
@@ -423,11 +425,11 @@
(assign-fills)))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-17"
[data _]
@@ -452,11 +454,11 @@
(assoc :fills [])))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
;; Remove position-data to solve a bug with the text positioning
(defmethod migrate-data "legacy-18"
@@ -467,11 +469,11 @@
(dissoc :position-data)))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-19"
[data _]
@@ -483,11 +485,11 @@
(dissoc :position-data)))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-25"
[data _]
@@ -499,10 +501,10 @@
(update :selrect grc/make-rect)
(cts/create-shape))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-26"
[data _]
@@ -515,11 +517,11 @@
(assoc :transform-inverse (gmt/matrix))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-27"
[data _]
@@ -546,11 +548,11 @@
(dissoc :saved-component-root?))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-28"
[data _]
@@ -575,8 +577,8 @@
(d/update-when container :objects #(update-vals % (partial update-object %))))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-29"
[data _]
@@ -607,11 +609,11 @@
(update :content #(txt/transform-nodes invalid-node? fix-node %)))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-31"
[data _]
@@ -622,10 +624,10 @@
(dissoc :use-for-thumbnail?))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-32"
[data _]
@@ -640,11 +642,11 @@
object)))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-33"
[data _]
@@ -662,9 +664,9 @@
object))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container))))
(update :pages-index d/update-vals update-container))))
(defmethod migrate-data "legacy-34"
[data _]
@@ -674,10 +676,10 @@
(dissoc object :x :y :width :height)
object))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-36"
[data _]
@@ -687,8 +689,8 @@
(dissoc objects nil)
objects))))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-37"
[data _]
@@ -716,11 +718,11 @@
shape)))
(update-container [container]
(d/update-when container :objects update-vals update-shape))]
(d/update-when container :objects d/update-vals update-shape))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-39"
[data _]
@@ -738,11 +740,11 @@
shape))
(update-container [container]
(d/update-when container :objects update-vals update-shape))]
(d/update-when container :objects d/update-vals update-shape))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-40"
[data _]
@@ -762,11 +764,11 @@
shape))
(update-container [container]
(d/update-when container :objects update-vals update-shape))]
(d/update-when container :objects d/update-vals update-shape))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-41"
[data _]
@@ -795,11 +797,11 @@
shape))
(update-container [container]
(d/update-when container :objects update-vals update-shape))]
(d/update-when container :objects d/update-vals update-shape))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-42"
[data _]
@@ -812,11 +814,11 @@
object))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(def ^:private valid-fill?
(sm/lazy-validator ::cts/fill))
@@ -841,14 +843,11 @@
object))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(def ^:private valid-shadow?
(sm/lazy-validator ::ctss/shadow))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-44"
[data _]
@@ -861,14 +860,14 @@
(update-object [object]
(let [xform (comp (map fix-shadow)
(filter valid-shadow?))]
(filter ctss/valid-shadow?))]
(d/update-when object :shadow #(into [] xform %))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-45"
[data _]
@@ -881,9 +880,9 @@
:parent-id parent-id)))
(update-container [container]
(d/update-when container :objects update-vals fix-shape))]
(d/update-when container :objects d/update-vals fix-shape))]
(-> data
(update :pages-index update-vals update-container))))
(update :pages-index d/update-vals update-container))))
(defmethod migrate-data "legacy-46"
[data _]
@@ -891,10 +890,10 @@
(dissoc object :thumbnail))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-47"
[data _]
@@ -915,9 +914,9 @@
shape)))
(update-page [page]
(d/update-when page :objects update-vals (partial fix-shape page)))]
(d/update-when page :objects d/update-vals (partial fix-shape page)))]
(-> data
(update :pages-index update-vals update-page))))
(update :pages-index d/update-vals update-page))))
(defmethod migrate-data "legacy-48"
[data _]
@@ -929,9 +928,9 @@
shape)))
(update-page [page]
(d/update-when page :objects update-vals fix-shape))]
(d/update-when page :objects d/update-vals fix-shape))]
(-> data
(update :pages-index update-vals update-page))))
(update :pages-index d/update-vals update-page))))
;; Remove hide-in-viewer for shapes that are origin or destination of an interaction
(defmethod migrate-data "legacy-49"
@@ -949,9 +948,9 @@
(mapcat :interactions)
(map :destination)
(set))]
(update page :objects update-vals (partial update-object destinations))))]
(update page :objects d/update-vals (partial update-object destinations))))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
;; This migration mainly fixes paths with curve-to segments
;; without :c1x :c1y :c2x :c2y properties. Additionally, we found a
@@ -994,11 +993,11 @@
update-container
(fn [page]
(d/update-when page :objects update-vals update-shape))]
(d/update-when page :objects d/update-vals update-shape))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(def ^:private valid-color?
(sm/lazy-validator ::ctc/color))
@@ -1018,9 +1017,9 @@
shape))
(update-page [page]
(d/update-when page :objects update-vals update-shape))]
(d/update-when page :objects d/update-vals update-shape))]
(update data :pages-index update-vals update-page)))
(update data :pages-index d/update-vals update-page)))
(defmethod migrate-data "legacy-53"
@@ -1036,15 +1035,15 @@
(update-shape [shape]
(let [xform (comp (map fix-shadow)
(filter valid-shadow?))]
(filter ctss/valid-shadow?))]
(d/update-when shape :shadow #(into [] xform %))))
(update-container [container]
(d/update-when container :objects update-vals update-shape))]
(d/update-when container :objects d/update-vals update-shape))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
;; This migration moves page options to the page level
(defmethod migrate-data "legacy-55"
@@ -1096,11 +1095,11 @@
(update :content (partial txt/transform-nodes identity fix-fills)))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-57"
@@ -1127,7 +1126,7 @@
(-> data
(update :pages (fn [pages] (into [] (remove nil?) pages)))
(update :pages-index dissoc nil)
(update :pages-index update-vals update-page))))
(update :pages-index d/update-vals update-page))))
(defmethod migrate-data "legacy-59"
[data _]
@@ -1138,11 +1137,11 @@
(d/update-when shape :touched #(into #{} (map fix-touched) %)))
(update-container [container]
(d/update-when container :objects update-vals update-shape))]
(d/update-when container :objects d/update-vals update-shape))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-62"
[data _]
@@ -1175,7 +1174,7 @@
;; so the relevant objects are inside the component
(d/update-when component :objects remove-cycles))]
(update data :components update-vals update-component)))
(d/update-when data :components d/update-vals update-component)))
(defmethod migrate-data "legacy-65"
[data _]
@@ -1186,14 +1185,14 @@
update-page
(fn [page]
(-> (update-object page)
(update :objects update-vals update-object)))]
(update :objects d/update-vals update-object)))]
(-> data
(update-object)
(d/update-when :pages-index update-vals update-page)
(d/update-when :colors update-vals update-object)
(d/update-when :typographies update-vals update-object)
(d/update-when :components update-vals update-object))))
(update :pages-index d/update-vals update-page)
(d/update-when :colors d/update-vals update-object)
(d/update-when :typographies d/update-vals update-object)
(d/update-when :components d/update-vals update-object))))
(defmethod migrate-data "legacy-66"
[data _]
@@ -1207,11 +1206,11 @@
object))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "legacy-67"
[data _]
@@ -1219,11 +1218,73 @@
(d/update-when object :shadow #(into [] (reverse %))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0001-remove-tokens-from-groups"
[data _]
(letfn [(update-object [object]
(cond-> object
(and (= :group (:type object))
(contains? (:applied-tokens object) :fill))
(assoc :fills [])
(and (= :group (:type object))
(contains? object :applied-tokens))
(dissoc :applied-tokens)))
(update-page [page]
(d/update-when page :objects d/update-vals update-object))]
(update data :pages-index d/update-vals update-page)))
(defmethod migrate-data "0002-clean-shape-interactions"
[data _]
(let [decode-fn (sm/decoder ctsi/schema:interaction sm/json-transformer)
validate-fn (sm/validator ctsi/schema:interaction)
xform
(comp
(map decode-fn)
(filter validate-fn))
update-object
(fn [object]
(d/update-when object :interactions
(fn [interactions]
(into [] xform interactions))))
update-container
(fn [container]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0003-fix-root-shape"
[data _]
(letfn [(update-object [shape]
(if (= (:id shape) uuid/zero)
(-> shape
(assoc :parent-id uuid/zero)
(assoc :frame-id uuid/zero)
;; We explicitly dissoc them and let the shape-setup
;; to regenerate it with valid values.
(dissoc :selrect)
(dissoc :points)
(cts/setup-shape))
shape))
(update-container [container]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container)
(d/without-nils))))
(def available-migrations
(into (d/ordered-set)
@@ -1278,4 +1339,7 @@
"legacy-62"
"legacy-65"
"legacy-66"
"legacy-67"]))
"legacy-67"
"0001-remove-tokens-from-groups"
"0002-clean-shape-interactions"
"0003-fix-root-shape"]))

View File

@@ -38,7 +38,7 @@
[shape changes]))
(defn prepare-move-shapes-into-frame
[changes frame-id shapes objects]
[changes frame-id shapes objects remove-layout-data?]
(let [parent-id (dm/get-in objects [frame-id :parent-id])
shapes (remove #(= % parent-id) shapes)
to-move (->> shapes
@@ -46,7 +46,8 @@
(not-empty))]
(if to-move
(-> changes
(cond-> (not (ctl/any-layout? objects frame-id))
(cond-> (and remove-layout-data?
(not (ctl/any-layout? objects frame-id)))
(pcb/update-shapes shapes ctl/remove-layout-item-data))
(pcb/update-shapes shapes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))
(pcb/change-parent frame-id to-move 0)
@@ -133,7 +134,7 @@
(prepare-add-shape changes shape objects)
changes
(prepare-move-shapes-into-frame changes (:id shape) selected' objects)
(prepare-move-shapes-into-frame changes (:id shape) selected' objects false)
changes
(cond-> changes

View File

@@ -124,7 +124,8 @@
;; TODO: deprecate this flag and consolidate the code
:export-file-v3
:render-wasm-dpr
:hide-release-modal})
:hide-release-modal
:subscriptions-old})
(def all-flags
(set/union email login varia))

View File

@@ -5,6 +5,7 @@
;; Copyright (c) KALEIDOS INC
(ns app.common.logic.libraries
#?(:cljs (:require-macros [app.common.logic.libraries :refer [shape-log container-log]]))
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
@@ -35,6 +36,35 @@
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn)
;; Add uuids here to filter logs to only show specific shapes or containers (and all shapes
;; contained in them).
(def log-shape-ids #{})
(def log-container-ids #{})
(defn enabled-shape?
[id container]
(or (empty? log-shape-ids)
(nil? id)
(let [ids (if container
(into #{} (cfh/get-parent-ids-seq-with-self (:objects container) id))
#{id})]
(seq (set/intersection log-shape-ids ids)))))
(defmacro shape-log
[level id container & params]
`(when (enabled-shape? ~id ~container)
(log/log ~level ~@params)))
(defn enabled-container?
[id]
(or (empty? log-container-ids)
(log-container-ids id)))
(defmacro container-log
[level id & params]
`(when (enabled-container? ~id)
(log/log ~level ~@params)))
(declare generate-sync-container)
(declare generate-sync-shape)
(declare generate-sync-text-shape)
@@ -72,10 +102,10 @@
;; ---- Components and instances creation ----
(defn duplicate-component
(defn- duplicate-component
"Clone the root shape of the component and all children. Generate new
ids from all of them."
[component new-component-id library-data]
[component new-component-id library-data force-id]
(let [components-v2 (dm/get-in library-data [:options :components-v2])]
(if components-v2
(let [main-instance-page (ctf/get-component-page library-data component)
@@ -111,7 +141,8 @@
(:parent-id main-instance-shape)
(:objects main-instance-page)
:update-new-shape update-new-shape
:update-original-shape update-original-shape)
:update-original-shape update-original-shape
:force-id force-id)
remap-frame
(fn [shape]
@@ -151,7 +182,7 @@
(defn generate-duplicate-component
"Create a new component copied from the one with the given id."
[changes library component-id components-v2]
[changes library component-id new-component-id components-v2 & {:keys [new-shape-id apply-changes-local-library?]}]
(let [component (ctkl/get-component (:data library) component-id)
new-name (:name component)
@@ -159,26 +190,39 @@
(ctf/get-component-page (:data library) component))
new-component-id (when components-v2
(uuid/next))
new-component-id)
[new-component-shape new-component-shapes ; <- null in components-v2
new-main-instance-shape new-main-instance-shapes]
(duplicate-component component new-component-id (:data library))]
(duplicate-component component new-component-id (:data library) new-shape-id)]
[new-main-instance-shape
(-> changes
(pcb/with-page main-instance-page)
(pcb/with-objects (:objects main-instance-page))
(pcb/add-objects new-main-instance-shapes {:ignore-touched true})
(pcb/add-component (if components-v2
new-component-id
(:id new-component-shape))
(:path component)
new-name
new-component-shapes
[]
(:id new-main-instance-shape)
(:id main-instance-page)
(:annotation component)
(:variant-id component)
(:variant-properties component)
{:apply-changes-local-library? apply-changes-local-library?})
;; Update grid layout if the new main instance is inside
(pcb/update-shapes
[(:frame-id new-main-instance-shape)]
(fn [shape objects]
(cond-> shape
(ctl/grid-layout? shape)
(ctl/assign-cells objects)))
{:with-objects? true}))]))
(-> changes
(pcb/with-page main-instance-page)
(pcb/with-objects (:objects main-instance-page))
(pcb/add-objects new-main-instance-shapes {:ignore-touched true})
(pcb/add-component (if components-v2
new-component-id
(:id new-component-shape))
(:path component)
new-name
new-component-shapes
[]
(:id new-main-instance-shape)
(:id main-instance-page)
(:annotation component)))))
(defn generate-instantiate-component
"Generate changes to create a new instance from a component."
@@ -251,72 +295,98 @@
(declare generate-detach-recursive)
(declare generate-advance-nesting-level)
(declare generate-detach-immediate)
(defn generate-detach-instance
"Generate changes to remove the links between a shape and all its children
with a component."
[changes container libraries shape-id]
(let [shape (ctn/get-shape container shape-id)]
(log/debug :msg "Detach instance" :shape-id shape-id :container (:id container))
(shape-log :debug shape-id container
:msg "Detach instance" :shape-id shape-id :container (:id container))
(generate-detach-recursive changes container libraries shape-id true (true? (:component-root shape)))))
(defn- generate-detach-recursive
[changes container libraries shape-id first component-root?]
(let [shape (ctn/get-shape container shape-id)]
(shape-log :trace shape-id container
:msg " Processing" :shape-id shape-id)
(if (and (ctk/instance-head? shape) (not first))
; Subinstances are not detached
(cond-> changes
component-root?
; If the initial shape was component-root, first level subinstances are converted in top instances
(pcb/update-shapes [shape-id] #(assoc % :component-root true))
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> promote to root")
(assoc % :component-root true)))
:always
; First level subinstances of a detached component can't have swap-slot
(pcb/update-shapes [shape-id] ctk/remove-swap-slot)
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> remove swap-slot")
(ctk/remove-swap-slot %)))
(nil? (ctk/get-swap-slot shape))
; Near shape-refs need to be advanced one level (except if the head is already swapped)
; Near shape-ref of shape and children need to be advanced one level
; (except if the head is already swapped)
(generate-advance-nesting-level nil container libraries (:id shape)))
;; Otherwise, detach the shape and all children
(let [children-ids (:shapes shape)]
(log/trace :msg " -> detach")
(reduce #(generate-detach-recursive %1 container libraries %2 false component-root?)
(pcb/update-shapes changes [(:id shape)] ctk/detach-shape)
children-ids)))))
(defn- generate-advance-nesting-level
[changes file container libraries shape-id]
(let [children (cfh/get-children-with-self (:objects container) shape-id)
skip-near (fn [changes shape]
(let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})]
(cond-> changes
(some? (:shape-ref ref-shape))
(pcb/update-shapes [(:id shape)] #(assoc % :shape-ref (:shape-ref ref-shape)))
(log/trace :msg " -> advance-nesting-level")
(let [detached-ids (atom #{})
children (cfh/get-children-with-self (:objects container) shape-id) ;; TODO: this function should be refactored to be a recursive tree traversal.
skip-near (fn [changes shape] ;; this way we could shake the tree more easily when detaching shapes
(shape-log :trace (:id shape) container ;; and perhaps even allow to recover nested instances that have been
:msg " * advancing" :shape-id (:id shape)) ;; swapped and so we can access the main instance again.
(if (contains? @detached-ids (:id shape))
(do (log/trace :msg " (detached)")
changes)
(let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})]
(cond-> changes
(some? (:shape-ref ref-shape))
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg " (advanced)")
(assoc % :shape-ref (:shape-ref ref-shape))))
;; When advancing level, the normal touched groups (not swap slots) of the
;; ref-shape must be merged into the current shape, because they refer to
;; the new referenced shape.
(some? ref-shape)
(pcb/update-shapes
[(:id shape)]
#(assoc % :touched
(clojure.set/union (:touched shape)
(ctk/normal-touched-groups ref-shape))))
;; When advancing level, the normal touched groups (not swap slots) of the
;; ref-shape must be merged into the current shape, because they refer to
;; the new referenced shape.
(some? ref-shape)
(pcb/update-shapes
[(:id shape)]
#(do (log/trace :msg " (merge touched)")
(assoc % :touched
(clojure.set/union (:touched shape)
(ctk/normal-touched-groups ref-shape)))))
;; Swap slot must also be copied if the current shape has not any,
;; except if this is the first level subcopy.
(and (some? (ctk/get-swap-slot ref-shape))
(nil? (ctk/get-swap-slot shape))
(not= (:id shape) shape-id))
(pcb/update-shapes [(:id shape)] #(ctk/set-swap-slot % (ctk/get-swap-slot ref-shape)))
;; Swap slot must also be copied if the current shape has not any,
;; except if this is the first level subcopy.
(and (some? (ctk/get-swap-slot ref-shape))
(nil? (ctk/get-swap-slot shape))
(not= (:id shape) shape-id))
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg " (got swap-slot)")
(ctk/set-swap-slot % (ctk/get-swap-slot ref-shape))))
;; If we can't get the ref-shape (e.g. it's in an external library not linked),
;: we can't do a suitable advance. So it's better to detach the shape
(nil? ref-shape)
(pcb/update-shapes [(:id shape)] ctk/detach-shape))))]
;; If we can't get the ref-shape (e.g. it's in an external library not linked),
;: we can't do a suitable advance. So it's better to detach the shape and all its
;; children (and add to detached-ids so they are not processed again).
(nil? ref-shape)
(generate-detach-immediate container (:id shape) detached-ids)))))]
(reduce skip-near changes children)))
(defn- generate-detach-immediate
[changes container shape-id detached-ids]
(let [shape-and-children (cfh/get-children-ids-with-self (:objects container) shape-id)]
(log/trace :msg " (cannot advance; detach shape and children)")
(swap! detached-ids #(into % shape-and-children))
(pcb/update-shapes changes shape-and-children ctk/detach-shape)))
(defn prepare-restore-component
([changes library-data component-id current-page]
(let [component (ctkl/get-deleted-component library-data component-id)
@@ -374,11 +444,12 @@
(s/assert ::us/uuid file-id)
(s/assert ::us/uuid library-id)
(log/info :msg "Sync file with library"
:asset-type asset-type
:asset-id asset-id
:file (pretty-file file-id libraries current-file-id)
:library (pretty-file library-id libraries current-file-id))
(container-log :info asset-id
:msg "Sync file with library"
:asset-type asset-type
:asset-id asset-id
:file (pretty-file file-id libraries current-file-id)
:library (pretty-file library-id libraries current-file-id))
(let [file (get-in libraries [file-id :data])
components-v2 (get-in file [:options :components-v2])]
@@ -412,11 +483,12 @@
(s/assert ::us/uuid file-id)
(s/assert ::us/uuid library-id)
(log/info :msg "Sync local components with library"
:asset-type asset-type
:asset-id asset-id
:file (pretty-file file-id libraries current-file-id)
:library (pretty-file library-id libraries current-file-id))
(container-log :info asset-id
:msg "Sync local components with library"
:asset-type asset-type
:asset-id asset-id
:file (pretty-file file-id libraries current-file-id)
:library (pretty-file library-id libraries current-file-id))
(let [file (get-in libraries [file-id :data])
components-v2 (get-in file [:options :components-v2])]
@@ -442,8 +514,8 @@
[changes asset-type asset-id library-id container components-v2 libraries current-file-id]
(if (cfh/page? container)
(log/debug :msg "Sync page in local file" :page-id (:id container))
(log/debug :msg "Sync component in local library" :component-id (:id container)))
(container-log :debug (:id container) :msg "Sync page in local file" :page-id (:id container))
(container-log :debug (:id container) :msg "Sync component in local library" :component-id (:id container)))
(let [linked-shapes (->> (vals (:objects container))
(filter #(uses-assets? asset-type asset-id % library-id)))]
@@ -498,7 +570,7 @@
(defmethod generate-sync-shape :colors
[_ changes library-id _ shape _ libraries _]
(log/debug :msg "Sync colors of shape" :shape (:name shape))
(shape-log :debug (:id shape) nil :msg "Sync colors of shape" :shape (:name shape))
;; Synchronize a shape that uses some colors of the library. The value of the
;; color in the library is copied to the shape.
@@ -509,7 +581,7 @@
(defmethod generate-sync-shape :typographies
[_ changes library-id container shape _ libraries _]
(log/debug :msg "Sync typographies of shape" :shape (:name shape))
(shape-log :debug (:id shape) nil :msg "Sync typographies of shape" :shape (:name shape))
;; Synchronize a shape that uses some typographies of the library. The attributes
;; of the typography are copied to the shape."
@@ -671,7 +743,8 @@
"Generate changes to synchronize one shape that is the root of a component
instance, and all its children, from the given component."
[changes file libraries container shape-id reset? components-v2]
(log/debug :msg "Sync shape direct" :shape-inst (str shape-id) :reset? reset?)
(shape-log :debug shape-id container
:msg "Sync shape direct" :shape-inst (str shape-id) :reset? reset?)
(let [shape-inst (ctn/get-shape container shape-id)
library (dm/get-in libraries [(:component-file shape-inst) :data])
component (ctkl/get-component library (:component-id shape-inst) true)]
@@ -735,7 +808,8 @@
(defn- generate-sync-shape-direct-recursive
[changes container shape-inst component library file libraries shape-main root-inst root-main reset? initial-root? redirect-shaperef components-v2]
(log/debug :msg "Sync shape direct recursive"
(shape-log :debug (:id shape-inst) container
:msg "Sync shape direct recursive"
:shape-inst (str (:name shape-inst) " " (pretty-uuid (:id shape-inst)))
:component (:name component))
@@ -786,7 +860,8 @@
(map #(redirect-shaperef %) children-inst) children-inst)
only-inst (fn [changes child-inst]
(log/trace :msg "Only inst"
(shape-log :trace (:id child-inst) container
:msg "Only inst"
:child-inst (str (:name child-inst) " " (pretty-uuid (:id child-inst))))
(if-not (and omit-touched?
(contains? (:touched shape-inst)
@@ -798,7 +873,8 @@
changes))
only-main (fn [changes child-main]
(log/trace :msg "Only main"
(shape-log :trace (:id child-main) component-container
:msg "Only main"
:child-main (str (:name child-main) " " (pretty-uuid (:id child-main))))
(if-not (and omit-touched?
(contains? (:touched shape-inst)
@@ -817,7 +893,8 @@
changes))
both (fn [changes child-inst child-main]
(log/trace :msg "Both"
(shape-log :trace (:id child-inst) container
:msg "Both"
:child-inst (str (:name child-inst) " " (pretty-uuid (:id child-inst)))
:child-main (str (:name child-main) " " (pretty-uuid (:id child-main))))
(generate-sync-shape-direct-recursive changes
@@ -836,14 +913,16 @@
components-v2))
swapped (fn [changes child-inst child-main]
(log/trace :msg "Match slot"
(shape-log :trace (:id child-inst) container
:msg "Match slot"
:child-inst (str (:name child-inst) " " (pretty-uuid (:id child-inst)))
:child-main (str (:name child-main) " " (pretty-uuid (:id child-main))))
;; For now we don't make any sync here.
changes)
moved (fn [changes child-inst child-main]
(log/trace :msg "Move"
(shape-log :trace (:id child-inst) container
:msg "Move"
:child-inst (str (:name child-inst) " " (pretty-uuid (:id child-inst)))
:child-main (str (:name child-main) " " (pretty-uuid (:id child-main))))
(move-shape
@@ -856,6 +935,7 @@
changes
(compare-children changes
shape-inst
children-inst
children-main
container
@@ -906,7 +986,7 @@
"Generate changes to update the component a shape is linked to, from
the values in the shape and all its children."
[changes file libraries container shape-id components-v2]
(log/debug :msg "Sync shape inverse" :shape (str shape-id))
(shape-log :debug shape-id container :msg "Sync shape inverse" :shape (str shape-id))
(let [redirect-shaperef (partial redirect-shaperef container libraries)
shape-inst (ctn/get-shape container shape-id)
library (dm/get-in libraries [(:component-file shape-inst) :data])
@@ -948,7 +1028,8 @@
(defn- generate-sync-shape-inverse-recursive
[changes container shape-inst component library file libraries shape-main root-inst root-main initial-root? redirect-shaperef components-v2]
(log/trace :msg "Sync shape inverse recursive"
(shape-log :trace (:id shape-inst) container
:msg "Sync shape inverse recursive"
:shape (str (:name shape-inst))
:component (:name component))
@@ -1041,7 +1122,8 @@
components-v2))
swapped (fn [changes child-inst child-main]
(log/trace :msg "Match slot"
(shape-log :trace (:id child-inst) container
:msg "Match slot"
:child-inst (str (:name child-inst) " " (pretty-uuid (:id child-inst)))
:child-main (str (:name child-main) " " (pretty-uuid (:id child-main))))
;; For now we don't make any sync here.
@@ -1058,6 +1140,7 @@
changes
(compare-children changes
shape-inst
children-inst
children-main
container
@@ -1089,14 +1172,15 @@
;; ---- Operation generation helpers ----
(defn- compare-children
[changes children-inst children-main container-inst container-main file libraries only-inst-cb only-main-cb both-cb swapped-cb moved-cb inverse? reset? components-v2]
(log/trace :msg "Compare children")
[changes shape-inst children-inst children-main container-inst container-main file libraries only-inst-cb only-main-cb both-cb swapped-cb moved-cb inverse? reset? components-v2]
(shape-log :trace (:id shape-inst) container-inst :msg "Compare children")
(loop [children-inst (seq (or children-inst []))
children-main (seq (or children-main []))
changes changes]
(let [child-inst (first children-inst)
child-main (first children-main)]
(log/trace :main (str (:name child-main) " " (pretty-uuid (:id child-main)))
(shape-log :trace (:id shape-inst) container-inst
:main (str (:name child-main) " " (pretty-uuid (:id child-main)))
:inst (str (:name child-inst) " " (pretty-uuid (:id child-inst))))
(cond
(and (nil? child-inst) (nil? child-main))
@@ -1159,10 +1243,11 @@
(defn- add-shape-to-instance
[changes component-shape index component-page container root-instance root-main omit-touched? set-remote-synced? components-v2]
(log/info :msg (str "ADD [P " (pretty-uuid (:id container)) "] "
(:name component-shape)
" "
(pretty-uuid (:id component-shape))))
(shape-log :info (:id component-shape) component-page
:msg (str "ADD [P " (pretty-uuid (:id container)) "] "
(:name component-shape)
" "
(pretty-uuid (:id component-shape))))
(let [component-parent-shape (ctn/get-shape component-page (:parent-id component-shape))
parent-shape (d/seek #(ctk/is-main-of? component-parent-shape % components-v2)
(cfh/get-children-with-self (:objects container)
@@ -1234,10 +1319,11 @@
(defn- add-shape-to-main
[changes shape index component component-container page root-instance root-main components-v2]
(log/info :msg (str "ADD [C " (pretty-uuid (:id component-container)) "] "
(:name shape)
" "
(pretty-uuid (:id shape))))
(shape-log :info (:id shape) page
:msg (str "ADD [C " (pretty-uuid (:id component-container)) "] "
(:name shape)
" "
(pretty-uuid (:id shape))))
(let [parent-shape (ctn/get-shape page (:parent-id shape))
component-parent-shape (d/seek #(ctk/is-main-of? % parent-shape components-v2)
(cfh/get-children-with-self (:objects component-container)
@@ -1337,12 +1423,13 @@
(defn- remove-shape
[changes shape container omit-touched?]
(log/info :msg (str "REMOVE-SHAPE "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name shape)
" "
(pretty-uuid (:id shape))))
(shape-log :info (:id shape) container
:msg (str "REMOVE-SHAPE "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name shape)
" "
(pretty-uuid (:id shape))))
(let [objects (get container :objects)
parents (cfh/get-parent-ids objects (:id shape))
parent (first parents)
@@ -1389,16 +1476,17 @@
(defn- move-shape
[changes shape index-before index-after container omit-touched?]
(log/info :msg (str "MOVE "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name shape)
" "
(pretty-uuid (:id shape))
" "
index-before
" -> "
index-after))
(shape-log :info (:id shape) container
:msg (str "MOVE "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name shape)
" "
(pretty-uuid (:id shape))
" "
index-before
" -> "
index-after))
(let [parent (ctn/get-shape container (:parent-id shape))
changes' (-> changes
@@ -1429,13 +1517,14 @@
(if (nil? (:shape-ref dest-shape))
changes
(do
(log/info :msg (str "CHANGE-TOUCHED "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name dest-shape)
" "
(pretty-uuid (:id dest-shape)))
:options options)
(shape-log :info (:id dest-shape) container
:msg (str "CHANGE-TOUCHED "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name dest-shape)
" "
(pretty-uuid (:id dest-shape)))
:options options)
(let [new-touched (cond
reset-touched?
nil
@@ -1471,13 +1560,14 @@
(if (nil? (:shape-ref shape))
changes
(do
(log/info :msg (str "CHANGE-REMOTE-SYNCED? "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name shape)
" "
(pretty-uuid (:id shape)))
:remote-synced remote-synced?)
(shape-log :info (:id shape) container
:msg (str "CHANGE-REMOTE-SYNCED? "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name shape)
" "
(pretty-uuid (:id shape)))
:remote-synced remote-synced?)
(-> changes
(update :redo-changes conj (make-change
container
@@ -1540,16 +1630,17 @@
in the destination shape will not be copied."
[changes dest-shape origin-shape dest-root origin-root container omit-touched?]
(log/info :msg (str "SYNC "
(:name origin-shape)
" "
(pretty-uuid (:id origin-shape))
" -> "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name dest-shape)
" "
(pretty-uuid (:id dest-shape))))
(shape-log :info (:id dest-shape) container
:msg (str "SYNC "
(:name origin-shape)
" "
(pretty-uuid (:id origin-shape))
" -> "
(if (cfh/page? container) "[P " "[C ")
(pretty-uuid (:id container)) "] "
(:name dest-shape)
" "
(pretty-uuid (:id dest-shape))))
(let [;; To synchronize geometry attributes we need to make a prior
;; operation, because coordinates are absolute, but we need to
@@ -1575,7 +1666,21 @@
(if (and (empty? roperations) (empty? applied-tokens))
changes
(let [all-parents (cfh/get-parent-ids (:objects container)
(:id dest-shape))]
(:id dest-shape))
;; Sync tokens of attributes ignored above.
;; FIXME: this probably may be merged with the other calculation
;; of applied tokens, below, and to the calculation only once
;; for all sync-attrs.
applied-tokens (reduce (fn [applied-tokens attr]
(let [attr-group (get ctk/sync-attrs attr)
token-attrs (cto/shape-attr->token-attrs attr)]
(if (not (and (touched attr-group)
omit-touched?))
(into applied-tokens token-attrs)
applied-tokens)))
applied-tokens
ctk/swap-keep-attrs)]
(cond-> changes
(seq roperations)
(-> (update :redo-changes conj (make-change
@@ -1722,13 +1827,26 @@
(pcb/update-shapes
[shape-copy-id]
(fn [shape-copy objects]
(let [ids-map
(let [component-page
(ctf/get-component-page main-container main-component)
component-swap-children
(->> shape-main
:shapes
(map #(get (:objects component-page) %))
(filter #(some? (ctk/get-swap-slot %)))
(group-by ctk/get-swap-slot))
ids-map
(into {}
(comp
(map #(get objects %))
(keep
(fn [copy-shape]
(let [main-shape (ctf/get-ref-shape main-container main-component copy-shape)]
(let [main-shape
(if (some? (ctk/get-swap-slot copy-shape))
(first (get component-swap-children (ctk/get-swap-slot copy-shape)))
(ctf/get-ref-shape main-container main-component copy-shape))]
[(:id main-shape) (:id copy-shape)]))))
(:shapes shape-copy))
@@ -1744,7 +1862,8 @@
main-cells (-> shape-main (ctl/remap-grid-cells ids-map) :layout-grid-cells)]
(-> shape-copy
(assoc :layout-grid-cells
(ctl/merge-cells copy-cells main-cells omit-touched?)))))
(ctl/merge-cells main-cells copy-cells omit-touched?))
(ctl/assign-cells objects))))
{:ignore-touched true :with-objects? true})))
(defn- update-grid-main-attrs
@@ -2023,7 +2142,7 @@
has-flow? (partial ctp/get-frame-flow flows)]
(reduce (fn [changes frame-id]
(let [name (cfh/generate-unique-name @unames "Flow 1")
(let [name (cfh/generate-unique-name "Flow" @unames :immediate-suffix? true)
frame-id (get ids-map frame-id)
flow-id (uuid/next)
new-flow {:id flow-id

View File

@@ -10,12 +10,14 @@
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh]
[app.common.logic.variants :as clv]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.types.token :as cto]
[app.common.uuid :as uuid]))
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
(defn- generate-unapply-tokens
"When updating attributes that have a token applied, we must unapply it, because the value
@@ -239,21 +241,21 @@
(defn generate-relocate
[changes objects parent-id page-id to-index ids & {:keys [cell ignore-parents?]}]
(let [ids (cfh/order-by-indexed-shapes objects ids)
shapes (map (d/getf objects) ids)
parent (get objects parent-id)
[changes parent-id to-index ids & {:keys [cell ignore-parents?]}]
(let [objects (pcb/get-objects changes)
ids (cfh/order-by-indexed-shapes objects ids)
shapes (map (d/getf objects) ids)
parent (get objects parent-id)
all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids)
parents (if ignore-parents? #{parent-id} all-parents)
parents (if ignore-parents? #{parent-id} all-parents)
children-ids
(->> ids
(mapcat #(cfh/get-children-ids-with-self objects %)))
children-ids (mapcat #(cfh/get-children-ids-with-self objects %) ids)
child-heads
(->> ids
(mapcat #(ctn/get-child-heads objects %))
(map :id))
child-heads (mapcat #(ctn/get-child-heads objects %) ids)
child-heads-ids (map :id child-heads)
variant-heads (filter ctk/is-variant? child-heads)
component-main-parent
(ctn/find-component-main objects parent false)
@@ -340,9 +342,6 @@
cell (or cell (and index-cell-data [(:row index-cell-data) (:column index-cell-data)]))]
(-> changes
(pcb/with-page-id page-id)
(pcb/with-objects objects)
;; Remove layout-item properties when moving a shape outside a layout
(cond-> (not (ctl/any-layout? parent))
(pcb/update-shapes ids ctl/remove-layout-item-data))
@@ -353,7 +352,7 @@
;; Remove the swap slots if it is moving to a different component
(pcb/update-shapes
child-heads
child-heads-ids
(fn [shape]
(cond-> shape
(not= component-main-parent (ctn/find-component-main objects shape false))
@@ -365,7 +364,86 @@
;; Add component-root property when moving a component outside a component
(cond-> (not (ctn/get-instance-root objects parent))
(pcb/update-shapes child-heads #(assoc % :component-root true)))
(pcb/update-shapes child-heads-ids #(assoc % :component-root true)))
;; Remove variant info and rename when moving outside a variant-container
(cond-> (not (ctk/is-variant-container? parent))
((fn [changes]
(reduce
(fn [changes shape]
(let [new-name (str/replace (:variant-name shape) #", " " / ")
[cpath cname] (cfh/parse-path-name new-name)]
(-> changes
(pcb/update-component (:component-id shape)
#(-> (dissoc % :variant-id :variant-properties)
(assoc :name cname
:path cpath))
{:apply-changes-local-library? true})
(pcb/update-shapes [(:id shape)]
#(-> (dissoc % :variant-id :variant-name)
(assoc :name new-name))))))
changes
variant-heads))))
;; Add variant info and rename when moving into a different variant-container
(cond-> (ctk/is-variant-container? parent)
((fn [changes]
(let [get-base-name #(if (some? (:variant-name %))
(str/replace (:variant-name %) #", " " / ")
(:name %))
calc-num-props #(-> %
get-base-name
cfh/split-path
count)
max-path-items (apply max (map calc-num-props child-heads))
first-comp-id (->> parent
:shapes
first
(get objects)
:component-id)
data (pcb/get-library-data changes)
variant-properties (get-in data [:components first-comp-id :variant-properties])
num-props (count variant-properties)
num-new-props (if (< max-path-items num-props)
0
(- max-path-items num-props))
changes (nth
(iterate #(clv/generate-add-new-property % (:id parent)) changes)
num-new-props)]
(reduce
(fn [changes shape]
(if (= (:id parent) (:variant-id shape))
changes ;; do nothing if we aren't changing the parent
(let [base-name (get-base-name shape)
;; we need to get the updated library data to have access to the current properties
data (pcb/get-library-data changes)
props (clv/path-to-properties
base-name
(get-in data [:components first-comp-id :variant-properties]))
variant-name (clv/properties-to-name props)
[cpath cname] (cfh/parse-path-name (:name parent))]
(-> (pcb/update-component changes
(:component-id shape)
#(assoc % :variant-id (:id parent)
:variant-properties props
:name cname
:path cpath)
{:apply-changes-local-library? true})
(pcb/update-shapes [(:id shape)]
#(assoc % :variant-id (:id parent)
:variant-name variant-name
:name (:name parent)))))))
changes
child-heads)))))
;; Move the shapes
(pcb/change-parent parent-id

View File

@@ -1,31 +1,38 @@
;; 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.logic.tokens
(:require
[app.common.files.changes-builder :as pcb]
[app.common.types.tokens-lib :as ctob]))
(defn generate-update-active-sets
"Copy the active sets from the currently active themes and move them to the hidden token theme and update the theme with `update-hidden-theme-fn`.
"Copy the active sets from the currently active themes and move them
to the hidden token theme and update the theme with
`update-theme-fn`.
Use this for managing sets active state without having to modify a user created theme (\"no themes selected\" state in the ui)."
[changes tokens-lib update-hidden-theme-fn]
Use this for managing sets active state without having to modify a
user created theme (\"no themes selected\" state in the ui)."
[changes tokens-lib update-theme-fn]
(let [prev-active-token-themes (ctob/get-active-theme-paths tokens-lib)
active-token-set-names (ctob/get-active-themes-set-names tokens-lib)
prev-hidden-token-theme (ctob/get-hidden-theme tokens-lib)
hidden-token-theme (-> (or (some-> prev-hidden-token-theme (ctob/set-sets active-token-set-names))
(ctob/make-hidden-token-theme :sets active-token-set-names))
(update-hidden-theme-fn))
prev-hidden-token-theme (ctob/get-hidden-theme tokens-lib)
changes (-> changes
(pcb/update-active-token-themes #{ctob/hidden-token-theme-path} prev-active-token-themes))
changes (if prev-hidden-token-theme
(pcb/update-token-theme changes hidden-token-theme prev-hidden-token-theme)
(pcb/add-token-theme changes hidden-token-theme))]
changes))
hidden-token-theme (-> (some-> prev-hidden-token-theme (ctob/set-sets active-token-set-names))
(update-theme-fn))]
(-> changes
(pcb/update-active-token-themes #{ctob/hidden-token-theme-path} prev-active-token-themes)
(pcb/set-token-theme (:group prev-hidden-token-theme)
(:name prev-hidden-token-theme)
hidden-token-theme))))
(defn generate-toggle-token-set
"Toggle a token set at `set-name` in `tokens-lib` without modifying a user theme."
"Toggle a token set at `set-name` in `tokens-lib` without modifying a
user theme."
[changes tokens-lib set-name]
(generate-update-active-sets changes tokens-lib #(ctob/toggle-set % set-name)))
@@ -48,17 +55,19 @@
(defn vec-starts-with? [v1 v2]
(= (subvec v1 0 (min (count v1) (count v2))) v2))
(defn calculate-move-token-set-or-set-group
(defn- calculate-move-token-set-or-set-group
[tokens-lib {:keys [from-index to-index position collapsed-paths]
:or {collapsed-paths #{}}}]
(let [tree (-> (ctob/get-set-tree tokens-lib)
(ctob/walk-sets-tree-seq :walk-children? #(contains? collapsed-paths %)))
(ctob/walk-sets-tree-seq :skip-children-pred #(contains? collapsed-paths %)))
from (nth tree from-index)
to (nth tree to-index)
before (case position
:top to
:bot (nth tree (inc to-index) nil)
:center nil)
prev-before (if (:group? from)
(->> (drop (inc from-index) tree)
(filter (fn [element]
@@ -72,6 +81,7 @@
(= :bot position)
(:group? to)
(not (get collapsed-paths (:path to)))))
from-path (:path from)
to-parent-path (if drop-as-direct-group-child?
(:path to)
@@ -117,15 +127,15 @@
(defn generate-move-token-set
"Create changes for dropping a token set or token set.
Throws for impossible moves."
[changes tokens-lib drop-opts]
(if-let [drop-opts' (calculate-move-token-set-or-set-group tokens-lib drop-opts)]
(pcb/move-token-set-before changes drop-opts')
[changes tokens-lib params]
(if-let [params (calculate-move-token-set-or-set-group tokens-lib params)]
(pcb/move-token-set changes params)
changes))
(defn generate-move-token-set-group
"Create changes for dropping a token set or token set group.
Throws for impossible moves"
[changes tokens-lib drop-opts]
(if-let [drop-opts' (calculate-move-token-set-or-set-group tokens-lib drop-opts)]
(pcb/move-token-set-group-before changes drop-opts')
[changes tokens-lib params]
(if-let [params (calculate-move-token-set-or-set-group tokens-lib params)]
(pcb/move-token-set-group changes params)
changes))

View File

@@ -0,0 +1,160 @@
;; 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.logic.variants
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.types.components-list :as ctcl]
[cuerdas.core :as str]))
(def property-prefix "Property")
(def property-regex (re-pattern (str property-prefix "(\\d+)")))
(def value-prefix "Value")
(defn find-related-components
"Find a list of the components thet belongs to this variant-id"
[data objects variant-id]
(->> (dm/get-in objects [variant-id :shapes])
(map #(dm/get-in objects [% :component-id]))
(map #(ctcl/get-component data % true))
reverse))
(defn properties-to-name
"Transform the properties into a name, with the values separated by comma"
[properties]
(->> properties
(map :value)
(remove str/empty?)
(str/join ", ")))
(defn next-property-number
"Returns the next property number, to avoid duplicates on the property names"
[properties]
(let [numbers (keep
#(some->> (:name %) (re-find property-regex) second d/parse-integer)
properties)
max-num (if (seq numbers)
(apply max numbers)
0)]
(inc (max max-num (count properties)))))
(defn path-to-properties
"From a list of properties and a name with path, assign each token of the
path as value of a different property"
[path properties]
(let [next-prop-num (next-property-number properties)
cpath (cfh/split-path path)
assigned (mapv #(assoc % :value (nth cpath %2 "")) properties (range))
remaining (drop (count properties) cpath)
new-properties (map-indexed (fn [i v] {:name (str property-prefix (+ next-prop-num i))
:value v}) remaining)]
(into assigned new-properties)))
(defn- dashes-to-end
[property-values]
(let [dashes (if (some #(= % "--") property-values) ["--"] [])]
(concat (remove #(= % "--") property-values) dashes)))
(defn extract-properties-values
[data objects variant-id]
(->> (find-related-components data objects variant-id)
(mapcat :variant-properties)
(group-by :name)
(map (fn [[k v]]
{:name k
:value (->> v
(map #(if (str/empty? (:value %)) "--" (:value %)))
distinct
dashes-to-end)}))))
(defn generate-update-property-name
[changes variant-id pos new-name]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (find-related-components data objects variant-id)]
(reduce (fn [changes component]
(pcb/update-component
changes (:id component)
#(assoc-in % [:variant-properties pos :name] new-name)
{:apply-changes-local-library? true}))
changes
related-components)))
(defn generate-remove-property
[changes variant-id pos]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (find-related-components data objects variant-id)]
(reduce (fn [changes component]
(let [props (:variant-properties component)
props (d/remove-at-index props pos)
main-id (:main-instance-id component)
name (properties-to-name props)]
(-> changes
(pcb/update-component (:id component) #(assoc % :variant-properties props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
changes
related-components)))
(defn generate-update-property-value
[changes component-id pos value]
(let [data (pcb/get-library-data changes)
component (ctcl/get-component data component-id true)
main-id (:main-instance-id component)
name (-> (:variant-properties component)
(update pos assoc :value value)
properties-to-name)]
(-> changes
(pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
(defn generate-add-new-property
[changes variant-id & {:keys [fill-values?]}]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (find-related-components data objects variant-id)
props (-> related-components first :variant-properties)
next-prop-num (next-property-number props)
property-name (str property-prefix next-prop-num)
[_ changes]
(reduce (fn [[num changes] component]
(let [main-id (:main-instance-id component)
update-props #(-> (d/nilv % [])
(conj {:name property-name
:value (if fill-values? (str value-prefix num) "")}))
update-name #(if fill-values?
(if (str/empty? %)
(str value-prefix num)
(str % ", " value-prefix num))
%)]
[(inc num)
(-> changes
(pcb/update-component (:id component)
#(update % :variant-properties update-props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(update % :variant-name update-name)))]))
[1 changes]
related-components)]
changes))

View File

@@ -390,14 +390,22 @@
(register! :merge (mu/-merge))
(register! :union (mu/-union))
(def uuid-rx
#"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
(defn parse-uuid
(defn- parse-uuid
[s]
(if (string? s)
(some->> (re-matches uuid-rx s) uuid/uuid)
s))
(if (uuid? s)
s
(if (str/empty? s)
nil
(try
(uuid/parse s)
(catch #?(:clj Exception :cljs :default) _cause
s)))))
(defn- encode-uuid
[v]
(if (uuid? v)
(str v)
v))
(register!
{:type ::uuid
@@ -409,8 +417,8 @@
:gen/gen (sg/uuid)
:decode/string parse-uuid
:decode/json parse-uuid
:encode/string str
:encode/json str
:encode/string encode-uuid
:encode/json encode-uuid
::oapi/type "string"
::oapi/format "uuid"}})
@@ -856,7 +864,7 @@
choices))]
{:pred pred
:type-properties
{:title "contains"
{:title "contains any"
:description "contains predicate"}}))})
(register!
@@ -1019,26 +1027,26 @@
(def valid-text?
(validator ::text))
(def check-safe-int!
(def check-safe-int
(check-fn ::safe-int))
(def check-set-of-strings!
(def check-set-of-strings
(check-fn ::set-of-strings))
(def check-email!
(def check-email
(check-fn ::email))
(def check-uuid!
(def check-uuid
(check-fn ::uuid :hint "expected valid uuid instance"))
(def check-string!
(def check-string
(check-fn :string :hint "expected string"))
(def check-coll-of-uuid!
(def check-coll-of-uuid
(check-fn ::coll-of-uuid))
(def check-set-of-uuid!
(def check-set-of-uuid
(check-fn ::set-of-uuid))
(def check-set-of-emails!
(def check-set-of-emails
(check-fn [::set ::email]))

View File

@@ -60,6 +60,7 @@
(let [smallest (-> params :shrunk :smallest vec)]
(println)
(println "Condition failed with the following params:")
(println "Seed:" (:seed params))
(println)
(pp/pprint smallest)))

View File

@@ -57,6 +57,14 @@
:main-instance-page (:id page)
:shapes updated-shapes))))))))
(defn update-component
[file component-label & {:keys [] :as params}]
(let [component-id (thi/id component-label)]
(ctf/update-file-data
file
(fn [file-data]
(ctkl/update-component file-data component-id #(merge % params))))))
(defn get-component
[file label & {:keys [include-deleted?] :or {include-deleted? false}}]
(ctkl/get-component (:data file) (thi/id label) include-deleted?))

View File

@@ -35,6 +35,11 @@
(ctob/get-set set-name)
(ctob/get-token token-name)))))
(defn token-data-eq?
"Compare token data without comparing modified timestamp"
[t1 t2]
(= (dissoc t1 :modified-at) (dissoc t2 :modified-at)))
(defn- set-stroke-width
[shape stroke-width]
(let [strokes (if (seq (:strokes shape))

View File

@@ -0,0 +1,25 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.test-helpers.variants
(:require
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]))
(defn add-variant
[file variant-label component1-label root1-label component2-label root2-label
& {:keys []}]
(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/make-component component1-label root1-label)
(thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]})
(thc/make-component component2-label root2-label)
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]}))))

View File

@@ -118,10 +118,10 @@
(def valid-color?
(sm/lazy-validator schema:color))
(def check-color!
(def check-color
(sm/check-fn schema:color :hint "expected valid color struct"))
(def check-recent-color!
(def check-recent-color
(sm/check-fn schema:recent-color))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -215,6 +215,19 @@
(and (= shape-id (:main-instance-id component))
(= page-id (:main-instance-page component))))
(defn is-variant?
"Check if this shape or component is a variant component"
[item]
(some? (:variant-id item)))
(defn is-variant-container?
"Check if this shape is a variant container"
[shape]
(:is-variant-container shape))
(defn set-touched-group
[touched group]
(when group
@@ -320,6 +333,8 @@
(let [parent (get objects (:parent-id shape))]
;; We don't want to change the structure of component copies
(and (not (in-component-copy-not-head? shape))
;; We don't want to duplicate variants
(not (is-variant? shape))
;; Non instance, non copy. We allow
(or (not (instance-head? shape))
(not (in-component-copy? parent))))))

View File

@@ -34,12 +34,14 @@
(assoc component :modified-at (dt/now)))
(defn add-component
[fdata {:keys [id name path main-instance-id main-instance-page shapes annotation]}]
[fdata {:keys [id name path main-instance-id main-instance-page shapes annotation variant-id variant-properties]}]
(let [components-v2 (dm/get-in fdata [:options :components-v2])
fdata (update fdata :components assoc id (touch {:id id :name name :path path}))]
(if components-v2
(cond-> (update-in fdata [:components id] assoc :main-instance-id main-instance-id :main-instance-page main-instance-page)
annotation (update-in [:components id] assoc :annotation annotation))
annotation (update-in [:components id] assoc :annotation annotation)
variant-id (update-in [:components id] assoc :variant-id variant-id)
variant-properties (update-in [:components id] assoc :variant-properties variant-properties))
(let [wrap-object-fn cfeat/*wrap-with-objects-map-fn*]
(assoc-in fdata [:components id :objects]
@@ -48,7 +50,7 @@
(wrap-object-fn)))))))
(defn mod-component
[file-data {:keys [id name path main-instance-id main-instance-page objects annotation modified-at]}]
[file-data {:keys [id name path main-instance-id main-instance-page objects annotation variant-id variant-properties modified-at]}]
(let [wrap-objects-fn cfeat/*wrap-with-objects-map-fn*]
(d/update-in-when file-data [:components id]
(fn [component]
@@ -76,10 +78,22 @@
(assoc :annotation annotation)
(nil? annotation)
(dissoc :annotation))
(dissoc :annotation)
(some? variant-id)
(assoc :variant-id variant-id)
(nil? variant-id)
(dissoc :variant-id)
(some? variant-properties)
(assoc :variant-properties variant-properties)
(nil? variant-properties)
(dissoc :variant-properties))
diff (set/difference
(ctk/diff-components component new-comp)
#{:annotation :modified-at})] ;; The set of properties that doesn't mark a component as touched
#{:annotation :modified-at :variant-id :variant-properties})] ;; The set of properties that doesn't mark a component as touched
(if (empty? diff)
new-comp

View File

@@ -406,7 +406,7 @@
(cond-> new-shape
:always
(-> (gsh/move delta)
(dissoc :touched))
(dissoc :touched :variant-id :variant-name))
(and main-instance? root?)
(assoc :main-instance true)
@@ -501,7 +501,12 @@
(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 [; When we are pasting, the main shapes will be pasted as copies, unless the
(let [; If the original shapes had been cutted, and we are pasting them now, they aren't
; in objects. We can add them to locate later
objects (merge objects
(into {} (map (juxt :id identity) children)))
; When we are pasting, the main shapes will be pasted as copies, unless the
; original component doesn't exist or is deleted. So for this function purposes, they
; are removed from the list
remove? (fn [shape]
@@ -535,11 +540,26 @@
(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.
;; 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?
(->> children (every? #(= parent-id (:parent-id %))))]
;; In case no-changes is true we must ensure we are copy pasting the children in the same position
(if (or (and no-changes? (not pasting?)) (not (invalid-structure-for-component? objects parent children pasting? libraries)))
(and (every? #(= parent-id (:parent-id %)) children)
(not pasting?))
all-main?
(every? ctk/main-instance? children)
any-main-descendant
(some
(fn [shape]
(some ctk/main-instance? (cfh/get-children-with-self objects (:id shape))))
children)]
(if (or no-changes?
(and (not (invalid-structure-for-component? objects parent children pasting? libraries))
;; If we are moving into a variant-container, all the items should be main
(or all-main? (not (ctk/is-variant-container? parent)))
;; If we are moving into a main component, no descendant can be main
(or (nil? any-main-descendant) (not (ctk/main-instance? parent)))))
[parent-id (get-frame parent-id)]
(recur (:parent-id parent) objects children pasting? libraries))))))

View File

@@ -148,7 +148,7 @@
(sm/register! ::stroke schema:stroke)
(def check-stroke!
(def check-stroke
(sm/check-fn schema:stroke))
(def schema:shape-base-attrs
@@ -211,7 +211,7 @@
[:interactions {:optional true}
[:vector {:gen/max 2} ::ctsi/interaction]]
[:shadow {:optional true}
[:vector {:gen/max 1} ::ctss/shadow]]
[:vector {:gen/max 1} ctss/schema:shadow]]
[:blur {:optional true} ::ctsb/blur]
[:grow-type {:optional true}
[::sm/one-of grow-types]]

View File

@@ -8,7 +8,7 @@
(:require
[app.common.schema :as sm]))
(def types #{:png :jpeg :svg :pdf})
(def types #{:png :jpeg :webp :svg :pdf})
(def schema:export
[:map {:title "ShapeExport"}

View File

@@ -7,7 +7,6 @@
(ns app.common.types.shape.interactions
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.bounds :as gsb]
@@ -110,13 +109,27 @@
(def check-animation!
(sm/check-fn schema:animation))
(def schema:interaction-attrs
[:map {:title "InteractionAttrs"}
[:action-type {:optional true} [::sm/one-of action-types]]
[:event-type {:optional true} [::sm/one-of event-types]]
[:destination {:optional true} [:maybe ::sm/uuid]]
[:preserve-scroll {:optional true} :boolean]
[:animation {:optional true} schema:animation]
[:overlay-position {:optional true} ::gpt/point]
[:overlay-pos-type {:optional true} [::sm/one-of overlay-positioning-types]]
[:close-click-outside {:optional true} :boolean]
[:background-overlay {:optional true} :boolean]
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]
[:url {:optional true} :string]])
(def schema:navigate-interaction
[:map
[:action-type [:= :navigate]]
[:event-type [::sm/one-of event-types]]
[:destination {:optional true} [:maybe ::sm/uuid]]
[:preserve-scroll {:optional true} :boolean]
[:animation {:optional true} ::animation]])
[:animation {:optional true} schema:animation]])
(def schema:open-overlay-interaction
[:map
@@ -127,7 +140,7 @@
[:destination {:optional true} [:maybe ::sm/uuid]]
[:close-click-outside {:optional true} :boolean]
[:background-overlay {:optional true} :boolean]
[:animation {:optional true} ::animation]
[:animation {:optional true} schema:animation]
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]])
(def schema:toggle-overlay-interaction
@@ -139,7 +152,7 @@
[:destination {:optional true} [:maybe ::sm/uuid]]
[:close-click-outside {:optional true} :boolean]
[:background-overlay {:optional true} :boolean]
[:animation {:optional true} ::animation]
[:animation {:optional true} schema:animation]
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]])
(def schema:close-overlay-interaction
@@ -147,7 +160,7 @@
[:action-type [:= :close-overlay]]
[:event-type [::sm/one-of event-types]]
[:destination {:optional true} [:maybe ::sm/uuid]]
[:animation {:optional true} ::animation]
[:animation {:optional true} schema:animation]
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]])
(def schema:prev-scren-interaction
@@ -162,25 +175,25 @@
[:url :string]])
(def schema:interaction
[:multi {:dispatch :action-type
: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))
:decode/json #(update % :action-type keyword)}
[: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]])
[: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)
(def check-interaction!
(def check-interaction
(sm/check-fn schema:interaction))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -203,18 +216,13 @@
(defn set-event-type
[interaction event-type shape]
(dm/assert!
"Should be an interraction map"
(check-interaction! interaction))
(assert (check-interaction interaction))
(assert (contains? event-types event-type)
"should be a valid event type")
(dm/assert!
"Should be a valid event type"
(contains? event-types event-type))
(dm/assert!
"The `:after-delay` event type incompatible with not frame shapes"
(or (not= event-type :after-delay)
(cfh/frame-shape? shape)))
(assert (or (not= event-type :after-delay)
(cfh/frame-shape? shape))
"the `:after-delay` event type incompatible with not frame shapes")
(if (= (:event-type interaction) event-type)
interaction
@@ -230,14 +238,9 @@
(defn set-action-type
[interaction action-type]
(dm/assert!
"Should be an interraction map"
(check-interaction! interaction))
(dm/assert!
"Should be a valid event type"
(contains? action-types action-type))
(assert (check-interaction interaction))
(assert (contains? action-types action-type)
"Should be a valid event type")
(let [new-interaction
(if (= (:action-type interaction) action-type)
@@ -284,18 +287,10 @@
(defn set-delay
[interaction delay]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid delay"
(sm/check-safe-int! delay))
(dm/assert!
"expected compatible interaction event type"
(has-delay interaction))
(assert (check-interaction interaction))
(assert (sm/check-safe-int delay))
(assert (has-delay interaction)
"expected compatible interaction event type")
(assoc interaction :delay delay))
@@ -315,14 +310,9 @@
(defn set-destination
[interaction destination]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected compatible interaction event type"
(has-destination interaction))
(assert (check-interaction interaction))
(assert (has-destination interaction)
"expected compatible interaction event type")
(cond-> interaction
:always
@@ -340,17 +330,11 @@
(defn set-preserve-scroll
[interaction preserve-scroll]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected boolean for `preserve-scroll`"
(boolean? preserve-scroll))
(dm/assert!
"expected compatible interaction map with preserve-scroll"
(has-preserve-scroll interaction))
(assert (check-interaction interaction))
(assert (boolean? preserve-scroll)
"expected boolean for `preserve-scroll`")
(assert (has-preserve-scroll interaction)
"expected compatible interaction map with preserve-scroll")
(assoc interaction :preserve-scroll preserve-scroll))
@@ -361,17 +345,11 @@
(defn set-url
[interaction url]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected a string for `url`"
(string? url))
(dm/assert!
"expected compatible interaction map with url param"
(has-url interaction))
(assert (check-interaction interaction))
(assert (string? url)
"expected a string for `url`")
(assert (has-url interaction)
"expected compatible interaction map with url param")
(assoc interaction :url url))
@@ -382,17 +360,12 @@
(defn set-overlay-pos-type
[interaction overlay-pos-type shape objects]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(assert (check-interaction interaction))
(dm/assert!
"expected valid overlay positioning type"
(contains? overlay-positioning-types overlay-pos-type))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (contains? overlay-positioning-types overlay-pos-type)
"expected valid overlay positioning type")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(assoc interaction
:overlay-pos-type overlay-pos-type
@@ -403,17 +376,11 @@
(defn toggle-overlay-pos-type
[interaction overlay-pos-type shape objects]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid overlay positioning type"
(contains? overlay-positioning-types overlay-pos-type))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (contains? overlay-positioning-types overlay-pos-type)
"expected valid overlay positioning type")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(let [new-pos-type (if (= (:overlay-pos-type interaction) overlay-pos-type)
:manual
@@ -427,17 +394,12 @@
(defn set-overlay-position
[interaction overlay-position]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(assert (check-interaction interaction))
(assert (gpt/point? overlay-position)
"expected valid overlay position")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(dm/assert!
"expected valid overlay position"
(gpt/point? overlay-position))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assoc interaction
:overlay-pos-type :manual
@@ -446,52 +408,34 @@
(defn set-close-click-outside
[interaction close-click-outside]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected boolean value for `close-click-outside`"
(boolean? close-click-outside))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (boolean? close-click-outside)
"expected boolean value for `close-click-outside`")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(assoc interaction :close-click-outside close-click-outside))
(defn set-background-overlay
[interaction background-overlay]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected boolean value for `background-overlay`"
(boolean? background-overlay))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (boolean? background-overlay)
"expected boolean value for `background-overlay`")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(assoc interaction :background-overlay background-overlay))
(defn set-position-relative-to
[interaction position-relative-to]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid uuid for `position-relative-to`"
(or (nil? position-relative-to)
(uuid? position-relative-to)))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (or (nil? position-relative-to)
(uuid? position-relative-to))
"expected valid uuid for `position-relative-to`")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(assoc interaction :position-relative-to position-relative-to))
@@ -519,13 +463,9 @@
frame-offset] ;; if this interaction starts in a frame opened
;; on another interaction, this is the position
;; of that frame
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(let [;; When the interactive item is inside a nested frame we need to add to the offset the position
;; of the parent-frame otherwise the position won't match
@@ -617,22 +557,15 @@
(defn set-animation-type
[interaction animation-type]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid value for `animation-type`"
(or (nil? animation-type)
(contains? animation-types animation-type)))
(dm/assert!
"expected interaction map compatible with animation"
(has-animation? interaction))
(dm/assert!
"expected allowed animation type"
(allowed-animation? (:action-type interaction) animation-type))
(assert (check-interaction interaction))
(assert (or (nil? animation-type)
(contains? animation-types animation-type))
"expected valid value for `animation-type`")
(assert (has-animation? interaction)
"expected interaction map compatible with animation")
(assert (allowed-animation? (:action-type interaction) animation-type)
"expected allowed animation type")
(if (= (-> interaction :animation :animation-type) animation-type)
interaction
@@ -668,17 +601,10 @@
(defn set-duration
[interaction duration]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid duration"
(sm/check-safe-int! duration))
(dm/assert!
"expected compatible interaction map"
(has-duration? interaction))
(assert (check-interaction interaction))
(assert (sm/check-safe-int duration))
(assert (has-duration? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :duration duration))
@@ -689,17 +615,11 @@
(defn set-easing
[interaction easing]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid easing"
(contains? easing-types easing))
(dm/assert!
"expected compatible interaction map"
(has-easing? interaction))
(assert (check-interaction interaction))
(assert (contains? easing-types easing)
"expected valid easing")
(assert (has-easing? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :easing easing))
@@ -712,17 +632,11 @@
(defn set-way
[interaction way]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid way"
(contains? way-types way))
(dm/assert!
"expected compatible interaction map"
(has-way? interaction))
(assert (check-interaction interaction))
(assert (contains? way-types way)
"expected valid way")
(assert (has-way? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :way way))
@@ -733,26 +647,20 @@
(defn set-direction
[interaction direction]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(assert (check-interaction interaction))
(assert (contains? direction-types direction)
"expected valid direction")
(dm/assert!
"expected valid direction"
(contains? direction-types direction))
(dm/assert!
"expected compatible interaction map"
(has-direction? interaction))
(assert (has-direction? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :direction direction))
(defn invert-direction
[animation]
(dm/assert!
"expected valid animation map"
(or (nil? animation)
(check-animation! animation)))
(assert (or (nil? animation)
(check-animation! animation))
"expected valid animation map")
(case (:direction animation)
:right
@@ -768,24 +676,18 @@
(defn has-offset-effect?
[interaction]
; Offset-effect is ignored in slide animations of overlay actions
;; Offset-effect is ignored in slide animations of overlay actions
(and (= (:action-type interaction) :navigate)
(= (-> interaction :animation :animation-type) :slide)))
(defn set-offset-effect
[interaction offset-effect]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid boolean for `offset-effect`"
(boolean? offset-effect))
(dm/assert!
"expected compatible interaction map"
(has-offset-effect? interaction))
(assert (check-interaction interaction))
(assert (boolean? offset-effect)
"expected valid boolean for `offset-effect`")
(assert (has-offset-effect? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :offset-effect offset-effect))

View File

@@ -1643,15 +1643,10 @@
untouched as possible"
[target-cells source-cells omit-touched?]
(if omit-touched?
(letfn [(get-data [cells id]
(dissoc (get cells id) :row :column :row-span :column-span))
(merge-cells [source-cell target-cell]
(letfn [(merge-cells [source-cell target-cell]
(-> source-cell
(d/patch-object
(dissoc target-cell :shapes :row :column :row-span :column-span))
(cond-> (d/not-empty? (:shapes target-cell))
(assoc :shapes (:shapes target-cell)))))]
(dissoc target-cell :row :column :row-span :column-span))))]
(let [deleted-cells
(into #{}
(filter #(not (contains? source-cells %)))
@@ -1659,10 +1654,7 @@
touched-cells
(into #{}
(filter #(and
(not (contains? deleted-cells %))
(not= (get-data source-cells %)
(get-data target-cells %))))
(filter #(not (contains? deleted-cells %)))
(keys target-cells))]
(->> touched-cells
@@ -1672,3 +1664,12 @@
(d/update-when id merge-cells (get target-cells id))))
source-cells))))
source-cells))
(defn toggle-fix-if-auto
"Changes the sizing to fix if it's fill"
[shape]
(cond-> shape
(= (:layout-item-h-sizing shape) :fill)
(assoc :layout-item-h-sizing :fix)
(= (:layout-item-v-sizing shape) :fill)
(assoc :layout-item-v-sizing :fix)))

View File

@@ -26,7 +26,9 @@
[:hidden :boolean]
[:color ::ctc/color]])
(sm/register! ::shadow schema:shadow)
(def check-shadow!
(def check-shadow
(sm/check-fn schema:shadow))
(def valid-shadow?
(sm/validator schema:shadow))

View File

@@ -60,7 +60,7 @@
(token-types t))
(def token-name-ref
[:and :string [:re #"^(?!\$)([a-zA-Z0-9-$]+\.?)*(?<!\.)$"]])
[:and :string [:re #"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?<!\.)$"]])
(defn valid-token-name-ref?
[n]
@@ -129,6 +129,10 @@
[:p2 {:optional true} token-name-ref]
[:p3 {:optional true} token-name-ref]
[:p4 {:optional true} token-name-ref]
[:m1 {:optional true} token-name-ref]
[:m2 {:optional true} token-name-ref]
[:m3 {:optional true} token-name-ref]
[:m4 {:optional true} token-name-ref]
[:x {:optional true} token-name-ref]
[:y {:optional true} token-name-ref]])
@@ -178,12 +182,27 @@
([shape-attr] (shape-attr->token-attrs shape-attr nil))
([shape-attr changed-sub-attr]
(cond
(= :fills shape-attr) #{:fill}
(and (= :strokes shape-attr) (nil? changed-sub-attr)) #{:stroke-width :stroke-color}
(= :fills shape-attr)
#{:fill}
(and (= :strokes shape-attr) (nil? changed-sub-attr))
#{:stroke-width :stroke-color}
(= :strokes shape-attr)
(cond
(some #{:stroke-color} changed-sub-attr) #{:stroke-color}
(some #{:stroke-width} changed-sub-attr) #{:stroke-width})
(= :layout-padding shape-attr)
(if (seq changed-sub-attr)
changed-sub-attr
#{:p1 :p2 :p3 :p4})
(= :layout-item-margin shape-attr)
(if (seq changed-sub-attr)
changed-sub-attr
#{:m1 :m2 :m3 :m4})
(border-radius-keys shape-attr) #{shape-attr}
(sizing-keys shape-attr) #{shape-attr}
(opacity-keys shape-attr) #{shape-attr}

View File

@@ -15,6 +15,7 @@
[:group :string]
[:description [:maybe :string]]
[:is-source :boolean]
[:id :string]
[:modified-at {:optional true} ::sm/inst]
[:sets :any]])
@@ -24,4 +25,4 @@
[:name :string]
[:description {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::sm/inst]
[:tokens :any]])
[:tokens {:optional true} :any]])

View File

File diff suppressed because it is too large Load Diff

View File

@@ -17,9 +17,14 @@
java.util.UUID
java.nio.ByteBuffer)))
(def regex
#"^[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]$")
(defn uuid
"Creates an UUID instance from string, expectes valid uuid strings,
the existense of validation is implementation detail"
the existense of validation is implementation detail.
UNSAFE: this can accept invalid uuids or incomplete uuids"
[s]
#?(:clj (UUID/fromString s)
:cljs (c/uuid s)))
@@ -27,8 +32,21 @@
(defn parse
"Parse string uuid representation into proper UUID instance, validates input"
[s]
#?(:clj (UUID/fromString s)
:cljs (c/parse-uuid s)))
(if (and (string? s) ^boolean (re-matches regex s))
#?(:clj (UUID/fromString s)
:cljs (uuid s))
(let [message (str "invalid string '" s "' for uuid")]
(throw #?(:clj (IllegalArgumentException. message)
:cljs (js/Error. message))))))
(defn parse*
"Exception safe version of `parse`."
[s]
(try
(parse s)
(catch #?(:clj Exception :cljs :default) _cause
nil)))
(defn next
[]
@@ -95,6 +113,11 @@
(impl/getUnsignedParts (.-uuid ^UUID this))))
#?(:cljs
(defn from-unsigned-parts
[a b c d]
(uuid (impl/fromUnsignedParts a b c d))))
#?(:cljs
(defn get-u32
"A cached variant of get-unsigned-parts"

View File

@@ -51,9 +51,13 @@
(t/is (= [0 0 0] (colors/hex->rgb "#kkk")))
(t/is (= [1 2 3] (colors/hex->rgb "#010203"))))
#?(:cljs
(t/deftest format-hsla
(t/is (= "210, 50%, 1%, 1" (colors/format-hsla [210.0 0.5 0.00784313725490196 1])))))
(t/deftest format-hsla
(t/is (= "210, 50%, 0.78%, 1" (colors/format-hsla [210.0 0.5 0.00784313725490196 1])))
(t/is (= "220, 5%, 30%, 0.8" (colors/format-hsla [220.0 0.05 0.3 0.8]))))
(t/deftest format-rgba
(t/is (= "210, 199, 12, 0.08" (colors/format-rgba [210 199 12 0.08])))
(t/is (= "210, 199, 12, 1" (colors/format-rgba [210 199 12 1]))))
(t/deftest rgb-to-hsl
(t/is (= [210.0 0.5 0.00784313725490196] (colors/rgb->hsl [1 2 3])))

View File

@@ -0,0 +1,38 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.files.helpers-test
(:require
[app.common.files.helpers :as cfh]
[clojure.test :as t]))
(t/deftest test-generate-unique-name
(t/testing "Test unique name generation"
(let [suffix-fn #(str "-copy-" %)]
(t/is (cfh/generate-unique-name "base-name"
#{"base-name" "base-name-copy-1"}
:suffix-fn suffix-fn)
"base-name-copy-2")
(t/is (cfh/generate-unique-name "base-name"
#{"base-name-copy-2"}
:suffix-fn suffix-fn)
"base-name-copy-1")
(t/is (cfh/generate-unique-name "base-name"
#{"base-namec-copy"}
:suffix-fn suffix-fn)
"base-name-copy-1")
(t/is (cfh/generate-unique-name "base-name"
#{"base-name"}
:suffix-fn suffix-fn)
"base-name-copy-1")))
(t/testing "Test unique name generation with immidate suffix and default suffix-fn"
(t/is (cfh/generate-unique-name "base-name" #{} :immediate-suffix? true)
"base-name 1")
(t/is (cfh/generate-unique-name "base-name"
#{"base-name 1" "base-name 2"}
:immediate-suffix? true)
"base-name 3")))

View File

@@ -20,6 +20,7 @@
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
@@ -285,10 +286,12 @@
component (thc/get-component file :component1)
;; ==== Action
changes (cll/generate-duplicate-component (pcb/empty-changes)
file
(:id component)
true)
[_ changes]
(cll/generate-duplicate-component (pcb/empty-changes)
file
(:id component)
(uuid/next)
true)
file' (thf/apply-changes file changes)

View File

@@ -61,10 +61,10 @@
blue1 (ths/get-shape file :blue1)
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
uuid/zero ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@@ -91,10 +91,10 @@
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id b2) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@@ -121,10 +121,10 @@
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@@ -134,10 +134,10 @@
yellow' (ths/get-shape file' :frame-yellow)
;; Move yellow into root
changes' (cls/generate-relocate (pcb/empty-changes nil)
(:objects page')
changes' (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page'))
(pcb/with-objects (:objects page')))
uuid/zero ;; parent-id
(:id page') ;; page-id
0 ;; to-index
#{(:id yellow')}) ;; ids
@@ -164,10 +164,10 @@
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@@ -178,10 +178,10 @@
b2' (ths/get-shape file' :frame-b2)
;; Move yellow into b2
changes' (cls/generate-relocate (pcb/empty-changes nil)
(:objects page')
changes' (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page'))
(pcb/with-objects (:objects page')))
(:id b2') ;; parent-id
(:id page') ;; page-id
0 ;; to-index
#{(:id yellow')}) ;; ids
@@ -254,10 +254,10 @@
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@@ -308,10 +308,10 @@
blue1 (ths/get-shape file :blue1)
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:parent-id blue1) ;; parent-id
(:id page) ;; page-id
2 ;; to-index
#{(:id blue1)}) ;; ids
@@ -338,10 +338,10 @@
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@@ -351,10 +351,10 @@
page' (thf/current-page file')
blue1' (ths/get-shape file' :blue1)
b1' (ths/get-shape file' :frame-b1)
changes' (cls/generate-relocate (pcb/empty-changes nil)
(:objects page')
changes' (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page'))
(pcb/with-objects (:objects page')))
(:id b1') ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1')}) ;; ids
@@ -382,10 +382,10 @@
;; ==== Action
;; Relocate blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@@ -396,10 +396,10 @@
page' (thf/current-page file')
blue1' (ths/get-shape file' :blue1)
b1' (ths/get-shape file' :frame-b1)
changes' (cls/generate-relocate (pcb/empty-changes nil)
(:objects page')
changes' (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page'))
(pcb/with-objects (:objects page')))
(:id b1') ;; parent-id
(:id page') ;; page-id
0 ;; to-index
#{(:id blue1')}) ;; ids
@@ -428,10 +428,10 @@
green-copy (ths/get-shape file :green-copy)
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
uuid/zero ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id green-copy)}) ;; ids
@@ -459,10 +459,10 @@
b2 (ths/get-shape file :frame-b2)
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id b2) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id green-copy)}) ;; ids

View File

@@ -136,10 +136,10 @@
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
0 ; to-index
#{(thi/id :free-shape)}) ; ids
@@ -231,10 +231,10 @@
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
2 ; to-index
#{(:id copy-child1)}) ; ids

View File

@@ -195,10 +195,10 @@
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes1 (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :main-root) ; parent-id
(:id page) ; page-id
0 ; to-index
#{(thi/id :free-shape)}) ; ids
@@ -292,10 +292,10 @@
main-child1 (ths/get-shape file :main-child1)
;; ==== Action
changes1 (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes1 (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :main-root) ; parent-id
(:id page) ; page-id
2 ; to-index
#{(:id main-child1)}) ; ids

View File

@@ -112,10 +112,10 @@
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
0 ; to-index
#{(thi/id :free-shape)}) ; ids
@@ -187,10 +187,10 @@
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
2 ; to-index
#{(:id copy-child1)}) ; ids

View File

@@ -29,10 +29,10 @@
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id frame-parent) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id frame-to-move)}) ;; ids
@@ -61,10 +61,10 @@
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
uuid/zero ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id circle)}) ;; ids

View File

@@ -22,7 +22,9 @@
(ctob/add-theme (ctob/make-token-theme :name "theme"
:sets #{"foo/bar"}))
(ctob/set-active-themes #{"/theme"})))
changes (clt/generate-toggle-token-set (pcb/empty-changes) (tht/get-tokens-lib file) "foo/bar")
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-token-set (tht/get-tokens-lib file) "foo/bar"))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
@@ -32,7 +34,6 @@
(t/is (= #{} (:sets (ctob/get-hidden-theme redo-lib))))
;; Undo
(t/is (nil? (ctob/get-hidden-theme undo-lib)))
(t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib)))))
(t/testing "toggling an inactive set will switch to hidden theme without user sets"
@@ -41,7 +42,9 @@
(ctob/add-theme (ctob/make-token-theme :name "theme"
:sets #{"foo/bar"}))
(ctob/set-active-themes #{"/theme"})))
changes (clt/generate-toggle-token-set (pcb/empty-changes) (tht/get-tokens-lib file) "foo/bar")
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-token-set (tht/get-tokens-lib file) "foo/bar"))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
@@ -51,7 +54,6 @@
(t/is (= #{} (:sets (ctob/get-hidden-theme redo-lib))))
;; Undo
(t/is (nil? (ctob/get-hidden-theme undo-lib)))
(t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib)))))
(t/testing "toggling an set with hidden theme already active will toggle set in hidden theme"
@@ -60,7 +62,9 @@
(ctob/add-theme (ctob/make-hidden-token-theme))
(ctob/set-active-themes #{ctob/hidden-token-theme-path})))
changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file) ["foo"])
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-token-set-group (tht/get-tokens-lib file) ["foo"]))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
@@ -68,10 +72,210 @@
undo-lib (tht/get-tokens-lib undo)]
(t/is (= (ctob/get-active-theme-paths redo-lib) (ctob/get-active-theme-paths undo-lib)))
(t/is (= #{"foo/bar"} (:sets (ctob/get-hidden-theme redo-lib))))
(t/is (= #{"foo/bar"} (:sets (ctob/get-hidden-theme redo-lib)))))))
(t/deftest set-token-theme-test
(t/testing "delete token theme"
(let [theme-name "foo"
group "main"
file (setup-file #(-> %
(ctob/add-theme (ctob/make-token-theme :name theme-name
:group group))))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-theme group theme-name nil))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
undo-lib (tht/get-tokens-lib undo)]
;; Redo
(t/is (nil? (ctob/get-theme redo-lib group theme-name)))
;; Undo
(t/is (some? (ctob/get-theme undo-lib group theme-name)))))
(t/testing "add token theme"
(let [theme-name "foo"
group "main"
theme (ctob/make-token-theme :name theme-name
:group group)
file (setup-file identity)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-theme group theme-name theme))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
undo-lib (tht/get-tokens-lib undo)]
;; Redo
(t/is (some? (ctob/get-theme redo-lib group theme-name)))
;; Undo
(t/is (nil? (ctob/get-theme undo-lib group theme-name)))))
(t/testing "update token theme"
(let [theme-name "foo"
group "main"
prev-theme (ctob/make-token-theme :name theme-name
:group group)
file (setup-file #(ctob/add-theme % prev-theme))
new-theme-name "foo1"
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-theme group new-theme-name prev-theme))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
undo-lib (tht/get-tokens-lib undo)]
;; Redo
(t/is (some? (ctob/get-theme redo-lib group theme-name)))
(t/is (nil? (ctob/get-theme redo-lib group new-theme-name)))
;; Undo
(t/is (some? (ctob/get-theme undo-lib group theme-name)))
(t/is (nil? (ctob/get-theme undo-lib group new-theme-name)))))
(t/testing "toggling token theme updates using changes history"
(let [theme-name "foo-theme"
group "main"
set-name "bar-set"
token-set (ctob/make-token-set :name set-name)
theme (ctob/make-token-theme :name theme-name
:group group)
file (setup-file #(-> %
(ctob/add-theme theme)
(ctob/add-set token-set)))
theme' (assoc theme :sets #{set-name})
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-theme group theme-name theme'))
changed-file (-> file
(thf/apply-changes changes)
(thf/apply-undo-changes changes)
(thf/apply-changes changes))
changed-lib (tht/get-tokens-lib changed-file)]
(t/is (= #{set-name}
(-> changed-lib (ctob/get-theme group theme-name) :sets))))))
(t/deftest set-token-test
(t/testing "delete token"
(let [set-name "foo"
token-name "to.delete.color.red"
file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name set-name))
(ctob/add-token-in-set set-name (ctob/make-token {:name token-name
:value "red"
:type :color}))))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token set-name token-name nil))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
undo-lib (tht/get-tokens-lib undo)]
(t/is (nil? (ctob/get-token-in-set redo-lib set-name token-name)))
;; Undo
(t/is (some? (ctob/get-token-in-set undo-lib set-name token-name)))))
(t/testing "add token"
(let [set-name "foo"
token (ctob/make-token {:name "to.add.color.red"
:value "red"
:type :color})
file (setup-file #(-> % (ctob/add-set (ctob/make-token-set :name set-name))))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token set-name (:name token) token))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
undo-lib (tht/get-tokens-lib undo)]
(t/is (= token (ctob/get-token-in-set redo-lib set-name (:name token))))
;; Undo
(t/is (nil? (ctob/get-token-in-set undo-lib set-name (:name token))))))
(t/testing "update token"
(let [set-name "foo"
prev-token (ctob/make-token {:name "to.update.color.red"
:value "red"
:type :color})
token (-> prev-token
(assoc :name "color.red.changed")
(assoc :value "blue"))
file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name set-name))
(ctob/add-token-in-set set-name prev-token)))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token set-name (:name prev-token) token))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
undo-lib (tht/get-tokens-lib undo)]
(t/is (tht/token-data-eq? token (ctob/get-token-in-set redo-lib set-name (:name token))))
(t/is (nil? (ctob/get-token-in-set redo-lib set-name (:name prev-token))))
;; Undo
(t/is (tht/token-data-eq? prev-token (ctob/get-token-in-set undo-lib set-name (:name prev-token))))
(t/is (nil? (ctob/get-token-in-set undo-lib set-name (:name token)))))))
(t/deftest set-token-set-test
(t/testing "delete token set"
(let [set-name "foo"
file (setup-file #(ctob/add-set % (ctob/make-token-set :name set-name)))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-set set-name false nil))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
undo-lib (tht/get-tokens-lib undo)]
(t/is (not (ctob/set-path-exists? redo-lib [set-name])))
;; Undo
(t/is (ctob/set-path-exists? undo-lib [set-name]))))
(t/testing "add token set"
(let [set-name "foo"
token-set (ctob/make-token-set :name set-name)
file (setup-file identity)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-set set-name false token-set))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
undo-lib (tht/get-tokens-lib undo)]
(t/is (not (ctob/set-path-exists? undo-lib [set-name])))
;; Undo
(t/is (ctob/set-path-exists? redo-lib [set-name]))))
(t/testing "update token set"
(let [set-name "foo"
token-name "bar"
token (ctob/make-token {:name token-name
:value "red"
:type :color})
file (setup-file #(-> (ctob/add-set % (ctob/make-token-set :name set-name))
(ctob/add-token-in-set set-name token)))
prev-token-set (-> file tht/get-tokens-lib :sets first)
new-set-name "foo1"
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-set set-name false (assoc prev-token-set
:name new-set-name)))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
undo-lib (tht/get-tokens-lib undo)]
;; Undo
(t/is (some? (ctob/get-hidden-theme undo-lib))))))
(t/is (some? (ctob/get-token-in-set undo-lib set-name token-name)))
(t/is (nil? (ctob/get-token-in-set undo-lib new-set-name token-name)))
;; Redo
(t/is (nil? (ctob/get-token-in-set redo-lib set-name token-name)))
(t/is (some? (ctob/get-token-in-set redo-lib new-set-name token-name))))))
(t/deftest generate-toggle-token-set-group-test
(t/testing "toggling set group with no active sets inside will activate all child sets"
@@ -81,7 +285,9 @@
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child"))
(ctob/add-theme (ctob/make-token-theme :name "theme"))
(ctob/set-active-themes #{"/theme"})))
changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file) ["foo" "bar"])
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-token-set-group (tht/get-tokens-lib file) ["foo" "bar"]))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
@@ -91,7 +297,6 @@
(t/is (= #{"foo/bar/baz" "foo/bar/baz/baz-child"} (:sets (ctob/get-hidden-theme redo-lib))))
;; Undo
(t/is (nil? (ctob/get-hidden-theme undo-lib)))
(t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib)))))
(t/testing "toggling set group with partially active sets inside will deactivate all child sets"
@@ -103,7 +308,9 @@
:sets #{"foo/bar/baz"}))
(ctob/set-active-themes #{"/theme"})))
changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file) ["foo" "bar"])
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-token-set-group (tht/get-tokens-lib file) ["foo" "bar"]))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
@@ -113,7 +320,6 @@
(t/is (= #{ctob/hidden-token-theme-path} (ctob/get-active-theme-paths redo-lib)))
;; Undo
(t/is (nil? (ctob/get-hidden-theme undo-lib)))
(t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib))))))
(t/deftest generate-move-token-set-test

View File

@@ -0,0 +1,194 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.logic.variants-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.variants :as clv]
[app.common.test-helpers.components :as thc]
[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.variants :as thv]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(t/deftest test-update-property-name
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-update-property-name v-id 0 "NewName1")
(clv/generate-update-property-name v-id 1 "NewName2"))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (-> comp01' :variant-properties first :name) "NewName1"))
(t/is (= (-> comp01' :variant-properties last :name) "NewName2"))
(t/is (= (-> comp02' :variant-properties first :name) "NewName1"))
(t/is (= (-> comp02' :variant-properties last :name) "NewName2"))))
(t/deftest test-add-new-property-without-values
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-add-new-property v-id))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 1))
(t/is (= (count (:variant-properties comp01')) 2))
(t/is (= (count (:variant-properties comp02)) 1))
(t/is (= (count (:variant-properties comp02')) 2))
(t/is (= (-> comp01' :variant-properties last :value) ""))))
(t/deftest test-add-new-property-with-values
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-add-new-property v-id {:fill-values? true}))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 1))
(t/is (= (count (:variant-properties comp01')) 2))
(t/is (= (count (:variant-properties comp02)) 1))
(t/is (= (count (:variant-properties comp02')) 2))
(t/is (= (-> comp01' :variant-properties last :value) "Value1"))))
(t/deftest test-remove-property
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-add-new-property v-id))
file (thf/apply-changes file changes)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-remove-property v-id 0))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 2))
(t/is (= (count (:variant-properties comp01')) 1))
(t/is (= (count (:variant-properties comp02)) 2))
(t/is (= (count (:variant-properties comp02')) 1))
(t/is (= (-> comp01' :variant-properties first :name) "Property2"))))
(t/deftest test-update-property-value
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-update-property-value (:id comp01) 0 "NewValue1")
(clv/generate-update-property-value (:id comp02) 0 "NewValue2"))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (-> comp01' :variant-properties first :value) "NewValue1"))
(t/is (= (-> comp02' :variant-properties first :value) "NewValue2"))))

View File

@@ -0,0 +1,6 @@
{"color":
{"red":
{"100":
{"value":"red",
"type":"color",
"description":""}}}}

View File

@@ -0,0 +1,6 @@
{"color":
{"red":
{"100":
{"$value":"red",
"$type":"color",
"$description":""}}}}

View File

@@ -0,0 +1,11 @@
{
"dark":
{"small":
{"$value": "8",
"$type":"borderRadius",
"$description":""}},
"$themes": [],
"$metadata":
{"tokenSetOrder": ["dark"],
"activeThemes": [],
"activeSets": ["dark"]}}

View File

@@ -805,6 +805,8 @@
"selectedTokenSets": {"light": "enabled"}
} ],
"$metadata": {
"tokenSetOrder": ["core", "light", "dark", "theme"]
"tokenSetOrder": ["core", "light", "dark", "theme"],
"activeSets": {},
"activeThemes": {}
}
}

View File

@@ -0,0 +1,810 @@
{
"core": {
"dimension": {
"scale": {
"value": "2",
"type": "dimension"
},
"xs": {
"value": "4",
"type": "dimension"
},
"sm": {
"value": "{dimension.xs} * {dimension.scale}",
"type": "dimension"
},
"md": {
"value": "{dimension.sm} * {dimension.scale}",
"type": "dimension"
},
"lg": {
"value": "{dimension.md} * {dimension.scale}",
"type": "dimension"
},
"xl": {
"value": "{dimension.lg} * {dimension.scale}",
"type": "dimension"
}
},
"spacing": {
"xs": {
"value": "{dimension.xs}",
"type": "spacing"
},
"sm": {
"value": "{dimension.sm}",
"type": "spacing"
},
"md": {
"value": "{dimension.md}",
"type": "spacing"
},
"lg": {
"value": "{dimension.lg}",
"type": "spacing"
},
"xl": {
"value": "{dimension.xl}",
"type": "spacing"
},
"multi-value": {
"value": "{dimension.sm} {dimension.xl}",
"type": "spacing",
"$description": "You can have multiple values in a single spacing token"
}
},
"borderRadius": {
"sm": {
"value": "4",
"type": "borderRadius"
},
"lg": {
"value": "8",
"type": "borderRadius"
},
"xl": {
"value": "16",
"type": "borderRadius"
},
"multi-value": {
"value": "{borderRadius.sm} {borderRadius.lg}",
"type": "borderRadius",
"$description": "You can have multiple values in a single radius token. Read more on these: https://docs.tokens.studio/available-tokens/border-radius-tokens#single--multiple-values"
}
},
"colors": {
"black": {
"value": "#000000",
"type": "color"
},
"white": {
"value": "#ffffff",
"type": "color"
},
"gray": {
"100": {
"value": "#f7fafc",
"type": "color"
},
"200": {
"value": "#edf2f7",
"type": "color"
},
"300": {
"value": "#e2e8f0",
"type": "color"
},
"400": {
"value": "#cbd5e0",
"type": "color"
},
"500": {
"value": "#a0aec0",
"type": "color"
},
"600": {
"value": "#718096",
"type": "color"
},
"700": {
"value": "#4a5568",
"type": "color"
},
"800": {
"value": "#2d3748",
"type": "color"
},
"900": {
"value": "#1a202c",
"type": "color"
}
},
"red": {
"100": {
"value": "#fff5f5",
"type": "color"
},
"200": {
"value": "#fed7d7",
"type": "color"
},
"300": {
"value": "#feb2b2",
"type": "color"
},
"400": {
"value": "#fc8181",
"type": "color"
},
"500": {
"value": "#f56565",
"type": "color"
},
"600": {
"value": "#e53e3e",
"type": "color"
},
"700": {
"value": "#c53030",
"type": "color"
},
"800": {
"value": "#9b2c2c",
"type": "color"
},
"900": {
"value": "#742a2a",
"type": "color"
}
},
"orange": {
"100": {
"value": "#fffaf0",
"type": "color"
},
"200": {
"value": "#feebc8",
"type": "color"
},
"300": {
"value": "#fbd38d",
"type": "color"
},
"400": {
"value": "#f6ad55",
"type": "color"
},
"500": {
"value": "#ed8936",
"type": "color"
},
"600": {
"value": "#dd6b20",
"type": "color"
},
"700": {
"value": "#c05621",
"type": "color"
},
"800": {
"value": "#9c4221",
"type": "color"
},
"900": {
"value": "#7b341e",
"type": "color"
}
},
"yellow": {
"100": {
"value": "#fffff0",
"type": "color"
},
"200": {
"value": "#fefcbf",
"type": "color"
},
"300": {
"value": "#faf089",
"type": "color"
},
"400": {
"value": "#f6e05e",
"type": "color"
},
"500": {
"value": "#ecc94b",
"type": "color"
},
"600": {
"value": "#d69e2e",
"type": "color"
},
"700": {
"value": "#b7791f",
"type": "color"
},
"800": {
"value": "#975a16",
"type": "color"
},
"900": {
"value": "#744210",
"type": "color"
}
},
"green": {
"100": {
"value": "#f0fff4",
"type": "color"
},
"200": {
"value": "#c6f6d5",
"type": "color"
},
"300": {
"value": "#9ae6b4",
"type": "color"
},
"400": {
"value": "#68d391",
"type": "color"
},
"500": {
"value": "#48bb78",
"type": "color"
},
"600": {
"value": "#38a169",
"type": "color"
},
"700": {
"value": "#2f855a",
"type": "color"
},
"800": {
"value": "#276749",
"type": "color"
},
"900": {
"value": "#22543d",
"type": "color"
}
},
"teal": {
"100": {
"value": "#e6fffa",
"type": "color"
},
"200": {
"value": "#b2f5ea",
"type": "color"
},
"300": {
"value": "#81e6d9",
"type": "color"
},
"400": {
"value": "#4fd1c5",
"type": "color"
},
"500": {
"value": "#38b2ac",
"type": "color"
},
"600": {
"value": "#319795",
"type": "color"
},
"700": {
"value": "#2c7a7b",
"type": "color"
},
"800": {
"value": "#285e61",
"type": "color"
},
"900": {
"value": "#234e52",
"type": "color"
}
},
"blue": {
"100": {
"value": "#ebf8ff",
"type": "color"
},
"200": {
"value": "#bee3f8",
"type": "color"
},
"300": {
"value": "#90cdf4",
"type": "color"
},
"400": {
"value": "#63b3ed",
"type": "color"
},
"500": {
"value": "#4299e1",
"type": "color"
},
"600": {
"value": "#3182ce",
"type": "color"
},
"700": {
"value": "#2b6cb0",
"type": "color"
},
"800": {
"value": "#2c5282",
"type": "color"
},
"900": {
"value": "#2a4365",
"type": "color"
}
},
"indigo": {
"100": {
"value": "#ebf4ff",
"type": "color"
},
"200": {
"value": "#c3dafe",
"type": "color"
},
"300": {
"value": "#a3bffa",
"type": "color"
},
"400": {
"value": "#7f9cf5",
"type": "color"
},
"500": {
"value": "#667eea",
"type": "color"
},
"600": {
"value": "#5a67d8",
"type": "color"
},
"700": {
"value": "#4c51bf",
"type": "color"
},
"800": {
"value": "#434190",
"type": "color"
},
"900": {
"value": "#3c366b",
"type": "color"
}
},
"purple": {
"100": {
"value": "#faf5ff",
"type": "color"
},
"200": {
"value": "#e9d8fd",
"type": "color"
},
"300": {
"value": "#d6bcfa",
"type": "color"
},
"400": {
"value": "#b794f4",
"type": "color"
},
"500": {
"value": "#9f7aea",
"type": "color"
},
"600": {
"value": "#805ad5",
"type": "color"
},
"700": {
"value": "#6b46c1",
"type": "color"
},
"800": {
"value": "#553c9a",
"type": "color"
},
"900": {
"value": "#44337a",
"type": "color"
}
},
"pink": {
"100": {
"value": "#fff5f7",
"type": "color"
},
"200": {
"value": "#fed7e2",
"type": "color"
},
"300": {
"value": "#fbb6ce",
"type": "color"
},
"400": {
"value": "#f687b3",
"type": "color"
},
"500": {
"value": "#ed64a6",
"type": "color"
},
"600": {
"value": "#d53f8c",
"type": "color"
},
"700": {
"value": "#b83280",
"type": "color"
},
"800": {
"value": "#97266d",
"type": "color"
},
"900": {
"value": "#702459",
"type": "color"
}
}
},
"opacity": {
"low": {
"value": "10%",
"type": "opacity"
},
"md": {
"value": "50%",
"type": "opacity"
},
"high": {
"value": "90%",
"type": "opacity"
}
},
"fontFamilies": {
"heading": {
"value": "Inter",
"type": "fontFamilies"
},
"body": {
"value": "Roboto",
"type": "fontFamilies"
}
},
"lineHeights": {
"heading": {
"value": "110%",
"type": "lineHeights"
},
"body": {
"value": "140%",
"type": "lineHeights"
}
},
"letterSpacing": {
"default": {
"value": "0",
"type": "letterSpacing"
},
"increased": {
"value": "150%",
"type": "letterSpacing"
},
"decreased": {
"value": "-5%",
"type": "letterSpacing"
}
},
"paragraphSpacing": {
"h1": {
"value": "32",
"type": "paragraphSpacing"
},
"h2": {
"value": "26",
"type": "paragraphSpacing"
}
},
"fontWeights": {
"headingRegular": {
"value": "Regular",
"type": "fontWeights"
},
"headingBold": {
"value": "Bold",
"type": "fontWeights"
},
"bodyRegular": {
"value": "Regular",
"type": "fontWeights"
},
"bodyBold": {
"value": "Bold",
"type": "fontWeights"
}
},
"fontSizes": {
"h1": {
"value": "{fontSizes.h2} * 1.25",
"type": "fontSizes"
},
"h2": {
"value": "{fontSizes.h3} * 1.25",
"type": "fontSizes"
},
"h3": {
"value": "{fontSizes.h4} * 1.25",
"type": "fontSizes"
},
"h4": {
"value": "{fontSizes.h5} * 1.25",
"type": "fontSizes"
},
"h5": {
"value": "{fontSizes.h6} * 1.25",
"type": "fontSizes"
},
"h6": {
"value": "{fontSizes.body} * 1",
"type": "fontSizes"
},
"body": {
"value": "16",
"type": "fontSizes"
},
"sm": {
"value": "{fontSizes.body} * 0.85",
"type": "fontSizes"
},
"xs": {
"value": "{fontSizes.body} * 0.65",
"type": "fontSizes"
}
}
},
"light": {
"fg": {
"default": {
"value": "{colors.black}",
"type": "color"
},
"muted": {
"value": "{colors.gray.700}",
"type": "color"
},
"subtle": {
"value": "{colors.gray.500}",
"type": "color"
}
},
"bg": {
"default": {
"value": "{colors.white}",
"type": "color"
},
"muted": {
"value": "{colors.gray.100}",
"type": "color"
},
"subtle": {
"value": "{colors.gray.200}",
"type": "color"
}
},
"accent": {
"default": {
"value": "{colors.indigo.400}",
"type": "color"
},
"onAccent": {
"value": "{colors.white}",
"type": "color"
},
"bg": {
"value": "{colors.indigo.200}",
"type": "color"
}
},
"shadows": {
"default": {
"value": "{colors.gray.900}",
"type": "color"
}
}
},
"dark": {
"fg": {
"default": {
"value": "{colors.white}",
"type": "color"
},
"muted": {
"value": "{colors.gray.300}",
"type": "color"
},
"subtle": {
"value": "{colors.gray.500}",
"type": "color"
}
},
"bg": {
"default": {
"value": "{colors.gray.900}",
"type": "color"
},
"muted": {
"value": "{colors.gray.700}",
"type": "color"
},
"subtle": {
"value": "{colors.gray.600}",
"type": "color"
}
},
"accent": {
"default": {
"value": "{colors.indigo.600}",
"type": "color"
},
"onAccent": {
"value": "{colors.white}",
"type": "color"
},
"bg": {
"value": "{colors.indigo.800}",
"type": "color"
}
},
"shadows": {
"default": {
"value": "rgba({colors.black}, 0.3)",
"type": "color"
}
}
},
"theme": {
"button": {
"primary": {
"background": {
"value": "{accent.default}",
"type": "color"
},
"text": {
"value": "{accent.onAccent}",
"type": "color"
}
},
"borderRadius": {
"value": "{borderRadius.lg}",
"type": "borderRadius"
},
"borderWidth": {
"value": "{dimension.sm}",
"type": "borderWidth"
}
},
"card": {
"borderRadius": {
"value": "{borderRadius.lg}",
"type": "borderRadius"
},
"background": {
"value": "{bg.default}",
"type": "color"
},
"padding": {
"value": "{dimension.md}",
"type": "dimension"
}
},
"boxShadow": {
"default": {
"value": [
{
"x": 5,
"y": 5,
"spread": 3,
"color": "rgba({shadows.default}, 0.15)",
"blur": 5,
"type": "dropShadow"
},
{
"x": 4,
"y": 4,
"spread": 6,
"color": "#00000033",
"blur": 5,
"type": "innerShadow"
}
],
"type": "boxShadow"
}
},
"typography": {
"H1": {
"Bold": {
"value": {
"fontFamily": "{fontFamilies.heading}",
"fontWeight": "{fontWeights.headingBold}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.h1}",
"paragraphSpacing": "{paragraphSpacing.h1}",
"letterSpacing": "{letterSpacing.decreased}"
},
"type": "typography"
},
"Regular": {
"value": {
"fontFamily": "{fontFamilies.heading}",
"fontWeight": "{fontWeights.headingRegular}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.h1}",
"paragraphSpacing": "{paragraphSpacing.h1}",
"letterSpacing": "{letterSpacing.decreased}"
},
"type": "typography"
}
},
"H2": {
"Bold": {
"value": {
"fontFamily": "{fontFamilies.heading}",
"fontWeight": "{fontWeights.headingBold}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.h2}",
"paragraphSpacing": "{paragraphSpacing.h2}",
"letterSpacing": "{letterSpacing.decreased}"
},
"type": "typography"
},
"Regular": {
"value": {
"fontFamily": "{fontFamilies.heading}",
"fontWeight": "{fontWeights.headingRegular}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.h2}",
"paragraphSpacing": "{paragraphSpacing.h2}",
"letterSpacing": "{letterSpacing.decreased}"
},
"type": "typography"
}
},
"Body": {
"value": {
"fontFamily": "{fontFamilies.body}",
"fontWeight": "{fontWeights.bodyRegular}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.body}",
"paragraphSpacing": "{paragraphSpacing.h2}"
},
"type": "typography"
}
}
},
"$themes": [ {
"name": "theme-1",
"group": "group-1",
"description": null,
"is-source": false,
"modified-at": "2024-01-01T00:00:00.000+00:00",
"selectedTokenSets": {"light": "enabled"}
} ],
"$metadata": {
"tokenSetOrder": ["core", "light", "dark", "theme"]
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ ENV NODE_VERSION=v22.13.1 \
BABASHKA_VERSION=1.12.196 \
CLJFMT_VERSION=0.13.0 \
RUSTUP_VERSION=1.27.1 \
RUST_VERSION=1.82.0 \
RUST_VERSION=1.85.0 \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8
@@ -264,7 +264,8 @@ RUN set -eux; \
./rustup-init -y --no-modify-path --profile minimal --default-toolchain $RUST_VERSION --default-host ${rustArch}; \
rm rustup-init; \
chmod -R a+w $RUSTUP_HOME $CARGO_HOME; \
rustup component add rustfmt;
rustup component add rustfmt; \
rustup component add clippy;
WORKDIR /usr/local

View File

@@ -156,10 +156,13 @@ http {
}
location / {
location ~ ^/github/penpot-files/(?<template_file>[a-zA-Z0-9\-\_\.]+) {
proxy_pass https://raw.githubusercontent.com/penpot/penpot-files/main/$template_file;
location ~ ^/github/penpot-files/(.+)$ {
rewrite ^/github/penpot-files/(.+) /penpot/penpot-files/refs/heads/main/$1 break;
proxy_pass https://raw.githubusercontent.com;
proxy_hide_header Access-Control-Allow-Origin;
proxy_set_header User-Agent "curl/7.74.0";
proxy_hide_header Cookies;
proxy_set_header User-Agent "curl/8.5.0";
proxy_set_header Host "raw.githubusercontent.com";
proxy_set_header Accept "*/*";
add_header Access-Control-Allow-Origin $http_origin;

View File

@@ -8,12 +8,12 @@ source ~/.bashrc
echo "[start-tmux.sh] Installing node dependencies"
pushd ~/penpot/frontend/
corepack up;
corepack install;
yarn install;
yarn run playwright install --with-deps chromium
popd
pushd ~/penpot/exporter/
corepack up;
corepack install;
yarn install
yarn run playwright install --with-deps chromium
popd

View File

@@ -52,7 +52,7 @@ services:
## penpot to the internet, or a different host than `localhost`.
# traefik:
# image: traefik:v2.9
# image: traefik:v3.3
# networks:
# - penpot
# command:
@@ -87,29 +87,16 @@ services:
networks:
- penpot
labels:
- "traefik.enable=true"
# labels:
# - "traefik.enable=true"
## HTTP: example of labels for the case where penpot will be exposed to the
## internet with only HTTP (without HTTPS) using traefik.
# ## HTTPS: example of labels for the case where penpot will be exposed to the
# ## internet with HTTPS using traefik.
# - "traefik.http.routers.penpot-http.entrypoints=web"
# - "traefik.http.routers.penpot-http.rule=Host(`<DOMAIN_NAME>`)"
# - "traefik.http.services.penpot-http.loadbalancer.server.port=80"
## HTTPS: example of labels for the case where penpot will be exposed to the
## internet with HTTPS using traefik.
# - "traefik.http.middlewares.http-redirect.redirectscheme.scheme=https"
# - "traefik.http.middlewares.http-redirect.redirectscheme.permanent=true"
# - "traefik.http.routers.penpot-http.entrypoints=web"
# - "traefik.http.routers.penpot-http.rule=Host(`<DOMAIN_NAME>`)"
# - "traefik.http.routers.penpot-http.middlewares=http-redirect"
# - "traefik.http.routers.penpot-https.entrypoints=websecure"
# - "traefik.http.routers.penpot-https.rule=Host(`<DOMAIN_NAME>`)"
# - "traefik.http.services.penpot-https.loadbalancer.server.port=80"
# - "traefik.http.routers.penpot-https.tls=true"
# - "traefik.http.routers.penpot-https.entrypoints=websecure"
# - "traefik.http.routers.penpot-https.tls.certresolver=letsencrypt"
# - "traefik.http.routers.penpot-https.tls=true"
environment:
<< : [*penpot-flags, *penpot-http-body-size]
@@ -130,8 +117,7 @@ services:
networks:
- penpot
## Configuration envronment variables for the backend
## container.
## Configuration envronment variables for the backend container.
environment:
<< : [*penpot-flags, *penpot-public-uri, *penpot-http-body-size]

View File

@@ -135,10 +135,13 @@ http {
}
location / {
location ~ ^/github/penpot-files/(?<template_file>[a-zA-Z0-9\-\_\.]+) {
proxy_pass https://raw.githubusercontent.com/penpot/penpot-files/main/$template_file;
location ~ ^/github/penpot-files/(.+)$ {
rewrite ^/github/penpot-files/(.+) /penpot/penpot-files/refs/heads/main/$1 break;
proxy_pass https://raw.githubusercontent.com;
proxy_hide_header Access-Control-Allow-Origin;
proxy_set_header User-Agent "curl/7.74.0";
proxy_hide_header Cookies;
proxy_set_header User-Agent "curl/8.5.0";
proxy_set_header Host "raw.githubusercontent.com";
proxy_set_header Accept "*/*";
add_header Access-Control-Allow-Origin $http_origin;

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