Compare commits

...

153 Commits

Author SHA1 Message Date
Alejandro
95829ff3de Merge pull request #2014 from penpot/3487-release-1.14
🎉 Adds new release info and images
2022-06-21 10:22:09 +02:00
Alejandro
2bed06de64 Merge pull request #2017 from penpot/hiru-fix-asset-names
🐛 Fix display of asset names and console warning
2022-06-21 06:46:22 +02:00
Andrés Moya
a08c1b1278 🐛 Fix display of asset names and console warning 2022-06-20 18:15:26 +02:00
Alejandro
3053e867cb Merge pull request #2006 from penpot/alotor-fix-thumbnails-viewer
🐛 Fix thumbnails in viewer thumbnails
2022-06-20 16:24:57 +02:00
Andrey Antukh
7cdbadc5b7 Merge pull request #2015 from penpot/alotor-fix-text-problem
🐛 Fix problem with empty text boxes events
2022-06-20 11:34:25 +02:00
alonso.torres
fb1dbd6f31 🐛 Fix problem with empty text boxes events 2022-06-20 11:29:52 +02:00
elhombretecla
9dabe2959f 🎉 Adds new release info and images 2022-06-20 06:47:08 +02:00
alonso.torres
529fb350fa 🐛 Fix thumbnails in viewer thumbnails 2022-06-17 14:39:07 +02:00
Pablo Alba
0654741e28 🎉 Navigate to the original link after log in 2022-06-17 10:22:11 +02:00
Andrey Antukh
c60c04f167 Merge pull request #2004 from penpot/alotor-bugfixing
1.14 Bugfixes
2022-06-17 08:22:02 +02:00
alonso.torres
24d23d9e5a 🐛 Fix visual glitch with thumbnails 2022-06-16 18:50:01 +02:00
alonso.torres
66cec51c44 🐛 Fix text problem 2022-06-16 10:40:33 +02:00
Alejandro Alonso
5e2a7e76f3 Merge remote-tracking branch 'origin/main' into staging 2022-06-15 12:00:14 +02:00
Andrey Antukh
e0bd3425bc Merge pull request #1999 from penpot/superalex-add-project-ids-to-create-file-audit-log
 Add project ids to create-file mutation for audit log
2022-06-15 10:53:33 +02:00
Alejandro Alonso
667598a0eb Add project ids to create-file mutation for audit log 2022-06-15 07:49:05 +02:00
Alejandro Alonso
310c322883 🐛 Fix show baground on export arboards 2022-06-14 11:26:19 +02:00
Alejandro Alonso
7f9d070692 Merge remote-tracking branch 'origin/main' into staging 2022-06-14 09:13:33 +02:00
Andrey Antukh
206ffcc6e8 Merge pull request #1995 from penpot/superalex-add-team-and-project-ids-to-update-file-audit-log
 Add team and project ids to update-file mutation for audit log
2022-06-14 09:12:21 +02:00
Alejandro Alonso
6b5ee24010 Add team and project ids to update-file mutation for audit log 2022-06-14 09:08:06 +02:00
Andrey Antukh
2132bad898 🐛 Add missing resolver to frontend docker image 2022-06-13 15:54:28 +02:00
Andrey Antukh
189d33221e 🐛 Add missing resolver to frontend docker image 2022-06-13 15:54:03 +02:00
Alejandro Alonso
a3fd5d6516 📚 Update changelog 2022-06-13 10:16:10 +02:00
Alejandro Alonso
85f2804af8 📎 Tag new minor release 2022-06-10 08:24:13 +02:00
Alejandro Alonso
424dd5c41a Merge remote-tracking branch 'origin/main' into staging 2022-06-09 13:45:13 +02:00
Alejandro Alonso
97f5f54d1c 📎 Tag new minor release 2022-06-09 13:44:22 +02:00
Alejandro
e6b80bf73e Merge pull request #1988 from penpot/superalex-fix-exporter-to-frontend-communication
🐛 fix exporter to frontend communication
2022-06-09 13:43:49 +02:00
Alejandro Alonso
bc3914e7e0 🐛 Fix exporter to frontend communication 2022-06-09 13:39:59 +02:00
Alejandro Alonso
801cdd940a Merge remote-tracking branch 'origin/main' into staging 2022-06-09 08:55:05 +02:00
Alejandro Alonso
28b6175943 📎 Tag new minor release 2022-06-09 08:47:55 +02:00
Alejandro Alonso
ba85dcf1a3 🐛 Fix orientation artboard preset does not work with differently sized artboards 2022-06-08 13:11:59 +02:00
Alejandro Alonso
c3486c566a 🐛 Fix exporter to frontend communication 2022-06-08 13:09:16 +02:00
Andrés Moya
71501d966c 🐛 Fix resize parents when there are nested groups 2022-06-07 14:36:26 +02:00
alonso.torres
8bb2f20eae 🐛 Fix problem with shadow spec 2022-06-07 12:53:48 +02:00
alonso.torres
0fcd414792 📚 Update changelog 2022-06-06 15:27:24 +02:00
alonso.torres
9eba666c31 Merge remote-tracking branch 'origin/main' into develop 2022-06-06 15:23:22 +02:00
alonso.torres
1764d965c1 📚 Upgrade to version 1.13.4-beta 2022-06-06 15:22:23 +02:00
Alejandro Alonso
a120630a7f 🐛 Fix environment import for exporter at docker 2022-06-06 13:23:40 +02:00
Alejandro
f33ad5e8fa Merge pull request #1972 from penpot/hiru-fix-orphaned-shapes
 Add script to fix broken objects
2022-06-06 13:18:07 +02:00
Andrés Moya
f04859f8a6 Add script to fix broken objects 2022-06-06 12:56:37 +02:00
Alejandro Alonso
31aed2aaa4 🐛 Fix base background not visible for imported svg 2022-06-06 12:34:19 +02:00
Alejandro
18109b2387 Merge pull request #1976 from penpot/hiru-fix-scrollintoview
🐛 Fix auto scroll layers panel in firefox
2022-06-06 11:05:35 +02:00
Andrés Moya
a0cc8a06b6 🐛 Fix auto scroll layers panel in firefox 2022-06-06 10:21:32 +02:00
Pablo Alba
4c1903b4e8 💄 Change text properties position at the sidebar 2022-06-03 12:42:35 +02:00
Pablo Alba
462ec0c12a Merge pull request #1973 from penpot/alotor-more-hotfixes
Hotfixes
2022-06-03 10:59:26 +02:00
Alejandro Alonso
2b61b1768f 🐛 Fix exporter white list domains configuration 2022-06-03 07:43:19 +02:00
alonso.torres
424630a67f 📚 Update changelog 2022-06-02 22:53:50 +02:00
alonso.torres
14b1970a8a 🐛 Fix concurrent thumbnail modification 2022-06-02 22:37:33 +02:00
alonso.torres
541168aee4 🐛 Fix problem with some data and text input 2022-06-02 22:35:59 +02:00
alonso.torres
6e9a77edcd 🐛 Fix undo for drawing curves 2022-06-02 22:31:27 +02:00
Pablo Alba
8d1cd2f56d 🐛 Empty groups were not deleted 2022-06-02 16:53:01 +02:00
Pablo Alba
65cda41245 Merge pull request #1970 from penpot/eva-shortcuts2
 Shortcuts improvements
