Compare commits

..

118 Commits

Author SHA1 Message Date
Alejandro Alonso
24fa4f71ad 📎 Update version.txt file 2023-02-27 10:37:39 +01:00
Andrey Antukh
fa21dc4cf9 📎 Fix tests 2023-02-25 10:35:00 +01:00
Alejandro
2460f36bab Merge pull request #2983 from penpot/niwinz-invitations-fixes
Fix issues with invitation user flow
2023-02-24 15:50:40 +01:00
Andrey Antukh
4d627f8993 🐛 Fix incorrect invitation flow 2023-02-24 15:44:29 +01:00
Andrey Antukh
7771467aa0 🐛 Fix missing member-id field on invitation copy-link 2023-02-24 15:41:15 +01:00
Alejandro
0e97182ef0 Merge pull request #2977 from penpot/niwinz-invitations-1
 Add proper audit log for invitations
2023-02-24 10:57:13 +01:00
Andrey Antukh
f0c0e5e43a Add proper audit log for invitations 2023-02-24 10:28:07 +01:00
Alejandro
475b6ff6e0 Merge pull request #2969 from penpot/alotor-fix-redo-curve-tool
🐛 Fix problem with redo curve drawings
2023-02-22 11:25:40 +01:00
alonso.torres
a1f41c80a2 🐛 Fix problem with redo curve drawings 2023-02-22 10:43:51 +01:00
Eva Marco
4297b6fda8 Merge pull request #2968 from penpot/alotor-bug-width-fill
🐛 Fix problem with align center and width 100%
2023-02-21 16:25:08 +01:00
alonso.torres
28dce3cc8b 🐛 Fix problem with align center and width 100% 2023-02-21 15:44:13 +01:00
Andrey Antukh
3c650ae47e Merge branch 'main' into staging 2023-02-20 13:28:51 +01:00
Alejandro
1806200613 Merge pull request #2947 from penpot/alotor-performance-improvement
 Performance improvement
2023-02-16 09:38:05 +01:00
alonso.torres
ed22e2c6d1 Performance improvement 2023-02-15 15:17:50 +01:00
Alejandro
0487539b23 Merge pull request #2946 from penpot/alotor-bug-new-frame
🐛 Fix problem with new frame inside layout
2023-02-15 13:48:57 +01:00
alonso.torres
fd15ff940f 🐛 Fix problem with new frame inside layout 2023-02-15 13:38:03 +01:00
Alejandro
ece6193260 Merge pull request #2939 from penpot/palba-fix-undo-duplicate-with-alt
Fix duplicate with alt and undo only undo one step
2023-02-15 12:21:00 +01:00
Pablo Alba
813a188e24 🐛 Fix duplicate with alt and undo only undo one step 2023-02-15 12:20:47 +01:00
Alejandro
0f07def536 Merge pull request #2940 from penpot/alotor-layout-improvements
 Add space-evenly option
2023-02-15 12:08:36 +01:00
alonso.torres
490f5f19f1 Add space-evenly option 2023-02-15 12:08:22 +01:00
Alejandro
b3216000fd Merge pull request #2941 from penpot/alotor-fix-frame-opacity
🐛 Fix problem with opacity in frames
2023-02-15 11:57:10 +01:00
Alejandro
2ef3e4b325 Merge pull request #2944 from penpot/alotor-fix-unhandled-error
🐛 Fix crash when resizing frame
2023-02-15 11:49:17 +01:00
alonso.torres
70edd2c290 🐛 Fix crash when resizing frame 2023-02-15 09:59:28 +01:00
alonso.torres
02543b1a4f 🐛 Fix problem with opacity in frames 2023-02-14 17:54:51 +01:00
Alejandro
094556926e Merge pull request #2932 from penpot/eva-change-onboarding-images
💄 Update onboarding images with new style
2023-02-13 16:54:23 +01:00
Andrey Antukh
1ed3b3cf75 📎 Add missing restart policy to some containers
on default compose file
2023-02-10 14:07:12 +01:00
Eva
1637e82018 💄 Update onboarding images with new style 2023-02-10 13:52:53 +01:00
Andrey Antukh
c467d04d50 🐛 Fix permission issue on docker images 2023-02-10 13:37:33 +01:00
Andrey Antukh
8d19c067e8 🐛 Fix incorrect mountpoint on docker compose 2023-02-10 13:23:22 +01:00
Alejandro
a99fb7ada3 Merge pull request #2922 from penpot/palba-fix-middle-button-drags-guides
🐛 Fix middle button panning can drag guides
2023-02-09 14:27:28 +01:00
Alejandro
2f1d1a6c41 Merge pull request #2921 from penpot/eva-fix-invite-members-btn
🐛 Fix invite members text on modal button
2023-02-09 14:23:40 +01:00
Eva
7f963edf9e 🐛 Fix invite members text on modal button 2023-02-09 13:51:43 +01:00
Eva Marco
9c99d86e08 Merge pull request #2927 from penpot/alotor-fix-auto-size
Fix auto size
2023-02-09 13:51:03 +01:00
Eva
6a5bfdd7fb ❤️ Add thanks for ondrejkonec 2023-02-09 13:36:26 +01:00
Ondřej Konečný
a98ba72c12 added width property to avoid shrinking on icons 2023-02-09 13:33:06 +01:00
alonso.torres
ee42dd8b01 🐛 Fix layout on multiple selection 2023-02-09 11:18:37 +01:00
alonso.torres
da209b7507 🐛 Fix problem with auto sizes 2023-02-09 10:41:18 +01:00
Pablo Alba
d49e1f1641 🐛 Fix middle button panning can drag guides 2023-02-09 08:53:42 +01:00
Pablo Alba
8e35ad0f7f Merge pull request #2896 from penpot/eva-bugfixing-6
🐛 Fix paste a frame inside itself
2023-02-08 12:16:09 +01:00
Eva
be3a973d09 🐛 Fix paste a frame inside itself 2023-02-08 12:01:11 +01:00
Andrey Antukh
78aea0f24e 🐛 Fix incorrect props cleaning on auditlog 2023-02-08 10:35:57 +01:00
Andrey Antukh
6e1ce62aad Merge branch 'staging' 2023-02-07 17:06:42 +01:00
Alejandro
070ea135e5 Merge pull request #2919 from penpot/niwinz-docker-oidc-fixes
🐛 Docker & OIDC fixes
2023-02-07 16:56:22 +01:00
Andrey Antukh
5ae1fe5867 📎 Add nano editor to backend docker image 2023-02-07 16:50:58 +01:00
Andrey Antukh
eef2cba976 🐛 Fix incorrect registration flag handling on frontend
registration flag should not prevent include register on the
router because a registration process can be started from oidc
auth process
2023-02-07 16:50:52 +01:00
Andrey Antukh
1c4dcf1574 Add minor improvements to logging on docker images 2023-02-07 15:06:35 +01:00
Andrey Antukh
220b80799d Add more logging to OIDC providers 2023-02-07 14:49:12 +01:00
Alejandro Alonso
22b6d4241d 📎 Update version.txt file 2023-02-07 11:47:50 +01:00
Alejandro
fa02df7106 Merge pull request #2914 from penpot/alotor-small-fixes
Small fixes
2023-02-07 11:23:54 +01:00
Andrey Antukh
5d6462b2a7 🐛 Fix compatibility issues on docker upgrade path 2023-02-07 11:19:46 +01:00
Alejandro
3464842c1e Merge pull request #2917 from penpot/eva-bugfixing-7
🐛 Fix tooltips on left toolbar
2023-02-07 11:18:43 +01:00
Pablo Alba
d74af6ddc1 Revert "🐛 Fix line-height inconsistent"
This reverts commit 3974a4778a.
2023-02-07 11:18:01 +01:00
Alejandro
8cb33dc19c Merge pull request #2908 from penpot/niwinz-bugfix-oidc-autidiscover
🐛 Fix issue with oidc autodiscover
2023-02-07 11:16:00 +01:00
Eva
4912107fcc 🐛 Fix tooltips on left toolbar 2023-02-07 10:03:18 +01:00
alonso.torres
d5c7a6e547 🐛 Fix problem with auto-width and space-around 2023-02-07 00:17:16 +01:00
Andrey Antukh
f1085aadd1 🐛 Fix compatibility issues on docker upgrade path 2023-02-06 19:21:55 +01:00
alonso.torres
ca5b59f102 🐛 Fix sizing when moving shapes into/out of a layout 2023-02-06 17:50:59 +01:00
alonso.torres
a0898fbabd 🐛 Named redis volume 2023-02-06 17:00:42 +01:00
Andrey Antukh
aaf332ed18 🐛 Fix issue with oidc autodiscover 2023-02-06 14:20:57 +01:00
Christian Clauss
b05ca4bb82 🐛 Fix undefined name RuntimeException on manage.py script
Python defines [`RuntimeError`](https://docs.python.org/3.7/library/exceptions.html#RuntimeError)
but it does not define `RuntimeException` so a `NameError` will be raised when any of these lines
are executed.

% `python3 -c "RuntimeException('This is a test...')"`
```
Traceback (most recent call last):
  File "<string>", line 1, in <module>
NameError: name 'RuntimeException' is not defined
```

% `flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics`
```
./backend/scripts/manage.py:22:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:25:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:49:23: F821 undefined name 'RuntimeException'
                raise RuntimeException("unexpected response from PREPL")
                      ^
3     F821 undefined name 'RuntimeException'
3
```
2023-02-05 11:19:41 +01:00
Christian Clauss
b46b23b027 🐛 Fix undefined name RuntimeException on manage.py script
Python defines [`RuntimeError`](https://docs.python.org/3.7/library/exceptions.html#RuntimeError)
but it does not define `RuntimeException` so a `NameError` will be raised when any of these lines
are executed.

% `python3 -c "RuntimeException('This is a test...')"`
```
Traceback (most recent call last):
  File "<string>", line 1, in <module>
NameError: name 'RuntimeException' is not defined
```

% `flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics`
```
./backend/scripts/manage.py:22:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:25:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:49:23: F821 undefined name 'RuntimeException'
                raise RuntimeException("unexpected response from PREPL")
                      ^
3     F821 undefined name 'RuntimeException'
3
```
2023-02-05 11:19:28 +01:00
Mario Bašić
29c0190b7a 🐛 Add mailcatch to penpot network on docker compose
Without this the backend complains that it cannot connect to the smtp host (when using mailcatcher). The reason is because the mailcatcher is not on the same network as the backend application.
2023-02-05 11:16:34 +01:00
Mario Bašić
f1b09e763e 🐛 Add mailcatch to penpot network on docker compose
Without this the backend complains that it cannot connect to the smtp host (when using mailcatcher). The reason is because the mailcatcher is not on the same network as the backend application.
2023-02-05 11:16:07 +01:00
Alejandro
2e5e772392 Merge pull request #2900 from penpot/alotor-fix-space-around-bounds
🐛 Fix problem with space-around
2023-02-02 19:47:41 +01:00
alonso.torres
ecd4bb54c9 🐛 Fix problem with space-around 2023-02-02 19:31:02 +01:00
Alejandro Alonso
3cfc432c23 Merge remote-tracking branch 'origin/staging' 2023-02-02 18:07:20 +01:00
Alejandro
e426425cb5 Merge pull request #2898 from penpot/alotor-fix-safari-thumbnails
🐛 Temporary deactivate thumbnails in Safari
2023-02-02 17:37:25 +01:00
alonso.torres
3a0cc63fa7 🐛 Temporary deactivate thumbnails in Safari 2023-02-02 17:32:12 +01:00
Alejandro
88a8370e8d Merge pull request #2897 from penpot/alotor-pro-fixes
Fixes
2023-02-02 16:08:30 +01:00
alonso.torres
e8972dd802 🐛 Fix problem with thumbnail updating 2023-02-02 15:34:35 +01:00
alonso.torres
3e52bef6d4 🐛 Fix problem with multiple selection of layout items 2023-02-02 13:37:34 +01:00
alonso.torres
7c215dc11b 🐛 Align-items center/end weren't respected when layout was outside bounds 2023-02-02 13:37:34 +01:00
alonso.torres
48c3e3e00b 🐛 Fix problem with Safari canvas behavior 2023-02-02 13:37:34 +01:00
Alejandro
412dcae01a Merge pull request #2894 from penpot/fix-social-urls
🐛 Fix social links broken
2023-02-02 13:20:25 +01:00
Pablo Alba
cc5f245209 🐛 Fix social links broken 2023-02-02 13:13:24 +01:00
Alejandro
dc4aabe263 Merge pull request #2893 from penpot/palba-fix-can-move-shape-with-lens-zoom
🐛 Fix can move shape with lens zoom active
2023-02-02 13:12:34 +01:00
Pablo Alba
708a8ce27b 🐛 Fix can move shape with lens zoom active 2023-02-02 12:59:02 +01:00
Alejandro
7c1d9ce06f Merge pull request #2892 from penpot/alotor-fix-safari-problem
🐛 Fix problem with thumbnails in safari
2023-02-02 11:36:19 +01:00
Aitor
b0cbf09950 Merge pull request #2885 from penpot/eva-bugfixing-5
Some bug fixing
2023-02-02 11:33:25 +01:00
Aitor
f31bc7457f Merge branch 'staging' into eva-bugfixing-5 2023-02-02 11:31:49 +01:00
alonso.torres
e47ce3235e 🐛 Fix problem with thumbnails in safari 2023-02-02 11:31:10 +01:00
Alejandro
fe76e0fab6 Merge pull request #2884 from penpot/alotor-post-release-fixes
Post release fixes
2023-02-02 11:22:53 +01:00
Alejandro
297ba10e9d Merge pull request #2886 from penpot/hiru-dashboard-names
🐛 Fix weird numeration creating elements in dashboard
2023-02-02 10:51:26 +01:00
Andrés Moya
dd2321a37b 🐛 Fix weird numeration creating elements in dashboard 2023-02-02 10:19:36 +01:00
Eva
f98630a46b 🐛 Fix invitations input on team management and onboarding modal 2023-02-02 09:37:21 +01:00
Eva
82d6ba790c 🐛 Fix button spacing on delete account modal 2023-02-02 09:37:19 +01:00
Eva
575aec209c 🐛 Fix button spacing on delete acount modal 2023-02-02 09:37:00 +01:00
alonso.torres
00e265695c Change parent/children constraint for problematic configurations 2023-02-02 09:18:53 +01:00
alonso.torres
071ac0366c 🐛 Fix problem with max-size 0 2023-02-02 09:18:53 +01:00
alonso.torres
1a2a90f829 🐛 Fix problems with strange file 2023-02-02 09:18:53 +01:00
alonso.torres
028c084b22 🐛 Add limit to growth fill shapes to the bounds of the layout 2023-02-02 09:18:53 +01:00
alonso.torres
e7e80e99bd 🐛 Fix thumbnail not updating when changing pages 2023-02-02 09:18:53 +01:00
alonso.torres
70fa169d0d 🐛 Fix problem with selection on nested boards 2023-02-02 09:18:53 +01:00
Alejandro
6be83fc6d6 Merge pull request #2889 from penpot/palba-fix-commad-z-in-mac
🐛 Fix typing CMD+Z on MacOS turns the cursor into a Zoom cursor
2023-02-01 22:45:26 +01:00
Pablo Alba
1e9ece43d0 🐛 Fix typing CMD+Z on MacOS turns the cursor into a Zoom cursor 2023-02-01 21:56:30 +01:00
Alejandro
965c0d6fa2 Merge pull request #2888 from penpot/hiru-move-readme-images
💄 Move all README images to a subfolder
2023-02-01 20:40:14 +01:00
Andrés Moya
950d5dcc2f 💄 Move all README images to a subfolder 2023-02-01 19:31:12 +01:00
Andrey Antukh
43d034798c Merge branch 'staging' 2023-02-01 18:06:50 +01:00
Andrey Antukh
86712f977d 🐛 Skip unnecesary mutation events from audit log 2023-02-01 18:06:12 +01:00
Alejandro Alonso
707e6c2a33 Merge remote-tracking branch 'origin/staging' 2023-02-01 13:12:34 +01:00
Alejandro
3dfd87eee1 Merge pull request #2883 from penpot/palba-fix-components-groups-names-in-list-mode
🐛 Fix components groups items show the component name in list mode
2023-02-01 10:41:33 +01:00
Alejandro
037ba19e87 Merge pull request #2882 from penpot/niwinz-webhooks-fix
🐛 Fix incorrect state management on webhooks crud
2023-02-01 10:38:33 +01:00
Pablo Alba
cdbab2c098 🐛 Fix components groups items show the component name in list mode 2023-02-01 10:23:51 +01:00
Andrey Antukh
e8ea61ee78 🐛 Fix incorrect state management on webhooks crud 2023-02-01 10:15:25 +01:00
Andrey Antukh
7ab91f68af Merge branch 'staging' 2023-01-31 23:02:22 +01:00
Andrey Antukh
91ececa59e 🐛 Fix backend flags on docker compose sample file 2023-01-31 23:01:58 +01:00
Andrey Antukh
8758723200 Merge pull request #2874 from penpot/niwinz-hotfix-1
🐛 Fix docker images issues in the latest release
2023-01-31 23:00:30 +01:00
Alejandro Alonso
8a968dc081 🐛 Fix upload team image 2023-01-31 22:29:05 +01:00
Andrey Antukh
f8cb505196 🐛 Fix arguments handling on docker/images/build.sh script 2023-01-31 19:48:41 +01:00
Andrey Antukh
14e3439cae 🔥 Remove admin mention from compose, still not working correctly 2023-01-31 19:08:38 +01:00
Andrey Antukh
7dd55c7f9d Revert JRE/JLINK changes from Dockerfile.backend file 2023-01-31 18:50:53 +01:00
Alejandro Alonso
e8e3398a74 🐛 Fix default nginx configuration for docker images 2023-01-31 17:42:28 +01:00
Alejandro Alonso
95cad24c18 Merge remote-tracking branch 'origin/staging' 2023-01-31 13:57:30 +01:00
Andrés Moya
77cd645e25 🔧 Update docker-compose without needing config file 2023-01-23 10:34:00 +01:00
Alejandro
04dc9f7881 Merge pull request #2736 from penpot/superalex-fix-text-sync-hotfix
🐛 Fix text content sync and touched detection in shape displacement
2023-01-09 11:35:59 +01:00
Andrés Moya
0863a96f93 🐛 Fix text content sync and touched detection in shape displacement 2023-01-05 13:26:33 +01:00
Alejandro
216a43cc43 Merge pull request #2731 from penpot/superalex-fix-enter-events-hotfix
🐛 Fix enter events
2023-01-05 07:02:34 +01:00
Alejandro Alonso
05431cc757 🐛 Fix enter events 2023-01-04 13:23:05 +01:00
107 changed files with 1655 additions and 1118 deletions

View File

@@ -1,4 +1,36 @@
# CHANGELOG
## 1.17.2
### :bug: Bugs fixed
- Fix invite members button text [Taiga #4794](https://tree.taiga.io/project/penpot/issue/4794)
- Fix problem with opacity in frames [Taiga #4795](https://tree.taiga.io/project/penpot/issue/4795)
- Fix correct behaviour for space-around and added space-evenly option
- Fix duplicate with alt and undo only undo one step [Taiga #4746](https://tree.taiga.io/project/penpot/issue/4746)
- Fix problem creating frames inside layout [Taiga #4844](https://tree.taiga.io/project/penpot/issue/4844)
## 1.17.2
### :bug: Bugs fixed
- Fix paste board inside itself [Taiga #4775](https://tree.taiga.io/project/penpot/issue/4775)
- Fix middle button panning can drag guides [Taiga #4266](https://tree.taiga.io/project/penpot/issue/4266)
### :heart: Community contributions by (Thank you!)
- To @ondrejkonec: for some code contributions on this release.
## 1.17.1
### :bug: Bugs fixed
- Fix components groups items show the component name in list mode [Taiga #4770](https://tree.taiga.io/project/penpot/issue/4770)
- Fix typing CMD+Z on MacOS turns the cursor into a Zoom cursor [Taiga #4778](https://tree.taiga.io/project/penpot/issue/4778)
- Fix white space on small screens [Taiga #4774](https://tree.taiga.io/project/penpot/issue/4774)
- Fix button spacing on delete acount modal [Taiga #4762](https://tree.taiga.io/project/penpot/issue/4762)
- Fix invitations input on team management and onboarding modal [Taiga #4760](https://tree.taiga.io/project/penpot/issue/4760)
- Fix weird numeration creating new elements in dashboard [Taiga #4755](https://tree.taiga.io/project/penpot/issue/4755)
- Fix can move shape with lens zoom active [Taiga #4787](https://tree.taiga.io/project/penpot/issue/4787)
- Fix social links broken [Taiga #4759](https://tree.taiga.io/project/penpot/issue/4759)
- Fix tooltips on left toolbar [Taiga #4793](https://tree.taiga.io/project/penpot/issue/4793)
## 1.17.0

View File

@@ -50,7 +50,7 @@ Being web based, Penpot is not dependent on operating systems or local installat
Using SVG as no other design and prototyping tool does, Penpot files sport compatibility with most of the vectorial tools, are tech friendly and extremely easy to use on the web. We make sure you will always own your work.
<p align="center">
<img src="https://penpot.app/images/open-source.png" alt="Open Source">
<img src="https://penpot.app/images/readme/open-source.png" alt="Open Source">
</p>
@@ -93,7 +93,7 @@ You will find the following categories:
- [Penpot in your language](https://community.penpot.app/c/penpot-in-your-language/12)
<p align="center">
<img src="https://penpot.app/images/cross-teams.webp" alt="Community">
<img src="https://penpot.app/images/readme/cross-teams.webp" alt="Community">
</p>
## Contributing ##

View File

@@ -48,8 +48,8 @@
<mj-social-element src="{{ public-uri }}/images/email/logo-uxbox.png" href="https://penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-twitter.png" href="https://twitter.com/penpotapp" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-github.png" href="https://github.com/penpot/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://instagram.com/penpotapp/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/uxbox" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://www.instagram.com/penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/penpot" padding="0 8px" />
</mj-social>
</mj-column>
</mj-section>

View File

@@ -41,8 +41,8 @@
<mj-social-element src="{{ public-uri }}/images/email/logo-uxbox.png" href="https://penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-twitter.png" href="https://twitter.com/penpotapp" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-github.png" href="https://github.com/penpot/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://instagram.com/penpotapp/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/uxbox" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://www.instagram.com/penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/penpot" padding="0 8px" />
</mj-social>
</mj-column>
</mj-section>

View File

@@ -50,8 +50,8 @@
<mj-social-element src="{{ public-uri }}/images/email/logo-uxbox.png" href="https://penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-twitter.png" href="https://twitter.com/penpotapp" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-github.png" href="https://github.com/penpot/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://instagram.com/penpotapp/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/uxbox" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://www.instagram.com/penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/penpot" padding="0 8px" />
</mj-social>
</mj-column>
</mj-section>

View File

@@ -47,8 +47,8 @@
<mj-social-element src="{{ public-uri }}/images/email/logo-uxbox.png" href="https://penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-twitter.png" href="https://twitter.com/penpotapp" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-github.png" href="https://github.com/penpot/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://instagram.com/penpotapp/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/uxbox" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://www.instagram.com/penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/penpot" padding="0 8px" />
</mj-social>
</mj-column>
</mj-section>

View File

@@ -103,9 +103,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -129,9 +129,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -143,7 +143,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -157,9 +157,9 @@
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -211,9 +211,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -225,7 +225,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -239,9 +239,9 @@
<td style="direction:ltr;font-size:0px;padding:24px 0 0 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:425px;"
>
@@ -257,9 +257,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -271,7 +271,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -285,9 +285,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -301,7 +301,7 @@
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -321,7 +321,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -341,7 +341,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -361,7 +361,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -370,7 +370,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://instagram.com/penpotapp/" target="_blank">
<a href="https://www.instagram.com/penpot.app/" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -381,7 +381,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -390,7 +390,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://tree.taiga.io/project/uxbox" target="_blank">
<a href="https://tree.taiga.io/project/penpot" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -401,7 +401,7 @@
</table>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
@@ -411,9 +411,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -425,7 +425,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -439,9 +439,9 @@
<td style="direction:ltr;font-size:0px;padding:0 0 24px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -457,9 +457,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>

View File

@@ -103,9 +103,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -129,9 +129,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -143,7 +143,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -157,9 +157,9 @@
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -201,9 +201,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -215,7 +215,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -229,9 +229,9 @@
<td style="direction:ltr;font-size:0px;padding:24px 0 0 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:425px;"
>
@@ -247,9 +247,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -261,7 +261,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -275,9 +275,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -291,7 +291,7 @@
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -311,7 +311,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -331,7 +331,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -351,7 +351,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -360,7 +360,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://instagram.com/penpotapp/" target="_blank">
<a href="https://www.instagram.com/penpot.app/" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -371,7 +371,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -380,7 +380,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://tree.taiga.io/project/uxbox" target="_blank">
<a href="https://tree.taiga.io/project/penpot" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -391,7 +391,7 @@
</table>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
@@ -401,9 +401,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -415,7 +415,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -429,9 +429,9 @@
<td style="direction:ltr;font-size:0px;padding:0 0 24px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -447,9 +447,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>

View File

@@ -103,9 +103,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -129,9 +129,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -143,7 +143,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -157,9 +157,9 @@
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -206,9 +206,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -220,7 +220,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -234,9 +234,9 @@
<td style="direction:ltr;font-size:0px;padding:24px 0 0 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:425px;"
>
@@ -252,9 +252,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -266,7 +266,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -280,9 +280,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -296,7 +296,7 @@
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -316,7 +316,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -336,7 +336,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -356,7 +356,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -365,7 +365,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://instagram.com/penpotapp/" target="_blank">
<a href="https://www.instagram.com/penpot.app/" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -376,7 +376,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -385,7 +385,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://tree.taiga.io/project/uxbox" target="_blank">
<a href="https://tree.taiga.io/project/penpot" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -396,7 +396,7 @@
</table>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
@@ -406,9 +406,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -420,7 +420,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -434,9 +434,9 @@
<td style="direction:ltr;font-size:0px;padding:0 0 24px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -452,9 +452,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>

View File

@@ -103,9 +103,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -129,9 +129,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -143,7 +143,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -157,9 +157,9 @@
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -201,9 +201,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -215,7 +215,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -229,9 +229,9 @@
<td style="direction:ltr;font-size:0px;padding:24px 0 0 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:425px;"
>
@@ -247,9 +247,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -261,7 +261,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -275,9 +275,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -291,7 +291,7 @@
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -311,7 +311,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -331,7 +331,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -351,7 +351,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -360,7 +360,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://instagram.com/penpotapp/" target="_blank">
<a href="https://www.instagram.com/penpot.app/" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -371,7 +371,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -380,7 +380,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://tree.taiga.io/project/uxbox" target="_blank">
<a href="https://tree.taiga.io/project/penpot" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -391,7 +391,7 @@
</table>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
@@ -401,9 +401,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -415,7 +415,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -429,9 +429,9 @@
<td style="direction:ltr;font-size:0px;padding:0 0 24px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -447,9 +447,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>

View File

@@ -12,6 +12,7 @@ cp ../CHANGES.md target/classes/changelog.md;
clojure -T:build jar;
mv target/penpot.jar target/dist/penpot.jar
cp resources/log4j2.xml target/dist/log4j2.xml
cp scripts/run.template.sh target/dist/run.sh;
cp scripts/manage.py target/dist/manage.py
chmod +x target/dist/run.sh;

View File

@@ -19,10 +19,10 @@ PREPL_URI = "tcp://localhost:6063"
def get_prepl_conninfo():
uri_data = urlparse(PREPL_URI)
if uri_data.scheme != "tcp":
raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
raise RuntimeError(f"invalid PREPL_URI: {PREPL_URI}")
if not isinstance(uri_data.netloc, str):
raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
raise RuntimeError(f"invalid PREPL_URI: {PREPL_URI}")
host, port = uri_data.netloc.split(":", 2)
@@ -46,7 +46,7 @@ def send_eval(expr):
result = json.load(f)
tag = result.get("tag", None)
if tag != "ret":
raise RuntimeException("unexpected response from PREPL")
raise RuntimeError("unexpected response from PREPL")
return result.get("val", None), result.get("exception", None)
def encode(val):

View File

@@ -18,5 +18,7 @@ if [ -f ./environ ]; then
source ./environ
fi
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow $JVM_OPTS"
set -x
exec $JAVA_CMD $JVM_OPTS "$@" -jar penpot.jar -m app.main

View File

@@ -50,7 +50,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- discover-oidc-config
[cfg {:keys [::base-uri] :as opts}]
[cfg {:keys [base-uri] :as opts}]
(let [discovery-uri (u/join base-uri ".well-known/openid-configuration")
response (ex/try! (http/req! cfg
{:method :get :uri (str discovery-uri)}
@@ -64,10 +64,17 @@
nil)
(= 200 (:status response))
(let [data (json/decode (:body response))]
{:token-uri (get data :token_endpoint)
:auth-uri (get data :authorization_endpoint)
:user-uri (get data :userinfo_endpoint)})
(let [data (json/decode (:body response))
token-uri (get data :token_endpoint)
auth-uri (get data :authorization_endpoint)
user-uri (get data :userinfo_endpoint)]
(l/debug :hint "oidc uris discovered"
:token-uri token-uri
:auth-uri auth-uri
:user-uri user-uri)
{:token-uri token-uri
:auth-uri auth-uri
:user-uri user-uri})
:else
(do
@@ -110,7 +117,7 @@
(if-let [opts (prepare-oidc-opts cfg)]
(do
(l/info :hint "provider initialized"
:provider :oidc
:provider "oidc"
:method (if (:discover? opts) "discover" "manual")
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts))
@@ -122,7 +129,7 @@
:roles (:roles opts))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :oidc)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "oidc")
nil))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -144,13 +151,13 @@
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :google
:provider "google"
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts)))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :google)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "google")
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -196,13 +203,13 @@
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :github
:provider "github"
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts)))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :github)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "github")
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -225,14 +232,14 @@
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :gitlab
:provider "gitlab"
:base-uri base
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts)))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :gitlab)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "gitlab")
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -275,8 +282,19 @@
"accept" "application/json"}
:uri (:token-uri provider)
:body (u/map->query-string params)}]
(l/trace :hint "request access token"
:provider (:name provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))
:grant-type (:grant_type params)
:redirect-uri (:redirect_uri params))
(->> (http/req! cfg req)
(p/map (fn [{:keys [status body] :as res}]
(l/trace :hint "access token response"
:status status
:body body)
(if (= status 200)
(let [data (json/decode body)]
{:token (get data :access_token)
@@ -289,12 +307,19 @@
(defn- retrieve-user-info
[{:keys [provider] :as cfg} tdata]
(letfn [(retrieve []
(l/trace :hint "request user info"
:uri (:user-uri provider)
:token (obfuscate-string (:token tdata))
:token-type (:type tdata))
(http/req! cfg
{:uri (:user-uri provider)
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get}))
(validate-response [response]
(l/trace :hint "user info response"
:status (:status response)
:body (:body response))
(when-not (s/int-in-range? 200 300 (:status response))
(ex/raise :type :internal
:code :unable-to-retrieve-user-info
@@ -309,7 +334,7 @@
(if-let [get-email-fn (:get-email-fn provider)]
(get-email-fn tdata info)
(let [attr-kw (cf/get :oidc-email-attr :email)]
(get info attr-kw))))
(p/resolved (get info attr-kw)))))
(get-name [info]
(let [attr-kw (cf/get :oidc-name-attr :name)]
@@ -325,6 +350,7 @@
(qualify-props provider))}))
(validate-info [info]
(l/trace :hint "authentication info" :info info)
(when-not (s/valid? ::info info)
(l/warn :hint "received incomplete profile info object (please set correct scopes)"
:info (pr-str info))
@@ -334,10 +360,10 @@
:info info))
info)]
(-> (retrieve)
(p/then validate-response)
(p/then process-response)
(p/then validate-info))))
(->> (retrieve)
(p/fmap validate-response)
(p/mcat process-response)
(p/fmap validate-info))))
(s/def ::backend ::us/not-empty-string)
(s/def ::email ::us/not-empty-string)
@@ -434,12 +460,11 @@
(ex/raise :type :restriction
:code :profile-blocked))
(when-let [collector (::audit/collector cfg)]
(audit/submit! collector {:type "command"
:name "login"
:profile-id (:id profile)
:ip-addr (audit/parse-client-ip request)
:props (audit/profile->props profile)}))
(audit/submit! cfg {:type "command"
:name "login-with-password"
:profile-id (:id profile)
:ip-addr (audit/parse-client-ip request)
:props (audit/profile->props profile)})
(->> (redirect-response uri)
(sxf request)))

