mirror of
https://github.com/penpot/penpot.git
synced 2025-12-24 06:58:34 -05:00
Compare commits
423 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99fb905070 | ||
|
|
faa68784af | ||
|
|
0748ef7267 | ||
|
|
9ca4fa752c | ||
|
|
ff9c8f5929 | ||
|
|
e4c563f917 | ||
|
|
2d3ad5a88f | ||
|
|
1334d733cd | ||
|
|
004a9f17d3 | ||
|
|
c87fa4f723 | ||
|
|
9378a5786f | ||
|
|
3224ba26f1 | ||
|
|
d33a5e6df1 | ||
|
|
b6be416c7b | ||
|
|
aaa57cb17f | ||
|
|
0b289153cb | ||
|
|
cf274099c4 | ||
|
|
6524e75770 | ||
|
|
9b80f7c9b3 | ||
|
|
bf76f328c8 | ||
|
|
ba25ce3098 | ||
|
|
f5f1316f0b | ||
|
|
79a164be6d | ||
|
|
ecb85778bc | ||
|
|
2cdc241e68 | ||
|
|
057bf9bf25 | ||
|
|
2ddcd0ce15 | ||
|
|
ca2891d441 | ||
|
|
37abb7b237 | ||
|
|
5fc2208c16 | ||
|
|
86bcd1b681 | ||
|
|
70b1989f10 | ||
|
|
e6b5618bd3 | ||
|
|
5199b306aa | ||
|
|
8febfaa21e | ||
|
|
5e675dbf0b | ||
|
|
fc5d9659d6 | ||
|
|
bc20598b3d | ||
|
|
9de8ebb52c | ||
|
|
9be569c54c | ||
|
|
7e6a621484 | ||
|
|
66b47f9444 | ||
|
|
71aa8e5a86 | ||
|
|
e203536506 | ||
|
|
3fe16bd8f9 | ||
|
|
a9725a1aac | ||
|
|
c3e76817cd | ||
|
|
36b78e5e21 | ||
|
|
a2c9d307df | ||
|
|
e52fd90963 | ||
|
|
d8b3b216e9 | ||
|
|
c2b13a6d5d | ||
|
|
a60b3d4b08 | ||
|
|
b14798b405 | ||
|
|
8382a88efe | ||
|
|
53057c4bf2 | ||
|
|
3e0f38e8c3 | ||
|
|
a5bbe765b9 | ||
|
|
4455adc813 | ||
|
|
abca763aa5 | ||
|
|
62a12a64a3 | ||
|
|
6935d54870 | ||
|
|
049427c6ca | ||
|
|
65e8526ee2 | ||
|
|
8ce71e792e | ||
|
|
202762027f | ||
|
|
d95551e651 | ||
|
|
44d68ad723 | ||
|
|
9e4c9d3101 | ||
|
|
050692952e | ||
|
|
c96fbfdcd6 | ||
|
|
ab90d9d01c | ||
|
|
281c0068d9 | ||
|
|
e7b74939cb | ||
|
|
c2ae58bf08 | ||
|
|
14e8026e30 | ||
|
|
eb29a42209 | ||
|
|
6fdaad1bf4 | ||
|
|
dfa6c502dc | ||
|
|
b958dcb891 | ||
|
|
6e5d64d403 | ||
|
|
3e0c2bf1a1 | ||
|
|
9c4896d72b | ||
|
|
283cdee5d6 | ||
|
|
ab5e01e54a | ||
|
|
01fec1a0cf | ||
|
|
caf13eb774 | ||
|
|
373248e304 | ||
|
|
fef342b489 | ||
|
|
6e9adece1f | ||
|
|
80308ceafa | ||
|
|
f65518f865 | ||
|
|
c0315e2c30 | ||
|
|
2f20ccf289 | ||
|
|
1a7d60bb88 | ||
|
|
90b1895f19 | ||
|
|
7945a95522 | ||
|
|
40fe6369cb | ||
|
|
f155042958 | ||
|
|
1dd23a3f47 | ||
|
|
55da3ee275 | ||
|
|
1194e40222 | ||
|
|
05fac41534 | ||
|
|
3f85e89f62 | ||
|
|
ee0f8ad19a | ||
|
|
b7d7cf233a | ||
|
|
38a708e12b | ||
|
|
53dcd94f0d | ||
|
|
a3ccc3dfef | ||
|
|
bd208c31e2 | ||
|
|
151dc352c8 | ||
|
|
77d8504baf | ||
|
|
ccbf17106d | ||
|
|
95c4d95fd3 | ||
|
|
484772e3b2 | ||
|
|
5c7a1fb407 | ||
|
|
064981ff3d | ||
|
|
fe003d7496 | ||
|
|
fae1df7f4b | ||
|
|
a72c07b657 | ||
|
|
0bff76e5f1 | ||
|
|
c7b062f483 | ||
|
|
83f72f3e41 | ||
|
|
d8b71d76dd | ||
|
|
a6d2f385af | ||
|
|
db9e397531 | ||
|
|
18dea6c3a3 | ||
|
|
8ebaecc472 | ||
|
|
708492afeb | ||
|
|
1305ab3cc6 | ||
|
|
29cc6b4f9c | ||
|
|
cc7f0b145c | ||
|
|
e69c0c3e27 | ||
|
|
a209966427 | ||
|
|
d5abbd4220 | ||
|
|
43a75b64b4 | ||
|
|
70a23a14c4 | ||
|
|
93c81ea49c | ||
|
|
3a741d1c14 | ||
|
|
ba442e1549 | ||
|
|
8c15296d07 | ||
|
|
d26464c810 | ||
|
|
8ee202e5a1 | ||
|
|
689cddfd0c | ||
|
|
25950bb5a5 | ||
|
|
1da623e63f | ||
|
|
4bf9e24d43 | ||
|
|
b41a7b8547 | ||
|
|
f500a00d04 | ||
|
|
64a2a08d24 | ||
|
|
1f58f96e88 | ||
|
|
dc3d802d3d | ||
|
|
5765d1c56c | ||
|
|
abcd050c69 | ||
|
|
f40ef26c69 | ||
|
|
fccd1a5bd7 | ||
|
|
16012a3881 | ||
|
|
ddc41027ab | ||
|
|
4f931fbe6a | ||
|
|
7ada3692cf | ||
|
|
1ab5d5027f | ||
|
|
1f16816fe4 | ||
|
|
daf048e258 | ||
|
|
f3d13005b2 | ||
|
|
25a44e1387 | ||
|
|
9e9612cf1f | ||
|
|
304c44048f | ||
|
|
99e64ad387 | ||
|
|
0f0c45af8e | ||
|
|
f2977cf938 | ||
|
|
bb80da137d | ||
|
|
f4b16a255c | ||
|
|
b49a4734ff | ||
|
|
ec8c30f060 | ||
|
|
7990400c7a | ||
|
|
2aaa2f3033 | ||
|
|
202b9f3075 | ||
|
|
eee5cf5fb4 | ||
|
|
be0814cdac | ||
|
|
f5c699ab7a | ||
|
|
80d719353c | ||
|
|
fa3fc12594 | ||
|
|
422a9db07b | ||
|
|
a4145a30f5 | ||
|
|
dcf1a593f7 | ||
|
|
785b61be2f | ||
|
|
39c7782019 | ||
|
|
1c5c51907a | ||
|
|
e004671346 | ||
|
|
a59014cad0 | ||
|
|
38e5c161e7 | ||
|
|
87650de9bc | ||
|
|
ee5596067e | ||
|
|
870fec6bbd | ||
|
|
686ab14b43 | ||
|
|
fced0cf3b1 | ||
|
|
25dd53290c | ||
|
|
a7c1f7ba69 | ||
|
|
de8e27feb8 | ||
|
|
e7144142e5 | ||
|
|
b6f2a434cf | ||
|
|
caf558f8dd | ||
|
|
6f2f1b7a76 | ||
|
|
744ef1958b | ||
|
|
08b44e1857 | ||
|
|
580b60550c | ||
|
|
e9755d437e | ||
|
|
20f695e8d7 | ||
|
|
1d7ff1f9e4 | ||
|
|
5b18f1d76d | ||
|
|
d880307a9b | ||
|
|
e5db66351e | ||
|
|
89153eef23 | ||
|
|
97c24c8b9c | ||
|
|
43535ae573 | ||
|
|
b7a8677036 | ||
|
|
61643f676c | ||
|
|
9b8c8c4971 | ||
|
|
033ca0d56b | ||
|
|
28a6797595 | ||
|
|
9ff2160c77 | ||
|
|
953db56a0d | ||
|
|
82cf474863 | ||
|
|
4c77b32171 | ||
|
|
34141ce9af | ||
|
|
edfcac3d5c | ||
|
|
774e11c827 | ||
|
|
58c867885c | ||
|
|
ccb6e25914 | ||
|
|
965d2d4036 | ||
|
|
9f8d7c9e41 | ||
|
|
8d352c1f82 | ||
|
|
faead09174 | ||
|
|
43f77376b6 | ||
|
|
ae3ce1220b | ||
|
|
c0ba92f503 | ||
|
|
90cb0357c6 | ||
|
|
d55e55ebcc | ||
|
|
c2522329fd | ||
|
|
2470c1788e | ||
|
|
230d259551 | ||
|
|
cb533335c4 | ||
|
|
a8890e4b13 | ||
|
|
0281e0dba4 | ||
|
|
1c209f49fc | ||
|
|
28caa1d47d | ||
|
|
a4701866a4 | ||
|
|
12f72c8ca9 | ||
|
|
c1165bd12d | ||
|
|
215fb53c52 | ||
|
|
8df780b237 | ||
|
|
79679cbb16 | ||
|
|
fb2db4b918 | ||
|
|
05b66f1dcf | ||
|
|
0f1b2003be | ||
|
|
6f91da9461 | ||
|
|
63666fca48 | ||
|
|
d279b6c232 | ||
|
|
17f7f920c4 | ||
|
|
b55c86544b | ||
|
|
af1d5b86e1 | ||
|
|
137e8d042f | ||
|
|
4ceaedcbe8 | ||
|
|
f375cc9a82 | ||
|
|
5937ed57ce | ||
|
|
2e3ed0c23f | ||
|
|
5d1d2ef289 | ||
|
|
480c224250 | ||
|
|
cd731c3ad2 | ||
|
|
9bc49e3381 | ||
|
|
f961b75bba | ||
|
|
1e16fb8ca2 | ||
|
|
c332528185 | ||
|
|
387c5e67f3 | ||
|
|
2ed780e14d | ||
|
|
1b8714fe7f | ||
|
|
e28f8cae74 | ||
|
|
87ef98dad5 | ||
|
|
e6e71e9278 | ||
|
|
02220d02ed | ||
|
|
ff7b77bda7 | ||
|
|
f8ffae75c4 | ||
|
|
cb350b26a1 | ||
|
|
dccebb0bea | ||
|
|
4cefbb52e1 | ||
|
|
d757009b48 | ||
|
|
ca202711e1 | ||
|
|
f04229d8cb | ||
|
|
076d64df8f | ||
|
|
3d7479f9aa | ||
|
|
76ffc2d268 | ||
|
|
d0d118b31e | ||
|
|
895b5b2ee1 | ||
|
|
9fd0e9af66 | ||
|
|
648a8f9237 | ||
|
|
4e1ae1bc1a | ||
|
|
b6ac1dea4d | ||
|
|
219d9af885 | ||
|
|
c6bba54573 | ||
|
|
f53cae0faa | ||
|
|
6953a57333 | ||
|
|
a109f11926 | ||
|
|
45c9904e05 | ||
|
|
08fc32cdc6 | ||
|
|
6c10f1e364 | ||
|
|
e8549ffb79 | ||
|
|
8a8d89dfc0 | ||
|
|
b6c4376217 | ||
|
|
bd5e47f5fc | ||
|
|
2aa756af38 | ||
|
|
78c2840b22 | ||
|
|
af0a516a79 | ||
|
|
651beb4b9c | ||
|
|
f4d04a3dcb | ||
|
|
d573da55b0 | ||
|
|
3c4be537d9 | ||
|
|
9800331505 | ||
|
|
7728d5b317 | ||
|
|
8f47ed8b0a | ||
|
|
c137e682dc | ||
|
|
14c639a425 | ||
|
|
06bfb1ad26 | ||
|
|
33c3611345 | ||
|
|
e012046f62 | ||
|
|
ebf3730454 | ||
|
|
3cf823ffb3 | ||
|
|
6231a9f931 | ||
|
|
dd30e939ae | ||
|
|
6f2e1d3794 | ||
|
|
2e41bd7607 | ||
|
|
b9907ec401 | ||
|
|
416e9e8e1d | ||
|
|
83d41dba6f | ||
|
|
7284fb539f | ||
|
|
f932f3efb1 | ||
|
|
60bc88a075 | ||
|
|
6eb686c06b | ||
|
|
065b50f5a2 | ||
|
|
85b24e1e8d | ||
|
|
9653e72e47 | ||
|
|
a80f114d66 | ||
|
|
74ae4743d8 | ||
|
|
d1d30e7eb5 | ||
|
|
e83be01475 | ||
|
|
22efd6574d | ||
|
|
f8a2291a55 | ||
|
|
8c302e314f | ||
|
|
a830c27ceb | ||
|
|
4c12af957c | ||
|
|
9ea3c54b92 | ||
|
|
4620764111 | ||
|
|
ca86137d0f | ||
|
|
b299a732c0 | ||
|
|
7a4c9d9933 | ||
|
|
91d15ea221 | ||
|
|
b043fec0d5 | ||
|
|
0bab46eb5c | ||
|
|
82bff09373 | ||
|
|
329b2d30d0 | ||
|
|
0d65b652d4 | ||
|
|
9d3c19e86a | ||
|
|
56a7800519 | ||
|
|
ba0cebd713 | ||
|
|
e28628d148 | ||
|
|
40bc860dc6 | ||
|
|
decf32fdd5 | ||
|
|
d9d6ee9922 | ||
|
|
903609a38f | ||
|
|
4504903b4c | ||
|
|
4bf4972b6e | ||
|
|
47e4b41dd2 | ||
|
|
af413ff1c0 | ||
|
|
5fcf0808c6 | ||
|
|
fb956b3aa1 | ||
|
|
93986af181 | ||
|
|
37a8bf7bfc | ||
|
|
e60e36a0e2 | ||
|
|
199e182399 | ||
|
|
a9f4b29f32 | ||
|
|
22cd43b8a2 | ||
|
|
2d61644b05 | ||
|
|
084816fb9f | ||
|
|
b5ea90f740 | ||
|
|
e6839e4983 | ||
|
|
82d3e466be | ||
|
|
b0dacf6b11 | ||
|
|
64d090839d | ||
|
|
12d3994f45 | ||
|
|
e3b6b24c5f | ||
|
|
1eb7205c12 | ||
|
|
92f4bdae03 | ||
|
|
bd63a460eb | ||
|
|
02e975f594 | ||
|
|
fdeabc15ab | ||
|
|
8363cb7449 | ||
|
|
da43a4d3b4 | ||
|
|
69d21e20c9 | ||
|
|
a925d6710a | ||
|
|
f5218e207b | ||
|
|
7fb6a095c6 | ||
|
|
6a0bb2f452 | ||
|
|
d02f85315a | ||
|
|
fbd5c404d2 | ||
|
|
32b65e8dbc | ||
|
|
bf153eb96b | ||
|
|
f16fcf25e2 | ||
|
|
0048f9725d | ||
|
|
4bb5592e75 | ||
|
|
d7fbd3c3bc | ||
|
|
0b62bee90d | ||
|
|
a4a88769af | ||
|
|
2dce8d09b8 | ||
|
|
e6e6401702 | ||
|
|
e83ece392c | ||
|
|
bb161f9da8 | ||
|
|
07360efd17 | ||
|
|
7c8eaaa4f9 | ||
|
|
01e04843bf | ||
|
|
3907f39c29 | ||
|
|
c570c0929f | ||
|
|
f53473f9e9 | ||
|
|
39e6d28826 | ||
|
|
0b56d07a67 |
82
CHANGES.md
82
CHANGES.md
@@ -1,5 +1,86 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.7.0 (Unreleased)
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
- Design improvements to the Invitations page with an empty state [GitHub #2608](https://github.com/penpot/penpot/issues/2608) by [@iprithvitharun](https://github.com/iprithvitharun)
|
||||
|
||||
### :sparkles: New features
|
||||
|
||||
- Update board presets with a newer devices [Taiga #10610](https://tree.taiga.io/project/penpot/us/10610)
|
||||
- Propagate "sharing a prototype" to editors and viewers [Taiga #8853](https://tree.taiga.io/project/penpot/us/8853)
|
||||
- Design improvements to the Invitations page with an empty state [Taiga #4554](https://tree.taiga.io/project/penpot/us/4554)
|
||||
- Duplicate token sets [Taiga #10694](https://tree.taiga.io/project/penpot/issue/10694)
|
||||
- Add set selection in create Token themes flow [Taiga #10746](https://tree.taiga.io/project/penpot/issue/10746)
|
||||
- Display indicator on not active sets [Taiga #10668](https://tree.taiga.io/project/penpot/issue/10668)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix "at" icon to match all icons on app [Taiga #11136](https://tree.taiga.io/project/penpot/issue/11136)
|
||||
- Fix problem in viewer with the back button [Taiga #10907](https://tree.taiga.io/project/penpot/issue/10907)
|
||||
- Fix resize bar background on tokens panel [Taiga #10811](https://tree.taiga.io/project/penpot/issue/10811)
|
||||
- Fix shortcut for history version panel [Taiga #11006](https://tree.taiga.io/project/penpot/issue/11006)
|
||||
- Fix positioning of comment drafts when near the right / bottom edges of viewport [Taiga #10534](https://tree.taiga.io/project/penpot/issue/10534)
|
||||
- Fix path having a wrong selrect [Taiga #10257](https://tree.taiga.io/project/penpot/issue/10257)
|
||||
- Fix SVG `stroke-linecap` property when importing SVGs [Taiga #9489](https://tree.taiga.io/project/penpot/issue/9489)
|
||||
- Fix position problems cutting-pasting a component [Taiga #10677](https://tree.taiga.io/project/penpot/issue/10677)
|
||||
- Fix design tab has a horizontal scroll [Taiga #10660](https://tree.taiga.io/project/penpot/issue/10660)
|
||||
- Fix long file names being clipped when longer than allowed length [Taiga #10662](https://tree.taiga.io/project/penpot/issue/10662)
|
||||
- Fix problem with error detail in toast [Taiga #10519](https://tree.taiga.io/project/penpot/issue/10519)
|
||||
- Fix view mode error when an external user tries to export something from a prototype using a shared link [Taiga #10251](https://tree.taiga.io/project/penpot/issue/10251)
|
||||
- Fix merge path nodes with only one node selected [Taiga #9626](https://tree.taiga.io/project/penpot/issue/9626)
|
||||
- Fix problem with import errors [Taiga #10040](https://tree.taiga.io/project/penpot/issue/10040)
|
||||
- Fix color gradient on texts [Taiga Issue #7488](https://tree.taiga.io/project/penpot/issue/7488)
|
||||
- Add support for self mentions [Taiga #10809](https://tree.taiga.io/project/penpot/issue/10809)
|
||||
- Fix team info settings alignment [Taiga #10869](https://tree.taiga.io/project/penpot/issue/10869)
|
||||
- Fix left sidebar horizontal scroll on nested layers [Taiga #10791](https://tree.taiga.io/project/penpot/issue/10791)
|
||||
- Improve error message details importing tokens [Taiga Issue #10772](https://tree.taiga.io/project/penpot/issue/10772)
|
||||
- Fix no selected set after Drag & Drop [Github #71](https://github.com/tokens-studio/penpot/issues/71)
|
||||
- Styledictionary v5 Update [Github #6283](https://github.com/penpot/penpot/pull/6283)
|
||||
- Fix Rename a set throws an internal error [Github #78](https://github.com/tokens-studio/penpot/issues/78)
|
||||
- Fix Out of Sync Token Value & Color Picker [Github #102](https://github.com/tokens-studio/penpot/issues/102)
|
||||
- Fix Color should preserve color space [Github #69](https://github.com/tokens-studio/penpot/issues/69)
|
||||
- Fix cannot rename Design Token Sets when group of same name exists [Taiga Issue #10773](https://tree.taiga.io/project/penpot/issue/10773)
|
||||
- Fix problem when duplicating grid layout [Github #6391](https://github.com/penpot/penpot/issues/6391)
|
||||
- Fix issue that makes workspace shortcuts stop working [Taiga #11062](https://tree.taiga.io/project/penpot/issue/11062)
|
||||
- Fix problem while syncing library colors and typographies [Taiga #11068](https://tree.taiga.io/project/penpot/issue/11068)
|
||||
- Fix problem with path edition of shapes [Taiga #9496](https://tree.taiga.io/project/penpot/issue/9496)
|
||||
- Fix exception on paste invalid html [Taiga #11047](https://tree.taiga.io/project/penpot/issue/11047)
|
||||
- Fix share button being displayed with no permissions [Taiga #11086](https://tree.taiga.io/project/penpot/issue/11086)
|
||||
- Fix inline styles in code tab [Taiga Issue #7583](https://tree.taiga.io/project/penpot/issue/7583)
|
||||
- Fix exception on returning openapi.json
|
||||
- Fix json encoding of TokensLib [Taiga #10994](https://tree.taiga.io/project/penpot/issue/10994)
|
||||
|
||||
## 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
|
||||
@@ -50,6 +131,7 @@
|
||||
- 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!)
|
||||
|
||||
41
README.md
41
README.md
@@ -16,36 +16,37 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://penpot.app/"><b>Website</b></a> •
|
||||
<a href="https://help.penpot.app/technical-guide/getting-started/"><b>Getting Started</b></a> •
|
||||
<a href="https://help.penpot.app/user-guide/"><b>User Guide</b></a> •
|
||||
<a href="https://help.penpot.app/user-guide/introduction/info/"><b>Tutorials & Info</b></a> •
|
||||
<a href="https://penpot.app/"><b>Website</b></a> •
|
||||
<a href="https://help.penpot.app/user-guide/"><b>User Guide</b></a> •
|
||||
<a href="https://penpot.app/learning-center"><b>Learning Center</b></a> •
|
||||
<a href="https://community.penpot.app/"><b>Community</b></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://www.youtube.com/@Penpot"><b>Youtube</b></a> •
|
||||
<a href="https://peertube.kaleidos.net/a/penpot_app/video-channels"><b>Peertube</b></a> •
|
||||
<a href="https://www.linkedin.com/company/penpot/"><b>Linkedin</b></a> •
|
||||
<a href="https://instagram.com/penpot.app"><b>Instagram</b></a> •
|
||||
<a href="https://fosstodon.org/@penpot/"><b>Mastodon</b></a> •
|
||||
<a href="https://www.youtube.com/@Penpot"><b>Youtube</b></a> •
|
||||
<a href="https://peertube.kaleidos.net/a/penpot_app/video-channels"><b>Peertube</b></a> •
|
||||
<a href="https://www.linkedin.com/company/penpot/"><b>Linkedin</b></a> •
|
||||
<a href="https://instagram.com/penpot.app"><b>Instagram</b></a> •
|
||||
<a href="https://fosstodon.org/@penpot/"><b>Mastodon</b></a> •
|
||||
<a href="https://bsky.app/profile/penpot.app"><b>Bluesky</b></a> •
|
||||
<a href="https://twitter.com/penpotapp"><b>X</b></a>
|
||||
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
[Penpot video](https://github.com/penpot/penpot/assets/5446186/b8ad0764-585e-4ddc-b098-9b4090d337cc)
|
||||
[Penpot video](https://github.com/user-attachments/assets/08b83119-c090-4a74-86ed-7bfbdda9a793)
|
||||
|
||||
<br />
|
||||
|
||||
Penpot is the first **open-source** design tool for design and code collaboration. Designers can create stunning designs, interactive prototypes, design systems at scale, while developers enjoy ready-to-use code and make their workflow easy and fast. And all of this with no handoff drama.
|
||||
|
||||
Penpot is available on browser and [self host](https://penpot.app/self-host). It’s web-based and works with open standards (SVG, CSS and HTML). And last but not least, it’s free!
|
||||
Available on browser or self-hosted, Penpot works with open standards like SVG, CSS, HTML and JSON, and it’s free!
|
||||
|
||||
Penpot’s latest [huge release 2.0](https://penpot.app/dev-diaries), takes the platform to a whole new level. This update introduces the ground-breaking [CSS Grid Layout feature](https://penpot.app/penpot-2.0), a complete UI redesign, a new Components system, and much more. Plus, it's faster and more accessible.
|
||||
The latest updates take Penpot even further. It’s the first design tool to integrate native [design tokens](https://penpot.dev/collaboration/design-tokens)—a single source of truth to improve efficiency and collaboration between product design and development.
|
||||
With the [huge 2.0 release](https://penpot.app/dev-diaries), Penpot took the platform to a whole new level. This update introduces the ground-breaking [CSS Grid Layout feature](https://penpot.app/penpot-2.0), a complete UI redesign, a new Components system, and much more.
|
||||
For organizations that need extra service for its teams, [get in touch](https://cal.com/team/penpot/talk-to-us)
|
||||
|
||||
|
||||
🎇 **Penpot Fest** is our design, code & Open Source event. Check out the highlights from [Penpot Fest 2023 edition](https://www.youtube.com/watch?v=sOpLZaK5mDc)!
|
||||
🎇 Design, code, and Open Source meet at [Penpot Fest](https://penpot.app/penpotfest)! Be part of the 2025 edition in Madrid, Spain, on October 9-10.
|
||||
|
||||
## Table of contents ##
|
||||
|
||||
@@ -61,7 +62,7 @@ Penpot’s latest [huge release 2.0](https://penpot.app/dev-diaries), takes the
|
||||
Penpot expresses designs as code. Designers can do their best work and see it will be beautifully implemented by developers in a two-way collaboration.
|
||||
|
||||
### Plugin system ###
|
||||
[Penpot plugins](https://penpot.app/penpothub/plugins) let you expand the platform's capabilities, give you the flexibility to integrate it with other apps, and design custom solutions.
|
||||
[Penpot plugins](https://penpot.app/penpothub/plugins) let you expand the platform's capabilities, give you the flexibility to integrate it with other apps, and design custom solutions.
|
||||
|
||||
### Designed for developers ###
|
||||
Penpot was built to serve both designers and developers and create a fluid design-code process. You have the choice to enjoy real-time collaboration or play "solo".
|
||||
@@ -78,6 +79,10 @@ Penpot offers integration into the development toolchain, thanks to its support
|
||||
### What’s great for design ###
|
||||
With Penpot you can design libraries to share and reuse; turn design elements into components and tokens to allow reusability and scalability; and build realistic user flows and interactions.
|
||||
|
||||
### Design Tokens ###
|
||||
With Penpot’s standardized [design tokens](https://penpot.dev/collaboration/design-tokens) format, you can easily reuse and sync tokens across different platforms, workflows, and disciplines.
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
<p align="center">
|
||||
@@ -125,13 +130,13 @@ You will find the following categories:
|
||||
|
||||
## Contributing ##
|
||||
|
||||
Any contribution will make a difference to improve Penpot. How can you get involved?
|
||||
Any contribution will make a difference to improve Penpot. How can you get involved?
|
||||
|
||||
Choose your way:
|
||||
Choose your way:
|
||||
|
||||
- Create and [share Libraries & Templates](https://penpot.app/libraries-templates.html) that will be helpful for the community
|
||||
- Invite your [team to join](https://design.penpot.app/#/auth/register)
|
||||
- Star this repo and follow us on Social Media: [Mastodon](https://fosstodon.org/@penpot/), [Youtube](https://www.youtube.com/c/Penpot), [Instagram](https://instagram.com/penpot.app), [Linkedin](https://www.linkedin.com/company/penpotdesign), [Peertube](https://peertube.kaleidos.net/a/penpot_app) and [X](https://twitter.com/penpotapp).
|
||||
- Give this repo a star and follow us on Social Media: [Mastodon](https://fosstodon.org/@penpot/), [Youtube](https://www.youtube.com/c/Penpot), [Instagram](https://instagram.com/penpot.app), [Linkedin](https://www.linkedin.com/company/penpotdesign), [Peertube](https://peertube.kaleidos.net/a/penpot_app), [X](https://twitter.com/penpotapp) and [BlueSky](https://bsky.app/profile/penpot.app)
|
||||
- Participate in the [Community](https://community.penpot.app/) space by asking and answering questions; reacting to others’ articles; opening your own conversations and following along on decisions affecting the project.
|
||||
- Report bugs with our easy [guide for bugs hunting](https://help.penpot.app/contributing-guide/reporting-bugs/) or [GitHub issues](https://github.com/penpot/penpot/issues)
|
||||
- Become a [translator](https://help.penpot.app/contributing-guide/translations)
|
||||
|
||||
@@ -30,7 +30,9 @@ export PENPOT_FLAGS="\
|
||||
enable-access-tokens \
|
||||
enable-tiered-file-data-storage \
|
||||
enable-file-validation \
|
||||
enable-file-schema-validation";
|
||||
enable-file-schema-validation \
|
||||
enable-subscriptons \
|
||||
enable-subscriptons-old";
|
||||
|
||||
# Default deletion delay for devenv
|
||||
export PENPOT_DELETION_DELAY="24h"
|
||||
@@ -70,15 +72,18 @@ export PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
|
||||
export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
|
||||
export PENPOT_OBJECTS_STORAGE_FS_DIRECTORY="assets"
|
||||
|
||||
export JAVA_OPTS="--enable-preview \
|
||||
export JAVA_OPTS="\
|
||||
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
||||
-Djdk.attach.allowAttachSelf \
|
||||
-Dlog4j2.configurationFile=log4j2-devenv-repl.xml \
|
||||
-Djdk.tracePinnedThreads=full \
|
||||
-XX:+EnableDynamicAgentLoading \
|
||||
-XX:-OmitStackTraceInFastThrow \
|
||||
-XX:-OmitStackTraceInFastThrow \
|
||||
-XX:+UnlockDiagnosticVMOptions \
|
||||
-XX:+DebugNonSafepoints";
|
||||
-XX:+DebugNonSafepoints \
|
||||
--sun-misc-unsafe-memory-access=allow \
|
||||
--enable-preview \
|
||||
--enable-native-access=ALL-UNNAMED";
|
||||
|
||||
export OPTIONS="-A:jmx-remote -A:dev"
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ -f ./environ ]; then
|
||||
source ./environ
|
||||
fi
|
||||
|
||||
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --enable-preview $JVM_OPTS"
|
||||
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --enable-native-access=ALL-UNNAMED --enable-preview $JVM_OPTS"
|
||||
|
||||
ENTRYPOINT=${1:-app.main};
|
||||
|
||||
|
||||
@@ -23,18 +23,9 @@ export PENPOT_FLAGS="\
|
||||
enable-access-tokens \
|
||||
enable-tiered-file-data-storage \
|
||||
enable-file-validation \
|
||||
enable-file-schema-validation";
|
||||
|
||||
export OPTIONS="
|
||||
-A:jmx-remote -A:dev \
|
||||
-J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
||||
-J-Djdk.attach.allowAttachSelf \
|
||||
-J-Dpolyglot.engine.WarnInterpreterOnly=false \
|
||||
-J-Dlog4j2.configurationFile=log4j2-devenv.xml \
|
||||
-J-XX:+EnableDynamicAgentLoading \
|
||||
-J-XX:-OmitStackTraceInFastThrow \
|
||||
-J-XX:+UnlockDiagnosticVMOptions \
|
||||
-J-XX:+DebugNonSafepoints"
|
||||
enable-file-schema-validation \
|
||||
enable-subscriptons \
|
||||
enable-subscriptons-old ";
|
||||
|
||||
# Default deletion delay for devenv
|
||||
export PENPOT_DELETION_DELAY="24h"
|
||||
@@ -65,6 +56,20 @@ export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
|
||||
|
||||
entrypoint=${1:-app.main};
|
||||
|
||||
set -ex
|
||||
export JAVA_OPTS="\
|
||||
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
||||
-Djdk.attach.allowAttachSelf \
|
||||
-Dlog4j2.configurationFile=log4j2-devenv.xml \
|
||||
-Djdk.tracePinnedThreads=full \
|
||||
-XX:+EnableDynamicAgentLoading \
|
||||
-XX:-OmitStackTraceInFastThrow \
|
||||
-XX:+UnlockDiagnosticVMOptions \
|
||||
-XX:+DebugNonSafepoints \
|
||||
--sun-misc-unsafe-memory-access=allow \
|
||||
--enable-preview \
|
||||
--enable-native-access=ALL-UNNAMED";
|
||||
|
||||
clojure $OPTIONS -A:dev -M -m $entrypoint;
|
||||
export OPTIONS="-A:jmx-remote -A:dev"
|
||||
|
||||
set -ex
|
||||
clojure $OPTIONS -M -m $entrypoint;
|
||||
|
||||
63
backend/src/app/binfile/cleaner.clj
Normal file
63
backend/src/app/binfile/cleaner.clj
Normal file
@@ -0,0 +1,63 @@
|
||||
;; 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]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; PRE DECODE
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn clean-shape-pre-decode
|
||||
"Applies a pre-decode phase migration to the shape"
|
||||
[shape]
|
||||
(if (= "bool" (:type shape))
|
||||
(if-let [content (get shape :bool-content)]
|
||||
(-> shape
|
||||
(assoc :content content)
|
||||
(dissoc :bool-content))
|
||||
shape)
|
||||
shape))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; POST DECODE
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(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)))
|
||||
@@ -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,35 @@
|
||||
|
||||
(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))))
|
||||
clean-component-pre-decode
|
||||
(fn [component]
|
||||
(d/update-when component :objects
|
||||
(fn [objects]
|
||||
(reduce-kv (fn [objects id shape]
|
||||
(assoc objects id (bfl/clean-shape-pre-decode shape)))
|
||||
objects
|
||||
objects))))]
|
||||
|
||||
(->> (keep (match-component-entry-fn file-id) entries)
|
||||
(reduce (fn [result {:keys [id entry]}]
|
||||
(let [object (->> (read-entry input entry)
|
||||
(clean-component-pre-decode)
|
||||
(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]}]
|
||||
@@ -630,7 +651,9 @@
|
||||
(->> (keep (match-shape-entry-fn file-id page-id) entries)
|
||||
(reduce (fn [result {:keys [id entry]}]
|
||||
(let [object (->> (read-entry input entry)
|
||||
(bfl/clean-shape-pre-decode)
|
||||
(decode-shape)
|
||||
(bfl/clean-shape-post-decode)
|
||||
(validate-shape))]
|
||||
(if (= id (:id object))
|
||||
(assoc result id object)
|
||||
@@ -733,8 +756,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)
|
||||
(bfc/save-file! cfg file ::db/return-keys false)
|
||||
|
||||
@@ -1462,8 +1462,6 @@
|
||||
(:objects page)
|
||||
(:id page)
|
||||
file-id
|
||||
true
|
||||
nil
|
||||
cfsh/prepare-create-artboard-from-selection)]
|
||||
|
||||
(shape-cb shape)
|
||||
|
||||
@@ -155,9 +155,9 @@
|
||||
[["" {:middleware [[mw/server-timing]
|
||||
[mw/params]
|
||||
[mw/format-response]
|
||||
[mw/parse-request]
|
||||
[session/soft-auth cfg]
|
||||
[actoken/soft-auth cfg]
|
||||
[mw/parse-request]
|
||||
[mw/errors errors/handle]
|
||||
[mw/restrict-methods]]}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
(:refer-clojure :exclude [tap])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.transit :as t]
|
||||
[app.http.errors :as errors]
|
||||
@@ -54,18 +53,20 @@
|
||||
::yres/status 200
|
||||
::yres/body (yres/stream-body
|
||||
(fn [_ output]
|
||||
(binding [events/*channel* (sp/chan :buf buf :xf (keep encode))]
|
||||
(let [listener (events/start-listener
|
||||
(partial write! output)
|
||||
(partial pu/close! output))]
|
||||
(try
|
||||
(let [channel (sp/chan :buf buf :xf (keep encode))
|
||||
listener (events/start-listener
|
||||
channel
|
||||
(partial write! output)
|
||||
(partial pu/close! output))]
|
||||
(try
|
||||
(binding [events/*channel* channel]
|
||||
(let [result (handler)]
|
||||
(events/tap :end result))
|
||||
(catch Throwable cause
|
||||
(events/tap :error (errors/handle' cause request))
|
||||
(when-not (ex/instance? java.io.EOFException cause)
|
||||
(binding [l/*context* (errors/request->context request)]
|
||||
(l/err :hint "unexpected error on processing sse response" :cause cause))))
|
||||
(finally
|
||||
(sp/close! events/*channel*)
|
||||
(px/await! listener)))))))}))
|
||||
(events/tap :end result)))
|
||||
|
||||
(catch Throwable cause
|
||||
(let [result (errors/handle' cause request)]
|
||||
(events/tap channel :error result)))
|
||||
|
||||
(finally
|
||||
(sp/close! channel)
|
||||
(px/await! listener))))))}))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
[::ip-addr {:optional true} ::sm/text]
|
||||
[::props {:optional true} [:map-of :keyword :any]]
|
||||
[::context {:optional true} [:map-of :keyword :any]]
|
||||
[::tracked-at {:optional true} ::sm/inst]
|
||||
[::webhooks/event? {:optional true} ::sm/boolean]
|
||||
[::webhooks/batch-timeout {:optional true} ::dt/duration]
|
||||
[::webhooks/batch-key {:optional true}
|
||||
@@ -118,12 +119,12 @@
|
||||
|
||||
(defn prepare-event
|
||||
[cfg mdata params result]
|
||||
(let [resultm (meta result)
|
||||
request (-> params meta ::http/request)
|
||||
profile-id (or (::profile-id resultm)
|
||||
(:profile-id result)
|
||||
(::rpc/profile-id params)
|
||||
uuid/zero)
|
||||
(let [resultm (meta result)
|
||||
request (-> params meta ::http/request)
|
||||
profile-id (or (::profile-id resultm)
|
||||
(:profile-id result)
|
||||
(::rpc/profile-id params)
|
||||
uuid/zero)
|
||||
|
||||
session-id (get params ::rpc/external-session-id)
|
||||
event-origin (get params ::rpc/external-event-origin)
|
||||
@@ -135,14 +136,14 @@
|
||||
|
||||
(clean-props))
|
||||
|
||||
token-id (::actoken/id request)
|
||||
context (-> (::context resultm)
|
||||
(assoc :external-session-id session-id)
|
||||
(assoc :external-event-origin event-origin)
|
||||
(assoc :access-token-id (some-> token-id str))
|
||||
(d/without-nils))
|
||||
token-id (::actoken/id request)
|
||||
context (-> (::context resultm)
|
||||
(assoc :external-session-id session-id)
|
||||
(assoc :external-event-origin event-origin)
|
||||
(assoc :access-token-id (some-> token-id str))
|
||||
(d/without-nils))
|
||||
|
||||
ip-addr (inet/parse-request request)]
|
||||
ip-addr (inet/parse-request request)]
|
||||
|
||||
{::type (or (::type resultm)
|
||||
(::rpc/type cfg))
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.http.client :as http]
|
||||
[app.loggers.audit :as audit]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[clojure.data.json :as json]
|
||||
@@ -67,18 +68,27 @@
|
||||
(defmethod ig/init-key ::process-event-handler
|
||||
[_ cfg]
|
||||
(fn [{:keys [props] :as task}]
|
||||
(l/dbg :hint "process webhook event" :name (:name props))
|
||||
|
||||
(when-let [items (lookup-webhooks cfg props)]
|
||||
(l/trc :hint "webhooks found for event" :total (count items))
|
||||
(db/tx-run! cfg (fn [cfg]
|
||||
(doseq [item items]
|
||||
(wrk/submit! (-> cfg
|
||||
(assoc ::wrk/task :run-webhook)
|
||||
(assoc ::wrk/queue :webhooks)
|
||||
(assoc ::wrk/max-retries 3)
|
||||
(assoc ::wrk/params {:event props
|
||||
:config item})))))))))
|
||||
(let [items (lookup-webhooks cfg props)
|
||||
event {::audit/profile-id (:profile-id props)
|
||||
::audit/name "webhook"
|
||||
::audit/type "trigger"
|
||||
::audit/props {:name (get props :name)
|
||||
:event-id (get props :id)
|
||||
:total-affected (count items)}}]
|
||||
|
||||
(audit/insert! cfg event)
|
||||
|
||||
(when items
|
||||
(l/trc :hint "webhooks found for event" :total (count items))
|
||||
(db/tx-run! cfg (fn [cfg]
|
||||
(doseq [item items]
|
||||
(wrk/submit! (-> cfg
|
||||
(assoc ::wrk/task :run-webhook)
|
||||
(assoc ::wrk/queue :webhooks)
|
||||
(assoc ::wrk/max-retries 3)
|
||||
(assoc ::wrk/params {:event props
|
||||
:config item}))))))))))
|
||||
;; --- RUN
|
||||
|
||||
(declare interpret-exception)
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
[:string {:max 250}]
|
||||
[::sm/one-of {:format "string"} valid-event-types]]]
|
||||
[:props
|
||||
[:map-of :keyword :any]]
|
||||
[:map-of :keyword ::sm/any]]
|
||||
[:context {:optional true}
|
||||
[:map-of :keyword :any]]])
|
||||
[:map-of :keyword ::sm/any]]])
|
||||
|
||||
(def schema:push-audit-events
|
||||
[:map {:title "push-audit-events"}
|
||||
|
||||
@@ -115,7 +115,8 @@
|
||||
|
||||
(db/update! pool :project
|
||||
{:modified-at (dt/now)}
|
||||
{:id project-id})
|
||||
{:id project-id}
|
||||
{::db/return-keys false})
|
||||
|
||||
result))
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
[:is-shared ::sm/boolean]
|
||||
[:project-id ::sm/uuid]
|
||||
[:created-at ::dt/instant]
|
||||
[:data {:optional true} :any]])
|
||||
[:data {:optional true} ::sm/any]])
|
||||
|
||||
(def schema:permissions-mixin
|
||||
[:map {:title "PermissionsMixin"}
|
||||
@@ -208,7 +208,7 @@
|
||||
[:project-id {:optional true} ::sm/uuid]])
|
||||
|
||||
(defn- migrate-file
|
||||
[{:keys [::db/conn] :as cfg} {:keys [id] :as file}]
|
||||
[{:keys [::db/conn] :as cfg} {:keys [id] :as file} {:keys [read-only?]}]
|
||||
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)
|
||||
pmap/*tracked* (pmap/create-tracked)]
|
||||
(let [;; For avoid unnecesary overhead of creating multiple pointers and
|
||||
@@ -219,43 +219,45 @@
|
||||
file (-> file
|
||||
(update :data feat.fdata/process-pointers deref)
|
||||
(update :data feat.fdata/process-objects (partial into {}))
|
||||
(fmg/migrate-file))
|
||||
(fmg/migrate-file))]
|
||||
|
||||
;; When file is migrated, we break the rule of no perform
|
||||
;; mutations on get operations and update the file with all
|
||||
;; migrations applied
|
||||
;;
|
||||
;; WARN: he following code will not work on read-only mode,
|
||||
;; it is a known issue; we keep is not implemented until we
|
||||
;; really need this.
|
||||
file (if (contains? (:features file) "fdata/objects-map")
|
||||
(feat.fdata/enable-objects-map file)
|
||||
file)
|
||||
file (if (contains? (:features file) "fdata/pointer-map")
|
||||
(feat.fdata/enable-pointer-map file)
|
||||
file)]
|
||||
(if (or read-only? (db/read-only? conn))
|
||||
file
|
||||
(let [;; When file is migrated, we break the rule of no perform
|
||||
;; mutations on get operations and update the file with all
|
||||
;; migrations applied
|
||||
file (if (contains? (:features file) "fdata/objects-map")
|
||||
(feat.fdata/enable-objects-map file)
|
||||
file)
|
||||
file (if (contains? (:features file) "fdata/pointer-map")
|
||||
(feat.fdata/enable-pointer-map file)
|
||||
file)]
|
||||
|
||||
(db/update! conn :file
|
||||
{:data (blob/encode (:data file))
|
||||
:version (:version file)
|
||||
:features (db/create-array conn "text" (:features file))}
|
||||
{:id id})
|
||||
(db/update! conn :file
|
||||
{:data (blob/encode (:data file))
|
||||
:version (:version file)
|
||||
:features (db/create-array conn "text" (:features file))}
|
||||
{:id id}
|
||||
{::db/return-keys false})
|
||||
|
||||
(when (contains? (:features file) "fdata/pointer-map")
|
||||
(feat.fdata/persist-pointers! cfg id))
|
||||
(when (contains? (:features file) "fdata/pointer-map")
|
||||
(feat.fdata/persist-pointers! cfg id))
|
||||
|
||||
(feat.fmigr/upsert-migrations! conn file)
|
||||
(feat.fmigr/resolve-applied-migrations cfg file))))
|
||||
(feat.fmigr/upsert-migrations! conn file)
|
||||
(feat.fmigr/resolve-applied-migrations cfg file))))))
|
||||
|
||||
(defn get-file
|
||||
[{:keys [::db/conn ::wrk/executor] :as cfg} id
|
||||
& {:keys [project-id
|
||||
migrate?
|
||||
include-deleted?
|
||||
lock-for-update?]
|
||||
lock-for-update?
|
||||
preload-pointers?]
|
||||
:or {include-deleted? false
|
||||
lock-for-update? false
|
||||
migrate? true}}]
|
||||
migrate? true
|
||||
preload-pointers? false}
|
||||
:as options}]
|
||||
|
||||
(assert (db/connection? conn) "expected cfg with valid connection")
|
||||
|
||||
@@ -273,10 +275,16 @@
|
||||
;; because it has heavy and synchronous operations for
|
||||
;; decoding file body that are not very friendly with virtual
|
||||
;; threads.
|
||||
file (px/invoke! executor #(decode-row file))]
|
||||
file (px/invoke! executor #(decode-row file))
|
||||
|
||||
file (if (and migrate? (fmg/need-migration? file))
|
||||
(migrate-file cfg file options)
|
||||
file)]
|
||||
|
||||
(if preload-pointers?
|
||||
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
|
||||
(update file :data feat.fdata/process-pointers deref))
|
||||
|
||||
(if (and migrate? (fmg/need-migration? file))
|
||||
(migrate-file cfg file)
|
||||
file)))
|
||||
|
||||
(defn get-minimal-file
|
||||
@@ -292,7 +300,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)))
|
||||
@@ -474,7 +482,7 @@
|
||||
(update page :objects update-vals #(dissoc % :thumbnail)))
|
||||
|
||||
(defn get-page
|
||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id file-id page-id object-id] :as params}]
|
||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id file-id page-id object-id share-id] :as params}]
|
||||
|
||||
(when (and (uuid? object-id)
|
||||
(not (uuid? page-id)))
|
||||
@@ -482,22 +490,30 @@
|
||||
:code :params-validation
|
||||
:hint "page-id is required when object-id is provided"))
|
||||
|
||||
(let [team (teams/get-team conn
|
||||
:profile-id profile-id
|
||||
:file-id file-id)
|
||||
(let [perms (get-permissions conn profile-id file-id share-id)
|
||||
|
||||
file (get-file cfg file-id)
|
||||
file (get-file cfg file-id :read-only? true)
|
||||
|
||||
_ (-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (:features params))
|
||||
(cfeat/check-file-features! (:features file)))
|
||||
proj (db/get conn :project {:id (:project-id file)})
|
||||
|
||||
page (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
|
||||
(let [page-id (or page-id (-> file :data :pages first))
|
||||
page (dm/get-in file [:data :pages-index page-id])]
|
||||
(if (pmap/pointer-map? page)
|
||||
(deref page)
|
||||
page)))]
|
||||
team (-> (db/get conn :team {:id (:team-id proj)})
|
||||
(teams/decode-row))
|
||||
|
||||
_ (-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (: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))
|
||||
page (dm/get-in file [:data :pages-index page-id])]
|
||||
(if (pmap/pointer-map? page)
|
||||
(deref page)
|
||||
page)))]
|
||||
|
||||
(when-not perms
|
||||
(ex/raise :type :not-found
|
||||
:code :object-not-found
|
||||
:hint "object not found"))
|
||||
|
||||
(cond-> (prune-thumbnails page)
|
||||
(some? object-id)
|
||||
@@ -733,7 +749,9 @@
|
||||
:project-id project-id
|
||||
:file-id id)
|
||||
|
||||
file (get-file cfg id :project-id project-id)]
|
||||
file (get-file cfg id
|
||||
:project-id project-id
|
||||
:read-only? true)]
|
||||
|
||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (:features params))
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.features :as cfeat]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.files.migrations :as fmg]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.thumbnails :as thc]
|
||||
@@ -18,7 +17,6 @@
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.db.sql :as-alias sql]
|
||||
[app.features.fdata :as feat.fdata]
|
||||
[app.loggers.audit :as-alias audit]
|
||||
[app.loggers.webhooks :as-alias webhooks]
|
||||
[app.media :as media]
|
||||
@@ -180,8 +178,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
|
||||
@@ -201,17 +198,15 @@
|
||||
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
||||
(files/check-read-permissions! conn profile-id file-id)
|
||||
|
||||
(let [team (teams/get-team conn
|
||||
:profile-id profile-id
|
||||
:file-id file-id)
|
||||
(let [team (teams/get-team conn
|
||||
:profile-id profile-id
|
||||
:file-id file-id)
|
||||
|
||||
file (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
|
||||
(-> (files/get-file cfg file-id :migrate? false)
|
||||
(update :data feat.fdata/process-pointers deref)
|
||||
(fmg/migrate-file)))]
|
||||
file (files/get-file cfg file-id
|
||||
:preload-pointers? true
|
||||
:read-only? true)]
|
||||
|
||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (:features params))
|
||||
(cfeat/check-file-features! (:features file)))
|
||||
|
||||
{:file-id file-id
|
||||
|
||||
@@ -80,9 +80,9 @@
|
||||
(def ^:private schema:create-font-variant
|
||||
[:map {:title "create-font-variant"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:data [:map-of :string :any]]
|
||||
[:data [:map-of ::sm/text ::sm/any]]
|
||||
[:font-id ::sm/uuid]
|
||||
[:font-family :string]
|
||||
[:font-family ::sm/text]
|
||||
[:font-weight [::sm/one-of {:format "number"} valid-weight]]
|
||||
[:font-style [::sm/one-of {:format "string"} valid-style]]])
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.json :as json]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.desc-js-like :as smdj]
|
||||
@@ -19,7 +20,6 @@
|
||||
[app.http.sse :as-alias sse]
|
||||
[app.loggers.webhooks :as-alias webhooks]
|
||||
[app.rpc :as-alias rpc]
|
||||
[app.util.json :as json]
|
||||
[app.util.services :as sv]
|
||||
[app.util.template :as tmpl]
|
||||
[clojure.java.io :as io]
|
||||
@@ -86,7 +86,7 @@
|
||||
(fn [request]
|
||||
(let [params (:query-params request)
|
||||
pstyle (:type params "js")
|
||||
context (assoc context :param-style pstyle)]
|
||||
context (assoc @context :param-style pstyle)]
|
||||
|
||||
{::yres/status 200
|
||||
::yres/body (-> (io/resource "app/templates/api-doc.tmpl")
|
||||
@@ -178,8 +178,7 @@
|
||||
(fn [_]
|
||||
{::yres/status 200
|
||||
::yres/headers {"content-type" "application/json; charset=utf-8"}
|
||||
::yres/body (json/encode context)})
|
||||
|
||||
::yres/body (json/encode @context)})
|
||||
(fn [_]
|
||||
{::yres/status 404})))
|
||||
|
||||
@@ -209,7 +208,7 @@
|
||||
|
||||
(defmethod ig/init-key ::routes
|
||||
[_ {:keys [::rpc/methods] :as cfg}]
|
||||
[(let [context (prepare-doc-context methods)]
|
||||
[(let [context (delay (prepare-doc-context methods))]
|
||||
[["/_doc"
|
||||
{:handler (doc-handler context)
|
||||
:allowed-methods #{:get}}]
|
||||
@@ -217,7 +216,7 @@
|
||||
{:handler (doc-handler context)
|
||||
:allowed-methods #{:get}}]])
|
||||
|
||||
(let [context (prepare-openapi-context methods)]
|
||||
(let [context (delay (prepare-openapi-context methods))]
|
||||
[["/openapi"
|
||||
{:handler (openapi-handler)
|
||||
:allowed-methods #{:get}}]
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
component-child (first component-children)]
|
||||
(if (or (nil? child) (nil? component-child))
|
||||
container
|
||||
(let [container (if (and (not (ctk/is-main-of? component-child child true))
|
||||
(let [container (if (and (not (ctk/is-main-of? component-child child))
|
||||
(nil? (ctk/get-swap-slot child))
|
||||
(ctk/instance-head? child))
|
||||
(let [slot (guess-swap-slot component-child component-container)]
|
||||
|
||||
@@ -337,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 _}]
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
to them. Mainly used in http.sse for progress reporting."
|
||||
(:refer-clojure :exclude [tap run!])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[promesa.exec :as px]
|
||||
@@ -18,33 +17,30 @@
|
||||
|
||||
(def ^:dynamic *channel* nil)
|
||||
|
||||
(defn channel
|
||||
[]
|
||||
(sp/chan :buf 32))
|
||||
|
||||
(defn tap
|
||||
[type data]
|
||||
(when-let [channel *channel*]
|
||||
(sp/put! channel [type data])
|
||||
nil))
|
||||
([type data]
|
||||
(when-let [channel *channel*]
|
||||
(sp/put! channel [type data])
|
||||
nil))
|
||||
([channel type data]
|
||||
(when channel
|
||||
(sp/put! channel [type data])
|
||||
nil)))
|
||||
|
||||
(defn start-listener
|
||||
[on-event on-close]
|
||||
|
||||
(dm/assert!
|
||||
"expected active events channel"
|
||||
(sp/chan? *channel*))
|
||||
[channel on-event on-close]
|
||||
(assert (sp/chan? channel) "expected active events channel")
|
||||
|
||||
(px/thread
|
||||
{:virtual true}
|
||||
(try
|
||||
(loop []
|
||||
(when-let [event (sp/take! *channel*)]
|
||||
(when-let [event (sp/take! channel)]
|
||||
(let [result (ex/try! (on-event event))]
|
||||
(if (ex/exception? result)
|
||||
(do
|
||||
(l/wrn :hint "unexpected exception" :cause result)
|
||||
(sp/close! *channel*))
|
||||
(sp/close! channel))
|
||||
(recur)))))
|
||||
(finally
|
||||
(on-close)))))
|
||||
@@ -55,7 +51,7 @@
|
||||
[f on-event]
|
||||
|
||||
(binding [*channel* (sp/chan :buf 32)]
|
||||
(let [listener (start-listener on-event (constantly nil))]
|
||||
(let [listener (start-listener *channel* on-event (constantly nil))]
|
||||
(try
|
||||
(f)
|
||||
(finally
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
(ns app.common.data.macros
|
||||
"Data retrieval & manipulation specific macros."
|
||||
(:refer-clojure :exclude [get-in select-keys str with-open min max])
|
||||
(:refer-clojure :exclude [get-in select-keys str with-open max])
|
||||
#?(:cljs (:require-macros [app.common.data.macros]))
|
||||
(:require
|
||||
#?(:clj [clojure.core :as c]
|
||||
@@ -144,3 +144,8 @@
|
||||
(str "expr assert: " (pr-str expr)))]
|
||||
(when *assert*
|
||||
`(runtime-assert ~hint (fn [] ~expr))))))
|
||||
|
||||
(defn truncate
|
||||
"Truncates a string to a certain length"
|
||||
[s max-length]
|
||||
(subs s 0 (min max-length (count s))))
|
||||
|
||||
@@ -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}]
|
||||
|
||||
@@ -103,9 +103,7 @@
|
||||
"Translate a flag to a feature name"
|
||||
[flag]
|
||||
(case flag
|
||||
:feature-components-v2 "components/v2"
|
||||
:feature-styles-v2 "styles/v2"
|
||||
:feature-grid-layout "layout/grid"
|
||||
:feature-fdata-objects-map "fdata/objects-map"
|
||||
:feature-fdata-pointer-map "fdata/pointer-map"
|
||||
:feature-plugins "plugins/runtime"
|
||||
@@ -216,6 +214,12 @@
|
||||
|
||||
(check-supported-features! file-features)
|
||||
|
||||
;; Components v1 is deprecated
|
||||
(when-not (contains? file-features "components/v2")
|
||||
(ex/raise :type :restriction
|
||||
:code :file-in-components-v1
|
||||
:hint "components v1 is deprecated"))
|
||||
|
||||
(let [not-supported (-> file-features
|
||||
(set/difference enabled-features)
|
||||
(set/difference backend-only-features)
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.files.changes :as ch]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema :as sm]
|
||||
@@ -38,20 +37,12 @@
|
||||
fail-on-spec?]
|
||||
:or {add-container? false
|
||||
fail-on-spec? false}}]
|
||||
(let [components-v2 (dm/get-in file [:data :options :components-v2])
|
||||
component-id (:current-component-id file)
|
||||
change (cond-> change
|
||||
(and add-container? (some? component-id) (not components-v2))
|
||||
(-> (assoc :component-id component-id)
|
||||
(cond-> (some? (:current-frame-id file))
|
||||
(assoc :frame-id (:current-frame-id file))))
|
||||
|
||||
(and add-container? (or (nil? component-id) components-v2))
|
||||
(let [change (cond-> change
|
||||
add-container?
|
||||
(assoc :page-id (:current-page-id file)
|
||||
:frame-id (:current-frame-id file)))
|
||||
|
||||
valid? (or (and components-v2
|
||||
(nil? (:component-id change))
|
||||
valid? (or (and (nil? (:component-id change))
|
||||
(nil? (:page-id change)))
|
||||
(ch/valid-change? change))]
|
||||
|
||||
@@ -66,11 +57,11 @@
|
||||
|
||||
(cond-> file
|
||||
(and valid? (or (not add-container?) (some? (:component-id change)) (some? (:page-id change))))
|
||||
(-> (update :changes conjv change) ;; In components-v2 we do not add shapes
|
||||
(update :data ch/process-changes [change] false)) ;; inside a component
|
||||
(-> (update :changes conjv change)
|
||||
(update :data ch/process-changes [change] false))
|
||||
|
||||
(not valid?)
|
||||
(update :errors conjv change)))));)
|
||||
(update :errors conjv change)))))
|
||||
|
||||
(defn- lookup-objects
|
||||
([file]
|
||||
@@ -185,12 +176,10 @@
|
||||
(update :parent-stack conjv (:id obj)))))
|
||||
|
||||
(defn close-artboard [file]
|
||||
(let [components-v2 (dm/get-in file [:data :options :components-v2])
|
||||
parent-id (-> file :parent-stack peek)
|
||||
(let [parent-id (-> file :parent-stack peek)
|
||||
parent (lookup-shape file parent-id)
|
||||
current-frame-id (or (:frame-id parent)
|
||||
(when (or (nil? (:current-component-id file)) components-v2)
|
||||
root-id))]
|
||||
root-id)]
|
||||
(-> file
|
||||
(assoc :current-frame-id current-frame-id)
|
||||
(update :parent-stack pop))))
|
||||
@@ -283,14 +272,14 @@
|
||||
|
||||
:else
|
||||
(let [objects (lookup-objects file)
|
||||
bool-content (gsh/calc-bool-content bool objects)
|
||||
bool' (gsh/update-bool-selrect bool children objects)]
|
||||
content (gsh/calc-bool-content bool objects)
|
||||
bool' (gsh/update-bool-selrect bool children objects)]
|
||||
(commit-change
|
||||
file
|
||||
{:type :mod-obj
|
||||
:id bool-id
|
||||
:operations
|
||||
[{:type :set :attr :bool-content :val bool-content :ignore-touched true}
|
||||
[{:type :set :attr :content :val content :ignore-touched true}
|
||||
{:type :set :attr :selrect :val (:selrect bool') :ignore-touched true}
|
||||
{:type :set :attr :points :val (:points bool') :ignore-touched true}
|
||||
{:type :set :attr :x :val (-> bool' :selrect :x) :ignore-touched true}
|
||||
@@ -514,58 +503,29 @@
|
||||
|
||||
(defn start-component
|
||||
([file data]
|
||||
(let [components-v2 (dm/get-in file [:data :options :components-v2])
|
||||
root-type (if components-v2 :frame :group)]
|
||||
(start-component file data root-type)))
|
||||
(start-component file data :frame))
|
||||
|
||||
([file data root-type]
|
||||
;; FIXME: data probably can be a shape instance, then we can use gsh/shape->rect
|
||||
(let [components-v2 (dm/get-in file [:data :options :components-v2])
|
||||
selrect (or (grc/make-rect (:x data) (:y data) (:width data) (:height data))
|
||||
grc/empty-rect)
|
||||
name (:name data)
|
||||
(let [name (:name data)
|
||||
path (:path data)
|
||||
main-instance-id (:main-instance-id data)
|
||||
main-instance-page (:main-instance-page data)
|
||||
|
||||
;; In components v1 we must create the root shape and set it inside
|
||||
;; the :objects attribute of the component. When in components-v2,
|
||||
;; this will be ignored as the root shape has already been created
|
||||
;; in its page, by the normal page import.
|
||||
attrs (-> data
|
||||
(assoc :type root-type)
|
||||
(assoc :x (:x selrect))
|
||||
(assoc :y (:y selrect))
|
||||
(assoc :width (:width selrect))
|
||||
(assoc :height (:height selrect))
|
||||
(assoc :selrect selrect)
|
||||
(dissoc :path)
|
||||
(dissoc :main-instance-id)
|
||||
(dissoc :main-instance-page)
|
||||
(dissoc :main-instance-x)
|
||||
(dissoc :main-instance-y))
|
||||
|
||||
obj (-> (cts/setup-shape attrs)
|
||||
(check-name file root-type)
|
||||
;; Components need to have nil values for frame and parent
|
||||
(assoc :frame-id nil)
|
||||
(assoc :parent-id nil))]
|
||||
obj-id (or (:id data) (uuid/next))]
|
||||
|
||||
(-> file
|
||||
(commit-change
|
||||
(cond-> {:type :add-component
|
||||
:id (:id obj)
|
||||
:name name
|
||||
:path path
|
||||
:main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page}
|
||||
(not components-v2)
|
||||
(assoc :shapes [obj])))
|
||||
{:type :add-component
|
||||
:id obj-id
|
||||
:name name
|
||||
:path path
|
||||
:main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page})
|
||||
|
||||
(assoc :last-id (:id obj))
|
||||
(assoc :parent-stack [(:id obj)])
|
||||
(assoc :current-component-id (:id obj))
|
||||
(assoc :current-frame-id (if (= (:type obj) :frame) (:id obj) uuid/zero))))))
|
||||
(assoc :last-id obj-id)
|
||||
(assoc :parent-stack [obj-id])
|
||||
(assoc :current-component-id obj-id)
|
||||
(assoc :current-frame-id (if (= root-type :frame) obj-id uuid/zero))))))
|
||||
|
||||
(defn start-deleted-component
|
||||
[file data]
|
||||
@@ -600,8 +560,7 @@
|
||||
|
||||
file
|
||||
(cond
|
||||
;; In components-v2 components haven't any shape inside them.
|
||||
(and component-data (:main-instance-id component-data))
|
||||
component-data
|
||||
(update file :data
|
||||
(fn [data]
|
||||
(ctkl/update-component data component-id dissoc :objects)))
|
||||
@@ -677,17 +636,12 @@
|
||||
page (ctpl/get-page (:data file) page-id)
|
||||
component (ctkl/get-component (:data file) component-id)
|
||||
|
||||
components-v2 (dm/get-in file [:options :components-v2])
|
||||
|
||||
[shape shapes]
|
||||
(ctn/make-component-instance page
|
||||
component
|
||||
(:id file)
|
||||
(gpt/point x
|
||||
y)
|
||||
components-v2
|
||||
#_{:main-instance true
|
||||
:force-id main-instance-id})]
|
||||
y))]
|
||||
|
||||
(as-> file $
|
||||
(reduce #(commit-change %1
|
||||
|
||||
@@ -26,11 +26,10 @@
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.token-theme :as ctot]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[app.common.types.typography :as ctt]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]))
|
||||
|
||||
@@ -48,14 +47,14 @@
|
||||
[:type [:= :assign]]
|
||||
;; NOTE: the full decoding is happening on the handler because it
|
||||
;; needs a proper context of the current shape and its type
|
||||
[:value [:map-of :keyword :any]]
|
||||
[:value [:map-of :keyword ::sm/any]]
|
||||
[:ignore-touched {:optional true} :boolean]
|
||||
[:ignore-geometry {:optional true} :boolean]]]
|
||||
[:set
|
||||
[:map {:title "SetOperation"}
|
||||
[:type [:= :set]]
|
||||
[:attr :keyword]
|
||||
[:val :any]
|
||||
[:val ::sm/any]
|
||||
[:ignore-touched {:optional true} :boolean]
|
||||
[:ignore-geometry {:optional true} :boolean]]]
|
||||
[:set-touched
|
||||
@@ -239,9 +238,9 @@
|
||||
[:component-id {:optional true} ::sm/uuid]
|
||||
[:ignore-touched {:optional true} :boolean]
|
||||
[:parent-id ::sm/uuid]
|
||||
[:shapes :any]
|
||||
[:shapes ::sm/any]
|
||||
[:index {:optional true} [:maybe :int]]
|
||||
[:after-shape {:optional true} :any]
|
||||
[:after-shape {:optional true} ::sm/any]
|
||||
[:component-swap {:optional true} :boolean]]]
|
||||
|
||||
[:reorder-children
|
||||
@@ -251,14 +250,14 @@
|
||||
[:component-id {:optional true} ::sm/uuid]
|
||||
[:ignore-touched {:optional true} :boolean]
|
||||
[:parent-id ::sm/uuid]
|
||||
[:shapes :any]]]
|
||||
[:shapes ::sm/any]]]
|
||||
|
||||
[:add-page
|
||||
[:map {:title "AddPageChange"}
|
||||
[:type [:= :add-page]]
|
||||
[:id {:optional true} ::sm/uuid]
|
||||
[:name {:optional true} :string]
|
||||
[:page {:optional true} :any]]]
|
||||
[:page {:optional true} ::sm/any]]]
|
||||
|
||||
[:mod-page
|
||||
[:map {:title "ModPageChange"}
|
||||
@@ -328,21 +327,25 @@
|
||||
[:type [:= :add-component]]
|
||||
[:id ::sm/uuid]
|
||||
[:name :string]
|
||||
[:shapes {:optional true} [:vector {:gen/max 3} :any]]
|
||||
[:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
|
||||
[:path {:optional true} :string]]]
|
||||
|
||||
[:mod-component
|
||||
[:map {:title "ModCompoenentChange"}
|
||||
[:type [:= :mod-component]]
|
||||
[:id ::sm/uuid]
|
||||
[:shapes {:optional true} [:vector {:gen/max 3} :any]]
|
||||
[:name {:optional true} :string]]]
|
||||
[:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
|
||||
[:name {:optional true} :string]
|
||||
[:variant-id {:optional true} ::sm/uuid]
|
||||
[:variant-properties {:optional true} [:vector ::ctv/variant-property]]]]
|
||||
|
||||
[:del-component
|
||||
[:map {:title "DelComponentChange"}
|
||||
[:type [:= :del-component]]
|
||||
[:id ::sm/uuid]
|
||||
[:main-instance {:optional true} :any]
|
||||
;; when it is an undo of a cut-paste, we need to undo the movement
|
||||
;; of the shapes so we need to move them delta
|
||||
[:delta {:optional true} ::gpt/point]
|
||||
[:skip-undelete? {:optional true} :boolean]]]
|
||||
|
||||
[:restore-component
|
||||
@@ -403,26 +406,26 @@
|
||||
[:type [:= :set-token-theme]]
|
||||
[:theme-name :string]
|
||||
[:group :string]
|
||||
[:theme [:maybe ::ctot/token-theme]]]]
|
||||
[:theme [:maybe ctob/schema:token-theme-attrs]]]]
|
||||
|
||||
[:set-tokens-lib
|
||||
[:map {:title "SetTokensLib"}
|
||||
[:type [:= :set-tokens-lib]]
|
||||
[:tokens-lib :any]]]
|
||||
[:tokens-lib ::sm/any]]]
|
||||
|
||||
[:set-token-set
|
||||
[:map {:title "SetTokenSetChange"}
|
||||
[:type [:= :set-token-set]]
|
||||
[:set-name :string]
|
||||
[:group? :boolean]
|
||||
[:token-set [:maybe ::ctot/token-set]]]]
|
||||
[:token-set [:maybe ctob/schema:token-set-attrs]]]]
|
||||
|
||||
[:set-token
|
||||
[:map {:title "SetTokenChange"}
|
||||
[:type [:= :set-token]]
|
||||
[:set-name :string]
|
||||
[:token-name :string]
|
||||
[:token [:maybe ::cto/token]]]]]])
|
||||
[:token [:maybe ctob/schema:token-attrs]]]]]])
|
||||
|
||||
(def schema:changes
|
||||
[:sequential {:gen/max 5 :gen/min 1} schema:change])
|
||||
@@ -956,8 +959,8 @@
|
||||
(ctkl/mod-component data params))
|
||||
|
||||
(defmethod process-change :del-component
|
||||
[data {:keys [id skip-undelete? main-instance]}]
|
||||
(ctf/delete-component data id skip-undelete? main-instance))
|
||||
[data {:keys [id skip-undelete? delta]}]
|
||||
(ctf/delete-component data id skip-undelete? delta))
|
||||
|
||||
(defmethod process-change :restore-component
|
||||
[data {:keys [id page-id]}]
|
||||
|
||||
@@ -25,18 +25,19 @@
|
||||
|
||||
;; Auxiliary functions to help create a set of changes (undo + redo)
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::changes}
|
||||
[:map {:title "changes"}
|
||||
[:redo-changes vector?]
|
||||
[:undo-changes seq?]
|
||||
[:origin {:optional true} any?]
|
||||
[:save-undo? {:optional true} boolean?]
|
||||
[:stack-undo? {:optional true} boolean?]
|
||||
[:undo-group {:optional true} any?]])
|
||||
(def schema:changes
|
||||
(sm/register!
|
||||
^{::sm/type ::changes}
|
||||
[:map {:title "changes"}
|
||||
[:redo-changes vector?]
|
||||
[:undo-changes seq?]
|
||||
[:origin {:optional true} ::sm/any]
|
||||
[:save-undo? {:optional true} boolean?]
|
||||
[:stack-undo? {:optional true} boolean?]
|
||||
[:undo-group {:optional true} ::sm/any]]))
|
||||
|
||||
(def check-changes!
|
||||
(sm/check-fn ::changes))
|
||||
(sm/check-fn schema:changes))
|
||||
|
||||
(defn empty-changes
|
||||
([origin page-id]
|
||||
@@ -921,11 +922,11 @@
|
||||
(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 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?]}]
|
||||
([changes id path name updated-shapes main-instance-id main-instance-page]
|
||||
(add-component changes id path name updated-shapes main-instance-id main-instance-page nil nil nil))
|
||||
([changes id path name updated-shapes main-instance-id main-instance-page annotation]
|
||||
(add-component changes id path name updated-shapes main-instance-id main-instance-page annotation nil nil))
|
||||
([changes id path name 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))
|
||||
@@ -964,11 +965,11 @@
|
||||
:name name
|
||||
:main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page
|
||||
: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))))
|
||||
:annotation annotation}
|
||||
(some? variant-id)
|
||||
(assoc :variant-id variant-id)
|
||||
(seq variant-properties)
|
||||
(assoc :variant-properties variant-properties)))
|
||||
(into (map mk-change) updated-shapes))))
|
||||
(update :undo-changes
|
||||
(fn [undo-changes]
|
||||
@@ -991,27 +992,39 @@
|
||||
new-component (update-fn prev-component)]
|
||||
(if prev-component
|
||||
(-> changes
|
||||
(update :redo-changes conj {:type :mod-component
|
||||
:id id
|
||||
:name (:name new-component)
|
||||
:path (:path new-component)
|
||||
: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
|
||||
:id id
|
||||
:name (:name prev-component)
|
||||
:path (:path prev-component)
|
||||
:main-instance-id (:main-instance-id prev-component)
|
||||
:main-instance-page (:main-instance-page prev-component)
|
||||
:annotation (:annotation prev-component)
|
||||
:variant-id (:variant-id prev-component)
|
||||
:variant-properties (:variant-properties prev-component)
|
||||
:objects (:objects prev-component)})
|
||||
(update :redo-changes conj (cond-> {:type :mod-component
|
||||
:id id
|
||||
:name (:name new-component)
|
||||
:path (:path new-component)
|
||||
:main-instance-id (:main-instance-id new-component)
|
||||
:main-instance-page (:main-instance-page new-component)
|
||||
:annotation (:annotation new-component)
|
||||
:objects (:objects new-component) ;; for deleted components
|
||||
:modified-at (:modified-at new-component)}
|
||||
(some? (:variant-id new-component))
|
||||
(assoc :variant-id (:variant-id new-component))
|
||||
(nil? (:variant-id new-component))
|
||||
(dissoc :variant-id)
|
||||
(seq (:variant-properties new-component))
|
||||
(assoc :variant-properties (:variant-properties new-component))
|
||||
(not (seq (:variant-properties new-component)))
|
||||
(dissoc :variant-properties)))
|
||||
(update :undo-changes conj (cond-> {:type :mod-component
|
||||
:id id
|
||||
:name (:name prev-component)
|
||||
:path (:path prev-component)
|
||||
:main-instance-id (:main-instance-id prev-component)
|
||||
:main-instance-page (:main-instance-page prev-component)
|
||||
:annotation (:annotation prev-component)
|
||||
:objects (:objects prev-component)}
|
||||
(some? (:variant-id prev-component))
|
||||
(assoc :variant-id (:variant-id prev-component))
|
||||
(nil? (:variant-id prev-component))
|
||||
(dissoc :variant-id)
|
||||
(seq (:variant-properties prev-component))
|
||||
(assoc :variant-properties (:variant-properties prev-component))
|
||||
(not (seq (:variant-properties prev-component)))
|
||||
(dissoc :variant-properties)))
|
||||
(cond-> apply-changes-local-library?
|
||||
(apply-changes-local {:apply-to-library? true})))
|
||||
changes)))
|
||||
@@ -1027,7 +1040,7 @@
|
||||
:page-id page-id})))
|
||||
|
||||
(defn restore-component
|
||||
[changes id page-id main-instance]
|
||||
[changes id page-id delta]
|
||||
(assert-library! changes)
|
||||
(-> changes
|
||||
(update :redo-changes conj {:type :restore-component
|
||||
@@ -1035,7 +1048,34 @@
|
||||
:page-id page-id})
|
||||
(update :undo-changes conj {:type :del-component
|
||||
:id id
|
||||
:main-instance main-instance})))
|
||||
:delta delta})))
|
||||
|
||||
(defn reorder-children
|
||||
[changes id children]
|
||||
(assert-page-id! changes)
|
||||
(assert-objects! changes)
|
||||
|
||||
(let [page-id (::page-id (meta changes))
|
||||
objects (lookup-objects changes)
|
||||
shape (get objects id)
|
||||
old-children (:shapes shape)
|
||||
|
||||
redo-change
|
||||
{:type :reorder-children
|
||||
:parent-id (:id shape)
|
||||
:page-id page-id
|
||||
:shapes children}
|
||||
|
||||
undo-change
|
||||
{:type :reorder-children
|
||||
:parent-id (:id shape)
|
||||
:page-id page-id
|
||||
:shapes old-children}]
|
||||
|
||||
(-> changes
|
||||
(update :redo-changes conj redo-change)
|
||||
(update :undo-changes conj undo-change)
|
||||
(apply-changes-local))))
|
||||
|
||||
(defn reorder-grid-children
|
||||
[changes ids]
|
||||
@@ -1083,3 +1123,11 @@
|
||||
(defn get-objects
|
||||
[changes]
|
||||
(dm/get-in (::file-data (meta changes)) [:pages-index uuid/zero :objects]))
|
||||
|
||||
(defn get-page
|
||||
[changes]
|
||||
(::page (meta changes)))
|
||||
|
||||
(defn get-page-id
|
||||
[changes]
|
||||
(::page-id (meta changes)))
|
||||
|
||||
@@ -427,11 +427,6 @@
|
||||
(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
|
||||
"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`.
|
||||
@@ -445,8 +440,7 @@
|
||||
|
||||
Returns:
|
||||
- A unique name not present in `existing-names`."
|
||||
[base-name existing-names & {:keys [suffix-fn immediate-suffix?]
|
||||
:or {suffix-fn get-suffix}}]
|
||||
[base-name existing-names & {:keys [suffix-fn immediate-suffix? suffix]}]
|
||||
(dm/assert!
|
||||
"expected a set of strings"
|
||||
(coll? existing-names))
|
||||
@@ -454,9 +448,21 @@
|
||||
(dm/assert!
|
||||
"expected a string for `basename`."
|
||||
(string? base-name))
|
||||
(let [existing-name-set (cond-> (set existing-names)
|
||||
(let [suffix-fn (if suffix-fn
|
||||
suffix-fn
|
||||
(if suffix
|
||||
(fn [copy-count]
|
||||
(str/concat "-"
|
||||
suffix
|
||||
(when (> copy-count 1)
|
||||
(str "-" copy-count))))
|
||||
(fn [copy-count]
|
||||
(str/concat " " copy-count))))
|
||||
|
||||
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)))
|
||||
|
||||
@@ -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))
|
||||
@@ -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,11 @@
|
||||
(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 _]
|
||||
@@ -1237,8 +1236,75 @@
|
||||
(dissoc :applied-tokens)))
|
||||
|
||||
(update-page [page]
|
||||
(d/update-when page :objects update-vals update-object))]
|
||||
(update data :pages-index update-vals update-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 "0002-normalize-bool-content"
|
||||
[data _]
|
||||
(letfn [(update-object [object]
|
||||
;; NOTE: we still preserve the previous value for possible
|
||||
;; rollback, we still need to perform an other migration
|
||||
;; for properly delete the bool-content prop from shapes
|
||||
;; once the know the migration was OK
|
||||
(if (cfh/bool-shape? object)
|
||||
(if-let [content (:bool-content object)]
|
||||
(assoc object :content content)
|
||||
object)
|
||||
(dissoc object :bool-content :bool-type)))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components 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)
|
||||
@@ -1294,4 +1360,7 @@
|
||||
"legacy-65"
|
||||
"legacy-66"
|
||||
"legacy-67"
|
||||
"0001-remove-tokens-from-groups"]))
|
||||
"0001-remove-tokens-from-groups"
|
||||
"0002-normalize-bool-content"
|
||||
"0002-clean-shape-interactions"
|
||||
"0003-fix-root-shape"]))
|
||||
|
||||
@@ -572,6 +572,51 @@
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :not-a-variant
|
||||
[_ error file _]
|
||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||
file)
|
||||
|
||||
(defmethod repair-error :invalid-variant-id
|
||||
[_ error file _]
|
||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||
file)
|
||||
|
||||
(defmethod repair-error :invalid-variant-properties
|
||||
[_ error file _]
|
||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||
file)
|
||||
|
||||
(defmethod repair-error :variant-not-main
|
||||
[_ error file _]
|
||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||
file)
|
||||
|
||||
(defmethod repair-error :parent-not-variant
|
||||
[_ error file _]
|
||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||
file)
|
||||
|
||||
(defmethod repair-error :variant-bad-name
|
||||
[_ error file _]
|
||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||
file)
|
||||
|
||||
(defmethod repair-error :variant-no-properties
|
||||
[_ error file _]
|
||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||
file)
|
||||
|
||||
(defmethod repair-error :variant-bad-variant-name
|
||||
[_ error file _]
|
||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||
file)
|
||||
|
||||
(defmethod repair-error :variant-component-bad-name
|
||||
[_ error file _]
|
||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||
file)
|
||||
|
||||
(defmethod repair-error :default
|
||||
[_ error file _]
|
||||
(log/error :hint "Unknown error code, don't know how to repair" :code (:code error))
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
changes id parent-id objects selected index frame-name without-fill? nil))
|
||||
|
||||
([changes id parent-id objects selected index frame-name without-fill? target-cell-id]
|
||||
(prepare-create-artboard-from-selection
|
||||
changes id parent-id objects selected index frame-name without-fill? target-cell-id nil))
|
||||
|
||||
([changes id parent-id objects selected index frame-name without-fill? target-cell-id delta]
|
||||
(when-let [selected-objs (->> selected
|
||||
(map (d/getf objects))
|
||||
(not-empty))]
|
||||
@@ -99,10 +103,11 @@
|
||||
:id))
|
||||
target-cell-id)
|
||||
|
||||
|
||||
attrs
|
||||
{:type :frame
|
||||
:x (:x srect)
|
||||
:y (:y srect)
|
||||
:x (cond-> (:x srect) delta (+ (:x delta)))
|
||||
:y (cond-> (:y srect) delta (+ (:y delta)))
|
||||
:width (:width srect)
|
||||
:height (:height srect)}
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
(ns app.main.ui.workspace.tokens.token
|
||||
;; 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.files.tokens
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
@@ -128,18 +133,6 @@
|
||||
(defn color-token? [token]
|
||||
(= (:type token) :color))
|
||||
|
||||
|
||||
;; FIXME: this should be precalculated ?
|
||||
(defn is-reference? [token]
|
||||
(str/includes? (:value token) "{"))
|
||||
|
||||
(defn color-bullet-color [token-color-value]
|
||||
(when-let [tc (tinycolor/valid-color token-color-value)]
|
||||
(if (tinycolor/alpha tc)
|
||||
{:color (tinycolor/->hex-string tc)
|
||||
:opacity (tinycolor/alpha tc)}
|
||||
(tinycolor/->hex-string tc))))
|
||||
|
||||
(defn resolved-token-bullet-color [{:keys [resolved-value] :as token}]
|
||||
(when (and resolved-value (color-token? token))
|
||||
(color-bullet-color resolved-value)))
|
||||
@@ -10,12 +10,15 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.files.variant :as cfv]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
@@ -56,7 +59,17 @@
|
||||
:instance-head-not-frame
|
||||
:misplaced-slot
|
||||
:missing-slot
|
||||
:shape-ref-cycle})
|
||||
:shape-ref-cycle
|
||||
:not-a-variant
|
||||
:invalid-variant-id
|
||||
:invalid-variant-properties
|
||||
:variant-not-main
|
||||
:parent-not-variant
|
||||
:variant-bad-name
|
||||
:variant-bad-variant-name
|
||||
:variant-component-bad-name
|
||||
:variant-no-properties
|
||||
:variant-component-bad-id})
|
||||
|
||||
(def ^:private schema:error
|
||||
[:map {:title "ValidationError"}
|
||||
@@ -401,6 +414,68 @@
|
||||
(check-empty-swap-slot shape file page)
|
||||
(run! #(check-shape % file page libraries :context :not-component) (:shapes shape)))
|
||||
|
||||
(defn- check-variant-container
|
||||
"Shape is a variant container, so:
|
||||
-all its children should be variants with variant-id equals to the shape-id
|
||||
-all the components should have the same properties
|
||||
"
|
||||
[shape file page]
|
||||
(let [shape-id (:id shape)
|
||||
shapes (:shapes shape)
|
||||
children (map #(ctst/get-shape page %) shapes)
|
||||
prop-names (cfv/extract-properties-names (first children) (:data file))]
|
||||
(doseq [child children]
|
||||
(if (not (ctk/is-variant? child))
|
||||
(report-error :not-a-variant
|
||||
(str/ffmt "Shape % should be a variant" (:id child))
|
||||
child file page)
|
||||
(do
|
||||
(when (not= (:variant-id child) shape-id)
|
||||
(report-error :invalid-variant-id
|
||||
(str/ffmt "Variant % has invalid variant-id %" (:id child) (:variant-id child))
|
||||
child file page))
|
||||
(when (not= prop-names (cfv/extract-properties-names child (:data file)))
|
||||
(report-error :invalid-variant-properties
|
||||
(str/ffmt "Variant % has invalid properties %" (:id child) (vec prop-names))
|
||||
child file page)))))))
|
||||
|
||||
(defn- check-variant
|
||||
"Shape is a variant, so
|
||||
-it should be a main component
|
||||
-its parent should be a variant-container
|
||||
-its variant-name is derived from the properties
|
||||
-its name should be tha same as its parent's
|
||||
"
|
||||
[shape file page]
|
||||
(let [parent (ctst/get-shape page (:parent-id shape))
|
||||
component (ctkl/get-component (:data file) (:component-id shape) true)
|
||||
name (ctv/properties-to-name (:variant-properties component))]
|
||||
(when-not (ctk/main-instance? shape)
|
||||
(report-error :variant-not-main
|
||||
(str/ffmt "Variant % is not a main instance" (:id shape))
|
||||
shape file page))
|
||||
(when-not (ctk/is-variant-container? parent)
|
||||
(report-error :parent-not-variant
|
||||
(str/ffmt "Variant % has an invalid parent" (:id shape))
|
||||
shape file page))
|
||||
|
||||
(when-not (= name (:variant-name shape))
|
||||
(report-error :variant-bad-variant-name
|
||||
(str/ffmt "Variant % has an invalid variant-name" (:id shape))
|
||||
shape file page))
|
||||
(when-not (= (:name parent) (:name shape))
|
||||
(report-error :variant-bad-name
|
||||
(str/ffmt "Variant % has an invalid name" (:id shape))
|
||||
shape file page))
|
||||
(when-not (= (:name parent) (cfh/merge-path-item (:path component) (:name component)))
|
||||
(report-error :variant-component-bad-name
|
||||
(str/ffmt "Component % has an invalid name" (:id shape))
|
||||
shape file page))
|
||||
(when-not (= (:variant-id component) (:variant-id shape))
|
||||
(report-error :variant-component-bad-id
|
||||
(str/ffmt "Variant % has adifferent variant-id than its component" (:id shape))
|
||||
shape file page))))
|
||||
|
||||
(defn- check-shape
|
||||
"Validate referential integrity and semantic coherence of
|
||||
a shape and all its children. Report all errors found.
|
||||
@@ -421,6 +496,12 @@
|
||||
(check-parent-children shape file page)
|
||||
(check-frame shape file page)
|
||||
|
||||
(when (ctk/is-variant-container? shape)
|
||||
(check-variant-container shape file page))
|
||||
|
||||
(when (ctk/is-variant? shape)
|
||||
(check-variant shape file page))
|
||||
|
||||
(if (ctk/instance-head? shape)
|
||||
(if (not= :frame (:type shape))
|
||||
(report-error :instance-head-not-frame
|
||||
@@ -496,6 +577,24 @@
|
||||
"This deleted component has shapes with shape-ref pointing to self"
|
||||
component file nil :cycles-ids cycles-ids))))
|
||||
|
||||
(defn- check-variant-component
|
||||
"Component is a variant, so:
|
||||
-Its main should be a variant
|
||||
-It should have at least one variant property"
|
||||
[component file]
|
||||
(let [component-page (ctf/get-component-page (:data file) component)
|
||||
main-component (if (:deleted component)
|
||||
(dm/get-in component [:objects (:main-instance-id component)])
|
||||
(ctst/get-shape component-page (:main-instance-id component)))]
|
||||
(when-not (ctk/is-variant? main-component)
|
||||
(report-error :not-a-variant
|
||||
(str/ffmt "Shape % should be a variant" (:id main-component))
|
||||
main-component file component-page))
|
||||
(when (< (count (:variant-properties component)) 1)
|
||||
(report-error :variant-no-properties
|
||||
(str/ffmt "Component variant % should have properties" (:id main-component))
|
||||
main-component file nil))))
|
||||
|
||||
(defn- check-component
|
||||
"Validate semantic coherence of a component. Report all errors found."
|
||||
[component file]
|
||||
@@ -505,7 +604,10 @@
|
||||
component file nil))
|
||||
(when (:deleted component)
|
||||
(check-component-duplicate-swap-slot component file)
|
||||
(check-ref-cycles component file)))
|
||||
(check-ref-cycles component file))
|
||||
|
||||
(when (ctk/is-variant? component)
|
||||
(check-variant-component component file)))
|
||||
|
||||
(defn- get-orphan-shapes
|
||||
[{:keys [objects] :as page}]
|
||||
|
||||
84
common/src/app/common/files/variant.cljc
Normal file
84
common/src/app/common/files/variant.cljc
Normal file
@@ -0,0 +1,84 @@
|
||||
;; 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.files.variant
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.components-list :as ctcl]
|
||||
[app.common.types.variant :as ctv]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
|
||||
(defn find-variant-components
|
||||
"Find a list of the components thet belongs to this variant-id"
|
||||
[data objects variant-id]
|
||||
;; We can't simply filter components, because we need to maintain the order
|
||||
(->> (dm/get-in objects [variant-id :shapes])
|
||||
(map #(dm/get-in objects [% :component-id]))
|
||||
(map #(ctcl/get-component data % true))
|
||||
reverse))
|
||||
|
||||
(defn- dashes-to-end
|
||||
[property-values]
|
||||
(let [dashes (if (some #(= % "--") property-values) ["--"] [])]
|
||||
(concat (remove #(= % "--") property-values) dashes)))
|
||||
|
||||
|
||||
(defn extract-properties-names
|
||||
[shape data]
|
||||
(->> shape
|
||||
(#(ctcl/get-component data (:component-id %) true))
|
||||
:variant-properties
|
||||
(map :name)))
|
||||
|
||||
|
||||
(defn extract-properties-values
|
||||
[data objects variant-id]
|
||||
(->> (find-variant-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 get-variant-mains
|
||||
[component data]
|
||||
(assert (ctv/valid-variant-component? component) "expected valid component variant")
|
||||
(when-let [variant-id (:variant-id component)]
|
||||
(let [page-id (:main-instance-page component)
|
||||
objects (-> (dm/get-in data [:pages-index page-id])
|
||||
(get :objects))]
|
||||
(dm/get-in objects [variant-id :shapes]))))
|
||||
|
||||
|
||||
(defn is-secondary-variant?
|
||||
[component data]
|
||||
(let [shapes (get-variant-mains component data)]
|
||||
(and (seq shapes)
|
||||
(not= (:main-instance-id component) (last shapes)))))
|
||||
|
||||
(defn get-primary-variant
|
||||
[data component]
|
||||
(let [page-id (:main-instance-page component)
|
||||
objects (-> (dm/get-in data [:pages-index page-id])
|
||||
(get :objects))
|
||||
variant-id (:variant-id component)]
|
||||
(->> (dm/get-in objects [variant-id :shapes])
|
||||
peek
|
||||
(get objects))))
|
||||
|
||||
(defn get-primary-component
|
||||
[data component-id]
|
||||
(when-let [component (ctcl/get-component data component-id)]
|
||||
(if (ctc/is-variant? component)
|
||||
(->> component
|
||||
(get-primary-variant data)
|
||||
:component-id
|
||||
(ctcl/get-component data))
|
||||
component)))
|
||||
@@ -79,7 +79,7 @@
|
||||
:file-schema-validation
|
||||
;; Reports the schema validation errors internally.
|
||||
:soft-file-schema-validation
|
||||
;; Activates the referential integrity validation during update file; related to components-v2.
|
||||
;; Activates the referential integrity validation during update file.
|
||||
:file-validation
|
||||
;; Reports the referential integrity validation errors internally.
|
||||
:soft-file-validation
|
||||
@@ -124,7 +124,9 @@
|
||||
;; TODO: deprecate this flag and consolidate the code
|
||||
:export-file-v3
|
||||
:render-wasm-dpr
|
||||
:hide-release-modal})
|
||||
:hide-release-modal
|
||||
:subscriptions
|
||||
:subscriptions-old})
|
||||
|
||||
(def all-flags
|
||||
(set/union email login varia))
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
;;
|
||||
;; 5. If any track still has an infinite growth limit set its growth limit to its base size.
|
||||
|
||||
;; - Distribute extra space accross spaned tracks
|
||||
;; - Distribute extra space accross spaned tracks
|
||||
;; - Maximize tracks
|
||||
;;
|
||||
;; - Expand flexible tracks
|
||||
@@ -198,7 +198,7 @@
|
||||
|
||||
track-list))
|
||||
|
||||
(defn add-auto-size
|
||||
(defn stretch-tracks
|
||||
[track-list add-size]
|
||||
(->> track-list
|
||||
(mapv (fn [{:keys [type size max-size] :as track}]
|
||||
@@ -357,7 +357,8 @@
|
||||
to-idx (+ (dec (get cell prop)) (get cell prop-span))
|
||||
indexed-tracks (subvec (d/enumerate track-list) from-idx to-idx)
|
||||
|
||||
to-allocate (size-to-allocate type parent (get children-map shape-id) cell bounds objects)
|
||||
to-allocate
|
||||
(size-to-allocate type parent (get children-map shape-id) cell bounds objects)
|
||||
|
||||
;; Remove the size and the tracks that are not allocated
|
||||
[to-allocate total-frs indexed-tracks]
|
||||
@@ -493,11 +494,11 @@
|
||||
|
||||
column-tracks (cond-> column-tracks
|
||||
(= :stretch (:layout-justify-content parent))
|
||||
(add-auto-size column-add-auto))
|
||||
(stretch-tracks column-add-auto))
|
||||
|
||||
row-tracks (cond-> row-tracks
|
||||
(= :stretch (:layout-align-content parent))
|
||||
(add-auto-size row-add-auto))
|
||||
(stretch-tracks row-add-auto))
|
||||
|
||||
column-total-size (tracks-total-size column-tracks)
|
||||
row-total-size (tracks-total-size row-tracks)
|
||||
|
||||
@@ -347,7 +347,11 @@
|
||||
move-p nil
|
||||
content (seq content)]
|
||||
(if content
|
||||
(let [command (first content)
|
||||
(let [last-p (last content)
|
||||
content (if (= :move-to (:command last-p))
|
||||
(butlast content)
|
||||
content)
|
||||
command (first content)
|
||||
to-p (command->point command)
|
||||
|
||||
[from-p move-p command-pts]
|
||||
|
||||
@@ -95,8 +95,8 @@
|
||||
(d/update-when :x d/safe+ dx)
|
||||
(d/update-when :y d/safe+ dy)
|
||||
(d/update-when :position-data move-position-data mvec)
|
||||
(cond-> (= :bool type) (update :bool-content gpa/move-content mvec))
|
||||
(cond-> (= :path type) (update :content gpa/move-content mvec)))))
|
||||
(cond-> (or (= :bool type) (= :path type))
|
||||
(update :content gpa/move-content mvec)))))
|
||||
|
||||
;; --- Absolute Movement
|
||||
|
||||
@@ -317,13 +317,10 @@
|
||||
points (gco/transform-points (dm/get-prop shape :points) transform-mtx)
|
||||
selrect (gco/transform-selrect (dm/get-prop shape :selrect) transform-mtx)
|
||||
|
||||
shape (if (= type :bool)
|
||||
(update shape :bool-content gpa/transform-content transform-mtx)
|
||||
shape)
|
||||
shape (if (= type :text)
|
||||
(update shape :position-data transform-position-data transform-mtx)
|
||||
shape)
|
||||
shape (if (= type :path)
|
||||
shape (if (or (= type :path) (= type :bool))
|
||||
(update shape :content gpa/transform-content transform-mtx)
|
||||
(assoc shape
|
||||
:x (dm/get-prop selrect :x)
|
||||
@@ -355,11 +352,8 @@
|
||||
rotation (mod (+ (d/nilv (:rotation shape) 0)
|
||||
(d/nilv (dm/get-in shape [:modifiers :rotation]) 0))
|
||||
360)
|
||||
shape (if (= type :bool)
|
||||
(update shape :bool-content gpa/transform-content transform-mtx)
|
||||
shape)
|
||||
|
||||
shape (if (= type :path)
|
||||
shape (if (or (= type :path) (= type :bool))
|
||||
(update shape :content gpa/transform-content transform-mtx)
|
||||
(assoc shape
|
||||
:x (dm/get-prop selrect :x)
|
||||
@@ -454,9 +448,14 @@
|
||||
"Calculates the selrect+points for the boolean shape"
|
||||
[shape children objects]
|
||||
|
||||
(let [bool-content (gshb/calc-bool-content shape objects)
|
||||
shape (assoc shape :bool-content bool-content)
|
||||
[points selrect] (gpa/content->points+selrect shape bool-content)]
|
||||
(let [content
|
||||
(gshb/calc-bool-content shape objects)
|
||||
|
||||
shape
|
||||
(assoc shape :content content)
|
||||
|
||||
[points selrect]
|
||||
(gpa/content->points+selrect shape content)]
|
||||
|
||||
(if (and (some? selrect) (d/not-empty? points))
|
||||
(-> shape
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,14 +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.logic.variant-properties :as clvp]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[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]
|
||||
[cuerdas.core :as str]))
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn- generate-unapply-tokens
|
||||
"When updating attributes that have a token applied, we must unapply it, because the value
|
||||
@@ -81,163 +81,167 @@
|
||||
(pcb/update-shapes ids update-fn {:attrs #{:blocked :hidden}}))))
|
||||
|
||||
(defn generate-delete-shapes
|
||||
[changes file page objects ids {:keys [components-v2 ignore-touched component-swap]}]
|
||||
(let [ids (cfh/clean-loops objects ids)
|
||||
([changes file page objects ids options]
|
||||
(generate-delete-shapes (-> changes
|
||||
(pcb/with-page page)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/with-library-data file))
|
||||
ids
|
||||
options))
|
||||
([changes ids {:keys [ignore-touched component-swap]}]
|
||||
(let [objects (pcb/get-objects changes)
|
||||
data (pcb/get-library-data changes)
|
||||
page-id (pcb/get-page-id changes)
|
||||
page (or (pcb/get-page changes)
|
||||
(ctpl/get-page data page-id))
|
||||
|
||||
in-component-copy?
|
||||
(fn [shape-id]
|
||||
ids (cfh/clean-loops objects ids)
|
||||
in-component-copy?
|
||||
(fn [shape-id]
|
||||
;; Look for shapes that are inside a component copy, but are
|
||||
;; not the root. In this case, they must not be deleted,
|
||||
;; but hidden (to be able to recover them more easily).
|
||||
;; Unless we are doing a component swap, in which case we want
|
||||
;; to delete the old shape
|
||||
(let [shape (get objects shape-id)]
|
||||
(and (ctn/has-any-copy-parent? objects shape)
|
||||
(not component-swap))))
|
||||
(let [shape (get objects shape-id)]
|
||||
(and (ctn/has-any-copy-parent? objects shape)
|
||||
(not component-swap))))
|
||||
|
||||
[ids-to-delete ids-to-hide]
|
||||
(if components-v2
|
||||
(loop [ids-seq (seq ids)
|
||||
ids-to-delete []
|
||||
ids-to-hide []]
|
||||
(let [id (first ids-seq)]
|
||||
(if (nil? id)
|
||||
[ids-to-delete ids-to-hide]
|
||||
(if (in-component-copy? id)
|
||||
(recur (rest ids-seq)
|
||||
ids-to-delete
|
||||
(conj ids-to-hide id))
|
||||
(recur (rest ids-seq)
|
||||
(conj ids-to-delete id)
|
||||
ids-to-hide)))))
|
||||
[ids []])
|
||||
[ids-to-delete ids-to-hide]
|
||||
(loop [ids-seq (seq ids)
|
||||
ids-to-delete []
|
||||
ids-to-hide []]
|
||||
(let [id (first ids-seq)]
|
||||
(if (nil? id)
|
||||
[ids-to-delete ids-to-hide]
|
||||
(if (in-component-copy? id)
|
||||
(recur (rest ids-seq)
|
||||
ids-to-delete
|
||||
(conj ids-to-hide id))
|
||||
(recur (rest ids-seq)
|
||||
(conj ids-to-delete id)
|
||||
ids-to-hide)))))
|
||||
|
||||
changes (-> changes
|
||||
(pcb/with-page page)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/with-library-data file))
|
||||
lookup (d/getf objects)
|
||||
|
||||
lookup (d/getf objects)
|
||||
|
||||
groups-to-unmask
|
||||
(reduce (fn [group-ids id]
|
||||
groups-to-unmask
|
||||
(reduce (fn [group-ids id]
|
||||
;; When the shape to delete is the mask of a masked group,
|
||||
;; the mask condition must be removed, and it must be
|
||||
;; converted to a normal group.
|
||||
(let [obj (lookup id)
|
||||
parent (lookup (:parent-id obj))]
|
||||
(if (and (:masked-group parent)
|
||||
(= id (first (:shapes parent))))
|
||||
(conj group-ids (:id parent))
|
||||
group-ids)))
|
||||
#{}
|
||||
ids-to-delete)
|
||||
(let [obj (lookup id)
|
||||
parent (lookup (:parent-id obj))]
|
||||
(if (and (:masked-group parent)
|
||||
(= id (first (:shapes parent))))
|
||||
(conj group-ids (:id parent))
|
||||
group-ids)))
|
||||
#{}
|
||||
ids-to-delete)
|
||||
|
||||
interacting-shapes
|
||||
(filter (fn [shape]
|
||||
interacting-shapes
|
||||
(filter (fn [shape]
|
||||
;; If any of the deleted shapes is the destination of
|
||||
;; some interaction, this must be deleted, too.
|
||||
(let [interactions (:interactions shape)]
|
||||
(some #(and (ctsi/has-destination %)
|
||||
(contains? ids-to-delete (:destination %)))
|
||||
interactions)))
|
||||
(vals objects))
|
||||
(let [interactions (:interactions shape)]
|
||||
(some #(and (ctsi/has-destination %)
|
||||
(contains? ids-to-delete (:destination %)))
|
||||
interactions)))
|
||||
(vals objects))
|
||||
|
||||
changes
|
||||
(reduce (fn [changes {:keys [id] :as flow}]
|
||||
(if (contains? ids-to-delete (:starting-frame flow))
|
||||
(pcb/set-flow changes id nil)
|
||||
changes))
|
||||
changes
|
||||
(:flows page))
|
||||
changes
|
||||
(reduce (fn [changes {:keys [id] :as flow}]
|
||||
(if (contains? ids-to-delete (:starting-frame flow))
|
||||
(pcb/set-flow changes id nil)
|
||||
changes))
|
||||
changes
|
||||
(:flows page))
|
||||
|
||||
|
||||
all-parents
|
||||
(reduce (fn [res id]
|
||||
all-parents
|
||||
(reduce (fn [res id]
|
||||
;; All parents of any deleted shape must be resized.
|
||||
(into res (cfh/get-parent-ids objects id)))
|
||||
(d/ordered-set)
|
||||
(concat ids-to-delete ids-to-hide))
|
||||
(into res (cfh/get-parent-ids objects id)))
|
||||
(d/ordered-set)
|
||||
(concat ids-to-delete ids-to-hide))
|
||||
|
||||
all-children
|
||||
(->> ids-to-delete ;; Children of deleted shapes must be also deleted.
|
||||
(reduce (fn [res id]
|
||||
(into res (cfh/get-children-ids objects id)))
|
||||
[])
|
||||
(reverse)
|
||||
(into (d/ordered-set)))
|
||||
all-children
|
||||
(->> ids-to-delete ;; Children of deleted shapes must be also deleted.
|
||||
(reduce (fn [res id]
|
||||
(into res (cfh/get-children-ids objects id)))
|
||||
[])
|
||||
(reverse)
|
||||
(into (d/ordered-set)))
|
||||
|
||||
find-all-empty-parents
|
||||
(fn recursive-find-empty-parents [empty-parents]
|
||||
(let [all-ids (into empty-parents ids-to-delete)
|
||||
contains? (partial contains? all-ids)
|
||||
xform (comp (map lookup)
|
||||
(filter #(or (cfh/group-shape? %) (cfh/bool-shape? %)))
|
||||
(remove #(->> (:shapes %) (remove contains?) seq))
|
||||
(map :id))
|
||||
parents (into #{} xform all-parents)]
|
||||
(if (= empty-parents parents)
|
||||
empty-parents
|
||||
(recursive-find-empty-parents parents))))
|
||||
find-all-empty-parents
|
||||
(fn recursive-find-empty-parents [empty-parents]
|
||||
(let [all-ids (into empty-parents ids-to-delete)
|
||||
contains? (partial contains? all-ids)
|
||||
xform (comp (map lookup)
|
||||
(filter #(or (cfh/group-shape? %) (cfh/bool-shape? %) (ctk/is-variant-container? %)))
|
||||
(remove #(->> (:shapes %) (remove contains?) seq))
|
||||
(map :id))
|
||||
parents (into #{} xform all-parents)]
|
||||
(if (= empty-parents parents)
|
||||
empty-parents
|
||||
(recursive-find-empty-parents parents))))
|
||||
|
||||
empty-parents
|
||||
empty-parents
|
||||
;; Any parent whose children are all deleted, must be deleted too.
|
||||
;; Unless we are during a component swap: in this case we are replacing a shape by
|
||||
;; other one, so must not delete empty parents.
|
||||
(if-not component-swap
|
||||
(into (d/ordered-set) (find-all-empty-parents #{}))
|
||||
#{})
|
||||
(if-not component-swap
|
||||
(into (d/ordered-set) (find-all-empty-parents #{}))
|
||||
#{})
|
||||
|
||||
components-to-delete
|
||||
(if components-v2
|
||||
(reduce (fn [components id]
|
||||
(let [shape (get objects id)]
|
||||
(if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file
|
||||
(:main-instance shape)) ;; but check anyway
|
||||
(conj components (:component-id shape))
|
||||
components)))
|
||||
[]
|
||||
(into ids-to-delete all-children))
|
||||
[])
|
||||
components-to-delete
|
||||
(reduce (fn [components id]
|
||||
(let [shape (get objects id)]
|
||||
(if (and (= (:component-file shape) (:id data)) ;; Main instances should exist only in local file
|
||||
(:main-instance shape)) ;; but check anyway
|
||||
(conj components (:component-id shape))
|
||||
components)))
|
||||
[]
|
||||
(into ids-to-delete all-children))
|
||||
|
||||
ids-set (set ids-to-delete)
|
||||
|
||||
guides-to-delete
|
||||
(->> (:guides page)
|
||||
(vals)
|
||||
(filter #(contains? ids-set (:frame-id %)))
|
||||
(map :id))
|
||||
ids-set (set ids-to-delete)
|
||||
|
||||
changes (reduce (fn [changes guide-id]
|
||||
(pcb/set-flow changes guide-id nil))
|
||||
changes
|
||||
guides-to-delete)
|
||||
guides-to-delete
|
||||
(->> (:guides page)
|
||||
(vals)
|
||||
(filter #(contains? ids-set (:frame-id %)))
|
||||
(map :id))
|
||||
|
||||
changes (reduce (fn [changes component-id]
|
||||
changes (reduce (fn [changes guide-id]
|
||||
(pcb/set-flow changes guide-id nil))
|
||||
changes
|
||||
guides-to-delete)
|
||||
|
||||
changes (reduce (fn [changes component-id]
|
||||
;; It's important to delete the component before the main instance, because we
|
||||
;; need to store the instance position if we want to restore it later.
|
||||
(pcb/delete-component changes component-id (:id page)))
|
||||
changes
|
||||
components-to-delete)
|
||||
(pcb/delete-component changes component-id (:id page)))
|
||||
changes
|
||||
components-to-delete)
|
||||
|
||||
changes (-> changes
|
||||
(generate-update-shape-flags ids-to-hide objects {:hidden true})
|
||||
(pcb/remove-objects all-children {:ignore-touched true})
|
||||
(pcb/remove-objects ids-to-delete {:ignore-touched ignore-touched})
|
||||
(pcb/remove-objects empty-parents)
|
||||
(pcb/resize-parents all-parents)
|
||||
(pcb/update-shapes groups-to-unmask
|
||||
(fn [shape]
|
||||
(assoc shape :masked-group false)))
|
||||
(pcb/update-shapes (map :id interacting-shapes)
|
||||
(fn [shape]
|
||||
(d/update-when shape :interactions
|
||||
(fn [interactions]
|
||||
(into []
|
||||
(remove #(and (ctsi/has-destination %)
|
||||
(contains? ids-to-delete (:destination %))))
|
||||
interactions))))))]
|
||||
[all-parents changes]))
|
||||
changes (-> changes
|
||||
(generate-update-shape-flags ids-to-hide objects {:hidden true})
|
||||
(pcb/remove-objects all-children {:ignore-touched true})
|
||||
(pcb/remove-objects ids-to-delete {:ignore-touched ignore-touched})
|
||||
(pcb/remove-objects empty-parents)
|
||||
(pcb/resize-parents all-parents)
|
||||
(pcb/update-shapes groups-to-unmask
|
||||
(fn [shape]
|
||||
(assoc shape :masked-group false)))
|
||||
(pcb/update-shapes (map :id interacting-shapes)
|
||||
(fn [shape]
|
||||
(d/update-when shape :interactions
|
||||
(fn [interactions]
|
||||
(into []
|
||||
(remove #(and (ctsi/has-destination %)
|
||||
(contains? ids-to-delete (:destination %))))
|
||||
interactions))))))]
|
||||
[all-parents changes])))
|
||||
|
||||
|
||||
(defn generate-relocate
|
||||
@@ -255,7 +259,7 @@
|
||||
|
||||
child-heads-ids (map :id child-heads)
|
||||
|
||||
variant-heads (filter ctk/is-variant? child-heads)
|
||||
variant-shapes (filter ctk/is-variant? shapes)
|
||||
|
||||
component-main-parent
|
||||
(ctn/find-component-main objects parent false)
|
||||
@@ -339,7 +343,19 @@
|
||||
(map :id)))
|
||||
|
||||
index-cell-data (when to-index (ctl/get-cell-by-index parent to-index))
|
||||
cell (or cell (and index-cell-data [(:row index-cell-data) (:column index-cell-data)]))]
|
||||
cell (or cell (and index-cell-data [(:row index-cell-data) (:column index-cell-data)]))
|
||||
|
||||
|
||||
;; Parents that are a variant-container that becomes empty
|
||||
empty-variant-cont (reduce
|
||||
(fn [to-delete parent-id]
|
||||
(let [parent (get objects parent-id)]
|
||||
(if (and (ctk/is-variant-container? parent)
|
||||
(empty? (remove (set ids) (:shapes parent))))
|
||||
(conj to-delete (:id parent))
|
||||
to-delete)))
|
||||
#{}
|
||||
(remove #(= % parent-id) all-parents))]
|
||||
|
||||
(-> changes
|
||||
;; Remove layout-item properties when moving a shape outside a layout
|
||||
@@ -368,82 +384,11 @@
|
||||
|
||||
;; 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))))
|
||||
(clvp/generate-make-shapes-no-variant variant-shapes))
|
||||
|
||||
;; 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)))))
|
||||
(clvp/generate-make-shapes-variant child-heads parent))
|
||||
|
||||
;; Move the shapes
|
||||
(pcb/change-parent parent-id
|
||||
@@ -518,7 +463,11 @@
|
||||
(pcb/update-shapes ids #(assoc % :blocked true)))
|
||||
|
||||
;; Resize parent containers that need to
|
||||
(pcb/resize-parents parents))))
|
||||
(pcb/resize-parents parents)
|
||||
|
||||
;; Remove parents when are a variant-container that becomes empty
|
||||
(cond-> (seq empty-variant-cont)
|
||||
(#(second (generate-delete-shapes % empty-variant-cont {})))))))
|
||||
|
||||
(defn change-show-in-viewer
|
||||
[shape hide?]
|
||||
|
||||
198
common/src/app/common/logic/variant_properties.cljc
Normal file
198
common/src/app/common/logic/variant_properties.cljc
Normal file
@@ -0,0 +1,198 @@
|
||||
;; 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.variant-properties
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.files.variant :as cfv]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctcl]
|
||||
[app.common.types.variant :as ctv]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(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 (cfv/find-variant-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 (cfv/find-variant-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 (ctv/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)
|
||||
ctv/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? property-name]}]
|
||||
(let [data (pcb/get-library-data changes)
|
||||
objects (pcb/get-objects changes)
|
||||
related-components (cfv/find-variant-components data objects variant-id)
|
||||
|
||||
props (-> related-components last :variant-properties)
|
||||
next-prop-num (ctv/next-property-number props)
|
||||
property-name (or property-name (str ctv/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 ctv/value-prefix num) "")}))
|
||||
|
||||
update-name #(if fill-values?
|
||||
(if (str/empty? %)
|
||||
(str ctv/value-prefix num)
|
||||
(str % ", " ctv/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))
|
||||
|
||||
(defn- generate-make-shape-no-variant
|
||||
[changes shape]
|
||||
(let [new-name (ctv/variant-name-to-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))))))
|
||||
|
||||
(defn generate-make-shapes-no-variant
|
||||
[changes shapes]
|
||||
(reduce generate-make-shape-no-variant changes shapes))
|
||||
|
||||
|
||||
(defn- generate-new-properties-from-variant
|
||||
[shape min-props data container-name base-properties]
|
||||
(let [component (ctcl/get-component data (:component-id shape) true)
|
||||
add-name? (not= (:name component) container-name)
|
||||
props (ctv/merge-properties base-properties
|
||||
(:variant-properties component))
|
||||
new-props (- min-props
|
||||
(+ (count props)
|
||||
(if add-name? 1 0)))
|
||||
props (ctv/add-new-props props (repeat new-props ""))]
|
||||
|
||||
(if add-name?
|
||||
(ctv/add-new-prop props (:name component))
|
||||
props)))
|
||||
|
||||
(defn- generate-new-properties-from-non-variant
|
||||
[shape min-props container-name base-properties]
|
||||
(let [;; Remove container name from shape name if present
|
||||
shape-name (ctv/remove-prefix (:name shape) container-name)]
|
||||
(ctv/path-to-properties shape-name base-properties min-props)))
|
||||
|
||||
|
||||
(defn generate-make-shapes-variant
|
||||
[changes shapes variant-container]
|
||||
(let [data (pcb/get-library-data changes)
|
||||
objects (pcb/get-objects changes)
|
||||
variant-id (:id variant-container)
|
||||
|
||||
;; If we are cut-pasting a variant-container, this will be null
|
||||
;; because it hasn't any shapes yet
|
||||
first-comp-id (->> variant-container
|
||||
:shapes
|
||||
first
|
||||
(get objects)
|
||||
:component-id)
|
||||
|
||||
base-props (->> (get-in data [:components first-comp-id :variant-properties])
|
||||
(map #(assoc % :value "")))
|
||||
num-base-props (count base-props)
|
||||
|
||||
[cpath cname] (cfh/parse-path-name (:name variant-container))
|
||||
container-name (:name variant-container)
|
||||
|
||||
generate-new-properties
|
||||
(fn [shape min-props]
|
||||
(if (ctk/is-variant? shape)
|
||||
(generate-new-properties-from-variant shape min-props data container-name base-props)
|
||||
(generate-new-properties-from-non-variant shape min-props container-name base-props)))
|
||||
|
||||
total-props (reduce (fn [m shape]
|
||||
(max m (count (generate-new-properties shape num-base-props))))
|
||||
0
|
||||
shapes)
|
||||
|
||||
num-new-props (if (or (zero? num-base-props)
|
||||
(< total-props num-base-props))
|
||||
0
|
||||
(- total-props num-base-props))
|
||||
|
||||
changes (nth
|
||||
(iterate #(generate-add-new-property % variant-id) changes)
|
||||
num-new-props)
|
||||
|
||||
changes (pcb/update-shapes changes (map :id shapes)
|
||||
#(assoc % :variant-id variant-id
|
||||
:name (:name variant-container)))]
|
||||
(reduce
|
||||
(fn [changes shape]
|
||||
(if (or (zero? num-base-props)
|
||||
(= variant-id (:variant-id shape)))
|
||||
changes ;; do nothing more if we aren't changing the parent or there are no base props
|
||||
(let [props (generate-new-properties shape total-props)
|
||||
variant-name (ctv/properties-to-name props)]
|
||||
(-> (pcb/update-component changes
|
||||
(:component-id shape)
|
||||
#(assoc % :variant-id variant-id
|
||||
:variant-properties props
|
||||
:name cname
|
||||
:path cpath)
|
||||
{:apply-changes-local-library? true})
|
||||
(pcb/update-shapes [(:id shape)]
|
||||
#(assoc % :variant-name variant-name))))))
|
||||
changes
|
||||
shapes)))
|
||||
@@ -1,160 +1,30 @@
|
||||
;; 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]))
|
||||
[app.common.files.variant :as cfv]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.variant-properties :as clvp]
|
||||
[app.common.types.variant :as ctv]))
|
||||
|
||||
|
||||
(defn generate-add-new-variant
|
||||
[changes shape variant-id new-component-id new-shape-id prop-num]
|
||||
(let [data (pcb/get-library-data changes)
|
||||
objects (pcb/get-objects changes)
|
||||
component-id (:component-id shape)
|
||||
value (str ctv/value-prefix
|
||||
(-> (cfv/extract-properties-values data objects variant-id)
|
||||
last
|
||||
:value
|
||||
count
|
||||
inc))
|
||||
|
||||
(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)]
|
||||
[new-shape changes] (-> changes
|
||||
(cll/generate-duplicate-component
|
||||
{:data data}
|
||||
component-id
|
||||
new-component-id
|
||||
{:new-shape-id new-shape-id :apply-changes-local-library? true}))]
|
||||
(-> 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))
|
||||
|
||||
(clvp/generate-update-property-value new-component-id prop-num value)
|
||||
(pcb/change-parent (:parent-id shape) [new-shape] 0))))
|
||||
|
||||
@@ -113,6 +113,10 @@
|
||||
[schema]
|
||||
(mu/optional-keys schema default-options))
|
||||
|
||||
(defn required-keys
|
||||
[schema]
|
||||
(mu/required-keys schema default-options))
|
||||
|
||||
(defn transformer
|
||||
[& transformers]
|
||||
(apply mt/transformer transformers))
|
||||
@@ -145,11 +149,30 @@
|
||||
;; :else
|
||||
;; o))
|
||||
|
||||
(defn -transform-map-keys
|
||||
([f]
|
||||
(let [xform (map (fn [[k v]] [(f k) v]))]
|
||||
#(cond->> % (map? %) (into (empty %) xform))))
|
||||
([ks f]
|
||||
(let [xform (map (fn [[k v]] [(cond-> k (contains? ks k) f) v]))]
|
||||
#(cond->> % (map? %) (into (empty %) xform)))))
|
||||
|
||||
(defn json-transformer
|
||||
[]
|
||||
(mt/transformer
|
||||
(mt/json-transformer)
|
||||
(mt/collection-transformer)))
|
||||
(let [map-of-key-decoders (mt/-string-decoders)]
|
||||
(mt/transformer
|
||||
{:name :json
|
||||
:decoders (-> (mt/-json-decoders)
|
||||
(assoc :map-of {:compile (fn [schema _]
|
||||
(let [key-schema (some-> schema (m/children) (first))]
|
||||
(or (some-> key-schema (m/type) map-of-key-decoders
|
||||
(mt/-interceptor schema {}) (m/-intercepting)
|
||||
(m/-comp m/-keyword->string)
|
||||
(mt/-transform-if-valid key-schema)
|
||||
(-transform-map-keys))
|
||||
(-transform-map-keys m/-keyword->string))))}))
|
||||
:encoders (mt/-json-encoders)}
|
||||
(mt/collection-transformer))))
|
||||
|
||||
(defn string-transformer
|
||||
[]
|
||||
@@ -191,8 +214,7 @@
|
||||
|
||||
(defn lazy-validator
|
||||
[s]
|
||||
(let [s (schema s)
|
||||
vfn (delay (validator s))]
|
||||
(let [vfn (delay (validator s))]
|
||||
(fn [v] (@vfn v))))
|
||||
|
||||
(defn lazy-explainer
|
||||
@@ -295,11 +317,14 @@
|
||||
([params]
|
||||
(cond
|
||||
(map? params)
|
||||
(let [type (get params :type)]
|
||||
(let [mdata (meta params)
|
||||
type (or (get mdata ::id)
|
||||
(get mdata ::type)
|
||||
(get params :type))]
|
||||
(assert (qualified-keyword? type) "expected qualified keyword for `type`")
|
||||
(let [s (m/-simple-schema params)]
|
||||
(swap! sr/registry assoc type s)
|
||||
nil))
|
||||
s))
|
||||
|
||||
(vector? params)
|
||||
(let [mdata (meta params)
|
||||
@@ -307,11 +332,12 @@
|
||||
(get mdata ::type))]
|
||||
(assert (qualified-keyword? type) "expected qualified keyword to be on metadata")
|
||||
(swap! sr/registry assoc type params)
|
||||
nil)
|
||||
params)
|
||||
|
||||
(m/into-schema? params)
|
||||
(let [type (m/-type params)]
|
||||
(swap! sr/registry assoc type params))
|
||||
(swap! sr/registry assoc type params)
|
||||
params)
|
||||
|
||||
:else
|
||||
(throw (ex-info "Invalid Arguments" {}))))
|
||||
@@ -390,14 +416,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 +443,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 +890,7 @@
|
||||
choices))]
|
||||
{:pred pred
|
||||
:type-properties
|
||||
{:title "contains"
|
||||
{:title "contains any"
|
||||
:description "contains predicate"}}))})
|
||||
|
||||
(register!
|
||||
@@ -866,7 +900,7 @@
|
||||
{:title "inst"
|
||||
:description "Satisfies Inst protocol"
|
||||
:error/message "should be an instant"
|
||||
:gen/gen (->> (sg/small-int)
|
||||
:gen/gen (->> (sg/small-int :min 0 :max 100000)
|
||||
(sg/fmap (fn [v] (tm/parse-instant v))))
|
||||
|
||||
:decode/string tm/parse-instant
|
||||
@@ -1011,6 +1045,8 @@
|
||||
{:title "agent"
|
||||
:description "instance of clojure agent"}}))
|
||||
|
||||
(register! ::any (mu/update-properties :any assoc :gen/gen sg/any))
|
||||
|
||||
;; ---- PREDICATES
|
||||
|
||||
(def valid-safe-number?
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.common.schema.desc-js-like
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.schema :as-alias sm]
|
||||
[cuerdas.core :as str]
|
||||
[malli.core :as m]
|
||||
[malli.util :as mu]))
|
||||
@@ -90,7 +91,7 @@
|
||||
(defmethod visit :int [_ schema _ _] (str "integer" (-titled schema) (-min-max-suffix-number schema)))
|
||||
(defmethod visit :double [_ schema _ _] (str "double" (-titled schema) (-min-max-suffix-number schema)))
|
||||
(defmethod visit :select-keys [_ schema _ options] (describe* (m/deref schema) options))
|
||||
(defmethod visit :and [_ s children _] (str (str/join ", and " children) (-titled s)))
|
||||
(defmethod visit :and [_ s children _] (str (str/join " && " children) (-titled s)))
|
||||
(defmethod visit :enum [_ s children _options] (str "enum" (-titled s) " of " (str/join ", " children)))
|
||||
(defmethod visit :maybe [_ _ children _] (str (first children) " nullable"))
|
||||
(defmethod visit :tuple [_ _ children _] (str "(" (str/join ", " children) ")"))
|
||||
@@ -106,7 +107,8 @@
|
||||
(defmethod visit :qualified-symbol [_ _ _ _] "qualified symbol")
|
||||
(defmethod visit :uuid [_ _ _ _] "uuid")
|
||||
(defmethod visit :boolean [_ _ _ _] "boolean")
|
||||
(defmethod visit :keyword [_ _ _ _] "keyword")
|
||||
(defmethod visit :keyword [_ _ _ _] "string")
|
||||
(defmethod visit :fn [_ _ _ _] "FN")
|
||||
|
||||
(defmethod visit :vector [_ _ children _]
|
||||
(str "[" (last children) "]"))
|
||||
@@ -123,10 +125,12 @@
|
||||
(defmethod visit :repeat [_ schema children _]
|
||||
(str "repeat " (-diamond (first children)) (-repeat-suffix schema)))
|
||||
|
||||
|
||||
(defmethod visit :set [_ schema children _]
|
||||
(str "set[" (first children) "]" (minmax-suffix schema)))
|
||||
|
||||
(defmethod visit ::sm/set [_ schema children _]
|
||||
(str "set[" (first children) "]" (minmax-suffix schema)))
|
||||
|
||||
(defmethod visit ::m/val [_ schema children _]
|
||||
(let [suffix (minmax-suffix schema)]
|
||||
(cond-> (first children)
|
||||
@@ -152,7 +156,6 @@
|
||||
(or (:title props)
|
||||
"*")))
|
||||
|
||||
|
||||
(defmethod visit :map
|
||||
[_ schema children {:keys [::level ::max-level] :as options}]
|
||||
(let [props (m/properties schema)
|
||||
@@ -172,13 +175,11 @@
|
||||
": " s)))
|
||||
(str/join ",\n"))
|
||||
|
||||
header (cond-> (if (zero? level)
|
||||
(str "type " title)
|
||||
(str title))
|
||||
header (cond-> (str "type " title)
|
||||
closed? (str "!")
|
||||
(some? title) (str " "))]
|
||||
|
||||
(str header "{\n" entries "\n" (pad "}" level))))))
|
||||
(str (pad header level) "{\n" entries "\n" (pad "}\n" level))))))
|
||||
|
||||
(defmethod visit :multi
|
||||
[_ s children {:keys [::level ::max-level] :as options}]
|
||||
@@ -205,18 +206,18 @@
|
||||
|
||||
(defmethod visit :merge
|
||||
[_ schema children _]
|
||||
(let [entries (str/join " , " children)
|
||||
(let [entries (str/join ",\n" children)
|
||||
props (m/properties schema)
|
||||
title (or (some-> (:title props) str/camel str/capital)
|
||||
"<untitled>")]
|
||||
(str "merge object " title " { " entries " }")))
|
||||
(str "merge type " title " { \n" entries "\n}\n")))
|
||||
|
||||
(defmethod visit :app.common.schema/one-of
|
||||
[_ _ children _]
|
||||
(defmethod visit ::sm/one-of
|
||||
[_ _ children _]
|
||||
(let [elems (last children)]
|
||||
(str "OneOf[" (->> elems
|
||||
(map d/name)
|
||||
(str/join ",")) "]")))
|
||||
(str "string oneOf (" (->> elems
|
||||
(map d/name)
|
||||
(str/join "|")) ")")))
|
||||
|
||||
(defmethod visit :schema [_ schema children options]
|
||||
(visit ::m/schema schema children options))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.schema.generators
|
||||
(:refer-clojure :exclude [set subseq uuid filter map let boolean])
|
||||
(:refer-clojure :exclude [set subseq uuid filter map let boolean vector keyword int double])
|
||||
#?(:cljs (:require-macros [app.common.schema.generators]))
|
||||
(:require
|
||||
[app.common.schema.registry :as sr]
|
||||
@@ -38,10 +38,6 @@
|
||||
([s opts]
|
||||
(mg/generator s (assoc opts :registry sr/default-registry))))
|
||||
|
||||
(defn filter
|
||||
[pred gen]
|
||||
(tg/such-that pred gen 100))
|
||||
|
||||
(defn small-double
|
||||
[& {:keys [min max] :or {min -100 max 100}}]
|
||||
(tg/double* {:min min, :max max, :infinite? false, :NaN? false}))
|
||||
@@ -61,7 +57,7 @@
|
||||
(defn word-keyword
|
||||
[]
|
||||
(->> (word-string)
|
||||
(tg/fmap keyword)))
|
||||
(tg/fmap c/keyword)))
|
||||
|
||||
(defn email
|
||||
[]
|
||||
@@ -100,12 +96,11 @@
|
||||
(c/map second))
|
||||
(c/map list bools elements)))))))
|
||||
|
||||
(def any tg/any)
|
||||
(def boolean tg/boolean)
|
||||
|
||||
(defn set
|
||||
[g]
|
||||
(tg/set g))
|
||||
(defn map-of
|
||||
([kg vg]
|
||||
(tg/map kg vg {:min-elements 1 :max-elements 3}))
|
||||
([kg vg opts]
|
||||
(tg/map kg vg opts)))
|
||||
|
||||
(defn elements
|
||||
[s]
|
||||
@@ -119,6 +114,10 @@
|
||||
[f g]
|
||||
(tg/fmap f g))
|
||||
|
||||
(defn filter
|
||||
[pred gen]
|
||||
(tg/such-that pred gen 100))
|
||||
|
||||
(defn mcat
|
||||
[f g]
|
||||
(tg/bind g f))
|
||||
@@ -126,3 +125,22 @@
|
||||
(defn tuple
|
||||
[& opts]
|
||||
(apply tg/tuple opts))
|
||||
|
||||
(defn vector
|
||||
[& opts]
|
||||
(apply tg/vector opts))
|
||||
|
||||
(defn set
|
||||
[g]
|
||||
(tg/set g))
|
||||
|
||||
;; Static Generators
|
||||
|
||||
(def boolean tg/boolean)
|
||||
(def text (word-string))
|
||||
(def double (small-double))
|
||||
(def int (small-int))
|
||||
(def keyword (word-keyword))
|
||||
|
||||
(def any
|
||||
(tg/one-of [text boolean double int keyword]))
|
||||
|
||||
@@ -97,7 +97,8 @@
|
||||
(defmethod visit :enum [_ _ children options] (merge (some-> (m/-infer children) (transform* options)) {:enum children}))
|
||||
(defmethod visit :maybe [_ _ children _] {:oneOf (conj children {:type "null"})})
|
||||
(defmethod visit :tuple [_ _ children _] {:type "array", :items children, :additionalItems false})
|
||||
(defmethod visit :re [_ schema _ options] {:type "string", :pattern (first (m/children schema options))})
|
||||
(defmethod visit :re [_ schema _ options]
|
||||
{:type "string", :pattern (str (first (m/children schema options)))})
|
||||
(defmethod visit :nil [_ _ _ _] {:type "null"})
|
||||
|
||||
(defmethod visit :string [_ schema _ _]
|
||||
|
||||
@@ -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)))
|
||||
|
||||
|
||||
@@ -40,76 +40,3 @@
|
||||
(map (fn [segment]
|
||||
(.toPersistentMap ^js segment)))
|
||||
(parser/parse path-str)))))
|
||||
|
||||
#?(:cljs
|
||||
(defn content->buffer
|
||||
"Converts the path content into binary format."
|
||||
[content]
|
||||
(let [total (count content)
|
||||
ssize 28
|
||||
buffer (new js/ArrayBuffer (* total ssize))
|
||||
dview (new js/DataView buffer)]
|
||||
(loop [index 0]
|
||||
(when (< index total)
|
||||
(let [segment (nth content index)
|
||||
offset (* index ssize)]
|
||||
(case (:command segment)
|
||||
:move-to
|
||||
(let [{:keys [x y]} (:params segment)]
|
||||
(.setInt16 dview (+ offset 0) 1)
|
||||
(.setFloat32 dview (+ offset 20) x)
|
||||
(.setFloat32 dview (+ offset 24) y))
|
||||
:line-to
|
||||
(let [{:keys [x y]} (:params segment)]
|
||||
(.setInt16 dview (+ offset 0) 2)
|
||||
(.setFloat32 dview (+ offset 20) x)
|
||||
(.setFloat32 dview (+ offset 24) y))
|
||||
:curve-to
|
||||
(let [{:keys [c1x c1y c2x c2y x y]} (:params segment)]
|
||||
(.setInt16 dview (+ offset 0) 3)
|
||||
(.setFloat32 dview (+ offset 4) c1x)
|
||||
(.setFloat32 dview (+ offset 8) c1y)
|
||||
(.setFloat32 dview (+ offset 12) c2x)
|
||||
(.setFloat32 dview (+ offset 16) c2y)
|
||||
(.setFloat32 dview (+ offset 20) x)
|
||||
(.setFloat32 dview (+ offset 24) y))
|
||||
|
||||
:close-path
|
||||
(.setInt16 dview (+ offset 0) 4))
|
||||
(recur (inc index)))))
|
||||
buffer)))
|
||||
|
||||
#?(:cljs
|
||||
(defn buffer->content
|
||||
"Converts the a buffer to a path content vector"
|
||||
[buffer]
|
||||
(assert (instance? js/ArrayBuffer buffer) "expected ArrayBuffer instance")
|
||||
(let [ssize 28
|
||||
total (/ (.-byteLength buffer) ssize)
|
||||
dview (new js/DataView buffer)]
|
||||
(loop [index 0
|
||||
result []]
|
||||
(if (< index total)
|
||||
(let [offset (* index ssize)
|
||||
type (.getInt16 dview (+ offset 0))
|
||||
command (case type
|
||||
1 :move-to
|
||||
2 :line-to
|
||||
3 :curve-to
|
||||
4 :close-path)
|
||||
params (case type
|
||||
1 {:x (.getFloat32 dview (+ offset 20))
|
||||
:y (.getFloat32 dview (+ offset 24))}
|
||||
2 {:x (.getFloat32 dview (+ offset 20))
|
||||
:y (.getFloat32 dview (+ offset 24))}
|
||||
3 {:c1x (.getFloat32 dview (+ offset 4))
|
||||
:c1y (.getFloat32 dview (+ offset 8))
|
||||
:c2x (.getFloat32 dview (+ offset 12))
|
||||
:c2y (.getFloat32 dview (+ offset 16))
|
||||
:x (.getFloat32 dview (+ offset 20))
|
||||
:y (.getFloat32 dview (+ offset 24))}
|
||||
4 {})]
|
||||
(recur (inc index)
|
||||
(conj result {:command command
|
||||
:params params})))
|
||||
result)))))
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.math :as mth]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.subpath :as ups]))
|
||||
|
||||
@@ -233,6 +234,45 @@
|
||||
(gsp/command->point current)
|
||||
(conj result (dissoc current :prev)))))))
|
||||
|
||||
(defn remove-duplicated-segments
|
||||
"Remove from the content segments"
|
||||
[content]
|
||||
(letfn [;; This is a comparator for float points with a precission
|
||||
;; used to remove already existing segments
|
||||
(comparator [[fx1 fy1 tx1 ty1 :as v1] [fx2 fy2 tx2 ty2 :as v2]]
|
||||
(if (and (mth/close? tx1 tx2)
|
||||
(mth/close? ty1 ty2)
|
||||
(mth/close? fx1 fx2)
|
||||
(mth/close? fy1 fy2))
|
||||
0 ;; equal
|
||||
(compare v1 v2)))]
|
||||
|
||||
(loop [current (first content)
|
||||
content (rest content)
|
||||
segments (sorted-set-by comparator)
|
||||
result []]
|
||||
|
||||
(if (nil? current)
|
||||
result
|
||||
|
||||
(let [fx (-> current :prev :x)
|
||||
fy (-> current :prev :y)
|
||||
tx (-> current :params :x)
|
||||
ty (-> current :params :y)
|
||||
|
||||
result
|
||||
(cond-> result
|
||||
(and (not (contains? segments [fx fy tx ty]))
|
||||
(not (contains? segments [tx ty fx fy])))
|
||||
(conj current))
|
||||
|
||||
segments (conj segments [fx fy tx ty])]
|
||||
|
||||
(recur (first content)
|
||||
(rest content)
|
||||
segments
|
||||
result))))))
|
||||
|
||||
(defn create-union [content-a content-a-split content-b content-b-split sr-a sr-b]
|
||||
;; Pick all segments in content-a that are not inside content-b
|
||||
;; Pick all segments in content-b that are not inside content-a
|
||||
@@ -312,15 +352,17 @@
|
||||
content-a-split (->> content-a-split add-previous (filter is-segment?))
|
||||
content-b-split (->> content-b-split add-previous (filter is-segment?))
|
||||
|
||||
bool-content
|
||||
content
|
||||
(case bool-type
|
||||
:union (create-union content-a content-a-split content-b content-b-split sr-a sr-b)
|
||||
:difference (create-difference content-a content-a-split content-b content-b-split sr-a sr-b)
|
||||
:intersection (create-intersection content-a content-a-split content-b content-b-split sr-a sr-b)
|
||||
:exclude (create-exclusion content-a-split content-b-split))]
|
||||
|
||||
(->> (fix-move-to bool-content)
|
||||
(ups/close-subpaths))))
|
||||
(-> content
|
||||
remove-duplicated-segments
|
||||
fix-move-to
|
||||
ups/close-subpaths)))
|
||||
|
||||
(defn content-bool
|
||||
[bool-type contents]
|
||||
@@ -331,4 +373,3 @@
|
||||
(reduce (partial content-bool-pair bool-type))
|
||||
(into []))
|
||||
[]))
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
(pt= (:from subpath) (:to subpath)))
|
||||
|
||||
(defn close-subpaths
|
||||
"Searches a path for possible supaths that can create closed loops and merge them"
|
||||
"Searches a path for possible subpaths that can create closed loops and merge them"
|
||||
[content]
|
||||
(let [subpaths (get-subpaths content)
|
||||
closed-subpaths
|
||||
|
||||
@@ -435,16 +435,12 @@
|
||||
|
||||
attrs
|
||||
(-> attrs
|
||||
(cond-> linecap
|
||||
(dissoc :strokeLinecap))
|
||||
(cond-> (some? color)
|
||||
(dissoc :stroke :strokeWidth :strokeOpacity))
|
||||
(update
|
||||
:style
|
||||
(fn [style]
|
||||
(-> style
|
||||
(cond-> linecap
|
||||
(dissoc :strokeLinecap))
|
||||
(cond-> (some? color)
|
||||
(dissoc :stroke :strokeWidth :strokeOpacity)))))
|
||||
(d/without-nils))]
|
||||
@@ -461,12 +457,14 @@
|
||||
|
||||
(and (some? linecap) (cfh/path-shape? shape)
|
||||
(or (= linecap :round) (= linecap :square)))
|
||||
|
||||
(assoc :stroke-cap-start linecap
|
||||
:stroke-cap-end linecap)
|
||||
:stroke-cap-end linecap
|
||||
:stroke-linecap linecap)
|
||||
|
||||
(d/any-key? (dm/get-in shape [:strokes 0])
|
||||
:strokeColor :strokeOpacity :strokeWidth
|
||||
:strokeCapStart :strokeCapEnd)
|
||||
:strokeLinecap :strokeCapStart :strokeCapEnd)
|
||||
(assoc-in [:strokes 0 :stroke-style] :svg))))
|
||||
|
||||
(defn setup-opacity [shape]
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"Need that root is already a frame"
|
||||
(cfh/frame-shape? root))
|
||||
|
||||
(let [[_new-root _new-shapes updated-shapes]
|
||||
(let [[_new-root updated-shapes]
|
||||
(ctn/convert-shape-in-component root (:objects page) (:id file))
|
||||
|
||||
updated-root (first updated-shapes) ; Can't use new-root because it has a new id
|
||||
@@ -54,8 +54,7 @@
|
||||
:name name
|
||||
:path path
|
||||
:main-instance-id (:id updated-root)
|
||||
:main-instance-page (:id page)
|
||||
:shapes updated-shapes))))))))
|
||||
:main-instance-page (:id page)))))))))
|
||||
|
||||
(defn update-component
|
||||
[file component-label & {:keys [] :as params}]
|
||||
@@ -98,7 +97,6 @@
|
||||
component
|
||||
(:data library)
|
||||
(gpt/point 100 100)
|
||||
true
|
||||
{:force-id (thi/new-id! copy-root-label)
|
||||
:force-frame-id frame-id})
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
& {:keys [component-params root-params child-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:root-label} [:name Frame1] # [Component :component-label]
|
||||
;; :child-label [:name Rect1]
|
||||
;; :child-label [:name Rect1]
|
||||
(-> file
|
||||
(add-frame-with-child root-label child-label :frame-params root-params :child-params child-params)
|
||||
(thc/make-component component-label root-label component-params)))
|
||||
@@ -95,7 +95,7 @@
|
||||
& {:keys [component-params main-root-params main-child-params copy-root-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:main-root-label} [:name Frame1] # [Component :component-label]
|
||||
;; :main-child-label [:name Rect1]
|
||||
;; :main-child-label [:name Rect1]
|
||||
;;
|
||||
;; :copy-root-label [:name Frame1] #--> [Component :component-label] :main-root-label
|
||||
;; <no-label> [:name Rect1] ---> :main-child-label
|
||||
@@ -113,9 +113,9 @@
|
||||
& {:keys [component-params root-params child-params-list]}]
|
||||
;; Generated shape tree:
|
||||
;; {:root-label} [:name Frame1] # [Component :component-label]
|
||||
;; :child1-label [:name Rect1]
|
||||
;; :child2-label [:name Rect2]
|
||||
;; :child3-label [:name Rect3]
|
||||
;; :child1-label [:name Rect1]
|
||||
;; :child2-label [:name Rect2]
|
||||
;; :child3-label [:name Rect3]
|
||||
(as-> file $
|
||||
(add-frame $ root-label root-params)
|
||||
(reduce (fn [file [index [label params]]]
|
||||
@@ -134,9 +134,9 @@
|
||||
& {:keys [component-params main-root-params main-child-params-list copy-root-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:root-label} [:name Frame1] # [Component :component-label]
|
||||
;; :child1-label [:name Rect1]
|
||||
;; :child2-label [:name Rect2]
|
||||
;; :child3-label [:name Rect3]
|
||||
;; :child1-label [:name Rect1]
|
||||
;; :child2-label [:name Rect2]
|
||||
;; :child3-label [:name Rect3]
|
||||
;;
|
||||
;; :copy-root-label [:name Frame1] #--> [Component :component-label] :root-label
|
||||
;; <no-label> [:name Rect1] ---> :child1-label
|
||||
@@ -156,7 +156,7 @@
|
||||
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
|
||||
;; :nested-head-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
|
||||
@@ -183,7 +183,7 @@
|
||||
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params copy2-root-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
|
||||
;; :nested-head-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
|
||||
@@ -336,8 +336,7 @@
|
||||
file
|
||||
{file-id file}
|
||||
(ctn/make-container container :page)
|
||||
(:id shape)
|
||||
true))
|
||||
(:id shape)))
|
||||
file' (thf/apply-changes file changes)]
|
||||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
@@ -361,7 +360,7 @@
|
||||
(:objects page)
|
||||
#{(-> (ths/get-shape file shape-tag :page-label page-label)
|
||||
:id)}
|
||||
{:components-v2 true})
|
||||
{})
|
||||
file' (thf/apply-changes file changes)]
|
||||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
@@ -380,7 +379,7 @@
|
||||
(gpt/point 0 0) ;; delta
|
||||
{(:id file) file} ;; libraries
|
||||
(:data file) ;; library-data
|
||||
(:id file)) ;; file-id
|
||||
(:id file)) ;; file-id
|
||||
(cll/generate-duplicate-changes-update-indices (:objects page) ;; objects
|
||||
#{(:id shape)}))
|
||||
file' (thf/apply-changes file changes)]
|
||||
|
||||
@@ -22,4 +22,18 @@
|
||||
(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"}]}))))
|
||||
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value2"}]}))))
|
||||
|
||||
(defn add-variant-two-properties
|
||||
[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 "p1v2, p2v2")
|
||||
(ths/add-sample-shape root1-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "p1v1, p2v1")
|
||||
(thc/make-component component1-label root1-label)
|
||||
(thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "p1v1"} {:name "Property2" :value "p2v1"}]})
|
||||
(thc/make-component component2-label root2-label)
|
||||
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "p1v2"} {:name "Property2" :value "p2v2"}]}))))
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
(.. r (toString 16) (padStart 2 "0"))
|
||||
(.. g (toString 16) (padStart 2 "0"))
|
||||
(.. b (toString 16) (padStart 2 "0"))))))
|
||||
sg/any))
|
||||
sg/int))
|
||||
|
||||
(defn rgb-color-string?
|
||||
[o]
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.plugins :as ctpg]
|
||||
[app.common.types.variant :as ctv]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -17,15 +18,17 @@
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def schema:component
|
||||
[:map
|
||||
[:id ::sm/uuid]
|
||||
[:name :string]
|
||||
[:path {:optional true} [:maybe :string]]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:objects {:gen/max 10 :optional true} ::ctp/objects]
|
||||
[:main-instance-id ::sm/uuid]
|
||||
[:main-instance-page ::sm/uuid]
|
||||
[:plugin-data {:optional true} ::ctpg/plugin-data]])
|
||||
[:merge
|
||||
[:map
|
||||
[:id ::sm/uuid]
|
||||
[:name :string]
|
||||
[:path {:optional true} [:maybe :string]]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:objects {:gen/max 10 :optional true} ::ctp/objects]
|
||||
[:main-instance-id ::sm/uuid]
|
||||
[:main-instance-page ::sm/uuid]
|
||||
[:plugin-data {:optional true} ::ctpg/plugin-data]]
|
||||
::ctv/variant-component])
|
||||
|
||||
(sm/register! ::component schema:component)
|
||||
|
||||
@@ -90,8 +93,8 @@
|
||||
:constraints-h :constraints-group
|
||||
:constraints-v :constraints-group
|
||||
:fixed-scroll :constraints-group
|
||||
:bool-type :bool-group
|
||||
:bool-content :bool-group
|
||||
:bool-type :content-group
|
||||
:bool-content :content-group
|
||||
:exports :exports-group
|
||||
:grids :grids-group
|
||||
|
||||
@@ -179,10 +182,8 @@
|
||||
(= (:component-file shape) file-id)))
|
||||
|
||||
(defn is-main-of?
|
||||
[shape-main shape-inst components-v2]
|
||||
(or (= (:shape-ref shape-inst) (:id shape-main))
|
||||
(and (= (:shape-ref shape-inst) (:shape-ref shape-main))
|
||||
(not components-v2))))
|
||||
[shape-main shape-inst]
|
||||
(= (:shape-ref shape-inst) (:id shape-main)))
|
||||
|
||||
(defn main-instance?
|
||||
"Check if this shape is the root of the main instance of some
|
||||
@@ -333,8 +334,6 @@
|
||||
(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))))))
|
||||
|
||||
@@ -34,20 +34,12 @@
|
||||
(assoc component :modified-at (dt/now)))
|
||||
|
||||
(defn add-component
|
||||
[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)
|
||||
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]
|
||||
(->> shapes
|
||||
(d/index-by :id)
|
||||
(wrap-object-fn)))))))
|
||||
[fdata {:keys [id name path main-instance-id main-instance-page annotation variant-id variant-properties]}]
|
||||
(let [fdata (update fdata :components assoc id (touch {:id id :name name :path path}))]
|
||||
(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)
|
||||
variant-id (update-in [:components id] assoc :variant-id variant-id)
|
||||
variant-properties (update-in [:components id] assoc :variant-properties variant-properties))))
|
||||
|
||||
(defn mod-component
|
||||
[file-data {:keys [id name path main-instance-id main-instance-page objects annotation variant-id variant-properties modified-at]}]
|
||||
@@ -119,7 +111,6 @@
|
||||
[file-data component-id f & args]
|
||||
(d/update-in-when file-data [:components component-id] #(-> (apply f % args)
|
||||
(touch))))
|
||||
|
||||
(defn set-component-modified
|
||||
[file-data component-id]
|
||||
(update-component file-data component-id identity))
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
[:map-of {:gen/max 10} ::sm/uuid :map]]
|
||||
[:plugin-data {:optional true} ::ctpg/plugin-data]])
|
||||
|
||||
(def check-container!
|
||||
(def check-container
|
||||
(sm/check-fn ::container))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -74,13 +74,9 @@
|
||||
(defn get-shape
|
||||
[container shape-id]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid container"
|
||||
(check-container! container))
|
||||
|
||||
(dm/assert!
|
||||
"expected valid uuid for `shape-id`"
|
||||
(uuid? shape-id))
|
||||
(assert (check-container container))
|
||||
(assert (uuid? shape-id)
|
||||
"expected valid uuid for `shape-id`")
|
||||
|
||||
(-> container
|
||||
(get :objects)
|
||||
@@ -267,67 +263,8 @@
|
||||
new-children (->> (cfh/get-children objects (:id root))
|
||||
(map #(dissoc % :component-root)))]
|
||||
[(assoc new-root :id new-id)
|
||||
nil
|
||||
(into [new-root] new-children)]))
|
||||
|
||||
(defn make-component-shape ;; Only used for components v1
|
||||
"Clone the shape and all children. Generate new ids and detach
|
||||
from parent and frame. Update the original shapes to have links
|
||||
to the new ones."
|
||||
[shape objects file-id components-v2]
|
||||
(assert (nil? (:component-id shape)))
|
||||
(assert (nil? (:component-file shape)))
|
||||
(assert (nil? (:shape-ref shape)))
|
||||
(let [frame-ids-map (volatile! {})
|
||||
|
||||
;; Ensure that the component root is not an instance
|
||||
update-new-shape (fn [new-shape original-shape]
|
||||
(when (= (:type original-shape) :frame)
|
||||
(vswap! frame-ids-map assoc (:id original-shape) (:id new-shape)))
|
||||
|
||||
(cond-> new-shape
|
||||
true
|
||||
(dissoc :component-root)
|
||||
|
||||
(nil? (:parent-id new-shape))
|
||||
(dissoc :component-id
|
||||
:component-file
|
||||
:shape-ref)))
|
||||
|
||||
;; Make the original shape an instance of the new component.
|
||||
;; If one of the original shape children already was a component
|
||||
;; instance, maintain this instanceness untouched.
|
||||
update-original-shape (fn [original-shape new-shape]
|
||||
(cond-> original-shape
|
||||
(nil? (:shape-ref original-shape))
|
||||
(-> (assoc :shape-ref (:id new-shape))
|
||||
(dissoc :touched))
|
||||
|
||||
(nil? (:parent-id new-shape))
|
||||
(assoc :component-id (:id new-shape)
|
||||
:component-file file-id
|
||||
:component-root true)
|
||||
|
||||
(and (nil? (:parent-id new-shape)) components-v2)
|
||||
(assoc :main-instance true)
|
||||
|
||||
(some? (:parent-id new-shape))
|
||||
(dissoc :component-root)))
|
||||
|
||||
[new-root-shape new-shapes updated-shapes]
|
||||
(ctst/clone-shape shape
|
||||
nil
|
||||
objects
|
||||
:update-new-shape update-new-shape
|
||||
:update-original-shape update-original-shape)
|
||||
|
||||
;; If frame-id points to a shape inside the component, remap it to the
|
||||
;; corresponding new frame shape. If not, set it to nil.
|
||||
remap-frame-id (fn [shape]
|
||||
(update shape :frame-id #(get @frame-ids-map % nil)))]
|
||||
|
||||
[new-root-shape (map remap-frame-id new-shapes) updated-shapes]))
|
||||
|
||||
(defn remove-swap-keep-attrs
|
||||
"Remove flex children properties except the fit-content for flex layouts. These are properties
|
||||
that we don't have to propagate to copies but will be respected when swapping components"
|
||||
@@ -352,20 +289,18 @@
|
||||
WARNING: This process does not remap media references (on fills, strokes, ...); that is
|
||||
delegated to an async process on the backend side that checks unreferenced shapes and
|
||||
automatically creates correct references."
|
||||
([page component library-data position components-v2]
|
||||
(make-component-instance page component library-data position components-v2 {}))
|
||||
([page component library-data position components-v2
|
||||
([page component library-data position]
|
||||
(make-component-instance page component library-data position {}))
|
||||
([page component library-data position
|
||||
{:keys [main-instance? force-id force-frame-id keep-ids?]
|
||||
:or {main-instance? false force-id nil force-frame-id nil keep-ids? false}}]
|
||||
(let [component-page (when components-v2
|
||||
(ctpl/get-page library-data (:main-instance-page component)))
|
||||
(let [component-page (ctpl/get-page library-data (:main-instance-page component))
|
||||
|
||||
component-shape (-> (get-shape component-page (:main-instance-id component))
|
||||
(assoc :parent-id nil) ;; On v2 we force parent-id to nil in order to behave like v1
|
||||
(assoc :frame-id uuid/zero)
|
||||
(remove-swap-keep-attrs))
|
||||
|
||||
component-shape (if components-v2
|
||||
(-> (get-shape component-page (:main-instance-id component))
|
||||
(assoc :parent-id nil) ;; On v2 we force parent-id to nil in order to behave like v1
|
||||
(assoc :frame-id uuid/zero)
|
||||
(remove-swap-keep-attrs))
|
||||
(get-shape component (:id component)))
|
||||
|
||||
orig-pos (gpt/point (:x component-shape) (:y component-shape))
|
||||
delta (gpt/subtract position orig-pos)
|
||||
@@ -395,8 +330,7 @@
|
||||
update-new-shape
|
||||
(fn [new-shape original-shape]
|
||||
(let [new-name (:name new-shape)
|
||||
root? (or (ctk/instance-root? original-shape) ; If shape is inside a component (not components-v2)
|
||||
(nil? (:parent-id original-shape)))] ; we detect it by having no parent)
|
||||
root? (ctk/instance-root? original-shape)]
|
||||
|
||||
(when root?
|
||||
(vswap! unames conj new-name))
|
||||
@@ -417,10 +351,8 @@
|
||||
main-instance?
|
||||
(dissoc :shape-ref)
|
||||
|
||||
(and (not main-instance?)
|
||||
(or components-v2 ; In v1, shape-ref points to the remote instance
|
||||
(nil? (:shape-ref original-shape)))) ; in v2, shape-ref points to the near instance
|
||||
(assoc :shape-ref (:id original-shape))
|
||||
(not main-instance?)
|
||||
(assoc :shape-ref (:id original-shape)) ; shape-ref points to the near instance
|
||||
|
||||
(nil? (:parent-id original-shape))
|
||||
(assoc :component-id (:id component)
|
||||
@@ -428,14 +360,14 @@
|
||||
:component-root true
|
||||
:name new-name)
|
||||
|
||||
(or (some? (:parent-id original-shape)) ; On v2 we have removed the parent-id for component roots (see above)
|
||||
(or (some? (:parent-id original-shape)) ; On v2 we have removed the parent-id for component roots
|
||||
(some? component-frame))
|
||||
(dissoc :component-root))))
|
||||
|
||||
[new-shape new-shapes _]
|
||||
(ctst/clone-shape component-shape
|
||||
frame-id
|
||||
(if components-v2 (:objects component-page) (:objects component))
|
||||
(:objects component-page)
|
||||
:update-new-shape update-new-shape
|
||||
:force-id force-id
|
||||
:keep-ids? keep-ids?
|
||||
@@ -543,14 +475,41 @@
|
||||
;; We can always move the children to the parent they already have.
|
||||
;; But if we are pasting, those are new items, so it is considered a change
|
||||
no-changes?
|
||||
(and (->> children (every? #(= parent-id (:parent-id %))))
|
||||
(and (every? #(= parent-id (:parent-id %)) children)
|
||||
(not pasting?))
|
||||
|
||||
;; When pasting frames, children have the frames and their children
|
||||
;; We need to check only the top shapes
|
||||
children-ids (set (map :id children))
|
||||
top-children (remove #(contains? children-ids (:parent-id %)) children)
|
||||
|
||||
;; Are all the top-children a main-instance of a component?
|
||||
all-main?
|
||||
(->> children (every? #(ctk/main-instance? %)))]
|
||||
(every? ctk/main-instance? top-children)
|
||||
|
||||
any-main-descendant
|
||||
(some
|
||||
(fn [shape]
|
||||
(some ctk/main-instance? (cfh/get-children-with-self objects (:id shape))))
|
||||
children)
|
||||
|
||||
;; Are all the top-children a main-instance of a cutted component?
|
||||
all-comp-cut?
|
||||
(when all-main?
|
||||
(->> top-children
|
||||
(map #(ctkl/get-component (dm/get-in libraries [(:component-file %) :data])
|
||||
(:component-id %)
|
||||
true))
|
||||
(every? :deleted)))]
|
||||
(if (or no-changes?
|
||||
(and (not (invalid-structure-for-component? objects parent children pasting? libraries))
|
||||
;; If we are moving into a main component, no descendant can be main
|
||||
(or (nil? any-main-descendant) (not (ctk/main-instance? parent)))
|
||||
;; If we are moving into a variant-container, all the items should be main
|
||||
(or all-main? (not (ctk/is-variant-container? parent)))))
|
||||
;; so if we are pasting, only allow main instances that are cut-and-pasted
|
||||
(or (not (ctk/is-variant-container? parent))
|
||||
(and (not pasting?) all-main?)
|
||||
all-comp-cut?)))
|
||||
[parent-id (get-frame parent-id)]
|
||||
(recur (:parent-id parent) objects children pasting? libraries))))))
|
||||
|
||||
@@ -593,8 +552,7 @@
|
||||
|
||||
;; TODO: the check of :width and :height probably may be
|
||||
;; removed after the check added in
|
||||
;; data/workspace/modifiers/check-delta function. Better check
|
||||
;; it and test toroughly when activating components-v2 mode.
|
||||
;; data/workspace/modifiers/check-delta function.
|
||||
in-copy?
|
||||
(ctk/in-component-copy? shape)
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
(some? page-id)
|
||||
(ctpl/add-page page)
|
||||
|
||||
(contains? cfeat/*current* "components/v2")
|
||||
:always
|
||||
(assoc-in [:options :components-v2] true)))))
|
||||
|
||||
(defn make-file
|
||||
@@ -221,48 +221,45 @@
|
||||
(ctpl/get-page file-data (:main-instance-page component)))
|
||||
|
||||
(defn get-component-container
|
||||
"Retrieve the container that holds the component shapes (the page in components-v2
|
||||
or the component itself in v1 or deleted component)."
|
||||
"Retrieve the container that holds the component shapes (the page
|
||||
or the component itself on deleted component)."
|
||||
[file-data component]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
|
||||
(if (and components-v2 (not (:deleted component)))
|
||||
(let [component-page (get-component-page file-data component)]
|
||||
(cfh/make-container component-page :page))
|
||||
(cfh/make-container component :component))))
|
||||
(if (not (:deleted component))
|
||||
(let [component-page (get-component-page file-data component)]
|
||||
(cfh/make-container component-page :page))
|
||||
(cfh/make-container component :component)))
|
||||
|
||||
(defn get-component-root
|
||||
"Retrieve the root shape of the component."
|
||||
[file-data component]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
|
||||
(if (and components-v2 (not (:deleted component)))
|
||||
(-> file-data
|
||||
(get-component-page component)
|
||||
(ctn/get-shape (:main-instance-id component)))
|
||||
(ctk/get-component-root component))))
|
||||
(if (not (:deleted component))
|
||||
(-> file-data
|
||||
(get-component-page component)
|
||||
(ctn/get-shape (:main-instance-id component)))
|
||||
(ctk/get-component-root component)))
|
||||
|
||||
(defn get-component-shape
|
||||
"Retrieve one shape in the component by id. If with-context? is true, add the
|
||||
file and container where the shape resides in its metadata."
|
||||
[file-data component shape-id & {:keys [with-context?] :or {with-context? false}}]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
|
||||
(if (and components-v2 (not (:deleted component)))
|
||||
(let [component-page (get-component-page file-data component)]
|
||||
(when component-page
|
||||
(let [child (cfh/get-child (:objects component-page)
|
||||
(:main-instance-id component)
|
||||
shape-id)]
|
||||
(cond-> child
|
||||
(and child with-context?)
|
||||
(with-meta {:file {:id (:id file-data)
|
||||
:data file-data}
|
||||
:container (ctn/make-container component-page :page)})))))
|
||||
(if (not (:deleted component))
|
||||
(let [component-page (get-component-page file-data component)]
|
||||
(when component-page
|
||||
(let [child (cfh/get-child (:objects component-page)
|
||||
(:main-instance-id component)
|
||||
shape-id)]
|
||||
(cond-> child
|
||||
(and child with-context?)
|
||||
(with-meta {:file {:id (:id file-data)
|
||||
:data file-data}
|
||||
:container (ctn/make-container component-page :page)})))))
|
||||
|
||||
(let [shape (dm/get-in component [:objects shape-id])]
|
||||
(cond-> shape
|
||||
(and shape with-context?)
|
||||
(with-meta {:file {:id (:id file-data)
|
||||
:data file-data}
|
||||
:container (ctn/make-container component :component)}))))))
|
||||
(let [shape (dm/get-in component [:objects shape-id])]
|
||||
(cond-> shape
|
||||
(and shape with-context?)
|
||||
(with-meta {:file {:id (:id file-data)
|
||||
:data file-data}
|
||||
:container (ctn/make-container component :component)})))))
|
||||
|
||||
(defn get-ref-shape
|
||||
"Retrieve the shape in the component that is referenced by the instance shape."
|
||||
@@ -384,12 +381,11 @@
|
||||
(defn get-component-shapes
|
||||
"Retrieve all shapes of the component"
|
||||
[file-data component]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
|
||||
(if (and components-v2
|
||||
(not (:deleted component))) ;; the deleted components have its children in the :objects property
|
||||
(let [instance-page (get-component-page file-data component)]
|
||||
(cfh/get-children-with-self (:objects instance-page) (:main-instance-id component)))
|
||||
(vals (:objects component)))))
|
||||
|
||||
(if (not (:deleted component)) ;; the deleted components have its children in the :objects property
|
||||
(let [instance-page (get-component-page file-data component)]
|
||||
(cfh/get-children-with-self (:objects instance-page) (:main-instance-id component)))
|
||||
(vals (:objects component))))
|
||||
|
||||
;; Return true if the object is a component that exists on the file or its libraries (even a deleted one)
|
||||
(defn is-main-of-known-component?
|
||||
@@ -403,44 +399,52 @@
|
||||
|
||||
(defn load-component-objects
|
||||
"Add an :objects property to the component, with only the shapes that belong to it"
|
||||
[file-data component]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
|
||||
(if (and components-v2 component (empty? (:objects component))) ;; This operation may be called twice, e.g. in an idempotent change
|
||||
(let [component-page (get-component-page file-data component)
|
||||
page-objects (:objects component-page)
|
||||
objects (->> (cons (:main-instance-id component)
|
||||
(cfh/get-children-ids page-objects (:main-instance-id component)))
|
||||
(map #(get page-objects %))
|
||||
(d/index-by :id))]
|
||||
(assoc component :objects objects))
|
||||
component)))
|
||||
([file-data component]
|
||||
(load-component-objects file-data component (gpt/point 0 0)))
|
||||
([file-data component delta]
|
||||
(if (and component (empty? (:objects component))) ;; This operation may be called twice, e.g. in an idempotent change
|
||||
(let [component-page (get-component-page file-data component)
|
||||
page-objects (:objects component-page)
|
||||
objects (->> (cons (:main-instance-id component)
|
||||
(cfh/get-children-ids page-objects (:main-instance-id component)))
|
||||
(map #(get page-objects %))
|
||||
;; when it is an undo of a cut-paste, we need to undo the movement
|
||||
;; of the shapes so we need to move them delta
|
||||
(map #(gsh/move % delta))
|
||||
(d/index-by :id))]
|
||||
(assoc component :objects objects))
|
||||
component)))
|
||||
|
||||
(defn delete-component
|
||||
"Mark a component as deleted and store the main instance shapes iside it, to
|
||||
be able to be recovered later."
|
||||
[file-data component-id skip-undelete? main-instance]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
|
||||
(if (or (not components-v2) skip-undelete?)
|
||||
[file-data component-id skip-undelete? delta]
|
||||
(let [delta (or delta (gpt/point 0 0))]
|
||||
(if skip-undelete?
|
||||
(ctkl/delete-component file-data component-id)
|
||||
(let [set-main-instance ;; If there is a saved main-instance, restore it. This happens on the restore-component action
|
||||
#(if main-instance
|
||||
(assoc-in % [:objects (:main-instance-id %)] main-instance)
|
||||
%)]
|
||||
(-> file-data
|
||||
(ctkl/update-component component-id (partial load-component-objects file-data))
|
||||
(ctkl/update-component component-id set-main-instance)
|
||||
(ctkl/mark-component-deleted component-id))))))
|
||||
(-> file-data
|
||||
(ctkl/update-component component-id #(load-component-objects file-data % delta))
|
||||
(ctkl/mark-component-deleted component-id)))))
|
||||
|
||||
(defn restore-component
|
||||
"Recover a deleted component and all its shapes and put all this again in place."
|
||||
[file-data component-id page-id]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])
|
||||
update-page? (and components-v2 (not (nil? page-id)))]
|
||||
(-> file-data
|
||||
(ctkl/update-component component-id #(dissoc % :objects))
|
||||
(ctkl/mark-component-undeleted component-id)
|
||||
(cond-> update-page?
|
||||
(ctkl/update-component component-id #(assoc % :main-instance-page page-id))))))
|
||||
(let [update-page? (not (nil? page-id))
|
||||
component (ctkl/get-component file-data component-id true)
|
||||
main-instance-page (or page-id (:main-instance-page component))
|
||||
main-instance (dm/get-in file-data [:pages-index main-instance-page
|
||||
:objects (:main-instance-id component)])]
|
||||
(cond-> file-data
|
||||
:always
|
||||
(->
|
||||
(ctkl/update-component component-id #(dissoc % :objects))
|
||||
(ctkl/mark-component-undeleted component-id))
|
||||
|
||||
update-page?
|
||||
(ctkl/update-component component-id #(assoc % :main-instance-page page-id))
|
||||
|
||||
(ctk/is-variant? component)
|
||||
(ctkl/update-component component-id #(assoc % :variant-id (:variant-id main-instance))))))
|
||||
|
||||
(defn purge-component
|
||||
"Remove permanently a component."
|
||||
@@ -557,7 +561,6 @@
|
||||
component
|
||||
library-data
|
||||
position
|
||||
(dm/get-in file-data [:options :components-v2])
|
||||
{:main-instance? true
|
||||
:keep-ids? true})
|
||||
|
||||
@@ -589,8 +592,7 @@
|
||||
:name (:name component)
|
||||
:path (:path component)
|
||||
:main-instance-id (:id main-instance-shape)
|
||||
:main-instance-page page-id
|
||||
:shapes (get-component-shapes library-data component)}))
|
||||
:main-instance-page page-id}))
|
||||
|
||||
; Change all existing instances to point to the local file
|
||||
remap-instances
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.types.shape.text :as ctsx]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]))
|
||||
|
||||
@@ -211,7 +212,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]]
|
||||
@@ -233,7 +234,7 @@
|
||||
[:map {:title "BoolAttrs"}
|
||||
[:shapes [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]
|
||||
[:bool-type [::sm/one-of bool-types]]
|
||||
[:bool-content ::ctsp/content]])
|
||||
[:content ::ctsp/content]])
|
||||
|
||||
(def ^:private schema:rect-attrs
|
||||
[:map {:title "RectAttrs"}])
|
||||
@@ -317,7 +318,9 @@
|
||||
schema:frame-attrs
|
||||
schema:shape-attrs
|
||||
schema:shape-geom-attrs
|
||||
schema:shape-base-attrs]]
|
||||
schema:shape-base-attrs
|
||||
::ctv/variant-shape
|
||||
::ctv/variant-container]]
|
||||
|
||||
[:bool
|
||||
[:merge {:title "BoolShape"}
|
||||
|
||||
@@ -109,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
|
||||
@@ -126,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
|
||||
@@ -138,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
|
||||
@@ -146,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
|
||||
@@ -161,21 +175,21 @@
|
||||
[: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)
|
||||
|
||||
|
||||
@@ -6,7 +6,16 @@
|
||||
|
||||
(ns app.common.types.shape.path
|
||||
(:require
|
||||
[app.common.schema :as sm]))
|
||||
[app.common.schema :as sm])
|
||||
(:import
|
||||
#?(:cljs [goog.string StringBuffer]
|
||||
:clj [java.nio ByteBuffer])))
|
||||
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMA: PLAIN FORMAT
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def schema:line-to-segment
|
||||
[:map
|
||||
@@ -52,5 +61,371 @@
|
||||
(def schema:path-content
|
||||
[:vector schema:path-segment])
|
||||
|
||||
(def check-path-content
|
||||
(sm/check-fn schema:path-content))
|
||||
|
||||
(sm/register! ::segment schema:path-segment)
|
||||
(sm/register! ::content schema:path-content)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TYPE: PATH-DATA
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def ^:const SEGMENT-BYTE-SIZE 28)
|
||||
|
||||
(defprotocol IPathData
|
||||
(-write-to [_ buffer offset] "write the content to the specified buffer"))
|
||||
|
||||
(defrecord PathSegment [command params])
|
||||
|
||||
(defn- get-path-string
|
||||
"Format the path data structure to string"
|
||||
[buffer size]
|
||||
(let [builder #?(:clj (java.lang.StringBuilder. (int (* size 4)))
|
||||
:cljs (StringBuffer.))]
|
||||
(loop [index 0]
|
||||
(when (< index size)
|
||||
(let [offset (* index SEGMENT-BYTE-SIZE)
|
||||
type #?(:clj (.getShort ^ByteBuffer buffer offset)
|
||||
:cljs (.getInt16 buffer offset))]
|
||||
(case (long type)
|
||||
1 (let [x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
|
||||
:cljs (.getFloat32 buffer (+ offset 20)))
|
||||
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
|
||||
:cljs (.getFloat32 buffer (+ offset 24)))]
|
||||
(doto builder
|
||||
(.append "M")
|
||||
(.append x)
|
||||
(.append ",")
|
||||
(.append y)))
|
||||
2 (let [x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
|
||||
:cljs (.getFloat32 buffer (+ offset 20)))
|
||||
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
|
||||
:cljs (.getFloat32 buffer (+ offset 24)))]
|
||||
(doto builder
|
||||
(.append "L")
|
||||
(.append x)
|
||||
(.append ",")
|
||||
(.append y)))
|
||||
|
||||
3 (let [c1x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 4))
|
||||
:cljs (.getFloat32 buffer (+ offset 4)))
|
||||
c1y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 8))
|
||||
:cljs (.getFloat32 buffer (+ offset 8)))
|
||||
c2x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 12))
|
||||
:cljs (.getFloat32 buffer (+ offset 12)))
|
||||
c2y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 16))
|
||||
:cljs (.getFloat32 buffer (+ offset 16)))
|
||||
x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
|
||||
:cljs (.getFloat32 buffer (+ offset 20)))
|
||||
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
|
||||
:cljs (.getFloat32 buffer (+ offset 24)))]
|
||||
(doto builder
|
||||
(.append "C")
|
||||
(.append c1x)
|
||||
(.append ",")
|
||||
(.append c1y)
|
||||
(.append ",")
|
||||
(.append c2x)
|
||||
(.append ",")
|
||||
(.append c2y)
|
||||
(.append ",")
|
||||
(.append x)
|
||||
(.append ",")
|
||||
(.append y)))
|
||||
4 (doto builder
|
||||
(.append "Z")))
|
||||
(recur (inc index)))))
|
||||
|
||||
(.toString builder)))
|
||||
|
||||
(defn- read-segment
|
||||
[buffer index]
|
||||
(let [offset (* index SEGMENT-BYTE-SIZE)
|
||||
type #?(:clj (.getShort ^ByteBuffer buffer offset)
|
||||
:cljs (.getInt16 buffer offset))]
|
||||
(case (long type)
|
||||
1 (let [x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
|
||||
:cljs (.getFloat32 buffer (+ offset 20)))
|
||||
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
|
||||
:cljs (.getFloat32 buffer (+ offset 24)))]
|
||||
(->PathSegment :move-to {:x x :y y}))
|
||||
|
||||
2 (let [x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
|
||||
:cljs (.getFloat32 buffer (+ offset 20)))
|
||||
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
|
||||
:cljs (.getFloat32 buffer (+ offset 24)))]
|
||||
(->PathSegment :line-to {:x x :y y}))
|
||||
|
||||
3 (let [c1x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 4))
|
||||
:cljs (.getFloat32 buffer (+ offset 4)))
|
||||
c1y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 8))
|
||||
:cljs (.getFloat32 buffer (+ offset 8)))
|
||||
c2x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 12))
|
||||
:cljs (.getFloat32 buffer (+ offset 12)))
|
||||
c2y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 16))
|
||||
:cljs (.getFloat32 buffer (+ offset 16)))
|
||||
x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
|
||||
:cljs (.getFloat32 buffer (+ offset 20)))
|
||||
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
|
||||
:cljs (.getFloat32 buffer (+ offset 24)))]
|
||||
|
||||
(->PathSegment :curve-to {:x x :y y :c1x c1x :c1y c1y :c2x c2x :c2y c2y}))
|
||||
|
||||
4 (->PathSegment :close-path {}))))
|
||||
|
||||
(defn- in-range?
|
||||
[size i]
|
||||
(and (< i size) (>= i 0)))
|
||||
|
||||
#?(:clj
|
||||
(deftype PathData [size buffer]
|
||||
Object
|
||||
(toString [_]
|
||||
(get-path-string buffer size))
|
||||
|
||||
clojure.lang.Sequential
|
||||
clojure.lang.IPersistentCollection
|
||||
|
||||
(empty [_]
|
||||
(throw (ex-info "not implemented" {})))
|
||||
(equiv [_ other]
|
||||
(if (instance? PathData other)
|
||||
(.equals ^ByteBuffer buffer (.-buffer ^PathData other))
|
||||
false))
|
||||
|
||||
(seq [this]
|
||||
(when (pos? size)
|
||||
(->> (range size)
|
||||
(map (fn [i] (nth this i))))))
|
||||
|
||||
(cons [_ _val]
|
||||
(throw (ex-info "not implemented" {})))
|
||||
|
||||
clojure.lang.IReduceInit
|
||||
(reduce [_ f start]
|
||||
(loop [index 0
|
||||
result start]
|
||||
(if (< index size)
|
||||
(let [result (f result (read-segment buffer index))]
|
||||
(if (reduced? result)
|
||||
@result
|
||||
(recur (inc index) result)))
|
||||
result)))
|
||||
|
||||
clojure.lang.Indexed
|
||||
(nth [_ i]
|
||||
(if (in-range? size i)
|
||||
(read-segment buffer i)
|
||||
nil))
|
||||
|
||||
(nth [_ i default]
|
||||
(if (in-range? size i)
|
||||
(read-segment buffer i)
|
||||
default))
|
||||
|
||||
clojure.lang.Counted
|
||||
(count [_] size))
|
||||
|
||||
:cljs
|
||||
(deftype PathData [size buffer dview]
|
||||
Object
|
||||
(toString [_]
|
||||
(get-path-string dview size))
|
||||
|
||||
IPathData
|
||||
(-write-to [_ into-buffer offset]
|
||||
(assert (instance? js/ArrayBuffer into-buffer) "expected an instance of Uint32Array")
|
||||
(let [size (.-byteLength buffer)
|
||||
mem (js/Uint32Array. into-buffer offset size)]
|
||||
(.set mem (js/Uint32Array. buffer))))
|
||||
|
||||
cljs.core/ISequential
|
||||
cljs.core/IEquiv
|
||||
(-equiv [_ other]
|
||||
(if (instance? PathData other)
|
||||
(let [obuffer (.-buffer other)
|
||||
osize (.-byteLength obuffer)
|
||||
csize (.-byteLength buffer)]
|
||||
(if (= osize csize)
|
||||
(let [cb (js/Uint32Array. buffer)
|
||||
ob (js/Uint32Array. obuffer)]
|
||||
(loop [i 0]
|
||||
(if (< i osize)
|
||||
(if (= (aget ob i)
|
||||
(aget cb i))
|
||||
(recur (inc i))
|
||||
false)
|
||||
true)))
|
||||
false))
|
||||
false))
|
||||
|
||||
cljs.core/IReduce
|
||||
(-reduce [_ f]
|
||||
(loop [index 1
|
||||
result (if (pos? size)
|
||||
(read-segment dview 0)
|
||||
nil)]
|
||||
(if (< index size)
|
||||
(let [result (f result (read-segment dview index))]
|
||||
(if (reduced? result)
|
||||
@result
|
||||
(recur (inc index) result)))
|
||||
result)))
|
||||
|
||||
(-reduce [_ f start]
|
||||
(loop [index 0
|
||||
result start]
|
||||
(if (< index size)
|
||||
(let [result (f result (read-segment dview index))]
|
||||
(if (reduced? result)
|
||||
@result
|
||||
(recur (inc index) result)))
|
||||
result)))
|
||||
|
||||
cljs.core/IHash
|
||||
(-hash [_]
|
||||
(throw (ex-info "not-implemented" {})))
|
||||
|
||||
cljs.core/ICounted
|
||||
(-count [_] size)
|
||||
|
||||
cljs.core/IIndexed
|
||||
(-nth [_ i]
|
||||
(if (in-range? size i)
|
||||
(read-segment dview i)
|
||||
nil))
|
||||
|
||||
(-nth [_ i default]
|
||||
(if (in-range? i size)
|
||||
(read-segment dview i)
|
||||
default))
|
||||
|
||||
cljs.core/ISeqable
|
||||
(-seq [this]
|
||||
(when (pos? size)
|
||||
(->> (range size)
|
||||
(map (fn [i] (cljs.core/-nth this i))))))))
|
||||
|
||||
(defn- from-bytes
|
||||
[buffer]
|
||||
#?(:clj
|
||||
(cond
|
||||
(instance? ByteBuffer buffer)
|
||||
(let [size (.capacity ^ByteBuffer buffer)
|
||||
count (long (/ size SEGMENT-BYTE-SIZE))]
|
||||
(PathData. count buffer))
|
||||
|
||||
(bytes? buffer)
|
||||
(let [size (alength ^bytes buffer)
|
||||
count (long (/ size SEGMENT-BYTE-SIZE))]
|
||||
(PathData. count
|
||||
(ByteBuffer/wrap buffer)))
|
||||
|
||||
:else
|
||||
(throw (java.lang.IllegalArgumentException. "invalid data provided")))
|
||||
|
||||
:cljs
|
||||
(cond
|
||||
(instance? js/ArrayBuffer buffer)
|
||||
(let [size (.-byteLength buffer)
|
||||
count (long (/ size SEGMENT-BYTE-SIZE))]
|
||||
(PathData. count
|
||||
buffer
|
||||
(js/DataView. buffer)))
|
||||
|
||||
(instance? js/DataView buffer)
|
||||
(let [dview buffer
|
||||
buffer (.-buffer dview)
|
||||
size (.-byteLength buffer)
|
||||
count (long (/ size SEGMENT-BYTE-SIZE))]
|
||||
(PathData. count buffer dview))
|
||||
|
||||
:else
|
||||
(throw (js/Error. "invalid data provided")))))
|
||||
|
||||
;; FIXME: consider implementing with reduce
|
||||
;; FIXME: consider ensure fixed precision for avoid doing it on formatting
|
||||
|
||||
(defn- from-plain
|
||||
"Create a PathData instance from plain data structures"
|
||||
[content]
|
||||
(assert (check-path-content content))
|
||||
|
||||
(let [content (vec content)
|
||||
total (count content)
|
||||
#?@(:cljs [buffer (new js/ArrayBuffer (* total SEGMENT-BYTE-SIZE))
|
||||
dview (new js/DataView buffer)]
|
||||
:clj [buffer (ByteBuffer/allocate (* total SEGMENT-BYTE-SIZE))])]
|
||||
(loop [index 0]
|
||||
(when (< index total)
|
||||
(let [segment (nth content index)
|
||||
offset (* index SEGMENT-BYTE-SIZE)]
|
||||
(case (get segment :command)
|
||||
:move-to
|
||||
(let [params (get segment :params)
|
||||
x (float (get params :x))
|
||||
y (float (get params :y))]
|
||||
#?(:clj (.putShort buffer (int offset) (short 1))
|
||||
:cljs (.setInt16 dview offset 1))
|
||||
#?(:clj (.putFloat buffer (+ offset 20) x)
|
||||
:cljs (.setFloat32 dview (+ offset 20) x))
|
||||
#?(:clj (.putFloat buffer (+ offset 24) y)
|
||||
:cljs (.setFloat32 dview (+ offset 24) y)))
|
||||
|
||||
:line-to
|
||||
(let [params (get segment :params)
|
||||
x (float (get params :x))
|
||||
y (float (get params :y))]
|
||||
#?(:clj (.putShort buffer (int offset) (short 2))
|
||||
:cljs (.setInt16 dview offset 2))
|
||||
#?(:clj (.putFloat buffer (+ offset 20) x)
|
||||
:cljs (.setFloat32 dview (+ offset 20) x))
|
||||
#?(:clj (.putFloat buffer (+ offset 24) y)
|
||||
:cljs (.setFloat32 dview (+ offset 24) y)))
|
||||
|
||||
:curve-to
|
||||
(let [params (get segment :params)
|
||||
x (float (get params :x))
|
||||
y (float (get params :y))
|
||||
c1x (float (get params :c1x x))
|
||||
c1y (float (get params :c1y y))
|
||||
c2x (float (get params :c2x x))
|
||||
c2y (float (get params :c2y y))]
|
||||
|
||||
#?(:clj (.putShort buffer (int offset) (short 3))
|
||||
:cljs (.setInt16 dview offset 3))
|
||||
#?(:clj (.putFloat buffer (+ offset 4) c1x)
|
||||
:cljs (.setFloat32 dview (+ offset 4) c1x))
|
||||
#?(:clj (.putFloat buffer (+ offset 8) c1y)
|
||||
:cljs (.setFloat32 dview (+ offset 8) c1y))
|
||||
#?(:clj (.putFloat buffer (+ offset 12) c2x)
|
||||
:cljs (.setFloat32 dview (+ offset 12) c2x))
|
||||
#?(:clj (.putFloat buffer (+ offset 16) c2y)
|
||||
:cljs (.setFloat32 dview (+ offset 16) c2y))
|
||||
#?(:clj (.putFloat buffer (+ offset 20) x)
|
||||
:cljs (.setFloat32 dview (+ offset 20) x))
|
||||
#?(:clj (.putFloat buffer (+ offset 24) y)
|
||||
:cljs (.setFloat32 dview (+ offset 24) y)))
|
||||
|
||||
:close-path
|
||||
#?(:clj (.putShort buffer (int offset) (short 4))
|
||||
:cljs (.setInt16 dview offset 4)))
|
||||
(recur (inc index)))))
|
||||
|
||||
#?(:cljs (from-bytes dview)
|
||||
:clj (from-bytes buffer))))
|
||||
|
||||
(defn path-data
|
||||
"Create an instance of PathData, returns itself if it is already
|
||||
PathData instance"
|
||||
[data]
|
||||
(cond
|
||||
(instance? PathData data)
|
||||
data
|
||||
|
||||
(sequential? data)
|
||||
(from-plain data)
|
||||
|
||||
:else
|
||||
(from-bytes data)))
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
[:hidden :boolean]
|
||||
[:color ::ctc/color]])
|
||||
|
||||
(sm/register! ::shadow schema:shadow)
|
||||
|
||||
(def check-shadow
|
||||
(sm/check-fn schema:shadow))
|
||||
|
||||
(def valid-shadow?
|
||||
(sm/validator schema:shadow))
|
||||
|
||||
|
||||
@@ -16,54 +16,56 @@
|
||||
|
||||
(def node-types #{"root" "paragraph-set" "paragraph"})
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::content}
|
||||
[:map
|
||||
[:type [:= "root"]]
|
||||
[:key {:optional true} :string]
|
||||
[:children
|
||||
{:optional true}
|
||||
[:maybe
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:type [:= "paragraph-set"]]
|
||||
[:key {:optional true} :string]
|
||||
[:children
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:type [:= "paragraph"]]
|
||||
[:key {:optional true} :string]
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} ::shape/fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
|
||||
[:children
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:text :string]
|
||||
[:key {:optional true} :string]
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} ::shape/fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])
|
||||
(def schema:content
|
||||
[:map
|
||||
[:type [:= "root"]]
|
||||
[:key {:optional true} :string]
|
||||
[:children
|
||||
{:optional true}
|
||||
[:maybe
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:type [:= "paragraph-set"]]
|
||||
[:key {:optional true} :string]
|
||||
[:children
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:type [:= "paragraph"]]
|
||||
[:key {:optional true} :string]
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} ::shape/fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
|
||||
[:children
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:text :string]
|
||||
[:key {:optional true} :string]
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} ::shape/fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])
|
||||
|
||||
(sm/register! ::content schema:content)
|
||||
|
||||
(def valid-content?
|
||||
(sm/lazy-validator schema:content))
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::position-data}
|
||||
|
||||
@@ -66,16 +66,6 @@
|
||||
[n]
|
||||
(string? n))
|
||||
|
||||
;; TODO Move this to tokens-lib
|
||||
(sm/register!
|
||||
^{::sm/type ::token}
|
||||
[:map {:title "Token"}
|
||||
[:name token-name-ref]
|
||||
[:type [::sm/one-of token-types]]
|
||||
[:value :any]
|
||||
[:description {:optional true} [:maybe :string]]
|
||||
[:modified-at {:optional true} ::sm/inst]])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::color}
|
||||
[:map
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.types.token-theme
|
||||
(:require
|
||||
[app.common.schema :as sm]))
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::token-theme}
|
||||
[:map {:title "TokenTheme"}
|
||||
[:name :string]
|
||||
[:group :string]
|
||||
[:description [:maybe :string]]
|
||||
[:is-source :boolean]
|
||||
[:id :string]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:sets :any]])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::token-set}
|
||||
[:map {:title "TokenSet"}
|
||||
[:name :string]
|
||||
[:description {:optional true} [:maybe :string]]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:tokens {:optional true} :any]])
|
||||
@@ -7,9 +7,12 @@
|
||||
(ns app.common.types.tokens-lib
|
||||
(:require
|
||||
#?(:clj [app.common.fressian :as fres])
|
||||
#?(:clj [clojure.data.json :as json])
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.time :as dt]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.token :as cto]
|
||||
@@ -116,13 +119,16 @@
|
||||
[:map {:title "Token"}
|
||||
[:name cto/token-name-ref]
|
||||
[:type [::sm/one-of cto/token-types]]
|
||||
[:value :any]
|
||||
[:description [:maybe :string]]
|
||||
[:modified-at ::sm/inst]])
|
||||
[:value ::sm/any]
|
||||
[:description {:optional true} :string]
|
||||
[:modified-at {:optional true} ::sm/inst]])
|
||||
|
||||
(declare make-token)
|
||||
|
||||
(def schema:token
|
||||
[:and
|
||||
schema:token-attrs
|
||||
[:and {:gen/gen (->> (sg/generator schema:token-attrs)
|
||||
(sg/fmap #(make-token %)))}
|
||||
(sm/required-keys schema:token-attrs)
|
||||
[:fn token?]])
|
||||
|
||||
(def check-token
|
||||
@@ -321,6 +327,7 @@
|
||||
(assoc-in [:ids temp-id] token))))
|
||||
{:tokens-tree {} :ids {}} tokens))
|
||||
|
||||
|
||||
(defprotocol ITokenSet
|
||||
(update-name [_ set-name] "change a token set name while keeping the path")
|
||||
(add-token [_ token] "add a token at the end of the list")
|
||||
@@ -380,15 +387,33 @@
|
||||
(def schema:token-set-attrs
|
||||
[:map {:title "TokenSet"}
|
||||
[:name :string]
|
||||
[:description [:maybe :string]]
|
||||
[:modified-at ::sm/inst]
|
||||
[:tokens [:and
|
||||
[:map-of {:gen/max 5} :string schema:token]
|
||||
[:fn d/ordered-map?]]]])
|
||||
[:description {:optional true} :string]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:tokens {:optional true
|
||||
:gen/gen (->> (sg/map-of (sg/generator ::sm/text)
|
||||
(sg/generator schema:token))
|
||||
(sg/fmap #(into (d/ordered-map) %)))}
|
||||
[:and
|
||||
[:map-of {:gen/max 5
|
||||
:decode/json (fn [v]
|
||||
(cond
|
||||
(d/ordered-map? v)
|
||||
v
|
||||
|
||||
(map? v)
|
||||
(into (d/ordered-map) v)
|
||||
|
||||
:else
|
||||
v))}
|
||||
:string schema:token]
|
||||
[:fn d/ordered-map?]]]])
|
||||
|
||||
(declare make-token-set)
|
||||
|
||||
(def schema:token-set
|
||||
[:and
|
||||
schema:token-set-attrs
|
||||
[:and {:gen/gen (->> (sg/generator schema:token-set-attrs)
|
||||
(sg/fmap #(make-token-set %)))}
|
||||
(sm/required-keys schema:token-set-attrs)
|
||||
[:fn token-set?]])
|
||||
|
||||
(sm/register! ::token-set schema:token-set)
|
||||
@@ -552,16 +577,16 @@
|
||||
(def schema:token-theme-attrs
|
||||
[:map {:title "TokenTheme"}
|
||||
[:name :string]
|
||||
[:group :string]
|
||||
[:description [:maybe :string]]
|
||||
[:is-source [:maybe :boolean]]
|
||||
[:id :string]
|
||||
[:modified-at ::sm/inst]
|
||||
[:sets [:set {:gen/max 5} :string]]])
|
||||
[:group {:optional true} :string]
|
||||
[:description {:optional true} :string]
|
||||
[:is-source {:optional true} :boolean]
|
||||
[:id {:optional true} :string]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:sets {:optional true} [:set {:gen/max 5} :string]]])
|
||||
|
||||
(def schema:token-theme
|
||||
[:and
|
||||
schema:token-theme-attrs
|
||||
(sm/required-keys schema:token-theme-attrs)
|
||||
[:fn token-theme?]])
|
||||
|
||||
(sm/register! ::token-theme schema:token-theme)
|
||||
@@ -803,7 +828,7 @@
|
||||
(map-indexed (fn [index item]
|
||||
(assoc item :index index))))))
|
||||
|
||||
(defn- flatten-nested-tokens-json
|
||||
(defn flatten-nested-tokens-json
|
||||
"Recursively flatten the dtcg token structure, joining keys with '.'."
|
||||
[tokens token-path]
|
||||
(reduce-kv
|
||||
@@ -830,7 +855,7 @@
|
||||
|
||||
(declare make-tokens-lib)
|
||||
|
||||
(defn- legacy-nodes->dtcg-nodes [sets-data]
|
||||
(defn legacy-nodes->dtcg-nodes [sets-data]
|
||||
(walk/postwalk
|
||||
(fn [node]
|
||||
(cond-> node
|
||||
@@ -866,8 +891,6 @@ Will return a value that matches this schema:
|
||||
(get-active-themes-set-tokens [_] "set of set names that are active in the the active themes")
|
||||
(encode-dtcg [_] "Encodes library to a dtcg compatible json string")
|
||||
(decode-dtcg-json [_ parsed-json] "Decodes parsed json containing tokens and converts to library")
|
||||
(decode-single-set-json [_ set-name tokens] "Decodes parsed json containing single token set and converts to library")
|
||||
(decode-single-set-legacy-json [_ set-name tokens] "Decodes parsed legacy json containing single token set and converts to library")
|
||||
(decode-legacy-json [_ parsed-json] "Decodes parsed legacy json containing tokens and converts to library")
|
||||
(get-all-tokens [_] "all tokens in the lib")
|
||||
(validate [_]))
|
||||
@@ -889,6 +912,12 @@ Will return a value that matches this schema:
|
||||
"themes" (clj->js themes)
|
||||
"active-themes" (clj->js active-themes)))])
|
||||
|
||||
|
||||
#?@(:clj
|
||||
[json/JSONWriter
|
||||
(-write [this writter options] (json/-write (encode-dtcg this) writter options))])
|
||||
|
||||
|
||||
ITokenSets
|
||||
(add-set [_ token-set]
|
||||
(let [path (get-token-set-prefixed-path token-set)
|
||||
@@ -922,6 +951,7 @@ Will return a value that matches this schema:
|
||||
this)))
|
||||
|
||||
|
||||
|
||||
(delete-set [_ set-name]
|
||||
(let [prefixed-path (set-name->prefixed-full-path set-name)]
|
||||
(TokensLib. (d/dissoc-in sets prefixed-path)
|
||||
@@ -1311,17 +1341,6 @@ Will return a value that matches this schema:
|
||||
(assoc-in ["$metadata" "activeThemes"] active-themes-clear)
|
||||
(assoc-in ["$metadata" "activeSets"] active-sets))))
|
||||
|
||||
(decode-single-set-json [this set-name tokens]
|
||||
(assert (map? tokens) "expected a map data structure for `data`")
|
||||
|
||||
(add-set this (make-token-set :name (normalize-set-name set-name)
|
||||
:tokens (flatten-nested-tokens-json tokens ""))))
|
||||
|
||||
|
||||
(decode-single-set-legacy-json [this set-name tokens]
|
||||
(assert (map? tokens) "expected a map data structure for `data`")
|
||||
(decode-single-set-json this set-name (legacy-nodes->dtcg-nodes tokens)))
|
||||
|
||||
(decode-dtcg-json [_ data]
|
||||
(assert (map? data) "expected a map data structure for `data`")
|
||||
|
||||
@@ -1481,6 +1500,14 @@ Will return a value that matches this schema:
|
||||
{:encode/json encode-dtcg
|
||||
:decode/json decode-dtcg}})
|
||||
|
||||
(defn duplicate-set [set-name lib & {:keys [suffix]}]
|
||||
(let [sets (get-sets lib)
|
||||
unames (map :name sets)
|
||||
copy-name (cfh/generate-unique-name set-name unames :suffix suffix)]
|
||||
(some-> (get-set lib set-name)
|
||||
(assoc :name copy-name)
|
||||
(assoc :modified-at (dt/now)))))
|
||||
|
||||
(sm/register! type:tokens-lib)
|
||||
|
||||
;; === Serialization handlers for RPC API (transit) and database (fressian)
|
||||
|
||||
281
common/src/app/common/types/variant.cljc
Normal file
281
common/src/app/common/types/variant.cljc
Normal file
@@ -0,0 +1,281 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.types.variant
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.math :as math]
|
||||
[app.common.schema :as sm]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMA
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def schema:variant-property
|
||||
[:map
|
||||
[:name :string]
|
||||
[:value :string]])
|
||||
|
||||
(def schema:variant-component
|
||||
;; A component that is part of a variant set.
|
||||
[:map
|
||||
[:variant-id {:optional true} ::sm/uuid]
|
||||
[:variant-properties {:optional true} [:vector schema:variant-property]]])
|
||||
|
||||
(def schema:variant-shape
|
||||
;; The root shape of the main instance of a variant component.
|
||||
[:map
|
||||
[:variant-id {:optional true} ::sm/uuid]
|
||||
[:variant-name {:optional true} :string]])
|
||||
|
||||
(def schema:variant-container
|
||||
;; is a board that contains all variant components of a variant set,
|
||||
;; for grouping them visually in the workspace.
|
||||
[:map
|
||||
[:is-variant-container {:optional true} :boolean]])
|
||||
|
||||
(sm/register! ::variant-property schema:variant-property)
|
||||
(sm/register! ::variant-component schema:variant-component)
|
||||
(sm/register! ::variant-shape schema:variant-shape)
|
||||
(sm/register! ::variant-container schema:variant-container)
|
||||
|
||||
(def valid-variant-component?
|
||||
(sm/check-fn schema:variant-component))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def property-prefix "Property")
|
||||
(def property-regex (re-pattern (str property-prefix "(\\d+)")))
|
||||
(def value-prefix "Value ")
|
||||
|
||||
|
||||
(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 add-new-prop
|
||||
"Adds a new property with generated name and provided value to the existing props list."
|
||||
[props value]
|
||||
(conj props {:name (str property-prefix (next-property-number props))
|
||||
:value value}))
|
||||
|
||||
(defn add-new-props
|
||||
"Adds new properties with generated names and provided values to the existing props list."
|
||||
[props values]
|
||||
(let [next-prop-num (next-property-number props)
|
||||
xf (map-indexed (fn [i v]
|
||||
{:name (str property-prefix (+ next-prop-num i))
|
||||
:value v}))]
|
||||
(into props xf values)))
|
||||
|
||||
(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]
|
||||
(path-to-properties path properties 0))
|
||||
([path properties min-props]
|
||||
(let [cpath (cfh/split-path path)
|
||||
total-props (max (count cpath) min-props)
|
||||
assigned (mapv #(assoc % :value (nth cpath %2 "")) properties (range))
|
||||
;; Add empty strings to the end of cpath to reach the minimum number of properties
|
||||
cpath (take total-props (concat cpath (repeat "")))
|
||||
remaining (drop (count properties) cpath)]
|
||||
(add-new-props assigned remaining))))
|
||||
|
||||
|
||||
(defn properties-map-to-string
|
||||
"Transforms a map of properties to a string of properties omitting the empty ones"
|
||||
[properties]
|
||||
(->> properties
|
||||
(keep (fn [{:keys [name value]}]
|
||||
(when (not (str/blank? value))
|
||||
(str name "=" value))))
|
||||
(str/join ", ")))
|
||||
|
||||
|
||||
(defn properties-string-to-map
|
||||
"Transforms a string of properties to a map of properties"
|
||||
[s]
|
||||
(->> (str/split s ",")
|
||||
(mapv #(str/split % "="))
|
||||
(mapv (fn [[k v]]
|
||||
{:name (str/trim k)
|
||||
:value (str/trim v)}))))
|
||||
|
||||
|
||||
(defn valid-properties-string?
|
||||
"Checks if a string of properties has a processable format or not"
|
||||
[s]
|
||||
(let [pattern #"^([a-zA-Z0-9\s]+=[a-zA-Z0-9\s]+)(,\s*[a-zA-Z0-9\s]+=[a-zA-Z0-9\s]+)*$"]
|
||||
(not (nil? (re-matches pattern s)))))
|
||||
|
||||
|
||||
(defn find-properties-to-remove
|
||||
"Compares two property maps to find which properties should be removed"
|
||||
[prev-props upd-props]
|
||||
(let [upd-names (set (map :name upd-props))]
|
||||
(filterv #(not (contains? upd-names (:name %))) prev-props)))
|
||||
|
||||
|
||||
(defn find-properties-to-update
|
||||
"Compares two property maps to find which properties should be updated"
|
||||
[prev-props upd-props]
|
||||
(filterv #(some (fn [prop] (and (= (:name %) (:name prop))
|
||||
(not= (:value %) (:value prop)))) prev-props) upd-props))
|
||||
|
||||
|
||||
(defn find-properties-to-add
|
||||
"Compares two property maps to find which properties should be added"
|
||||
[prev-props upd-props]
|
||||
(let [prev-names (set (map :name prev-props))]
|
||||
(filterv #(not (contains? prev-names (:name %))) upd-props)))
|
||||
|
||||
|
||||
(defn find-index-for-property-name
|
||||
"Finds the index of a name in a property map"
|
||||
[props name]
|
||||
(some (fn [[idx prop]]
|
||||
(when (= (:name prop) name)
|
||||
idx))
|
||||
(map-indexed vector props)))
|
||||
|
||||
(defn remove-prefix
|
||||
"Removes the given prefix (with or without a trailing ' / ') from the beginning of the name"
|
||||
[name prefix]
|
||||
(let [long-name (str prefix " / ")]
|
||||
(cond
|
||||
(str/starts-with? name long-name)
|
||||
(subs name (count long-name))
|
||||
|
||||
(str/starts-with? name prefix)
|
||||
(subs name (count prefix))
|
||||
|
||||
:else
|
||||
name)))
|
||||
|
||||
(def ^:private xf:map-name
|
||||
(map :name))
|
||||
|
||||
(defn- matching-indices
|
||||
[props1 props2]
|
||||
(let [names-in-p2 (into #{} xf:map-name props2)
|
||||
xform (comp
|
||||
(map-indexed (fn [index {:keys [name]}]
|
||||
(when (contains? names-in-p2 name)
|
||||
index)))
|
||||
(filter some?))]
|
||||
(into #{} xform props1)))
|
||||
|
||||
(defn- find-index-by-name
|
||||
"Returns the index of the first item in props with the given name, or nil if not found."
|
||||
[name props]
|
||||
(some (fn [[idx item]]
|
||||
(when (= (:name item) name)
|
||||
idx))
|
||||
(map-indexed vector props)))
|
||||
|
||||
(defn- next-valid-position
|
||||
"Returns the first non-negative integer not present in the used-pos set."
|
||||
[used-pos]
|
||||
(loop [p 0]
|
||||
(if (contains? used-pos p)
|
||||
(recur (inc p))
|
||||
p)))
|
||||
|
||||
(defn- find-position
|
||||
"Returns the index of the property with the given name in `props`,
|
||||
or the next available index not in `used-pos` if not found."
|
||||
[name props used-pos]
|
||||
(or (find-index-by-name name props)
|
||||
(next-valid-position used-pos)))
|
||||
|
||||
(defn merge-properties
|
||||
"Merges props2 into props1 with the following rules:
|
||||
- For each property p2 in props2:
|
||||
- Skip it if its value is empty.
|
||||
- If props1 contains a property with the same name, update its value with that of p2.
|
||||
- Otherwise, assign p2's value to the first unused property in props1. A property is considered used if:
|
||||
- Its name exists in both props1 and props2, or
|
||||
- Its value has already been updated during the merge.
|
||||
- If no unused properties are available in props1, append a new property with a default name and p2's value."
|
||||
[props1 props2]
|
||||
(let [props2 (remove #(str/empty? (:value %)) props2)]
|
||||
(-> (reduce
|
||||
(fn [{:keys [props used-pos]} prop]
|
||||
(let [pos (find-position (:name prop) props used-pos)
|
||||
used-pos (conj used-pos pos)]
|
||||
(if (< pos (count props))
|
||||
{:props (assoc-in (vec props) [pos :value] (:value prop)) :used-pos used-pos}
|
||||
{:props (add-new-prop props (:value prop)) :used-pos used-pos})))
|
||||
{:props (vec props1) :used-pos (matching-indices props1 props2)}
|
||||
props2)
|
||||
:props)))
|
||||
|
||||
(defn compare-properties
|
||||
"Compares vectors of properties keeping the value if it is the same for all
|
||||
or setting a custom value where their values do not coincide"
|
||||
([props-list]
|
||||
(compare-properties props-list nil))
|
||||
|
||||
([props-list distinct-mark]
|
||||
(let [grouped (group-by :name (apply concat props-list))
|
||||
check-values (fn [values]
|
||||
(let [vals (map :value values)]
|
||||
(if (apply = vals)
|
||||
(first vals)
|
||||
distinct-mark)))]
|
||||
(mapv (fn [[name values]]
|
||||
{:name name :value (check-values values)})
|
||||
grouped))))
|
||||
|
||||
(defn same-variant?
|
||||
"Determines if all elements belong to the same variant"
|
||||
[components]
|
||||
(let [variant-ids (distinct (map :variant-id components))
|
||||
not-blank? (complement str/blank?)]
|
||||
(and
|
||||
(= 1 (count variant-ids))
|
||||
(not-blank? (first variant-ids)))))
|
||||
|
||||
(defn distance
|
||||
"Computes a weighted distance between two property lists `props1` and `props2`.
|
||||
Latter properties weight less that previous ones"
|
||||
[props1 props2]
|
||||
(let [total-num-props (count props1)
|
||||
xform (map-indexed
|
||||
(fn [idx [p1 p2]]
|
||||
(if (not= p1 p2)
|
||||
(math/pow 2 (- total-num-props idx))
|
||||
0)))]
|
||||
(transduce
|
||||
xform
|
||||
+
|
||||
(map vector props1 props2))))
|
||||
|
||||
(defn variant-name-to-name
|
||||
"Transforms a variant-name (its properties values) into a standard name:
|
||||
the real name of the shape joined by the properties values separated by '/'"
|
||||
[variant]
|
||||
(cfh/merge-path-item (:name variant) (str/replace (:variant-name variant) #", " " / ")))
|
||||
@@ -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
|
||||
[]
|
||||
|
||||
@@ -4,36 +4,36 @@
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns frontend-tests.tokens.token-test
|
||||
(ns common-tests.files.tokens-test
|
||||
(:require
|
||||
[app.main.ui.workspace.tokens.token :as wtt]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
[app.common.files.tokens :as cft]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest test-parse-token-value
|
||||
(t/testing "parses double from a token value"
|
||||
(t/is (= {:value 100.1 :unit nil} (wtt/parse-token-value "100.1")))
|
||||
(t/is (= {:value -9 :unit nil} (wtt/parse-token-value "-9"))))
|
||||
(t/is (= {:value 100.1 :unit nil} (cft/parse-token-value "100.1")))
|
||||
(t/is (= {:value -9.0 :unit nil} (cft/parse-token-value "-9"))))
|
||||
(t/testing "trims white-space"
|
||||
(t/is (= {:value -1.3 :unit nil} (wtt/parse-token-value " -1.3 "))))
|
||||
(t/is (= {:value -1.3 :unit nil} (cft/parse-token-value " -1.3 "))))
|
||||
(t/testing "parses unit: px"
|
||||
(t/is (= {:value 70.3 :unit "px"} (wtt/parse-token-value " 70.3px "))))
|
||||
(t/is (= {:value 70.3 :unit "px"} (cft/parse-token-value " 70.3px "))))
|
||||
(t/testing "parses unit: %"
|
||||
(t/is (= {:value -10 :unit "%"} (wtt/parse-token-value "-10%"))))
|
||||
(t/is (= {:value -10.0 :unit "%"} (cft/parse-token-value "-10%"))))
|
||||
(t/testing "parses unit: px")
|
||||
(t/testing "returns nil for any invalid characters"
|
||||
(t/is (nil? (wtt/parse-token-value " -1.3a "))))
|
||||
(t/is (nil? (cft/parse-token-value " -1.3a "))))
|
||||
(t/testing "doesnt accept invalid double"
|
||||
(t/is (nil? (wtt/parse-token-value ".3")))))
|
||||
(t/is (nil? (cft/parse-token-value ".3")))))
|
||||
|
||||
(t/deftest token-applied-test
|
||||
(t/testing "matches passed token with `:token-attributes`"
|
||||
(t/is (true? (wtt/token-applied? {:name "a"} {:applied-tokens {:x "a"}} #{:x}))))
|
||||
(t/is (true? (cft/token-applied? {:name "a"} {:applied-tokens {:x "a"}} #{:x}))))
|
||||
(t/testing "doesn't match empty token"
|
||||
(t/is (nil? (wtt/token-applied? {} {:applied-tokens {:x "a"}} #{:x}))))
|
||||
(t/is (nil? (cft/token-applied? {} {:applied-tokens {:x "a"}} #{:x}))))
|
||||
(t/testing "does't match passed token `:id`"
|
||||
(t/is (nil? (wtt/token-applied? {:name "b"} {:applied-tokens {:x "a"}} #{:x}))))
|
||||
(t/is (nil? (cft/token-applied? {:name "b"} {:applied-tokens {:x "a"}} #{:x}))))
|
||||
(t/testing "doesn't match passed `:token-attributes`"
|
||||
(t/is (nil? (wtt/token-applied? {:name "a"} {:applied-tokens {:x "a"}} #{:y})))))
|
||||
(t/is (nil? (cft/token-applied? {:name "a"} {:applied-tokens {:x "a"}} #{:y})))))
|
||||
|
||||
(t/deftest shapes-ids-by-applied-attributes
|
||||
(t/testing "Returns set of matched attributes that fit the applied token"
|
||||
@@ -54,7 +54,7 @@
|
||||
shape-applied-x-y
|
||||
shape-applied-all
|
||||
shape-applied-none]
|
||||
expected (wtt/shapes-ids-by-applied-attributes {:name "1"} shapes attributes)]
|
||||
expected (cft/shapes-ids-by-applied-attributes {:name "1"} shapes attributes)]
|
||||
(t/is (= (:x expected) (shape-ids shape-applied-x
|
||||
shape-applied-x-y
|
||||
shape-applied-all)))
|
||||
@@ -62,34 +62,34 @@
|
||||
shape-applied-x-y
|
||||
shape-applied-all)))
|
||||
(t/is (= (:z expected) (shape-ids shape-applied-all)))
|
||||
(t/is (true? (wtt/shapes-applied-all? expected (shape-ids shape-applied-all) attributes)))
|
||||
(t/is (false? (wtt/shapes-applied-all? expected (apply shape-ids shapes) attributes)))
|
||||
(t/is (true? (cft/shapes-applied-all? expected (shape-ids shape-applied-all) attributes)))
|
||||
(t/is (false? (cft/shapes-applied-all? expected (apply shape-ids shapes) attributes)))
|
||||
(shape-ids shape-applied-x
|
||||
shape-applied-x-y
|
||||
shape-applied-all))))
|
||||
|
||||
(t/deftest tokens-applied-test
|
||||
(t/testing "is true when single shape matches the token and attributes"
|
||||
(t/is (true? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
|
||||
(t/is (true? (cft/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
|
||||
{:applied-tokens {:x "b"}}]
|
||||
#{:x}))))
|
||||
(t/testing "is false when no shape matches the token or attributes"
|
||||
(t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "b"}}
|
||||
(t/is (nil? (cft/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "b"}}
|
||||
{:applied-tokens {:x "b"}}]
|
||||
#{:x})))
|
||||
(t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
|
||||
(t/is (nil? (cft/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
|
||||
{:applied-tokens {:x "a"}}]
|
||||
#{:y})))))
|
||||
|
||||
(t/deftest name->path-test
|
||||
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo.bar.baz")))
|
||||
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo..bar.baz")))
|
||||
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo..bar.baz...."))))
|
||||
(t/is (= ["foo" "bar" "baz"] (cft/token-name->path "foo.bar.baz")))
|
||||
(t/is (= ["foo" "bar" "baz"] (cft/token-name->path "foo..bar.baz")))
|
||||
(t/is (= ["foo" "bar" "baz"] (cft/token-name->path "foo..bar.baz...."))))
|
||||
|
||||
(t/deftest token-name-path-exists?-test
|
||||
(t/is (true? (wtt/token-name-path-exists? "border-radius" {"border-radius" {"sm" {:name "sm"}}})))
|
||||
(t/is (true? (wtt/token-name-path-exists? "border-radius" {"border-radius" {:name "sm"}})))
|
||||
(t/is (true? (wtt/token-name-path-exists? "border-radius.sm" {"border-radius" {:name "sm"}})))
|
||||
(t/is (true? (wtt/token-name-path-exists? "border-radius.sm.x" {"border-radius" {:name "sm"}})))
|
||||
(t/is (false? (wtt/token-name-path-exists? "other" {"border-radius" {:name "sm"}})))
|
||||
(t/is (false? (wtt/token-name-path-exists? "dark.border-radius.md" {"dark" {"border-radius" {"sm" {:name "sm"}}}}))))
|
||||
(t/is (true? (cft/token-name-path-exists? "border-radius" {"border-radius" {"sm" {:name "sm"}}})))
|
||||
(t/is (true? (cft/token-name-path-exists? "border-radius" {"border-radius" {:name "sm"}})))
|
||||
(t/is (true? (cft/token-name-path-exists? "border-radius.sm" {"border-radius" {:name "sm"}})))
|
||||
(t/is (true? (cft/token-name-path-exists? "border-radius.sm.x" {"border-radius" {:name "sm"}})))
|
||||
(t/is (false? (cft/token-name-path-exists? "other" {"border-radius" {:name "sm"}})))
|
||||
(t/is (false? (cft/token-name-path-exists? "dark.border-radius.md" {"dark" {"border-radius" {"sm" {:name "sm"}}}}))))
|
||||
@@ -40,8 +40,6 @@
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
nil)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -74,8 +72,6 @@
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
cfsh/prepare-create-artboard-from-selection)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -111,8 +107,6 @@
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
cfsh/prepare-create-artboard-from-selection)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -151,8 +145,6 @@
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
cfsh/prepare-create-artboard-from-selection)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -191,8 +183,6 @@
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
nil)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -232,8 +222,6 @@
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
cfsh/prepare-create-artboard-from-selection)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -265,8 +253,7 @@
|
||||
changes (cll/generate-rename-component (pcb/empty-changes)
|
||||
(:id component)
|
||||
"Test component after"
|
||||
(:data file)
|
||||
true)
|
||||
(:data file))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
@@ -445,8 +432,8 @@
|
||||
(t/is (some? copy1-child'))
|
||||
(t/is (ctk/instance-root? copy1-root'))
|
||||
(t/is (ctk/instance-of? copy1-root' (:id file') (:id component')))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root'))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child'))
|
||||
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
|
||||
|
||||
(t/deftest test-instantiate-component-from-lib
|
||||
@@ -489,8 +476,8 @@
|
||||
(t/is (some? copy1-child'))
|
||||
(t/is (ctk/instance-root? copy1-root'))
|
||||
(t/is (ctk/instance-of? copy1-root' (:id library) (:id component')))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root'))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child'))
|
||||
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
|
||||
|
||||
(t/deftest test-instantiate-nested-component
|
||||
@@ -533,8 +520,8 @@
|
||||
(t/is (some? copy1-child'))
|
||||
(t/is (ctk/instance-root? copy1-root'))
|
||||
(t/is (ctk/instance-of? copy1-root' (:id file') (:id component')))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root'))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child'))
|
||||
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
|
||||
|
||||
(t/deftest test-instantiate-nested-component-from-lib
|
||||
@@ -580,8 +567,8 @@
|
||||
(t/is (some? copy1-child'))
|
||||
(t/is (ctk/instance-root? copy1-root'))
|
||||
(t/is (ctk/instance-of? copy1-root' (:id library) (:id component')))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root'))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child'))
|
||||
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
|
||||
|
||||
(t/deftest test-detach-copy
|
||||
|
||||
@@ -47,8 +47,7 @@
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
(:id copy-root))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
@@ -99,8 +98,7 @@
|
||||
{(:id file-mdf) file-mdf
|
||||
(:id library) library}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
(:id copy-root))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
@@ -151,8 +149,7 @@
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
(:id copy-root))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
@@ -198,8 +195,7 @@
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
(:id copy-root))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
@@ -246,8 +242,7 @@
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
(:id copy-root))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
@@ -291,8 +286,7 @@
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy2-root)
|
||||
true)
|
||||
(:id copy2-root))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
@@ -338,8 +332,7 @@
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy2-root)
|
||||
true)
|
||||
(:id copy2-root))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-new-child') nil))
|
||||
(t/is (ctst/parent-of? copy-root' copy-new-child'))
|
||||
(t/is (ctk/is-main-of? main-free-shape' copy-new-child' true))))
|
||||
(t/is (ctk/is-main-of? main-free-shape' copy-new-child'))))
|
||||
|
||||
(t/deftest test-sync-when-deleting-shape
|
||||
(let [;; ==== Setup
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
;; 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.token-test
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
(ns common-tests.logic.variants-test
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.variants :as clv]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.logic.variant-properties :as clvp]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
@@ -20,7 +23,7 @@
|
||||
(t/deftest test-update-property-name
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
|
||||
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
|
||||
v-id (-> (ths/get-shape file :v01) :id)
|
||||
page (thf/current-page file)
|
||||
|
||||
@@ -29,8 +32,8 @@
|
||||
(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"))
|
||||
(clvp/generate-update-property-name v-id 0 "NewName1")
|
||||
(clvp/generate-update-property-name v-id 1 "NewName2"))
|
||||
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -65,7 +68,7 @@
|
||||
(pcb/with-page-id (:id page))
|
||||
(pcb/with-library-data (:data file))
|
||||
(pcb/with-objects (:objects page))
|
||||
(clv/generate-add-new-property v-id))
|
||||
(clvp/generate-add-new-property v-id))
|
||||
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -101,7 +104,7 @@
|
||||
(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}))
|
||||
(clvp/generate-add-new-property v-id {:fill-values? true}))
|
||||
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -117,7 +120,7 @@
|
||||
(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/is (= (-> comp01' :variant-properties last :value) "Value 1"))))
|
||||
|
||||
|
||||
|
||||
@@ -132,7 +135,7 @@
|
||||
(pcb/with-page-id (:id page))
|
||||
(pcb/with-library-data (:data file))
|
||||
(pcb/with-objects (:objects page))
|
||||
(clv/generate-add-new-property v-id))
|
||||
(clvp/generate-add-new-property v-id))
|
||||
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
@@ -147,7 +150,7 @@
|
||||
(pcb/with-page-id (:id page))
|
||||
(pcb/with-library-data (:data file))
|
||||
(pcb/with-objects (:objects page))
|
||||
(clv/generate-remove-property v-id 0))
|
||||
(clvp/generate-remove-property v-id 0))
|
||||
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
@@ -180,8 +183,8 @@
|
||||
(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"))
|
||||
(clvp/generate-update-property-value (:id comp01) 0 "NewValue1")
|
||||
(clvp/generate-update-property-value (:id comp02) 0 "NewValue2"))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
@@ -192,3 +195,73 @@
|
||||
;; ==== Check
|
||||
(t/is (= (-> comp01' :variant-properties first :value) "NewValue1"))
|
||||
(t/is (= (-> comp02' :variant-properties first :value) "NewValue2"))))
|
||||
|
||||
|
||||
(t/deftest test-duplicate-variant-container
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
|
||||
data (:data file)
|
||||
page (thf/current-page file)
|
||||
objects (:objects page)
|
||||
|
||||
variant-container (ths/get-shape file :v01)
|
||||
|
||||
|
||||
|
||||
|
||||
;; ==== Action
|
||||
changes (-> (pcb/empty-changes nil)
|
||||
(pcb/with-page-id (:id page))
|
||||
(pcb/with-library-data (:data file))
|
||||
(pcb/with-objects (:objects page))
|
||||
(cll/generate-duplicate-changes objects ;; objects
|
||||
page ;; page
|
||||
#{(:id variant-container)} ;; ids
|
||||
(gpt/point 0 0) ;; delta
|
||||
{(:id file) file} ;; libraries
|
||||
(:data file) ;; library-data
|
||||
(:id file))) ;; file-id
|
||||
|
||||
;; ==== Get
|
||||
file' (thf/apply-changes file changes)
|
||||
data' (:data file')
|
||||
page' (thf/current-page file')
|
||||
objects' (:objects page')]
|
||||
|
||||
;; ==== Check
|
||||
(thf/validate-file! file')
|
||||
(t/is (= (count (:components data)) 2))
|
||||
(t/is (= (count (:components data')) 4))
|
||||
(t/is (= (count objects) 4))
|
||||
(t/is (= (count objects') 7))))
|
||||
|
||||
|
||||
(t/deftest test-delete-variant
|
||||
;; When a variant container becomes empty, it id automatically deleted
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
|
||||
container (ths/get-shape file :v01)
|
||||
m01-id (-> (ths/get-shape file :m01) :id)
|
||||
m02-id (-> (ths/get-shape file :m02) :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))
|
||||
(#(second (cls/generate-delete-shapes % #{m01-id m02-id} {}))))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
container' (ths/get-shape file' :v01)]
|
||||
|
||||
;; ==== Check
|
||||
;; The variant containew was not nil before the deletion
|
||||
(t/is (not (nil? container)))
|
||||
;; The variant containew is nil after the deletion
|
||||
(t/is (nil? container'))))
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
[common-tests.types.modifiers-test]
|
||||
[common-tests.types.shape-decode-encode-test]
|
||||
[common-tests.types.shape-interactions-test]
|
||||
[common-tests.types.shape-path-data-test]
|
||||
[common-tests.types.tokens-lib-test]
|
||||
[common-tests.uuid-test]))
|
||||
|
||||
@@ -90,4 +91,5 @@
|
||||
'common-tests.types.tokens-lib-test
|
||||
'common-tests.types.components-test
|
||||
'common-tests.types.absorb-assets-test
|
||||
'common-tests.types.shape-path-data-test
|
||||
'common-tests.uuid-test))
|
||||
|
||||
@@ -547,4 +547,3 @@
|
||||
|
||||
;; FOR POSSIBLE FUTURE TEST CASES
|
||||
;; (str "M259.958 89.134c-6.88-.354-10.484-1.241-12.44-3.064-1.871-1.743-6.937-3.098-15.793-4.226-7.171-.913-17.179-2.279-22.24-3.034-5.06-.755-15.252-2.016-22.648-2.8-18.685-1.985-35.63-4.223-38.572-5.096-3.655-1.084-3.016-3.548.708-2.726 1.751.387 13.376 1.701 25.833 2.922 12.456 1.22 29.018 3.114 36.803 4.208 29.94 4.206 29.433 4.204 34.267.136 3.787-3.186 5.669-3.669 14.303-3.669 14.338 0 17.18 1.681 12.182 7.205-2.053 2.268-1.994 2.719.707 5.42 3.828 3.827 3.74 5.846-.238 5.5-1.752-.153-7.544-.502-12.872-.776zm7.563-3.194c0-.778-1.751-1.352-3.892-1.274l-3.893.141 3.539 1.133c1.946.624 3.698 1.197 3.893 1.275.194.077.354-.496.354-1.275zm-15.899-8.493c1.43-2.29 1.414-2.83-.084-2.83-2.05 0-5.25 2.76-5.25 4.529 0 2.226 3.599 1.08 5.334-1.699zm8.114 0c2.486-2.746 2.473-2.83-.438-2.83-1.65 0-3.683 1.273-4.516 2.83-1.175 2.196-1.077 2.831.438 2.831 1.075 0 3.107-1.274 4.516-2.83zm7.814.674c2.858-3.444.476-4.085-3.033-.816-2.451 2.284-2.677 2.973-.975 2.973 1.22 0 3.023-.97 4.008-2.157zm-49.571-4.509c-1.168-.43-3.294-1.802-4.725-3.051-2.112-1.843-9.304-2.595-38.219-3.994-46.474-2.25-63-4.077-60.27-6.665.324-.308 9.507.261 20.406 1.264 10.9 1.003 31.16 2.258 45.024 2.789l25.207.964 4.625-3.527c4.313-3.29 5.41-3.474 16.24-2.732 6.389.438 11.981 1.388 12.428 2.111.447.723-.517 2.73-2.141 4.46l-2.954 3.144c1.607 1.697 3.308 3.289 5.049 4.845 3.248 2.189-5.438 1.289-8.678 1.284-5.428-.061-10.825-.463-11.992-.892zm12.74-3.242c-1.123-.694-2.36-.943-2.75-.554-.389.39.21 1.275 1.334 1.97 1.122.693 2.36.942 2.749.553.389-.39-.21-1.275-1.334-1.97zm-5.663 0a1.42 1.42 0 00-1.415-1.416 1.42 1.42 0 00-1.416 1.416 1.42 1.42 0 001.416 1.415 1.42 1.42 0 001.415-1.415zm-8.464-6.404c.984-1.187 1.35-2.598.813-3.135-1.181-1.18-5.408 1.297-6.184 3.624-.806 2.42 3.265 2.048 5.37-.49zm6.863.258c.867-1.045 1.163-2.313.658-2.819-1.063-1.062-4.719 1.631-4.719 3.476 0 1.864 2.274 1.496 4.061-.657zm8.792-.36c1.637-1.972 1.448-2.197-1.486-1.77-1.848.27-3.622 1.287-3.943 2.26-.838 2.547 3.212 2.181 5.429-.49zm32.443-4.11c-6.156-2.228-67.1-6.138-119.124-7.642-39.208-1.134-72.072-.928-94.618.593-6.617.446-19.681 1.16-29.03 1.587-15.798.72-17.183.573-19.588-2.085-4.498-4.97-2.544-7.857 6.39-9.44 4.394-.778 9.164-2.436 10.6-3.685 5.44-4.729 20.332-14.06 31.14-19.509C65.717 11.88 78.955 7.79 103.837 3.08 121.686-.3 125.552-.642 129.318.82c2.44.948 12.4 1.948 22.132 2.221 15.37.432 20.004 1.18 35.294 5.698 22.36 6.606 39.732 15.1 56.55 27.653 7.307 5.452 14.086 9.913 15.066 9.913.98 0 2.148.956 2.596 2.124.55 1.432 2.798 2.123 6.914 2.123 6.213 0 12.4 3.046 12.38 6.096-.012 1.75-6.502 5.353-9.118 5.063-.818-.09-3.717-.972-6.442-1.958zm-16.986-7.436c0-1.575-33.326-18.118-43.173-21.43-23.008-7.739-54.084-12.922-77.136-12.866-16.863.041-37.877 3.628-52.465 8.956-18.062 6.596-26.563 10.384-29.181 13.002-1.205 1.205-5.306 3.769-9.112 5.698-7.754 3.929-8.841 5.482-3.029 4.325 13.494-2.685 66.794-3.773 110.913-2.264 38.005 1.3 96.812 4.435 102.122 5.443.584.111 1.061-.277 1.061-.864zm-236.39-3.18c0-.78-1.592-1.416-3.539-1.416-1.946 0-3.538.637-3.538 1.415 0 .779 1.592 1.416 3.538 1.416 1.947 0 3.54-.637 3.54-1.416zm7.078-1.416c0-.779-.956-1.416-2.124-1.416-1.167 0-2.123.637-2.123 1.416 0 .778.956 1.415 2.123 1.415 1.168 0 2.124-.637 2.124-1.415zm11.734-4.437c3.278-1.661 6.278-3.483 6.667-4.048 1.366-1.98 20.645-11.231 32.557-15.622 11.862-4.372 36.546-9.865 44.327-9.865 3.485 0 3.867-.404 3.012-3.185-.538-1.752-1.177-3.41-1.42-3.685-.907-1.026-36.72 7.16-45.065 10.302-17.226 6.484-47.566 24.27-47.566 27.886 0 1.786.845 1.585 7.488-1.783zm206.254-5.577c-12.298-10.518-53.842-27.166-70.896-28.41-5.526-.404-6.3-.097-6.695 2.655-.33 2.307.402 3.275 2.831 3.742 32.436 6.237 52.205 12.315 66.975 20.594 11.904 6.673 14.477 7.141 7.785 1.419zM150.1 11.04c-1.949-3.64-7.568-4.078-6.886-.538.256 1.329 2.054 2.817 3.997 3.309 4.498 1.137 4.816.832 2.888-2.771zm6.756.94c-.248-1.752-1.026-3.185-1.727-3.185-.7 0-1.493 1.433-1.76 3.185-.328 2.152.232 3.185 1.727 3.185 1.485 0 2.064-1.047 1.76-3.185zm-30.178-2.458c0-2.303-.908-3.694-2.627-4.025-3.6-.694-5.23 1.301-4.22 5.166 1.216 4.647 6.847 3.709 6.847-1.14zm12.544 2.104c-.448-1.168-1.224-2.132-1.725-2.142-.5-.013-2.343-.404-4.095-.873-2.569-.689-3.185-.274-3.185 2.142 0 2.476.854 2.996 4.91 2.996 3.783 0 4.723-.487 4.095-2.123z")
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
(t/is (= (count components') 1))
|
||||
|
||||
(t/is (ctk/instance-of? copy-root' (:id file') (:id component')))
|
||||
(t/is (ctk/is-main-of? main-root' copy-root' true))
|
||||
(t/is (ctk/is-main-of? main-root' copy-root'))
|
||||
(t/is (ctk/main-instance-of? (:id main-root') (:id (second pages')) component'))))
|
||||
|
||||
(t/deftest absorb-colors
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{"color":
|
||||
{"red":
|
||||
{"100":
|
||||
{"value":"red",
|
||||
"type":"color",
|
||||
"description":""}}}}
|
||||
@@ -1,6 +0,0 @@
|
||||
{"color":
|
||||
{"red":
|
||||
{"100":
|
||||
{"$value":"red",
|
||||
"$type":"color",
|
||||
"$description":""}}}}
|
||||
59
common/test/common_tests/types/shape_path_data_test.cljc
Normal file
59
common/test/common_tests/types/shape_path_data_test.cljc
Normal file
@@ -0,0 +1,59 @@
|
||||
;; 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.types.shape-path-data-test
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.types.shape.path :as path]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(def sample-content
|
||||
[{:command :move-to, :params {:x 480.0, :y 839.0}}
|
||||
{:command :line-to, :params {:x 439.0, :y 802.0}}
|
||||
{:command :curve-to, :params {:c1x 368.0, :c1y 737.0, :c2x 310.0, :c2y 681.0, :x 264.0, :y 634.0}}
|
||||
{:command :close-path :params {}}])
|
||||
|
||||
(def sample-bytes
|
||||
[0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 67 -16 0 0 68 81 -64 0
|
||||
0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 67 -37 -128 0 68 72 -128 0
|
||||
0 3 0 0 67 -72 0 0 68 56 64 0 67 -101 0 0 68 42 64 0 67 -124 0 0 68 30 -128 0
|
||||
0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0])
|
||||
|
||||
;; This means it implements IReduceInit/IReduce protocols
|
||||
(t/deftest path-data-to-vector
|
||||
(let [pdata (path/path-data sample-content)
|
||||
result (vec pdata)]
|
||||
(t/is (= 4 (count result)))
|
||||
(t/is (= (get-in sample-content [0 :command])
|
||||
(get-in result [0 :command])))
|
||||
(t/is (= (get-in sample-content [1 :command])
|
||||
(get-in result [1 :command])))
|
||||
(t/is (= (get-in sample-content [2 :command])
|
||||
(get-in result [2 :command])))
|
||||
(t/is (= (get-in sample-content [3 :command])
|
||||
(get-in result [3 :command])))
|
||||
|
||||
(t/is (= (get-in sample-content [0 :params])
|
||||
(get-in result [0 :params])))
|
||||
(t/is (= (get-in sample-content [1 :params])
|
||||
(get-in result [1 :params])))
|
||||
(t/is (= (get-in sample-content [2 :params])
|
||||
(get-in result [2 :params])))
|
||||
(t/is (= (get-in sample-content [3 :params])
|
||||
(get-in result [3 :params])))))
|
||||
|
||||
(t/deftest path-data-plain-to-binary
|
||||
(let [pdata (path/path-data sample-content)]
|
||||
(t/is (= sample-bytes
|
||||
(vec
|
||||
#?(:cljs (js/Int8Array. (.-buffer pdata))
|
||||
:clj (.array (.-buffer pdata))))))
|
||||
(t/is (= (->> sample-content
|
||||
(mapv path/map->PathSegment))
|
||||
(vec pdata)))))
|
||||
|
||||
@@ -120,7 +120,6 @@
|
||||
(t/is (= ["Foo/Foo" "Foo/Baz" "Foo/Bar"] (move ["Foo"] ["Foo" "Foo"] ["Foo" "Baz"] false)))
|
||||
(t/is (= ["Foo/Baz" "Foo/Bar" "Foo/Foo"] (move ["Foo"] ["Foo" "Foo"] nil false))))))
|
||||
|
||||
|
||||
(t/deftest move-token-set-nested-2
|
||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||
(ctob/add-set (ctob/make-token-set :name "a/b"))
|
||||
@@ -220,7 +219,6 @@
|
||||
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid params for token-theme"
|
||||
(ctob/make-token-theme params)))))
|
||||
|
||||
|
||||
(t/deftest make-tokens-lib
|
||||
(let [tokens-lib (ctob/make-tokens-lib)]
|
||||
(t/is (= (ctob/set-count tokens-lib) 0))))
|
||||
@@ -315,6 +313,58 @@
|
||||
(t/is (= (:sets token-theme') #{}))
|
||||
(t/is (nil? token-set'))))
|
||||
|
||||
(t/deftest duplicate-token-set
|
||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||
(ctob/add-set (ctob/make-token-set :name "test-token-set"
|
||||
:tokens {"test-token"
|
||||
(ctob/make-token :name "test-token"
|
||||
:type :boolean
|
||||
:value true)})))
|
||||
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
|
||||
token (get-in token-set-copy [:tokens "test-token"])]
|
||||
|
||||
(t/is (some? token-set-copy))
|
||||
(t/is (= (:name token-set-copy) "test-token-set-copy"))
|
||||
(t/is (= (count (:tokens token-set-copy)) 1))
|
||||
(t/is (= (:name token) "test-token"))))
|
||||
|
||||
(t/deftest duplicate-token-set-twice
|
||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||
(ctob/add-set (ctob/make-token-set :name "test-token-set"
|
||||
:tokens {"test-token"
|
||||
(ctob/make-token :name "test-token"
|
||||
:type :boolean
|
||||
:value true)})))
|
||||
|
||||
tokens-lib (ctob/add-set tokens-lib (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"}))
|
||||
|
||||
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
|
||||
token (get-in token-set-copy [:tokens "test-token"])]
|
||||
|
||||
(t/is (some? token-set-copy))
|
||||
(t/is (= (:name token-set-copy) "test-token-set-copy-2"))
|
||||
(t/is (= (count (:tokens token-set-copy)) 1))
|
||||
(t/is (= (:name token) "test-token"))))
|
||||
|
||||
(t/deftest duplicate-empty-token-set
|
||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||
(ctob/add-set (ctob/make-token-set :name "test-token-set")))
|
||||
|
||||
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
|
||||
tokens (get token-set-copy :tokens)]
|
||||
|
||||
(t/is (some? token-set-copy))
|
||||
(t/is (= (:name token-set-copy) "test-token-set-copy"))
|
||||
(t/is (= (count (:tokens token-set-copy)) 0))
|
||||
(t/is (= (count tokens) 0))))
|
||||
|
||||
(t/deftest duplicate-not-existing-token-set
|
||||
(let [tokens-lib (ctob/make-tokens-lib)
|
||||
|
||||
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})]
|
||||
|
||||
(t/is (nil? token-set-copy))))
|
||||
|
||||
(t/deftest active-themes-set-names
|
||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||
(ctob/add-set (ctob/make-token-set :name "test-token-set")))
|
||||
@@ -918,7 +968,6 @@
|
||||
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
|
||||
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
|
||||
|
||||
|
||||
(t/deftest update-token-in-sets-rename
|
||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
|
||||
@@ -1371,30 +1420,6 @@
|
||||
(t/testing "invalid tokens got discarded"
|
||||
(t/is (nil? (get-set-token "typography" "H1.Bold")))))))
|
||||
|
||||
#?(:clj
|
||||
(t/deftest single-set-legacy-json-decoding
|
||||
(let [json (-> (slurp "test/common_tests/types/data/legacy-single-set.json")
|
||||
(tr/decode-str))
|
||||
lib (ctob/decode-single-set-legacy-json (ctob/ensure-tokens-lib nil) "single_set" json)
|
||||
get-set-token (fn [set-name token-name]
|
||||
(some-> (ctob/get-set lib set-name)
|
||||
(ctob/get-token token-name)))]
|
||||
(t/is (= '("single_set") (ctob/get-ordered-set-names lib)))
|
||||
(t/testing "token added"
|
||||
(t/is (some? (get-set-token "single_set" "color.red.100")))))))
|
||||
|
||||
#?(:clj
|
||||
(t/deftest single-set-dtcg-json-decoding
|
||||
(let [json (-> (slurp "test/common_tests/types/data/single-set.json")
|
||||
(tr/decode-str))
|
||||
lib (ctob/decode-single-set-json (ctob/ensure-tokens-lib nil) "single_set" json)
|
||||
get-set-token (fn [set-name token-name]
|
||||
(some-> (ctob/get-set lib set-name)
|
||||
(ctob/get-token token-name)))]
|
||||
(t/is (= '("single_set") (ctob/get-ordered-set-names lib)))
|
||||
(t/testing "token added"
|
||||
(t/is (some? (get-set-token "single_set" "color.red.100")))))))
|
||||
|
||||
#?(:clj
|
||||
(t/deftest dtcg-encoding-decoding-json
|
||||
(let [json (-> (slurp "test/common_tests/types/data/tokens-multi-set-example.json")
|
||||
|
||||
112
common/test/common_tests/types/variant_test.cljc
Normal file
112
common/test/common_tests/types/variant_test.cljc
Normal file
@@ -0,0 +1,112 @@
|
||||
;; 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.types.variant-test
|
||||
(:require
|
||||
[app.common.types.variant :as ctv]
|
||||
[clojure.test :as t]))
|
||||
|
||||
|
||||
(t/deftest variant-distance01
|
||||
;;c1: primary, default, rounded, blue, dark
|
||||
;;c2: primary, hover, squared, blue, dark
|
||||
;;c3: primary, default, squared, blue, light
|
||||
|
||||
;; I have a copy of c1, and I change from rounded to squared
|
||||
;; c2: 1 difference in pos 2
|
||||
;; c3: 1 differences in pos 5
|
||||
;; The min distance should be c3
|
||||
|
||||
(let [target [{:name "type" :value "primary"}
|
||||
{:name "status" :value "default"}
|
||||
{:name "borders" :value "squared"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "dark"}]
|
||||
props2 [{:name "type" :value "primary"}
|
||||
{:name "status" :value "hover"}
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "dark"}]
|
||||
props3 [{:name "type" :value "primary"}
|
||||
{:name "status" :value "default"}
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "light"}]
|
||||
dist2 (ctv/distance target props2)
|
||||
dist3 (ctv/distance target props3)]
|
||||
(t/is (< dist3 dist2))))
|
||||
|
||||
|
||||
(t/deftest variant-distance02
|
||||
;;c1: primary, default, rounded, blue, dark
|
||||
;;c2: primary, hover, squared, red, dark
|
||||
;;c3: secondary, hover, rounded, blue, dark
|
||||
|
||||
;; I have a copy of c1, and I change from default to hover
|
||||
;; c2: 2 differences in pos 3 and 4
|
||||
;; c3: 1 differences in pos 1
|
||||
;; The min distance should be c2
|
||||
|
||||
(let [target [{:name "type" :value "primary"}
|
||||
{:name "status" :value "hover"}
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "dark"}]
|
||||
props2 [{:name "type" :value "primary"}
|
||||
{:name "status" :value "hover"}
|
||||
{:name "borders" :value "squared"}
|
||||
{:name "color" :value "red"}
|
||||
{:name "theme" :value "dark"}]
|
||||
props3 [{:name "type" :value "secondary"}
|
||||
{:name "status" :value "hover"}
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "dark"}]
|
||||
dist2 (ctv/distance target props2)
|
||||
dist3 (ctv/distance target props3)]
|
||||
(t/is (< dist2 dist3))))
|
||||
|
||||
(t/deftest variant-distance03
|
||||
;;c1: primary, default, rounded, blue, dark
|
||||
;;c2: secondary, default, rounded, blue, light
|
||||
;;c3: secondary, hover, squared, blue, dark
|
||||
;;c4: secondary, hover, rounded, blue, dark
|
||||
|
||||
;; I have a copy of c1, and I change from primary to secondary
|
||||
;; c2: 1 difference in pos 4
|
||||
;; c3: 2 differences in pos 1 and 2
|
||||
;; c4: 1 difference in pos 1
|
||||
;; The distances should be c2 < c4 < c3
|
||||
|
||||
(let [target [{:name "type" :value "secondary"}
|
||||
{:name "status" :value "default"}
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "dark"}]
|
||||
props2 [{:name "type" :value "secondary"}
|
||||
{:name "status" :value "default"}
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "light"}]
|
||||
props3 [{:name "type" :value "secondary"}
|
||||
{:name "status" :value "hover"}
|
||||
{:name "borders" :value "squared"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "dark"}]
|
||||
props4 [{:name "type" :value "secondary"}
|
||||
{:name "status" :value "hover"}
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "dark"}]
|
||||
dist2 (ctv/distance target props2)
|
||||
dist3 (ctv/distance target props3)
|
||||
dist4 (ctv/distance target props4)]
|
||||
(t/is (< dist2 dist4))
|
||||
(t/is (< dist4 dist3))))
|
||||
|
||||
|
||||
|
||||
|
||||
108
common/test/common_tests/variant_test.cljc
Normal file
108
common/test/common_tests/variant_test.cljc
Normal file
@@ -0,0 +1,108 @@
|
||||
;; 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.variant-test
|
||||
(:require
|
||||
[app.common.types.variant :as ctv]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest convert-between-variant-properties-maps-and-strings
|
||||
(let [map-with-two-props [{:name "border" :value "yes"} {:name "color" :value "gray"}]
|
||||
map-with-two-props-one-blank [{:name "border" :value "no"} {:name "color" :value ""}]
|
||||
map-with-one-prop [{:name "border" :value "no"}]
|
||||
map-with-spaces [{:name "border 1" :value "of course"} {:name "color 2" :value "dark gray"}]
|
||||
|
||||
string-valid-with-two-props "border=yes, color=gray"
|
||||
string-valid-with-one-prop "border=no"
|
||||
string-valid-with-spaces "border 1=of course, color 2=dark gray"
|
||||
string-invalid "border=yes, color="]
|
||||
|
||||
(t/testing "convert map to string"
|
||||
(t/is (= (ctv/properties-map-to-string map-with-two-props) string-valid-with-two-props))
|
||||
(t/is (= (ctv/properties-map-to-string map-with-two-props-one-blank) string-valid-with-one-prop))
|
||||
(t/is (= (ctv/properties-map-to-string map-with-spaces) string-valid-with-spaces)))
|
||||
|
||||
(t/testing "convert string to map"
|
||||
(t/is (= (ctv/properties-string-to-map string-valid-with-two-props) map-with-two-props))
|
||||
(t/is (= (ctv/properties-string-to-map string-valid-with-one-prop) map-with-one-prop))
|
||||
(t/is (= (ctv/properties-string-to-map string-valid-with-spaces) map-with-spaces)))
|
||||
|
||||
(t/testing "check if a string is valid"
|
||||
(t/is (= (ctv/valid-properties-string? string-valid-with-two-props) true))
|
||||
(t/is (= (ctv/valid-properties-string? string-valid-with-one-prop) true))
|
||||
(t/is (= (ctv/valid-properties-string? string-valid-with-spaces) true))
|
||||
(t/is (= (ctv/valid-properties-string? string-invalid) false)))))
|
||||
|
||||
|
||||
(t/deftest find-properties
|
||||
(let [prev-props [{:name "border" :value "yes"} {:name "color" :value "gray"}]
|
||||
upd-props-1 [{:name "border" :value "yes"}]
|
||||
upd-props-2 [{:name "border" :value "yes"} {:name "color" :value "blue"}]
|
||||
upd-props-3 [{:name "border" :value "yes"} {:name "color" :value "gray"} {:name "shadow" :value "large"}]
|
||||
upd-props-4 [{:name "color" :value "yellow"} {:name "shadow" :value "large"}]]
|
||||
|
||||
(t/testing "a property to remove"
|
||||
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-1)
|
||||
[{:name "color" :value "gray"}]))
|
||||
(t/is (= (ctv/find-properties-to-update prev-props upd-props-1)
|
||||
[]))
|
||||
(t/is (= (ctv/find-properties-to-add prev-props upd-props-1)
|
||||
[])))
|
||||
|
||||
(t/testing "a property to update"
|
||||
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-2)
|
||||
[]))
|
||||
(t/is (= (ctv/find-properties-to-update prev-props upd-props-2)
|
||||
[{:name "color" :value "blue"}]))
|
||||
(t/is (= (ctv/find-properties-to-add prev-props upd-props-2)
|
||||
[])))
|
||||
|
||||
(t/testing "a property to add"
|
||||
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-3)
|
||||
[]))
|
||||
(t/is (= (ctv/find-properties-to-update prev-props upd-props-3)
|
||||
[]))
|
||||
(t/is (= (ctv/find-properties-to-add prev-props upd-props-3)
|
||||
[{:name "shadow" :value "large"}])))
|
||||
|
||||
(t/testing "properties to remove, update & add"
|
||||
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-4)
|
||||
[{:name "border" :value "yes"}]))
|
||||
(t/is (= (ctv/find-properties-to-update prev-props upd-props-4)
|
||||
[{:name "color" :value "yellow"}]))
|
||||
(t/is (= (ctv/find-properties-to-add prev-props upd-props-4)
|
||||
[{:name "shadow" :value "large"}])))
|
||||
|
||||
(t/testing "find property index"
|
||||
(t/is (= (ctv/find-index-for-property-name prev-props "border") 0))
|
||||
(t/is (= (ctv/find-index-for-property-name prev-props "color") 1)))))
|
||||
|
||||
|
||||
(t/deftest compare-properties
|
||||
(let [props-1 [{:name "border" :value "yes"} {:name "color" :value "gray"}]
|
||||
props-2 [{:name "border" :value "yes"} {:name "color" :value "red"}]
|
||||
props-3 [{:name "border" :value "no"} {:name "color" :value "gray"}]]
|
||||
|
||||
(t/testing "compare properties"
|
||||
(t/is (= (ctv/compare-properties [props-1 props-2])
|
||||
[{:name "border" :value "yes"} {:name "color" :value nil}]))
|
||||
(t/is (= (ctv/compare-properties [props-1 props-2 props-3])
|
||||
[{:name "border" :value nil} {:name "color" :value nil}]))
|
||||
(t/is (= (ctv/compare-properties [props-1 props-2 props-3] "&")
|
||||
[{:name "border" :value "&"} {:name "color" :value "&"}])))))
|
||||
|
||||
|
||||
(t/deftest check-belong-same-variant
|
||||
(let [components-1 [{:variant-id "a variant"} {:variant-id "a variant"}]
|
||||
components-2 [{:variant-id "a variant"} {:variant-id "another variant"}]
|
||||
components-3 [{:variant-id "a variant"} {}]
|
||||
components-4 [{} {}]]
|
||||
|
||||
(t/testing "check-belong-same-variant"
|
||||
(t/is (= (ctv/same-variant? components-1) true))
|
||||
(t/is (= (ctv/same-variant? components-2) false))
|
||||
(t/is (= (ctv/same-variant? components-3) false))
|
||||
(t/is (= (ctv/same-variant? components-4) false)))))
|
||||
@@ -1,15 +1,16 @@
|
||||
FROM debian:bookworm
|
||||
FROM ubuntu:24.04
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV NODE_VERSION=v22.13.1 \
|
||||
ENV NODE_VERSION=v22.14.0 \
|
||||
CLOJURE_VERSION=1.12.0.1501 \
|
||||
CLJKONDO_VERSION=2025.01.16 \
|
||||
BABASHKA_VERSION=1.12.196 \
|
||||
CLJFMT_VERSION=0.13.0 \
|
||||
RUSTUP_VERSION=1.27.1 \
|
||||
RUST_VERSION=1.85.0 \
|
||||
EMSCRIPTEN_VERSION=4.0.6 \
|
||||
LANG=en_US.UTF-8 \
|
||||
LC_ALL=en_US.UTF-8
|
||||
|
||||
@@ -45,7 +46,7 @@ RUN set -ex; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
RUN set -ex; \
|
||||
useradd -m -g users -s /bin/bash penpot; \
|
||||
usermod -l penpot -d /home/penpot -G users -s /bin/bash ubuntu; \
|
||||
passwd penpot -d; \
|
||||
echo "penpot ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||
|
||||
@@ -63,8 +64,6 @@ RUN set -ex; \
|
||||
woff-tools \
|
||||
woff2 \
|
||||
fontforge \
|
||||
gconf-service \
|
||||
libasound2 \
|
||||
libatk1.0-0 \
|
||||
libatk-bridge2.0-0 \
|
||||
libcairo2 \
|
||||
@@ -73,7 +72,6 @@ RUN set -ex; \
|
||||
libexpat1 \
|
||||
libfontconfig1 \
|
||||
libgcc1 \
|
||||
libgconf-2-4 \
|
||||
libgdk-pixbuf2.0-0 \
|
||||
libglib2.0-0 \
|
||||
libgtk-3-0 \
|
||||
@@ -95,7 +93,6 @@ RUN set -ex; \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
fonts-liberation \
|
||||
libappindicator1 \
|
||||
libnss3 \
|
||||
libgbm1 \
|
||||
xvfb \
|
||||
@@ -107,12 +104,12 @@ RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
ESUM='fb43ae1202402842559cb6223886ec1663b90ffbec48479abbcb92c92c9012eb'; \
|
||||
BINARY_URL='https://github.com/adoptium/temurin23-binaries/releases/download/jdk-23.0.2%2B7/OpenJDK23U-jdk_aarch64_linux_hotspot_23.0.2_7.tar.gz'; \
|
||||
ESUM='18071047526ab4b53131f9bb323e8703485ae37fcb2f2c5ef0f1b7bab66d1b94'; \
|
||||
BINARY_URL='https://github.com/adoptium/temurin24-binaries/releases/download/jdk-24%2B36/OpenJDK24U-jdk_aarch64_linux_hotspot_24_36.tar.gz'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
ESUM='870ac8c05c6fe563e7a3878a47d0234b83c050e83651d2c47e8b822ec74512dd'; \
|
||||
BINARY_URL='https://github.com/adoptium/temurin23-binaries/releases/download/jdk-23.0.2%2B7/OpenJDK23U-jdk_x64_linux_hotspot_23.0.2_7.tar.gz'; \
|
||||
ESUM='c340dee97b6aa215d248bc196dcac5b56e7be9b5c5d45e691344d40d5d0b171d'; \
|
||||
BINARY_URL='https://github.com/adoptium/temurin24-binaries/releases/download/jdk-24%2B36/OpenJDK24U-jdk_x64_linux_hotspot_24_36.tar.gz'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
@@ -137,7 +134,7 @@ RUN set -ex; \
|
||||
RUN set -ex; \
|
||||
install -d /usr/share/postgresql-common/pgdg; \
|
||||
curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc; \
|
||||
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \
|
||||
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt noble-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy install postgresql-client-16; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
@@ -273,8 +270,8 @@ WORKDIR /usr/local
|
||||
RUN set -eux; \
|
||||
git clone https://github.com/emscripten-core/emsdk.git; \
|
||||
cd emsdk; \
|
||||
./emsdk install latest; \
|
||||
./emsdk activate latest; \
|
||||
./emsdk install $EMSCRIPTEN_VERSION; \
|
||||
./emsdk activate $EMSCRIPTEN_VERSION; \
|
||||
rustup target add wasm32-unknown-emscripten;
|
||||
|
||||
WORKDIR /home
|
||||
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
- PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto
|
||||
|
||||
minio:
|
||||
image: "minio/minio:RELEASE.2023-11-11T08-14-41Z"
|
||||
image: "minio/minio:RELEASE.2025-04-03T14-56-28Z"
|
||||
command: minio server /mnt/data --console-address ":9001"
|
||||
|
||||
volumes:
|
||||
@@ -83,7 +83,7 @@ services:
|
||||
- 9001:9001
|
||||
|
||||
postgres:
|
||||
image: postgres:16
|
||||
image: postgres:16.8
|
||||
command: postgres -c config_file=/etc/postgresql.conf
|
||||
restart: always
|
||||
stop_signal: SIGINT
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -87,7 +87,7 @@ services:
|
||||
networks:
|
||||
- penpot
|
||||
|
||||
labels:
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
|
||||
# ## HTTPS: example of labels for the case where penpot will be exposed to the
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="{{ description or metadata.description }}">
|
||||
<link rel="stylesheet" href="{{ '/css/index.css' | url }}">
|
||||
<link rel="stylesheet" href="{{ '/css/prism.css' | url }}">
|
||||
<link rel="shortcut icon" href="/img/favicon.png">
|
||||
@@ -15,7 +14,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
||||
{% metagen
|
||||
title=title or metadata.title,
|
||||
desc=desc or metadata.desc,
|
||||
desc=desc or metadata.desc or description or metadata.description,
|
||||
url="https://help.penpot.app" + page.url,
|
||||
img="https://help.penpot.app/img/th-help-center.jpg",
|
||||
img_alt=alt,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 04· Code of Conduct
|
||||
desc: Learn about contributing to the Penpot project! This page outlines the Code of Conduct, reporting bugs, translations, core code contributions, & more.
|
||||
---
|
||||
|
||||
<h1 id="coc">Code of conduct</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 03· Core code contributions
|
||||
desc: Learn how to contribute to Penpot's open-source design collaboration platform. Find guidelines for bug reporting, code contributions & more.
|
||||
---
|
||||
|
||||
<h1 id="code-contributions">Core code contributions</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Contributing
|
||||
desc: Learn how to contribute to Penpot, the open-source design collaboration platform! Find guides on bug reporting, translations, code contributions, and more.
|
||||
eleventyNavigation:
|
||||
key: Contributing
|
||||
order: 3
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 05· Libraries & Templates
|
||||
desc: Contribute to Penpot's libraries & templates! Learn how to share your files and access resources. Try Penpot - It's free!
|
||||
---
|
||||
|
||||
<h1 id="libraries">Libraries & templates</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 01· Reporting bugs
|
||||
desc: Learn how to contribute to Penpot, the open-source design and prototyping platform! Find guidelines for reporting bugs, translations, & code contributions.
|
||||
---
|
||||
|
||||
<h1 id="reporting-bugs">Reporting bugs</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 02· Translations
|
||||
desc: Contribute to Penpot! Learn how to translate Penpot into your language using Weblate. Add new translations, languages, or edit existing ones today.
|
||||
---
|
||||
|
||||
<h1 id="translations">Translations</h1>
|
||||
|
||||
BIN
docs/img/abstraction-levels/abstraction-levels.png
Normal file
BIN
docs/img/abstraction-levels/abstraction-levels.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Help center
|
||||
desc: Find user guides, technical documentation, plugin info, FAQs, and contributing guidelines in Penpot's help center. Join the open-source community!
|
||||
layout: layouts/home.njk
|
||||
twitter: "@penpotapp"
|
||||
image: img/placeholder.png
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user