2022-06-02 16:50:42 +02:00
Eva
c029948cce Shortcuts improvements 2022-06-02 16:40:46 +02:00
Pablo Alba
32540f1ba5 🐛 Components groups were not exported 2022-06-02 16:40:00 +02:00
alonso.torres
5d20815776 Merge remote-tracking branch 'origin/main' into develop 2022-06-01 10:42:59 +02:00
alonso.torres
0b149dd302 ⬆️ Update to 1.13.3-beta 2022-06-01 10:41:08 +02:00
Pablo Alba
662fc073df Merge pull request #1966 from penpot/alotor-fix-font-loading
🐛 Fix problem with font loading
2022-06-01 10:37:30 +02:00
alonso.torres
46be4ca6d1 🐛 Fix problem with font loading 2022-06-01 09:38:27 +02:00
alonso.torres
23d3e88214 Merge remote-tracking branch 'origin/main' into develop 2022-05-31 11:15:10 +02:00
Alejandro Alonso
c356ae6de8 🐛 Fix github auth without name 2022-05-31 10:26:13 +02:00
Pablo Alba
c5e872b81d 🐛 Remove default font on team change 2022-05-30 14:19:34 +02:00
Alejandro Alonso
0307e58fbe 🐛 Fix old texts with opacity and no fill 2022-05-30 12:40:07 +02:00
Alejandro
5c14c3fafc Merge pull request #1960 from penpot/alotor-fixes
🐛 Fix thumbnails. Add safety text position
2022-05-30 12:21:40 +02:00
alonso.torres
321c3fb34b 🐛 Fix problem with missplaced texts 2022-05-30 12:09:04 +02:00
alonso.torres
4764674374 🐛 Fix thumbnails. Add safety text position 2022-05-30 12:09:04 +02:00
Pablo Alba
0416988913 Set invitations expiration to 7 days 2022-05-30 10:41:23 +02:00
Eva Marco
72251f57b1 Merge pull request #1957 from yarons/patch-1
Typo fix
2022-05-30 10:21:45 +02:00
Andrés Moya
ec884787f1 🔧 Fix docker dependencies 2022-05-27 13:57:33 +02:00
alonso.torres
3d8c41cd69 Merge remote-tracking branch 'origin/main' into develop 2022-05-27 09:25:22 +02:00
Yaron Shahrabani
be7733a2cf Typo fix
intertactions -> interactions
2022-05-26 15:38:49 +03:00
Andrés Moya
8c51d1ac95 🌐 Add translations for: Spanish.
Currently translated at 100.0% (1110 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2022-05-25 17:17:58 +02:00
Eva Marco
64e2fa9e2f Merge pull request #1951 from penpot/palba-change-emails-footer
💄 Update footer in emails
2022-05-25 17:14:16 +02:00
Pablo Alba
fa5b0ed6ac 💄 Update footer in emails 2022-05-25 16:56:40 +02:00
Andrés Moya
89f485a674 🌐 Revise translations file format 2022-05-25 16:32:10 +02:00
Hosted Weblate
68e38271fb Merge branch 'origin/develop' into Weblate. 2022-05-25 11:24:03 +02:00
Vincas Dundzys
066d53b81b 🌐 Add translations for: Lithuanian.
Currently translated at 8.7% (84 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lt/
2022-05-25 11:24:03 +02:00
Pablo Alba
ffbc098af8 🐛 Prototype connection should be under the rules
https://tree.taiga.io/project/penpot/issue/3384
2022-05-24 13:42:10 +02:00
Pablo Alba
e8f61df710 🐛 Remove deprecated menu options 2022-05-24 12:49:41 +02:00
Eva Marco
3604d0cfc9 Merge pull request #1933 from penpot/palba-fix-file-menu-not-accessible
🐛 Fix menu file not accessible in certain conditions
2022-05-24 12:39:42 +02:00
Eva Marco
393d959289 Merge pull request #1929 from penpot/hiru-remove-emitf
♻️ Remove obsolete st/emitf macro
2022-05-24 12:21:29 +02:00
andy
a453f1a648 🌐 Added translation for: Lithuanian. 2022-05-24 11:00:01 +02:00
Pablo Alba
cdd6801360 🐛 Fix menu file not accessible in certain conditions
https://tree.taiga.io/project/penpot/issue/3385
2022-05-23 17:08:30 +02:00
alonso.torres
cca5ddb81a Merge remote-tracking branch 'origin/main' into develop 2022-05-23 12:17:56 +02:00
alonso.torres
28e2d64ac6 Merge remote-tracking branch 'origin/main' into develop 2022-05-23 10:58:29 +02:00
Eva Marco
6eb24bd1b7 Merge pull request #1928 from penpot/palba-fix-wrong-not-found-message
🐛 Fix wrong 'no assets found' message
2022-05-23 10:17:50 +02:00
Pablo Alba
79467b7b72 🐛 Fix wrong 'no assets found' message 2022-05-23 10:10:06 +02:00
Locness
e14c6e5a6f 🌐 Add translations for: French.
Currently translated at 72.8% (703 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-05-23 00:15:40 +02:00
Andrés Moya
2be432e1d4 ♻️ Remove obsolete st/emitf macro 2022-05-20 16:37:33 +02:00
alonso.torres
eb07350cac 🐛 Fix problem with typographies 2022-05-20 13:00:50 +02:00
alonso.torres
235d3dbf3d Merge remote-tracking branch 'origin/main' into develop 2022-05-20 11:10:14 +02:00
Alejandro
684805067a Merge pull request #1915 from JamieSlome/develop
Create SECURITY.md
2022-05-18 09:39:18 +02:00
Jamie Slome
db7761b742 Create SECURITY.md 2022-05-17 19:35:14 +01:00
Joseph V M
08beb57ff1 🌐 Add translations for: Malayalam.
Currently translated at 7.1% (69 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ml/
2022-05-16 23:16:12 +02:00
Radek Sawicki
accba56b89 🌐 Add translations for: Polish.
Currently translated at 100.0% (965 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2022-05-10 14:15:08 +02:00
Eva Marco
c5ba399bcd Merge pull request #1856 from penpot/palba-onboarding-multiple-invitations
 Multiple team invitations on onboarding
2022-05-09 09:45:05 +02:00
Pablo Alba
fb879660d0 Multiple team invitations on onboarding 2022-05-09 09:40:44 +02:00
Andrés Moya
1729fe7312 🌐 Add translations for: Spanish.
Currently translated at 98.9% (955 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2022-05-06 10:05:46 +02:00
Eva
bd7ea210f5 💄 Add changes line 2022-05-05 09:13:22 +02:00
Eva
9cacca4802 Add shortcut panel 2022-05-04 16:36:47 +02:00
Ahmad HosseinBor
9fab2fc24a 🌐 Add translations for: Persian.
Currently translated at 31.3% (303 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-05-04 12:14:36 +02:00
alonso.torres
4363e32aae Merge remote-tracking branch 'origin/staging' into develop 2022-05-03 10:29:19 +02:00
Ahmad HosseinBor
28fc7178f1 🌐 Add translations for: Persian.
Currently translated at 21.4% (207 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-05-02 19:14:48 +02:00
Andrés Moya
151de33586 🔧 Small fix of debug functions 2022-04-29 11:05:04 +02:00
alonso.torres
0afbf02443 💄 Fix linter 2022-04-28 11:22:36 +02:00
Ahmad HosseinBor
eb143c8399 🌐 Add translations for: Persian.
Currently translated at 21.3% (206 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-04-28 11:14:38 +02:00
Locness
85f1cb47a7 🌐 Add translations for: French.
Currently translated at 72.7% (702 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-04-28 11:14:38 +02:00
alonso.torres
f7dbb4f944 Merge remote-tracking branch 'origin/staging' into develop 2022-04-27 12:24:16 +02:00
Andrey Antukh
b90a308d66 Merge remote-tracking branch 'origin/staging' into develop 2022-04-26 17:11:00 +02:00
Alejandro Alonso
b3847cafa8 Merge remote-tracking branch 'origin/staging' into develop 2022-04-26 06:17:27 +02:00
Radek Sawicki
a18e067d7a 🌐 Add translations for: Polish.
Currently translated at 33.7% (326 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2022-04-23 21:14:43 +02:00
Locness
036fe44471 🌐 Add translations for: French.
Currently translated at 72.6% (701 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-04-23 21:14:41 +02:00
Andrey Antukh
b008835d43 Merge remote-tracking branch 'origin/staging' into develop 2022-04-22 14:50:04 +02:00
alonso.torres
fc95443cc4 Merge remote-tracking branch 'origin/staging' into develop 2022-04-22 14:46:22 +02:00
andy
2061018742 🌐 Added translation for: Polish. 2022-04-22 08:31:21 +02:00
Andrey Antukh
121b5af5d0 Merge pull request #1820 from penpot/palba-handoff-size-round-two-decimals
🐛 Round the size values on handoff to two decimals
2022-04-20 09:11:10 +02:00
Alejandro Alonso
1d69cb2580 Merge remote-tracking branch 'origin/staging' into develop 2022-04-20 06:31:19 +02:00
Pablo Alba
e68689aa4f 🐛 Round the size values on handoff to two decimals 2022-04-19 19:29:11 +02:00
Andrey Antukh
989ff8db7a Merge pull request #1796 from penpot/fixed-scroll
 Add fixed position in viewer
2022-04-19 14:54:24 +02:00
Andrés Moya
b68fdee946 Add fixed position in viewer 2022-04-19 14:41:19 +02:00
Andrey Antukh
81df2ca355 Merge pull request #1794 from penpot/palba-group-assets-by-drag-drop
 Group assets by drag and drop
2022-04-19 10:30:29 +02:00
Pablo Alba
56cdd1ffeb Group assets by drag and drop 2022-04-18 17:36:20 +02:00
Andrey Antukh
c9937f6b91 Merge remote-tracking branch 'origin/staging' into develop 2022-04-18 11:16:08 +02:00
Andrey Antukh
7e37aca5ee Merge remote-tracking branch 'origin/staging' into develop 2022-04-18 08:59:19 +02:00
Yaron Shahrabani
070886bbf6 🌐 Add translations for: Hebrew.
Currently translated at 100.0% (965 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-04-13 18:08:55 +02:00
nautilusx
c00168b61d 🌐 Add translations for: German.
Currently translated at 98.2% (948 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2022-04-13 18:08:54 +02:00
Oğuz Ersen
0e9119d603 🌐 Add translations for: Turkish.
Currently translated at 100.0% (965 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2022-04-13 18:08:54 +02:00
Andrés Moya
df39e9baf4 🌐 Disable italian for now 2022-04-11 16:00:15 +02:00
Andrés Moya
668aca725c 🌐 Update translations 2022-04-11 15:52:01 +02:00
Andrés Moya
c865082a6a Merge remote-tracking branch 'weblate/develop' into translations 2022-04-11 15:44:55 +02:00
andy
395f23dec8 🌐 Added translation for: Italian. 2022-04-07 11:34:14 +02:00
Andrey Antukh
58905f0b99 Merge remote-tracking branch 'origin/staging' into develop 2022-04-07 10:16:29 +02:00
Eva Marco
bf70719899 Merge pull request #1787 from penpot/superalex-fix-selected-colors-doesnt-work-for-shadows
🐛 Selected colors doesn't work for shadows
2022-04-06 10:39:11 +02:00
Alejandro Alonso
18855ef2ef 🐛 Selected colors doesn't work for shadows 2022-04-06 08:05:58 +02:00
Andrey Antukh
8ae05ff7b6 🐛 Fix issue with password persistence 2022-04-04 23:55:05 +02:00
Andrey Antukh
9e4650cbb6 Merge remote-tracking branch 'origin/staging' into develop 2022-04-04 23:18:29 +02:00
Oğuz Ersen
3b75d9b362 🌐 Add translations for: Turkish.
Currently translated at 100.0% (949 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2022-04-04 13:11:35 +02:00
Andrey Antukh
c9ddc83eef Merge remote-tracking branch 'origin/staging' into develop 2022-04-01 11:58:07 +02:00
bingling_sama
5170634b90 🌐 Add translations for: Chinese (Simplified).
Currently translated at 93.8% (891 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-04-01 09:10:00 +02:00
Oğuz Ersen
01c92c04cf 🌐 Add translations for: Turkish.
Currently translated at 99.7% (947 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2022-04-01 09:10:00 +02:00
Andrey Antukh
3cb15df08d Merge remote-tracking branch 'origin/staging' into develop 2022-03-30 11:50:03 +02:00
Eva
87f5efeadb Add Selected colors menu 2022-03-29 11:56:18 +02:00
Rodolfo Carvalho
6897c0c3fe Remove duplicate require of clojure.test 2022-03-29 09:54:58 +02:00
Andrey Antukh
4010fb7d1e Merge remote-tracking branch 'origin/staging' into develop 2022-03-28 20:27:19 +02:00
Eva
09c57bdb86 🐛 Fix comments modal remains open on page change 2022-03-28 17:26:20 +02:00
Yaron Shahrabani
d28bbdaaf7 🌐 Add translations for: Hebrew.
Currently translated at 99.8% (948 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-03-28 01:09:43 +02:00
Andrey Antukh
a44f1df0d4 Merge pull request #1728 from penpot/alotor/bugfixes
Safari/MacOS Fixes
2022-03-25 09:30:33 +01:00
Yaron Shahrabani
3e745ff45d 🌐 Add translations for: Hebrew.
Currently translated at 99.4% (944 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-03-25 08:03:09 +01:00
bingling_sama
bc87e3d6d0 🌐 Add translations for: Chinese (Simplified).
Currently translated at 80.6% (765 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-03-25 08:03:08 +01:00
Andrey Antukh
c1a67c0097 Merge remote-tracking branch 'origin/staging' into develop 2022-03-24 17:03:43 +01:00
Andrey Antukh
4ef9d4d5f6 🐛 Fix unexpected decoding of fresian data 2022-03-24 15:14:43 +01:00
Andrey Antukh
4ed093f28f Merge remote-tracking branch 'origin/staging' into develop 2022-03-24 11:41:33 +01:00
Andrés Moya
2e3addc6da 🎉 Add more unit tests 2022-03-23 15:44:55 +01:00
Andrey Antukh
80549bda9b Merge remote-tracking branch 'origin/staging' into develop 2022-03-23 14:16:15 +01:00
Andrey Antukh
1d5d597103 📎 Set correct version on version.txt file 2022-03-23 13:24:08 +01:00
152 changed files with 6469 additions and 1611 deletions

View File

@@ -1,12 +1,52 @@
# CHANGELOG
## :rocket: Next
## 1.14.0-beta
### :boom: Breaking changes
### :sparkles: New features
- Added shortcut panel in workspace [Taiga #36](https://tree.taiga.io/project/penpot/us/36)
- Added selected colors widget in right sidebar [Taiga #2485](https://tree.taiga.io/project/penpot/us/2485)
- Added fixed elements when scrolling [Taiga #1533](https://tree.taiga.io/project/penpot/us/1533)
- Multiple team invitations on onboarding [Taiga #3084](https://tree.taiga.io/project/penpot/us/3084)
- Change text properties position at the sidebar [Taiga #3047](https://tree.taiga.io/project/penpot/us/3047)
- Group assets by drag and drop [Taiga #2831](https://tree.taiga.io/project/penpot/us/2831)
- Navigate to the original link after log in [Taiga #3624](https://tree.taiga.io/project/penpot/issue/3624)
### :bug: Bugs fixed
### :arrow_up: Deps updates
### :heart: Community contributions by (Thank you!)
- Fix menu file not accessible in certain conditions [Taiga #3385](https://tree.taiga.io/project/penpot/issue/3385)
- Remove deprecated menu options [Taiga #3333](https://tree.taiga.io/project/penpot/issue/3333)
- Prototype connection should be under the rules [Taiga #3384](https://tree.taiga.io/project/penpot/issue/3384)
- Fix problem with empty text boxes events [Taiga #3627](https://tree.taiga.io/project/penpot/issue/3627)
## 1.13.5-beta
### :bug: Bugs fixed
- Fix orientation artboard preset not working with differently sized artboards [Taiga #3548](https://tree.taiga.io/project/penpot/issue/3548)
- Fix background on export arboards [Taiga #1991](https://tree.taiga.io/project/penpot/issue/1991)
## 1.13.4-beta
### :bug: Bugs fixed
- Fix undo when drawing curves [Taiga #3523](https://tree.taiga.io/project/penpot/issue/3523)
- Fix issue with text edition and certain fonts (WorkSans, Raleway, ...) and foreign objects [Taiga #3521](https://tree.taiga.io/project/penpot/issue/3521)
- Fix thumbnail generation when concurrent edition [Taiga #3522](https://tree.taiga.io/project/penpot/issue/3522)
- Fix environment imporot for exporter in Docker
- Fix auto scroll layers in Firefox [Taiga #3531](https://tree.taiga.io/project/penpot/issue/3531)
- Fix base background not visible for imported SVG
## 1.13.3-beta
### :bug: Bugs fixed
- Fix docker dependencies
- Sets invitations expirations to 7 days
- Add safety measure for text positions
- Fix old texts with opacity and no fill
- Remove default font on team change
- Fix github auth without name
- Fix problems with font loading in Firefox 95
## 1.13.2-beta
@@ -70,6 +110,7 @@
- Duplicate artboards create new flows if needed [Taiga #2221](https://tree.taiga.io/project/penpot/issue/2221)
- Round the size values on handoff to two decimals [Taiga #3227](https://tree.taiga.io/project/penpot/issue/3227)
- Fix paste shapes while editing text [Taiga #2396](https://tree.taiga.io/project/penpot/issue/2396)
- Round the size values on handoff to two decimals [Taiga #3227](https://tree.taiga.io/project/penpot/issue/3227)
- Fix blend modes ignored in component updates [Taiga #2626](https://tree.taiga.io/project/penpot/issue/2626)
- Fix internal error when hoverin over shape [Taiga #3237](https://tree.taiga.io/project/penpot/issue/3237)
- Fix mouse leave in handoff close overlay animation breaks [Taiga #3173](https://tree.taiga.io/project/penpot/issue/3173)

5
SECURITY.md Normal file
View File

@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Please report security issues to `support@penpot.app`

View File

@@ -25,7 +25,6 @@
[clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as sgen]
[clojure.test :as test]
[clojure.test :as test]
[clojure.tools.namespace.repl :as repl]
[clojure.walk :refer [macroexpand-all]]
[datoteka.core]

View File

@@ -37,7 +37,7 @@
<mj-section padding="24px 0 0 0">
<mj-column width="425px">
<mj-text align="center" font-size="14px" color="#64666A">
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>

View File

@@ -30,7 +30,7 @@
<mj-section padding="24px 0 0 0">
<mj-column width="425px">
<mj-text align="center" font-size="14px" color="#64666A">
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>

View File

@@ -39,7 +39,7 @@
<mj-section padding="24px 0 0 0">
<mj-column width="425px">
<mj-text align="center" font-size="14px" color="#64666A">
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>

View File

@@ -36,7 +36,7 @@
<mj-section padding="24px 0 0 0">
<mj-column width="425px">
<mj-text align="center" font-size="14px" color="#64666A">
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>

View File

@@ -250,7 +250,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
</td>
</tr>
</table>
@@ -450,7 +450,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with &lt;3 and Open Source</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with &lt;3 and Open Source</div>
</td>
</tr>
</table>

View File

@@ -240,7 +240,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
</td>
</tr>
</table>
@@ -440,7 +440,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with &lt;3 and Open Source</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with &lt;3 and Open Source</div>
</td>
</tr>
</table>

View File

@@ -245,7 +245,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
</td>
</tr>
</table>
@@ -445,7 +445,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with &lt;3 and Open Source</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with &lt;3 and Open Source</div>
</td>
</tr>
</table>

View File

@@ -240,7 +240,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
</td>
</tr>
</table>
@@ -440,7 +440,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with &lt;3 and Open Source</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with &lt;3 and Open Source</div>
</td>
</tr>
</table>

View File

@@ -119,7 +119,7 @@
(get-email info))]
{:backend (:name provider)
:email email
:fullname (get-name info)
:fullname (or (get-name info) email)
:props (->> (dissoc info :name :email)
(qualify-props provider))}))

View File

@@ -13,6 +13,7 @@
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.loggers.audit :as audit]
[app.metrics :as mtx]
[app.rpc.permissions :as perms]
[app.rpc.queries.files :as files]
@@ -26,6 +27,7 @@
[promesa.core :as p]))
(declare create-file)
(declare retrieve-team-id)
;; --- Helpers & Specs
@@ -47,8 +49,11 @@
(sv/defmethod ::create-file
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
(db/with-atomic [conn pool]
(proj/check-edition-permissions! conn profile-id project-id)
(create-file conn params)))
(let [team-id (retrieve-team-id conn project-id)]
(proj/check-edition-permissions! conn profile-id project-id)
(with-meta
(create-file conn params)
{::audit/props {:team-id team-id}}))))
(defn create-file-role
[conn {:keys [file-id profile-id role]}]
@@ -245,7 +250,6 @@
(declare insert-change)
(declare retrieve-lagged-changes)
(declare retrieve-team-id)
(declare send-notifications)
(declare update-file)
@@ -279,10 +283,14 @@
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
(db/with-atomic [conn pool]
(db/xact-lock! conn id)
(let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true})]
(let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true})
team-id (retrieve-team-id conn (:project-id file))]
(files/check-edition-permissions! conn profile-id id)
(update-file (assoc cfg :conn conn)
(assoc params :file file)))))
(with-meta
(update-file (assoc cfg :conn conn)
(assoc params :file file))
{::audit/props {:project-id (:project-id file)
:team-id team-id}}))))
(defn- take-snapshot?
"Defines the rule when file `data` snapshot should be saved."

View File

@@ -400,7 +400,7 @@
(defn- create-team-invitation
[{:keys [conn tokens team profile role email] :as cfg}]
(let [member (profile/retrieve-profile-data-by-email conn email)
token-exp (dt/in-future "48h")
token-exp (dt/in-future "168h") ;; 7 days
itoken (tokens :generate
{:iss :team-invitation
:exp token-exp

View File

@@ -53,8 +53,10 @@
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}]
(p/let [slink (slnk/retrieve-share-link pool file-id share-id)
perms (files/get-permissions pool profile-id file-id share-id)
thumbs (files/retrieve-object-thumbnails cfg file-id)
bundle (p/-> (retrieve-bundle cfg file-id)
(assoc :permissions perms))]
(assoc :permissions perms)
(assoc-in [:file :thumbnails] thumbs))]
;; When we have neither profile nor share, we just return a not
;; found response to the user.

View File

@@ -23,7 +23,41 @@
[expound.alpha :as expound]
[fipp.edn :refer [pprint]]))
;; ==== Utility functions
(defn reset-file-data
"Hardcode replace of the data of one file."
[system id data]
(db/with-atomic [conn (:app.db/pool system)]
(db/update! conn :file
{:data data}
{:id id})))
(defn get-file
"Get the migrated data of one file."
[system id]
(-> (:app.db/pool system)
(db/get-by-id :file id)
(update :data app.util.blob/decode)
(update :data pmg/migrate-data)))
(defn duplicate-file
"This is a raw version of duplication of file just only for forensic analysis."
[system file-id email]
(db/with-atomic [conn (:app.db/pool system)]
(when-let [profile (some->> (prof/retrieve-profile-data-by-email conn (str/lower email))
(prof/populate-additional-data conn))]
(when-let [file (db/exec-one! conn (sql/select :file {:id file-id}))]
(let [params (assoc file
:id (uuid/next)
:project-id (:default-project-id profile))]
(db/insert! conn :file params)
(:id file))))))
(defn update-file
"Apply a function to the data of one file. Optionally save the changes or not.
The function receives the decoded and migrated file data."
([system id f] (update-file system id f false))
([system id f save?]
(db/with-atomic [conn (:app.db/pool system)]
@@ -40,85 +74,115 @@
{:id (:id file)}))
(update file :data blob/decode)))))
(defn reset-file-data
[system id data]
(db/with-atomic [conn (:app.db/pool system)]
(db/update! conn :file
{:data data}
{:id id})))
(defn analyze-files
"Apply a function to all files in the database, reading them in batches. Do not change data.
The function receives an object with some properties of the file and the decoded data, and
an empty atom where it may accumulate statistics, if desired."
[system {:keys [sleep chunk-size max-chunks on-file]
:or {sleep 1000 chunk-size 10 max-chunks ##Inf}}]
(let [stats (atom {})]
(letfn [(retrieve-chunk [conn cursor]
(let [sql (str "select id, name, modified_at, data from file "
" where modified_at < ? and deleted_at is null "
" order by modified_at desc limit ?")]
(->> (db/exec! conn [sql cursor chunk-size])
(map #(update % :data blob/decode)))))
(defn get-file
[system id]
(-> (:app.db/pool system)
(db/get-by-id :file id)
(update :data app.util.blob/decode)
(update :data pmg/migrate-data)))
(process-chunk [chunk]
(loop [files chunk]
(when-let [file (first files)]
(on-file file stats)
(recur (rest files)))))]
(defn duplicate-file
"This is a raw version of duplication of file just only for forensic analysis"
[system file-id email]
(db/with-atomic [conn (:app.db/pool system)]
(when-let [profile (some->> (prof/retrieve-profile-data-by-email conn (str/lower email))
(prof/populate-additional-data conn))]
(when-let [file (db/exec-one! conn (sql/select :file {:id file-id}))]
(let [params (assoc file
:id (uuid/next)
:project-id (:default-project-id profile))]
(db/insert! conn :file params)
(:id file))))))
(db/with-atomic [conn (:app.db/pool system)]
(loop [cursor (dt/now)
chunks 0]
(when (< chunks max-chunks)
(let [chunk (retrieve-chunk conn cursor)]
(when-not (empty? chunk)
(let [cursor (-> chunk last :modified-at)]
(process-chunk chunk)
(Thread/sleep (inst-ms (dt/duration sleep)))
(recur cursor (inc chunks)))))))
@stats))))
(defn repair-orphaned-components
"We have detected some cases of component instances that are not nested, but
however they have not the :component-root? attribute (so the system considers
them nested). This script fixes this adding them the attribute.
(defn update-pages
"Apply a function to all pages of one file. The function receives a page and returns an updated page."
[data f]
(update data :pages-index d/update-vals f))
Use it with the update-file function above."
[data]
(let [update-page
(fn [page]
(prn "================= Page:" (:name page))
(letfn [(is-nested? [object]
(and (some? (:component-id object))
(nil? (:component-root? object))))
(defn update-shapes
"Apply a function to all shapes of one page The function receives a shape and returns an updated shape"
[page f]
(update page :objects d/update-vals f))
(is-instance? [object]
(some? (:shape-ref object)))
(get-parent [object]
(get (:objects page) (:parent-id object)))
;; ==== Specific fixes
(update-object [object]
(if (and (is-nested? object)
(not (is-instance? (get-parent object))))
(do
(prn "Orphan:" (:name object))
(assoc object :component-root? true))
object))]
(defn repair-orphaned-shapes
"There are some shapes whose parent has been deleted. This
function detects them and puts them as children of the root node."
([file _] ; to be called from analyze-files to search for files with the problem
(repair-orphaned-shapes (:data file)))
(update page :objects d/update-vals update-object)))]
([data]
(let [is-orphan? (fn [shape objects]
(and (some? (:parent-id shape))
(nil? (get objects (:parent-id shape)))))
(update data :pages-index d/update-vals update-page)))
update-page (fn [page]
(let [objects (:objects page)
orphans (set (filter #(is-orphan? % objects) (vals objects)))]
(if (seq orphans)
(do
(prn (:id data) "file has" (count orphans) "broken shapes")
(-> page
(update-shapes (fn [shape]
(if (orphans shape)
(assoc shape :parent-id uuid/zero)
shape)))
(update-in [:objects uuid/zero :shapes]
(fn [shapes] (into shapes (map :id orphans))))))
page)))]
(defn repair-idless-components
"There are some files that contains components with no :id attribute.
This function detects them and repairs it.
(update-pages data update-page))))
Use it with the update-file function above."
[data]
(letfn [(update-component [id component]
(if (nil? (:id component))
(do
(prn (:id data) "Broken component" (:name component) id)
(assoc component :id id))
component))]
(update data :components #(d/mapm update-component %))))
;; DO NOT DELETE already used scripts, could be taken as templates for easyly writing new ones
;; -------------------------------------------------------------------------------------------
(defn analyze-idless-components
"Scan all files to check if there are any one with idless components.
(Does not save the changes, only used to detect affected files)."
[file _]
(repair-idless-components (:data file)))
;; (defn repair-orphaned-components
;; "We have detected some cases of component instances that are not nested, but
;; however they have not the :component-root? attribute (so the system considers
;; them nested). This script fixes this adding them the attribute.
;;
;; Use it with the update-file function above."
;; [data]
;; (let [update-page
;; (fn [page]
;; (prn "================= Page:" (:name page))
;; (letfn [(is-nested? [object]
;; (and (some? (:component-id object))
;; (nil? (:component-root? object))))
;;
;; (is-instance? [object]
;; (some? (:shape-ref object)))
;;
;; (get-parent [object]
;; (get (:objects page) (:parent-id object)))
;;
;; (update-object [object]
;; (if (and (is-nested? object)
;; (not (is-instance? (get-parent object))))
;; (do
;; (prn "Orphan:" (:name object))
;; (assoc object :component-root? true))
;; object))]
;;
;; (update page :objects d/update-vals update-object)))]
;;
;; (update data :pages-index d/update-vals update-page)))
;; (defn check-image-shapes
;; [{:keys [data] :as file} stats]
@@ -138,32 +202,3 @@
;; (when @affected?
;; (swap! stats update :affected-files (fnil inc 0)))))
(defn analyze-files
[system {:keys [sleep chunk-size max-chunks on-file]
:or {sleep 1000 chunk-size 10 max-chunks ##Inf}}]
(let [stats (atom {})]
(letfn [(retrieve-chunk [conn cursor]
(let [sql (str "select id, name, modified_at, data from file "
" where modified_at < ? and deleted_at is null "
" order by modified_at desc limit ?")]
(->> (db/exec! conn [sql cursor chunk-size])
(map #(update % :data blob/decode)))))
(process-chunk [chunk]
(loop [items chunk]
(when-let [item (first items)]
(on-file item stats)
(recur (rest items)))))]
(db/with-atomic [conn (:app.db/pool system)]
(loop [cursor (dt/now)
chunks 0]
(when (< chunks max-chunks)
(let [chunk (retrieve-chunk conn cursor)]
(when-not (empty? chunk)
(let [cursor (-> chunk last :modified-at)]
(process-chunk chunk)
(Thread/sleep (inst-ms (dt/duration sleep)))
(recur cursor (inc chunks)))))))
@stats))))

View File

@@ -166,6 +166,7 @@
(dm/export gtr/update-group-selrect)
(dm/export gtr/update-mask-selrect)
(dm/export gtr/resize-modifiers)
(dm/export gtr/change-orientation-modifiers)
(dm/export gtr/rotation-modifiers)
(dm/export gtr/merge-modifiers)
(dm/export gtr/transform-shape)

View File

@@ -28,3 +28,11 @@
[shape]
(gpr/points->selrect (position-data-points shape)))
(defn overlaps-position-data?
"Checks if the given position data is inside the shape"
[{:keys [points]} position-data]
(let [bounding-box (gpr/points->selrect points)
fix-rect #(assoc % :y (- (:y %) (:height %)))]
(->> position-data
(some #(gpr/overlaps-rects? bounding-box (fix-rect %)))
(boolean))))

View File

@@ -426,6 +426,31 @@
:resize-transform shape-transform
:resize-transform-inverse shape-transform-inv}))
(defn change-orientation-modifiers
[shape orientation]
(us/assert map? shape)
(us/verify #{:horiz :vert} orientation)
(let [width (:width shape)
height (:height shape)
new-width (if (= orientation :horiz) (max width height) (min width height))
new-height (if (= orientation :horiz) (min width height) (max width height))
shape-transform (:transform shape)
shape-transform-inv (:transform-inverse shape)
shape-center (gco/center-shape shape)
{sr-width :width sr-height :height} (:selrect shape)
origin (cond-> (gpt/point (:selrect shape))
(some? shape-transform)
(transform-point-center shape-center shape-transform))
scalev (gpt/divide (gpt/point new-width new-height)
(gpt/point sr-width sr-height))]
{:resize-vector scalev
:resize-origin origin
:resize-transform shape-transform
:resize-transform-inverse shape-transform-inv}))
(defn rotation-modifiers
[shape center angle]
(let [displacement (let [shape-center (gco/center-shape shape)]

View File

@@ -372,8 +372,8 @@
(assert-page-id changes)
(assert-objects changes)
(let [page-id (::page-id (meta changes))
objects (lookup-objects changes)
objects (lookup-objects changes)
xform (comp
(mapcat #(cons % (cph/get-parent-ids objects %)))
(map (d/getf objects))
@@ -409,7 +409,8 @@
resize-parent
(fn [changes parent]
(let [children (->> parent :shapes (map (d/getf objects)))
(let [objects (lookup-objects changes)
children (->> parent :shapes (map (d/getf objects)))
resized-parent (cond
(empty? children) ;; a parent with no children will be deleted,
nil ;; so it does not need resize
@@ -434,12 +435,12 @@
(if (seq rops)
(-> changes
(update :redo-changes conj (assoc change :operations rops))
(update :undo-changes d/preconj (assoc change :operations uops)))
(update :undo-changes d/preconj (assoc change :operations uops))
(apply-changes-local))
changes))
changes)))]
(-> (reduce resize-parent changes all-parents)
(apply-changes-local))))
(reduce resize-parent changes all-parents)))
;; Library changes

View File

@@ -9,7 +9,7 @@
[app.common.colors :as clr]
[app.common.uuid :as uuid]))
(def file-version 18)
(def file-version 19)
(def default-color clr/gray-20)
(def root uuid/zero)

View File

@@ -10,6 +10,7 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.text :as gsht]
[app.common.logging :as l]
[app.common.math :as mth]
[app.common.pages :as cp]
@@ -415,5 +416,21 @@
(update :pages-index d/update-vals update-container)
(update :components d/update-vals update-container))))
(defmethod migrate 19
[data]
(letfn [(update-object [object]
(cond-> object
(and (cph/text-shape? object)
(d/not-empty? (:position-data object))
(not (gsht/overlaps-position-data? object (:position-data object))))
(dissoc :position-data)))
(update-container [container]
(update container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
(update :components d/update-vals update-container))))
;; TODO: pending to do a migration for delete already not used fill
;; and stroke props. This should be done for >1.14.x version.

View File

@@ -13,19 +13,32 @@
;;; SHADOW EFFECT
(s/def ::id uuid?)
(s/def ::id (s/nilable uuid?))
(s/def ::style #{:drop-shadow :inner-shadow})
(s/def ::color ::color/color)
(s/def ::offset-x ::us/safe-number)
(s/def ::offset-y ::us/safe-number)
(s/def ::blur ::us/safe-number)
(s/def ::spread ::us/safe-number)
(s/def ::hidden boolean?)
(s/def ::color string?)
(s/def ::opacity ::us/safe-number)
(s/def ::gradient (s/nilable ::color/gradient))
(s/def ::file-id (s/nilable uuid?))
(s/def ::ref-id (s/nilable uuid?))
(s/def :shadow/color
(s/keys :opt-un [::color
::opacity
::gradient
::file-id
::id]))
(s/def ::shadow-props
(s/keys :req-un [:internal.shadow/id
:internal.shadow/style
:internal.shadow/color
:shadow/color
:internal.shadow/offset-x
:internal.shadow/offset-y
:internal.shadow/blur

View File

@@ -28,6 +28,7 @@ RUN set -ex; \
libasound2 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libatomic1 \
libcairo2 \
libcups2 \
libdbus-1-3 \

View File

@@ -79,3 +79,6 @@ PENPOT_FLAGS="enable-registration"
# PENPOT_LDAP_ATTRS_FULLNAME=cn
# PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto
# PENPOT_LOGIN_WITH_LDAP=true
# Exporter
PENPOT_DOMAIN_WHITE_LIST=localhost:9001

View File

@@ -44,6 +44,8 @@ services:
penpot-exporter:
image: "penpotapp/exporter:latest"
env_file:
- config.env
environment:
# Don't touch it; this uses internal docker network to
# communicate with the frontend.

View File

@@ -38,6 +38,8 @@ http {
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json;
resolver 127.0.0.11;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;

View File

@@ -26,7 +26,7 @@
:http-server-port 6061
:http-server-host "localhost"
:redis-uri "redis://redis/0"
:exporter-domain-whitelist #{"localhost:3449"}})
:domain-white-list #{"localhost:3449"}})
(s/def ::http-server-port ::us/integer)
(s/def ::http-server-host ::us/string)
@@ -45,7 +45,7 @@
::http-server-host
::browser-pool-max
::browser-pool-min
::domain-whitelist]))
::domain-white-list]))
(defn- read-env
[prefix]

View File

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

View File

@@ -22,7 +22,7 @@
[promesa.core :as p]))
(defn render
[{:keys [file-id page-id token scale type uri objects] :as params} on-object]
[{:keys [file-id page-id token scale type objects] :as params} on-object]
(letfn [(prepare-options [uri]
#js {:screen #js {:width bw/default-viewport-width
:height bw/default-viewport-height}
@@ -63,6 +63,6 @@
(on-object (assoc object :path path))
(p/recur (rest objects))))))]
(let [base-uri (or uri (cf/get :public-uri))]
(let [base-uri (cf/get :public-uri)]
(bw/exec! (prepare-options base-uri)
(partial render base-uri)))))

View File

@@ -114,7 +114,7 @@
:height height}))
(defn render
[{:keys [page-id file-id objects token scale suffix type uri]} on-object]
[{:keys [page-id file-id objects token scale type]} on-object]
(letfn [(convert-to-ppm [pngpath]
(l/trace :fn :convert-to-ppm)
(let [basepath (path/dirname pngpath)
@@ -363,7 +363,7 @@
:render-embed true
:object-id (mapv :id objects)
:route "objects"}
uri (-> (or uri (cf/get :public-uri))
uri (-> (cf/get :public-uri)
(assoc :path "/render.html")
(assoc :query (u/map->query-string params)))]
(bw/exec! (prepare-options uri)

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

View File

@@ -1,3 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M78.822.204l-3.3 3.3L56.69 22.335l2.698 9.296-17.315 17.316-18.998-12.4L0 59.624l32.865 32.865-31.33 31.33 6.598 6.599 31.33-31.33 33.001 33 23.076-23.075-12.128-18.726 17.316-17.315 9.432 2.834 22.132-22.13zm0 13.197l40.274 40.274-9.983 10.123-9.369-2.771-28.33 28.33 11.919 18.665-10.869 10.869-59.268-59.269 10.868-10.868 18.926 12.18 28.331-28.33-2.578-9.177z"/>
<svg width="12" xmlns="http://www.w3.org/2000/svg" height="12" x="767" viewBox="0 0 500 499.172" y="920.67">
<path d="M325.318.012c-21.828-.283-42.138 15.34-48.74 35.798-15.498 31.788-36.62 62.656-67.738 80.85-46.02 26.248-99.73 35.59-152.188 35.65-11.824-.026-24.336 1.564-34 9.065-18.02 12.488-27.52 36.96-20.133 58.034 4.15 13.85 14.838 24.125 25.197 33.582l86.5 86.5C84.488 381.137 30.993 456.543 1.26 498.187c41.623-29.727 117.01-83.215 158.632-112.942 37.542 36.62 68.373 70.025 106.956 105.56 23.195 15.987 59.377 8.335 73.035-16.723 7.6-12.733 8.225-27.804 7.5-42.23 1.366-50.12 11.25-101.404 37.675-144.67 20.658-31.714 53.59-52.978 87.635-68.023 20.972-10.427 31.76-36.413 25.57-58.77-2.664-11.118-9.64-20.31-17.897-27.873-37.25-36.595-73.2-74.495-110.7-110.84-7.227-6.593-13.718-14.767-23.268-17.904-6.682-2.644-13.898-3.908-21.08-3.758zm-.26 36.837c45.35 46.82 90.704 93.64 136.056 140.46-40.437 18.15-79.907 42.182-106.303 78.613-30.216 43.44-43.98 96.178-48.765 148.346-1.852 21.542-2.097 43.256-1.726 64.933-91.546-91.52-183.09-183.044-274.634-274.564 60.078 2.462 121.696-4.117 176.98-28.967 38.773-15.762 70.437-46.032 91.712-81.66 9.637-15.522 17.813-31.905 25.36-48.525z" />
</svg>

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="48.265" style="-webkit-print-color-adjust:exact" viewBox="-3.536 -3.536 64 48.265">
<path d="M-3.536 20.597V-3.536h64V44.73h-64zm59.37 0V1.096l-27.356.014-27.354.014L1.11 20.54c-.01 10.68 0 19.45.012 19.488.02.055 5.546.07 27.369.07h27.342V20.596zM12.701 31.254V28.94h31.525v4.631H12.702zM7.159 22.43c-.02-.02-.037-1.062-.037-2.316v-2.278H14.543v4.631H10.87c-2.02 0-3.69-.016-3.71-.037zm12.071-2.278v-2.316h7.422l-.015 2.302-.014 2.301-3.696.015-3.697.014zm12.052 0v-2.316h7.423l-.014 2.302-.015 2.301-3.697.015-3.697.014zm12.053 0v-2.316h7.42v4.631h-7.42zM7.135 9.005l.015-2.302 3.697-.014 3.696-.014v4.632H7.121zm12.095-.014V6.675l3.697.014 3.696.014.014 2.302.015 2.302H19.23zm12.052 0V6.675H38.703v4.632h-7.42zm12.053 0V6.675h7.42v4.632h-7.42z"/>
</svg>

After

Width:  |  Height:  |  Size: 818 B

View File

@@ -3,8 +3,9 @@
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
var parent = this.parentNode,
parentComputedStyle = window.getComputedStyle(parent, null),
var parent = this.parentNode;
if (parent) {
var parentComputedStyle = window.getComputedStyle(parent, null),
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
@@ -13,14 +14,15 @@
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom;
if ((overTop || overBottom) && centerIfNeeded) {
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
}
if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
if ((overTop || overBottom) && centerIfNeeded) {
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
}
if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
}
}
};
}

View File

@@ -274,7 +274,7 @@ textarea {
max-width: 85%;
overflow: hidden;
text-overflow: ellipsis;
line-height: 15px;
line-height: 16px;
font-size: 14px;
color: $color-black;
}

View File

@@ -7,9 +7,6 @@
.left-toolbar {
background-color: $color-gray-50;
}
.left-toolbar-inside {
align-items: center;
border-right: 1px solid $color-gray-60;
display: flex;
@@ -25,39 +22,46 @@
margin: 0;
li {
align-items: center;
background-color: transparent;
border: 1px solid transparent;
cursor: pointer;
display: flex;
flex-shrink: 0;
height: 48px;
justify-content: center;
position: relative;
width: 48px;
color: $color-gray-20;
svg {
fill: $color-gray-20;
height: 16px;
width: 16px;
}
&:hover {
background-color: $color-primary;
color: $color-gray-50;
button {
background-color: transparent;
border: none;
cursor: pointer;
height: 48px;
width: 48px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: inherit;
svg {
fill: $color-gray-50;
fill: $color-gray-20;
height: 16px;
width: 16px;
}
}
&.selected {
background-color: $color-gray-60;
color: $color-primary;
&:hover {
background-color: $color-primary;
color: $color-gray-50;
svg {
fill: $color-primary;
svg {
fill: $color-gray-50;
}
}
&.selected {
background-color: $color-gray-60;
color: $color-primary;
svg {
fill: $color-primary;
}
}
&.separator {
border-top: 1px solid $color-gray-60;
}
}
}

View File

@@ -1231,7 +1231,7 @@
}
.btn-primary {
width: 117px;
min-width: 117px;
}
}
@@ -1254,7 +1254,18 @@
}
.custom-select {
width: 118px;
width: 180px;
overflow: hidden;
justify-content: normal;
select {
height: auto;
}
}
svg {
width: 12px;
height: 12px;
fill: $color-gray-20;
}
}

View File

@@ -323,12 +323,41 @@
border: 2px solid $color-primary;
}
.grid-placeholder {
border: 2px solid $color-gray-20;
border-radius: 4px;
}
.drop-space {
height: 10px;
}
.typography-container {
position: relative;
}
.drag-counter {
position: absolute;
top: 5px;
left: 4px;
width: 16px;
height: 16px;
background-color: $color-primary;
border-radius: 50%;
color: $color-black;
font-size: $fs12;
display: flex;
justify-content: center;
align-items: center;
}
.asset-title + .asset-enum {
margin-top: $size-2;
}
.asset-enum {
.enum-item {
position: relative;
display: flex;
align-items: center;
margin-bottom: $size-2;
@@ -370,6 +399,10 @@
.enum-item.selected {
color: $color-primary;
}
.grid-placeholder {
margin-bottom: 5px;
}
}
/* TODO: see if this is useful, or is better to leave only
@@ -390,6 +423,7 @@
font-size: $fs12;
color: $color-white;
cursor: pointer;
position: relative;
& span {
margin-left: $size-1;
@@ -418,6 +452,15 @@
background-color: $color-gray-60;
}
}
.dragging {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: color.adjust($color-primary, $alpha: -0.5);
}
}
}

View File

@@ -1450,7 +1450,8 @@
.input-select {
font-size: $fs12;
margin: 0 $size-1;
margin: 0 $size-1 $size-2 $size-1;
padding: 0 $size-1;
}
svg {
@@ -1471,6 +1472,7 @@
.fix-when {
font-size: $fs12;
cursor: pointer;
display: flex;
span {
margin-left: $size-2;
@@ -1540,3 +1542,44 @@
margin-right: $size-2;
}
}
.expand-colors {
cursor: pointer;
display: flex;
.text {
color: $color-gray-30;
font-size: 0.75rem;
padding-left: 10px;
}
svg {
width: 16px;
height: 16px;
fill: $color-gray-20;
stroke: $color-gray-20;
}
}
.selected-colors {
.color-data {
margin-bottom: 0;
padding-bottom: 5px;
svg {
visibility: hidden;
}
.percentil {
margin-bottom: 0;
}
}
.color-data:hover {
background-color: $color-gray-60;
svg {
visibility: visible;
}
}
}

View File

@@ -292,3 +292,183 @@ button.collapse-sidebar {
height: 100%;
overflow: hidden;
}
.shortcuts {
.shortcuts-header {
display: flex;
height: 40px;
background-color: $color-gray-60;
.shortcuts-title {
color: $color-white;
font-size: $fs12;
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
svg {
height: 18px;
width: 18px;
transform: rotate(45deg);
fill: $color-gray-20;
}
}
.shortcuts-close-button {
display: flex;
justify-content: center;
background-color: transparent;
border: none;
cursor: pointer;
padding: 2px 0 2px 15px;
position: absolute;
top: 8px;
svg {
height: 18px;
width: 18px;
transform: rotate(45deg);
fill: $color-gray-20;
}
}
}
.search-field {
height: 60px;
display: flex;
justify-content: center;
align-items: center;
padding: 12px 10px;
.search-box {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid $color-gray-30;
border-radius: 2px;
width: 100%;
.input-text {
margin: 0;
background: $color-gray-50;
width: 100%;
color: $color-white;
&:focus {
border-bottom: none;
}
}
.icon-wrapper {
display: flex;
justify-content: center;
align-items: center;
&.close {
transform: rotate(45deg);
}
}
svg {
width: 16px;
height: 16px;
margin: 0 7px;
cursor: pointer;
fill: $color-gray-20;
}
}
}
.shortcut-list {
border-top: 1px solid $color-gray-60;
padding: 10px;
overflow-y: auto;
height: 90%;
margin-bottom: 15px;
.section-title {
background-color: $color-gray-60;
padding: 4px 0;
}
.section-title,
.subsection-title {
display: flex;
cursor: pointer;
margin-top: 4px;
font-size: $fs12;
.section-name {
color: $color-white;
}
.collapesed-shortcuts {
padding: 0 10px;
svg {
height: 8px;
width: 8px;
fill: $color-gray-20;
}
&.open {
svg {
transform: rotate(90deg);
}
}
}
.shortcut-count {
padding-left: 5px;
color: $color-white;
}
}
.subsection-title {
padding: 4px 0px;
.subsection-name {
color: $color-white;
}
}
.section-title,
.subsection-title {
&:hover {
background-color: $color-primary;
.subsection-name,
.section-name {
color: $color-gray-60;
}
svg {
fill: $color-gray-60;
}
}
}
.shortcut-name {
border: 1px solid $color-gray-60;
border-radius: 4px;
padding: 7px;
display: flex;
justify-content: space-between;
margin-top: 4px;
color: $color-white;
font-size: $fs12;
.keys {
flex-grow: 1;
display: flex;
align-items: flex-start;
justify-content: flex-end;
}
.char-box {
min-width: 15px;
background-color: $color-white;
color: $color-black;
border-radius: 3px;
padding: 2px 5px;
font-size: $fs11;
font-weight: 600;
margin: 0 2px;
text-transform: capitalize;
display: inline-block;
text-align: center;
}
.space {
margin: 0 3px;
}
}
}
.not-found {
background-color: $color-gray-60;
padding: 4px 0;
color: $color-white;
display: flex;
justify-content: center;
margin-top: 4px;
font-size: $fs12;
}
}

View File

@@ -152,7 +152,7 @@
position: absolute;
top: 40px;
left: 40px;
width: 183px;
width: 195px;
z-index: 12;
background-color: $color-white;
@@ -192,7 +192,7 @@
background-color: $color-primary-lighter;
}
&.feedback {
&.info {
border-top: 1px solid $color-gray-10;
}
}
@@ -200,7 +200,7 @@
.sub-menu {
position: absolute;
left: 230px;
left: 238px;
width: 270px;
z-index: 12;
background-color: $color-white;
@@ -234,6 +234,10 @@
top: 150px;
}
&.help-info {
top: 186px;
}
li {
cursor: pointer;
font-size: $fs14;
@@ -254,6 +258,10 @@
&:hover {
background-color: $color-primary-lighter;
}
&.separator {
border-top: 1px solid $color-gray-10;
}
}
}

View File

@@ -79,7 +79,8 @@
(dissoc :dashboard-shared-files)
(dissoc :dashboard-recent-files)
(dissoc :dashboard-team-members)
(dissoc :dashboard-team-stats)))))
(dissoc :dashboard-team-stats)
(update :workspace-global dissoc :default-font)))))
ptk/WatchEvent
(watch [_ state stream]
@@ -326,11 +327,9 @@
;; --- EVENT: create-team-with-invitations
;; NOTE: right now, it only handles a single email, in a near future
;; this will be changed to the ability to specify multiple emails.
(defn create-team-with-invitations
[{:keys [name email role] :as params}]
[{:keys [name emails role] :as params}]
(us/assert string? name)
(ptk/reify ::create-team-with-invitations
ptk/WatchEvent
@@ -339,7 +338,7 @@
:or {on-success identity
on-error rx/throw}} (meta params)
params {:name name
:emails #{email}
:emails #{emails}
:role role}]
(->> (rp/mutation! :create-team-and-invite-members params)
(rx/tap on-success)

View File

@@ -13,19 +13,23 @@
(def shortcuts
{:go-to-search {:tooltip (ds/meta "F")
:command (ds/c-mod "f")
:fn (st/emitf (dd/go-to-search))}
:subsections [:navigation-dashboard]
:fn #(st/emit! (dd/go-to-search))}
:go-to-drafts {:tooltip "G D"
:command "g d"
:fn (st/emitf (dd/go-to-drafts))}
:subsections [:navigation-dashboard]
:fn #(st/emit! (dd/go-to-drafts))}
:go-to-libs {:tooltip "G L"
:command "g l"
:fn (st/emitf (dd/go-to-libs))}
:subsections [:navigation-dashboard]
:fn #(st/emit! (dd/go-to-libs))}
:create-new-project {:tooltip "+"
:command "+"
:fn (st/emitf (dd/create-element))}})
:subsections [:general-dashboard]
:fn #(st/emit! (dd/create-element))}})
(defn get-tooltip [shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))

View File

@@ -157,8 +157,13 @@
accepting invitation, or third party auth signup or singin."
[profile]
(letfn [(get-redirect-event []
(let [team-id (:default-team-id profile)]
(rt/nav' :dashboard-projects {:team-id team-id})))]
(let [team-id (:default-team-id profile)
redirect-url (:redirect-url @storage)]
(if (some? redirect-url)
(do
(swap! storage dissoc :redirect-url)
(.replace js/location redirect-url))
(rt/nav' :dashboard-projects {:team-id team-id}))))]
(ptk/reify ::logged-in
IDeref
(-deref [_] profile)

View File

@@ -13,50 +13,62 @@
(def shortcuts
{:increase-zoom {:tooltip "+"
:command "+"
:fn (st/emitf dv/increase-zoom)}
:subsections [:zoom-viewer]
:fn #(st/emit! dv/increase-zoom)}
:decrease-zoom {:tooltip "-"
:command "-"
:fn (st/emitf dv/decrease-zoom)}
:subsections [:zoom-viewer]
:fn #(st/emit! dv/decrease-zoom)}
:select-all {:tooltip (ds/meta "A")
:command (ds/c-mod "a")
:fn (st/emitf (dv/select-all))}
:subsections [:general-viewer]
:fn #(st/emit! (dv/select-all))}
:reset-zoom {:tooltip (ds/shift "0")
:command "shift+0"
:fn (st/emitf dv/reset-zoom)}
:subsections [:zoom-viewer]
:fn #(st/emit! dv/reset-zoom)}
:toggle-zoom-style {:tooltip "F"
:command "f"
:fn (st/emitf dv/toggle-zoom-style)}
:subsections [:zoom-viewer]
:fn #(st/emit! dv/toggle-zoom-style)}
:toogle-fullscreen {:tooltip (ds/shift "F")
:command "shift+f"
:fn (st/emitf dv/toggle-fullscreen)}
:subsections [:zoom-viewer]
:fn #(st/emit! dv/toggle-fullscreen)}
:next-frame {:tooltip ds/left-arrow
:command "left"
:fn (st/emitf dv/select-prev-frame)}
:subsections [:general-viewer]
:fn #(st/emit! dv/select-prev-frame)}
:prev-frame {:tooltip ds/right-arrow
:command "right"
:fn (st/emitf dv/select-next-frame)}
:subsections [:general-viewer]
:fn #(st/emit! dv/select-next-frame)}
:open-handoff {:tooltip "G H"
:command "g h"
:subsections [:navigation-viewer]
:fn #(st/emit! (dv/go-to-section :handoff))}
:open-comments {:tooltip "G C"
:command "g c"
:subsections [:navigation-viewer]
:fn #(st/emit! (dv/go-to-section :comments))}
:open-interactions {:tooltip "G V"
:command "g v"
:subsections [:navigation-viewer]
:fn #(st/emit! (dv/go-to-section :interactions))}
:open-workspace {:tooltip "G W"
:command "g w"
:subsections [:navigation-viewer]
:fn #(st/emit! (dv/go-to-workspace))}})
(defn get-tooltip [shortcut]

View File

@@ -150,7 +150,11 @@
:workspace-undo {}
:workspace-project project
:workspace-file (assoc file :initialized true)
:workspace-data (:data file)
:workspace-data (-> (:data file)
;; DEBUG: Uncomment this to try out migrations in local without changing
;; the version number
#_(assoc :version 17)
#_(app.common.pages.migrations/migrate-data 19))
:workspace-libraries (d/index-by :id libraries)))
ptk/WatchEvent
@@ -1694,6 +1698,7 @@
(dm/export dwt/start-resize)
(dm/export dwt/update-dimensions)
(dm/export dwt/change-orientation)
(dm/export dwt/start-rotate)
(dm/export dwt/increase-rotation)
(dm/export dwt/start-move-selected)

View File

@@ -284,6 +284,15 @@
(assoc :strokes []))
(assoc-in [:strokes index] new-attrs))))))))))
(defn change-shadow
[ids attrs index]
(ptk/reify ::change-shadow
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dch/update-shapes ids (fn [shape]
(let [new-attrs (merge (get-in shape [:shadow index :color]) attrs)]
(assoc-in shape [:shadow index :color] new-attrs))))))))
(defn add-stroke
[ids stroke]
(ptk/reify ::add-stroke
@@ -396,6 +405,36 @@
(-> state
(assoc-in [:workspace-global :editing-stop] spot)))))
(defn color-att->text
[color]
{:fill-color (:color color)
:fill-opacity (:opacity color)
:fill-color-ref-id (:id color)
:fill-color-ref-file (:file-id color)
:fill-color-gradient (:gradient color)})
(defn change-text-color
[old-color new-color index node]
(let [fills (:fills node)
parsed-color (d/without-nils (color-att->text old-color))
parsed-new-color (d/without-nils (color-att->text new-color))
has-color? (d/index-of fills parsed-color)]
(cond-> node
(some? has-color?)
(assoc-in [:fills index] parsed-new-color))))
(defn change-color-in-selected
[new-color shapes-by-color old-color]
(ptk/reify ::change-color-in-selected
ptk/WatchEvent
(watch [_ _ _]
(->> (rx/from shapes-by-color)
(rx/map (fn [shape] (case (:prop shape)
:fill (change-fill [(:shape-id shape)] new-color (:index shape))
:stroke (change-stroke [(:shape-id shape)] new-color (:index shape))
:shadow (change-shadow [(:shape-id shape)] new-color (:index shape))
:content (dwt/update-text-with-function (:shape-id shape) (partial change-text-color old-color new-color (:index shape))))))))))
(defn apply-color-from-palette
[color is-alt?]
(ptk/reify ::apply-color-from-palette
@@ -415,4 +454,4 @@
ids (mapcat #(select-shapes-for-color % objects) selected-obj)]
(if is-alt?
(rx/of (change-stroke ids (merge uc/empty-color color) 0))
(rx/of (change-fill ids (merge uc/empty-color color) 0)))))))
(rx/of (change-fill ids (merge uc/empty-color color) 0)))))))

View File

@@ -124,7 +124,7 @@
(let [edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
;; Editors handle their own undo's
(when-not (or (some? edition) (and (not-empty drawing) (nil? (:object drawing))))
(when (and (nil? edition) (nil? (:object drawing)))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
@@ -420,19 +420,26 @@
(reverse)
(into (d/ordered-set)))
empty-parents-xform
(comp
(map (fn [id] (get objects id)))
(map (fn [{:keys [shapes type] :as obj}]
(when (and (= :group type)
(zero? (count (remove #(contains? ids %) shapes))))
obj)))
(take-while some?)
(map :id))
find-all-empty-parents (fn recursive-find-empty-parents [empty-parents]
(let [all-ids (into empty-parents ids)
empty-parents-xform
(comp
(map (fn [id] (get objects id)))
(map (fn [{:keys [shapes type] :as obj}]
(when (and (= :group type)
(zero? (count (remove #(contains? all-ids %) shapes))))
obj)))
(take-while some?)
(map :id))
calculated-empty-parents (into #{} empty-parents-xform all-parents)]
(if (= empty-parents calculated-empty-parents)
empty-parents
(recursive-find-empty-parents calculated-empty-parents))))
empty-parents
;; Any parent whose children are all deleted, must be deleted too.
(into (d/ordered-set) empty-parents-xform all-parents)
(into (d/ordered-set) (find-all-empty-parents #{}))
changes (-> (pcb/empty-changes it page-id)
(pcb/with-page page)
@@ -448,13 +455,13 @@
(pcb/update-shapes (map :id interacting-shapes)
(fn [shape]
(update shape :interactions
(fn [interactions]
(when interactions
(d/removev #(and (csi/has-destination %)
(contains? ids (:destination %)))
interactions))))))
(fn [interactions]
(when interactions
(d/removev #(and (csi/has-destination %)
(contains? ids (:destination %)))
interactions))))))
(cond->
(seq starting-flows)
(seq starting-flows)
(pcb/update-page-option :flows (fn [flows]
(reduce #(csp/remove-flow %1 (:id %2))
flows

View File

@@ -126,23 +126,40 @@
(update [_ state]
(assoc-in state [:workspace-local :color-for-rename] nil))))
(defn- do-update-color
[it state color file-id]
(let [data (get state :workspace-data)
[path name] (cph/parse-path-name (:name color))
color (assoc color :path path :name name)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/update-color color))]
(rx/of (dwu/start-undo-transaction)
(dch/commit-changes changes)
(sync-file (:current-file-id state) file-id)
(dwu/commit-undo-transaction))))
(defn update-color
[color file-id]
(us/assert ::spec.color/color color)
(us/assert ::us/uuid file-id)
(ptk/reify ::update-color
ptk/WatchEvent
(watch [it state _]
(do-update-color it state color file-id))))
(defn rename-color
[file-id id new-name]
(us/assert ::us/uuid file-id)
(us/assert ::us/uuid id)
(us/assert ::us/string new-name)
(ptk/reify ::rename-color
ptk/WatchEvent
(watch [it state _]
(let [data (get state :workspace-data)
[path name] (cph/parse-path-name (:name color))
color (assoc color :path path :name name)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/update-color color))]
(rx/of (dwu/start-undo-transaction)
(dch/commit-changes changes)
(sync-file (:current-file-id state) file-id)
(dwu/commit-undo-transaction))))))
object (get-in data [:colors id])
new-object (assoc object :name new-name)]
(do-update-color it state new-object file-id)))))
(defn delete-color
[{:keys [id] :as params}]
@@ -183,6 +200,7 @@
(pcb/update-media new-object))]
(rx/of (dch/commit-changes changes))))))
(defn delete-media
[{:keys [id] :as params}]
(us/assert ::us/uuid id)
@@ -213,26 +231,39 @@
edit?
(assoc-in [:workspace-global :rename-typography] (:id typography))))))))))
(defn- do-update-tipography
[it state typography file-id]
(let [data (get state :workspace-data)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/update-typography typography))]
(rx/of (dwu/start-undo-transaction)
(dch/commit-changes changes)
(sync-file (:current-file-id state) file-id)
(dwu/commit-undo-transaction))))
(defn update-typography
[typography file-id]
(us/assert ::spec.typography/typography typography)
(us/assert ::us/uuid file-id)
(ptk/reify ::update-typography
ptk/WatchEvent
(watch [it state _]
(do-update-tipography it state typography file-id))))
(defn rename-typography
[file-id id new-name]
(us/assert ::us/uuid file-id)
(us/assert ::us/uuid id)
(us/assert ::us/string new-name)
(ptk/reify ::rename-typography
ptk/WatchEvent
(watch [it state _]
(let [data (get state :workspace-data)
[path name] (cph/parse-path-name (:name typography))
path (if (and (:path typography) (= "" path))
(:path typography)
path)
typography (assoc typography :path path :name name)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/update-typography typography))]
(rx/of (dwu/start-undo-transaction)
(dch/commit-changes changes)
(sync-file (:current-file-id state) file-id)
(dwu/commit-undo-transaction))))))
[path name] (cph/parse-path-name new-name)
object (get-in data [:typographies id])
new-object (assoc object :path path :name name)]
(do-update-tipography it state new-object file-id)))))
(defn delete-typography
[id]

View File

@@ -189,10 +189,14 @@
(s/def ::file-change-event
(s/keys :req-un [::type ::profile-id ::file-id ::session-id ::revn ::changes]))
(defn handle-file-change
[{:keys [file-id changes] :as msg}]
(us/assert ::file-change-event msg)
(ptk/reify ::handle-file-change
IDeref
(-deref [_] {:changes changes})
ptk/WatchEvent
(watch [_ _ _]
(let [position-data-operation?

View File

@@ -34,42 +34,52 @@
(def shortcuts
{:move-nodes {:tooltip "M"
:command "m"
:subsections [:path-editor]
:fn #(st/emit! (drp/change-edit-mode :move))}
:draw-nodes {:tooltip "P"
:command "p"
:subsections [:path-editor]
:fn #(st/emit! (drp/change-edit-mode :draw))}
:add-node {:tooltip (ds/shift "+")
:command "shift++"
:subsections [:path-editor]
:fn #(st/emit! (drp/add-node))}
:delete-node {:tooltip (ds/supr)
:command ["del" "backspace"]
:subsections [:path-editor]
:fn #(st/emit! (drp/remove-node))}
:merge-nodes {:tooltip (ds/meta "J")
:command (ds/c-mod "j")
:subsections [:path-editor]
:fn #(st/emit! (drp/merge-nodes))}
:join-nodes {:tooltip "J"
:command "j"
:subsections [:path-editor]
:fn #(st/emit! (drp/join-nodes))}
:separate-nodes {:tooltip "K"
:command "k"
:subsections [:path-editor]
:fn #(st/emit! (drp/separate-nodes))}
:make-corner {:tooltip "X"
:command "x"
:subsections [:path-editor]
:fn #(st/emit! (drp/make-corner))}
:make-curve {:tooltip "C"
:command "c"
:subsections [:path-editor]
:fn #(st/emit! (drp/make-curve))}
:snap-nodes {:tooltip (ds/meta "'")
:command (ds/c-mod "'")
:subsections [:path-editor]
:fn #(st/emit! (drp/toggle-snap))}
:escape {:tooltip (ds/esc)

View File

@@ -34,356 +34,464 @@
;; Shortcuts format https://github.com/ccampbell/mousetrap
(def base-shortcuts
{:toggle-layers {:tooltip (ds/alt "L")
:command (ds/a-mod "l")
:fn #(st/emit! (dw/go-to-layout :layers))}
:toggle-assets {:tooltip (ds/alt "I")
:command (ds/a-mod "i")
:fn #(st/emit! (dw/go-to-layout :assets))}
:toggle-history {:tooltip (ds/alt "H")
:command (ds/a-mod "h")
:fn #(st/emit! (dw/go-to-layout :document-history))}
:toggle-colorpalette {:tooltip (ds/alt "P")
:command (ds/a-mod "p")
:fn #(do (r/set-resize-type! :bottom)
(st/emit! (dw/remove-layout-flag :textpalette)
(toggle-layout-flag :colorpalette)))}
:toggle-textpalette {:tooltip (ds/alt "T")
:command (ds/a-mod "t")
:fn #(do (r/set-resize-type! :bottom)
(st/emit! (dw/remove-layout-flag :colorpalette)
(toggle-layout-flag :textpalette)))}
:toggle-rules {:tooltip (ds/meta-shift "R")
:command (ds/c-mod "shift+r")
:fn #(st/emit! (toggle-layout-flag :rules))}
:export-shapes {:tooltip (ds/meta-shift "E")
:command (ds/c-mod "shift+e")
:fn #(st/emit!
(de/show-workspace-export-dialog))}
:select-all {:tooltip (ds/meta "A")
:command (ds/c-mod "a")
:fn #(st/emit! (dw/select-all))}
:toggle-grid {:tooltip (ds/meta "'")
:command (ds/c-mod "'")
:fn #(st/emit! (toggle-layout-flag :display-grid))}
:toggle-snap-grid {:tooltip (ds/meta-shift "'")
:command (ds/c-mod "shift+'")
:fn #(st/emit! (toggle-layout-flag :snap-grid))}
:toggle-snap-guide {:tooltip (ds/meta-shift "G")
:command (ds/c-mod "shift+G")
:fn #(st/emit! (toggle-layout-flag :snap-guides))}
:toggle-alignment {:tooltip (ds/meta "\\")
:command (ds/c-mod "\\")
:fn #(st/emit! (toggle-layout-flag :dynamic-alignment))}
:toggle-scale-text {:tooltip "K"
:command "k"
:fn #(st/emit! (toggle-layout-flag :scale-text))}
:increase-zoom {:tooltip "+"
:command ["+" "="]
:fn #(st/emit! (dw/increase-zoom nil))}
:decrease-zoom {:tooltip "-"
:command ["-" "_"]
:fn #(st/emit! (dw/decrease-zoom nil))}
:group {:tooltip (ds/meta "G")
:command (ds/c-mod "g")
:fn #(st/emit! dw/group-selected)}
:ungroup {:tooltip (ds/shift "G")
:command "shift+g"
:fn #(st/emit! dw/ungroup-selected)}
:mask {:tooltip (ds/meta "M")
:command (ds/c-mod "m")
:fn #(st/emit! dw/mask-group)}
:unmask {:tooltip (ds/meta-shift "M")
:command (ds/c-mod "shift+m")
:fn #(st/emit! dw/unmask-group)}
:create-component {:tooltip (ds/meta "K")
:command (ds/c-mod "k")
:fn #(st/emit! (dwl/add-component))}
:detach-component {:tooltip (ds/meta-shift "K")
:command (ds/c-mod "shift+k")
:fn #(st/emit! dwl/detach-selected-components)}
:flip-vertical {:tooltip (ds/shift "V")
:command "shift+v"
:fn #(st/emit! (dw/flip-vertical-selected))}
:flip-horizontal {:tooltip (ds/shift "H")
:command "shift+h"
:fn #(st/emit! (dw/flip-horizontal-selected))}
:reset-zoom {:tooltip (ds/shift "0")
:command "shift+0"
:fn #(st/emit! dw/reset-zoom)}
:fit-all {:tooltip (ds/shift "1")
:command "shift+1"
:fn #(st/emit! dw/zoom-to-fit-all)}
:zoom-selected {:tooltip (ds/shift "2")
:command ["shift+2" "@" "\""]
:fn #(st/emit! dw/zoom-to-selected-shape)}
:duplicate {:tooltip (ds/meta "D")
:command (ds/c-mod "d")
:fn #(st/emit! (dw/duplicate-selected true))}
{;; EDIT
:undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z")
:subsections [:edit]
:fn #(st/emit! dwc/undo)}
:redo {:tooltip (ds/meta "Y")
:command [(ds/c-mod "shift+z") (ds/c-mod "y")]
:subsections [:edit]
:fn #(st/emit! dwc/redo)}
:clear-undo {:tooltip (ds/meta "Q")
:command (ds/c-mod "q")
:subsections [:edit]
:fn #(st/emit! dwu/reinitialize-undo)}
:draw-frame {:tooltip "A"
:command "a"
:fn #(st/emit! (dwd/select-for-drawing :frame))}
:move {:tooltip "V"
:command "v"
:fn #(st/emit! :interrupt)}
:draw-rect {:tooltip "R"
:command "r"
:fn #(st/emit! (dwd/select-for-drawing :rect))}
:draw-ellipse {:tooltip "E"
:command "e"
:fn #(st/emit! (dwd/select-for-drawing :circle))}
:draw-text {:tooltip "T"
:command "t"
:fn #(st/emit! dwtxt/start-edit-if-selected
(dwd/select-for-drawing :text))}
:draw-path {:tooltip "P"
:command "p"
:fn #(st/emit! (dwd/select-for-drawing :path))}
:draw-curve {:tooltip (ds/shift "C")
:command "shift+c"
:fn #(st/emit! (dwd/select-for-drawing :curve))}
:add-comment {:tooltip "C"
:command "c"
:fn #(st/emit! (dwd/select-for-drawing :comments))}
:insert-image {:tooltip (ds/shift "K")
:command "shift+k"
:fn #(-> "image-upload" dom/get-element dom/click)}
:copy {:tooltip (ds/meta "C")
:command (ds/c-mod "c")
:subsections [:edit]
:fn #(st/emit! (dw/copy-selected))}
:cut {:tooltip (ds/meta "X")
:command (ds/c-mod "x")
:subsections [:edit]
:fn #(st/emit! (dw/copy-selected)
(dw/delete-selected))}
:paste {:tooltip (ds/meta "V")
:disabled true
:command (ds/c-mod "v")
:subsections [:edit]
:fn (constantly nil)}
:delete {:tooltip (ds/supr)
:command ["del" "backspace"]
:subsections [:edit]
:fn #(st/emit! (dw/delete-selected))}
:bring-forward {:tooltip (ds/meta ds/up-arrow)
:command (ds/c-mod "up")
:fn #(st/emit! (dw/vertical-order-selected :up))}
:bring-backward {:tooltip (ds/meta ds/down-arrow)
:command (ds/c-mod "down")
:fn #(st/emit! (dw/vertical-order-selected :down))}
:bring-front {:tooltip (ds/meta-shift ds/up-arrow)
:command (ds/c-mod "shift+up")
:fn #(st/emit! (dw/vertical-order-selected :top))}
:bring-back {:tooltip (ds/meta-shift ds/down-arrow)
:command (ds/c-mod "shift+down")
:fn #(st/emit! (dw/vertical-order-selected :bottom))}
:move-fast-up {:tooltip (ds/shift ds/up-arrow)
:command "shift+up"
:fn #(st/emit! (dwt/move-selected :up true))}
:move-fast-down {:tooltip (ds/shift ds/down-arrow)
:command "shift+down"
:fn #(st/emit! (dwt/move-selected :down true))}
:move-fast-right {:tooltip (ds/shift ds/right-arrow)
:command "shift+right"
:fn #(st/emit! (dwt/move-selected :right true))}
:move-fast-left {:tooltip (ds/shift ds/left-arrow)
:command "shift+left"
:fn #(st/emit! (dwt/move-selected :left true))}
:move-unit-up {:tooltip ds/up-arrow
:command "up"
:fn #(st/emit! (dwt/move-selected :up false))}
:move-unit-down {:tooltip ds/down-arrow
:command "down"
:fn #(st/emit! (dwt/move-selected :down false))}
:move-unit-left {:tooltip ds/right-arrow
:command "right"
:fn #(st/emit! (dwt/move-selected :right false))}
:move-unit-right {:tooltip ds/left-arrow
:command "left"
:fn #(st/emit! (dwt/move-selected :left false))}
:open-color-picker {:tooltip "I"
:command "i"
:fn #(st/emit! (mdc/picker-for-selected-shape))}
:open-viewer {:tooltip "G V"
:command "g v"
:fn #(st/emit! (dw/go-to-viewer))}
:open-handoff {:tooltip "G H"
:command "g h"
:fn #(st/emit! (dw/go-to-viewer {:section :handoff}))}
:open-comments {:tooltip "G C"
:command "g c"
:fn #(st/emit! (dw/go-to-viewer {:section :comments}))}
:open-dashboard {:tooltip "G D"
:command "g d"
:fn #(st/emit! (dw/go-to-dashboard))}
:escape {:tooltip (ds/esc)
:command "escape"
:fn #(st/emit! :interrupt (dw/deselect-all true))}
:duplicate {:tooltip (ds/meta "D")
:command (ds/c-mod "d")
:subsections [:edit]
:fn #(st/emit! (dw/duplicate-selected true))}
:start-editing {:tooltip (ds/enter)
:command "enter"
:subsections [:edit]
:fn #(st/emit! (dw/start-editing-selected))}
:start-measure {:tooltip (ds/alt "")
:command ["alt" "."]
:type "keydown"
:subsections [:edit]
:fn #(st/emit! (dw/toggle-distances-display true))}
:stop-measure {:tooltip (ds/alt "")
:command ["alt" "."]
:type "keyup"
:subsections [:edit]
:fn #(st/emit! (dw/toggle-distances-display false))}
:bool-union {:tooltip (ds/meta (ds/alt "U"))
:command (ds/c-mod "alt+u")
:fn #(st/emit! (dw/create-bool :union))}
:escape {:tooltip (ds/esc)
:command "escape"
:subsections [:edit]
:fn #(st/emit! :interrupt (dw/deselect-all true))}
:bool-difference {:tooltip (ds/meta (ds/alt "D"))
:command (ds/c-mod "alt+d")
:fn #(st/emit! (dw/create-bool :difference))}
;; MODIFY LAYERS
:bool-intersection {:tooltip (ds/meta (ds/alt "I"))
:command (ds/c-mod "alt+i")
:fn #(st/emit! (dw/create-bool :intersection))}
:bool-exclude {:tooltip (ds/meta (ds/alt "E"))
:command (ds/c-mod "alt+e")
:fn #(st/emit! (dw/create-bool :exclude))}
:group {:tooltip (ds/meta "G")
:command (ds/c-mod "g")
:subsections [:modify-layers]
:fn #(st/emit! dw/group-selected)}
:align-left {:tooltip (ds/alt "A")
:command "alt+a"
:fn #(st/emit! (dw/align-objects :hleft))}
:ungroup {:tooltip (ds/shift "G")
:command "shift+g"
:subsections [:modify-layers]
:fn #(st/emit! dw/ungroup-selected)}
:align-right {:tooltip (ds/alt "D")
:command "alt+d"
:fn #(st/emit! (dw/align-objects :hright))}
:mask {:tooltip (ds/meta "M")
:command (ds/c-mod "m")
:subsections [:modify-layers]
:fn #(st/emit! dw/mask-group)}
:align-top {:tooltip (ds/alt "W")
:command "alt+w"
:fn #(st/emit! (dw/align-objects :vtop))}
:unmask {:tooltip (ds/meta-shift "M")
:command (ds/c-mod "shift+m")
:subsections [:modify-layers]
:fn #(st/emit! dw/unmask-group)}
:align-hcenter {:tooltip (ds/alt "H")
:command "alt+h"
:fn #(st/emit! (dw/align-objects :hcenter))}
:create-component {:tooltip (ds/meta "K")
:command (ds/c-mod "k")
:subsections [:modify-layers]
:fn #(st/emit! (dwl/add-component))}
:align-vcenter {:tooltip (ds/alt "V")
:command "alt+v"
:fn #(st/emit! (dw/align-objects :vcenter))}
:detach-component {:tooltip (ds/meta-shift "K")
:command (ds/c-mod "shift+k")
:subsections [:modify-layers]
:fn #(st/emit! dwl/detach-selected-components)}
:align-bottom {:tooltip (ds/alt "S")
:command "alt+s"
:fn #(st/emit! (dw/align-objects :vbottom))}
:flip-vertical {:tooltip (ds/shift "V")
:command "shift+v"
:subsections [:modify-layers]
:fn #(st/emit! (dw/flip-vertical-selected))}
:h-distribute {:tooltip (ds/meta-shift (ds/alt "H"))
:command (ds/c-mod "shift+alt+h")
:fn #(st/emit! (dw/distribute-objects :horizontal))}
:flip-horizontal {:tooltip (ds/shift "H")
:command "shift+h"
:subsections [:modify-layers]
:fn #(st/emit! (dw/flip-horizontal-selected))}
:bring-forward {:tooltip (ds/meta ds/up-arrow)
:command (ds/c-mod "up")
:subsections [:modify-layers]
:fn #(st/emit! (dw/vertical-order-selected :up))}
:v-distribute {:tooltip (ds/meta-shift (ds/alt "V"))
:command (ds/c-mod "shift+alt+v")
:fn #(st/emit! (dw/distribute-objects :vertical))}
:bring-backward {:tooltip (ds/meta ds/down-arrow)
:command (ds/c-mod "down")
:subsections [:modify-layers]
:fn #(st/emit! (dw/vertical-order-selected :down))}
:bring-front {:tooltip (ds/meta-shift ds/up-arrow)
:command (ds/c-mod "shift+up")
:subsections [:modify-layers]
:fn #(st/emit! (dw/vertical-order-selected :top))}
:bring-back {:tooltip (ds/meta-shift ds/down-arrow)
:command (ds/c-mod "shift+down")
:subsections [:modify-layers]
:fn #(st/emit! (dw/vertical-order-selected :bottom))}
:move-fast-up {:tooltip (ds/shift ds/up-arrow)
:command "shift+up"
:subsections [:modify-layers]
:fn #(st/emit! (dwt/move-selected :up true))}
:move-fast-down {:tooltip (ds/shift ds/down-arrow)
:command "shift+down"
:subsections [:modify-layers]
:fn #(st/emit! (dwt/move-selected :down true))}
:move-fast-right {:tooltip (ds/shift ds/right-arrow)
:command "shift+right"
:subsections [:modify-layers]
:fn #(st/emit! (dwt/move-selected :right true))}
:move-fast-left {:tooltip (ds/shift ds/left-arrow)
:command "shift+left"
:subsections [:modify-layers]
:fn #(st/emit! (dwt/move-selected :left true))}
:move-unit-up {:tooltip ds/up-arrow
:command "up"
:subsections [:modify-layers]
:fn #(st/emit! (dwt/move-selected :up false))}
:move-unit-down {:tooltip ds/down-arrow
:command "down"
:subsections [:modify-layers]
:fn #(st/emit! (dwt/move-selected :down false))}
:move-unit-left {:tooltip ds/right-arrow
:command "right"
:subsections [:modify-layers]
:fn #(st/emit! (dwt/move-selected :right false))}
:move-unit-right {:tooltip ds/left-arrow
:command "left"
:subsections [:modify-layers]
:fn #(st/emit! (dwt/move-selected :left false))}
:artboard-selection {:tooltip (ds/meta (ds/alt "G"))
:command (ds/c-mod "alt+g")
:subsections [:modify-layers]
:fn #(st/emit! (dw/create-artboard-from-selection))}
;; TOOLS
:draw-frame {:tooltip "A"
:command "a"
:subsections [:tools :basics]
:fn #(st/emit! (dwd/select-for-drawing :frame))}
:move {:tooltip "V"
:command "v"
:subsections [:tools]
:fn #(st/emit! :interrupt)}
:draw-rect {:tooltip "R"
:command "r"
:subsections [:tools]
:fn #(st/emit! (dwd/select-for-drawing :rect))}
:draw-ellipse {:tooltip "E"
:command "e"
:subsections [:tools]
:fn #(st/emit! (dwd/select-for-drawing :circle))}
:draw-text {:tooltip "T"
:command "t"
:subsections [:tools]
:fn #(st/emit! dwtxt/start-edit-if-selected
(dwd/select-for-drawing :text))}
:draw-path {:tooltip "P"
:command "p"
:subsections [:tools]
:fn #(st/emit! (dwd/select-for-drawing :path))}
:draw-curve {:tooltip (ds/shift "C")
:command "shift+c"
:subsections [:tools]
:fn #(st/emit! (dwd/select-for-drawing :curve))}
:add-comment {:tooltip "C"
:command "c"
:subsections [:tools]
:fn #(st/emit! (dwd/select-for-drawing :comments))}
:insert-image {:tooltip (ds/shift "K")
:command "shift+k"
:subsections [:tools]
:fn #(-> "image-upload" dom/get-element dom/click)}
:toggle-visibility {:tooltip (ds/meta-shift "H")
:command (ds/c-mod "shift+h")
:subsections [:tools]
:fn #(st/emit! (dw/toggle-visibility-selected))}
:toggle-lock {:tooltip (ds/meta-shift "L")
:command (ds/c-mod "shift+l")
:subsections [:tools]
:fn #(st/emit! (dw/toggle-lock-selected))}
:toggle-lock-size {:tooltip (ds/meta (ds/alt "L"))
:command (ds/c-mod "alt+l")
:subsections [:tools]
:fn #(st/emit! (dw/toggle-proportion-lock))}
:artboard-selection {:tooltip (ds/meta (ds/alt "G"))
:command (ds/c-mod "alt+g")
:fn #(st/emit! (dw/create-artboard-from-selection))}
:toggle-scale-text {:tooltip "K"
:command "k"
:subsections [:tools]
:fn #(st/emit! (toggle-layout-flag :scale-text))}
:hide-ui {:tooltip "\\"
:command "\\"
:fn #(st/emit! (toggle-layout-flag :hide-ui))}
:open-color-picker {:tooltip "I"
:command "i"
:subsections [:tools]
:fn #(st/emit! (mdc/picker-for-selected-shape))}
:toggle-focus-mode {:command "f"
:tooltip "F"
:subsections [:basics :tools]
:fn #(st/emit! (dw/toggle-focus-mode))}
:thumbnail-set {:tooltip (ds/shift "T")
:command "shift+t"
:fn #(st/emit! (dw/toggle-file-thumbnail-selected))}
;; ITEM ALIGNMENT
:align-left {:tooltip (ds/alt "A")
:command "alt+a"
:subsections [:alignment]
:fn #(st/emit! (dw/align-objects :hleft))}
:align-right {:tooltip (ds/alt "D")
:command "alt+d"
:subsections [:alignment]
:fn #(st/emit! (dw/align-objects :hright))}
:align-top {:tooltip (ds/alt "W")
:command "alt+w"
:subsections [:alignment]
:fn #(st/emit! (dw/align-objects :vtop))}
:align-hcenter {:tooltip (ds/alt "H")
:command "alt+h"
:subsections [:alignment]
:fn #(st/emit! (dw/align-objects :hcenter))}
:align-vcenter {:tooltip (ds/alt "V")
:command "alt+v"
:subsections [:alignment]
:fn #(st/emit! (dw/align-objects :vcenter))}
:align-bottom {:tooltip (ds/alt "S")
:command "alt+s"
:subsections [:alignment]
:fn #(st/emit! (dw/align-objects :vbottom))}
:h-distribute {:tooltip (ds/meta-shift (ds/alt "H"))
:command (ds/c-mod "shift+alt+h")
:subsections [:alignment]
:fn #(st/emit! (dw/distribute-objects :horizontal))}
:v-distribute {:tooltip (ds/meta-shift (ds/alt "V"))
:command (ds/c-mod "shift+alt+v")
:subsections [:alignment]
:fn #(st/emit! (dw/distribute-objects :vertical))}
;; MAIN MENU
:toggle-rules {:tooltip (ds/meta-shift "R")
:command (ds/c-mod "shift+r")
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :rules))}
:select-all {:tooltip (ds/meta "A")
:command (ds/c-mod "a")
:subsections [:main-menu]
:fn #(st/emit! (dw/select-all))}
:toggle-grid {:tooltip (ds/meta "'")
:command (ds/c-mod "'")
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :display-grid))}
:toggle-snap-grid {:tooltip (ds/meta-shift "'")
:command (ds/c-mod "shift+'")
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :snap-grid))}
:toggle-alignment {:tooltip (ds/meta "\\")
:command (ds/c-mod "\\")
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :dynamic-alignment))}
:thumbnail-set {:tooltip (ds/shift "T")
:command "shift+t"
:subsections [:main-menu]
:fn #(st/emit! (dw/toggle-file-thumbnail-selected))}
:show-pixel-grid {:tooltip (ds/shift ",")
:command "shift+,"
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :show-pixel-grid))}
:snap-pixel-grid {:command ","
:tooltip ","
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :snap-pixel-grid))}
})
:export-shapes {:tooltip (ds/meta-shift "E")
:command (ds/c-mod "shift+e")
:subsections [:basics :main-menu]
:fn #(st/emit!
(de/show-workspace-export-dialog))}
:toggle-snap-guide {:tooltip (ds/meta-shift "G")
:command (ds/c-mod "shift+g")
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :snap-guides))}
:show-shortcuts {:tooltip "?"
:command "?"
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :shortcuts)) }
;; PANELS
:toggle-layers {:tooltip (ds/alt "L")
:command (ds/a-mod "l")
:subsections [:panels]
:fn #(st/emit! (dw/go-to-layout :layers))}
:toggle-assets {:tooltip (ds/alt "I")
:command (ds/a-mod "i")
:subsections [:panels]
:fn #(st/emit! (dw/go-to-layout :assets))}
:toggle-history {:tooltip (ds/alt "H")
:command (ds/a-mod "h")
:subsections [:panels]
:fn #(st/emit! (dw/go-to-layout :document-history))}
:toggle-colorpalette {:tooltip (ds/alt "P")
:command (ds/a-mod "p")
:subsections [:panels]
:fn #(do (r/set-resize-type! :bottom)
(st/emit! (dw/remove-layout-flag :textpalette)
(toggle-layout-flag :colorpalette)))}
:toggle-textpalette {:tooltip (ds/alt "T")
:command (ds/a-mod "t")
:subsections [:panels]
:fn #(do (r/set-resize-type! :bottom)
(st/emit! (dw/remove-layout-flag :colorpalette)
(toggle-layout-flag :textpalette)))}
:hide-ui {:tooltip "\\"
:command "\\"
:subsections [:panels :basics]
:fn #(st/emit! (toggle-layout-flag :hide-ui))}
;; ZOOM-WORKSPACE
:increase-zoom {:tooltip "+"
:command ["+" "="]
:subsections [:zoom-workspace]
:fn #(st/emit! (dw/increase-zoom nil))}
:decrease-zoom {:tooltip "-"
:command ["-" "_"]
:subsections [:zoom-workspace]
:fn #(st/emit! (dw/decrease-zoom nil))}
:reset-zoom {:tooltip (ds/shift "0")
:command "shift+0"
:subsections [:zoom-workspace]
:fn #(st/emit! dw/reset-zoom)}
:fit-all {:tooltip (ds/shift "1")
:command "shift+1"
:subsections [:zoom-workspace]
:fn #(st/emit! dw/zoom-to-fit-all)}
:zoom-selected {:tooltip (ds/shift "2")
:command ["shift+2" "@" "\""]
:subsections [:zoom-workspace]
:fn #(st/emit! dw/zoom-to-selected-shape)}
;; NAVIGATION
:open-viewer {:tooltip "G V"
:command "g v"
:subsections [:navigation-workspace]
:fn #(st/emit! (dw/go-to-viewer))}
:open-handoff {:tooltip "G H"
:command "g h"
:subsections [:navigation-workspace]
:fn #(st/emit! (dw/go-to-viewer {:section :handoff}))}
:open-comments {:tooltip "G C"
:command "g c"
:subsections [:navigation-workspace]
:fn #(st/emit! (dw/go-to-viewer {:section :comments}))}
:open-dashboard {:tooltip "G D"
:command "g d"
:subsections [:navigation-workspace]
:fn #(st/emit! (dw/go-to-dashboard))}
;; SHAPE
:bool-union {:tooltip (ds/meta (ds/alt "U"))
:command (ds/c-mod "alt+u")
:subsections [:shape]
:fn #(st/emit! (dw/create-bool :union))}
:bool-difference {:tooltip (ds/meta (ds/alt "D"))
:command (ds/c-mod "alt+d")
:subsections [:shape]
:fn #(st/emit! (dw/create-bool :difference))}
:bool-intersection {:tooltip (ds/meta (ds/alt "I"))
:command (ds/c-mod "alt+i")
:subsections [:shape]
:fn #(st/emit! (dw/create-bool :intersection))}
:bool-exclude {:tooltip (ds/meta (ds/alt "E"))
:command (ds/c-mod "alt+e")
:subsections [:shape]
:fn #(st/emit! (dw/create-bool :exclude))}}
)
(def opacity-shortcuts
(into {} (->>
@@ -391,6 +499,7 @@
(map (fn [n] [(keyword (str "opacity-" n))
{:tooltip (str n)
:command (str n)
:subsections [:modify-layers]
:fn #(st/emit! (dwly/pressed-opacity n))}])))))
(def shortcuts

View File

@@ -18,6 +18,10 @@
([state page-id]
(get-in state [:workspace-data :pages-index page-id])))
(defn lookup-data-objects
[data page-id]
(dm/get-in data [:pages-index page-id :objects]))
(defn lookup-page-objects
([state]
(lookup-page-objects state (:current-page-id state)))

View File

@@ -355,7 +355,7 @@
(assoc :svg-attrs (dissoc attrs :x :y :width :height :href :xlink:href))))))
(defn parse-svg-element [frame-id svg-data element-data unames]
(let [{:keys [tag attrs]} element-data
(let [{:keys [tag attrs hidden]} element-data
attrs (usvg/format-styles attrs)
element-data (cond-> element-data (map? element-data) (assoc :attrs attrs))
name (dwc/generate-unique-name unames (or (:id attrs) (tag->name tag)))
@@ -402,6 +402,9 @@
(setup-fill)
(setup-stroke))
shape (cond-> shape
hidden (assoc :hidden true))
children (cond->> (:content element-data)
(or (= tag :g) (= tag :svg))
(mapv #(usvg/inherit-attributes attrs %)))]
@@ -471,6 +474,7 @@
:height (str (:height root-shape))
:fill "none"
:id "base-background"}
:hidden true
:content []}
svg-data (-> svg-data

View File

@@ -308,7 +308,7 @@
(assoc-in [:workspace-local :edition] (-> selected first :id)))))))
(defn not-changed? [old-dim new-dim]
(> (mth/abs (- old-dim new-dim)) 0.1))
(> (mth/abs (- old-dim new-dim)) 1))
(defn resize-text
[id new-width new-height]

View File

@@ -10,6 +10,7 @@
[app.common.pages.helpers :as cph]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.refs :as refs]
[app.main.repo :as rp]
[app.main.store :as st]
@@ -31,7 +32,9 @@
[object-id]
(rx/create
(fn [subs]
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'" object-id))]
;; We look in the DOM a canvas that 1) matches the id and 2) that it's not empty
;; will be empty on first rendering before drawing the thumbnail and we don't want to store that
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%']:not([data-empty])" object-id))]
(if (some? node)
(-> node
(.toBlob (fn [blob]
@@ -43,6 +46,14 @@
(do (rx/push! subs nil)
(rx/end! subs)))))))
(defn clear-thumbnail
[page-id frame-id]
(ptk/reify ::clear-thumbnail
ptk/UpdateEvent
(update [_ state]
(let [object-id (dm/str page-id frame-id)]
(assoc-in state [:workspace-file :thumbnails object-id] nil)))))
(defn update-thumbnail
"Updates the thumbnail information for the given frame `id`"
[page-id frame-id]
@@ -71,50 +82,39 @@
(defn- extract-frame-changes
"Process a changes set in a commit to extract the frames that are changing"
[[event [old-objects new-objects]]]
[[event [old-data new-data]]]
(let [changes (-> event deref :changes)
extract-ids
(fn [{type :type :as change}]
(fn [{:keys [page-id type] :as change}]
(case type
:add-obj [(:id change)]
:mod-obj [(:id change)]
:del-obj [(:id change)]
:reg-objects (:shapes change)
:mov-objects (:shapes change)
:add-obj [[page-id (:id change)]]
:mod-obj [[page-id (:id change)]]
:del-obj [[page-id (:id change)]]
:mov-objects (->> (:shapes change) (map #(vector page-id %)))
[]))
get-frame-id
(fn [id]
(let [shape (or (get new-objects id)
(get old-objects id))]
(or (and (cph/frame-shape? shape) id) (:frame-id shape))))
(fn [[page-id id]]
(let [old-objects (wsh/lookup-data-objects old-data page-id)
new-objects (wsh/lookup-data-objects new-data page-id)
;; Extracts the frames and then removes nils and the root frame
xform (comp (mapcat extract-ids)
(map get-frame-id)
(remove nil?)
(filter #(not= uuid/zero %))
(filter #(contains? new-objects %)))]
new-shape (get new-objects id)
old-shape (get old-objects id)
(into #{} xform changes)))
old-frame-id (if (cph/frame-shape? old-shape) id (:frame-id old-shape))
new-frame-id (if (cph/frame-shape? new-shape) id (:frame-id new-shape))]
(defn thumbnail-change?
"Checks if a event is only updating thumbnails to ignore in the thumbnail generation process"
[event]
(let [changes (-> event deref :changes)
(cond-> #{}
(and old-frame-id (not= uuid/zero old-frame-id))
(conj [page-id old-frame-id])
is-thumbnail-op?
(fn [{type :type attr :attr}]
(and (= type :set)
(= attr :thumbnail)))
is-thumbnail-change?
(fn [change]
(and (= (:type change) :mod-obj)
(->> change :operations (every? is-thumbnail-op?))))]
(->> changes (every? is-thumbnail-change?))))
(and new-frame-id (not= uuid/zero new-frame-id))
(conj [page-id new-frame-id]))))]
(into #{}
(comp (mapcat extract-ids)
(mapcat get-frame-id))
changes)))
(defn watch-state-changes
"Watch the state for changes inside frames. If a change is detected will force a rendering
@@ -123,32 +123,39 @@
(ptk/reify ::watch-state-changes
ptk/WatchEvent
(watch [_ _ stream]
(let [stopper (->> stream
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
(= ::watch-state-changes (ptk/type %)))))
(let [stopper
(->> stream
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
(= ::watch-state-changes (ptk/type %)))))
objects-stream (->> (rx/concat
(rx/of nil)
(rx/from-atom refs/workspace-page-objects {:emit-current-value? true}))
;; We need to keep the old-objects so we can check the frame for the
;; deleted objects
(rx/buffer 2 1))
workspace-data-str
(->> (rx/concat
(rx/of nil)
(rx/from-atom refs/workspace-data {:emit-current-value? true}))
;; We need to keep the old-objects so we can check the frame for the
;; deleted objects
(rx/buffer 2 1))
frame-changes (->> stream
(rx/filter dch/commit-changes?)
change-str
(->> stream
(rx/filter #(or (dch/commit-changes? %)
(= (ptk/type %) :app.main.data.workspace.notifications/handle-file-change)))
(rx/observe-on :async))
;; Async so we wait for additional side-effects of commit-changes
(rx/observe-on :async)
(rx/filter (complement thumbnail-change?))
(rx/with-latest-from objects-stream)
(rx/map extract-frame-changes)
(rx/share))]
frame-changes-str
(->> change-str
(rx/with-latest-from workspace-data-str)
(rx/flat-map extract-frame-changes)
(rx/share))]
(->> frame-changes
(rx/flat-map
(fn [ids]
(->> (rx/from ids)
(rx/map #(ptk/data-event ::force-render %)))))
(->> (rx/merge
(->> frame-changes-str
(rx/filter (fn [[page-id _]] (not= page-id (:current-page-id @st/state))))
(rx/map (fn [[page-id frame-id]] (clear-thumbnail page-id frame-id))))
(->> frame-changes-str
(rx/filter (fn [[page-id _]] (= page-id (:current-page-id @st/state))))
(rx/map (fn [[_ frame-id]] (ptk/data-event ::force-render frame-id)))))
(rx/take-until stopper))))))
(defn duplicate-thumbnail

View File

@@ -556,6 +556,32 @@
(watch [_ _ _]
(rx/of (apply-modifiers ids)))))
(defn change-orientation
"Change orientation of shapes, from the sidebar options form.
Will ignore pixel snap used in the options side panel"
[ids orientation]
(us/verify (s/coll-of ::us/uuid) ids)
(us/verify #{:horiz :vert} orientation)
(ptk/reify ::change-orientation
ptk/UpdateEvent
(update [_ state]
(let [objects (wsh/lookup-page-objects state)
layout (get state :workspace-layout)
snap-pixel? (contains? layout :snap-pixel-grid)
update-modifiers
(fn [state id]
(let [shape (get objects id)
modifiers (gsh/change-orientation-modifiers shape orientation)]
(-> state
(update :workspace-modifiers
#(set-objects-modifiers % objects shape modifiers false snap-pixel?)))))]
(reduce update-modifiers state ids)))
ptk/WatchEvent
(watch [_ _ _]
(rx/of (apply-modifiers ids)))))
;; -- Rotate --------------------------------------------------------
(defn start-rotate
@@ -786,7 +812,7 @@
(rx/of (apply-modifiers selected)
(finish-transform))))
(rx/empty))))))
(rx/empty))))))
(s/def ::x number?)
(s/def ::y number?)

View File

@@ -19,6 +19,7 @@
[app.util.globals :as glob]
[app.util.i18n :refer [tr]]
[app.util.router :as rt]
[app.util.storage :refer [storage]]
[app.util.timers :as ts]
[potok.core :as ptk]))
@@ -35,7 +36,7 @@
:else
(let [hint (ex-message error)
msg (dm/str "Internal Error: " hint)]
(ts/schedule (st/emitf (rt/assign-exception error)))
(ts/schedule #(st/emit! (rt/assign-exception error)))
(js/console.group msg)
(ex/ignoring (js/console.error error))
@@ -49,9 +50,11 @@
;; here and not in app.main.errors because of circular dependency.
(defmethod ptk/handle-error :authentication
[_]
(let [msg (tr "errors.auth.unable-to-login")]
(let [msg (tr "errors.auth.unable-to-login")
uri (. (. js/document -location) -href)]
(st/emit! (du/logout {:capture-redirect true}))
(ts/schedule 500 (st/emitf (msg/warn msg)))))
(ts/schedule 500 #(st/emit! (msg/warn msg)))
(ts/schedule 1000 #(swap! storage assoc :redirect-url uri))))
;; Error that happens on an active business model validation does not
;; passes an validation (example: profile can't leave a team). From
@@ -154,7 +157,7 @@
(defmethod ptk/handle-error ::exceptional-state
[error]
(ts/schedule
(st/emitf (rt/assign-exception error))))
#(st/emit! (rt/assign-exception error))))
;; This happens when the backed server fails to process the
;; request. This can be caused by an internal assertion or any other
@@ -188,7 +191,7 @@
(-> error ex-data ptk/handle-error)
(let [hint (ex-message error)
msg (dm/str "Unhandled Internal Error: " hint)]
(ts/schedule (st/emitf (rt/assign-exception error)))
(ts/schedule #(st/emit! (rt/assign-exception error)))
(js/console.group msg)
(ex/ignoring (js/console.error error))
(js/console.groupEnd msg))))

View File

@@ -349,15 +349,18 @@
;; ---- Viewer refs
(def viewer-file
(l/derived :viewer-file st/state))
(def viewer-project
(l/derived :viewer-file st/state))
(def viewer-data
(l/derived :viewer st/state))
(def viewer-file
(l/derived :file viewer-data))
(def viewer-thumbnails
(l/derived :thumbnails viewer-file))
(def viewer-project
(l/derived :project viewer-data))
(def viewer-state
(l/derived :viewer st/state))

View File

@@ -433,6 +433,7 @@
(mf/defc component-symbol
[{:keys [id data] :as props}]
(let [name (:name data)
path (:path data)
objects (-> (:objects data)
(adapt-objects-for-shape id))
object (get objects id)
@@ -448,7 +449,7 @@
(mf/deps objects)
(fn [] (group-wrapper-factory objects)))]
[:> "symbol" #js {:id (str id) :viewBox vbox}
[:> "symbol" #js {:id (str id) :viewBox vbox "penpot:path" path}
[:title name]
[:> shape-container {:shape object}
[:& group-wrapper {:shape object :view-box vbox}]]]))

View File

@@ -1,13 +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) UXBOX Labs SL
(ns app.main.store)
(defmacro emitf
[& events]
`(fn []
(app.main.store/emit! ~@events)))

View File

@@ -5,7 +5,6 @@
;; Copyright (c) UXBOX Labs SL
(ns app.main.store
(:require-macros [app.main.store])
(:require
[app.util.object :as obj]
[beicon.core :as rx]
@@ -56,10 +55,6 @@
(apply ptk/emit! state (cons event events))
nil))
(defn emitf
[& events]
#(apply ptk/emit! state events))
(defonce ongoing-tasks (l/atom #{}))
(add-watch ongoing-tasks ::ongoing-tasks

View File

@@ -210,6 +210,6 @@
[:div.links.demo
[:div.link-entry
[:span (tr "auth.create-demo-profile") " "]
[:a {:on-click (st/emitf (du/create-demo-profile))
[:a {:on-click #(st/emit! (du/create-demo-profile))
:data-test "demo-account-link"}
(tr "auth.create-demo-account")]]])]])

View File

@@ -204,19 +204,19 @@
on-delete-comment
(mf/use-callback
(mf/deps comment)
(st/emitf (dcm/delete-comment comment)))
#(st/emit! (dcm/delete-comment comment)))
delete-thread
(mf/use-callback
(mf/deps thread)
(st/emitf (dcm/close-thread)
(dcm/delete-comment-thread thread)))
#(st/emit! (dcm/close-thread)
(dcm/delete-comment-thread thread)))
on-delete-thread
(mf/use-callback
(mf/deps thread)
(st/emitf (modal/show
#(st/emit! (modal/show
{:type :confirm
:title (tr "modals.delete-comment-thread.title")
:message (tr "modals.delete-comment-thread.message")
@@ -292,11 +292,11 @@
(mf/use-layout-effect
(mf/deps thread)
(st/emitf (dcm/retrieve-comments (:id thread))))
#(st/emit! (dcm/retrieve-comments (:id thread))))
(mf/use-effect
(mf/deps thread)
(st/emitf (dcm/update-comment-thread-status thread)))
#(st/emit! (dcm/update-comment-thread-status thread)))
(mf/use-layout-effect
(mf/deps thread comments-map)

View File

@@ -35,39 +35,45 @@
route (mf/deref refs/route)
in-dashboard? (= :dashboard-projects (:name (:data route)))
local (mf/use-state {:offset 0
local (mf/use-state {:offset-y 0
:offset-x 0
:levels nil})
on-local-close
(mf/use-callback
(fn []
(swap! local assoc :levels [{:parent-option nil
:options options}])
(on-close)))
(fn []
(swap! local assoc :levels [{:parent-option nil
:options options}])
(on-close)))
check-menu-offscreen
(mf/use-callback
(mf/deps top (:offset @local))
(mf/deps top (:offset-y @local) left (:offset-x @local))
(fn [node]
(when (some? node)
(let [{node-height :height} (dom/get-bounding-rect node)
{window-height :height} (dom/get-window-size)
target-offset (if (> (+ top node-height) window-height)
(let [bounding_rect (dom/get-bounding-rect node)
window_size (dom/get-window-size)
{node-height :height node-width :width} bounding_rect
{window-height :height window-width :width} window_size
target-offset-y (if (> (+ top node-height) window-height)
(- node-height)
0)]
0)
target-offset-x (if (> (+ left node-width) window-width)
(- node-width)
0)]
(when (not= target-offset (:offset @local))
(swap! local assoc :offset target-offset))))))
(when (or (not= target-offset-y (:offset-y @local)) (not= target-offset-x (:offset-x @local)))
(swap! local assoc :offset-y target-offset-y :offset-x target-offset-x))))))
enter-submenu
(mf/use-callback
(mf/deps options)
(fn [option-name sub-options]
(fn [event]
(dom/stop-propagation event)
(swap! local update :levels
conj {:parent-option option-name
:options sub-options}))))
(mf/deps options)
(fn [option-name sub-options]
(fn [event]
(dom/stop-propagation event)
(swap! local update :levels
conj {:parent-option option-name
:options sub-options}))))
exit-submenu
(mf/use-callback
@@ -87,8 +93,8 @@
[:div.context-menu {:class (dom/classnames :is-open open?
:fixed fixed?
:is-selectable is-selectable)
:style {:top (+ top (:offset @local))
:left left}}
:style {:top (+ top (:offset-y @local))
:left (+ left (:offset-x @local))}}
(let [level (-> @local :levels peek)]
[:ul.context-menu-items {:class (dom/classnames :min-width min-width?)
:ref check-menu-offscreen}

View File

@@ -236,6 +236,14 @@
(events/listen globals/window EventType.CLICK on-click)]]
#(doseq [key keys]
(events/unlistenByKey key)))))
(mf/use-layout-effect
(mf/deps handle-mouse-wheel)
(fn []
(let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:pasive false})]]
#(doseq [key keys]
(events/unlistenByKey key)))))
(mf/use-layout-effect
(mf/deps handle-mouse-wheel)

View File

@@ -21,3 +21,4 @@
(def current-project-id (mf/create-context nil))
(def current-page-id (mf/create-context nil))
(def current-file-id (mf/create-context nil))
(def scroll-ctx (mf/create-context nil))

View File

@@ -50,7 +50,7 @@
(mf/defc dashboard-content
[{:keys [team projects project section search-term profile] :as props}]
[:div.dashboard-content {:on-click (st/emitf (dd/clear-selected-files))}
[:div.dashboard-content {:on-click #(st/emit! (dd/clear-selected-files))}
(case section
:dashboard-projects
[:& projects-section {:team team :projects projects}]

View File

@@ -33,7 +33,7 @@
(filter #(not= (:label %) (:fullname profile))
(map #(hash-map :label (:name %) :value (str (:id %))) members)))
on-cancel (st/emitf (modal/hide))
on-cancel #(st/emit! (modal/hide))
on-accept
(fn [_]
(let [member-id (get-in @form [:clean-data :member-id])]

View File

@@ -126,10 +126,10 @@
{:on-success #(on-move-success team-id project-id)}))))))
add-shared
(st/emitf (dd/set-file-shared (assoc file :is-shared true)))
#(st/emit! (dd/set-file-shared (assoc file :is-shared true)))
del-shared
(st/emitf (dd/set-file-shared (assoc file :is-shared false)))
#(st/emit! (dd/set-file-shared (assoc file :is-shared false)))
on-add-shared
(fn [event]

View File

@@ -39,7 +39,7 @@
toggle-pin
(mf/use-callback
(mf/deps project)
(st/emitf (dd/toggle-project-pin project)))
#(st/emit! (dd/toggle-project-pin project)))
on-create-clicked
(mf/use-callback

View File

@@ -41,12 +41,12 @@
;; (let [go-fonts
;; (mf/use-callback
;; (mf/deps team)
;; (st/emitf (rt/nav :dashboard-fonts {:team-id (:id team)})))
;; #(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})))
;; go-providers
;; (mf/use-callback
;; (mf/deps team)
;; (st/emitf (rt/nav :dashboard-font-providers {:team-id (:id team)})))]
;; #(st/emit! (rt/nav :dashboard-font-providers {:team-id (:id team)})))]
(use-set-page-title team section)

View File

@@ -33,8 +33,8 @@
(dom/set-html-title (tr "title.dashboard.shared-libraries" tname))))))
(mf/use-effect
(st/emitf (dd/fetch-shared-files)
(dd/clear-selected-files)))
#(st/emit! (dd/fetch-shared-files)
(dd/clear-selected-files)))
[:*
[:header.dashboard-header

View File

@@ -56,7 +56,7 @@
(with-meta project {:on-success on-duplicate-success}))))
toggle-pin
(st/emitf (dd/toggle-project-pin project))
#(st/emit! (dd/toggle-project-pin project))
on-move-success
(fn [team-id]
@@ -66,7 +66,7 @@
(fn [team-id]
(let [data {:id (:id project) :team-id team-id}
mdata {:on-success #(on-move-success team-id)}]
(st/emitf (dm/success (tr "dashboard.success-move-project"))
#(st/emit! (dm/success (tr "dashboard.success-move-project"))
(dd/move-project (with-meta data mdata)))))
delete-fn
@@ -76,7 +76,7 @@
(dd/go-to-projects (:team-id project))))
on-delete
(st/emitf
#(st/emit!
(modal/show
{:type :confirm
:title (tr "modals.delete-project-confirm.title")

View File

@@ -25,7 +25,7 @@
(mf/defc header
{::mf/wrap [mf/memo]}
[]
(let [create (st/emitf (dd/create-project))]
(let [create #(st/emit! (dd/create-project))]
[:header.dashboard-header
[:div.dashboard-title
[:h1 (tr "dashboard.projects-title")]]
@@ -49,13 +49,13 @@
on-nav
(mf/use-callback
(mf/deps project)
(st/emitf (rt/nav :dashboard-files {:team-id (:team-id project)
:project-id (:id project)})))
#(st/emit! (rt/nav :dashboard-files {:team-id (:team-id project)
:project-id (:id project)})))
toggle-pin
(mf/use-callback
(mf/deps project)
(st/emitf (dd/toggle-project-pin project)))
#(st/emit! (dd/toggle-project-pin project)))
on-menu-click
(mf/use-callback (fn [event]

View File

@@ -32,6 +32,7 @@
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[goog.functions :as f]
[potok.core :as ptk]
[rumext.alpha :as mf]))
(mf/defc sidebar-project
@@ -101,8 +102,8 @@
on-drop-success
(mf/use-callback
(mf/deps (:id item))
(st/emitf (msg/success (tr "dashboard.success-move-file"))
(dd/go-to-files (:id item))))
#(st/emit! (msg/success (tr "dashboard.success-move-file"))
(dd/go-to-files (:id item))))
on-drop
(mf/use-callback
@@ -208,7 +209,7 @@
on-create-clicked
(mf/use-callback
(st/emitf (modal/show :team-form {})))
#(st/emit! (modal/show :team-form {})))
team-selected
(mf/use-callback
@@ -239,9 +240,9 @@
(mf/defc team-options-dropdown
[{:keys [team profile] :as props}]
(let [go-members (st/emitf (dd/go-to-team-members))
go-invitations (st/emitf (dd/go-to-team-invitations))
go-settings (st/emitf (dd/go-to-team-settings))
(let [go-members #(st/emit! (dd/go-to-team-members))
go-invitations #(st/emit! (dd/go-to-team-invitations))
go-settings #(st/emit! (dd/go-to-team-settings))
members-map (mf/deref refs/dashboard-team-members)
members (vals members-map)
@@ -282,12 +283,12 @@
(st/emit! (modal/show :team-form {:team team})))
on-leave-clicked
(st/emitf (modal/show
{:type :confirm
:title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-confirm.message")
:accept-label (tr "modals.leave-confirm.accept")
:on-accept leave-fn}))
#(st/emit! (modal/show
{:type :confirm
:title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-confirm.message")
:accept-label (tr "modals.leave-confirm.accept")
:on-accept leave-fn}))
on-leave-as-owner-clicked
(fn []
@@ -299,22 +300,22 @@
:accept leave-fn})))
leave-and-close
(st/emitf (modal/show
{:type :confirm
:title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-and-close-confirm.message" (:name team))
:scd-message (tr "modals.leave-and-close-confirm.hint")
:accept-label (tr "modals.leave-confirm.accept")
:on-accept delete-fn}))
#(st/emit! (modal/show
{:type :confirm
:title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-and-close-confirm.message" (:name team))
:scd-message (tr "modals.leave-and-close-confirm.hint")
:accept-label (tr "modals.leave-confirm.accept")
:on-accept delete-fn}))
on-delete-clicked
(st/emitf
(modal/show
{:type :confirm
:title (tr "modals.delete-team-confirm.title")
:message (tr "modals.delete-team-confirm.message")
:accept-label (tr "modals.delete-team-confirm.accept")
:on-accept delete-fn}))]
#(st/emit!
(modal/show
{:type :confirm
:title (tr "modals.delete-team-confirm.title")
:message (tr "modals.delete-team-confirm.message")
:accept-label (tr "modals.delete-team-confirm.accept")
:on-accept delete-fn}))]
[:ul.dropdown.options-dropdown
[:li {:on-click go-members :data-test "team-members"} (tr "labels.members")]
@@ -390,12 +391,12 @@
go-projects
(mf/use-callback
(mf/deps team)
(st/emitf (rt/nav :dashboard-projects {:team-id (:id team)})))
#(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)})))
go-fonts
(mf/use-callback
(mf/deps team)
(st/emitf (rt/nav :dashboard-fonts {:team-id (:id team)})))
#(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})))
go-drafts
(mf/use-callback
@@ -407,7 +408,7 @@
go-libs
(mf/use-callback
(mf/deps team)
(st/emitf (rt/nav :dashboard-libraries {:team-id (:id team)})))
#(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)})))
pinned-projects
(->> (vals projects)
@@ -472,7 +473,16 @@
(dom/stop-propagation event)
(if (keyword? section)
(st/emit! (rt/nav section))
(st/emit! section))))]
(st/emit! section))))
show-release-notes
(mf/use-callback
(fn [event]
(let [version (:main @cf/version)]
(st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version}))
(if (and (kbd/alt? event) (kbd/mod? event))
(st/emit! (modal/show {:type :onboarding}))
(st/emit! (modal/show {:type :release-notes :version version}))))))]
[:div.profile-section
[:div.profile {:on-click #(reset! show true)
@@ -485,26 +495,26 @@
[:ul.dropdown
[:li {:on-click (partial on-click :settings-profile)
:data-test "profile-profile-opt"}
[:span.icon i/user]
[:span.text (tr "labels.your-account")]]
[:li.separator {:on-click #(dom/open-new-window "https://help.penpot.app")
:data-test "help-center-profile-opt"}
[:span.icon i/help]
[:span.text (tr "labels.help-center")]]
[:li {:on-click #(dom/open-new-window "https://penpot.app/libraries-templates.html")
:data-test "libraries-templates-profile-opt"}
[:span.icon i/download]
[:li {:on-click #(dom/open-new-window "https://www.youtube.com/c/Penpot")}
[:span.text (tr "labels.tutorials")]]
[:li {:on-click show-release-notes}
[:span (tr "labels.release-notes")]]
[:li.separator {:on-click #(dom/open-new-window "https://penpot.app/libraries-templates.html")
:data-test "libraries-templates-profile-opt"}
[:span.text (tr "labels.libraries-and-templates")]]
;;[:li {:on-click #(dom/open-new-window "https://penpot.app?no-redirect=1")
[:li {:on-click #(dom/open-new-window "https://landing-next.penpot.app?no-redirect=1")
:data-test "about-penpot-profile-opt"} ;; Parameter ?no-redirect is to force stay in landing page
[:span.icon i/logo-icon] ;; instead of redirecting to app
[:span.text (tr "labels.about-penpot")]]
[:li {:on-click #(dom/open-new-window "https://github.com/penpot/penpot")}
[:span (tr "labels.github-repo")]]
[:li {:on-click #(dom/open-new-window "https://penpot.app/terms.html")}
[:span (tr "auth.terms-of-service")]]
(when (contains? @cf/flags :user-feedback)
[:li.separator {:on-click (partial on-click :settings-feedback)
:data-test "feedback-profile-opt"}
[:span.icon i/msg-info]
[:span.text (tr "labels.give-feedback")]])
[:li.separator {:on-click #(on-click (du/logout) %)

View File

@@ -592,8 +592,8 @@
(mf/use-effect
(st/emitf (dd/fetch-team-members)
(dd/fetch-team-stats)))
#(st/emit! (dd/fetch-team-members)
(dd/fetch-team-stats)))
[:*
[:& header {:section :dashboard-team-settings

View File

@@ -82,7 +82,7 @@
[:h2 (tr "labels.create-team")])]
[:div.modal-close-button
{:on-click (st/emitf (modal/hide))} i/close]]
{:on-click #(st/emit! (modal/hide))} i/close]]
[:div.modal-content.generic-form
[:& fm/input {:type "text"

View File

@@ -147,6 +147,7 @@
(def shape-valign-center (icon-xref :shape-valign-center))
(def shape-valign-top (icon-xref :shape-valign-top))
(def shape-vdistribute (icon-xref :shape-vdistribute))
(def shortcut (icon-xref :shortcut))
(def size-horiz (icon-xref :size-horiz))
(def size-vert (icon-xref :size-vert))
(def sort-ascending (icon-xref :sort-ascending))

View File

@@ -49,7 +49,7 @@
(mf/defc notifications
[]
(let [message (mf/deref refs/message)
on-close (st/emitf dm/hide)]
on-close #(st/emit! dm/hide)]
(when message
[:& banner (assoc message
:position (or (:position message) :fixed)

View File

@@ -6,6 +6,7 @@
(ns app.main.ui.onboarding.team-choice
(:require
[app.common.data :as d]
[app.common.spec :as us]
[app.main.data.dashboard :as dd]
[app.main.data.messages :as dm]
@@ -75,8 +76,8 @@
[:div.modal-overlay
[:div.modal-container.onboarding-team
[:div.title
[:h2 {:data-test "onboarding-choice-team-up"} (tr "onboarding.choice.team-up")]
[:p (tr "onboarding.choice.team-up-desc")]]
[:h2 {:data-test "onboarding-choice-team-up"} (tr "onboarding.choice.team-up.create-team")]
[:p (tr "onboarding.choice.team-up.create-team-desc")]]
[:& fm/form {:form form
:on-submit on-submit}
@@ -84,14 +85,14 @@
[:div.team-row
[:& fm/input {:type "text"
:name :name
:label (tr "onboarding.team-input-placeholder")}]]
:label (tr "onboarding.choice.team-up.create-team-placeholder")}]]
[:div.buttons
[:button.btn-secondary.btn-large
{:on-click #(st/emit! (modal/show {:type :onboarding-choice}))}
(tr "labels.cancel")]
(tr "labels.back")]
[:& fm/submit-button
{:label (tr "labels.next")}]]]
{:label (tr "labels.continue")}]]]
[:img.deco {:src "images/deco-left.png" :border "0"}]
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))
@@ -101,10 +102,10 @@
[{:value "editor" :label (tr "labels.editor")}
{:value "admin" :label (tr "labels.admin")}])
(s/def ::email ::us/email)
(s/def ::emails (s/and ::us/set-of-emails d/not-empty?))
(s/def ::role ::us/keyword)
(s/def ::invite-form
(s/keys :req-un [::role ::email]))
(s/keys :req-un [::role ::emails]))
;; This is the final step of team creation, consists in provide a
;; shortcut for invite users.
@@ -157,27 +158,31 @@
[:div.modal-overlay
[:div.modal-container.onboarding-team
[:div.title
[:h2 (tr "onboarding.choice.team-up")]
[:p (tr "onboarding.choice.team-up-desc")]]
[:h2 (tr "onboarding.choice.team-up.invite-members")]
[:p (tr "onboarding.choice.team-up.invite-members-desc")]]
[:& fm/form {:form form
:on-submit on-submit}
[:div.invite-row
[:& fm/input {:name :email
:label (tr "labels.email")}]
[:& fm/select {:name :role
:options roles}]]
[:div.invite-row
[:& fm/multi-input {:type "email"
:name :emails
:auto-focus? true
:trim true
:valid-item-fn us/parse-email
:label (tr "modals.invite-member.emails")}]
[:& fm/select {:name :role :options roles}]]
[:div.buttons
[:button.btn-secondary.btn-large
{:on-click #(st/emit! (modal/show {:type :onboarding-choice}))}
(tr "labels.cancel")]
(tr "labels.back")]
[:& fm/submit-button
{:label (tr "labels.create")}]]
{:label (tr "onboarding.choice.team-up.invite-members-submit")}]]
[:div.skip-action
{:on-click on-skip}
[:div.action "Skip and invite later"]]]
[:div.action (tr "onboarding.choice.team-up.invite-members-skip")]]]
[:img.deco {:src "images/deco-left.png" :border "0"}]
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))

View File

@@ -14,6 +14,7 @@
[app.main.ui.releases.v1-11]
[app.main.ui.releases.v1-12]
[app.main.ui.releases.v1-13]
[app.main.ui.releases.v1-14]
[app.main.ui.releases.v1-4]
[app.main.ui.releases.v1-5]
[app.main.ui.releases.v1-6]
@@ -44,14 +45,13 @@
finish
(mf/use-callback
(st/emitf (modal/hide)
(du/mark-onboarding-as-viewed {:version version})))
]
#(st/emit! (modal/hide)
(du/mark-onboarding-as-viewed {:version version})))]
(mf/use-effect
(mf/deps)
(fn []
(st/emitf (du/mark-onboarding-as-viewed {:version version}))))
#(st/emit! (du/mark-onboarding-as-viewed {:version version}))))
(mf/use-layout-effect
(mf/deps @slide)
@@ -84,4 +84,4 @@
(defmethod rc/render-release-notes "0.0"
[params]
(rc/render-release-notes (assoc params :version "1.13")))
(rc/render-release-notes (assoc params :version "1.14")))

View 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) UXBOX Labs SL
(ns app.main.ui.releases.v1-14
(:require
[app.main.ui.releases.common :as c]
[rumext.alpha :as mf]))
(defmethod c/render-release-notes "1.14"
[{:keys [slide klass next finish navigate version]}]
(mf/html
(case @slide
:start
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/login-on.jpg" :border "0" :alt "What's new Beta release 1.14"}]]
[:div.modal-right
[:div.modal-title
[:h2 "What's new?"]]
[:span.release "Beta version " version]
[:div.modal-content
[:p "Penpot continues to grow with new features that improve performance, user experience and visual design."]
[:p "We are happy to show you a sneak peak of the most important stuff that the Beta 1.14 version brings."]]
[:div.modal-navigation
[:button.btn-secondary {:on-click next} "Continue"]]]
[:img.deco {:src "images/deco-left.png" :border "0"}]
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]]
0
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/features/1.14-shortcuts.gif" :border "0" :alt "Shortcuts panel"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Shortcuts panel"]]
[:div.modal-content
[:p "Shortcuts boost your productivity but are not easy to find and learn. A handy panel at your workspace will help you with that."]
[:p "Categories and filters will help you to find the shortcut you need. One of the most requested features by the community!"]]
[:div.modal-navigation
[:button.btn-secondary {:on-click next} "Continue"]
[:& c/navigation-bullets
{:slide @slide
:navigate navigate
:total 4}]]]]]]
1
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/features/1.14-color-group.gif" :border "0" :alt "Colors selection"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Colors selection"]]
[:div.modal-content
[:p "All of the colors that are contained within a selection of objects are showcased at the sidebar."]
[:p "Play with the colors of a group without the hassles of individual selection!"]]
[:div.modal-navigation
[:button.btn-secondary {:on-click next} "Continue"]
[:& c/navigation-bullets
{:slide @slide
:navigate navigate
:total 4}]]]]]]
2
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/features/1.14-fix-on-scroll.gif" :border "0" :alt "Fix elements at scroll"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Fix elements at scroll"]]
[:div.modal-content
[:p "A new option that allows you to fix the position of an object when scrolling at the presentation view."]
[:p "Ideal for prototyping fixed headers, navbars and floating buttons."]]
[:div.modal-navigation
[:button.btn-secondary {:on-click next} "Continue"]
[:& c/navigation-bullets
{:slide @slide
:navigate navigate
:total 4}]]]]]]
3
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/features/1.14-group-assets.gif" :border "0" :alt "Group library assets with drag & drop"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Group library assets with drag & drop"]]
[:div.modal-content
[:p "We have improved the way to manage asset groups at libraries."]
[:p "Until now you could only do it by renaming the groups, now with drag & drop it is much more user friendly."]]
[:div.modal-navigation
[:button.btn-secondary {:on-click finish} "Start!"]
[:& c/navigation-bullets
{:slide @slide
:navigate navigate
:total 4}]]]]]])))

View File

@@ -77,7 +77,7 @@
:validators [email-equality]
:initial profile)
on-close
(mf/use-callback (st/emitf (modal/hide)))]
(mf/use-callback #(st/emit! (modal/hide)))]
[:div.modal-overlay
[:div.modal-container.change-email-modal.form-container

View File

@@ -28,13 +28,13 @@
::mf/register-as :delete-account}
[]
(let [on-close
(mf/use-callback (st/emitf (modal/hide)))
(mf/use-callback #(st/emit! (modal/hide)))
on-accept
(mf/use-callback
(st/emitf (modal/hide)
(du/request-account-deletion
(with-meta {} {:on-error on-error}))))]
#(st/emit! (modal/hide)
(du/request-account-deletion
(with-meta {} {:on-error on-error}))))]
[:div.modal-overlay
[:div.modal-container.change-email-modal

View File

@@ -29,27 +29,27 @@
go-dashboard
(mf/use-callback
(mf/deps profile)
(st/emitf (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)})))
#(st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)})))
go-settings-profile
(mf/use-callback
(mf/deps profile)
(st/emitf (rt/nav :settings-profile)))
#(st/emit! (rt/nav :settings-profile)))
go-settings-feedback
(mf/use-callback
(mf/deps profile)
(st/emitf (rt/nav :settings-feedback)))
#(st/emit! (rt/nav :settings-feedback)))
go-settings-password
(mf/use-callback
(mf/deps profile)
(st/emitf (rt/nav :settings-password)))
#(st/emit! (rt/nav :settings-password)))
go-settings-options
(mf/use-callback
(mf/deps profile)
(st/emitf (rt/nav :settings-options)))
#(st/emit! (rt/nav :settings-options)))
show-release-notes
(mf/use-callback

View File

@@ -56,49 +56,53 @@
attrs))
(defn add-fill
([attrs shape render-id]
(add-fill attrs shape render-id nil))
([attrs fill-data render-id type]
(add-fill attrs fill-data render-id nil type))
([attrs shape render-id index]
([attrs fill-data render-id index type]
(let [fill-attrs
(cond
(contains? shape :fill-image)
(contains? fill-data :fill-image)
(let [fill-image-id (str "fill-image-" render-id)]
{:fill (str "url(#" fill-image-id ")")})
(and (contains? shape :fill-color-gradient) (some? (:fill-color-gradient shape)))
(and (contains? fill-data :fill-color-gradient) (some? (:fill-color-gradient fill-data)))
(let [fill-color-gradient-id (str "fill-color-gradient_" render-id (if index (str "_" index) ""))]
{:fill (str "url(#" fill-color-gradient-id ")")})
(contains? shape :fill-color)
{:fill (:fill-color shape)}
(contains? fill-data :fill-color)
{:fill (:fill-color fill-data)}
:else
{:fill "none"})
fill-attrs (cond-> fill-attrs
(contains? shape :fill-opacity)
(assoc :fillOpacity (:fill-opacity shape)))]
(contains? fill-data :fill-opacity)
(assoc :fillOpacity (:fill-opacity fill-data))
;; Old texts with only an opacity set are black by default
(and (= type :text) (nil? (:fill-color-gradient fill-data)) (nil? (:fill-color fill-data)))
(assoc :fill "black"))]
(obj/merge! attrs (clj->js fill-attrs)))))
(defn add-stroke [attrs shape render-id index]
(let [stroke-style (:stroke-style shape :none)
(defn add-stroke [attrs stroke-data render-id index]
(let [stroke-style (:stroke-style stroke-data :none)
stroke-color-gradient-id (str "stroke-color-gradient_" render-id "_" index)
stroke-width (:stroke-width shape 1)]
stroke-width (:stroke-width stroke-data 1)]
(if (not= stroke-style :none)
(let [stroke-attrs
(cond-> {:strokeWidth stroke-width}
(:stroke-color-gradient shape)
(:stroke-color-gradient stroke-data)
(assoc :stroke (str/format "url(#%s)" stroke-color-gradient-id))
(and (not (:stroke-color-gradient shape))
(:stroke-color shape nil))
(assoc :stroke (:stroke-color shape nil))
(and (not (:stroke-color-gradient stroke-data))
(:stroke-color stroke-data nil))
(assoc :stroke (:stroke-color stroke-data nil))
(and (not (:stroke-color-gradient shape))
(:stroke-opacity shape nil))
(assoc :strokeOpacity (:stroke-opacity shape nil))
(and (not (:stroke-color-gradient stroke-data))
(:stroke-opacity stroke-data nil))
(assoc :strokeOpacity (:stroke-opacity stroke-data nil))
(not= stroke-style :svg)
(assoc :strokeDasharray (stroke-type->dasharray stroke-width stroke-style))
@@ -106,29 +110,29 @@
;; For simple line caps we use svg stroke-line-cap attribute. This
;; only works if all caps are the same and we are not using the tricks
;; for inner or outer strokes.
(and (stroke-caps-line (:stroke-cap-start shape))
(= (:stroke-cap-start shape) (:stroke-cap-end shape))
(not (#{:inner :outer} (:stroke-alignment shape)))
(and (stroke-caps-line (:stroke-cap-start stroke-data))
(= (:stroke-cap-start stroke-data) (:stroke-cap-end stroke-data))
(not (#{:inner :outer} (:stroke-alignment stroke-data)))
(not= :dotted stroke-style))
(assoc :strokeLinecap (:stroke-cap-start shape))
(assoc :strokeLinecap (:stroke-cap-start stroke-data))
(= :dotted stroke-style)
(assoc :strokeLinecap "round")
;; For other cap types we use markers.
(and (or (stroke-caps-marker (:stroke-cap-start shape))
(and (stroke-caps-line (:stroke-cap-start shape))
(not= (:stroke-cap-start shape) (:stroke-cap-end shape))))
(not (#{:inner :outer} (:stroke-alignment shape))))
(and (or (stroke-caps-marker (:stroke-cap-start stroke-data))
(and (stroke-caps-line (:stroke-cap-start stroke-data))
(not= (:stroke-cap-start stroke-data) (:stroke-cap-end stroke-data))))
(not (#{:inner :outer} (:stroke-alignment stroke-data))))
(assoc :markerStart
(str/format "url(#marker-%s-%s)" render-id (name (:stroke-cap-start shape))))
(str/format "url(#marker-%s-%s)" render-id (name (:stroke-cap-start stroke-data))))
(and (or (stroke-caps-marker (:stroke-cap-end shape))
(and (stroke-caps-line (:stroke-cap-end shape))
(not= (:stroke-cap-start shape) (:stroke-cap-end shape))))
(not (#{:inner :outer} (:stroke-alignment shape))))
(and (or (stroke-caps-marker (:stroke-cap-end stroke-data))
(and (stroke-caps-line (:stroke-cap-end stroke-data))
(not= (:stroke-cap-start stroke-data) (:stroke-cap-end stroke-data))))
(not (#{:inner :outer} (:stroke-alignment stroke-data))))
(assoc :markerEnd
(str/format "url(#marker-%s-%s)" render-id (name (:stroke-cap-end shape)))))]
(str/format "url(#marker-%s-%s)" render-id (name (:stroke-cap-end stroke-data)))))]
(obj/merge! attrs (clj->js stroke-attrs)))
attrs)))
@@ -195,7 +199,7 @@
(obj/set! "fill" (or (obj/get (:wrapper-styles shape) "fill") clr/black)))
(d/not-empty? (:fills shape))
(add-fill styles (d/without-nils (get-in shape [:fills 0])) render-id 0)
(add-fill styles (d/without-nils (get-in shape [:fills 0])) render-id 0 (:type shape))
:else
styles)]
@@ -211,16 +215,16 @@
(add-style-attrs shape)))
(defn extract-fill-attrs
[shape render-id index]
(let [fill-styles (-> (obj/get shape "style" (obj/new))
(add-fill shape render-id index))]
[fill-data render-id index type]
(let [fill-styles (-> (obj/get fill-data "style" (obj/new))
(add-fill fill-data render-id index type))]
(-> (obj/new)
(obj/set! "style" fill-styles))))
(defn extract-stroke-attrs
[shape index render-id]
(let [stroke-styles (-> (obj/get shape "style" (obj/new))
(add-stroke shape render-id index))]
[stroke-data index render-id]
(let [stroke-styles (-> (obj/get stroke-data "style" (obj/new))
(add-stroke stroke-data render-id index))]
(-> (obj/new)
(obj/set! "style" stroke-styles))))

View File

@@ -375,7 +375,7 @@
(d/not-empty? (:fills shape))
(let [fill-props
(attrs/extract-fill-attrs (get-in shape [:fills 0]) render-id 0)
(attrs/extract-fill-attrs (get-in shape [:fills 0]) render-id 0 (:type shape))
style (-> (obj/get props "style")
(obj/clone)

View File

@@ -53,7 +53,8 @@
:width width
:data-loading loading?}
(= :path (:type shape))
(obj/set! "patternTransform" transform))]
(obj/set! "patternTransform" transform))
type (:type shape)]
(for [[shape-index shape] (d/enumerate (or (:position-data shape) [shape]))]
[:* {:key (dm/str shape-index)}
@@ -73,7 +74,7 @@
(obj/set! "id" fill-id))
[:g
(for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)]
[:> :rect (-> (attrs/extract-fill-attrs value render-id fill-index)
[:> :rect (-> (attrs/extract-fill-attrs value render-id fill-index type)
(obj/set! "key" (dm/str fill-index))
(obj/set! "width" width)
(obj/set! "height" height))])

View File

@@ -20,6 +20,7 @@
[props]
(let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs")
objects (unchecked-get props "objects")
render-id (mf/use-ctx muc/render-ctx)
masked-group? (:masked-group? shape)
@@ -41,12 +42,21 @@
(if masked-group?
["g" (-> (obj/new)
(obj/set! "mask" (mask-url render-id mask)))]
[mf/Fragment nil])]
[mf/Fragment nil])
;; This factory is generic, it's used for viewer, workspace and handoff.
;; These props are generated in viewer wrappers only, in the rest of the
;; cases these props will be nil, not affecting the code.
delta (unchecked-get props "delta")
fixed? (unchecked-get props "fixed?")]
[:> clip-wrapper clip-props
[:> mask-wrapper mask-props
(when masked-group?
[:> render-mask #js {:mask mask}])
[:> render-mask #js {:mask mask
:objects objects
:delta delta
:fixed? fixed?}])
(for [item childs]
[:& shape-wrapper {:shape item

View File

@@ -46,11 +46,19 @@
(mf/fnc mask-shape
{::mf/wrap-props false}
[props]
(let [mask (unchecked-get props "mask")
render-id (mf/use-ctx muc/render-ctx)
svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
(let [mask (unchecked-get props "mask")
render-id (mf/use-ctx muc/render-ctx)
svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
mask-bb (-> (gsh/transform-shape mask) (:points))
;; This factory is generic, it's used for viewer, workspace and handoff.
;; These props are generated in viewer wrappers only, in the rest of the
;; cases these props will be nil, not affecting the code.
fixed? (unchecked-get props "fixed?")
delta (unchecked-get props "delta")
mask-bb (-> (gsh/transform-shape mask)
(cond-> fixed? (gsh/move delta))
(:points))
mask-bb-rect (gsh/points->rect mask-bb)]
[:defs
[:filter {:id (filter-id render-id mask)}

View File

@@ -41,7 +41,7 @@
[:div.desc-message (tr "labels.bad-gateway.desc-message")]
[:div.sign-info
[:a.btn-primary.btn-small
{:on-click (st/emitf #(dissoc % :exception))}
{:on-click (fn [] (st/emit! #(dissoc % :exception)))}
(tr "labels.retry")]]])
(mf/defc service-unavailable
@@ -52,7 +52,7 @@
[:div.desc-message (tr "labels.service-unavailable.desc-message")]
[:div.sign-info
[:a.btn-primary.btn-small
{:on-click (st/emitf #(dissoc % :exception))}
{:on-click (fn [] (st/emit! #(dissoc % :exception)))}
(tr "labels.retry")]]])
(mf/defc internal-error
@@ -63,7 +63,7 @@
[:div.desc-message (tr "labels.internal-error.desc-message")]
[:div.sign-info
[:a.btn-primary.btn-small
{:on-click (st/emitf (rt/assign-exception nil))}
{:on-click (fn [] (st/emit! (rt/assign-exception nil)))}
(tr "labels.retry")]]])
(mf/defc exception-page

View File

@@ -18,6 +18,7 @@
[app.main.fonts :as fonts]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.main.ui.shapes.filters :as filters]
@@ -70,6 +71,7 @@
nav-scroll (:nav-scroll local)
orig-viewport-ref (mf/use-ref nil)
current-viewport-ref (mf/use-ref nil)
viewer-section-ref (mf/use-ref nil)
current-animation (:current-animation local)
page-id (or page-id (-> file :data :pages first))
@@ -90,6 +92,7 @@
frame (get frames index)
fullscreen? (mf/deref refs/viewer-fullscreen?)
overlays (:overlays local)
scroll (mf/use-state nil)
orig-frame
(when (:orig-frame-id current-animation)
@@ -127,7 +130,11 @@
(fn [_]
(let [viewer-section (dom/get-element "viewer-section")
size (dom/get-client-size viewer-section)]
(st/emit! (dv/set-viewport-size {:size size})))))]
(st/emit! (dv/set-viewport-size {:size size})))))
on-scroll
(fn [event]
(reset! scroll (dom/get-target-scroll event)))]
(hooks/use-shortcuts ::viewer sc/shortcuts)
@@ -144,9 +151,11 @@
(mf/use-effect
(fn []
(dom/set-html-theme-color clr/gray-50 "dark")
(let [key1 (events/listen js/window "click" on-click)]
(let [key1 (events/listen js/window "click" on-click)
key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)]
(fn []
(events/unlistenByKey key1)))))
(events/unlistenByKey key1)
(events/unlistenByKey key2)))))
(mf/use-layout-effect
(fn []
@@ -258,8 +267,10 @@
[:& thumbnails-panel {:frames frames
:show? (:show-thumbnails local false)
:page page
:index index}]
:index index
:thumbnail-data (:thumbnails file)}]
[:section.viewer-section {:id "viewer-section"
:ref viewer-section-ref
:class (if fullscreen? "fullscreen" "")}
(cond
(empty? frames)
@@ -284,79 +295,79 @@
[:div.viewer-wrapper
{:style {:width (:width wrapper-size)
:height (:height wrapper-size)}}
[:& (mf/provider ctx/scroll-ctx) {:value @scroll}
[:div.viewer-clipper
[:*
(when orig-frame
[:div.viewport-container
{:ref orig-viewport-ref
:style {:width (:width orig-size)
:height (:height orig-size)
:position "relative"}}
[:div.viewer-clipper
[:*
(when orig-frame
[:div.viewport-container
{:ref orig-viewport-ref
:style {:width (:width orig-size)
:height (:height orig-size)
:position "relative"}}
[:& interactions/viewport
{:frame orig-frame
:base-frame orig-frame
:frame-offset (gpt/point 0 0)
:size orig-size
:page page
:file file
:users users
:interactions-mode :hide}]])
[:& interactions/viewport
{:frame orig-frame
:base-frame orig-frame
:frame-offset (gpt/point 0 0)
:size orig-size
:page page
:file file
:users users
:interactions-mode :hide}]])
[:div.viewport-container
{:ref current-viewport-ref
:style {:width (:width size)
:height (:height size)
:position "relative"}}
[:div.viewport-container
{:ref current-viewport-ref
:style {:width (:width size)
:height (:height size)
:position "relative"}}
[:& interactions/viewport
{:frame frame
:base-frame frame
:frame-offset (gpt/point 0 0)
:size size
:page page
:file file
:users users
:interactions-mode interactions-mode}]
[:& interactions/viewport
{:frame frame
:base-frame frame
:frame-offset (gpt/point 0 0)
:size size
:page page
:file file
:users users
:interactions-mode interactions-mode}]
(for [overlay overlays]
(let [size-over (calculate-size (:frame overlay) zoom)]
[:*
(when (or (:close-click-outside overlay)
(:background-overlay overlay))
[:div.viewer-overlay-background
{:class (dom/classnames
:visible (:background-overlay overlay))
:style {:width (:width wrapper-size)
:height (:height wrapper-size)
:position "absolute"
:left 0
:top 0}
:on-click #(when (:close-click-outside overlay)
(close-overlay (:frame overlay)))}])
[:div.viewport-container.viewer-overlay
{:id (str "overlay-" (str (:id (:frame overlay))))
:style {:width (:width size-over)
:height (:height size-over)
:left (* (:x (:position overlay)) zoom)
:top (* (:y (:position overlay)) zoom)}}
[:& interactions/viewport
{:frame (:frame overlay)
:base-frame frame
:frame-offset (:position overlay)
:size size-over
:page page
:file file
:users users
:interactions-mode interactions-mode}]]]))]]
(for [overlay overlays]
(let [size-over (calculate-size (:frame overlay) zoom)]
[:*
(when (or (:close-click-outside overlay)
(:background-overlay overlay))
[:div.viewer-overlay-background
{:class (dom/classnames
:visible (:background-overlay overlay))
:style {:width (:width wrapper-size)
:height (:height wrapper-size)
:position "absolute"
:left 0
:top 0}
:on-click #(when (:close-click-outside overlay)
(close-overlay (:frame overlay)))}])
[:div.viewport-container.viewer-overlay
{:id (str "overlay-" (str (:id (:frame overlay))))
:style {:width (:width size-over)
:height (:height size-over)
:left (* (:x (:position overlay)) zoom)
:top (* (:y (:position overlay)) zoom)}}
[:& interactions/viewport
{:frame (:frame overlay)
:base-frame frame
:frame-offset (:position overlay)
:size size-over
:page page
:file file
:users users
:interactions-mode interactions-mode}]]]))]]
(when (= section :comments)
[:& comments-layer {:file file
:users users
:frame frame
:page page
:zoom zoom}])]]]))]]]))
(when (= section :comments)
[:& comments-layer {:file file
:users users
:frame frame
:page page
:zoom zoom}])]]]]))]]]))
;; --- Component: Viewer Page

View File

@@ -122,7 +122,7 @@
on-draft-cancel
(mf/use-callback
(mf/deps cstate)
(st/emitf (dcm/close-thread)))
#(st/emit! (dcm/close-thread)))
on-draft-submit
(mf/use-callback

View File

@@ -92,11 +92,11 @@
[:& export-progress-widget]
[:& zoom-widget
{:zoom zoom
:on-increase (st/emitf dv/increase-zoom)
:on-decrease (st/emitf dv/decrease-zoom)
:on-zoom-reset (st/emitf dv/reset-zoom)
:on-zoom-fill (st/emitf dv/zoom-to-fill)
:on-zoom-fit (st/emitf dv/zoom-to-fit)
:on-increase #(st/emit! dv/increase-zoom)
:on-decrease #(st/emit! dv/decrease-zoom)
:on-zoom-reset #(st/emit! dv/reset-zoom)
:on-zoom-fill #(st/emit! dv/zoom-to-fill)
:on-zoom-fit #(st/emit! dv/zoom-to-fit)
:on-fullscreen toggle-fullscreen}]
[:span.btn-icon-dark.btn-small.tooltip.tooltip-bottom-left
@@ -172,7 +172,7 @@
(mf/defc header
[{:keys [project file page frame zoom section permissions index]}]
(let [go-to-dashboard
(st/emitf (dv/go-to-dashboard))
#(st/emit! (dv/go-to-dashboard))
go-to-handoff
(fn []

View File

@@ -7,12 +7,14 @@
(ns app.main.ui.viewer.shapes
"The main container for a frame in viewer mode"
(:require
[app.common.data :as d]
[app.common.geom.shapes :as geom]
[app.common.pages.helpers :as cph]
[app.common.spec.interactions :as cti]
[app.main.data.viewer :as dv]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.shapes.bool :as bool]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.frame :as frame]
@@ -214,7 +216,8 @@
childs (unchecked-get props "childs")
frame (unchecked-get props "frame")
objects (unchecked-get props "objects")
fixed? (unchecked-get props "fixed?")
delta (unchecked-get props "delta")
base-frame (mf/use-ctx base-frame-ctx)
frame-offset (mf/use-ctx frame-offset-ctx)
@@ -241,7 +244,10 @@
[:& component {:shape shape
:frame frame
:childs childs
:is-child-selected? true}]
:is-child-selected? true
:objects objects
:fixed? fixed?
:delta delta}]
[:& interaction {:shape shape
:interactions interactions
@@ -250,7 +256,8 @@
;; Don't wrap svg elements inside a <g> otherwise some can break
[:& component {:shape shape
:frame frame
:childs childs}]))))
:childs childs
:objects objects}]))))
(defn frame-wrapper
[shape-container]
@@ -313,12 +320,12 @@
(mf/fnc group-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape))
props (obj/merge! #js {} props
#js {:childs childs
:objects objects})]
[:> group-wrapper props]))))
(let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape")))
props (obj/merge! #js {} props
#js {:childs childs
:objects objects})]
(when (not-empty childs)
[:> group-wrapper props])))))
(defn bool-container-factory
[objects]
@@ -327,8 +334,7 @@
(mf/fnc bool-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (->> (cph/get-children-ids objects (:id shape))
(let [childs (->> (cph/get-children-ids objects (:id (unchecked-get props "shape")))
(select-keys objects))
props (obj/merge! #js {} props
#js {:childs childs
@@ -342,8 +348,7 @@
(mf/fnc svg-raw-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape))
(let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape")))
props (obj/merge! #js {} props
#js {:childs childs
:objects objects})]
@@ -359,7 +364,15 @@
(mf/fnc shape-container
{::mf/wrap-props false}
[props]
(let [group-container
(let [scroll (mf/use-ctx ctx/scroll-ctx)
local (mf/deref refs/viewer-local)
zoom (:zoom local)
shape (unchecked-get props "shape")
parents (map (d/getf objects) (cph/get-parent-ids objects (:id shape)))
fixed? (or (:fixed-scroll shape) (some :fixed-scroll parents))
frame (unchecked-get props "frame")
delta {:x (/ (:scroll-left scroll) zoom) :y (/ (:scroll-top scroll) zoom)}
group-container
(mf/use-memo (mf/deps objects)
#(group-container-factory objects))
@@ -369,12 +382,12 @@
svg-raw-container
(mf/use-memo (mf/deps objects)
#(svg-raw-container-factory objects))
shape (unchecked-get props "shape")
frame (unchecked-get props "frame")]
#(svg-raw-container-factory objects))]
(when (and shape (not (:hidden shape)))
(let [shape (-> (geom/transform-shape shape)
(geom/translate-to-frame frame))
(geom/translate-to-frame frame)
(cond-> fixed? (geom/move delta)))
opts #js {:shape shape
:objects objects}]
(case (:type shape)
@@ -384,7 +397,7 @@
:path [:> path-wrapper opts]
:image [:> image-wrapper opts]
:circle [:> circle-wrapper opts]
:group [:> group-container {:shape shape :frame frame :objects objects}]
:group [:> group-container {:shape shape :frame frame :objects objects :fixed? fixed? :delta delta}]
:bool [:> bool-container {:shape shape :frame frame :objects objects}]
:svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}])))))))

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