View File

@@ -167,7 +167,11 @@
(instance? javax.sql.DataSource v))
(s/def ::pool pool?)
(s/def ::conn some?)
;; DEPRECATED: to be removed in 1.18
(s/def ::conn-or-pool some?)
(s/def ::pool-or-conn some?)
(defn closed?
[pool]

View File

@@ -20,7 +20,6 @@
[app.loggers.audit.tasks :as-alias tasks]
[app.loggers.webhooks :as-alias webhooks]
[app.main :as-alias main]
[app.metrics :as mtx]
[app.rpc :as-alias rpc]
[app.tokens :as tokens]
[app.util.retry :as rtry]
@@ -30,7 +29,6 @@
[cuerdas.core :as str]
[integrant.core :as ig]
[lambdaisland.uri :as u]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.request :as yrq]))
@@ -77,28 +75,20 @@
(merge (:props profile))
(d/without-nils)))
(defn clean-props
[{:keys [profile-id] :as event}]
(let [invalid-keys #{:session-id
:password
:old-password
:token}
xform (comp
(remove (fn [kv]
(qualified-keyword? (first kv))))
(remove (fn [kv]
(contains? invalid-keys (first kv))))
(remove (fn [[k v]]
(and (= k :profile-id)
(= v profile-id))))
(filter (fn [[_ v]]
(or (string? v)
(keyword? v)
(uuid? v)
(boolean? v)
(number? v)))))]
(def reserved-props
#{:session-id
:password
:old-password
:token})
(update event :props #(into {} xform %))))
(defn clean-props
[props]
(into {}
(comp
(d/without-nils)
(d/without-qualified)
(remove #(contains? reserved-props (key %))))
props))
;; --- SPECS
@@ -132,7 +122,7 @@
(s/keys :req [::wrk/executor ::db/pool]))
(defmethod ig/pre-init-spec ::collector [_]
(s/keys :req [::db/pool ::wrk/executor ::mtx/metrics]))
(s/keys :req [::db/pool ::wrk/executor]))
(defmethod ig/init-key ::collector
[_ {:keys [::db/pool] :as cfg}]
@@ -143,8 +133,8 @@
:else
cfg))
(defn- persist-event!
[pool event]
(defn- handle-event!
[conn-or-pool event]
(us/verify! ::event event)
(let [params {:id (uuid/next)
:name (:name event)
@@ -161,7 +151,7 @@
::rtry/max-retries 6
::rtry/label "persist-audit-log-event"}
(let [now (dt/now)]
(db/insert! pool :audit-log
(db/insert! conn-or-pool :audit-log
(-> params
(update :props db/tjson)
(update :ip-addr db/inet)
@@ -180,7 +170,7 @@
:else label)
dedupe? (boolean (and batch-key batch-timeout))]
(wrk/submit! ::wrk/conn pool
(wrk/submit! ::wrk/conn conn-or-pool
::wrk/task :process-webhook-event
::wrk/queue :webhooks
::wrk/max-retries 0
@@ -191,16 +181,19 @@
::webhooks/event
(-> params
(dissoc :ip-addr)
(dissoc :type)))))))
(dissoc :type)))))
params))
(defn submit!
"Submit audit event to the collector."
[{:keys [::wrk/executor ::db/pool] :as collector} params]
(us/assert! ::collector collector)
(->> (px/submit! executor (partial persist-event! pool (d/without-nils params)))
(p/merr (fn [cause]
(l/error :hint "audit: unexpected error processing event" :cause cause)
(p/resolved nil)))))
[{:keys [::wrk/executor] :as cfg} params]
(let [conn (or (::db/conn cfg) (::db/pool cfg))]
(us/assert! ::wrk/executor executor)
(us/assert! ::db/pool-or-conn conn)
(try
(handle-event! conn (d/without-nils params))
(catch Throwable cause
(l/error :hint "audit: unexpected error processing event" :cause cause)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TASK: ARCHIVE
@@ -247,7 +240,7 @@
from audit_log
where archived_at is null
order by created_at asc
limit 256
limit 128
for update skip locked;")
(defn archive-events

View File

@@ -14,7 +14,6 @@
[app.db :as-alias db]
[app.http.client :as-alias http.client]
[app.http.session :as-alias http.session]
[app.loggers.audit :as-alias audit]
[app.loggers.audit.tasks :as-alias audit.tasks]
[app.loggers.webhooks :as-alias webhooks]
[app.loggers.zmq :as-alias lzmq]
@@ -268,10 +267,8 @@
:github (ig/ref ::oidc.providers/github)
:gitlab (ig/ref ::oidc.providers/gitlab)
:oidc (ig/ref ::oidc.providers/generic)}
::audit/collector (ig/ref ::audit/collector)
::http.session/session (ig/ref :app.http.session/manager)}
;; TODO: revisit the dependencies of this service, looks they are too much unused of them
:app.http/router
{:assets (ig/ref :app.http.assets/handlers)
@@ -324,8 +321,7 @@
:scheduled-executor (ig/ref ::wrk/scheduled-executor)}
:app.rpc/methods
{::audit/collector (ig/ref ::audit/collector)
::http.client/client (ig/ref ::http.client/client)
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::props (ig/ref :app.setup/props)
@@ -423,11 +419,6 @@
::lzmq/receiver
{}
::audit/collector
{::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::mtx/metrics (ig/ref ::mtx/metrics)}
::audit.tasks/archive
{::props (ig/ref :app.setup/props)
::db/pool (ig/ref ::db/pool)

View File

@@ -7,11 +7,11 @@
(ns app.rpc
(:require
[app.auth.ldap :as-alias ldap]
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.http :as-alias http]
[app.http.client :as-alias http.client]
@@ -164,7 +164,8 @@
(defn- wrap-audit
[cfg f mdata]
(if-let [collector (::audit/collector cfg)]
(if (or (contains? cf/flags :webhooks)
(contains? cf/flags :audit-log))
(letfn [(handle-audit [params result]
(let [resultm (meta result)
request (::http/request params)
@@ -181,8 +182,7 @@
(merge (::audit/props resultm))
(dissoc :profile-id)
(dissoc :type)))
(d/without-qualified)
(d/without-nils))
(audit/clean-props))
event {:type (or (::audit/type resultm)
(::type cfg))
@@ -210,13 +210,14 @@
(::webhooks/event? resultm)
false)}]
(audit/submit! collector event)))
(audit/submit! cfg event)))
(handle-request [cfg params]
(->> (f cfg params)
(p/mcat (fn [result]
(->> (handle-audit params result)
(p/map (constantly result)))))))]
(p/fnly (fn [result cause]
(when-not cause
(handle-audit params result))))))]
(if-not (::audit/skip mdata)
(with-meta handle-request mdata)
f))
@@ -316,8 +317,7 @@
(s/def ::sprops map?)
(defmethod ig/pre-init-spec ::methods [_]
(s/keys :req [::audit/collector
::http.client/client
(s/keys :req [::http.client/client
::db/pool
::ldap/provider
::wrk/executor]

View File

@@ -348,7 +348,7 @@
:extra-data ptoken})))
(defn register-profile
[{:keys [conn session] :as cfg} {:keys [token] :as params}]
[{:keys [::db/conn session] :as cfg} {:keys [token] :as params}]
(let [claims (tokens/verify (::main/props cfg) {:token token :iss :prepared-register})
params (merge params claims)
@@ -372,11 +372,10 @@
;; accordingly.
(when-let [id (:profile-id claims)]
(db/update! conn :profile {:modified-at (dt/now)} {:id id})
(when-let [collector (::audit/collector cfg)]
(audit/submit! collector
{:type "fact"
:name "register-profile-retry"
:profile-id id})))
(audit/submit! cfg
{:type "fact"
:name "register-profile-retry"
:profile-id id}))
(cond
;; If invitation token comes in params, this is because the
@@ -428,7 +427,7 @@
::doc/added "1.15"}
[{:keys [::db/pool] :as cfg} params]
(db/with-atomic [conn pool]
(-> (assoc cfg :conn conn)
(-> (assoc cfg ::db/conn conn)
(register-profile params))))
;; ---- COMMAND: Request Profile Recovery

View File

@@ -996,7 +996,8 @@
:opt-un [::data]))
(sv/defmethod ::upsert-file-object-thumbnail
{::doc/added "1.17"}
{::doc/added "1.17"
::audit/skip true}
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
@@ -1026,7 +1027,8 @@
(sv/defmethod ::upsert-file-thumbnail
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.17"}
{::doc/added "1.17"
::audit/skip true}
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)

View File

@@ -473,7 +473,7 @@
(s/def ::team-id ::us/uuid)
(s/def ::member-id ::us/uuid)
;; Temporarily disabled viewer role
;; https://tree.taiga.io/project/uxboxproject/issue/1083
;; https://tree.taiga.io/project/penpot/issue/1083
;; (s/def ::role #{:owner :admin :editor :viewer})
(s/def ::role #{:owner :admin :editor})
@@ -638,10 +638,11 @@
;; --- Mutation: Create Team Invitation
(def sql:upsert-team-invitation
"insert into team_invitation(team_id, email_to, role, valid_until)
values (?, ?, ?, ?)
"insert into team_invitation(id, team_id, email_to, role, valid_until)
values (?, ?, ?, ?, ?)
on conflict(team_id, email_to) do
update set role = ?, valid_until = ?, updated_at = now();")
update set role = ?, valid_until = ?, updated_at = now()
returning *")
(defn- create-invitation-token
[cfg {:keys [profile-id valid-until team-id member-id member-email role]}]
@@ -662,16 +663,8 @@
:exp (dt/in-future {:days 30})}))
(defn- create-invitation
[{:keys [::conn] :as cfg} {:keys [team profile role email] :as params}]
(let [member (profile/retrieve-profile-data-by-email conn email)
expire (dt/in-future "168h") ;; 7 days
itoken (create-invitation-token cfg {:profile-id (:id profile)
:valid-until expire
:team-id (:id team)
:member-email (or (:email member) email)
:member-id (:id member)
:role role})
ptoken (create-profile-identity-token cfg profile)]
[{:keys [::db/conn] :as cfg} {:keys [team profile role email] :as params}]
(let [member (profile/retrieve-profile-data-by-email conn email)]
(when (and member (not (eml/allow-send-emails? conn member)))
(ex/raise :type :validation
@@ -686,9 +679,6 @@
:email email
:hint "the email you invite has been repeatedly reported as spam or bounce"))
(when (contains? cf/flags :log-invitation-tokens)
(l/trace :hint "invitation token" :token itoken))
;; When we have email verification disabled and invitation user is
;; already present in the database, we proceed to add it to the
;; team as-is, without email roundtrip.
@@ -709,10 +699,38 @@
(when-not (:is-active member)
(db/update! conn :profile
{:is-active true}
{:id (:id member)})))
(do
(db/exec-one! conn [sql:upsert-team-invitation
(:id team) (str/lower email) (name role) expire (name role) expire])
{:id (:id member)}))
nil)
(let [id (uuid/next)
expire (dt/in-future "168h") ;; 7 days
invitation (db/exec-one! conn [sql:upsert-team-invitation id
(:id team) (str/lower email)
(name role) expire
(name role) expire])
updated? (not= id (:id invitation))
tprops {:profile-id (:id profile)
:invitation-id (:id invitation)
:valid-until expire
:team-id (:id team)
:member-email (:email-to invitation)
:member-id (:id member)
:role role}
itoken (create-invitation-token cfg tprops)
ptoken (create-profile-identity-token cfg profile)]
(when (contains? cf/flags :log-invitation-tokens)
(l/info :hint "invitation token" :token itoken))
(audit/submit! cfg
{:type "action"
:name (if updated?
"update-team-invitation"
"create-team-invitation")
:profile-id (:id profile)
:props (-> (dissoc tprops :profile-id)
(d/without-nils))})
(eml/send! {::eml/conn conn
::eml/factory eml/invite-to-team
:public-uri (cf/get :public-uri)
@@ -720,9 +738,9 @@
:invited-by (:fullname profile)
:team (:name team)
:token itoken
:extra-data ptoken})))
:extra-data ptoken})
itoken))
itoken))))
(s/def ::email ::us/email)
(s/def ::emails ::us/set-of-valid-emails)
@@ -763,14 +781,14 @@
:code :profile-is-muted
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
(let [cfg (assoc cfg ::conn conn)
(let [cfg (assoc cfg ::db/conn conn)
invitations (->> emails
(map (fn [email]
{:email (str/lower email)
:team team
:profile profile
:role role}))
(map (partial create-invitation cfg)))]
(keep (partial create-invitation cfg)))]
(with-meta (vec invitations)
{::audit/props {:invitations (count invitations)}})))))
@@ -789,7 +807,7 @@
(let [params (assoc params :profile-id profile-id)
team (create-team conn params)
profile (db/get-by-id conn :profile profile-id)
cfg (assoc cfg ::conn conn)]
cfg (assoc cfg ::db/conn conn)]
;; Create invitations for all provided emails.
(->> emails
@@ -812,18 +830,16 @@
::quotes/team-id (:id team)
::quotes/incr (count emails)}))
(-> team
(vary-meta assoc ::audit/props {:invitations (count emails)})
(rph/with-defer
#(when-let [collector (::audit/collector cfg)]
(audit/submit! collector
{:type "command"
:name "create-team-invitations"
:profile-id profile-id
:props {:emails emails
:role role
:profile-id profile-id
:invitations (count emails)}})))))))
(audit/submit! cfg
{:type "command"
:name "create-team-invitations"
:profile-id profile-id
:props {:emails emails
:role role
:profile-id profile-id
:invitations (count emails)}})
(vary-meta team assoc ::audit/props {:invitations (count emails)}))))
;; --- Query: get-team-invitation-token
@@ -839,7 +855,7 @@
{:team-id team-id
:email-to (str/lower email)})
(update :role keyword))
member (profile/retrieve-profile-data-by-email pool (:email invit))
member (profile/retrieve-profile-data-by-email pool (:email-to invit))
token (create-invitation-token cfg {:team-id (:team-id invit)
:profile-id profile-id
:valid-until (:valid-until invit)
@@ -885,6 +901,7 @@
(ex/raise :type :validation
:code :insufficient-permissions))
(db/delete! conn :team-invitation
{:team-id team-id :email-to (str/lower email)})
nil)))
(let [invitation (db/delete! conn :team-invitation
{:team-id team-id
:email-to (str/lower email)})]
(rph/wrap nil {::audit/props {:invitation-id (:id invitation)}})))))

View File

@@ -133,7 +133,7 @@
:opt-un [::spec.team-invitation/member-id]))
(defmethod process-token :team-invitation
[{:keys [conn session] :as cfg}
[{:keys [conn] :as cfg}
{:keys [::rpc/profile-id token]}
{:keys [member-id team-id member-email] :as claims}]
@@ -152,45 +152,30 @@
(if (some? profile)
(if (or (= member-id profile-id)
(= member-email (:email profile)))
;; if we have logged-in user and it matches the invitation we
;; proceed with accepting the invitation and joining the
;; current profile to the invited team.
;; if we have logged-in user and it matches the invitation we proceed
;; with accepting the invitation and joining the current profile to the
;; invited team.
(let [profile (accept-invitation cfg claims invitation profile)]
(-> (assoc claims :state :created)
(rph/with-meta {::audit/name "accept-team-invitation"
::audit/props (merge
(audit/profile->props profile)
{:team-id (:team-id claims)
:role (:role claims)})
::audit/profile-id profile-id})))
::audit/profile-id (:id profile)
::audit/props {:team-id (:team-id claims)
:role (:role claims)
:invitation-id (:id invitation)}})))
(ex/raise :type :validation
:code :invalid-token
:hint "logged-in user does not matches the invitation"))
;; If we have not logged-in user, we try find the invited
;; profile by member-id or member-email props of the invitation
;; token; If profile is found, we accept the invitation and
;; leave the user logged-in.
(if-let [member (db/get* conn :profile
(if member-id
{:id member-id}
{:email member-email})
{:columns [:id :email]})]
(let [profile (accept-invitation cfg claims invitation member)]
(-> (assoc claims :state :created)
(rph/with-transform (session/create-fn session (:id profile)))
(rph/with-meta {::audit/name "accept-team-invitation"
::audit/props (merge
(audit/profile->props profile)
{:team-id (:team-id claims)
:role (:role claims)})
::audit/profile-id member-id})))
;; If we have not logged-in user, and invitation comes with member-id we
;; redirect user to login, if no memeber-id is present in the invitation
;; token, we redirect user the the register page.
{:invitation-token token
:iss :team-invitation
:redirect-to :auth-register
:state :pending}))))
{:invitation-token token
:iss :team-invitation
:redirect-to (if member-id :auth-login :auth-register)
:state :pending})))
;; --- Default

View File

@@ -8,6 +8,7 @@
(:require
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.db :as db]
[app.http.client :as http]
@@ -22,10 +23,15 @@
[cuerdas.core :as str]
[promesa.core :as p]))
(defn decode-row
[{:keys [uri] :as row}]
(cond-> row
(string? uri) (assoc :uri (u/uri uri))))
;; --- Mutation: Create Webhook
(s/def ::team-id ::us/uuid)
(s/def ::uri ::us/not-empty-string)
(s/def ::uri ::us/uri)
(s/def ::is-active ::us/boolean)
(s/def ::mtype
#{"application/json"
@@ -59,7 +65,7 @@
(if (not= (:uri whook) (:uri params))
(->> (http/req! cfg {:method :head
:uri (:uri params)
:uri (str (:uri params))
:timeout (dt/duration "3s")})
(p/hmap (fn [response exception]
(if exception
@@ -79,22 +85,24 @@
(defn- insert-webhook!
[{:keys [::db/pool]} {:keys [team-id uri mtype is-active] :as params}]
(db/insert! pool :webhook
{:id (uuid/next)
:team-id team-id
:uri uri
:is-active is-active
:mtype mtype}))
(-> (db/insert! pool :webhook
{:id (uuid/next)
:team-id team-id
:uri (str uri)
:is-active is-active
:mtype mtype})
(decode-row)))
(defn- update-webhook!
[{:keys [::db/pool] :as cfg} {:keys [id] :as wook} {:keys [uri mtype is-active] :as params}]
(db/update! pool :webhook
{:uri uri
:is-active is-active
:mtype mtype
:error-code nil
:error-count 0}
{:id id}))
(-> (db/update! pool :webhook
{:uri (str uri)
:is-active is-active
:mtype mtype
:error-code nil
:error-count 0}
{:id id})
(decode-row)))
(sv/defmethod ::create-webhook
{::doc/added "1.17"}
@@ -110,7 +118,7 @@
(sv/defmethod ::update-webhook
{::doc/added "1.17"}
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(let [whook (db/get pool :webhook {:id id})]
(let [whook (-> (db/get pool :webhook {:id id}) (decode-row))]
(check-edition-permissions! pool profile-id (:team-id whook))
(->> (validate-webhook! cfg whook params)
(p/fmap executor (fn [_] (update-webhook! cfg whook params))))))
@@ -123,7 +131,7 @@
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id]}]
(db/with-atomic [conn pool]
(let [whook (db/get conn :webhook {:id id})]
(let [whook (-> (db/get conn :webhook {:id id}) decode-row)]
(check-edition-permissions! conn profile-id (:team-id whook))
(db/delete! conn :webhook {:id id})
nil)))
@@ -143,4 +151,5 @@
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id team-id]}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id team-id)
(db/exec! conn [sql:get-webhooks team-id])))
(->> (db/exec! conn [sql:get-webhooks team-id])
(mapv decode-row))))

View File

@@ -10,7 +10,7 @@
[app.common.logging :as l]
[app.common.spec :as us]
[app.db :as db]
[app.loggers.audit :as audit]
[app.loggers.audit :as-alias audit]
[app.rpc.climit :as-alias climit]
[app.rpc.commands.files :as cmd.files]
[app.rpc.commands.files.create :as cmd.files.create]
@@ -176,7 +176,8 @@
(sv/defmethod ::upsert-file-object-thumbnail
{::doc/added "1.13"
::doc/deprecated "1.17"}
::doc/deprecated "1.17"
::audit/skip true}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(cmd.files/check-edition-permissions! conn profile-id file-id)
@@ -192,7 +193,8 @@
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.13"
::doc/deprecated "1.17"}
::doc/deprecated "1.17"
::audit/skip true}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(cmd.files/check-edition-permissions! conn profile-id file-id)

View File

@@ -154,7 +154,7 @@
(t/is (th/success? out))
(let [[thread :as result] (:result out)]
(t/is (= 1 (count result)))
(t/is (= "Page-1" (:page-name thread)))
(t/is (= "Page 1" (:page-name thread)))
(t/is (= "hello world" (:content thread)))
(t/is (= 2 (:count-comments thread)))
(t/is (true? (:is-resolved thread))))))

View File

@@ -166,6 +166,7 @@
(t/deftest accept-invitation-tokens
(let [profile1 (th/create-profile* 1 {:is-active true})
profile2 (th/create-profile* 2 {:is-active true})
profile3 (th/create-profile* 3 {:is-active true})
team (th/create-team* 1 {:profile-id (:id profile1)})
@@ -181,25 +182,29 @@
:member-email (:email profile2)
:member-id (:id profile2)})]
;; --- Verify token as anonymous user
(t/testing "Verify token as anonymous user"
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [data {::th/type :verify-token :token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [data {::th/type :verify-token :token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(t/is (= :created (:state result)))
(t/is (= (:email profile2) (:member-email result)))
(t/is (= (:id profile2) (:member-id result))))
(let [result (:result out)]
(t/is (contains? result :invitation-token))
(t/is (contains? result :iss))
(t/is (contains? result :redirect-to))
(t/is (contains? result :state))
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows)))))
(t/is (= :pending (:state result)))
(t/is (= :auth-login (:redirect-to result))))
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 1 (count rows))))))
;; Clean members
(db/delete! pool :team-profile-rel
@@ -207,46 +212,37 @@
:profile-id (:id profile2)})
;; --- Verify token as logged-in user
(t/testing "Verify token as logged-in user"
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile2)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(t/is (= :created (:state result)))
(t/is (= (:email profile2) (:member-email result)))
(t/is (= (:id profile2) (:member-id result))))
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows))))))
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile2)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(t/is (= :created (:state result)))
(t/is (= (:email profile2) (:member-email result)))
(t/is (= (:id profile2) (:member-id result))))
(t/testing "Verify token as logged-in wrong user"
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile3)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows)))))
;; --- Verify token as logged-in wrong user
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile1)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (not (th/success? out)))
(let [edata (-> out :error ex-data)]
(t/is (= :validation (:type edata)))
(t/is (= :invalid-token (:code edata)))))
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile1)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (not (th/success? out)))
(let [edata (-> out :error ex-data)]
(t/is (= :validation (:type edata)))
(t/is (= :invalid-token (:code edata))))))
)))

View File

@@ -6,11 +6,12 @@
(ns backend-tests.rpc-webhooks-test
(:require
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.db :as db]
[app.http :as http]
[app.storage :as sto]
[app.rpc :as-alias rpc]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
[mockery.core :refer [with-mocks]]))
@@ -31,7 +32,7 @@
(let [params {::th/type :create-webhook
::rpc/profile-id (:id prof)
:team-id team-id
:uri "http://example.com"
:uri (u/uri "http://example.com")
:mtype "application/json"}
out (th/command! params)]

View File

@@ -203,19 +203,22 @@
([coll value]
(sequence (replace-by-id value) coll)))
(defn without-nils
"Given a map, return a map removing key-value
pairs when value is `nil`."
[data]
(into {} (remove (comp nil? second)) data))
(defn vec-without-nils
[coll]
(into [] (remove nil?) coll))
(defn without-nils
"Given a map, return a map removing key-value
pairs when value is `nil`."
([] (remove (comp nil? val)))
([data]
(into {} (without-nils) data)))
(defn without-qualified
[data]
(into {} (remove (comp qualified-keyword? first)) data))
([]
(remove (comp qualified-keyword? key)))
([data]
(into {} (without-qualified) data)))
(defn without-keys
"Return a map without the keys provided

View File

@@ -200,7 +200,7 @@
(assert (nil? (:current-component-id file)))
(let [page-id (or (:id data) (uuid/next))
page (-> (ctp/make-empty-page page-id "Page-1")
page (-> (ctp/make-empty-page page-id "Page 1")
(d/deep-merge data))]
(-> file
(commit-change

View File

@@ -43,13 +43,13 @@
(defn bounding-box
"Returns a rect that wraps the shape after all transformations applied."
[shape]
; TODO: perhaps we need to store this calculation in a shape attribute
;; TODO: perhaps we need to store this calculation in a shape attribute
(gpr/points->rect (:points shape)))
(defn left-bound
"Returns the lowest x coord of the shape BEFORE applying transformations."
; TODO: perhaps some day we want after transformations, but for the
; moment it's enough as is now.
;; TODO: perhaps some day we want after transformations, but for the
;; moment it's enough as is now.
[shape]
(or (:x shape) (:x (:selrect shape)))) ; Paths don't have :x attribute
@@ -106,8 +106,8 @@
([attr val1 val2 precision]
(let [close-val? (fn [num1 num2]
(when (and (number? num1) (number? num2))
(< (mth/abs (- num1 num2)) precision)))]
(when (and (number? num1) (number? num2))
(< (mth/abs (- num1 num2)) precision)))]
(cond
(and (number? val1) (number? val2))
(close-val? val1 val2)

View File

@@ -210,7 +210,7 @@
;; after-vec will contain the side length of the grown side
;; we scale the shape by the diference and translate it by the start
;; displacement (so its left+top position is constant)
scale (/ (gpt/length after-vec) (gpt/length before-vec))
scale (/ (gpt/length after-vec) (max 0.01 (gpt/length before-vec)))
resize-origin (gpo/origin child-points-after)
@@ -268,11 +268,11 @@
scale-x (if (= :scale constraints-h)
1
(/ (gpo/width-points child-bb-before) (gpo/width-points child-bb-after)))
(/ (gpo/width-points child-bb-before) (max 0.01 (gpo/width-points child-bb-after))))
scale-y (if (= :scale constraints-v)
1
(/ (gpo/height-points child-bb-before) (gpo/height-points child-bb-after)))
(/ (gpo/height-points child-bb-before) (max 0.01 (gpo/height-points child-bb-after))))
resize-vector (gpt/point scale-x scale-y)
resize-origin (gpo/origin transformed-child-bounds)

View File

@@ -64,21 +64,32 @@
min-width (max min-width 0.01)
min-height (max min-height 0.01)]
(cond-> [base-p]
(or col? h-start?)
(cond-> [base-p
(gpt/add base-p (hv 0.01))
(gpt/add base-p (vv 0.01))]
col?
(conj (gpt/add base-p (vv min-height)))
row?
(conj (gpt/add base-p (hv min-width)))
(and col? h-start?)
(conj (gpt/add base-p (hv min-width)))
(and col? h-center?)
(conj (gpt/add base-p (hv (/ min-width 2))))
(conj (gpt/add base-p (hv (/ min-width 2)))
(gpt/subtract base-p (hv (/ min-width 2))))
(and col? h-center?)
(and col? h-end?)
(conj (gpt/subtract base-p (hv min-width)))
(or row? v-start?)
(and row? v-start?)
(conj (gpt/add base-p (vv min-height)))
(and row? v-center?)
(conj (gpt/add base-p (vv (/ min-height 2))))
(conj (gpt/add base-p (vv (/ min-height 2)))
(gpt/subtract base-p (vv (/ min-height 2))))
(and row? v-end?)
(conj (gpt/subtract base-p (vv min-height))))))
@@ -117,16 +128,19 @@
row? (ctl/row? parent)
col? (ctl/col? parent)
space-around? (ctl/space-around? parent)
content-around? (ctl/content-around? parent)
space-evenly? (ctl/space-evenly? parent)
content-evenly? (ctl/content-evenly? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
row-pad (if (or (and col? space-around?)
(and row? content-around?))
row-pad (if (or (and col? space-evenly?)
(and col? space-around?)
(and row? content-evenly?))
layout-gap-row
0)
col-pad (if (or(and row? space-around?)
(and col? content-around?))
col-pad (if (or(and row? space-evenly?)
(and row? space-around?)
(and col? content-evenly?))
layout-gap-col
0)

View File

@@ -24,9 +24,10 @@
"Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation"
[shape children layout-bounds]
(let [col? (ctl/col? shape)
row? (ctl/row? shape)
(let [col? (ctl/col? shape)
row? (ctl/row? shape)
space-around? (ctl/space-around? shape)
space-evenly? (ctl/space-evenly? shape)
wrap? (and (ctl/wrap? shape)
(or col? (not (ctl/auto-width? shape)))
@@ -78,18 +79,28 @@
next-max-width (+ child-margin-width (if fill-width? child-max-width child-width))
next-max-height (+ child-margin-height (if fill-height? child-max-height child-height))
total-gap-col (if space-around?
total-gap-col (cond
space-evenly?
(* layout-gap-col (+ num-children 2))
space-around?
(* layout-gap-col (+ num-children 1))
:else
(* layout-gap-col num-children))
total-gap-row (if space-around?
total-gap-row (cond
space-evenly?
(* layout-gap-row (+ num-children 2))
space-around?
(* layout-gap-row (+ num-children 1))
:else
(* layout-gap-row num-children))
next-line-min-width (+ line-min-width next-min-width total-gap-col)
next-line-min-height (+ line-min-height next-min-height total-gap-row)
]
next-line-min-height (+ line-min-height next-min-height total-gap-row)]
(if (and (some? line-data)
(or (not wrap?)
@@ -150,10 +161,11 @@
(defn add-lines-positions
[parent layout-bounds layout-lines]
(let [row? (ctl/row? parent)
col? (ctl/col? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
(let [row? (ctl/row? parent)
col? (ctl/col? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
space-evenly? (ctl/space-evenly? parent)
space-around? (ctl/space-around? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
@@ -183,10 +195,26 @@
(->> layout-lines (reduce add-ranges [0 0 0 0]))
get-layout-width (fn [{:keys [num-children]}]
(let [num-gap (if space-around? (inc num-children) (dec num-children))]
(let [num-gap (cond
space-evenly?
(inc num-children)
space-around?
num-children
:else
(dec num-children))]
(- layout-width (* layout-gap-col num-gap))))
get-layout-height (fn [{:keys [num-children]}]
(let [num-gap (if space-around? (inc num-children) (dec num-children))]
(let [num-gap (cond
space-evenly?
(inc num-children)
space-around?
num-children
:else
(dec num-children))]
(- layout-height (* layout-gap-row num-gap))))
num-lines (count layout-lines)
@@ -238,6 +266,30 @@
(and col? (< total-min-width rest-layout-width total-max-width) (not (ctl/auto-width? parent)))
(distribute-space :line-width :line-min-width :line-max-width total-min-width rest-layout-width))
;; Add information to limit the growth of width: 100% shapes to the bounds of the layout
layout-lines
(cond
row?
(->> layout-lines
(reduce
(fn [[result rest-layout-height] {:keys [line-height] :as line}]
[(conj result (assoc line :to-bound-height rest-layout-height))
(- rest-layout-height line-height layout-gap-row)])
[[] layout-height])
(first))
col?
(->> layout-lines
(reduce
(fn [[result rest-layout-width] {:keys [line-width] :as line}]
[(conj result (assoc line :to-bound-width rest-layout-width))
(- rest-layout-width line-width layout-gap-col)])
[[] layout-width])
(first))
:else
layout-lines)
[total-width total-height] (->> layout-lines (reduce add-lines [0 0]))
base-p (flp/get-base-line parent layout-bounds total-width total-height num-lines)]
@@ -256,34 +308,47 @@
auto-height? (ctl/auto-height? shape)
auto-width? (ctl/auto-width? shape)
space-between? (ctl/space-between? shape)
space-evenly? (ctl/space-evenly? shape)
space-around? (ctl/space-around? shape)
[layout-gap-row layout-gap-col] (ctl/gaps shape)
margin-x
(cond (and row? space-around? (not auto-width?))
(cond (and row? space-evenly? (not auto-width?))
(max layout-gap-col (/ (- width line-width) (inc num-children)))
(and row? space-around? auto-width?)
(and row? space-around? (not auto-width?))
(/ (max layout-gap-col (/ (- width line-width) num-children)) 2)
(and row? (or space-evenly? space-around?) auto-width?)
layout-gap-col
:else
0)
margin-y
(cond (and col? space-around? (not auto-height?))
(cond (and col? space-evenly? (not auto-height?))
(max layout-gap-row (/ (- height line-height) (inc num-children)))
(and col? space-around? auto-height?)
(and col? space-around? (not auto-height?))
(/ (max layout-gap-row (/ (- height line-height) num-children)) 2)
(and col? (or space-evenly? space-around?) auto-height?)
layout-gap-row
:else
0)
layout-gap-col
(cond (and row? space-around?)
(cond (and row? space-evenly?)
0
(and row? space-around? auto-width?)
0
(and row? space-around?)
(/ (max layout-gap-col (/ (- width line-width) num-children)) 2)
(and row? space-between? (not auto-width?))
(max layout-gap-col (/ (- width line-width) (dec num-children)))
@@ -291,9 +356,15 @@
layout-gap-col)
layout-gap-row
(cond (and col? space-around?)
(cond (and col? space-evenly?)
0
(and col? space-evenly? auto-height?)
0
(and col? space-around?)
(/ (max layout-gap-row (/ (- height line-height) num-children)) 2)
(and col? space-between? (not auto-height?))
(max layout-gap-row (/ (- height line-height) (dec num-children)))

View File

@@ -20,7 +20,7 @@
transform-inverse
child
child-origin child-width
{:keys [children-data line-width] :as layout-data}]
{:keys [children-data line-width to-bound-width] :as layout-data}]
(cond
(ctl/row? parent)
@@ -30,8 +30,9 @@
:modifiers (ctm/resize-modifiers (gpt/point fill-scale 1) child-origin transform transform-inverse)})
(ctl/col? parent)
(let [target-width (max (- line-width (ctl/child-width-margin child)) 0.01)
max-width (ctl/child-max-width child)
(let [line-width (min line-width (or to-bound-width line-width))
target-width (max (- line-width (ctl/child-width-margin child)) 0.01)
max-width (max (ctl/child-max-width child) 0.01)
target-width (min max-width target-width)
fill-scale (/ target-width child-width)]
{:width target-width
@@ -43,7 +44,7 @@
transform transform-inverse
child
child-origin child-height
{:keys [children-data line-height] :as layout-data}]
{:keys [children-data line-height to-bound-height] :as layout-data}]
(cond
(ctl/col? parent)
@@ -53,8 +54,9 @@
:modifiers (ctm/resize-modifiers (gpt/point 1 fill-scale) child-origin transform transform-inverse)})
(ctl/row? parent)
(let [target-height (max (- line-height (ctl/child-height-margin child)) 0.01)
max-height (ctl/child-max-height child)
(let [line-height (min line-height (or to-bound-height line-height))
target-height (max (- line-height (ctl/child-height-margin child)) 0.01)
max-height (max (ctl/child-max-height child) 0.01)
target-height (min max-height target-height)
fill-scale (/ target-height child-height)]
{:height target-height
@@ -71,8 +73,13 @@
(when (or (ctl/fill-width? child) (ctl/fill-height? child))
(gtr/calculate-geometry @parent-bounds))
fill-width (when (ctl/fill-width? child) (calc-fill-width-data parent transform transform-inverse child child-origin child-width layout-line))
fill-height (when (ctl/fill-height? child) (calc-fill-height-data parent transform transform-inverse child child-origin child-height layout-line))
fill-width
(when (ctl/fill-width? child)
(calc-fill-width-data parent transform transform-inverse child child-origin child-width layout-line))
fill-height
(when (ctl/fill-height? child)
(calc-fill-height-data parent transform transform-inverse child child-origin child-height layout-line))
child-width (or (:width fill-width) child-width)
child-height (or (:height fill-height) child-height)

View File

@@ -20,9 +20,14 @@
hv (partial gpo/start-hv layout-bounds)
vv (partial gpo/start-vv layout-bounds)
end? (ctl/content-end? parent)
center? (ctl/content-center? parent)
around? (ctl/content-around? parent)
wrap? (ctl/wrap? parent)
end? (or (and wrap? (ctl/content-end? parent))
(and (not wrap?) (ctl/align-items-end? parent)))
center? (or (and wrap? (ctl/content-center? parent))
(and (not wrap?) (ctl/align-items-center? parent)))
around? (and wrap? (ctl/content-around? parent))
evenly? (and wrap? (ctl/content-evenly? parent))
;; Adjust the totals so it takes into account the gaps
[layout-gap-row layout-gap-col] (ctl/gaps parent)
@@ -43,6 +48,9 @@
(gpt/add (vv free-height-gap))
around?
(gpt/add (vv (max lines-gap-row (/ free-height num-lines 2))))
evenly?
(gpt/add (vv (max lines-gap-row (/ free-height (inc num-lines))))))
col?
@@ -53,6 +61,9 @@
(gpt/add (hv free-width-gap))
around?
(gpt/add (hv (max lines-gap-col (/ free-width num-lines) 2)))
evenly?
(gpt/add (hv (max lines-gap-col (/ free-width (inc num-lines)))))))))
(defn get-next-line
@@ -74,6 +85,7 @@
stretch? (ctl/content-stretch? parent)
between? (ctl/content-between? parent)
around? (ctl/content-around? parent)
evenly? (ctl/content-evenly? parent)
free-width (- layout-width total-width)
free-height (- layout-height total-height)
@@ -90,6 +102,9 @@
(/ free-width (dec num-lines))
around?
(/ free-width num-lines)
evenly?
(/ free-width (inc num-lines))
:else
@@ -107,6 +122,9 @@
(/ free-height (dec num-lines))
around?
(/ free-height num-lines)
evenly?
(/ free-height (inc num-lines))
:else
@@ -130,6 +148,7 @@
col? (ctl/col? parent)
space-between? (ctl/space-between? parent)
space-around? (ctl/space-around? parent)
space-evenly? (ctl/space-evenly? parent)
h-center? (ctl/h-center? parent)
h-end? (ctl/h-end? parent)
v-center? (ctl/v-center? parent)
@@ -155,20 +174,20 @@
start-p
(cond-> base-p
;; X AXIS
(and row? h-center? (not space-around?) (not space-between?))
(and row? h-center? (not space-around?) (not space-evenly?) (not space-between?))
(-> (gpt/add (hv (/ layout-width 2)))
(gpt/subtract (hv (/ (+ line-width children-gap-width) 2))))
(and row? h-end? (not space-around?) (not space-between?))
(and row? h-end? (not space-around?) (not space-evenly?) (not space-between?))
(-> (gpt/add (hv layout-width))
(gpt/subtract (hv (+ line-width children-gap-width))))
;; Y AXIS
(and col? v-center? (not space-around?) (not space-between?))
(and col? v-center? (not space-around?) (not space-evenly?) (not space-between?))
(-> (gpt/add (vv (/ layout-height 2)))
(gpt/subtract (vv (/ (+ line-height children-gap-height) 2))))
(and col? v-end? (not space-around?) (not space-between?))
(and col? v-end? (not space-around?) (not space-evenly?) (not space-between?))
(-> (gpt/add (vv layout-height))
(gpt/subtract (vv (+ line-height children-gap-height)))))]

View File

@@ -115,13 +115,15 @@
(if (empty? children)
modif-tree
(let [child-id (first children)
child (get objects child-id)
child-bounds @(get bounds child-id)
child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints child-bounds parent-bounds transformed-parent-bounds)]
(recur (cond-> modif-tree
(not (ctm/empty? child-modifiers))
(update-in [child-id :modifiers] ctm/add-modifiers child-modifiers))
(rest children)))))))))
child (get objects child-id)]
(if (some? child)
(let [child-bounds @(get bounds child-id)
child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints child-bounds parent-bounds transformed-parent-bounds)]
(recur (cond-> modif-tree
(not (ctm/empty? child-modifiers))
(update-in [child-id :modifiers] ctm/add-modifiers child-modifiers))
(rest children)))
(recur modif-tree (rest children))))))))))
(defn get-group-bounds
[objects bounds modif-tree shape]

View File

@@ -6,6 +6,7 @@
(ns app.common.geom.shapes.points
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.intersect :as gsi]
@@ -54,11 +55,11 @@
(defn width-points
[[p0 p1 _ _]]
(gpt/length (gpt/to-vec p0 p1)))
(max 0.01 (gpt/length (gpt/to-vec p0 p1))))
(defn height-points
[[p0 _ _ p3]]
(gpt/length (gpt/to-vec p0 p3)))
(max 0.01 (gpt/length (gpt/to-vec p0 p3))))
(defn pad-points
[[p0 p1 p2 p3 :as points] pad-top pad-right pad-bottom pad-left]
@@ -115,7 +116,9 @@
(max tv-max ctv)]))
[th-min th-max tv-min tv-max]
(->> child-bounds (reduce find-boundary-ts [##Inf ##-Inf ##Inf ##-Inf]))
(->> child-bounds
(filter #(and (d/num? (:x %)) (d/num? (:y %))))
(reduce find-boundary-ts [##Inf ##-Inf ##Inf ##-Inf]))
minv-start (pv tv-min)
minv-end (gpt/add minv-start hv)

View File

@@ -34,7 +34,7 @@
(defn abs
[v]
#?(:cljs (js/Math.abs v)
:clj (Math/abs v)))
:clj (Math/abs (double v))))
(defn sin
"Returns the sine of a number"

View File

@@ -19,6 +19,8 @@
(dm/export common/file-version)
(dm/export common/default-color)
(dm/export common/component-sync-attrs)
(dm/export common/retrieve-used-names)
(dm/export common/generate-unique-name)
;; Focus
(dm/export focus/focus-objects)

View File

@@ -7,7 +7,10 @@
(ns app.common.pages.common
(:require
[app.common.colors :as clr]
[app.common.uuid :as uuid]))
[app.common.data :as d]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[clojure.spec.alpha :as s]))
(def file-version 20)
(def default-color clr/gray-20)
@@ -580,3 +583,31 @@
:layout-item-min-w
:layout-item-align-self}})
(defn retrieve-used-names
"Return a set with the all unique names used in the
elements (any entity thas has a :name)"
[elements]
(into #{} (comp (map :name) (remove nil?)) (vals elements)))
(defn- extract-numeric-suffix
[basename]
(if-let [[_ p1 p2] (re-find #"(.*) ([0-9]+)$" basename)]
[p1 (+ 1 (d/parse-integer p2))]
[basename 1]))
(s/def ::set-of-strings
(s/every ::us/string :kind set?))
(defn generate-unique-name
"A unique name generator"
[used basename]
(us/assert! ::set-of-strings used)
(us/assert! ::us/string basename)
(if-not (contains? used basename)
basename
(let [[prefix initial] (extract-numeric-suffix basename)]
(loop [counter initial]
(let [candidate (str prefix " " counter)]
(if (contains? used candidate)
(recur (inc counter))
candidate))))))

View File

@@ -109,6 +109,20 @@
(recur (conj result parent-id) parent-id)
result))))
(defn get-parent-ids-with-index
"Returns a tuple with the list of parents and a map with the position within each parent"
[objects shape-id]
(loop [parent-list []
parent-indices {}
current shape-id]
(let [parent-id (dm/get-in objects [current :parent-id])
parent (get objects parent-id)]
(if (and (some? parent) (not= parent-id current))
(let [parent-list (conj parent-list parent-id)
parent-indices (assoc parent-indices parent-id (d/index-of (:shapes parent) current))]
(recur parent-list parent-indices parent-id))
[parent-list parent-indices]))))
(defn get-siblings-ids
[objects id]
(let [parent (get-parent objects id)]

View File

@@ -9,6 +9,7 @@
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.common :as common]
[app.common.spec :as us]
[app.common.types.shape-tree :as ctst]
[clojure.spec.alpha :as s]))
@@ -130,7 +131,7 @@
delta (gpt/subtract position orig-pos)
objects (:objects container)
unames (volatile! (ctst/retrieve-used-names objects))
unames (volatile! (common/retrieve-used-names objects))
frame-id (ctst/frame-id-by-position objects (gpt/add orig-pos delta))
frame-ids-map (volatile! {})

View File

@@ -73,7 +73,7 @@
([file-id page-id]
(let [page (when (some? page-id)
(ctp/make-empty-page page-id "Page-1"))]
(ctp/make-empty-page page-id "Page 1"))]
(cond-> (-> empty-file-data
(assoc :id file-id))

View File

@@ -15,8 +15,8 @@
;; :layout-gap-type ;; :simple, :multiple
;; :layout-gap ;; {:row-gap number , :column-gap number}
;; :layout-align-items ;; :start :end :center :stretch
;; :layout-justify-content ;; :start :center :end :space-between :space-around
;; :layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
;; :layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly
;; :layout-align-content ;; :start :center :end :space-between :space-around :space-evenly :stretch (by default)
;; :layout-wrap-type ;; :wrap, :nowrap
;; :layout-padding-type ;; :simple, :multiple
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
@@ -36,8 +36,8 @@
(s/def ::layout-gap-type #{:simple :multiple})
(s/def ::layout-gap ::us/safe-number)
(s/def ::layout-align-items #{:start :end :center :stretch})
(s/def ::layout-align-content #{:start :end :center :space-between :space-around :stretch})
(s/def ::layout-justify-content #{:start :center :end :space-between :space-around})
(s/def ::layout-align-content #{:start :end :center :space-between :space-around :space-evenly :stretch})
(s/def ::layout-justify-content #{:start :center :end :space-between :space-around :space-evenly})
(s/def ::layout-wrap-type #{:wrap :nowrap :no-wrap}) ;;TODO remove no-wrap after script
(s/def ::layout-padding-type #{:simple :multiple})
@@ -134,25 +134,41 @@
(defn wrap? [{:keys [layout-wrap-type]}]
(= layout-wrap-type :wrap))
(defn fill-width? [child]
(= :fill (:layout-item-h-sizing child)))
(defn fill-width?
([objects id]
(= :fill (dm/get-in objects [id :layout-item-h-sizing])))
([child]
(= :fill (:layout-item-h-sizing child))))
(defn fill-height? [child]
(= :fill (:layout-item-v-sizing child)))
(defn fill-height?
([objects id]
(= :fill (dm/get-in objects [id :layout-item-v-sizing])))
([child]
(= :fill (:layout-item-v-sizing child))))
(defn auto-width? [child]
(= :auto (:layout-item-h-sizing child)))
(defn auto-width?
([objects id]
(= :auto (dm/get-in objects [id :layout-item-h-sizing])))
([child]
(= :auto (:layout-item-h-sizing child))))
(defn auto-height? [child]
(= :auto (:layout-item-v-sizing child)))
(defn auto-height?
([objects id]
(= :auto (dm/get-in objects [id :layout-item-v-sizing])))
([child]
(= :auto (:layout-item-v-sizing child))))
(defn col?
[{:keys [layout-flex-dir]}]
(or (= :column layout-flex-dir) (= :column-reverse layout-flex-dir)))
([objects id]
(col? (get objects id)))
([{:keys [layout-flex-dir]}]
(or (= :column layout-flex-dir) (= :column-reverse layout-flex-dir))))
(defn row?
[{:keys [layout-flex-dir]}]
(or (= :row layout-flex-dir) (= :row-reverse layout-flex-dir)))
([objects id]
(row? (get objects id)))
([{:keys [layout-flex-dir]}]
(or (= :row layout-flex-dir) (= :row-reverse layout-flex-dir))))
(defn gaps
[{:keys [layout-gap]}]
@@ -270,11 +286,30 @@
[{:keys [layout-align-content]}]
(= :space-around layout-align-content))
(defn content-evenly?
[{:keys [layout-align-content]}]
(= :space-evenly layout-align-content))
(defn content-stretch?
[{:keys [layout-align-content]}]
(or (= :stretch layout-align-content)
(nil? layout-align-content)))
(defn align-items-center?
[{:keys [layout-align-items]}]
(= layout-align-items :center))
(defn align-items-start?
[{:keys [layout-align-items]}]
(= layout-align-items :start))
(defn align-items-end?
[{:keys [layout-align-items]}]
(= layout-align-items :end))
(defn align-items-stretch?
[{:keys [layout-align-items]}]
(= layout-align-items :stretch))
(defn reverse?
[{:keys [layout-flex-dir]}]
@@ -289,6 +324,10 @@
[{:keys [layout-justify-content]}]
(= layout-justify-content :space-around))
(defn space-evenly?
[{:keys [layout-justify-content]}]
(= layout-justify-content :space-evenly))
(defn align-self-start? [{:keys [layout-item-align-self]}]
(= :start layout-item-align-self))
@@ -300,3 +339,21 @@
(defn align-self-stretch? [{:keys [layout-item-align-self]}]
(= :stretch layout-item-align-self))
(defn change-h-sizing?
[frame-id objects children-ids]
(and (layout? objects frame-id)
(auto-width? objects frame-id)
(or (and (col? objects frame-id)
(every? (partial fill-width? objects) children-ids))
(and (row? objects frame-id)
(some (partial fill-width? objects) children-ids)))))
(defn change-v-sizing?
[frame-id objects children-ids]
(and (layout? objects frame-id)
(auto-height? objects frame-id)
(or (and (col? objects frame-id)
(some (partial fill-height? objects) children-ids))
(and (row? objects frame-id)
(every? (partial fill-height? objects) children-ids)))))

View File

@@ -132,43 +132,34 @@
(defn get-base
[objects id-a id-b]
(let [parents-a (reverse (cons id-a (cph/get-parent-ids objects id-a)))
parents-b (reverse (cons id-b (cph/get-parent-ids objects id-b)))
(let [[parents-a parents-a-index] (cph/get-parent-ids-with-index objects id-a)
[parents-b parents-b-index] (cph/get-parent-ids-with-index objects id-b)
[base base-child-a base-child-b]
(loop [parents-a (rest parents-a)
parents-b (rest parents-b)
base uuid/zero]
(cond
(not= (first parents-a) (first parents-b))
[base (first parents-a) (first parents-b)]
parents-a (cons id-a parents-a)
parents-b (into #{id-b} parents-b)
(or (empty? parents-a) (empty? parents-b))
[uuid/zero (first parents-a) (first parents-b)]
;; Search for the common frame in order
base (or (d/seek parents-b parents-a) uuid/zero)
:else
(recur (rest parents-a) (rest parents-b) (first parents-a))))
idx-a (get parents-a-index base)
idx-b (get parents-b-index base)]
index-base-a (when base-child-a (cph/get-position-on-parent objects base-child-a))
index-base-b (when base-child-b (cph/get-position-on-parent objects base-child-b))]
[base index-base-a index-base-b]))
[base idx-a idx-b]))
(defn is-shape-over-shape?
[objects base-shape-id over-shape-id]
(let [[base index-a index-b] (get-base objects base-shape-id over-shape-id)]
(cond
;; The base the base shape, so the other item is bellow
(= base base-shape-id)
(let [object (get objects base-shape-id)]
(or (cph/frame-shape? object)
(cph/root-frame? object)))
false
;; The base is the testing over, so it's over
(= base over-shape-id)
(let [object (get objects over-shape-id)]
(or (not (cph/frame-shape? object))
(not (cph/root-frame? object))))
true
;; Check which index is lower
:else
(< index-a index-b))))
@@ -214,9 +205,8 @@
(defn all-frames-by-position
[objects position]
(->> (get-frames-ids objects)
(sort-z-index objects)
(filterv #(and position (gsh/has-point? (get objects %) position)))))
(filter #(and position (gsh/has-point? (get objects %) position)))
(sort-z-index objects)))
(defn top-nested-frame
"Search for the top nested frame for positioning shapes when moving or creating.
@@ -284,35 +274,6 @@
[frame]
(not (mth/almost-zero? (:rotation frame 0))))
(defn retrieve-used-names
[objects]
(into #{} (comp (map :name) (remove nil?)) (vals objects)))
(defn- extract-numeric-suffix
[basename]
(if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
[p1 (+ 1 (d/parse-integer p2))]
[basename 1]))
(s/def ::set-of-strings
(s/every ::us/string :kind set?))
(defn generate-unique-name
"A unique name generator"
[used basename]
(us/assert! ::set-of-strings used)
(us/assert! ::us/string basename)
;; We have add a condition because UX doesn't want numbers on
;; layer names.
(if-not (contains? used basename)
basename
(let [[prefix initial] (extract-numeric-suffix basename)]
(loop [counter initial]
(let [candidate (str prefix "-" counter)]
(if (contains? used candidate)
(recur (inc counter))
candidate))))))
(defn clone-object
"Gets a copy of the object and all its children, with new ids
and with the parent-children links correctly set. Admits functions

View File

@@ -126,7 +126,7 @@
(fn [file-data]
(let [id (uuid/next)
props (merge {:id id
:name "Color-1"
:name "Color 1"
:color "#000000"
:opacity 1}
props)]
@@ -140,7 +140,7 @@
(fn [file-data]
(let [id (uuid/next)
props (merge {:id id
:name "Typography-1"
:name "Typography 1"
:font-id "sourcesanspro"
:font-family "sourcesanspro"
:font-size "14"

View File

@@ -101,7 +101,7 @@
;; false)
(t/is (= (count pages) 2))
(t/is (= (:name (first pages)) "Page-1"))
(t/is (= (:name (first pages)) "Page 1"))
(t/is (= (:name (second pages)) "Library backup"))
(t/is (= (count components) 1))

View File

@@ -11,6 +11,7 @@ volumes:
postgres_data_pg15:
user_data:
minio_data:
redis_data:
services:
main:
@@ -80,22 +81,6 @@ services:
- 9000:9000
- 9001:9001
# keycloak:
# image: "quay.io/keycloak/keycloak:15.0.2"
# environment:
# - DB_VENDOR=POSTGRES
# - DB_ADDR=postgres
# - DB_DATABASE=keycloak
# - DB_USER=keycloak
# - DB_SCHEMA=public
# - DB_PASSWORD=keycloak
# - KEYCLOAK_USER=admin
# - KEYCLOAK_PASSWORD=admin
# expose:
# - '8080'
# ports:
# - "8080:8080"
postgres:
image: postgres:15
command: postgres -c config_file=/etc/postgresql.conf
@@ -116,6 +101,8 @@ services:
hostname: "penpot-devenv-redis"
container_name: "penpot-devenv-redis"
restart: always
volumes:
- "redis_data:/data"
mailer:
image: sj26/mailcatcher:latest

View File

@@ -1,15 +1,38 @@
FROM ubuntu:22.04 as jre-build
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive \
LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
ENV LANG='en_US.UTF-8' \
LC_ALL='en_US.UTF-8' \
JAVA_HOME="/opt/jdk" \
PATH=/opt/jdk/bin:$PATH \
DEBIAN_FRONTEND=noninteractive \
TZ=Etc/UTC
RUN set -eux; \
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
apt-get -qq update; \
apt-get -qq upgrade; \
apt-get -qqy --no-install-recommends install \
curl \
ca-certificates \
binutils \
nano \
curl \
tzdata \
locales \
ca-certificates \
imagemagick \
webp \
rlwrap \
fontconfig \
woff-tools \
woff2 \
python3 \
fontforge \
; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen; \
mkdir -p /opt/data/assets; \
mkdir -p /opt/penpot; \
chown -R penpot:penpot /opt/penpot; \
chown -R penpot:penpot /opt/data; \
rm -rf /var/lib/apt/lists/*;
RUN set -eux; \
@@ -39,49 +62,6 @@ RUN set -eux; \
tar -xf /tmp/openjdk.tar.gz --strip-components=1; \
rm -rf /tmp/openjdk.tar.gz;
RUN /opt/jdk/bin/jlink \
--verbose \
--module-path /opt/jdk/jmods \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress 0 \
--add-modules java.base,java.naming,java.xml,java.logging,java.net.http,java.sql,java.management,java.desktop,jdk.jfr,jdk.unsupported,jdk.management.jfr \
--output /opt/jre
FROM ubuntu:22.04
LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
ENV LANG='en_US.UTF-8' \
LC_ALL='en_US.UTF-8' \
JAVA_HOME="/opt/jre" \
PATH=/opt/jre/bin:$PATH \
TZ=Etc/UTC
COPY --from=jre-build /opt/jre /opt/jre
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
apt-get -qq update; \
apt-get -qqy --no-install-recommends install \
curl \
tzdata \
locales \
ca-certificates \
imagemagick \
webp \
fontconfig \
woff-tools \
woff2 \
python3 \
fontforge \
; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen; \
mkdir -p /opt/penpot/assets; \
chown -R penpot:penpot /opt/penpot; \
rm -rf /var/lib/apt/lists/*;
COPY --chown=penpot:penpot ./bundle-backend/ /opt/penpot/backend/

View File

@@ -1,6 +1,11 @@
FROM nginx:1.23
LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
mkdir -p /opt/data/assets; \
chown -R penpot:penpot /opt/data;
ADD ./bundle-frontend/ /var/www/app/
ADD ./files/config.js /var/www/app/js/config.js
ADD ./files/nginx.conf /etc/nginx/nginx.conf

View File

@@ -4,10 +4,13 @@ set -x
DOCKER_CLI_EXPERIMENTAL=enabled
ORG=${PENPOT_DOCKER_NAMESPACE:-penpotapp};
PLATFORM=${PENPOT_BUILD_PLATFORM:-linux/amd64};
IMAGE=${1:-backend};
IMAGE=${PENPOT_BUILD_IMAGE:-backend}
PLATFORM=${PENPOT_BUILD_PLATFORM:-linux/amd64};
VERSION=${PENPOT_BUILD_VERSION:-latest}
DOCKER_IMAGE="$ORG/$IMAGE";
OPTIONS="-t $DOCKER_IMAGE:$PENPOT_BUILD_VERSION";
OPTIONS="-t $DOCKER_IMAGE:$VERSION";
IFS=", "
read -a TAGS <<< $PENPOT_BUILD_TAGS;
@@ -16,10 +19,6 @@ for element in "${TAGS[@]}"; do
OPTIONS="$OPTIONS -t $DOCKER_IMAGE:$element";
done
if [ "$PENPOT_BUILD_PUSH" = "true" ]; then
OPTIONS="--push $OPTIONS"
fi
docker buildx inspect penpot > /dev/null 2>&1;
docker run --privileged --rm tonistiigi/binfmt --install all
@@ -32,4 +31,5 @@ else
fi
unset IFS;
docker buildx build --platform ${PLATFORM// /,} $OPTIONS -f Dockerfile.$IMAGE "$@" .;

View File

@@ -36,11 +36,12 @@ services:
penpot-frontend:
image: "penpotapp/frontend:latest"
restart: always
ports:
- 9001:80
volumes:
- penpot_assets:/opt/penpot/assets
- penpot_assets:/opt/data/assets
depends_on:
- penpot-backend
@@ -96,8 +97,10 @@ services:
penpot-backend:
image: "penpotapp/backend:latest"
restart: always
volumes:
- penpot_assets:/opt/penpot/assets
- penpot_assets:/opt/data/assets
depends_on:
- penpot-postgres
@@ -134,7 +137,7 @@ services:
## environment variables for the backend here:
## https://help.penpot.app/technical-guide/configuration/#advanced-configuration
- PENPOT_FLAGS=enable-registration enable-login disable-email-verification enable-smtp enable-prepl-server
- PENPOT_FLAGS=enable-registration enable-login-with-password disable-email-verification enable-smtp enable-prepl-server
## Penpot SECRET KEY. It serves as a master key from which other keys for subsystems
## (eg http sessions) are derived.
@@ -180,7 +183,7 @@ services:
## stored in a docker volume.
- PENPOT_ASSETS_STORAGE_BACKEND=assets-fs
- PENPOT_STORAGE_ASSETS_FS_DIRECTORY=/opt/penpot/assets
- PENPOT_STORAGE_ASSETS_FS_DIRECTORY=/opt/data/assets
## Also can be configured to to use a S3 compatible storage
## service like MiniIO. Look below for minio service setup.
@@ -214,6 +217,7 @@ services:
penpot-exporter:
image: "penpotapp/exporter:latest"
restart: always
networks:
- penpot
@@ -259,40 +263,8 @@ services:
- '1025'
ports:
- "1080:1080"
## An optional admin application for pentpot. It allows manage users, teams and inspect
## some parts of the database. You can read more about it on:
## https://github.com/penpot/penpot-admin
##
## If you are going to use admin, ensure to have `enable-prepl-server` in backend flags
## and uncomment the `PENPOT_PREPL_HOST` environment variable.
##
## Status: EXPERIMENTAL
# penpot-admin:
# image: "penpotapp/admin:latest"
# networks:
# - penpot
#
# depends_on:
# - penpot-postgres
# - penpot-backend
#
# environment:
# ## Adjust to the same value as on backend
# - PENPOT_PUBLIC_URI=http://localhost:9001
#
# ## Do not touch it, this is an internal routes
# - PENPOT_API_URI=http://penpot-frontend/
# - PENPOT_PREPL_URI=tcp://penpot-backend:6063/
# - PENPOT_DEBUG="false"
#
# ## Adjust to the same values as on backend
# - PENPOT_DATABASE_HOST=penpot-postgres
# - PENPOT_DATABASE_NAME=penpot
# - PENPOT_DATABASE_USERNAME=penpot
# - PENPOT_DATABASE_PASSWORD=penpot
# - PENPOT_REDIS_URI=redis://penpot-redis/0
networks:
- penpot
## Example configuration of MiniIO (S3 compatible object storage service); If you don't
## have preference, then just use filesystem, this is here just for the completeness.
@@ -300,6 +272,7 @@ services:
# minio:
# image: "minio/minio:latest"
# command: minio server /mnt/data --console-address ":9001"
# restart: always
#
# volumes:
# - "penpot_minio:/mnt/data"

View File

@@ -90,7 +90,7 @@ http {
location /internal/assets {
internal;
alias /opt/penpot/assets;
alias /opt/data/assets;
add_header x-internal-redirect "$upstream_http_x_accel_redirect";
}
@@ -102,9 +102,9 @@ http {
proxy_pass http://penpot-backend:6060/api;
}
location /admin {
proxy_pass http://penpot-admin:6065/admin;
}
# location /admin {
# proxy_pass http://penpot-admin:6065/admin;
# }
location /ws/notifications {
proxy_set_header Upgrade $http_upgrade;

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M0 0v132.292h11.207V0Zm121.085 0v132.292h11.207V0ZM36.023 28.259c-6.487 0-11.745 5.258-11.745 11.744 0 6.487 5.258 11.745 11.745 11.745 6.486 0 11.744-5.26 11.744-11.745 0-6.486-5.258-11.744-11.744-11.744zm30.04 0c-6.486 0-11.744 5.258-11.744 11.744 0 6.487 5.258 11.745 11.744 11.745 6.487 0 11.745-5.26 11.745-11.745 0-6.486-5.259-11.744-11.745-11.744zm30.496 0c-6.487 0-11.745 5.258-11.745 11.744 0 6.487 5.258 11.745 11.745 11.745 6.486 0 11.744-5.26 11.744-11.745 0-6.486-5.258-11.744-11.744-11.744zM36.023 80.545c-6.487 0-11.745 5.26-11.745 11.745 0 6.486 5.258 11.745 11.745 11.745 6.486 0 11.744-5.259 11.744-11.745 0-6.486-5.258-11.744-11.744-11.744zm30.04 0c-6.486 0-11.744 5.26-11.744 11.745 0 6.486 5.258 11.745 11.744 11.745 6.487 0 11.745-5.259 11.745-11.745 0-6.486-5.259-11.744-11.745-11.744z"/>
</svg>

After

Width:  |  Height:  |  Size: 926 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M0 0v11.207h132.292V0H0zm36.567 29.029c-5.96.092-12.09 4.407-12.289 10.974 0 6.487 5.258 11.745 11.745 11.745 6.486 0 11.744-5.26 11.744-11.745-.81-7.848-5.94-11.055-11.2-10.974zm60.536 0c-5.96.092-12.09 4.407-12.289 10.974 0 6.487 5.258 11.745 11.745 11.745 6.486 0 11.744-5.26 11.744-11.745-.81-7.848-5.94-11.055-11.2-10.974zm-30.495 0c-5.96.092-12.09 4.407-12.29 10.974 0 6.487 5.259 11.745 11.745 11.745 6.487 0 11.745-5.26 11.745-11.745-.811-7.848-5.94-11.055-11.2-10.974zM36.023 80.545c-6.487 0-11.745 5.26-11.745 11.745 0 6.486 5.258 11.745 11.745 11.745 6.486 0 11.744-5.259 11.744-11.745 0-6.486-5.258-11.744-11.744-11.744zm30.04 0c-6.486 0-11.744 5.26-11.744 11.745 0 6.486 5.258 11.745 11.744 11.745 6.487 0 11.745-5.259 11.745-11.745 0-6.486-5.26-11.744-11.745-11.744zM0 121.085v11.207h132.292v-11.207H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 934 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M0 0v11.207h132.292V0Zm18.947 28.264V57.99h94.4V28.264zm.098 47.096v29.726h94.302V75.36ZM0 121.086v11.206h132.292v-11.207z"/>
</svg>

After

Width:  |  Height:  |  Size: 240 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M0 132.292h11.207V0H0Zm28.264-18.947H57.99v-94.4H28.264zm47.096-.098h29.726V18.945H75.36Zm45.726 19.045h11.206V0h-11.207z"/>
</svg>

After

Width:  |  Height:  |  Size: 239 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -29,7 +29,35 @@
.custom-input {
width: 100%;
height: 115px;
min-height: 116px;
max-height: 176px;
overflow-y: hidden;
input {
&.no-padding {
padding-top: 12px;
}
min-height: 40px;
}
.selected-items {
gap: 8px;
padding: 8px;
max-height: 132px;
overflow-y: scroll;
.selected-item {
.around {
height: 24px;
display: flex;
align-items: center;
justify-content: flex-start;
width: fit-content;
.icon {
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
.custom-select {

View File

@@ -50,7 +50,7 @@
}
img {
width: 274px;
margin-bottom: -41px;
margin-bottom: -19px;
@media (max-width: 1200px) {
display: none;
width: 0;

View File

@@ -184,6 +184,7 @@
.modal-footer .action-buttons {
justify-content: space-around;
gap: 15px;
}
.fields-container {
@@ -1402,6 +1403,7 @@
align-items: center;
justify-content: center;
background: #31efb8;
min-width: 28px;
width: 28px;
height: 28px;
border-radius: 50%;
@@ -1493,10 +1495,6 @@
display: grid;
grid-template-columns: 1fr auto;
gap: 8px;
.btn-large {
overflow: hidden;
text-overflow: ellipsis;
}
.btn-primary {
max-width: 250px;
}
@@ -1541,8 +1539,64 @@
.onboarding-team-members {
.team-left {
padding: 42px 64px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: auto;
form {
margin-top: 32px;
margin-top: 5px;
.invite-row {
.custom-input {
width: 100%;
min-height: 80px;
height: fit-content;
max-height: 176px;
overflow-y: hidden;
input {
&.no-padding {
padding-top: 12px;
}
min-height: 40px;
}
.selected-items {
gap: 7px;
padding: 7px;
max-height: 132px;
overflow-y: scroll;
.selected-item {
.around {
height: 24px;
display: flex;
align-items: center;
justify-content: flex-start;
width: fit-content;
.icon {
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
}
.buttons {
margin: 12px 0;
button {
height: auto;
}
input {
white-space: break-spaces;
word-break: break-word;
height: fit-content;
margin: 0;
padding: 5px 10px;
min-height: 40px;
max-height: 90px;
}
}
}
}
}

View File

@@ -1644,6 +1644,7 @@
font-family: "worksans", sans-serif;
&.justify-content,
&.align-content,
&.sizing {
align-items: start;
margin-top: 4px;
@@ -1658,7 +1659,8 @@
gap: 5px;
}
&.justify-content {
&.justify-content,
&.align-content {
display: flex;
flex-direction: column;
gap: 5px;

View File

@@ -17,7 +17,7 @@ $height-palette-max: 80px;
#workspace {
width: 100vw;
height: 100vh;
height: 100%;
user-select: none;
background-color: $color-canvas;
display: grid;
@@ -37,6 +37,8 @@ $height-palette-max: 80px;
.left-toolbar {
grid-area: toolbar;
width: $width-left-toolbar;
overflow-y: auto;
overflow-x: hidden;
}
.settings-bar.settings-bar-left {

View File

@@ -7,6 +7,7 @@
(ns app.main.data.dashboard
(:require
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -426,8 +427,8 @@
(rx/ignore)))))
(defn update-team-photo
[{:keys [file] :as params}]
(us/assert! ::di/file file)
[file]
(us/assert! ::di/blob file)
(ptk/reify ::update-team-photo
ptk/WatchEvent
(watch [_ state _]
@@ -664,10 +665,12 @@
(ptk/reify ::create-project
ptk/WatchEvent
(watch [_ state _]
(let [name (name (gensym (str (tr "dashboard.new-project-prefix") " ")))
team-id (:current-team-id state)
params {:name name
:team-id team-id}
(let [projects (get state :dashboard-projects)
unames (cp/retrieve-used-names projects)
name (cp/generate-unique-name unames (str (tr "dashboard.new-project-prefix") " 1"))
team-id (:current-team-id state)
params {:name name
:team-id team-id}
{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)]
@@ -875,7 +878,9 @@
:or {on-success identity
on-error rx/throw}} (meta params)
name (name (gensym (str (tr "dashboard.new-file-prefix") " ")))
files (get state :dashboard-files)
unames (cp/retrieve-used-names files)
name (cp/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1"))
features (cond-> #{}
(features/active-feature? state :components-v2)
(conj "components/v2"))
@@ -1067,8 +1072,12 @@
pparams (:path-params route)
in-project? (contains? pparams :project-id)
name (if in-project?
(name (gensym (str (tr "dashboard.new-file-prefix") " ")))
(name (gensym (str (tr "dashboard.new-project-prefix") " "))))
(let [files (get state :dashboard-files)
unames (cp/retrieve-used-names files)]
(cp/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1")))
(let [projects (get state :dashboard-projects)
unames (cp/retrieve-used-names projects)]
(cp/generate-unique-name unames (str (tr "dashboard.new-project-prefix") " 1"))))
params (if in-project?
{:project-id (:project-id pparams)
:name name}

View File

@@ -15,6 +15,7 @@
[app.common.geom.proportions :as gpp]
[app.common.geom.shapes :as gsh]
[app.common.logging :as log]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
@@ -408,8 +409,8 @@
ptk/WatchEvent
(watch [it state _]
(let [pages (get-in state [:workspace-data :pages-index])
unames (ctst/retrieve-used-names pages)
name (ctst/generate-unique-name unames "Page-1")
unames (cp/retrieve-used-names pages)
name (cp/generate-unique-name unames "Page 1")
changes (-> (pcb/empty-changes it)
(pcb/add-empty-page id name))]
@@ -423,9 +424,9 @@
(watch [it state _]
(let [id (uuid/next)
pages (get-in state [:workspace-data :pages-index])
unames (ctst/retrieve-used-names pages)
unames (cp/retrieve-used-names pages)
page (get-in state [:workspace-data :pages-index page-id])
name (ctst/generate-unique-name unames (:name page))
name (cp/generate-unique-name unames (:name page))
no_thumbnails_objects (->> (:objects page)
(d/mapm (fn [_ val] (dissoc val :use-for-thumbnail?))))
@@ -650,20 +651,20 @@
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
; Move the shapes
;; Move the shapes
(pcb/change-parent parent-id
shapes
to-index)
; Remove empty groups
;; Remove empty groups
(pcb/remove-objects groups-to-delete)
; Unmask groups whose mask have moved outside
;; Unmask groups whose mask have moved outside
(pcb/update-shapes groups-to-unmask
(fn [shape]
(assoc shape :masked-group? false)))
; Detach shapes moved out of their component
;; Detach shapes moved out of their component
(pcb/update-shapes shapes-to-detach
(fn [shape]
(assoc shape :component-id nil
@@ -673,17 +674,17 @@
:shape-ref nil
:touched nil)))
; Make non root a component moved inside another one
;; Make non root a component moved inside another one
(pcb/update-shapes shapes-to-deroot
(fn [shape]
(assoc shape :component-root? nil)))
; Make root a subcomponent moved outside its parent component
;; Make root a subcomponent moved outside its parent component
(pcb/update-shapes shapes-to-reroot
(fn [shape]
(assoc shape :component-root? true)))
; Reset constraints depending on the new parent
;; Reset constraints depending on the new parent
(pcb/update-shapes shapes-to-unconstraint
(fn [shape]
(let [parent (get objects parent-id)
@@ -698,7 +699,19 @@
:constraints-v (gsh/default-constraints-v moved-shape))))
{:ignore-touched true})
; Resize parent containers that need to
;; Fix the sizing when moving a shape
(pcb/update-shapes parents
(fn [parent]
(if (ctl/layout? parent)
(cond-> parent
(ctl/change-h-sizing? (:id parent) objects (:shapes parent))
(assoc :layout-item-h-sizing :fix)
(ctl/change-v-sizing? (:id parent) objects (:shapes parent))
(assoc :layout-item-v-sizing :fix))
parent)))
;; Resize parent containers that need to
(pcb/resize-parents parents))))
(defn relocate-shapes
@@ -718,9 +731,9 @@
;; If we try to move a parent into a child we remove it
ids (filter #(not (cph/is-parent? objects parent-id %)) ids)
parents (if ignore-parents?
#{parent-id}
(into #{parent-id} (map #(cph/get-parent-id objects %)) ids))
all-parents (into #{parent-id} (map #(cph/get-parent-id objects %)) ids)
parents (if ignore-parents? #{parent-id} all-parents)
groups-to-delete
(loop [current-id (first parents)
@@ -813,17 +826,12 @@
shapes-to-reroot
shapes-to-deroot
ids)
layouts-to-update
(into #{}
(filter (partial ctl/layout? objects))
(concat [parent-id] (cph/get-parent-ids objects parent-id)))
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(dwco/expand-collapse parent-id)
(ptk/data-event :layout/update layouts-to-update)
(ptk/data-event :layout/update (concat all-parents ids))
(dwu/commit-undo-transaction undo-id))))))
(defn relocate-selected-shapes
@@ -1432,6 +1440,19 @@
(and (= 1 (count selected))
(= :frame (get-in objects [(first selected) :type])))))
(defn same-frame-from-selected? [state frame-id]
(let [selected (wsh/lookup-selected state)]
(contains? frame-id (first selected))))
(defn frame-same-size?
[paste-obj frame-obj]
(and
(= (:heigth (:selrect (first (vals paste-obj))))
(:heigth (:selrect frame-obj)))
(= (:width (:selrect (first (vals paste-obj))))
(:width (:selrect frame-obj)))))
(defn- paste-shape
[{selected :selected
paste-objects :objects ;; rename this because here comes only the clipboard shapes,
@@ -1470,55 +1491,67 @@
item))
(calculate-paste-position [state mouse-pos in-viewport?]
(let [page-objects (wsh/lookup-page-objects state)
selected-objs (map #(get paste-objects %) selected)
page-selected (wsh/lookup-selected state)
wrapper (gsh/selection-rect selected-objs)
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))]
(let [page-objects (wsh/lookup-page-objects state)
selected-objs (map #(get paste-objects %) selected)
first-selected-obj (first selected-objs)
page-selected (wsh/lookup-selected state)
wrapper (gsh/selection-rect selected-objs)
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))
frame-id (first page-selected)
frame-object (get page-objects frame-id)
base (cph/get-base-shape page-objects page-selected)
index (cph/get-position-on-parent page-objects (:id base))]
(cond
;; Pasting inside a frame
(selected-frame? state)
(let [frame-id (first page-selected)
frame-object (get page-objects frame-id)
origin-frame-id (:frame-id (first selected-objs))
origin-frame-object (get page-objects origin-frame-id)
(if (or (same-frame-from-selected? state (first (vals paste-objects)))
(frame-same-size? paste-objects frame-object))
;; Paste next to selected frame, if selected is itself or of the same size as the copied
(let [selected-frame-obj (get page-objects (first page-selected))
parent-id (:parent-id base)
paste-x (+ (:width selected-frame-obj) (:x selected-frame-obj) 50)
paste-y (:y selected-frame-obj)
delta (gpt/subtract (gpt/point paste-x paste-y) orig-pos)]
margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
(min (- (:width frame-object) (:width wrapper))))
[(:frame-id base) parent-id delta index])
margin-y (-> (- (:height origin-frame-object) (+ (:y wrapper) (:height wrapper)))
(min (- (:height frame-object) (:height wrapper))))
;; Paste inside selected frame otherwise
(let [origin-frame-id (:frame-id first-selected-obj)
origin-frame-object (get page-objects origin-frame-id)
margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
(min (- (:width frame-object) (:width wrapper))))
margin-y (-> (- (:height origin-frame-object) (+ (:y wrapper) (:height wrapper)))
(min (- (:height frame-object) (:height wrapper))))
;; Pasted objects mustn't exceed the selected frame x limit
paste-x (if (> (+ (:width wrapper) (:x1 wrapper)) (:width frame-object))
(+ (- (:x frame-object) (:x orig-pos)) (- (:width frame-object) (:width wrapper) margin-x))
(:x frame-object))
paste-x (if (> (+ (:width wrapper) (:x1 wrapper)) (:width frame-object))
(+ (- (:x frame-object) (:x orig-pos)) (- (:width frame-object) (:width wrapper) margin-x))
(:x frame-object))
;; Pasted objects mustn't exceed the selected frame y limit
paste-y (if (> (+ (:height wrapper) (:y1 wrapper)) (:height frame-object))
(+ (- (:y frame-object) (:y orig-pos)) (- (:height frame-object) (:height wrapper) margin-y))
(:y frame-object))
paste-y (if (> (+ (:height wrapper) (:y1 wrapper)) (:height frame-object))
(+ (- (:y frame-object) (:y orig-pos)) (- (:height frame-object) (:height wrapper) margin-y))
(:y frame-object))
delta (if (= origin-frame-id uuid/zero)
delta (if (= origin-frame-id uuid/zero)
;; When the origin isn't in a frame the result is pasted in the center.
(gpt/subtract (gsh/center-shape frame-object) (gsh/center-selrect wrapper))
(gpt/subtract (gsh/center-shape frame-object) (gsh/center-selrect wrapper))
;; When pasting from one frame to another frame the object position must be limited to container boundaries. If the pasted object doesn't fit we try to:
;; - Align it to the limits on the x and y axis
;; - Respect the distance of the object to the right and bottom in the original frame
(gpt/point paste-x paste-y))]
[frame-id frame-id delta])
(gpt/point paste-x paste-y))]
[frame-id frame-id delta]))
(empty? page-selected)
(let [frame-id (ctst/top-nested-frame page-objects mouse-pos)
delta (gpt/subtract mouse-pos orig-pos)]
[frame-id frame-id delta])
:else
(let [base (cph/get-base-shape page-objects page-selected)
index (cph/get-position-on-parent page-objects (:id base))
frame-id (:frame-id base)
(let [frame-id (:frame-id base)
parent-id (:parent-id base)
delta (if in-viewport?
(gpt/subtract mouse-pos orig-pos)

View File

@@ -34,6 +34,23 @@
(declare commit-changes)
(defn- add-group-id
[changes state]
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))
prev-item (when-not (or (empty? items) (= index -1))
(get items index))
group-id (:group-id prev-item)
add-group-id? (and
(not (nil? group-id))
(= (get-in changes [:redo-changes 0 :type]) :mod-obj)
(= (get-in prev-item [:redo-changes 0 :type]) :add-obj)) ;; This is a copy-and-move with mouse+alt
]
(cond-> changes add-group-id? (assoc :group-id group-id))))
(def commit-changes? (ptk/type? ::commit-changes))
(defn update-shapes
@@ -64,7 +81,8 @@
(-> (pcb/empty-changes it page-id)
(pcb/set-save-undo? save-undo?)
(pcb/with-objects objects))
ids)]
ids)
changes (add-group-id changes state)]
(rx/concat
(if (seq (:redo-changes changes))
(let [changes (cond-> changes reg-objects? (pcb/resize-parents ids))]
@@ -147,7 +165,7 @@
(defn commit-changes
[{:keys [redo-changes undo-changes
origin save-undo? file-id]
origin save-undo? file-id group-id]
:or {save-undo? true}}]
(log/debug :msg "commit-changes"
:js/redo-changes redo-changes
@@ -164,7 +182,8 @@
:changes redo-changes
:page-id page-id
:frames frames
:save-undo? save-undo?})
:save-undo? save-undo?
:group-id group-id})
ptk/UpdateEvent
(update [_ state]
@@ -212,5 +231,6 @@
(when (and save-undo? (seq undo-changes))
(let [entry {:undo-changes undo-changes
:redo-changes redo-changes}]
:redo-changes redo-changes
:group-id group-id}]
(rx/of (dwu/append-undo entry)))))))))))

View File

@@ -27,6 +27,7 @@
(defn interrupt? [e] (= e :interrupt))
(declare undo-to-index)
(defn- assure-valid-current-page
[]
@@ -60,13 +61,25 @@
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index -1))
(let [changes (get-in items [index :undo-changes])]
(rx/of (dwu/materialize-undo changes (dec index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:save-undo? false
:origin it})
(assure-valid-current-page))))))))))
(let [item (get items index)
changes (:undo-changes item)
group-id (:group-id item)
find-first-group-idx (fn ffgidx[index]
(let [item (get items index)]
(if (= (:group-id item) group-id)
(ffgidx (dec index))
(inc index))))
undo-group-index (when group-id
(find-first-group-idx index))]
(if group-id
(rx/of (undo-to-index (dec undo-group-index)))
(rx/of (dwu/materialize-undo changes (dec index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:save-undo? false
:origin it})
(assure-valid-current-page)))))))))))
(def redo
(ptk/reify ::redo
@@ -74,17 +87,29 @@
(watch [it state _]
(let [edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
(when-not (or (some? edition) (not-empty drawing))
(when (and (nil? edition) (or (empty drawing) (= :curve (:tool drawing))))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index (dec (count items))))
(let [changes (get-in items [(inc index) :redo-changes])]
(rx/of (dwu/materialize-undo changes (inc index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:origin it
:save-undo? false}))))))))))
(let [item (get items (inc index))
changes (:redo-changes item)
group-id (:group-id item)
find-last-group-idx (fn flgidx [index]
(let [item (get items index)]
(if (= (:group-id item) group-id)
(flgidx (inc index))
(dec index))))
redo-group-index (when group-id
(find-last-group-idx (inc index)))]
(if group-id
(rx/of (undo-to-index redo-group-index))
(rx/of (dwu/materialize-undo changes (inc index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:origin it
:save-undo? false})))))))))))
(defn undo-to-index
"Repeat undoing or redoing until dest-index is reached."
@@ -99,7 +124,7 @@
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when (and (some? items)
(<= 0 dest-index (dec (count items))))
(<= -1 dest-index (dec (count items))))
(let [changes (vec (apply concat
(cond
(< dest-index index)

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
@@ -33,7 +34,7 @@
flows (get-in page [:options :flows] [])
unames (into #{} (map :name flows))
name (ctst/generate-unique-name unames "Flow-1")
name (cp/generate-unique-name unames "Flow 1")
new-flow {:id (uuid/next)
:name name

View File

@@ -73,7 +73,7 @@
(pcb/with-objects objects))]
(let [group-name (if (= 1 (count shapes))
(:name (first shapes))
"Component-1")]
"Component 1")]
(dwg/prepare-create-group it
objects
page-id

View File

@@ -174,42 +174,62 @@
modif-tree)))
(defn build-change-frame-modifiers
[modif-tree objects selected target-frame drop-index]
[modif-tree objects selected target-frame-id drop-index]
(let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id])))
child-set (set (get-in objects [target-frame :shapes]))
layout? (ctl/layout? objects target-frame)
child-set (set (get-in objects [target-frame-id :shapes]))
target-frame (get objects target-frame-id)
target-layout? (ctl/layout? target-frame)
children-ids (concat (:shapes target-frame) selected)
set-parent-ids
(fn [modif-tree shapes target-frame]
(fn [modif-tree shapes target-frame-id]
(reduce
(fn [modif-tree id]
(update-in
modif-tree
[id :modifiers]
#(-> %
(ctm/change-property :frame-id target-frame)
(ctm/change-property :parent-id target-frame))))
(ctm/change-property :frame-id target-frame-id)
(ctm/change-property :parent-id target-frame-id))))
modif-tree
shapes))
update-frame-modifiers
(fn [modif-tree [original-frame shapes]]
(let [shapes (->> shapes (d/removev #(= target-frame %)))
(let [shapes (->> shapes (d/removev #(= target-frame-id %)))
shapes (cond->> shapes
(and layout? (= original-frame target-frame))
(and target-layout? (= original-frame target-frame-id))
;; When movining inside a layout frame remove the shapes that are not immediate children
(filterv #(contains? child-set %)))]
(filterv #(contains? child-set %)))
children-ids (->> (dm/get-in objects [original-frame :shapes])
(remove (set selected)))
h-sizing? (ctl/change-h-sizing? original-frame objects children-ids)
v-sizing? (ctl/change-v-sizing? original-frame objects children-ids)]
(cond-> modif-tree
(not= original-frame target-frame)
(not= original-frame target-frame-id)
(-> (modifier-remove-from-parent objects shapes)
(update-in [target-frame :modifiers] ctm/add-children shapes drop-index)
(set-parent-ids shapes target-frame))
(update-in [target-frame-id :modifiers] ctm/add-children shapes drop-index)
(set-parent-ids shapes target-frame-id)
(cond-> h-sizing?
(update-in [original-frame :modifiers] ctm/change-property :layout-item-h-sizing :fix))
(cond-> v-sizing?
(update-in [original-frame :modifiers] ctm/change-property :layout-item-v-sizing :fix)))
(and layout? (= original-frame target-frame))
(update-in [target-frame :modifiers] ctm/add-children shapes drop-index))))]
(and target-layout? (= original-frame target-frame-id))
(update-in [target-frame-id :modifiers] ctm/add-children shapes drop-index))))]
(reduce update-frame-modifiers modif-tree origin-frame-ids)))
(as-> modif-tree $
(reduce update-frame-modifiers $ origin-frame-ids)
(cond-> $
(ctl/change-h-sizing? target-frame-id objects children-ids)
(update-in [target-frame-id :modifiers] ctm/change-property :layout-item-h-sizing :fix))
(cond-> $
(ctl/change-v-sizing? target-frame-id objects children-ids)
(update-in [target-frame-id :modifiers] ctm/change-property :layout-item-v-sizing :fix)))))
(defn modif->js
[modif-tree objects]

View File

@@ -15,7 +15,6 @@
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.page :as ctp]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.interactions :as ctsi]
[app.common.uuid :as uuid]
[app.main.data.modal :as md]
@@ -290,7 +289,7 @@
move to the desired position, and recalculate parents and frames as needed."
[all-objects page ids delta it]
(let [shapes (map (d/getf all-objects) ids)
unames (volatile! (ctt/retrieve-used-names (:objects page)))
unames (volatile! (cp/retrieve-used-names (:objects page)))
update-unames! (fn [new-name] (vswap! unames conj new-name))
all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids all-objects %2))) (d/ordered-set) ids)
ids-map (into {} (map #(vector % (uuid/next))) all-ids)
@@ -367,7 +366,7 @@
(let [update-flows (fn [flows]
(reduce
(fn [flows frame]
(let [name (ctt/generate-unique-name @unames "Flow-1")
(let [name (cp/generate-unique-name @unames "Flow 1")
_ (vswap! unames conj name)
new-flow {:id (uuid/next)
:name name
@@ -486,7 +485,10 @@
(gpt/subtract new-pos pt-obj)))))
(defn duplicate-selected [move-delta?]
(defn duplicate-selected
([move-delta?]
(duplicate-selected move-delta? false))
([move-delta? add-group-id?]
(ptk/reify ::duplicate-selected
ptk/WatchEvent
(watch [it state _]
@@ -503,6 +505,8 @@
changes (->> (prepare-duplicate-changes objects page selected delta it)
(duplicate-changes-update-indices objects selected))
changes (cond-> changes add-group-id? (assoc :group-id (uuid/random)))
id-original (first selected)
new-selected (->> changes
@@ -526,7 +530,7 @@
(select-shapes new-selected)
(ptk/data-event :layout/update frames)
(memorize-duplicated id-original id-duplicated)
(dwu/commit-undo-transaction undo-id)))))))))
(dwu/commit-undo-transaction undo-id))))))))))
(defn change-hover-state
[id value]

View File

@@ -271,16 +271,89 @@
(ptk/data-event :layout/update ids)
(dwu/commit-undo-transaction undo-id))))))
(defn fix-child-sizing
[objects parent-changes shape]
(let [parent (-> (cph/get-parent objects (:id shape))
(d/deep-merge parent-changes))
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
col? (ctl/col? parent)
row? (ctl/row? parent)
all-children (->> parent :shapes (map (d/getf objects)))]
(cond-> shape
;; If the parent is hug width and the direction column
;; change to fixed when ALL children are fill
(and col? auto-width? (every? ctl/fill-width? all-children))
(assoc :layout-item-h-sizing :fix)
;; If the parent is hug height and the direction is column
;; change to fixed when ANY children is fill
(and col? auto-height? (ctl/fill-height? shape))
(assoc :layout-item-v-sizing :fix)
;; If the parent is hug width and the direction row
;; change to fixed when ANY children is fill
(and row? auto-width? (ctl/fill-width? shape))
(assoc :layout-item-h-sizing :fix)
;; If the parent is hug height and the direction row
;; change to fixed when ALL children are fill
(and row? auto-height? (every? ctl/fill-height? all-children))
(assoc :layout-item-v-sizing :fix))))
(defn fix-parent-sizing
[objects ids-set changes parent]
(let [auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
col? (ctl/col? parent)
row? (ctl/row? parent)
all-children
(->> parent :shapes
(map (d/getf objects))
(map (fn [shape]
(if (contains? ids-set (:id shape))
(d/deep-merge shape changes)
shape))))]
(cond-> parent
;; Col layout and parent is hug-width if all children are fill-width
;; change parent to fixed
(and col? auto-width? (every? ctl/fill-width? all-children))
(assoc :layout-item-h-sizing :fix)
;; Col layout and parent is hug-height if any children is fill-height
;; change parent to fixed
(and col? auto-height? (some ctl/fill-height? all-children))
(assoc :layout-item-v-sizing :fix)
;; Row layout and parent is hug-width if any children is fill-width
;; change parent to fixed
(and row? auto-width? (some ctl/fill-width? all-children))
(assoc :layout-item-h-sizing :fix)
;; Row layout and parent is hug-height if all children are fill-height
;; change parent to fixed
(and row? auto-height? (every? ctl/fill-height? all-children))
(assoc :layout-item-v-sizing :fix))))
(defn update-layout-child
[ids changes]
(ptk/reify ::update-layout-child
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
children-ids (->> ids (mapcat #(cph/get-children-ids objects %)))
parent-ids (->> ids (map #(cph/get-parent-id objects %)))
layout-ids (->> ids (filter (comp ctl/layout? (d/getf objects))))
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dwc/update-shapes ids #(d/deep-merge (or % {}) changes))
(ptk/data-event :layout/update (d/concat-vec layout-ids parent-ids))
(dwc/update-shapes children-ids (partial fix-child-sizing objects changes))
(dwc/update-shapes parent-ids (partial fix-parent-sizing objects (set ids) changes))
(ptk/data-event :layout/update ids)
(dwu/commit-undo-transaction undo-id))))))

View File

@@ -77,7 +77,7 @@
([attrs]
(add-shape attrs {}))
([attrs {:keys [no-select?]}]
([attrs {:keys [no-select? no-update-layout?]}]
(us/verify ::shape-attrs attrs)
(ptk/reify ::add-shape
ptk/WatchEvent
@@ -108,7 +108,8 @@
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(ptk/data-event :layout/update [(:parent-id shape)])
(when-not no-update-layout?
(ptk/data-event :layout/update [(:parent-id shape)]))
(when-not no-select?
(dws/select-shapes (d/ordered-set id)))
(dwu/commit-undo-transaction undo-id))
@@ -387,8 +388,9 @@
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(add-shape shape)
(add-shape shape {:no-update-layout? true})
(move-shapes-into-frame (:id shape) selected)
(ptk/data-event :layout/update [(:id shape)])
(dwu/commit-undo-transaction undo-id)))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -453,4 +455,3 @@
(map (fn [[page-id frame-ids]]
(dch/update-shapes frame-ids #(dissoc % :use-for-thumbnail?) {:page-id page-id})))))
(rx/of (dch/update-shapes selected #(update % :use-for-thumbnail? not))))))))

View File

@@ -12,6 +12,7 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us :refer [max-safe-int min-safe-int]]
@@ -493,7 +494,7 @@
(- y vb-y (/ vb-height 2))
y))
unames (ctst/retrieve-used-names objects)
unames (cp/retrieve-used-names objects)
svg-name (str/replace (:name svg-data) ".svg" "")

View File

@@ -77,6 +77,11 @@
;; Delete the thumbnail first so if we interrupt we can regenerate after
(->> (rp/cmd! :upsert-file-object-thumbnail params)
(rx/catch #(rx/empty)))
;; Remove the thumbnail temporary. If the user changes pages the thumbnail is regenerated
(rx/of #(update % :workspace-thumbnails assoc object-id nil))
;; Send the update to the back-end
(->> blob-result
(rx/merge-map
(fn [blob]

View File

@@ -382,7 +382,7 @@
(if alt?
;; When alt is down we start a duplicate+move
(rx/of (start-move-duplicate initial)
(dws/duplicate-selected false))
(dws/duplicate-selected false true))
;; Otherwise just plain old move
(rx/of (start-move initial selected))))))

View File

@@ -55,10 +55,11 @@
state))
(defn- accumulate-undo-entry
[state {:keys [undo-changes redo-changes]}]
[state {:keys [undo-changes redo-changes group-id]}]
(-> state
(update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %))
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))))
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))
(assoc-in [:workspace-undo :transaction :group-id] group-id)))
(defn append-undo
[entry]

View File

@@ -484,6 +484,15 @@
(some (partial ctl/layout-immediate-child? objects))))
workspace-page-objects))
(defn all-layout-child?
[ids]
(l/derived
(fn [objects]
(->> ids
(map (d/getf objects))
(every? (partial ctl/layout-immediate-child? objects))))
workspace-page-objects))
(defn get-flex-child-viewer
[ids page-id]
(l/derived

View File

@@ -200,3 +200,12 @@
:body (http/form-data params)})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))
(defmethod command ::multipart-upload
[id params]
(->> (http/send! {:method :post
:uri (u/join @cf/public-uri "api/rpc/command/" (name id))
:credentials "include"
:body (http/form-data params)})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))

View File

@@ -130,7 +130,7 @@
(cond
(and touched? (:message error))
[:span.error {:id (dm/str "error-" input-name)
:data-test (clojure.string/join [data-test "-error"]) }(tr (:message error))]
:data-test (clojure.string/join [data-test "-error"])} (tr (:message error))]
(string? hint)
[:span.hint hint])]]))
@@ -328,7 +328,13 @@
remove-item!
(mf/use-fn
(fn [item]
(swap! items #(into [] (remove (fn [x] (= x item))) %))))]
(swap! items #(into [] (remove (fn [x] (= x item))) %))))
manage-key-down
(mf/use-fn
(fn [item event]
(when (kbd/enter? event)
(remove-item! item))))]
(mf/with-effect [result @value]
(let [val (cond-> @value trim str/trim)
@@ -337,14 +343,6 @@
(update-form! values)))
[:div {:class klass}
(when-let [items (seq @items)]
[:div.selected-items
(for [item items]
[:div.selected-item {:key (:text item)}
[:span.around {:class (when-not (:valid item) "invalid")}
[:span.text (:text item)]
[:span.icon {:on-click #(remove-item! item)} i/cross]]])])
[:input {:id (name input-name)
:class in-klass
:type "text"
@@ -355,4 +353,14 @@
:value @value
:on-change on-change
:placeholder (when empty? label)}]
[:label {:for (name input-name)} label]]))
[:label {:for (name input-name)} label]
(when-let [items (seq @items)]
[:div.selected-items
(for [item items]
[:div.selected-item {:key (:text item)
:tab-index "0"
:on-key-down (partial manage-key-down item)}
[:span.around {:class (when-not (:valid item) "invalid")}
[:span.text (:text item)]
[:span.icon {:on-click #(remove-item! item)} i/cross]]])])]))

View File

@@ -82,7 +82,7 @@
(when (:is-admin permissions)
{:value "admin" :label (tr "labels.admin")})
;; Temporarily disabled viewer roles
;; https://tree.taiga.io/project/uxboxproject/issue/1083
;; https://tree.taiga.io/project/penpot/issue/1083
;; {:value "viewer" :label (tr "labels.viewer")}
]
(filterv identity)))
@@ -209,7 +209,7 @@
[:li {:on-click set-admin} (tr "labels.admin")]
[:li {:on-click set-editor} (tr "labels.editor")]
;; Temporarily disabled viewer role
;; https://tree.taiga.io/project/uxboxproject/issue/1083
;; https://tree.taiga.io/project/penpot/issue/1083
;; [:li {:on-click set-viewer} (tr "labels.viewer")]
(when you-owner?
[:li {:on-click (partial set-owner member)} (tr "labels.owner")])]]]))
@@ -637,7 +637,9 @@
{::mf/register modal/components
::mf/register-as :webhook}
[{:keys [webhook] :as props}]
(let [initial (mf/use-memo (fn [] (or webhook {:is-active false :mtype "application/json"})))
;; FIXME: this is a workaround because input fields do not support rendering hooks
(let [initial (mf/use-memo (fn [] (or (some-> webhook (update :uri str))
{:is-active false :mtype "application/json"})))
form (fm/use-form :spec ::webhook-form
:initial initial)
on-success
@@ -735,8 +737,6 @@
]
[:div.explain (tr "dashboard.webhooks.active.explain")]]]
[:div.modal-footer
[:div.action-buttons
[:input.cancel-button
@@ -827,7 +827,7 @@
{:success? (nil? error-code)
:text last-delivery-text}]]]
[:div.table-field.uri
[:div (:uri webhook)]]
[:div (dm/str (:uri webhook))]]
[:div.table-field.active
[:div (if (:is-active webhook)
(tr "labels.active")
@@ -888,7 +888,7 @@
on-file-selected
(fn [file]
(st/emit! (dd/update-team-photo {:file file})))]
(st/emit! (dd/update-team-photo file)))]
(mf/use-effect

View File

@@ -15,11 +15,13 @@
(def actions (icon-xref :actions))
(def align-bottom (icon-xref :align-bottom))
(def align-content-column-around (icon-xref :align-content-column-around))
(def align-content-column-evenly (icon-xref :align-content-column-evenly))
(def align-content-column-between (icon-xref :align-content-column-between))
(def align-content-column-center (icon-xref :align-content-column-center))
(def align-content-column-end (icon-xref :align-content-column-end))
(def align-content-column-start (icon-xref :align-content-column-start))
(def align-content-row-around (icon-xref :align-content-row-around))
(def align-content-row-evenly (icon-xref :align-content-row-evenly))
(def align-content-row-between (icon-xref :align-content-row-between))
(def align-content-row-center (icon-xref :align-content-row-center))
(def align-content-row-end (icon-xref :align-content-row-end))
@@ -126,11 +128,13 @@
(def infocard (icon-xref :infocard))
(def interaction (icon-xref :interaction))
(def justify-content-column-around (icon-xref :justify-content-column-around))
(def justify-content-column-evenly (icon-xref :justify-content-column-evenly))
(def justify-content-column-between (icon-xref :justify-content-column-between))
(def justify-content-column-center (icon-xref :justify-content-column-center))
(def justify-content-column-end (icon-xref :justify-content-column-end))
(def justify-content-column-start (icon-xref :justify-content-column-start))
(def justify-content-row-around (icon-xref :justify-content-row-around))
(def justify-content-row-evenly (icon-xref :justify-content-row-evenly))
(def justify-content-row-between (icon-xref :justify-content-row-between))
(def justify-content-row-center (icon-xref :justify-content-row-center))
(def justify-content-row-end (icon-xref :justify-content-row-end))

View File

@@ -36,7 +36,7 @@
(next))]
[:div.modal-container.onboarding.onboarding-v2
[:div.modal-left.welcome
[:img {:src "images/onboarding-welcome.jpg" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:img {:src "images/onboarding-welcome.png" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:div.modal-right
[:div.release-container [:span.release "Version " (:main @cf/version)]]
[:div.right-content
@@ -71,7 +71,7 @@
(next))]
[:div.modal-container.onboarding.onboarding-v2
[:div.modal-left.welcome
[:img {:src "images/onboarding-people.jpg" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:img {:src "images/onboarding-people.png" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:div.modal-right
[:div.release-container [:span.release "Version " (:main @cf/version)]]
[:div.right-content

View File

@@ -196,9 +196,8 @@
:name name
:step 2}))}
(tr "labels.back")]
[:div {:title (tr "onboarding.choice.team-up.invite-members-submit")}
[:& fm/submit-button
{:label (tr "onboarding.choice.team-up.invite-members-submit")}]]]
[:& fm/submit-button
{:label (tr "onboarding.choice.team-up.invite-members-submit")}]]
[:div.skip-action
{:on-click on-skip}
[:div.action (tr "onboarding.choice.team-up.invite-members-skip")]]]]

View File

@@ -8,7 +8,6 @@
(:require
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.users :as du]
[app.main.repo :as rp]
[app.main.store :as st]
@@ -35,12 +34,9 @@
(def routes
[["/auth"
["/login" :auth-login]
(when (contains? @cf/flags :registration)
["/register" :auth-register])
(when (contains? @cf/flags :registration)
["/register/validate" :auth-register-validate])
(when (contains? @cf/flags :registration)
["/register/success" :auth-register-success])
["/register" :auth-register]
["/register/validate" :auth-register-validate]
["/register/success" :auth-register-success]
["/recovery/request" :auth-recovery-request]
["/recovery" :auth-recovery]
["/verify-token" :auth-verify-token]]

View File

@@ -124,11 +124,10 @@
(mf/fnc frame-shape
{::mf/wrap-props false}
[props]
(let [childs (unchecked-get props "childs")]
(let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs")]
[:> frame-container props
[:g.frame-children
[:g.frame-children {:opacity (:opacity shape)}
(for [item childs]
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}]
)]])))
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}])]])))

View File

@@ -84,7 +84,9 @@
:color (if show-text? text-color "transparent")
:caretColor (or text-color "black")
:overflowWrap "initial"
:lineBreak "auto"}
:lineBreak "auto"
:whiteSpace "break-spaces"
:textRendering "geometricPrecision"}
fills
(cond
(some? (:fills data))

View File

@@ -49,8 +49,8 @@
(st/emit! (dwm/upload-media-workspace params)))))]
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image))
[:button
{:title (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image))
:aria-label (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image))
:on-click on-click}
[:*
@@ -73,8 +73,8 @@
[:aside.left-toolbar
[:ul.left-toolbar-options
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.move" (sc/get-tooltip :move))
[:button
{:title (tr "workspace.toolbar.move" (sc/get-tooltip :move))
:aria-label (tr "workspace.toolbar.move" (sc/get-tooltip :move))
:class (when (and (nil? selected-drawtool)
(not edition)) "selected")
@@ -83,32 +83,32 @@
(when-not workspace-read-only?
[:*
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame))
[:button
{:title (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame))
:aria-label (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame))
:class (when (= selected-drawtool :frame) "selected")
:on-click (partial select-drawtool :frame)
:data-test "artboard-btn"}
i/artboard]]
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
[:button
{:title (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
:aria-label (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
:class (when (= selected-drawtool :rect) "selected")
:on-click (partial select-drawtool :rect)
:data-test "rect-btn"}
i/box]]
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
[:button
{:title (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
:aria-label (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
:class (when (= selected-drawtool :circle) "selected")
:on-click (partial select-drawtool :circle)
:data-test "ellipse-btn"}
i/circle]]
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
[:button
{:title (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
:aria-label (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
:class (when (= selected-drawtool :text) "selected")
:on-click (partial select-drawtool :text)}
@@ -117,16 +117,16 @@
[:& image-upload]
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
[:button
{:title (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
:aria-label (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
:class (when (= selected-drawtool :curve) "selected")
:on-click (partial select-drawtool :curve)
:data-test "curve-btn"}
i/pencil]]
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
[:button
{:title (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
:aria-label (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
:class (when (= selected-drawtool :path) "selected")
:on-click (partial select-drawtool :path)
@@ -134,8 +134,8 @@
i/pen]]])
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment))
[:button
{:title (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment))
:aria-label (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment))
:class (when (= selected-drawtool :comments) "selected")
:on-click (partial select-drawtool :comments)}
@@ -145,8 +145,8 @@
(when-not workspace-read-only?
[:*
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette))
[:button
{:title (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette))
:aria-label (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette))
:class (when (contains? layout :textpalette) "selected")
:on-click (fn []
@@ -158,8 +158,8 @@
"Ag"]]
[:li
[:button.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
[:button
{:title (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
:aria-label (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
:class (when (contains? layout :colorpalette) "selected")
:on-click (fn []
@@ -170,8 +170,8 @@
(vary-meta assoc ::ev/origin "workspace-left-toolbar")))))}
i/palette]]])
[:li
[:button.tooltip.tooltip-right.separator
{:alt (tr "workspace.toolbar.shortcuts" (sc/get-tooltip :show-shortcuts))
[:button
{:title (tr "workspace.toolbar.shortcuts" (sc/get-tooltip :show-shortcuts))
:aria-label (tr "workspace.toolbar.shortcuts" (sc/get-tooltip :show-shortcuts))
:class (when (contains? layout :shortcuts) "selected")
:on-click (fn []
@@ -183,8 +183,8 @@
i/shortcut]
(when *assert*
[:button.tooltip.tooltip-right
{:alt "Debugging tool"
[:button
{:title "Debugging tool"
:class (when (contains? layout :debug-panel) "selected")
:on-click (fn []
(let [is-sidebar-closed? (contains? layout :collapse-left-sidebar)]

View File

@@ -27,23 +27,21 @@
(defn- draw-thumbnail-canvas!
[canvas-node img-node]
(ts/raf
(fn []
(try
(when (and (some? canvas-node) (some? img-node))
(let [canvas-context (.getContext canvas-node "2d")
canvas-width (.-width canvas-node)
canvas-height (.-height canvas-node)]
(.clearRect canvas-context 0 0 canvas-width canvas-height)
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
(try
(when (and (some? canvas-node) (some? img-node))
(let [canvas-context (.getContext canvas-node "2d")
canvas-width (.-width canvas-node)
canvas-height (.-height canvas-node)]
(.clearRect canvas-context 0 0 canvas-width canvas-height)
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
;; Set a true on the next animation frame, we make sure the drawImage is completed
(ts/raf
#(dom/set-data! canvas-node "ready" "true"))
true))
(catch :default err
(.error js/console err)
false)))))
;; Set a true on the next animation frame, we make sure the drawImage is completed
(ts/raf
#(dom/set-data! canvas-node "ready" "true"))
true))
(catch :default err
(.error js/console err)
false)))
(defn- remove-image-loading
"Remove the changes related to change a url for its embed value. This is necessary
@@ -78,8 +76,12 @@
(gsh/selection-rect (concat [shape] all-children))
(-> shape :points gsh/points->selrect))
fixed-width (mth/clamp width 250 2000)
fixed-height (/ (* height fixed-width) width)
[fixed-width fixed-height]
(if (> width height)
[(mth/clamp width 250 2000)
(/ (* height (mth/clamp width 250 2000)) width)]
[(/ (* width (mth/clamp height 250 2000)) height)
(mth/clamp height 250 2000)])
image-url (mf/use-state nil)
observer-ref (mf/use-var nil)
@@ -91,6 +93,10 @@
thumbnail-data-ref (mf/use-memo (mf/deps page-id id) #(refs/thumbnail-frame-data page-id id))
thumbnail-data (mf/deref thumbnail-data-ref)
;; We only need the zoom level in Safari. For other browsers we don't want to activate this because
;; will render for every zoom change
zoom (when (cf/check-browser? :safari) (mf/deref refs/selected-zoom))
prev-thumbnail-data (hooks/use-previous thumbnail-data)
;; State to indicate to the parent that should render the frame
@@ -108,7 +114,9 @@
(let [canvas-node (mf/ref-val frame-canvas-ref)
img-node (mf/ref-val frame-image-ref)]
(when (draw-thumbnail-canvas! canvas-node img-node)
(reset! image-url nil)
(when-not (cf/check-browser? :safari)
(reset! image-url nil))
(when @show-frame-thumbnail
(reset! show-frame-thumbnail false))
;; If we don't have the thumbnail data saved (normally the first load) we update the data
@@ -266,12 +274,16 @@
{:key (dm/str "thumbnail-canvas-" (:id shape))
:ref frame-canvas-ref
:data-object-id (dm/str page-id (:id shape))
:width fixed-width
:height fixed-height
;; DEBUG
:style {:filter (when (and (not (cf/check-browser? :safari)) (debug? :thumbnails)) "invert(1)")
:width "100%"
:height "100%"}}]]
:width width
:height height
:style {;; Safari has a problem with the positioning of the canvas. All this is to fix Safari behavior
;; https://bugs.webkit.org/show_bug.cgi?id=23113
:display (when (cf/check-browser? :safari) "none")
:position "fixed"
:transform-origin "top left"
:transform (when (cf/check-browser? :safari) (dm/fmt "scale(%)" zoom))
;; DEBUG
:filter (when (debug? :thumbnails) "invert(1)")}}]]
;; Safari don't support filters so instead we add a rectangle around the thumbnail
(when (and (cf/check-browser? :safari) (debug? :thumbnails))
@@ -285,10 +297,10 @@
(when (some? @image-url)
[:foreignObject {:x x
:y y
:width width
:height height}
:width fixed-width
:height fixed-height}
[:img {:ref frame-image-ref
:src @image-url
:width width
:height height
:width fixed-width
:height fixed-height
:on-load on-image-load}]])])]))

View File

@@ -446,10 +446,7 @@
:editing renaming?)
:value (cph/merge-path-item (:path component) (:name component))
:tooltip (cph/merge-path-item (:path component) (:name component))
:display-value (if listing-thumbs?
(:name component)
(cph/compact-name (:path component)
(:name component)))
:display-value (:name component)
:editing? renaming?
:disable-dbl-click? true
:on-change do-rename
@@ -831,10 +828,7 @@
:editing renaming?)
:value (cph/merge-path-item (:path object) (:name object))
:tooltip (cph/merge-path-item (:path object) (:name object))
:display-value (if listing-thumbs?
(:name object)
(cph/compact-name (:path object)
(:name object)))
:display-value (:name object)
:editing? renaming?
:disable-dbl-click? true
:on-change do-rename

View File

@@ -21,8 +21,8 @@
:layout-gap-type ;; :simple, :multiple
:layout-gap ;; {:row-gap number , :column-gap number}
:layout-align-items ;; :start :end :center :stretch
:layout-justify-content ;; :start :center :end :space-between :space-around
:layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
:layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly
:layout-align-content ;; :start :center :end :space-between :space-around :space-evenly :stretch (by default)
:layout-wrap-type ;; :wrap, :nowrap
:layout-padding-type ;; :simple, :multiple
:layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
@@ -50,12 +50,14 @@
:end i/justify-content-column-end
:center i/justify-content-column-center
:space-around i/justify-content-column-around
:space-evenly i/justify-content-column-evenly
:space-between i/justify-content-column-between)
(case val
:start i/justify-content-row-start
:end i/justify-content-row-end
:center i/justify-content-row-center
:space-around i/justify-content-row-around
:space-evenly i/justify-content-row-evenly
:space-between i/justify-content-row-between))
:align-content (if is-col?
@@ -64,6 +66,7 @@
:end i/align-content-column-end
:center i/align-content-column-center
:space-around i/align-content-column-around
:space-evenly i/align-content-column-evenly
:space-between i/align-content-column-between
:stretch nil)
@@ -72,6 +75,7 @@
:end i/align-content-row-end
:center i/align-content-row-center
:space-around i/align-content-row-around
:space-evenly i/align-content-row-evenly
:space-between i/align-content-row-between
:stretch nil))
@@ -140,16 +144,27 @@
(mf/defc align-content-row
[{:keys [is-col? align-content set-align-content] :as props}]
[:div.align-content-style
(for [align [:start :center :end :space-around :space-between]]
[:button.align-content.tooltip
{:class (dom/classnames :active (= align-content align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Align content " (d/name align))
:on-click #(set-align-content align)
:key (dm/str "align-content" (d/name align))}
(get-layout-flex-icon :align-content align is-col?)])])
[:*
[:div.align-content-style
(for [align [:start :center :end]]
[:button.align-content.tooltip
{:class (dom/classnames :active (= align-content align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Align content " (d/name align))
:on-click #(set-align-content align)
:key (dm/str "align-content" (d/name align))}
(get-layout-flex-icon :align-content align is-col?)])]
[:div.align-content-style
(for [align [:space-between :space-around :space-evenly]]
[:button.align-content.tooltip
{:class (dom/classnames :active (= align-content align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Align content " (d/name align))
:on-click #(set-align-content align)
:key (dm/str "align-content" (d/name align))}
(get-layout-flex-icon :align-content align is-col?)])]])
(mf/defc justify-content-row
[{:keys [is-col? justify-content set-justify] :as props}]
@@ -165,7 +180,7 @@
:key (dm/str "justify-content" (d/name justify))}
(get-layout-flex-icon :justify-content justify is-col?)])]
[:div.justify-content-style
(for [justify [:space-around :space-between]]
(for [justify [:space-between :space-around :space-evenly]]
[:button.justify.tooltip
{:class (dom/classnames :active (= justify-content justify)
:tooltip-bottom-left (not= justify :space-around)
@@ -399,7 +414,7 @@
(when (= :wrap wrap-type)
[:div.layout-row
[:div.align-content.row-title "Content"]
[:div.btn-wrapper
[:div.btn-wrapper.align-content
[:& align-content-row {:is-col? is-col?
:align-content align-content
:set-align-content set-align-content}]]])

View File

@@ -11,6 +11,7 @@
[app.common.geom.shapes :as gsh]
[app.common.pages.common :as cpc]
[app.common.text :as txt]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.texts :as dwt]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
@@ -26,7 +27,7 @@
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-attrs shadow-menu]]
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]]
[app.main.ui.workspace.sidebar.options.menus.text :as ot]
[rumext.v2 :as mf]))
[rumext.v2 :as mf]))
;; Define how to read each kind of attribute depending on the shape type:
;; - shape: read the attribute directly from the shape.
@@ -298,6 +299,13 @@
has-text? (contains? all-types :text)
has-layout-container? (->> shapes (some ctl/layout?))
all-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/all-layout-child? ids))
all-layout-child? (mf/deref all-layout-child-ref)
all-layout-container? (->> shapes (every? ctl/layout?))
[measure-ids measure-values] (get-attrs shapes objects :measure)
[layer-ids layer-values
@@ -334,12 +342,12 @@
[:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values :multiple true}]
(when is-layout-child?
(when (or is-layout-child? has-layout-container?)
[:& layout-item-menu
{:type type
:ids layout-item-ids
:is-layout-child? true
:is-layout-container? true
:is-layout-child? all-layout-child?
:is-layout-container? all-layout-container?
:values layout-item-values}])
(when-not (or (empty? constraint-ids) is-layout-child?)

View File

@@ -162,14 +162,14 @@
on-drag-over (actions/on-drag-over)
on-drop (actions/on-drop file)
on-mouse-down (actions/on-mouse-down @hover selected edition drawing-tool text-editing? node-editing?
drawing-path? create-comment? space? panning workspace-read-only?)
drawing-path? create-comment? space? panning z? workspace-read-only?)
on-mouse-up (actions/on-mouse-up disable-paste)
on-pointer-down (actions/on-pointer-down)
on-pointer-enter (actions/on-pointer-enter in-viewport?)
on-pointer-leave (actions/on-pointer-leave in-viewport?)
on-pointer-move (actions/on-pointer-move move-stream)
on-pointer-up (actions/on-pointer-up)
on-move-selected (actions/on-move-selected hover hover-ids selected space? workspace-read-only?)
on-move-selected (actions/on-move-selected hover hover-ids selected space? z? workspace-read-only?)
on-menu-selected (actions/on-menu-selected hover hover-ids selected workspace-read-only?)
on-frame-enter (actions/on-frame-enter frame-hover)
@@ -439,8 +439,6 @@
[:& presence/active-cursors
{:page-id page-id}])
[:& widgets/viewport-actions]
[:& scroll-bars/viewport-scrollbars
{:objects base-objects
:zoom zoom

View File

@@ -34,70 +34,71 @@
(defn on-mouse-down
[{:keys [id blocked hidden type]} selected edition drawing-tool text-editing?
node-editing? drawing-path? create-comment? space? panning workspace-read-only?]
node-editing? drawing-path? create-comment? space? panning z? workspace-read-only?]
(mf/use-callback
(mf/deps id blocked hidden type selected edition drawing-tool text-editing?
node-editing? drawing-path? create-comment? @space?
node-editing? drawing-path? create-comment? @z? @space?
panning workspace-read-only?)
(fn [bevent]
(when (or (dom/class? (dom/get-target bevent) "viewport-controls")
(dom/class? (dom/get-target bevent) "viewport-selrect"))
(dom/stop-propagation bevent)
(let [event (.-nativeEvent bevent)
ctrl? (kbd/ctrl? event)
meta? (kbd/meta? event)
shift? (kbd/shift? event)
alt? (kbd/alt? event)
mod? (kbd/mod? event)
(when-not @z?
(let [event (.-nativeEvent bevent)
ctrl? (kbd/ctrl? event)
meta? (kbd/meta? event)
shift? (kbd/shift? event)
alt? (kbd/alt? event)
mod? (kbd/mod? event)
left-click? (and (not panning) (= 1 (.-which event)))
middle-click? (and (not panning) (= 2 (.-which event)))]
left-click? (and (not panning) (= 1 (.-which event)))
middle-click? (and (not panning) (= 2 (.-which event)))]
(cond
(or middle-click? (and left-click? @space?))
(do
(dom/prevent-default bevent)
(if mod?
(let [raw-pt (dom/get-client-position event)
pt (uwvv/point->viewport raw-pt)]
(st/emit! (dw/start-zooming pt)))
(st/emit! (dw/start-panning))))
(cond
(or middle-click? (and left-click? @space?))
(do
(dom/prevent-default bevent)
(if mod?
(let [raw-pt (dom/get-client-position event)
pt (uwvv/point->viewport raw-pt)]
(st/emit! (dw/start-zooming pt)))
(st/emit! (dw/start-panning))))
left-click?
(do
(st/emit! (ms/->MouseEvent :down ctrl? shift? alt? meta?))
left-click?
(do
(st/emit! (ms/->MouseEvent :down ctrl? shift? alt? meta?))
(when (and (not= edition id) text-editing?)
(st/emit! dw/clear-edition-mode))
(when (and (not= edition id) text-editing?)
(st/emit! dw/clear-edition-mode))
(when (and (not text-editing?)
(not blocked)
(not hidden)
(not create-comment?)
(not drawing-path?))
(cond
node-editing?
(when (and (not text-editing?)
(not blocked)
(not hidden)
(not create-comment?)
(not drawing-path?))
(cond
node-editing?
;; Handle path node area selection
(when-not workspace-read-only?
(st/emit! (dwdp/handle-area-selection shift?)))
(when-not workspace-read-only?
(st/emit! (dwdp/handle-area-selection shift?)))
drawing-tool
(when-not workspace-read-only?
(st/emit! (dd/start-drawing drawing-tool)))
drawing-tool
(when-not workspace-read-only?
(st/emit! (dd/start-drawing drawing-tool)))
(or (not id) mod?)
(st/emit! (dw/handle-area-selection shift? mod?))
(or (not id) mod?)
(st/emit! (dw/handle-area-selection shift? mod?))
(not drawing-tool)
(when-not workspace-read-only?
(st/emit! (dw/start-move-selected id shift?))))))))))))
(not drawing-tool)
(when-not workspace-read-only?
(st/emit! (dw/start-move-selected id shift?)))))))))))))
(defn on-move-selected
[hover hover-ids selected space? workspace-read-only?]
[hover hover-ids selected space? z? workspace-read-only?]
(mf/use-callback
(mf/deps @hover @hover-ids selected @space? workspace-read-only?)
(mf/deps @hover @hover-ids selected @space? @z? workspace-read-only?)
(fn [bevent]
(let [event (.-nativeEvent bevent)
shift? (kbd/shift? event)
@@ -110,7 +111,7 @@
(not @space?))
(dom/prevent-default bevent)
(dom/stop-propagation bevent)
(when-not workspace-read-only?
(when-not (or workspace-read-only? @z?)
(st/emit! (dw/start-move-selected))))))))
(defn on-frame-select

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