Compare commits

...

158 Commits

Author SHA1 Message Date
Alejandro
1eaf7b2b44 Merge pull request #3593 from penpot/niwinz-staging-bugfixes-9
🐛 Bugfixes and logging improvements
2023-09-04 11:42:27 +02:00
Andrey Antukh
903f064e87 Decrease slightly argon2id cost for improve usability
The previous values are set too high. The current value are still
valid under current recomendation but improves a little bit the
time of password verification.
2023-09-04 11:35:31 +02:00
Andrey Antukh
a23d1908e9 Improve worker logging 2023-09-04 11:35:31 +02:00
Andrey Antukh
1e8226a3fc 🐛 Fix log level setting on file migrations ns 2023-09-04 11:35:31 +02:00
Andrey Antukh
b7459726f5 Merge pull request #3592 from penpot/superalex-remember-last-team-log-out-2
 Remember last team accross logouts and sessions and fix some weird stuff
2023-09-04 11:12:42 +02:00
Alejandro Alonso
b8179d0e35 Remember last team accross logouts and sessions and fix some auth weird stuff 2023-09-04 10:34:30 +02:00
Andrey Antukh
e36b49b4f0 Merge pull request #3587 from penpot/superalex-layer-multiselection-behaviour
 Improve layers multiselection behaviour
2023-09-01 11:20:27 +02:00
Alejandro Alonso
92ff5de538 Improve layers multiselection behaviour 2023-09-01 11:20:10 +02:00
Alejandro Alonso
c83d028466 Colorpicker: remember las color mode 2023-09-01 11:18:45 +02:00
Alejandro
56a0d522dc Merge pull request #3585 from penpot/niwinz-staging-storage-gc-deleted
 Add minor improvements to logging
2023-09-01 06:40:41 +02:00
Andrey Antukh
a3495800b5 Add minor logging improvements to worker namespace 2023-08-31 21:09:18 +02:00
Andrey Antukh
750cf05784 Add minor logging related improvements to binfile namespace 2023-08-31 21:08:23 +02:00
Andrey Antukh
1384219ae7 📎 Update devenv logging file 2023-08-31 21:08:01 +02:00
Andrey Antukh
d2d9aeff25 📎 Reduce log level of worker submit operation
Start logging to as TRACE instead of DEBUG
2023-08-31 20:59:58 +02:00
Andrey Antukh
95d80c9578 Merge pull request #3582 from penpot/superalex-fix-invalid-comments-when-delete-page
🐛 Fix deleted pages comments shown in right sidebar
2023-08-31 20:02:00 +02:00
Alejandro
b523bef8ba Merge pull request #3581 from penpot/niwinz-staging-storage-gc-deleted
 Improve storage-gc-deleted task reliability
2023-08-31 15:18:15 +02:00
Alejandro Alonso
0c5c04e58a 🐛 Fix deleted pages comments shown in right sidebar 2023-08-31 15:16:55 +02:00
Andrey Antukh
a0973b9ddf Improve storage-gc-deleted task reliability 2023-08-31 14:36:31 +02:00
Alejandro
c53b6117c0 Merge pull request #3574 from penpot/azazeln28-fix-text-shapes-rendered-with-bad-proportions
🐛 Fix text shapes rendered with bad proportions
2023-08-31 12:10:36 +02:00
Aitor
bd3ddebcc4 🐛 Fix text shapes rendered with bad proportions 2023-08-31 12:06:31 +02:00
Alejandro
0441f28880 Merge pull request #3577 from penpot/hiru-hide-messages-on-exit
🐛 Fix message popup remains open when exiting workspace
2023-08-31 11:45:41 +02:00
Andrés Moya
288030888a 🐛 Fix message popup remains open when exiting workspace 2023-08-31 11:39:46 +02:00
Alejandro
203c0ed87d Merge pull request #3579 from penpot/eva-refix-lock-title
🐛 Fix lock and hide tooltip
2023-08-31 11:38:51 +02:00
Eva
09e28076cd 🐛 Fix lock and hide tooltip 2023-08-31 11:31:58 +02:00
Alejandro
ad4e489312 Merge pull request #3578 from penpot/superalex-fix-list-view-is-discarded-on-tab-change-for-assets-sidebar-tab
🐛 Fix list view is discarded on tab change for assets sidebar
2023-08-31 11:31:13 +02:00
Alejandro Alonso
50932dea54 🐛 Fix list view is discarded on tab change for assets sidebar 2023-08-31 11:25:36 +02:00
Andrey Antukh
da3c829b1b 📎 Fix clj linter issues on backend 2023-08-31 11:24:30 +02:00
Andrey Antukh
d4b4e6be7d 🐛 Fix frontend cljs linter issues 2023-08-31 10:49:09 +02:00
Andrey Antukh
cc5b1c950b Merge branch 'translations' into staging 2023-08-30 10:35:47 +02:00
Andrey Antukh
52851f4c6f 📎 Add dutch language 2023-08-30 10:35:33 +02:00
Andrey Antukh
9bd42be771 Merge remote-tracking branch 'weblate/develop' into translations 2023-08-30 10:26:28 +02:00
Andrey Antukh
5f65960d42 Merge pull request #3568 from penpot/eva-fix-tooltip-visibility
🐛 Fix tooltip on toggle visibility and toggle lock buttons
2023-08-29 13:15:54 +02:00
Eva
dc813732c3 🐛 Fix tooltip on toggle visibility and toggle lock buttons 2023-08-29 13:15:40 +02:00
Andrey Antukh
661e4a001a Merge pull request #3569 from penpot/superalex-fix-invalid-file-amount-after-moving-files
🐛 Bugfixing
2023-08-29 13:13:36 +02:00
Alejandro Alonso
53d1624f3f 🐛 Fix deleted pages comments shown in right sidebar 2023-08-29 13:13:12 +02:00
Alejandro Alonso
514ba6604b 🐛 Fix invalid file amount after moving files 2023-08-29 13:13:11 +02:00
Alejandro
0aa361013a Merge pull request #3551 from penpot/niwinz-bugfixes-1
🐛 Fix unexpected output on get-page when invalid object-id is pro…
2023-08-29 13:04:34 +02:00
Andrey Antukh
ddbc828342 🐛 Fix unexpected output on get-page when invalid object-id is provided 2023-08-29 13:04:23 +02:00
Sebastiaan Pasma
67cff1ed74 🌐 Add translations for: Dutch.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-08-28 16:57:12 +02:00
Sebastiaan Pasma
22c88a19e2 🌐 Add translations for: Dutch.
Currently translated at 83.2% (1007 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-08-26 15:54:37 +02:00
Andrey Antukh
159ac92021 Merge pull request #3561 from penpot/superalex-click-flow-tag-open-viewer
 Click on flow tag open viewer
2023-08-25 12:40:10 +02:00
Andrey Antukh
1a92657c7c Merge pull request #3559 from penpot/superalex-fix-alt-l-shortcuts
🐛 Fix alt+l shortcuts
2023-08-25 12:37:10 +02:00
Alejandro Alonso
8669207086 Click on flow tag open viewer 2023-08-25 11:40:56 +02:00
Alejandro Alonso
b82ce671b9 🐛 Fix alt+l shortcuts 2023-08-25 10:54:34 +02:00
Aitor Moreno
ff14208a95 Merge pull request #3555 from penpot/superalex-navigate-up-in-layer-hierarchy-with-shift-enter-shortcut
 Navigate up in layer hierarchy with Shift+Enter shortcut
2023-08-24 13:42:12 +02:00
Aitor
8593ca1310 🐛 Fix scroll automatically to layer item 2023-08-24 13:31:47 +02:00
Alejandro Alonso
f69e141ac1 Navigate up in layer lierarchy with Shift+Enter shortcut 2023-08-24 12:25:03 +02:00
Alejandro
b0497f1352 Merge pull request #3554 from penpot/niwinz-staging-bugfixes-8
🐛 Prevent rollback for idle-in-transaction errors on cron tasks
2023-08-24 12:02:13 +02:00
Alejandro Alonso
aaf9c6e50b Enable access tokens by default 2023-08-24 12:00:56 +02:00
Andrey Antukh
d80aa7593b 🐛 Fix unexpected exception on encoding error response 2023-08-24 11:37:59 +02:00
Andrey Antukh
5275c35002 🐛 Prevent rollback for idle-in-transaction errors on cron tasks 2023-08-24 11:18:56 +02:00
Alejandro Alonso
f02b5765d7 🐛 Fix safe number max values 2023-08-23 14:59:08 +02:00
Alejandro Alonso
1f31722571 📎 Update version.txt file 2023-08-23 09:38:23 +02:00
Alejandro Alonso
834c18323e Revert "📎 Update version.txt file"
This reverts commit a7f39e89f6.
2023-08-23 09:38:07 +02:00
andy
1d2f5b6c0b 🌐 Add translations for: Norwegian Bokmål.
Currently translated at 12.5% (152 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nb_NO/
2023-08-22 11:49:11 +02:00
Alejandro
ab87db099a Merge pull request #3542 from penpot/niwinz-bugfixes-1
🐛 Fix inconsistencies on handlong :file-image attr on import and file-gc task
2023-08-22 06:50:58 +02:00
Andrey Antukh
661a916a5f 🐛 Fix reference counting of file-media objects in :fill-image attr 2023-08-21 19:11:55 +02:00
Andrey Antukh
b8dee17075 🐛 Fix incorrect streams handling on thumbnail_render 2023-08-21 19:11:55 +02:00
Stas Haas
c8d5e4ef35 🌐 Add translations for: German.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-08-19 13:55:24 +02:00
Alejandro Alonso
a7f39e89f6 📎 Update version.txt file 2023-08-18 10:35:12 +02:00
Pablo Alba
70bb34118c Merge pull request #3532 from penpot/hiru-fix-component-modified
🐛 Fix component modified date in v1
2023-08-17 17:18:48 +02:00
Andrés Moya
f409dfd3d1 🐛 Fix component modified date in v1 2023-08-17 16:05:54 +02:00
Alejandro Alonso
e1954b5dd7 🐛 Fix old files with invalid refs for texts and fills 2023-08-17 09:48:18 +02:00
Sebastiaan Pasma
196d57dd5c 🌐 Add translations for: Dutch.
Currently translated at 74.1% (897 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-08-15 15:50:00 +02:00
Alejandro
a1ac839b2a Merge pull request #3517 from penpot/niwinz-enhancements-push-notifications
🎉 Add the ability to send push notifications
2023-08-14 12:24:43 +02:00
Andrey Antukh
1e9a4d74eb 🐛 Add safechecks to binfile exportation 2023-08-14 12:13:31 +02:00
Andrey Antukh
7a9777419c Backport db module improvements from develop 2023-08-14 12:13:31 +02:00
Andrey Antukh
28836d82cd Add minor improvements to error report template 2023-08-14 12:13:31 +02:00
Andrey Antukh
da62a6809c Stop report oidc failed operations as exceptions 2023-08-14 12:13:31 +02:00
Andrey Antukh
5d5d238fec 💄 Add minor cosmetic improvements on dashboard ui component 2023-08-14 12:13:31 +02:00
Andrey Antukh
e5dedb1e3d 🎉 Add push notifications support 2023-08-14 12:13:31 +02:00
Sebastiaan Pasma
4c7cd02f56 🌐 Add translations for: Dutch.
Currently translated at 54.0% (654 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-08-12 14:49:53 +02:00
Alejandro Alonso
b3128bd32b 🐛 Fix overlay manual positioning 2023-08-10 11:15:42 +02:00
Alejandro Alonso
15a9035ed1 🐛 Fix multiple elements export 2023-08-09 12:19:27 +02:00
Vincas Dundzys
82e51d358b 🌐 Add translations for: Lithuanian.
Currently translated at 10.3% (125 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lt/
2023-08-09 09:04:18 +02:00
Alejandro
fbcc2494b4 Merge pull request #3509 from penpot/niwinz-staging-bugfixes-7
 Add improvements to api doc
2023-08-09 08:00:02 +02:00
Andrey Antukh
4a016dce14 📎 Add minor improvements on params formating on error logger 2023-08-08 14:53:55 +02:00
Andrey Antukh
53f40043aa 📎 Fix typo on api doc main template 2023-08-08 14:52:39 +02:00
Alejandro Alonso
937dd5a857 🐛 Fix zip importer for none fills 2023-08-08 13:52:09 +02:00
Andrey Antukh
36b167956c Add improvements to api doc 2023-08-08 13:44:47 +02:00
Alejandro
695152274c Merge pull request #3506 from penpot/niwinz-staging-bugfixes-7
 Improve error report of invalid image
2023-08-08 13:17:34 +02:00
Andrey Antukh
486c638076 🐛 Fix image upload issues on safari with drag&drop 2023-08-08 12:58:39 +02:00
Andrey Antukh
81facd58c9 Improve error report of invalid image 2023-08-08 12:57:49 +02:00
Alejandro
2a0031d23c Merge pull request #3505 from penpot/niwinz-staging-bugfixes-7
💄 Add  minor cosmetic improvement on error report template
2023-08-08 11:07:02 +02:00
Andrey Antukh
63a3186e6d 💄 Add minor cosmetic improvement on error report template 2023-08-08 10:42:26 +02:00
Alejandro Alonso
fcdf33b134 🐛 Fix backend api doc generation for auth required endpoints 2023-08-08 10:39:09 +02:00
Alejandro Alonso
19d88cc1a6 🐛 Fix backend api doc generation 2023-08-08 09:55:32 +02:00
Alejandro
1f68c6164a Merge pull request #3501 from penpot/niwinz-staging-bugfixes-7
 Improve get-user-info implementation (oidc)
2023-08-07 16:36:19 +02:00
Andrey Antukh
c39702fbf7 Improve get-user-info implementation (oidc) 2023-08-07 15:55:54 +02:00
Alejandro Alonso
b3f0683d02 🐛 Fix image validation 2023-08-07 15:06:59 +02:00
Alejandro
211de1bb9c Merge pull request #3498 from penpot/niwinz-staging-bugfixes-6
🐛 Allow nil values on bool content params
2023-08-07 12:57:30 +02:00
Andrey Antukh
fe80aab394 🐛 Allow nil values on bool content params 2023-08-07 11:46:19 +02:00
Alejandro
a494b89bba Merge pull request #3497 from penpot/niwinz-staging-bugfixes-6
🐛 Fix incorrect implementation on error reporting context collection
2023-08-07 11:20:00 +02:00
Andrey Antukh
6e313dff84 🐛 Add workaround for unexpected exception on fix-broken-shapes
which happens when we have a component shape tree with an ephimeral
shape with id ZERO (unused and with invalid children)
2023-08-07 11:12:27 +02:00
Alejandro Alonso
766040198a 🐛 Fix text validation 2023-08-07 09:29:04 +02:00
Andrey Antukh
7afaa9d31f 🐛 Fix incorrect implementation on error reporting context collection 2023-08-04 18:40:47 +02:00
Alejandro Alonso
cf68a9cf1e 🐛 Fix safe number max values 2023-08-04 15:16:43 +02:00
Alejandro
c69f6da2d7 Merge pull request #3493 from penpot/niwinz-staging-bugfixes-6
🐛 Several bugfixes
2023-08-04 13:35:12 +02:00
Andrey Antukh
259b05db51 Add more improvements to error reporting 2023-08-04 13:10:36 +02:00
Andrey Antukh
2ba7996116 🐛 Fix unexpected viewport update on leave workspace 2023-08-04 12:58:27 +02:00
Andrey Antukh
66e877ed40 🐛 Fix stroke-width parsing on svg upload
And refactor a bit the stroke parsing function
2023-08-04 12:58:27 +02:00
Alejandro
f3bf04e1c9 Merge pull request #3488 from penpot/niwinz-staging-bugfixes-5
 Add better error reporting on response encoding middleware
2023-08-03 16:46:05 +02:00
Alejandro Alonso
79e3aadfcf 🐛 Fix undo change for multiple shapes 2023-08-03 16:38:21 +02:00
Andrey Antukh
0527c55398 Add better exception handling on json content type handling 2023-08-03 16:31:35 +02:00
Andrey Antukh
54bb89b2bb ⬆️ Upgrade yetti to v9.16 (fixes exception unwrapping) 2023-08-03 16:31:35 +02:00
Andrey Antukh
9334f935eb Add better error reporting on response encoding middleware 2023-08-03 16:10:41 +02:00
Alejandro
fed31d366f Merge pull request #3480 from penpot/azazeln28-bugfixing-1
🐛 Bug fixing
2023-08-03 07:28:28 +02:00
Aitor Moreno
55b7bba944 Merge pull request #3484 from penpot/superalex-fix-duplicate-board
🐛 Fix duplicate board
2023-08-02 18:27:18 +02:00
Alejandro Alonso
3ff13f1d8f 🐛 Fix duplicate board 2023-08-02 18:22:46 +02:00
Aitor
4b28685a6d 🐛 Fix prototype selects preventing ctrl-z 2023-08-02 16:15:08 +02:00
Alejandro
53001921d5 Merge pull request #3481 from penpot/niwinz-staging-hotfix-4
🐛 Bugfixes & Improvements
2023-08-02 16:08:12 +02:00
Andrey Antukh
046f501152 Improve error reporting context 2023-08-02 14:51:12 +02:00
Andrey Antukh
00f7c94377 Improve database error reporter 2023-08-02 13:43:53 +02:00
Andrey Antukh
eae5dfc828 🐛 Don't send empty changes on fix broken shape links 2023-08-02 13:43:53 +02:00
Andrey Antukh
88261c2ec3 Increase network timeout on exporter dockerfile 2023-08-02 13:43:53 +02:00
Andrey Antukh
1bfc28f63d Add missing index on server_error_report table 2023-08-02 13:43:53 +02:00
Alejandro Alonso
e7a82579c1 🐛 Fix paste groups without shapes attr 2023-08-02 11:17:20 +02:00
Alejandro
30c786741f Merge pull request #3478 from penpot/niwinz-staging-hotfix-4
🐛 Fix broken shape relations on workspace initialization
2023-08-02 11:13:39 +02:00
Andrey Antukh
3eb2569465 Add better exception reporting on commit-changes 2023-08-02 10:45:11 +02:00
Andrey Antukh
7efeeec9b1 Add workspace initialization fix for broken shape references
Is the code that executes at workspace initialization that checks all
the shape children for broken references and proceed to emit a special
event that fixes the shape children references.
2023-08-02 10:45:11 +02:00
Aitor
67f56dd0f8 🐛 Fix color picker not working when using shortcut 2023-08-02 10:18:40 +02:00
Alejandro
2ec5a3ba6a Merge pull request #3476 from penpot/niwinz-staging-hotfix-4
 Improve ws-conn handling on session expiration
2023-08-01 14:41:15 +02:00
Andrey Antukh
958931d264 Improve ws-conn handling on session expiration 2023-08-01 13:09:51 +02:00
Alejandro Alonso
e3f69bcc98 🐛 Fix path validation 2023-08-01 12:39:33 +02:00
Alejandro
9c53a33bac Merge pull request #3472 from penpot/niwinz-staging-hotfix-3
🐛 Ensure :shapes attr on importing an svg with an empty group
2023-07-31 16:33:06 +02:00
Andrey Antukh
f72206bba3 🐛 Ensure :shapes attr on importing an svg with an empty group
This commit should not not be backported to, because the affected
code is already refactored and the issue is already fixed on develop
branch
2023-07-31 16:26:03 +02:00
Alejandro
37a19aa6b5 Merge pull request #3471 from penpot/niwinz-staging-hotfix-3
🐛 Hot Fixes
2023-07-31 16:20:47 +02:00
Andrey Antukh
17ea8300ed 🐛 Accept nil values for :fill-color-gradient attr 2023-07-31 15:58:32 +02:00
Andrey Antukh
aac044fa0a 🐛 Fix incorrect schema on bool-content 2023-07-31 15:49:42 +02:00
Alejandro
e935ccae76 Merge pull request #3469 from penpot/niwinz-staging-hotfix-2
🐛 Allow nil values for x,y,width and height on paths
2023-07-31 13:41:22 +02:00
Andrey Antukh
13312dc467 🐛 Allow nil values for x,y,width and height on paths 2023-07-31 13:36:28 +02:00
Alejandro Alonso
0ec49e5e95 🐛 Fix remove content from boolean 2023-07-31 13:02:52 +02:00
Alejandro
a49999186f Merge pull request #3466 from penpot/niwinz-staging-hotfix-1
🐛 Remove limits that can cause unexpected exceptions
2023-07-31 12:09:58 +02:00
Andrey Antukh
fc416ee4af 🐛 Make grid params type optional 2023-07-31 12:06:31 +02:00
Andrey Antukh
37bd537bfd 🐛 Remove limits that can cause unexpected exceptions 2023-07-31 11:54:29 +02:00
Yaron Shahrabani
1a92bd0478 🌐 Add translations for: Hebrew.
Currently translated at 99.7% (1206 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2023-07-24 21:05:43 +02:00
AlexTECPlayz
cd55adefb8 🌐 Add translations for: Romanian.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2023-07-18 15:02:57 +02:00
Stas Haas
3006ed7966 🌐 Add translations for: German.
Currently translated at 99.8% (1207 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-07-14 10:49:25 +02:00
Kristijan Žic
53ed1404e7 🌐 Add translations for: Croatian.
Currently translated at 84.9% (1027 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/
2023-07-05 04:17:05 +02:00
Linerly
f691f8d5b5 🌐 Add translations for: Indonesian.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2023-07-02 19:52:28 +02:00
Amine Gdoura
2c68e8309e 🌐 Add translations for: Arabic.
Currently translated at 61.4% (743 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2023-07-02 19:52:27 +02:00
Amerey.eu
dce8b5b37c 🌐 Add translations for: Czech.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/cs/
2023-07-01 14:52:54 +02:00
Mikel Larreategi
6546bfc889 🌐 Add translations for: Basque.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2023-07-01 14:52:52 +02:00
Linerly
b915abb2d2 🌐 Add translations for: Indonesian.
Currently translated at 97.0% (1173 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2023-07-01 14:52:51 +02:00
Stas Haas
5cb5df63d9 🌐 Add translations for: German.
Currently translated at 99.8% (1207 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-06-19 13:49:09 +02:00
Stas Haas
74552a4989 🌐 Add translations for: Russian.
Currently translated at 63.1% (763 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2023-06-19 13:49:08 +02:00
K.B.Dharun Krishna
b72b8a6d53 🌐 Add translations for: Tamil.
Currently translated at 4.2% (51 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ta/
2023-06-17 11:51:35 +02:00
Mikel Larreategi
0a74696874 🌐 Add translations for: Basque.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2023-06-17 11:51:35 +02:00
Stas Haas
6548fe069e 🌐 Add translations for: German.
Currently translated at 99.4% (1202 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-06-17 11:51:33 +02:00
Stas Haas
22d852fca8 🌐 Add translations for: German.
Currently translated at 98.6% (1193 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-06-09 03:52:52 +02:00
王世阳
17c2f44780 🌐 Add translations for: Chinese (Simplified).
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2023-06-09 03:52:51 +02:00
Sebastiaan Pasma
40286c81d4 🌐 Add translations for: Dutch.
Currently translated at 11.6% (141 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-06-06 10:49:44 +02:00
Stas Haas
3b262f2ae5 🌐 Add translations for: German.
Currently translated at 97.5% (1179 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-06-04 14:49:45 +02:00
Ņikita K
80dd910d58 🌐 Add translations for: Latvian.
Currently translated at 98.8% (1195 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2023-06-03 02:50:25 +02:00
王世阳
21a066ec64 🌐 Add translations for: Chinese (Simplified).
Currently translated at 99.8% (1207 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2023-06-03 02:50:24 +02:00
andy
29c091a26b 🌐 Added translation for: Dutch. 2023-06-02 17:17:44 +02:00
Ņikita K
b249cd1b72 🌐 Add translations for: Latvian.
Currently translated at 96.5% (1167 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2023-06-01 08:51:33 +02:00
Hosted Weblate
e2a0a40704 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2023-04-24 16:55:45 +02:00
122 changed files with 8094 additions and 1735 deletions

View File

@@ -6,7 +6,6 @@
rumext.v2/defc clojure.core/defn
rumext.v2/fnc clojure.core/fn
app.common.data/export clojure.core/def
app.db/with-atomic clojure.core/with-open
app.common.data.macros/get-in clojure.core/get-in
app.common.data.macros/with-open clojure.core/with-open
app.common.data.macros/select-keys clojure.core/select-keys
@@ -17,6 +16,7 @@
{app.common.data.macros/export hooks.export/export
potok.core/reify hooks.export/potok-reify
app.util.services/defmethod hooks.export/service-defmethod
app.db/with-atomic hooks.export/penpot-with-atomic
}}
:output

View File

@@ -39,6 +39,43 @@
other))]
{:node result})))
(defn penpot-with-atomic
[{:keys [node]}]
(let [[_ params & other] (:children node)
result (if (api/vector-node? params)
(api/list-node
(into [(api/token-node (symbol "clojure.core" "with-open")) params] other))
(api/list-node
(into [(api/token-node (symbol "clojure.core" "with-open"))
(api/vector-node [params params])]
other)))
]
{:node result}))
(defn penpot-defrecord
[{:keys [:node]}]
(let [[rnode rtype rparams & other] (:children node)
nodes [(api/token-node (symbol "do"))
(api/list-node
(into [(api/token-node (symbol (name (:value rnode)))) rtype rparams] other))
(api/list-node
[(api/token-node (symbol "defn"))
(api/token-node (symbol (str "pos->" (:string-value rtype))))
(api/vector-node
(->> (:children rparams)
(mapv (fn [t]
(api/token-node (symbol (str "_" (:string-value t))))))))
(api/token-node nil)])]
result (api/list-node nodes)]
;; (prn "=====>" (into {} rparams))
;; (prn (api/sexpr result))
{:node result}))
(defn clojure-specify
[{:keys [:node]}]
(let [[rnode rtype & other] (:children node)
@@ -48,7 +85,6 @@
other))]
{:node result}))
(defn service-defmethod
[{:keys [:node]}]
(let [[rnode rtype ?meta & other] (:children node)

View File

@@ -1,5 +1,41 @@
# CHANGELOG
## 1.19.3
### :sparkles: New features
- Remember last color mode in colorpicker [Taiga #5508](https://tree.taiga.io/project/penpot/issue/5508)
- Improve layers multiselection behaviour [Github #5741](https://github.com/penpot/penpot/issues/5741)
- Remember last active team across logouts / sessions [Github #3325](https://github.com/penpot/penpot/issues/3325)
### :bug: Bugs fixed
- List view is discarded on tab change on Workspace Assets Sidebar tab [Github #3547](https://github.com/penpot/penpot/issues/3547)
- Fix message popup remains open when exiting workspace with browser back button [Taiga #5747](https://tree.taiga.io/project/penpot/issue/5747)
- When editing text if font is changed, the proportions of the rendered shape are wrong [Taiga #5786](https://tree.taiga.io/project/penpot/issue/5786)
## 1.19.2
### :sparkles: New features
- Navigate up in layer hierarchy with Shift+Enter shortcut [Taiga #5734](https://tree.taiga.io/project/penpot/us/5734)
- Click on the flow tags open viewer with the selected frame [Taiga #5044](https://tree.taiga.io/project/penpot/us/5044)
- Add Dutch language & update translation files with weblate
### :bug: Bugs fixed
- Fix unexpected output on get-page rpc method when invalid object-id is provided [Github #3546](https://github.com/penpot/penpot/issues/3546)
- Fix Invalid files amount after moving file from Project to Drafts [Taiga #5638](https://tree.taiga.io/project/penpot/us/5638)
- Fix deleted pages comments shown in right sidebar [Taiga #5648](https://tree.taiga.io/project/penpot/us/5648)
- Fix tooltip on toggle visibility and toggle lock buttons [Taiga #5141](https://tree.taiga.io/project/penpot/issue/5141)
## 1.19.1
### :bug: Bugs fixed
- Fix components not registered as updated [Taiga #5725](https://tree.taiga.io/project/penpot/issue/5725)
## 1.19.0
### :boom: Breaking changes & Deprecations

View File

@@ -21,8 +21,8 @@
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
funcool/yetti
{:git/tag "v9.15"
:git/sha "aa9b967"
{:git/tag "v9.16"
:git/sha "7df3e08"
:git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]}

View File

@@ -22,11 +22,7 @@
{% endif %}
{% if item.params-schema-js %}
<span class="tag">
<span>SC</span>
</span>
{% else %}
<span class="tag">
<span>SP</span>
<span>SCHEMA</span>
</span>
{% endif %}
</div>

View File

@@ -38,7 +38,7 @@
<h2>GENERAL NOTES</h2>
<h3>Authentication</h3>
<p>The penpot backend right now offerts two way for authenticate the request:
<p>The penpot backend right now offers two way for authenticate the request:
<b>cookies</b> (the same mechanism that we use ourselves on accessing the API from the
web application) and <b>access tokens</b>.</p>

View File

@@ -6,13 +6,19 @@ penpot - error list
{% block content %}
<nav>
<h1>Latest error reports:</h1>
<div class="title">
<h1>Error reports (last 200)</h1>
</div>
</nav>
<main class="horizontal-list">
<ul>
{% for item in items %}
<li><a class="date" href="/dbg/error/{{item.id}}">{{item.created-at}}</a>
<span class="title">{{item.hint|abbreviate:150}}</span></li>
<li>
<a class="date" href="/dbg/error/{{item.id}}">{{item.created-at}}</a>
<a class="hint" href="/dbg/error/{{item.id}}">
<span class="title">{{item.hint|abbreviate:150}}</span>
</a>
</li>
{% endfor %}
</ul>
</main>

View File

@@ -1,13 +1,13 @@
{% extends "app/templates/base.tmpl" %}
{% block title %}
penpot - error report v2 {{id}}
Report: {{hint|abbreviate:150}} - {{id}} - Penpot Error Report (v3)
{% endblock %}
{% block content %}
<nav>
<div>[<a href="/dbg/error">⮜</a>]</div>
<div>[<a href="#message">message</a>]</div>
<div>[<a href="#head">head</a>]</div>
<div>[<a href="#props">props</a>]</div>
<div>[<a href="#context">context</a>]</div>
{% if params %}
@@ -29,10 +29,11 @@ penpot - error report v2 {{id}}
<main>
<div class="table">
<div class="table-row multiline">
<div id="message" class="table-key">MESSAGE: </div>
<div id="head" class="table-key">HEAD</div>
<div class="table-val">
<h1>{{hint}}</h1>
<h1><span class="not-important">Hint:</span> <br/> {{hint}}</h1>
<h2><span class="not-important">Reported at:</span> <br/> {{created-at}}</h2>
<h2><span class="not-important">Report ID:</span> <br/> {{id}}</h2>
</div>
</div>
@@ -71,7 +72,7 @@ penpot - error report v2 {{id}}
{% if value %}
<div class="table-row multiline">
<div id="value" class="table-key">VALIDATION VALUE: </div>
<div id="value" class="table-key">VALUE: </div>
<div class="table-val">
<pre>{{value}}</pre>
</div>

View File

@@ -36,6 +36,11 @@ small {
color: #888;
}
.not-important {
color: #888;
font-weight: 200;
}
small > strong {
font-size: 9px;
}
@@ -50,7 +55,13 @@ nav {
background: #e3e3e3;
}
nav > h1 {
nav > .title {
display: flex;
justify-content: center;
width: 100%;
}
nav > .title > h1 {
padding: 0px;
margin: 0px;
font-size: 11px;
@@ -151,7 +162,6 @@ nav > div:not(:last-child) {
line-height: 18px;
min-width: 210px;
margin: 0px 20px;
cursor: pointer;
display: flex;
border-radius: 3px;
}

View File

@@ -23,7 +23,7 @@
<Logger name="app.rpc.commands.binfile" level="debug" />
<Logger name="app.storage.tmp" level="debug" />
<Logger name="app.worker" level="info" />
<Logger name="app.worker" level="trace" />
<Logger name="app.msgbus" level="info" />
<Logger name="app.http.websocket" level="info" />
<Logger name="app.util.websocket" level="info" />

View File

@@ -15,7 +15,7 @@ export PENPOT_FLAGS="\
enable-fdata-storage-objets-map \
disable-secure-session-cookies \
enable-smtp \
enable-webhooks";
enable-access-tokens";
set -ex

View File

@@ -8,14 +8,13 @@
(:require
[app.config :as cf]
[buddy.hashers :as hashers]
[cuerdas.core :as str]
[promesa.exec :as px]))
[cuerdas.core :as str]))
(def default-params
{:alg :argon2id
:memory (* 32768 2) ;; 64 MiB
:iterations 7
:parallelism (px/get-available-processors)})
:memory 32768 ;; 32 MiB
:iterations 3
:parallelism 2})
(defn derive-password
[password]

View File

@@ -391,13 +391,14 @@
(defn- get-user-info
[{:keys [provider]} tdata]
(try
(let [{:keys [kid alg] :as theader} (jwt/decode-header (:token/id tdata))]
(when-let [key (if (str/starts-with? (name alg) "hs")
(:client-secret provider)
(get-in provider [:jwks kid]))]
(when (:token/id tdata)
(let [{:keys [kid alg] :as theader} (jwt/decode-header (:token/id tdata))]
(when-let [key (if (str/starts-with? (name alg) "hs")
(:client-secret provider)
(get-in provider [:jwks kid]))]
(let [claims (jwt/unsign (:token/id tdata) key {:alg alg})]
(dissoc claims :exp :iss :iat :sid :aud :sub))))
(let [claims (jwt/unsign (:token/id tdata) key {:alg alg})]
(dissoc claims :exp :iss :iat :sid :aud :sub)))))
(catch Throwable cause
(l/warn :hint "unable to get user info from JWT token (unexpected exception)"
:cause cause))))
@@ -566,7 +567,7 @@
profile (get-profile cfg info)]
(generate-redirect cfg request info profile))
(catch Throwable cause
(l/error :hint "error on oauth process" :cause cause)
(l/warn :hint "error on oauth process" :cause cause)
(generate-error-redirect cfg cause))))
(def provider-lookup

View File

@@ -5,7 +5,7 @@
;; Copyright (c) KALEIDOS INC
(ns app.db
(:refer-clojure :exclude [get])
(:refer-clojure :exclude [get run!])
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
@@ -218,7 +218,13 @@
(defmacro with-atomic
[& args]
`(jdbc/with-transaction ~@args))
(if (symbol? (first args))
(let [cfgs (first args)
body (rest args)]
`(jdbc/with-transaction [conn# (::pool ~cfgs)]
(let [~cfgs (assoc ~cfgs ::conn conn#)]
~@body)))
`(jdbc/with-transaction ~@args)))
(defn open
[pool]
@@ -293,6 +299,10 @@
:hint "database object not found"))
row))
(defn plan
[ds sql]
(jdbc/plan ds sql sql/default-opts))
(defn get-by-id
[ds table id & {:as opts}]
(get ds table {:id id} opts))
@@ -381,6 +391,52 @@
([^Connection conn ^Savepoint sp]
(.rollback conn sp)))
(defn tx-run!
[cfg f]
(cond
(connection? cfg)
(tx-run! {::conn cfg} f)
(pool? cfg)
(tx-run! {::pool cfg} f)
(::conn cfg)
(let [conn (::conn cfg)
sp (savepoint conn)]
(try
(let [result (f cfg)]
(release! conn sp)
result)
(catch Throwable cause
(rollback! sp)
(throw cause))))
(::pool cfg)
(with-atomic [conn (::pool cfg)]
(f (assoc cfg ::conn conn)))
:else
(throw (IllegalArgumentException. "invalid arguments"))))
(defn run!
[cfg f]
(cond
(connection? cfg)
(run! {::conn cfg} f)
(pool? cfg)
(run! {::pool cfg} f)
(::conn cfg)
(f cfg)
(::pool cfg)
(with-open [^Connection conn (open (::pool cfg))]
(f (assoc cfg ::conn conn)))
:else
(throw (IllegalArgumentException. "invalid arguments"))))
(defn interval
[o]
(cond

View File

@@ -238,9 +238,11 @@
(-> (io/resource "app/templates/error-report.v2.tmpl")
(tmpl/render report)))
(render-template-v3 [{report :content}]
(render-template-v3 [{:keys [content id created-at]}]
(-> (io/resource "app/templates/error-report.v3.tmpl")
(tmpl/render report)))
(tmpl/render (-> content
(assoc :id id)
(assoc :created-at (dt/format-instant created-at :rfc1123))))))
]
(when-not (authorized? pool request)
@@ -264,7 +266,7 @@
content->>'~:hint' AS hint
FROM server_error_report
ORDER BY created_at DESC
LIMIT 100")
LIMIT 200")
(defn error-list-handler
[{:keys [::db/pool]} request]

View File

@@ -10,6 +10,7 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.config :as cf]
[app.http :as-alias http]
[app.http.access-token :as-alias actoken]
[app.http.session :as-alias session]
@@ -30,14 +31,14 @@
(let [claims (-> {}
(into (::session/token-claims request))
(into (::actoken/token-claims request)))]
{:path (:path request)
:method (:method request)
:params (:params request)
:ip-addr (parse-client-ip request)
:user-agent (yrq/get-header request "user-agent")
:profile-id (:uid claims)
:version (or (yrq/get-header request "x-frontend-version")
"unknown")}))
{:request/path (:path request)
:request/method (:method request)
:request/params (:params request)
:request/user-agent (yrq/get-header request "user-agent")
:request/ip-addr (parse-client-ip request)
:request/profile-id (:uid claims)
:version/frontend (or (yrq/get-header request "x-frontend-version") "unknown")
:version/backend (:full cf/version)}))
(defmulti handle-exception
(fn [err & _rest]
@@ -73,14 +74,14 @@
::yrs/headers headers}))
(defmethod handle-exception :validation
[err _]
[err request]
(let [{:keys [code] :as data} (ex-data err)]
(cond
(= code :spec-validation)
(let [explain (ex/explain data)]
{::yrs/status 400
::yrs/body (-> data
(dissoc ::s/problems ::s/value)
(dissoc ::s/problems ::s/value ::s/spec)
(cond-> explain (assoc :explain explain)))})
(= code :params-validation)
@@ -94,6 +95,11 @@
(= code :request-body-too-large)
{::yrs/status 413 ::yrs/body data}
(= code :invalid-image)
(binding [l/*context* (request->context request)]
(l/error :hint "unexpected error on processing image" :cause err)
{::yrs/status 400 ::yrs/body data})
:else
{::yrs/status 400 ::yrs/body data})))

View File

@@ -22,9 +22,10 @@
(:import
com.fasterxml.jackson.core.JsonParseException
com.fasterxml.jackson.core.io.JsonEOFException
com.fasterxml.jackson.databind.exc.MismatchedInputException
io.undertow.server.RequestTooBigException
java.io.OutputStream
java.io.InputStream))
java.io.InputStream
java.io.OutputStream))
(set! *warn-on-reflection* true)
@@ -78,11 +79,13 @@
(or (instance? JsonEOFException cause)
(instance? JsonParseException cause))
(instance? JsonParseException cause)
(instance? MismatchedInputException cause))
(raise (ex/error :type :validation
:code :malformed-json
:hint (ex-message cause)
:cause cause))
:else
(raise cause)))]
@@ -118,7 +121,9 @@
(t/write! tw data)))
(catch java.io.IOException _)
(catch Throwable cause
(l/error :hint "unexpected error on encoding response" :cause cause))
(binding [l/*context* {:value data}]
(l/error :hint "unexpected error on encoding response"
:cause cause)))
(finally
(.close ^OutputStream output-stream))))))
@@ -131,8 +136,9 @@
(catch java.io.IOException _)
(catch Throwable cause
(l/error :hint "unexpected error on encoding response"
:cause cause))
(binding [l/*context* {:value data}]
(l/error :hint "unexpected error on encoding response"
:cause cause)))
(finally
(.close ^OutputStream output-stream))))))

View File

@@ -11,6 +11,7 @@
[app.common.logging :as l]
[app.common.pprint :as pp]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.db :as db]
[app.http.session :as session]
[app.metrics :as mtx]
@@ -99,7 +100,10 @@
(sp/pipe ch output-ch false)
;; Subscribe to the profile topic on msgbus/redis
(mbus/sub! msgbus :topic profile-id :chan ch)))
(mbus/sub! msgbus :topic profile-id :chan ch)
;; Subscribe to the system topic on msgbus/redis
(mbus/sub! msgbus :topic (str uuid/zero) :chan ch)))
(defmethod handle-message :close
[{:keys [::mbus/msgbus]} {:keys [::ws/id ::ws/state ::profile-id ::session-id]} _]

View File

@@ -40,35 +40,33 @@
[{:keys [::l/context ::l/message ::l/props ::l/logger ::l/level ::l/cause] :as record}]
(us/assert! ::l/record record)
(let [data (ex-data cause)]
(let [data (ex-data cause)
ctx (-> context
(assoc :tenant (cf/get :tenant))
(assoc :host (cf/get :host))
(assoc :public-uri (cf/get :public-uri))
(assoc :logger/name logger)
(assoc :logger/level level)
(dissoc :request/params :value :params :data))]
(merge
{:context (-> context
(assoc :tenant (cf/get :tenant))
(assoc :host (cf/get :host))
(assoc :public-uri (cf/get :public-uri))
(assoc :version (:full cf/version))
(assoc :logger-name logger)
(assoc :logger-level level)
(dissoc :params)
(pp/pprint-str :width 200))
:props (pp/pprint-str props :width 200)
{:context (-> (into (sorted-map) ctx)
(pp/pprint-str :width 200 :length 50 :level 10))
:props (pp/pprint-str props :width 200 :length 50)
:hint (or (ex-message cause) @message)
:trace (ex/format-throwable cause :data? false :explain? false :header? false :summary? false)}
(when-let [params (:params context)]
{:params (pp/pprint-str params :width 200)})
(when-let [params (or (:request/params context) (:params context))]
{:params (pp/pprint-str params :width 200 :length 50 :level 10)})
(when-let [value (:value context)]
{:value (pp/pprint-str value :width 200 :length 50 :level 10)})
(when-let [data (some-> data (dissoc ::s/problems ::s/value ::s/spec ::sm/explain :hint))]
{:data (pp/pprint-str data :width 200)})
(when-let [value (-> data ::sm/explain :value)]
{:value (pp/pprint-str value :width 200)})
(when-let [explain (ex/explain data)]
(when-let [explain (ex/explain data {:level 10 :length 50})]
{:explain explain}))))
(defn error-record?
[{:keys [::l/level ::l/cause]}]
(and (= :error level)

View File

@@ -30,7 +30,9 @@
"```\n"
"- host: `" (:host report) "`\n"
"- tenant: `" (:tenant report) "`\n"
"- version: `" (:version report) "`\n"
"- request-path: `" (:request-path report) "`\n"
"- frontend-version: `" (:frontend-version report) "`\n"
"- backend-version: `" (:backend-version report) "`\n"
"\n"
"Trace:\n"
(:trace report)
@@ -50,13 +52,15 @@
(defn record->report
[{:keys [::l/context ::l/id ::l/cause] :as record}]
(us/assert! ::l/record record)
{:id id
:tenant (cf/get :tenant)
:host (cf/get :host)
:public-uri (cf/get :public-uri)
:version (:full cf/version)
:profile-id (:profile-id context)
:trace (ex/format-throwable cause :detail? false :header? false)})
{:id id
:tenant (cf/get :tenant)
:host (cf/get :host)
:public-uri (cf/get :public-uri)
:backend-version (or (:version/backend context) (:full cf/version))
:frontend-version (:version/frontend context)
:profile-id (:request/profile-id context)
:request-path (:request/path context)
:trace (ex/format-throwable cause :detail? false :header? false)})
(defn handle-event
[cfg record]

View File

@@ -9,7 +9,6 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.media :as cm]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
@@ -227,7 +226,6 @@
(defmethod process-error org.im4java.core.InfoException
[error]
(l/error :hint "unexpected error on processing image" :cause error)
(ex/raise :type :validation
:code :invalid-image
:hint "invalid image"

View File

@@ -324,6 +324,9 @@
{:name "0104-mod-file-thumbnail-table"
:fn (mg/resource "app/migrations/sql/0104-mod-file-thumbnail-table.sql")}
{:name "0105-mod-server-error-report-table"
:fn (mg/resource "app/migrations/sql/0105-mod-server-error-report-table.sql")}
])
(defn apply-migrations!

View File

@@ -0,0 +1,2 @@
CREATE INDEX server_error_report__created_at__idx
ON server_error_report ( created_at );

View File

@@ -68,6 +68,7 @@
::climit/key-fn ::rpc/profile-id
::sm/params schema:push-audit-events
::audit/skip true
::doc/skip true
::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} params]
(if (or (db/read-only? pool)

View File

@@ -294,28 +294,40 @@
[output & {:keys [level] :or {level 0}}]
(ZstdOutputStream. ^OutputStream output (int level)))
(defn- retrieve-file
[pool file-id]
(dm/with-open [conn (db/open pool)]
(binding [pmap/*load-fn* (partial files/load-pointer conn file-id)]
(some-> (db/get* conn :file {:id file-id})
(files/decode-row)
(files/process-pointers deref)))))
(defn- get-files
[cfg ids]
(letfn [(get-files* [{:keys [::db/conn]}]
(let [sql (str "SELECT id FROM file "
" WHERE id = ANY(?) ")
ids (db/create-array conn "uuid" ids)]
(->> (db/exec! conn [sql ids])
(into [] (map :id))
(not-empty))))]
(def ^:private sql:file-media-objects
"SELECT * FROM file_media_object WHERE id = ANY(?)")
(db/run! cfg get-files*)))
(defn- retrieve-file-media
[pool {:keys [data id] :as file}]
(defn- get-file
[cfg file-id]
(letfn [(get-file* [{:keys [::db/conn]}]
(binding [pmap/*load-fn* (partial files/load-pointer conn file-id)]
(some-> (db/get* conn :file {:id file-id} {::db/remove-deleted? false})
(files/decode-row)
(files/process-pointers deref))))]
(db/run! cfg get-file*)))
(defn- get-file-media
[{:keys [::db/pool]} {:keys [data id] :as file}]
(dm/with-open [conn (db/open pool)]
(let [ids (app.tasks.file-gc/collect-used-media data)
ids (db/create-array conn "uuid" ids)]
ids (db/create-array conn "uuid" ids)
sql (str "SELECT * FROM file_media_object WHERE id = ANY(?)")]
;; We assoc the file-id again to the file-media-object row
;; because there are cases that used objects refer to other
;; files and we need to ensure in the exportation process that
;; all ids matches
(->> (db/exec! conn [sql:file-media-objects ids])
(->> (db/exec! conn [sql ids])
(mapv #(assoc % :file-id id))))))
(def ^:private storage-object-id-xf
@@ -325,34 +337,32 @@
(def ^:private sql:file-libraries
"WITH RECURSIVE libs AS (
SELECT fl.id, fl.deleted_at
SELECT fl.id
FROM file AS fl
JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id)
WHERE flr.file_id = ANY(?)
UNION
SELECT fl.id, fl.deleted_at
SELECT fl.id
FROM file AS fl
JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id)
JOIN libs AS l ON (flr.file_id = l.id)
)
SELECT DISTINCT l.id
FROM libs AS l
WHERE l.deleted_at IS NULL OR l.deleted_at > now();")
FROM libs AS l")
(defn- retrieve-libraries
[pool ids]
(defn- get-libraries
[{:keys [::db/pool]} ids]
(dm/with-open [conn (db/open pool)]
(let [ids (db/create-array conn "uuid" ids)]
(map :id (db/exec! pool [sql:file-libraries ids])))))
(def ^:private sql:file-library-rels
"SELECT * FROM file_library_rel
WHERE file_id = ANY(?)")
(defn- retrieve-library-relations
[pool ids]
(dm/with-open [conn (db/open pool)]
(db/exec! conn [sql:file-library-rels (db/create-array conn "uuid" ids)])))
(defn- get-library-relations
[cfg ids]
(db/run! cfg (fn [{:keys [::db/conn]}]
(let [ids (db/create-array conn "uuid" ids)
sql (str "SELECT flr.* FROM file_library_rel AS flr "
" WHERE flr.file_id = ANY(?)")]
(db/exec! conn [sql ids])))))
(defn- create-or-update-file
[conn params]
@@ -372,13 +382,13 @@
;; --- GENERAL PURPOSE DYNAMIC VARS
(def ^:dynamic *state*)
(def ^:dynamic *options*)
(def ^:dynamic *state* nil)
(def ^:dynamic *options* nil)
;; --- EXPORT WRITER
(defn- embed-file-assets
[data conn file-id]
[data cfg file-id]
(letfn [(walk-map-form [form state]
(cond
(uuid? (:fill-color-ref-file form))
@@ -408,7 +418,7 @@
;; NOTE: there is a possibility that shape refers to an
;; non-existant file because the file was removed. In this
;; case we just ignore the asset.
(if-let [lib (retrieve-file conn lib-id)]
(if-let [lib (get-file cfg lib-id)]
(reduce (partial process-asset lib) data items)
data))
@@ -476,31 +486,39 @@
[:v1/metadata :v1/files :v1/rels :v1/sobjects])))))
(defmethod write-section :v1/metadata
[{:keys [::db/pool ::output ::file-ids ::include-libraries?]}]
(let [libs (when include-libraries?
(retrieve-libraries pool file-ids))
files (into file-ids libs)]
(write-obj! output {:version cf/version :files files})
(vswap! *state* assoc :files files)))
[{:keys [::output ::file-ids ::include-libraries?] :as cfg}]
(if-let [fids (get-files cfg file-ids)]
(let [lids (when include-libraries?
(get-libraries cfg file-ids))
ids (into fids lids)]
(write-obj! output {:version cf/version :files ids})
(vswap! *state* assoc :files ids))
(ex/raise :type :not-found
:code :files-not-found
:hint "unable to retrieve files for export")))
(defmethod write-section :v1/files
[{:keys [::db/pool ::output ::embed-assets?]}]
[{:keys [::output ::embed-assets?] :as cfg}]
;; Initialize SIDS with empty vector
(vswap! *state* assoc :sids [])
(doseq [file-id (-> *state* deref :files)]
(let [file (cond-> (retrieve-file pool file-id)
(let [file (cond-> (get-file cfg file-id)
embed-assets?
(update :data embed-file-assets pool file-id))
(update :data embed-file-assets cfg file-id))
media (retrieve-file-media pool file)]
media (get-file-media cfg file)]
(l/debug :hint "write penpot file"
:id file-id
:name (:name file)
:media (count media)
::l/sync? true)
(doseq [item media]
(l/debug :hint "write penpot file media object" :id (:id item) ::l/sync? true))
(doto output
(write-obj! file)
(write-obj! media))
@@ -508,9 +526,10 @@
(vswap! *state* update :sids into storage-object-id-xf media))))
(defmethod write-section :v1/rels
[{:keys [::db/pool ::output ::include-libraries?]}]
(let [rels (when include-libraries?
(retrieve-library-relations pool (-> *state* deref :files)))]
[{:keys [::output ::include-libraries?] :as cfg}]
(let [ids (-> *state* deref :files)
rels (when include-libraries?
(get-library-relations cfg ids))]
(l/debug :hint "found rels" :total (count rels) ::l/sync? true)
(write-obj! output rels)))
@@ -518,6 +537,7 @@
[{:keys [::sto/storage ::output]}]
(let [sids (-> *state* deref :sids)
storage (media/configure-assets-storage storage)]
(l/debug :hint "found sobjects"
:items (count sids)
::l/sync? true)
@@ -630,6 +650,8 @@
(when (not= file-id expected-file-id)
(ex/raise :type :validation
:code :inconsistent-penpot-file
:found-id file-id
:expected-id expected-file-id
:hint "the penpot file seems corrupt, found unexpected uuid (file-id)"))
;; Update index using with media
@@ -679,18 +701,27 @@
(defmethod read-section :v1/rels
[{:keys [::db/conn ::input ::timestamp]}]
(let [rels (read-obj! input)]
(let [rels (read-obj! input)
ids (into #{} (-> *state* deref :files))]
;; Insert all file relations
(doseq [rel rels]
(doseq [{:keys [library-file-id] :as rel} rels]
(let [rel (-> rel
(assoc :synced-at timestamp)
(update :file-id lookup-index)
(update :library-file-id lookup-index))]
(l/debug :hint "create file library link"
:file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/sync? true)
(db/insert! conn :file-library-rel rel)))))
(if (contains? ids library-file-id)
(do
(l/debug :hint "create file library link"
:file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/sync? true)
(db/insert! conn :file-library-rel rel))
(l/warn :hint "ignoring file library link"
:file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/sync? true))))))
(defmethod read-section :v1/sobjects
[{:keys [::sto/storage ::db/conn ::input ::overwrite?]}]
@@ -742,7 +773,7 @@
(defn- lookup-index
[id]
(let [val (get-in @*state* [:index id])]
(l/trace :fn "lookup-index" :id id :val val ::l/sync? true)
(l/trc :fn "lookup-index" :id id :val val ::l/sync? true)
(when (and (not (::ignore-index-errors? *options*)) (not val))
(ex/raise :type :validation
:code :incomplete-index
@@ -755,7 +786,7 @@
index index]
(if-let [id (first items)]
(let [new-id (if (::overwrite? *options*) id (uuid/next))]
(l/trace :fn "update-index" :id id :new-id new-id ::l/sync? true)
(l/debug :fn "update-index" :id id :new-id new-id ::l/sync? true)
(recur (rest items)
(assoc index id new-id)))
index)))
@@ -773,8 +804,7 @@
(update-in [:metadata :id] lookup-index)
;; Relink paths with fill image
(and (map? (:fill-image form))
(= :path (:type form)))
(map? (:fill-image form))
(update-in [:fill-image :id] lookup-index)
;; This covers old shapes and the new :fills.

View File

@@ -498,7 +498,8 @@
other not needed objects removed from the `:objects` data
structure."
[{:keys [objects] :as page} object-id]
(let [objects (cph/get-children-with-self objects object-id)]
(let [objects (->> (cph/get-children-with-self objects object-id)
(filter some?))]
(assoc page :objects (d/index-by :id objects))))
(defn- prune-thumbnails

View File

@@ -67,6 +67,7 @@
(sv/defmethod ::get-file-object-thumbnails
"Retrieve a file object thumbnails."
{::doc/added "1.17"
::doc/module :files
::sm/params [:map {:title "get-file-object-thumbnails"}
[:file-id ::sm/uuid]]
::sm/result [:map-of :string :string]
@@ -112,6 +113,7 @@
(sv/defmethod ::get-file-thumbnail
{::doc/added "1.17"
::doc/module :files
::doc/deprecated "1.19"}
[{:keys [::db/pool]} {:keys [::rpc/profile-id file-id revn]}]
(dm/with-open [conn (db/open pool)]
@@ -220,6 +222,7 @@
mainly for render thumbnails on dashboard."
{::doc/added "1.17"
::doc/module :files
::sm/params [:map {:title "get-file-data-for-thumbnail"}
[:file-id ::sm/uuid]
[:features {:optional true} ::files/features]]
@@ -267,6 +270,7 @@
(sv/defmethod ::upsert-file-object-thumbnail
{::doc/added "1.17"
::doc/module :files
::doc/deprecated "1.19"
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
@@ -312,6 +316,7 @@
(sv/defmethod ::create-file-object-thumbnail
{:doc/added "1.19"
::doc/module :files
::audit/skip true
::sm/params schema:create-file-object-thumbnail}
@@ -350,6 +355,7 @@
(sv/defmethod ::delete-file-object-thumbnail
{:doc/added "1.19"
::doc/module :files
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id object-id]}]
@@ -388,6 +394,7 @@
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.17"
::doc/module :files
::doc/deprecated "1.19"
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
@@ -430,6 +437,7 @@
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.19"
::doc/module :files
::audit/skip true
::sm/params [:map {:title "create-file-thumbnail"}
[:file-id ::sm/uuid]

View File

@@ -38,7 +38,8 @@
"Performs the authentication using LDAP backend. Only works if LDAP
is properly configured and enabled with `login-with-ldap` flag."
{::rpc/auth false
::doc/added "1.15"}
::doc/added "1.15"
::doc/module :auth}
[{:keys [::main/props ::ldap/provider] :as cfg} params]
(when-not provider
(ex/raise :type :restriction

View File

@@ -171,7 +171,8 @@
:opt-un [::id ::name]))
(sv/defmethod ::create-file-media-object-from-url
{::doc/added "1.17"}
{::doc/added "1.17"
::doc/deprecated "1.19"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(let [cfg (update cfg ::sto/storage media/configure-assets-storage)]
(files/check-edition-permissions! pool profile-id file-id)

View File

@@ -64,6 +64,7 @@
:opt-un [::search-term]))
(sv/defmethod ::search-files
{::doc/added "1.17"}
{::doc/added "1.17"
::doc/module :files}
[{:keys [::db/pool]} {:keys [::rpc/profile-id team-id search-term]}]
(some->> search-term (search-files pool profile-id team-id)))

View File

@@ -34,7 +34,8 @@
(sv/defmethod ::verify-token
{::rpc/auth false
::doc/added "1.15"}
::doc/added "1.15"
::doc/module :auth}
[{:keys [::db/pool] :as cfg} {:keys [token] :as params}]
(db/with-atomic [conn pool]
(let [claims (tokens/verify (::main/props cfg) {:token token})

View File

@@ -54,14 +54,14 @@
{:name (::sv/name mdata)
:module (or (some-> (::module mdata) d/name)
(-> (:ns mdata) (str/split ".") last))
:auth (:auth mdata true)
:auth (::rpc/auth mdata true)
:webhook (::webhooks/event? mdata false)
:docs (::sv/docstring mdata)
:deprecated (::deprecated mdata)
:added (::added mdata)
:changes (some->> (::changes mdata) (partition-all 2) (map vec))
:spec (fmt-spec mdata)
:entrypoint (str (cf/get :public-uri) "/api/rpc/commands/" (::sv/name mdata))
:entrypoint (str (cf/get :public-uri) "/api/rpc/command/" (::sv/name mdata))
:params-schema-js (fmt-schema :js mdata ::sm/params)
:result-schema-js (fmt-schema :js mdata ::sm/result)
@@ -75,6 +75,7 @@
(->> methods
(map val)
(map first)
(remove ::skip)
(map get-context)
(sort-by (juxt :module :name)))}))
@@ -155,7 +156,7 @@
(map (partial gen-method-doc options))
(sort-by (juxt :module :name))
(map (fn [doc]
[(str/ffmt "/commands/%" (:name doc)) (:repr doc)]))
[(str/ffmt "/command/%" (:name doc)) (:repr doc)]))
(into {})))]
{:openapi "3.0.0"
:info {:version (:main cf/version)}

View File

@@ -33,7 +33,7 @@
[cuerdas.core :as str]
[expound.alpha :as expound]))
(def ^:dynamic *conn*)
(def ^:dynamic *conn* nil)
(defn reset-password!
"Reset a password to a specific one for a concrete user or all users

View File

@@ -8,10 +8,15 @@
"A collection of adhoc fixes scripts."
#_:clj-kondo/ignore
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.logging :as l]
[app.common.pprint :as p]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.msgbus :as mbus]
[app.rpc.commands.auth :as auth]
[app.rpc.commands.profile :as profile]
[app.srepl.fixes :as f]
@@ -164,3 +169,106 @@
(alter-var-root var (fn [f]
(or (::original (meta f)) f))))
(defn notify!
[{:keys [::mbus/msgbus ::db/pool]} & {:keys [dest code message level]
:or {code :generic level :info}
:as params}]
(dm/verify!
["invalid level %" level]
(contains? #{:success :error :info :warning} level))
(dm/verify!
["invalid code: %" code]
(contains? #{:generic :upgrade-version} code))
(letfn [(send [dest]
(l/inf :hint "sending notification" :dest (str dest))
(let [message {:type :notification
:code code
:level level
:version (:full cf/version)
:subs-id dest
:message message}
message (->> (dissoc params :dest :code :message :level)
(merge message))]
(mbus/pub! msgbus
:topic (str dest)
:message message)))
(resolve-profile [email]
(some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector))
(resolve-team [team-id]
(->> (db/query pool :team-profile-rel
{:team-id team-id}
{:columns [:profile-id]})
(map :profile-id)))
(parse-uuid [v]
(if (uuid? v)
v
(d/parse-uuid v)))
(resolve-dest [dest]
(cond
(uuid? dest)
[dest]
(string? dest)
(some-> dest parse-uuid resolve-dest)
(nil? dest)
(resolve-dest uuid/zero)
(map? dest)
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(and (coll? dest)
(every? coll? dest))
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(vector? dest)
(let [[op param] dest]
(cond
(= op :email)
(cond
(and (coll? param)
(every? string? param))
(sequence (comp
(keep resolve-profile)
(mapcat identity))
param)
(string? param)
(resolve-profile param))
(= op :team-id)
(cond
(coll? param)
(sequence (comp
(mapcat resolve-team)
(keep parse-uuid))
param)
(uuid? param)
(resolve-team param)
(string? param)
(some-> param parse-uuid resolve-team))
(= op :profile-id)
(if (coll? param)
(sequence (keep parse-uuid) param)
(resolve-dest param))))))
]
(->> (resolve-dest dest)
(filter some?)
(into #{})
(run! send))))

View File

@@ -251,53 +251,59 @@
(defmethod ig/init-key ::gc-deleted-task
[_ {:keys [::db/pool ::storage ::min-age]}]
(letfn [(retrieve-deleted-objects-chunk [conn min-age cursor]
(let [min-age (db/interval min-age)
rows (db/exec! conn [sql:retrieve-deleted-objects-chunk min-age cursor])]
[(some-> rows peek :created-at)
(letfn [(get-to-delete-chunk [cursor]
(let [sql (str "select s.* "
" from storage_object as s "
" where s.deleted_at is not null "
" and s.deleted_at < ? "
" order by s.deleted_at desc "
" limit 25")
rows (db/exec! pool [sql cursor])]
[(some-> rows peek :deleted-at)
(some->> (seq rows) (d/group-by #(-> % :backend keyword) :id #{}) seq)]))
(retrieve-deleted-objects [conn min-age]
(d/iteration (partial retrieve-deleted-objects-chunk conn min-age)
:initk (dt/now)
(get-to-delete-chunks [min-age]
(d/iteration get-to-delete-chunk
:initk (dt/minus (dt/now) min-age)
:vf second
:kf first))
(delete-in-bulk [backend-id ids]
(let [backend (impl/resolve-backend storage backend-id)]
(delete-in-bulk! [backend-id ids]
(try
(db/with-atomic [conn pool]
(let [sql "delete from storage_object where id = ANY(?)"
ids' (db/create-array conn "uuid" ids)
(doseq [id ids]
(l/debug :hint "gc-deleted: permanently delete storage object" :backend backend-id :id id))
total (-> (db/exec-one! conn [sql ids'])
(db/get-update-count))]
(impl/del-objects-in-bulk backend ids)))]
(-> (impl/resolve-backend storage backend-id)
(impl/del-objects-in-bulk ids))
(doseq [id ids]
(l/dbg :hint "gc-deleted: permanently delete storage object" :backend backend-id :id id))
total))
(catch Throwable cause
(l/err :hint "gc-deleted: unexpected error on bulk deletion"
:ids (vec ids)
:cause cause)
0)))]
(fn [params]
(let [min-age (or (:min-age params) min-age)]
(db/with-atomic [conn pool]
(loop [total 0
groups (retrieve-deleted-objects conn min-age)]
(if-let [[backend-id ids] (first groups)]
(do
(delete-in-bulk backend-id ids)
(recur (+ total (count ids))
(rest groups)))
(do
(l/info :hint "gc-deleted: task finished" :min-age (dt/format-duration min-age) :total total)
{:deleted total}))))))))
(def sql:retrieve-deleted-objects-chunk
"with items_part as (
select s.id
from storage_object as s
where s.deleted_at is not null
and s.deleted_at < (now() - ?::interval)
and s.created_at < ?
order by s.created_at desc
limit 25
)
delete from storage_object
where id in (select id from items_part)
returning *;")
(let [min-age (or (some-> params :min-age dt/duration) min-age)]
(loop [total 0
chunks (get-to-delete-chunks min-age)]
(if-let [[backend-id ids] (first chunks)]
(let [deleted (delete-in-bulk! backend-id ids)]
(recur (+ total deleted)
(rest chunks)))
(do
(l/inf :hint "gc-deleted: task finished"
:min-age (dt/format-duration min-age)
:total total)
{:deleted total})))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Garbage Collection: Analyze touched objects

View File

@@ -113,8 +113,15 @@
(mapcat vals)
(keep (fn [{:keys [type] :as obj}]
(case type
:path (get-in obj [:fill-image :id])
:path (get-in obj [:fill-image :id])
:bool (get-in obj [:fill-image :id])
;; NOTE: because of some bug, we ended with
;; many shape types having the ability to
;; have fill-image attribute (which initially
;; designed for :path shapes).
:group (get-in obj [:fill-image :id])
:image (get-in obj [:metadata :id])
nil))))
pages (concat
(vals (:pages-index data))

View File

@@ -87,10 +87,10 @@
(defmethod ig/init-key ::registry
[_ {:keys [::mtx/metrics ::tasks]}]
(l/info :hint "registry initialized" :tasks (count tasks))
(l/inf :hint "registry initialized" :tasks (count tasks))
(reduce-kv (fn [registry k v]
(let [tname (name k)]
(l/trace :hint "register task" :name tname)
(l/trc :hint "register task" :name tname)
(assoc registry tname (wrap-task-handler metrics tname v))))
{}
tasks))
@@ -141,18 +141,18 @@
(px/thread
{:name "penpot/executors-monitor" :virtual true}
(l/info :hint "monitor: started" :name name)
(l/inf :hint "monitor: started" :name name)
(try
(loop [steals 0]
(when-not (px/shutdown? executor)
(px/sleep interval)
(recur (long (monitor! executor steals)))))
(catch InterruptedException _cause
(l/debug :hint "monitor: interrupted" :name name))
(l/trc :hint "monitor: interrupted" :name name))
(catch Throwable cause
(l/error :hint "monitor: unexpected error" :name name :cause cause))
(l/err :hint "monitor: unexpected error" :name name :cause cause))
(finally
(l/info :hint "monitor: terminated" :name name))))))
(l/inf :hint "monitor: terminated" :name name))))))
(defmethod ig/halt-key! ::monitor
[_ thread]
@@ -207,10 +207,10 @@
(db/create-array conn "uuid" ids)]]
(db/exec-one! conn sql)
(l/debug :hist "dispatcher: queue tasks"
:queue queue
:tasks (count ids)
:queued res)))
(l/trc :hist "dispatcher: queue tasks"
:queue queue
:tasks (count ids)
:queued res)))
(run-batch! [rconn]
(try
@@ -225,35 +225,35 @@
(cond
(rds/exception? cause)
(do
(l/warn :hint "dispatcher: redis exception (will retry in an instant)" :cause cause)
(l/wrn :hint "dispatcher: redis exception (will retry in an instant)" :cause cause)
(px/sleep (::rds/timeout rconn)))
(db/sql-exception? cause)
(do
(l/warn :hint "dispatcher: database exception (will retry in an instant)" :cause cause)
(l/wrn :hint "dispatcher: database exception (will retry in an instant)" :cause cause)
(px/sleep (::rds/timeout rconn)))
:else
(do
(l/error :hint "dispatcher: unhandled exception (will retry in an instant)" :cause cause)
(l/err :hint "dispatcher: unhandled exception (will retry in an instant)" :cause cause)
(px/sleep (::rds/timeout rconn)))))))
(dispatcher []
(l/info :hint "dispatcher: started")
(l/inf :hint "dispatcher: started")
(try
(dm/with-open [rconn (rds/connect redis)]
(loop []
(run-batch! rconn)
(recur)))
(catch InterruptedException _
(l/trace :hint "dispatcher: interrupted"))
(l/trc :hint "dispatcher: interrupted"))
(catch Throwable cause
(l/error :hint "dispatcher: unexpected exception" :cause cause))
(l/err :hint "dispatcher: unexpected exception" :cause cause))
(finally
(l/info :hint "dispatcher: terminated"))))]
(l/inf :hint "dispatcher: terminated"))))]
(if (db/read-only? pool)
(l/warn :hint "dispatcher: not started (db is read-only)")
(l/wrn :hint "dispatcher: not started (db is read-only)")
(px/fn->thread dispatcher :name "penpot/worker/dispatcher" :virtual true))))
(defmethod ig/halt-key! ::dispatcher
@@ -286,7 +286,7 @@
(let [queue (d/name queue)
cfg (assoc cfg ::queue queue)]
(if (db/read-only? pool)
(l/warn :hint "worker: not started (db is read-only)" :queue queue :parallelism parallelism)
(l/wrn :hint "worker: not started (db is read-only)" :queue queue :parallelism parallelism)
(doall
(->> (range parallelism)
(map #(assoc cfg ::worker-id %))
@@ -300,7 +300,7 @@
[{:keys [::rds/redis ::worker-id ::queue] :as cfg}]
(px/thread
{:name (format "penpot/worker/runner:%s" worker-id)}
(l/info :hint "worker: started" :worker-id worker-id :queue queue)
(l/inf :hint "worker: started" :worker-id worker-id :queue queue)
(try
(dm/with-open [rconn (rds/connect redis)]
(let [tenant (cf/get :tenant "main")
@@ -320,14 +320,14 @@
:worker-id worker-id
:queue queue))
(catch Throwable cause
(l/error :hint "worker: unexpected exception"
:worker-id worker-id
:queue queue
:cause cause))
(l/err :hint "worker: unexpected exception"
:worker-id worker-id
:queue queue
:cause cause))
(finally
(l/info :hint "worker: terminated"
:worker-id worker-id
:queue queue)))))
(l/inf :hint "worker: terminated"
:worker-id worker-id
:queue queue)))))
(defn- run-worker-loop!
[{:keys [::db/pool ::rds/rconn ::timeout ::queue ::registry ::worker-id]}]
@@ -368,19 +368,19 @@
(let [task-id (t/decode payload)]
(if (uuid? task-id)
task-id
(l/error :hint "worker: received unexpected payload (uuid expected)"
:payload task-id)))
(l/err :hint "worker: received unexpected payload (uuid expected)"
:payload task-id)))
(catch Throwable cause
(l/error :hint "worker: unable to decode payload"
:payload payload
:length (alength payload)
:cause cause))))
(l/err :hint "worker: unable to decode payload"
:payload payload
:length (alength payload)
:cause cause))))
(handle-task [{:keys [name] :as task}]
(let [task-fn (get registry name)]
(if task-fn
(task-fn task)
(l/warn :hint "no task handler found" :name name))
(l/wrn :hint "no task handler found" :name name))
{:status :completed :task task}))
(handle-task-exception [cause task]
@@ -395,9 +395,9 @@
(= ::noop (:strategy edata))
(assoc :inc-by 0))
(do
(l/error :hint "worker: unhandled exception on task"
::l/context (get-error-context cause task)
:cause cause)
(l/err :hint "worker: unhandled exception on task"
::l/context (get-error-context cause task)
:cause cause)
(if (>= (:retry-num task) (:max-retries task))
{:status :failed :task task :error cause}
{:status :retry :task task :error cause})))))
@@ -414,31 +414,31 @@
(if (or (db/connection-error? task)
(db/serialization-error? task))
(do
(l/warn :hint "worker: connection error on retrieving task from database (retrying in some instants)"
:worker-id worker-id
:cause task)
(l/wrn :hint "worker: connection error on retrieving task from database (retrying in some instants)"
:worker-id worker-id
:cause task)
(px/sleep (::rds/timeout rconn))
(recur (get-task task-id)))
(do
(l/error :hint "worker: unhandled exception on retrieving task from database (retrying in some instants)"
:worker-id worker-id
:cause task)
(l/err :hint "worker: unhandled exception on retrieving task from database (retrying in some instants)"
:worker-id worker-id
:cause task)
(px/sleep (::rds/timeout rconn))
(recur (get-task task-id))))
(nil? task)
(l/warn :hint "worker: no task found on the database"
:worker-id worker-id
:task-id task-id)
(l/wrn :hint "worker: no task found on the database"
:worker-id worker-id
:task-id task-id)
:else
(try
(l/debug :hint "worker: executing task"
:name (:name task)
:id (:id task)
:queue queue
:worker-id worker-id
:retry (:retry-num task))
(l/trc :hint "executing task"
:name (:name task)
:id (str (:id task))
:queue queue
:worker-id worker-id
:retry (:retry-num task))
(handle-task task)
(catch InterruptedException cause
(throw cause))
@@ -459,13 +459,13 @@
(if (or (db/connection-error? cause)
(db/serialization-error? cause))
(do
(l/warn :hint "worker: database exeption on processing task result (retrying in some instants)"
:cause cause)
(l/wrn :hint "worker: database exeption on processing task result (retrying in some instants)"
:cause cause)
(px/sleep (::rds/timeout rconn))
(recur result))
(do
(l/error :hint "worker: unhandled exception on processing task result (retrying in some instants)"
:cause cause)
(l/err :hint "worker: unhandled exception on processing task result (retrying in some instants)"
:cause cause)
(px/sleep (::rds/timeout rconn))
(recur result))))))]
@@ -481,24 +481,16 @@
(catch Exception cause
(if (rds/timeout-exception? cause)
(do
(l/error :hint "worker: redis pop operation timeout, consider increasing redis timeout (will retry in some instants)"
:timeout timeout
:cause cause)
(l/err :hint "worker: redis pop operation timeout, consider increasing redis timeout (will retry in some instants)"
:timeout timeout
:cause cause)
(px/sleep timeout))
(l/error :hint "worker: unhandled exception" :cause cause))))))
(l/err :hint "worker: unhandled exception" :cause cause))))))
(defn- get-error-context
[error item]
(let [data (ex-data error)]
(merge
{:hint (ex-message error)
:spec-problems (some->> data ::s/problems (take 10) seq vec)
:spec-value (some->> data ::s/value)
:data (some-> data (dissoc ::s/problems ::s/value ::s/spec))
:params item}
(when-let [explain (ex/explain data)]
{:spec-explain explain}))))
[_ item]
{:params item})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CRON
@@ -525,7 +517,7 @@
(defmethod ig/init-key ::cron
[_ {:keys [::entries ::registry ::db/pool] :as cfg}]
(if (db/read-only? pool)
(l/warn :hint "cron: not started (db is read-only)")
(l/wrn :hint "cron: not started (db is read-only)")
(let [running (atom #{})
entries (->> entries
(filter some?)
@@ -548,22 +540,22 @@
cfg (assoc cfg ::entries entries ::running running)]
(l/info :hint "cron: started" :tasks (count entries))
(synchronize-cron-entries! cfg)
(l/inf :hint "cron: started" :tasks (count entries))
(synchronize-cron-entries! cfg)
(->> (filter some? entries)
(run! (partial schedule-cron-task cfg)))
(->> (filter some? entries)
(run! (partial schedule-cron-task cfg)))
(reify
clojure.lang.IDeref
(deref [_] @running)
(reify
clojure.lang.IDeref
(deref [_] @running)
java.lang.AutoCloseable
(close [_]
(l/info :hint "cron: terminated")
(doseq [item @running]
(when-not (.isDone ^Future item)
(.cancel ^Future item true))))))))
java.lang.AutoCloseable
(close [_]
(l/inf :hint "cron: terminated")
(doseq [item @running]
(when-not (.isDone ^Future item)
(.cancel ^Future item true))))))))
(defmethod ig/halt-key! ::cron
[_ instance]
@@ -579,11 +571,14 @@
[{:keys [::db/pool ::entries]}]
(db/with-atomic [conn pool]
(doseq [{:keys [id cron]} entries]
(l/trace :hint "register cron task" :id id :cron (str cron))
(l/trc :hint "register cron task" :id id :cron (str cron))
(db/exec-one! conn [sql:upsert-cron-task id (str cron) (str cron)]))))
(def sql:lock-cron-task
"select id from scheduled_task where id=? for update skip locked")
(defn- lock-scheduled-task!
[conn id]
(let [sql (str "SELECT id FROM scheduled_task "
" WHERE id=? FOR UPDATE SKIP LOCKED")]
(some? (db/exec-one! conn [sql (d/name id)]))))
(defn- execute-cron-task
[{:keys [::db/pool] :as cfg} {:keys [id] :as task}]
@@ -591,16 +586,21 @@
{:name (str "penpot/cront-task/" id)}
(try
(db/with-atomic [conn pool]
(when (db/exec-one! conn [sql:lock-cron-task (d/name id)])
(l/trace :hint "cron: execute task" :task-id id)
((:fn task) task)))
(db/exec-one! conn ["SET statement_timeout=0;"])
(db/exec-one! conn ["SET idle_in_transaction_session_timeout=0;"])
(when (lock-scheduled-task! conn id)
(l/dbg :hint "cron: execute task" :task-id id)
((:fn task) task))
(db/rollback! conn))
(catch InterruptedException _
(l/debug :hint "cron: task interrupted" :task-id id))
(catch Throwable cause
(l/error :hint "cron: unhandled exception on running task"
::l/context (get-error-context cause task)
(binding [l/*context* (get-error-context cause task)]
(l/err :hint "cron: unhandled exception on running task"
:task-id id
:cause cause))
:cause cause)))
(finally
(when-not (px/interrupted? :current)
(schedule-cron-task cfg task))))))
@@ -610,12 +610,16 @@
(s/assert dt/cron? cron)
(let [now (dt/now)
next (dt/next-valid-instant-from cron now)]
(inst-ms (dt/diff now next))))
(dt/diff now next)))
(defn- schedule-cron-task
[{:keys [::running] :as cfg} {:keys [cron] :as task}]
(let [ft (px/schedule! (ms-until-valid cron)
(partial execute-cron-task cfg task))]
[{:keys [::running] :as cfg} {:keys [cron id] :as task}]
(let [ts (ms-until-valid cron)
ft (px/schedule! ts (partial execute-cron-task cfg task))]
(l/dbg :hint "cron: schedule task" :task-id id
:ts (dt/format-duration ts)
:at (dt/format-instant (dt/in-future ts)))
(swap! running #(into #{ft} (filter p/pending?) %))))
@@ -678,13 +682,13 @@
(-> (db/exec-one! conn [sql:remove-not-started-tasks task queue label])
:next.jdbc/update-count))]
(l/debug :hint "submit task"
:name task
:queue queue
:label label
:dedupe (boolean dedupe)
:deleted (or deleted 0)
:in (dt/format-duration duration))
(l/trc :hint "submit task"
:name task
:queue queue
:label label
:dedupe (boolean dedupe)
:deleted (or deleted 0)
:in (dt/format-duration duration))
(db/exec-one! conn [sql:insert-new-task id task props queue
label priority max-retries interval])

View File

@@ -100,6 +100,7 @@
(configure-storage-backend))
content1 (sto/content "content1")
content2 (sto/content "content2")
content3 (sto/content "content3")
object1 (sto/put-object! storage {::sto/content content1
::sto/expired-at (dt/now)
:content-type "text/plain"
@@ -107,16 +108,20 @@
object2 (sto/put-object! storage {::sto/content content2
::sto/expired-at (dt/in-past {:hours 2})
:content-type "text/plain"
})
object3 (sto/put-object! storage {::sto/content content3
::sto/expired-at (dt/in-past {:hours 1})
:content-type "text/plain"
})]
(th/sleep 200)
(let [task (:app.storage/gc-deleted-task th/*system*)
res (task {})]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 1 (:deleted res))))
(let [res (db/exec-one! th/*pool* ["select count(*) from storage_object;"])]
(t/is (= 1 (:count res))))))
(t/is (= 2 (:count res))))))
(t/deftest test-touched-gc-task-1
(let [storage (-> (:app.storage/storage th/*system*)

View File

@@ -93,6 +93,13 @@
[:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean]]]
[:fix-obj
[:map {:title "FixObjChange"}
[:type [:= :fix-obj]]
[:id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]]]
[:mov-objects
[:map {:title "MovObjectsChange"}
[:type [:= :mov-objects]]
@@ -218,7 +225,7 @@
(sm/def! ::changes
[:sequential {:gen/max 2} ::change])
(def change?
(sm/pred-fn ::change))
@@ -322,7 +329,9 @@
component-root (ctn/get-component-shape objects shape {:allow-main? true})]
(if (and (some? component-root) (ctk/main-instance? component-root))
(ctkl/set-component-modified data (:component-id component-root))
data))
(if (some? component-id)
(ctkl/set-component-modified data component-id)
data)))
data))]
(as-> data $
@@ -337,6 +346,12 @@
(d/update-in-when data [:pages-index page-id] ctst/delete-shape id ignore-touched)
(d/update-in-when data [:components component-id] ctst/delete-shape id ignore-touched)))
(defmethod process-change :fix-obj
[data {:keys [page-id component-id] :as params}]
(if page-id
(d/update-in-when data [:pages-index page-id] ctst/fix-shape-children params)
(d/update-in-when data [:components component-id] ctst/fix-shape-children params)))
;; FIXME: remove, seems like this method is already unused
;; reg-objects operation "regenerates" the geometry and selrect of the parent groups
(defmethod process-change :reg-objects

View File

@@ -12,7 +12,7 @@
[app.common.schema :as sm]
[app.common.uuid :as uuid]))
(def file-version 21)
(def file-version 22)
(def default-color clr/gray-20)
(def root uuid/zero)

View File

@@ -12,10 +12,11 @@
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.text :as gsht]
[app.common.logging :as log]
[app.common.logging :as l]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
@@ -24,7 +25,7 @@
(defmulti migrate :version)
(log/set-level! :info)
#?(:cljs (l/set-level! :info))
(defn migrate-data
([data] (migrate-data data cp/file-version))
@@ -32,7 +33,7 @@
(if (= (:version data) to-version)
data
(let [migrate-fn #(do
(log/trace :hint "migrate file" :id (:id %) :version-from %2 :version-to (inc %2))
(l/trc :hint "migrate file" :id (:id %) :version-from %2 :version-to (inc %2))
(migrate (assoc %1 :version (inc %2))))]
(reduce migrate-fn data (range (:version data 0) to-version))))))
@@ -449,11 +450,11 @@
;; If we cannot find any we let the frame-id as it was before
frame-id)]
(when (not= frame-id calculated-frame-id)
(log/info :hint "Fix wrong frame-id"
:shape (:name object)
:id (:id object)
:current (dm/get-in objects [frame-id :name])
:calculated (get-in objects [calculated-frame-id :name])))
(l/trc :hint "Fix wrong frame-id"
:shape (:name object)
:id (:id object)
:current (dm/get-in objects [frame-id :name])
:calculated (get-in objects [calculated-frame-id :name])))
(assoc object :frame-id calculated-frame-id)))
(update-container [container]
@@ -465,3 +466,38 @@
;; TODO: pending to do a migration for delete already not used fill
;; and stroke props. This should be done for >1.14.x version.
(defmethod migrate 22
[data]
(letfn [(valid-ref? [ref]
(or (uuid? ref)
(nil? ref)))
(valid-node? [node]
(and (valid-ref? (:typography-ref-file node))
(valid-ref? (:typography-ref-id node))
(valid-ref? (:fill-color-ref-file node))
(valid-ref? (:fill-color-ref-id node))))
(fix-ref [ref]
(if (valid-ref? ref) ref nil))
(fix-node [node]
(-> node
(d/update-when :typography-ref-file fix-ref)
(d/update-when :typography-ref-id fix-ref)
(d/update-when :fill-color-ref-file fix-ref)
(d/update-when :fill-color-ref-id fix-ref)))
(update-object [object]
(let [invalid-node? (complement valid-node?)]
(cond-> object
(cph/text-shape? object)
(update :content #(txt/transform-nodes invalid-node? fix-node %)))))
(update-container [container]
(update container :objects update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))

View File

@@ -382,8 +382,10 @@
keyword
identity)}}))})
(def max-safe-int (int 1e6))
(def min-safe-int (int -1e6))
;; Integer/MAX_VALUE
(def max-safe-int 2147483647)
;; Integer/MIN_VALUE
(def min-safe-int -2147483648)
(def! ::safe-int
{:type ::safe-int

View File

@@ -29,8 +29,10 @@
(def uuid-rx
#"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
(def max-safe-int (int 1e6))
(def min-safe-int (int -1e6))
;; Integer/MAX_VALUE
(def max-safe-int 2147483647)
;; Integer/MIN_VALUE
(def min-safe-int -2147483648)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DEFAULT SPECS

View File

@@ -21,7 +21,7 @@
(sm/def! ::column-params
[:map
[:color ::grid-color]
[:type [::sm/one-of #{:stretch :left :center :right}]]
[:type {:optional true} [::sm/one-of #{:stretch :left :center :right}]]
[:size {:optional true} [:maybe ::sm/safe-number]]
[:margin {:optional true} [:maybe ::sm/safe-number]]
[:item-length {:optional true} [:maybe ::sm/safe-number]]

View File

@@ -72,10 +72,10 @@
[:vector {:gen/max 5} ::gpt/point])
(sm/def! ::fill
[:map {:title "Fill" :min 1}
[:map {:title "Fill"}
[:fill-color {:optional true} ::ctc/rgb-color]
[:fill-opacity {:optional true} ::sm/safe-number]
[:fill-color-gradient {:optional true} ::ctc/gradient]
[:fill-color-gradient {:optional true} [:maybe ::ctc/gradient]]
[:fill-color-ref-file {:optional true} [:maybe ::sm/uuid]]
[:fill-color-ref-id {:optional true} [:maybe ::sm/uuid]]])
@@ -156,7 +156,7 @@
[:map {:title "GroupAttrs"}
[:type [:= :group]]
[:id ::sm/uuid]
[:shapes [:vector {:min 1 :gen/max 10 :gen/min 1} ::sm/uuid]]])
[:shapes {:optional true} [:maybe [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]]])
(sm/def! ::frame-attrs
[:map {:title "FrameAttrs"}
@@ -172,18 +172,20 @@
[:map {:title "BoolAttrs"}
[:type [:= :bool]]
[:id ::sm/uuid]
[:shapes [:vector {:min 1 :gen/max 10 :gen/min 1} ::sm/uuid]]
[:shapes {:optional true} [:maybe [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]]
;; FIXME: improve this schema
[:bool-type :keyword]
;; FIXME: improve this schema
[:bool-content
[:vector {:gen/max 2}
[:map
[:command :keyword]
[:relative {:optional true} :boolean]
[:params [:map-of {:gen/max 5} :keyword ::sm/safe-number]]]]]])
[:prev-pos {:optional true} ::gpt/point]
[:params {:optional true}
[:maybe
[:map-of {:gen/max 5} :keyword ::sm/safe-number]]]]]]])
(sm/def! ::rect-attrs
[:map {:title "RectAttrs"}
@@ -208,14 +210,19 @@
[:map
[:width :int]
[:height :int]
[:mtype :string]
[:mtype {:optional true} [:maybe :string]]
[:id ::sm/uuid]]]])
(sm/def! ::path-attrs
[:map {:title "PathAttrs"}
[:type [:= :path]]
[:id ::sm/uuid]
[:x {:optional true} [:maybe ::sm/safe-number]]
[:y {:optional true} [:maybe ::sm/safe-number]]
[:width {:optional true} [:maybe ::sm/safe-number]]
[:height {:optional true} [:maybe ::sm/safe-number]]
[:content
{:optional true}
[:vector
[:map
[:command :keyword]

View File

@@ -21,44 +21,46 @@
[:type [:= "root"]]
[:key {:optional true} :string]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:type [:= "paragraph-set"]]
[:key {:optional true} :string]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:type [:= "paragraph"]]
[:key {:optional true} :string]
[:fills {:optional true}
[:maybe
[:vector {:gen/max 2} ::shape/fill]]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:direction {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:text :string]
[:key {:optional true} :string]
[:fills {:optional true}
[:maybe
[:vector {:gen/max 2} ::shape/fill]]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:direction {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]])
{:optional true}
[:maybe
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:type [:= "paragraph-set"]]
[:key {:optional true} :string]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:type [:= "paragraph"]]
[:key {:optional true} :string]
[:fills {:optional true}
[:maybe
[:vector {:gen/max 2} ::shape/fill]]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:direction {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:text :string]
[:key {:optional true} :string]
[:fills {:optional true}
[:maybe
[:vector {:gen/max 2} ::shape/fill]]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:direction {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])

View File

@@ -90,16 +90,24 @@
(delete-from-objects [objects]
(if-let [target (get objects shape-id)]
(let [parent-id (or (:parent-id target)
(:frame-id target))
children-ids (cph/get-children-ids objects shape-id)]
(-> (reduce dissoc objects children-ids)
(dissoc shape-id)
(let [parent-id (or (:parent-id target)
(:frame-id target))
children-ids (cph/get-children-ids objects shape-id)]
(-> (reduce dissoc objects (cons shape-id children-ids))
(d/update-when parent-id delete-from-parent)))
objects))]
(update container :objects delete-from-objects))))
(defn fix-shape-children
"Checks and fix the children relations of the shape. If a children does not
exists on the objects tree, it will be removed from shape."
[{:keys [objects] :as container} {:keys [id] :as params}]
(let [contains? (partial contains? objects)]
(d/update-in-when container [:objects id :shapes]
(fn [shapes]
(into [] (filter contains?) shapes)))))
(defn get-frames
"Retrieves all frame objects as vector"
([objects] (get-frames objects nil))
@@ -350,6 +358,7 @@
(some? force-id) force-id
keep-ids? (:id object)
:else (uuid/next))]
(loop [child-ids (seq (:shapes object))
new-direct-children []
new-children []

View File

@@ -104,7 +104,7 @@ WORKDIR /opt/penpot/exporter
USER penpot:penpot
RUN set -ex; \
yarn; \
yarn run playwright install chromium;
yarn --network-timeout 1000000; \
yarn --network-timeout 1000000 run playwright install chromium;
CMD ["node", "app.js"]

View File

@@ -49,7 +49,7 @@ function readLocales() {
const langs = ["ar", "ca", "de", "el", "en", "eu", "it", "es",
"fa", "fr", "he", "nb_NO", "pl", "pt_BR", "ro", "id",
"ru", "tr", "zh_CN", "zh_Hant", "hr", "gl", "pt_PT",
"cs", "fo", "ko", "lv",
"cs", "fo", "ko", "lv", "nl",
// this happens when file does not matches correct
// iso code for the language.
["ja_jp", "jpn_JP"],

View File

@@ -1157,6 +1157,7 @@ input[type="range"]:focus::-ms-fill-upper {
.wrapper {
display: flex;
align-items: center;
.icon {
padding: $size-2;
@@ -1173,6 +1174,9 @@ input[type="range"]:focus::-ms-fill-upper {
padding: $size-2;
width: 100%;
align-items: center;
padding: 10px 15px;
min-height: 48px;
}
}

View File

@@ -176,6 +176,7 @@
}
.flow-badge {
cursor: pointer;
display: flex;
& .content {
@@ -199,7 +200,8 @@
}
}
&.selected .content {
&.selected .content,
&:hover .content {
background-color: $color-primary;
& svg {

View File

@@ -50,6 +50,23 @@
(mf/mount (mf/element ui/app) (dom/get-element "app"))
(mf/mount (mf/element modal) (dom/get-element "modal")))
(defn- initialize-profile
"Event used mainly on application bootstrap; it fetches the profile
and if and only if the fetched profile corresponds to an
authenticated user; proceed to fetch teams."
[stream]
(rx/merge
(rx/of (du/fetch-profile))
(->> stream
(rx/filter (ptk/type? ::profile-fetched))
(rx/take 1)
(rx/map deref)
(rx/mapcat (fn [profile]
(if (du/is-authenticated? profile)
(rx/of (du/fetch-teams))
(rx/empty))))
(rx/observe-on :async))))
(defn initialize
[]
(ptk/reify ::initialize
@@ -61,14 +78,19 @@
(watch [_ _ stream]
(rx/merge
(rx/of (ev/initialize)
(feat/initialize)
(du/initialize-profile))
(feat/initialize))
(initialize-profile stream)
;; Once profile is fetched, initialize all penpot application
;; routes
(->> stream
(rx/filter du/profile-fetched?)
(rx/take 1)
(rx/map #(rt/init-routes)))
;; Once profile fetched and the current user is authenticated,
;; proceed to initialize the websockets connection.
(->> stream
(rx/filter du/profile-fetched?)
(rx/map deref)

View File

@@ -279,7 +279,10 @@
(assoc-in (conj path :position) (:position comment-thread))
(assoc-in (conj path :frame-id) (:frame-id comment-thread))))))
(fetched [[users comments] state]
(let [state (-> state
(let [pages (-> (get-in state [:workspace-data :pages])
set)
comments (filter #(contains? pages (:page-id %)) comments)
state (-> state
(assoc :comment-threads (d/index-by :id comments))
(update :current-file-comments-users merge (d/index-by :id users)))]
(reduce set-comment-threds state comments)))]

View File

@@ -7,7 +7,10 @@
(ns app.main.data.common
"A general purpose events."
(:require
[app.config :as cf]
[app.main.data.messages :as msg]
[app.main.repo :as rp]
[app.util.i18n :refer [tr]]
[beicon.core :as rx]
[potok.core :as ptk]))
@@ -43,3 +46,33 @@
(watch [_ _ _]
(->> (rp/cmd! :delete-share-link {:id id})
(rx/ignore)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; NOTIFICATIONS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn force-reload!
[]
(.reload js/location))
(defn handle-notification
[{:keys [message code level] :as params}]
(ptk/reify ::show-notification
ptk/WatchEvent
(watch [_ _ _]
(case code
:upgrade-version
(when (or (not= (:version params) (:full cf/version))
(true? (:force params)))
(rx/of (msg/dialog
:content (tr "notifications.by-code.upgrade-version")
:controls :inline-actions
:type level
:actions [{:label "Refresh" :callback force-reload!}]
:tag :notification)))
(rx/of (msg/dialog
:content message
:controls :close
:type level
:tag :notification))))))

View File

@@ -13,10 +13,12 @@
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.common :refer [handle-notification]]
[app.main.data.events :as ev]
[app.main.data.fonts :as df]
[app.main.data.media :as di]
[app.main.data.users :as du]
[app.main.data.websocket :as dws]
[app.main.features :as features]
[app.main.repo :as rp]
[app.util.dom :as dom]
@@ -57,11 +59,28 @@
ptk/WatchEvent
(watch [_ state stream]
(rx/merge
;;fetch teams must be first in case the team doesn't exist
(ptk/watch (du/fetch-teams) state stream)
(ptk/watch (df/load-team-fonts id) state stream)
(ptk/watch (fetch-projects) state stream)
(ptk/watch (fetch-team-members) state stream)
(ptk/watch (du/fetch-teams) state stream)
(ptk/watch (du/fetch-users {:team-id id}) state stream)))))
(ptk/watch (du/fetch-users {:team-id id}) state stream)
(let [stoper (rx/filter (ptk/type? ::finalize) stream)
profile-id (:profile-id state)]
(->> stream
(rx/filter (ptk/type? ::dws/message))
(rx/map deref)
(rx/filter (fn [{:keys [subs-id type] :as msg}]
(and (or (= subs-id uuid/zero)
(= subs-id profile-id))
(= :notification type))))
(rx/map handle-notification)
(rx/take-until stoper)))))))
(defn finalize
[params]
(ptk/data-event ::finalize params))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Fetching (context aware: current team)
@@ -873,13 +892,13 @@
ptk/UpdateEvent
(update [_ state]
(let [origin-project (get-in state [:dashboard-files (first ids) :project-id])
update-project (fn [project delta]
update-project (fn [project delta op]
(-> project
(update :count #(+ % (count ids)))
(update :count #(op % (count ids)))
(assoc :modified-at (dt/plus (dt/now) {:milliseconds delta}))))]
(-> state
(d/update-in-when [:dashboard-projects origin-project] update-project 0)
(d/update-in-when [:dashboard-projects project-id] update-project 10))))
(d/update-in-when [:dashboard-projects origin-project] update-project 0 -)
(d/update-in-when [:dashboard-projects project-id] update-project 10 +))))
ptk/WatchEvent
(watch [_ _ _]

View File

@@ -120,6 +120,17 @@
:position :fixed
:timeout timeout})))
(defn dialog
[& {:keys [content controls actions position tag type]
:or {controls :none position :floating type :info}}]
(show (d/without-nils
{:content content
:type type
:position position
:controls controls
:actions actions
:tag tag})))
(defn info-dialog
([content controls actions]
(info-dialog content controls actions nil))

View File

@@ -117,28 +117,6 @@
(->> (rp/cmd! :get-profile)
(rx/map profile-fetched)))))
;; --- EVENT: INITIALIZE PROFILE
(defn initialize-profile
"Event used mainly on application bootstrap; it fetches the profile
and if and only if the fetched profile corresponds to an
authenticated user; proceed to fetch teams."
[]
(ptk/reify ::initialize-profile
ptk/WatchEvent
(watch [_ _ stream]
(rx/merge
(rx/of (fetch-profile))
(->> stream
(rx/filter (ptk/type? ::profile-fetched))
(rx/take 1)
(rx/map deref)
(rx/mapcat (fn [profile]
(if (= uuid/zero (:id profile))
(rx/empty)
(rx/of (fetch-teams)))))
(rx/observe-on :async))))))
;; --- EVENT: login
(defn- logged-in
@@ -147,7 +125,7 @@
accepting invitation, or third party auth signup or singin."
[profile]
(letfn [(get-redirect-event []
(let [team-id (:default-team-id profile)
(let [team-id (get-current-team-id profile)
redirect-url (:redirect-url @storage)]
(if (some? redirect-url)
(do
@@ -164,7 +142,8 @@
(when (is-authenticated? profile)
(->> (rx/of (profile-fetched profile)
(fetch-teams)
(get-redirect-event))
(get-redirect-event)
(ws/initialize))
(rx/observe-on :async)))))))
(declare login-from-register)
@@ -268,7 +247,9 @@
ptk/EffectEvent
(effect [_ _ _]
(reset! storage {})
;; We prefer to keek some stuff in the storage like the current-team-id
(swap! storage dissoc :redirect-url)
(swap! storage dissoc :profile)
(i18n/reset-locale)))))
(defn logout

View File

@@ -155,6 +155,8 @@
(rx/of (df/fonts-fetched fonts)
(bundle-fetched (merge bundle params))))))))))
(declare go-to-frame)
(declare go-to-frame-by-index)
(declare go-to-frame-auto)
(defn bundle-fetched
@@ -182,16 +184,20 @@
ptk/WatchEvent
(watch [_ state _]
(let [route (:route state)
qparams (:query-params route)
index (:index qparams)]
(let [route (:route state)
qparams (:query-params route)
index (:index qparams)
frame-id (:frame-id qparams)]
(rx/merge
(rx/of (case (:zoom qparams)
"fit" zoom-to-fit
"fill" zoom-to-fill
nil))
(when (nil? index)
(rx/of (go-to-frame-auto)))))))))
(rx/of
(cond
(some? frame-id) (go-to-frame (uuid frame-id))
(some? index) (go-to-frame-by-index index)
:else (go-to-frame-auto)))))))))
(defn fetch-comment-threads
[{:keys [file-id page-id share-id] :as params}]

View File

@@ -44,6 +44,7 @@
[app.main.data.workspace.drawing.common :as dwdc]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.fix-bool-contents :as fbc]
[app.main.data.workspace.fix-broken-shape-links :as fbs]
[app.main.data.workspace.fix-deleted-fonts :as fdf]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.guides :as dwgu]
@@ -130,8 +131,10 @@
has-graphics? (-> file :media seq)
components-v2 (features/active-feature? state :components-v2)]
(rx/merge
(rx/of (fbc/fix-bool-contents))
(rx/of (fdf/fix-deleted-fonts))
(rx/of (fbc/fix-bool-contents)
(fdf/fix-deleted-fonts)
(fbs/fix-broken-shapes))
(if (and has-graphics? components-v2)
(rx/of (remove-graphics (:id file) (:name file)))
(rx/empty)))))))
@@ -985,6 +988,23 @@
(rx/of (dwe/start-edition-mode id)
(dwdp/start-path-edit id)))))))))
(defn select-parent-layer
[]
(ptk/reify ::select-parent-layer
ptk/WatchEvent
(watch [_ state _]
(let [selected (wsh/lookup-selected state)
objects (wsh/lookup-page-objects state)
shapes-to-select
(->> selected
(reduce
(fn [result shape-id]
(let [parent-id (dm/get-in objects [shape-id :parent-id])]
(if (and (some? parent-id) (not= parent-id uuid/zero))
(conj result parent-id)
(conj result shape-id))))
(d/ordered-set)))]
(rx/of (dws/select-shapes shapes-to-select))))))
;; --- Change Page Order (D&D Ordering)
@@ -1099,7 +1119,7 @@
(defn toggle-proportion-lock
[]
(ptk/reify ::toggle-propotion-lock
(ptk/reify ::toggle-proportion-lock
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
@@ -1356,7 +1376,7 @@
(defn go-to-viewer
([] (go-to-viewer {}))
([{:keys [file-id page-id section]}]
([{:keys [file-id page-id section frame-id]}]
(ptk/reify ::go-to-viewer
ptk/WatchEvent
(watch [_ state _]
@@ -1364,7 +1384,9 @@
pparams {:file-id (or file-id current-file-id)}
qparams (cond-> {:page-id (or page-id current-page-id)}
(some? section)
(assoc :section section))]
(assoc :section section)
(some? frame-id)
(assoc :frame-id frame-id))]
(rx/of ::dwp/force-persist
(rt/nav-new-window* {:rname :viewer
:path-params pparams
@@ -1817,9 +1839,14 @@
detach? (or (foreign-instance? shape paste-objects state)
(and (ctk/in-component-copy-not-root? shape)
(not= (:id component-shape)
(:id component-shape-parent))))]
(:id component-shape-parent))))
assign-shapes? (and (or (cph/group-shape? shape)
(cph/bool-shape? shape))
(nil? (:shapes shape)))]
(-> shape
(assoc :frame-id frame-id :parent-id parent-id)
(cond-> assign-shapes?
(assoc :shapes []))
(cond-> detach?
(->
;; this is used later, if the paste needs to create a new component from the detached shape

View File

@@ -0,0 +1,28 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.assets
"Workspace assets management events and helpers."
(:require
[app.util.storage :refer [storage]]))
(defn get-current-assets-ordering
[]
(let [ordering (::ordering @storage)]
(or ordering :asc)))
(defn set-current-assets-ordering!
[ordering]
(swap! storage assoc ::ordering ordering))
(defn get-current-assets-list-style
[]
(let [list-style (::list-style @storage)]
(or list-style :thumbs)))
(defn set-current-assets-list-style!
[list-style]
(swap! storage assoc ::list-style list-style))

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.logging :as log]
[app.common.pages :as cp]
[app.common.pages.changes :as cpc]
@@ -205,6 +206,7 @@
path (if (= file-id current-file-id)
[:workspace-data]
[:workspace-libraries file-id :data])]
(try
(dm/assert!
"expect valid vector of changes"
@@ -217,7 +219,11 @@
(ctst/update-object-indices page-id))))
(catch :default err
(log/error :js/error err)
(when-let [data (ex-data err)]
(js/console.log (ex/explain data)))
(when (ex/error? err)
(js/console.log (.-stack ^js err)))
(vreset! error err)
state))))

View File

@@ -22,6 +22,7 @@
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.util.color :as uc]
[app.util.storage :refer [storage]]
[beicon.core :as rx]
[potok.core :as ptk]))
@@ -353,9 +354,12 @@
(-> state
(assoc-in [:workspace-global :picking-color?] true)
(assoc ::md/modal {:id (random-uuid)
:data {:color colors/black :opacity 1}
:type :colorpicker
:props {:on-change handle-change-color}
:props {:data {:color colors/black
:opacity 1}
:disable-opacity false
:disable-gradient false
:on-change handle-change-color}
:allow-click-outside true})))))))
(defn color-att->text
@@ -644,3 +648,12 @@
:position :right})
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
:asset-type "color"}))))))
(defn get-active-color-tab
[]
(let [tab (::tab @storage)]
(or tab :ramp)))
(defn set-active-color-tab!
[tab]
(swap! storage assoc ::tab tab))

View File

@@ -0,0 +1,55 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.fix-broken-shape-links
(:require
[app.main.data.workspace.changes :as dch]
[beicon.core :as rx]
[potok.core :as ptk]))
(defn- generate-changes
[attr {:keys [objects id] :as container}]
(let [base {:type :fix-obj attr id}
contains? (partial contains? objects)
xform (comp
;; FIXME: Ensure all obj have id field (this is needed
;; because some bug adds an ephimeral shape with id ZERO,
;; with a single attr `:shapes` having a vector of ids
;; pointing to not existing shapes). That happens on
;; components. THIS IS A WORKAOURD
(map (fn [[id obj]]
(if (some? (:id obj))
obj
(assoc obj :id id))))
;; Remove all valid shapes
(remove (fn [obj]
(every? contains? (:shapes obj))))
(map (fn [obj]
(assoc base :id (:id obj)))))]
(sequence xform objects)))
(defn fix-broken-shapes
[]
(ptk/reify ::fix-broken-shape-links
ptk/WatchEvent
(watch [it state _]
(let [data (get state :workspace-data)
changes (concat
(mapcat (partial generate-changes :page-id)
(vals (:pages-index data)))
(mapcat (partial generate-changes :component-id)
(vals (:components data))))]
(if (seq changes)
(rx/of (dch/commit-changes
{:origin it
:redo-changes (vec changes)
:undo-changes []
:save-undo? false}))
(rx/empty))))))

View File

@@ -73,16 +73,20 @@
(rx/map #(svg/add-svg-shapes (assoc svg-data :image-data %) position))))))
(defn- process-uris
[{:keys [file-id local? name uris mtype on-image on-svg]}]
[{:keys [file-id local? name uris mtype on-image on-svg] }]
(letfn [(svg-url? [url]
(or (and mtype (= mtype "image/svg+xml"))
(str/ends-with? url ".svg")))
(prepare [uri]
{:file-id file-id
:is-local local?
:name (or name (svg/extract-name uri))
:url uri})
(upload [uri]
(->> (http/send! {:method :get :uri uri :mode :no-cors :response-type :blob})
(rx/map :body)
(rx/map (fn [content]
{:file-id file-id
:name (or name (svg/extract-name uri))
:is-local local?
:content content}))
(rx/mapcat #(rp/cmd! :upload-file-media-object %))))
(fetch-svg [name uri]
(->> (http/send! {:method :get :uri uri :mode :no-cors})
@@ -93,8 +97,7 @@
(rx/merge
(->> (rx/from uris)
(rx/filter (comp not svg-url?))
(rx/map prepare)
(rx/mapcat #(rp/cmd! :create-file-media-object-from-url %))
(rx/mapcat upload)
(rx/do on-image))
(->> (rx/from uris)
@@ -142,7 +145,7 @@
[:local? :boolean]
[:name {:optional true} :string]
[:data {:optional true} :any] ; FIXME
[:uris {:optional true} [:vector :string]]
[:uris {:optional true} [:sequential :string]]
[:mtype {:optional true} :string]])
(defn- process-media-objects
@@ -213,8 +216,6 @@
:on-image #(st/emit! (dwl/add-media %)))]
(process-media-objects params)))
;; TODO: it is really need handle SVG here, looks like it already
;; handled separately
(defn upload-media-workspace
[{:keys [position file-id] :as params}]
(let [params (assoc params

View File

@@ -10,6 +10,8 @@
[app.common.data.macros :as dm]
[app.common.pages.changes :as cpc]
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[app.main.data.common :refer [handle-notification]]
[app.main.data.websocket :as dws]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.libraries :as dwl]
@@ -57,8 +59,9 @@
(rx/filter (ptk/type? ::dws/message))
(rx/map deref)
(rx/filter (fn [{:keys [subs-id] :as msg}]
(or (= subs-id team-id)
(or (= subs-id uuid/zero)
(= subs-id profile-id)
(= subs-id team-id)
(= subs-id file-id))))
(rx/map process-message))
@@ -96,6 +99,7 @@
:pointer-update (handle-pointer-update msg)
:file-change (handle-file-change msg)
:library-change (handle-library-change msg)
:notification (handle-notification msg)
nil))
(defn- handle-pointer-send

View File

@@ -122,7 +122,9 @@
(ptk/reify ::select-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :selected] d/toggle-selection id toggle?))
(-> state
(update-in [:workspace-local :selected] d/toggle-selection id toggle?)
(assoc-in [:workspace-local :last-selected] id)))
ptk/WatchEvent
(watch [_ state _]
@@ -185,7 +187,9 @@
(ptk/reify ::deselect-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :selected] disj id))))
(-> state
(update-in [:workspace-local :selected] disj id)
(update :workspace-local dissoc :last-selected)))))
(defn shift-select-shapes
([id]
@@ -196,12 +200,14 @@
ptk/UpdateEvent
(update [_ state]
(let [objects (or objects (wsh/lookup-page-objects state))
append-to-selection (cph/expand-region-selection objects (into #{} [(get-in state [:workspace-local :last-selected]) id]))
selection (-> state
wsh/lookup-selected
(conj id))]
(-> state
(assoc-in [:workspace-local :selected]
(cph/expand-region-selection objects selection))))))))
(set/union selection append-to-selection))
(update :workspace-local assoc :last-selected id)))))))
(defn select-shapes
[ids]
@@ -409,6 +415,8 @@
:else
(let [frame? (cph/frame-shape? obj)
group? (cph/group-shape? obj)
bool? (cph/bool-shape? obj)
new-id (ids-map (:id obj))
parent-id (or parent-id frame-id)
name (:name obj)
@@ -427,9 +435,15 @@
:name name
:parent-id parent-id
:frame-id frame-id)
(dissoc :shapes
:main-instance?
:use-for-thumbnail?)
(cond->
(or group? bool?)
(assoc :shapes []))
(gsh/move delta)
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)))

View File

@@ -57,6 +57,11 @@
cts/default-frame-attrs
cts/default-shape-attrs)
default-attrs (if (or (= :group (:type attrs))
(= :bool (:type attrs)))
(assoc default-attrs :shapes [])
default-attrs)
selected-non-frames
(into #{} (comp (map (d/getf objects))
(remove cph/frame-shape?))
@@ -288,7 +293,7 @@
(let [all-ids (into empty-parents ids)
contains? (partial contains? all-ids)
xform (comp (map lookup)
(filter cph/group-shape?)
(filter #(or (cph/group-shape? %) (cph/bool-shape? %)))
(remove #(->> (:shapes %) (remove contains?) seq))
(map :id))
parents (into #{} xform all-parents)]

View File

@@ -279,8 +279,8 @@
:subsections [:tools]
:fn #(emit-when-no-readonly (dw/toggle-lock-selected))}
:toggle-lock-size {:tooltip (ds/meta (ds/alt "L"))
:command (ds/c-mod "alt+l")
:toggle-lock-size {:tooltip (ds/shift "L")
:command "shift+l"
:subsections [:tools]
:fn #(emit-when-no-readonly (dw/toggle-proportion-lock))}
@@ -509,6 +509,10 @@
:subsections [:navigation-workspace]
:fn #(st/emit! (dw/select-next-shape))}
:select-parent-layer {:tooltip (ds/shift ds/enter)
:command "shift+enter"
:subsections [:navigation-workspace]
:fn #(emit-when-no-readonly (dw/select-parent-layer))}
;; SHAPE

View File

@@ -8,6 +8,7 @@
(:require
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
@@ -123,56 +124,65 @@
(assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :style :fill-opacity])
(d/parse-double 1)))))))
(defn setup-stroke [shape]
(let [stroke-linecap (-> (or (get-in shape [:svg-attrs :stroke-linecap])
(get-in shape [:svg-attrs :style :stroke-linecap]))
((d/nilf str/trim))
((d/nilf keyword)))
color-attr (str/trim (get-in shape [:svg-attrs :stroke]))
color-attr (if (= color-attr "currentColor") clr/black color-attr)
color-style (str/trim (get-in shape [:svg-attrs :style :stroke]))
color-style (if (= color-style "currentColor") clr/black color-style)
shape
(cond-> shape
;; Color present as attribute
(uc/color? color-attr)
(-> (update :svg-attrs dissoc :stroke)
(assoc-in [:strokes 0 :stroke-color] (uc/parse-color color-attr)))
(defn- setup-stroke
[shape]
(let [attrs (get shape :svg-attrs)
style (get attrs :style)
;; Color present as style
(uc/color? color-style)
(-> (update-in [:svg-attrs :style] dissoc :stroke)
(assoc-in [:strokes 0 :stroke-color] (uc/parse-color color-style)))
stroke (or (str/trim (:stroke attrs))
(str/trim (:stroke style)))
(get-in shape [:svg-attrs :stroke-opacity])
(-> (update :svg-attrs dissoc :stroke-opacity)
(assoc-in [:strokes 0 :stroke-opacity] (-> (get-in shape [:svg-attrs :stroke-opacity])
(d/parse-double 1))))
color (cond
(= stroke "currentColor") clr/black
(= stroke "none") nil
:else (uc/parse-color stroke))
(get-in shape [:svg-attrs :style :stroke-opacity])
(-> (update-in [:svg-attrs :style] dissoc :stroke-opacity)
(assoc-in [:strokes 0 :stroke-opacity] (-> (get-in shape [:svg-attrs :style :stroke-opacity])
(d/parse-double 1))))
opacity (when (some? color)
(d/parse-double
(or (:stroke-opacity attrs)
(:stroke-opacity style))
1))
(get-in shape [:svg-attrs :stroke-width])
(-> (update :svg-attrs dissoc :stroke-width)
(assoc-in [:strokes 0 :stroke-width] (-> (get-in shape [:svg-attrs :stroke-width])
(d/parse-double))))
width (when (some? color)
(d/parse-double
(or (:stroke-width attrs)
(:stroke-width style))
1))
(get-in shape [:svg-attrs :style :stroke-width])
(-> (update-in [:svg-attrs :style] dissoc :stroke-width)
(assoc-in [:strokes 0 :stroke-width] (-> (get-in shape [:svg-attrs :style :stroke-width])
(d/parse-double))))
linecap (or (get attrs :stroke-linecap)
(get style :stroke-linecap))
linecap (some-> linecap str/trim keyword)
(and stroke-linecap (= (:type shape) :path))
(-> (update-in [:svg-attrs :style] dissoc :stroke-linecap)
(cond-> (#{:round :square} stroke-linecap)
(assoc :stroke-cap-start stroke-linecap
:stroke-cap-end stroke-linecap))))]
attrs (-> attrs
(dissoc :stroke)
(dissoc :stroke-width)
(dissoc :stroke-opacity)
(update :style (fn [style]
(-> style
(dissoc :stroke)
(dissoc :stroke-linecap)
(dissoc :stroke-width)
(dissoc :stroke-opacity)))))]
(cond-> shape
(d/any-key? (get-in shape [:strokes 0]) :stroke-color :stroke-opacity :stroke-width :stroke-cap-start :stroke-cap-end)
(cond-> (assoc shape :svg-attrs attrs)
(some? color)
(assoc-in [:strokes 0 :stroke-color] color)
(and (some? color) (some? opacity))
(assoc-in [:strokes 0 :stroke-opacity] opacity)
(and (some? color) (some? width))
(assoc-in [:strokes 0 :stroke-width] width)
(and (some? linecap) (= (:type shape) :path)
(or (= linecap :round) (= linecap :square)))
(assoc :stroke-cap-start linecap
:stroke-cap-end linecap)
(d/any-key? (dm/get-in shape [:strokes 0])
:stroke-color :stroke-opacity :stroke-width
:stroke-cap-start :stroke-cap-end)
(assoc-in [:strokes 0 :stroke-style] :svg))))
(defn setup-opacity [shape]

View File

@@ -106,7 +106,7 @@
:viewer
(let [{:keys [query-params path-params]} route
{:keys [index share-id section page-id interactions-mode] :or {section :interactions interactions-mode :show-on-click}} query-params
{:keys [index share-id section page-id interactions-mode frame-id] :or {section :interactions interactions-mode :show-on-click}} query-params
{:keys [file-id]} path-params]
(if (:token query-params)
[:& viewer/breaking-change-notice]
@@ -119,7 +119,8 @@
:interactions-show? (case (keyword interactions-mode)
:hide false
:show true
:show-on-click false)}]))
:show-on-click false)
:frame-id frame-id}]))
:workspace
(let [project-id (some-> params :path :project-id uuid)

View File

@@ -153,19 +153,21 @@
(hooks/use-shortcuts ::dashboard sc/shortcuts)
(mf/with-effect [team-id]
(st/emit! (dd/initialize {:id team-id})))
(mf/with-effect [profile team-id]
(when profile
;; When doing logout we must avoid reinitializing the dashboard
(st/emit! (dd/initialize {:id team-id})))
(fn []
(dd/finalize {:id team-id})))
(mf/use-effect
(fn []
(let [events [(events/listen goog/global "keydown"
(fn [event]
(when (kbd/enter? event)
(dom/stop-propagation event)
(st/emit! (dd/open-selected-file)))))]]
(fn []
(doseq [key events]
(events/unlistenByKey key))))))
(mf/with-effect []
(let [key (events/listen goog/global "keydown"
(fn [event]
(when (kbd/enter? event)
(dom/stop-propagation event)
(st/emit! (dd/open-selected-file)))))]
(fn []
(events/unlistenByKey key))))
[:& (mf/provider ctx/current-team-id) {:value team-id}
[:& (mf/provider ctx/current-project-id) {:value project-id}

View File

@@ -230,8 +230,8 @@
]
(mf/with-effect [collapsed]
(when-not collapsed
(mf/with-effect [profile collapsed]
(when (and profile (not collapsed))
(st/emit! (dd/fetch-builtin-templates))))
[:div.dashboard-templates-section

View File

@@ -106,7 +106,7 @@
(mf/set-ref-val! prev-val-ref node))))]
(mf/with-effect []
(mf/with-layout-effect []
;; On dismount we need to disconnect the current observer
(fn []
(when-let [observer (mf/ref-val observer-ref)]

View File

@@ -89,7 +89,7 @@
(obj/merge! attrs (clj->js fill-attrs)))))
(defn add-stroke [attrs stroke-data render-id index]
(let [stroke-style (:stroke-style stroke-data :none)
(let [stroke-style (:stroke-style stroke-data :solid)
stroke-color-gradient-id (str "stroke-color-gradient_" render-id "_" index)
stroke-width (:stroke-width stroke-data 1)]
(if (not= stroke-style :none)

View File

@@ -38,11 +38,10 @@
(defn- find-relative-to-base-frame
[shape objects overlays-ids base-frame]
(if (or (empty? overlays-ids) (nil? shape) (cph/root? shape))
base-frame
(if (contains? overlays-ids (:id shape))
shape
(find-relative-to-base-frame (cph/get-parent objects (:id shape)) objects overlays-ids base-frame))))
(cond
(cph/frame-shape? shape) shape
(or (empty? overlays-ids) (nil? shape) (cph/root? shape)) base-frame
:else (find-relative-to-base-frame (cph/get-parent objects (:id shape)) objects overlays-ids base-frame)))
(defn- activate-interaction
[interaction shape base-frame frame-offset objects overlays]

View File

@@ -8,8 +8,10 @@
(:require-macros [app.main.style :refer [css]])
(:require
[app.common.data.macros :as dm]
[app.main.data.messages :as msg]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.colors :as dc]
[app.main.data.workspace.persistence :as dwp]
[app.main.features :as features]
[app.main.refs :as refs]
@@ -185,6 +187,9 @@
(st/emit! (dw/initialize-file project-id file-id))
(fn []
(st/emit! ::dwp/force-persist
(dc/stop-picker)
(modal/hide)
msg/hide
(dw/finalize-file project-id file-id))))
[:& (mf/provider ctx/current-file-id) {:value file-id}

View File

@@ -56,12 +56,17 @@
current-color (:current-color state)
active-tab (mf/use-state :ramp #_:harmony #_:hsva)
set-ramp-tab! (mf/use-fn #(reset! active-tab :ramp))
set-harmony-tab! (mf/use-fn #(reset! active-tab :harmony))
set-hsva-tab! (mf/use-fn #(reset! active-tab :hsva))
active-tab (mf/use-state (dc/get-active-color-tab))
drag? (mf/use-state false)
set-tab!
(mf/use-fn
(fn [event]
(let [tab (-> (dom/get-current-target event)
(dom/get-data "tab")
(keyword))]
(reset! active-tab tab)
(dc/set-active-color-tab! tab))))
handle-change-color
(mf/use-fn
@@ -81,9 +86,9 @@
(fn []
(if picking-color?
(do (modal/disallow-click-outside!)
(st/emit! (dc/stop-picker)))
(st/emit! (dc/stop-picker)))
(do (modal/allow-click-outside!)
(st/emit! (dc/start-picker))))))
(st/emit! (dc/start-picker))))))
handle-change-stop
(mf/use-fn
@@ -225,15 +230,18 @@
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
{:class (when (= @active-tab :ramp) "active")
:alt (tr "workspace.libraries.colors.rgba")
:on-click set-ramp-tab!} i/picker-ramp]
:on-click set-tab!
:data-tab "ramp"} i/picker-ramp]
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
{:class (when (= @active-tab :harmony) "active")
:alt (tr "workspace.libraries.colors.rgb-complementary")
:on-click set-harmony-tab!} i/picker-harmony]
:on-click set-tab!
:data-tab "harmony"} i/picker-harmony]
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
{:class (when (= @active-tab :hsva) "active")
:alt (tr "workspace.libraries.colors.hsv")
:on-click set-hsva-tab!} i/picker-hsv]]
:on-click set-tab!
:data-tab "hsva"} i/picker-hsv]]
(if picking-color?
[:div.picker-detail-wrapper

View File

@@ -13,7 +13,6 @@
[app.main.data.exports :as de]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.colors :as dc]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shortcuts :as sc]
@@ -162,9 +161,7 @@
(st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version}))
(if (and (kbd/alt? event) (kbd/mod? event))
(st/emit! (modal/show {:type :onboarding}))
(st/emit! (modal/show {:type :release-notes :version version}))))))
]
(st/emit! (modal/show {:type :release-notes :version version}))))))]
[:& dropdown {:show true :on-close on-close}
[:ul.sub-menu.help-info
@@ -582,16 +579,10 @@
(dom/prevent-default event)
(reset! editing* true)))
close-modals
(mf/use-fn
#(st/emit! (dc/stop-picker)
(modal/hide)))
go-back
(mf/use-fn
(mf/deps project)
(fn []
(close-modals)
(st/emit! (dw/go-to-dashboard project))))
nav-to-viewer

View File

@@ -26,6 +26,7 @@
[app.util.object :as obj]
[app.util.text-editor :as ted]
[app.util.text-svg-position :as tsp]
[app.util.timers :as ts]
[promesa.core :as p]
[rumext.v2 :as mf]))
@@ -79,25 +80,29 @@
(defn- update-text-modifier
[{:keys [grow-type id] :as shape} node]
(->> (tsp/calc-position-data id)
(p/fmap (fn [position-data]
(let [props {:position-data position-data}]
(if (contains? #{:auto-height :auto-width} grow-type)
(let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
width (mth/ceil width)
height (mth/ceil height)]
(if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
(cond-> props
(= grow-type :auto-width)
(assoc :width width)
(p/let [position-data (tsp/calc-position-data id)
props {:position-data position-data}
props
(if (contains? #{:auto-height :auto-width} grow-type)
(let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
width (mth/ceil width)
height (mth/ceil height)]
(if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
(cond-> props
(= grow-type :auto-width)
(assoc :width width)
(or (= grow-type :auto-height) (= grow-type :auto-width))
(assoc :height height))
props))
props)]
(st/emit! (dwt/update-text-modifier id props))))
(or (= grow-type :auto-height) (= grow-type :auto-width))
(assoc :height height))
props))
props))))
(p/fmap (fn [props]
;; We need to wait for the text modifier to be updated before
;; we can update the position data. Otherwise the position data
;; will be wrong.
;; TODO: This is a hack. We need to find a better way to do this.
(st/emit! (dwt/update-text-modifier id props))
(ts/schedule 30 #(update-text-shape shape node))))))
(mf/defc text-container
{::mf/wrap-props false

View File

@@ -16,6 +16,7 @@
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.assets :as dwa]
[app.main.data.workspace.colors :as dc]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.media :as dwm]
@@ -2424,19 +2425,32 @@
[]
(let [components-v2 (mf/use-ctx ctx/components-v2)
read-only? (mf/use-ctx ctx/workspace-read-only?)
filters* (mf/use-state
{:term ""
:section :all
:ordering :asc
:list-style :thumbs})
:ordering (dwa/get-current-assets-ordering)
:list-style (dwa/get-current-assets-list-style)})
filters (deref filters*)
term (:term filters)
ordering (:ordering filters)
list-style (:list-style filters)
toggle-ordering
(mf/use-fn #(swap! filters* update :ordering toggle-values [:asc :desc]))
(mf/use-fn
(mf/deps ordering)
(fn []
(let [new-value (toggle-values ordering [:asc :desc])]
(swap! filters* assoc :ordering new-value)
(dwa/set-current-assets-ordering! new-value))))
toggle-list-style
(mf/use-fn #(swap! filters* update :list-style toggle-values [:thumbs :list]))
(mf/use-fn
(mf/deps list-style)
(fn []
(let [new-value (toggle-values list-style [:thumbs :list])]
(swap! filters* assoc :list-style new-value)
(dwa/set-current-assets-list-style! new-value))))
on-search-term-change
(mf/use-fn

View File

@@ -22,6 +22,7 @@
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.layer-name :refer [layer-name]]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.timers :as ts]
[beicon.core :as rx]
@@ -175,6 +176,7 @@
;; seek for an alternate solution. Maybe use-context?
scroll-node (dom/get-parent-with-data node "scrollContainer")
parent-node (dom/get-parent-at node 2)
first-child-node (dom/get-first-child parent-node)
subid
(when (and single? selected?)
@@ -184,9 +186,9 @@
#(let [scroll-distance-ratio (dom/get-scroll-distance-ratio node scroll-node)
scroll-behavior (if (> scroll-distance-ratio 1) "instant" "smooth")]
(if scroll-to
(dom/scroll-into-view! parent-node #js {:block "center" :behavior scroll-behavior :inline "start"})
(dom/scroll-into-view! first-child-node #js {:block "center" :behavior scroll-behavior :inline "start"})
(do
(dom/scroll-into-view-if-needed! parent-node #js {:block "center" :behavior scroll-behavior :inline "start"})
(dom/scroll-into-view-if-needed! first-child-node #js {:block "center" :behavior scroll-behavior :inline "start"})
(reset! scroll-to-middle? true)))))))]
#(when (some? subid)
@@ -268,10 +270,16 @@
(css :selected) (:blocked item))}
[:button {:class (dom/classnames (css :toggle-element) true
(css :selected) (:hidden item))
:title (if (:hidden item)
(tr "workspace.shape.menu.show")
(tr "workspace.shape.menu.hide"))
:on-click toggle-visibility}
(if (:hidden item) i/hide-refactor i/shown-refactor)]
[:button {:class (dom/classnames (css :block-element) true
(css :selected) (:blocked item))
:title (if (:blocked item)
(tr "workspace.shape.menu.unlock")
(tr "workspace.shape.menu.lock"))
:on-click toggle-blocking}
(if (:blocked item) i/lock-refactor i/unlock-refactor)]]]]
(when (and (:shapes item) expanded?)
@@ -328,10 +336,16 @@
[:div.element-actions {:class (when (:shapes item) "is-parent")}
[:div.toggle-element {:class (when (:hidden item) "selected")
:title (if (:hidden item)
(tr "workspace.shape.menu.show")
(tr "workspace.shape.menu.hide"))
:on-click toggle-visibility}
(if (:hidden item) i/eye-closed i/eye)]
[:div.block-element {:class (when (:blocked item) "selected")
:on-click toggle-blocking}
:on-click toggle-blocking
:title (if (:blocked item)
(tr "workspace.shape.menu.unlock")
(tr "workspace.shape.menu.lock"))}
(if (:blocked item) i/lock i/unlock)]]
(when (:shapes item)

View File

@@ -319,7 +319,8 @@
[:div.interactions-element.separator
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-trigger")]
[:select.input-select
{:value (str (:event-type interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:event-type interaction))
:on-change change-event-type}
(for [[value name] (event-type-names)]
(when-not (and (= value :after-delay)
@@ -342,7 +343,8 @@
[:div.interactions-element.separator
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-action")]
[:select.input-select
{:value (str (:action-type interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:action-type interaction))
:on-change change-action-type}
(for [[value name] (action-type-names)]
[:option {:key (dm/str "action-" value)
@@ -353,7 +355,8 @@
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-destination")]
[:select.input-select
{:value (str (:destination interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:destination interaction))
:on-change change-destination}
(if (= (:action-type interaction) :close-overlay)
[:option {:value ""} (tr "workspace.options.interaction-self")]
@@ -390,7 +393,8 @@
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-relative-to")]
[:select.input-select
{:value (str (:position-relative-to interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:position-relative-to interaction))
:on-change change-position-relative-to}
(when (not= (:overlay-pos-type interaction) :manual)
[:*
@@ -405,7 +409,8 @@
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-position")]
[:select.input-select
{:value (str (:overlay-pos-type interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:overlay-pos-type interaction))
:on-change (partial change-overlay-pos-type (:id shape))}
(for [[value name] (overlay-pos-type-names)]
[:option {:value (str value)} name])]]
@@ -467,7 +472,8 @@
[:div.interactions-element.separator
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-animation")]
[:select.input-select
{:value (str (-> interaction :animation :animation-type))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (-> interaction :animation :animation-type))
:on-change change-animation-type}
[:option {:value ""} (tr "workspace.options.interaction-animation-none")]
(for [[value name] (animation-type-names interaction)]
@@ -529,7 +535,8 @@
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-easing")]
[:select.input-select
{:value (str (-> interaction :animation :easing))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (-> interaction :animation :easing))
:on-change change-easing}
(for [[value name] (easing-names)]
[:option {:value (str value)} name])]

View File

@@ -319,7 +319,7 @@
layout-container-ids layout-container-values
layout-item-ids layout-item-values]
(mf/use-memo
(mf/deps objects-no-measures)
(mf/deps shapes objects-no-measures)
(fn []
(into
[]

View File

@@ -451,8 +451,8 @@
(dnd/has-type? event "text/uri-list")
(let [data (dnd/get-data event "text/uri-list")
lines (str/lines data)
uris (->> lines (filter #(str/starts-with? % "http")))
data (->> lines (filter #(str/starts-with? % "data:image/")))
uris (filterv #(str/starts-with? % "http") lines)
data (filterv #(str/starts-with? % "data:image/") lines)
params {:file-id (:id file)
:position viewport-coord}
params (if (seq uris)

View File

@@ -222,11 +222,13 @@
(mf/use-callback
(mf/deps (:id frame) on-frame-select)
(fn [bevent]
(let [event (.-nativeEvent bevent)]
(let [event (.-nativeEvent bevent)
params {:section "interactions"
:frame-id (:id frame)}]
(when (= 1 (.-which event))
(dom/prevent-default event)
(dom/stop-propagation event)
(on-frame-select event (:id frame))))))
(st/emit! (dw/go-to-viewer params))))))
on-double-click
(mf/use-callback

View File

@@ -151,9 +151,9 @@
(rx/tap (fn [[fonts]]
(when (seq fonts)
(st/emit! (df/fonts-fetched fonts)))))
(rx/map (comp :objects second))))))
(rx/map (fn [[_ page]] {:objects (:objects page)}))))))
objects (use-resource fetch-state)]
{:keys [objects]} (use-resource fetch-state)]
(when objects
(for [object-id object-ids]

View File

@@ -97,11 +97,12 @@
(defn- svg-update-image!
"Updates an image in an SVG to a Data URI."
[image]
(when-let [href (dom/get-attribute image "href")]
(if-let [href (dom/get-attribute image "href")]
(->> (fetch-as-data-uri href)
(rx/map (fn [url]
(dom/set-attribute! image "href" url)
image)))))
image)))
(rx/empty)))
(defn- svg-resolve-images!
"Resolves all images in an SVG to Data URIs."

View File

@@ -8,6 +8,7 @@
"Color conversion utils."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.util.i18n :as i18n :refer [tr]]
[app.util.object :as obj]
[app.util.strings :as ust]
@@ -176,14 +177,16 @@
(= id :multiple)
(= file-id :multiple)))
(defn color? [^string color-str]
(and (not (nil? color-str))
(seq color-str)
(gcolor/isValidColor color-str)))
(defn color?
[color]
(and (string? color)
(gcolor/isValidColor color)))
(defn parse-color [^string color-str]
(let [result (gcolor/parse color-str)]
(str (.-hex ^js result))))
(defn parse-color
[color]
(when (color? color)
(let [result (gcolor/parse color)]
(dm/str (.-hex ^js result)))))
(def color-names
(obj/get-keys ^js gcolor/names))

View File

@@ -25,6 +25,7 @@
{:label "Español" :value "es"}
{:label "Català" :value "ca"}
{:label "Deutsch (community)" :value "de"}
{:label "Dutch (community)" :value "nl"}
{:label "Euskera (community)" :value "eu"}
{:label "Français (community)" :value "fr"}
{:label "Gallego (Community)" :value "gl"}

View File

@@ -737,12 +737,13 @@
:fill-color-ref-file (get-meta fill-node :fill-color-ref-file uuid/uuid)
:fill-color-ref-id (get-meta fill-node :fill-color-ref-id uuid/uuid)
:fill-opacity (get-meta fill-node :fill-opacity d/parse-double)}))
(mapv d/without-nils))]
(mapv d/without-nils)
(filterv #(not= (:fill-color %) "none")))]
(if (seq fills)
fills
(->> [(-> (add-fill {} node svg-data)
(d/without-nils))]
(filterv not-empty)))))
(filterv #(and (not-empty %) (not= (:fill-color %) "none")))))))
(defn parse-strokes
[node svg-data]
@@ -761,12 +762,13 @@
:stroke-alignment (get-meta stroke-node :stroke-alignment keyword)
:stroke-cap-start (get-meta stroke-node :stroke-cap-start keyword)
:stroke-cap-end (get-meta stroke-node :stroke-cap-end keyword)}))
(mapv d/without-nils))]
(mapv d/without-nils)
(filterv #(not= (:stroke-color %) "none")))]
(if (seq strokes)
strokes
(->> [(-> (add-stroke {} node svg-data)
(d/without-nils))]
(filterv #(and (not-empty %) (not= (:stroke-style %) :none)))))))
(filterv #(and (not-empty %) (not= (:stroke-color %) "none") (not= (:stroke-style %) :none)))))))
(defn add-svg-content
[props node]

View File

@@ -86,7 +86,7 @@
(progress! context type file nil nil))
([context type current total]
(keyword? type)
(assert (keyword? type))
(assert (number? current))
(assert (number? total))
(progress! context type nil current total))

View File

@@ -1,16 +1,16 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2022-10-04 18:22+0000\n"
"Last-Translator: Youkho <youkho@gmail.com>\n"
"Language-Team: Arabic "
"<https://hosted.weblate.org/projects/penpot/frontend/ar/>\n"
"PO-Revision-Date: 2023-07-02 17:52+0000\n"
"Last-Translator: Amine Gdoura <amine2gdoura@gmail.com>\n"
"Language-Team: Arabic <https://hosted.weblate.org/projects/penpot/frontend/"
"ar/>\n"
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 4.14.1\n"
"X-Generator: Weblate 5.0-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
@@ -515,10 +515,6 @@ msgstr "+ مشروع جديد"
msgid "dashboard.new-project-prefix"
msgstr "مشروع جديد"
#: src/app/main/ui/settings/profile.cljs
msgid "dashboard.newsletter-title"
msgstr "الإشتراك في"
#: src/app/main/ui/dashboard/search.cljs
msgid "dashboard.no-matches-for"
msgstr "لم يتم العثور على مطابقات ل \"%s\""
@@ -825,10 +821,6 @@ msgstr "يبدو أن اسم المستخدم أو كلمة المرور خاط
msgid "errors.wrong-old-password"
msgstr "كلمة المرور القديمة غير صحيحة"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.chat-subtitle"
msgstr "ترغب في الكلام؟ تحدث معنا في Gitter"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.description"
msgstr "وصف"
@@ -944,14 +936,6 @@ msgstr "عرض"
msgid "inspect.attributes.shadow"
msgstr "ظل"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.offset-x"
msgstr "X"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.spread"
msgstr "S"
#: src/app/main/ui/inspect/attributes/stroke.cljs
msgid "inspect.attributes.stroke"
msgstr "لون الحدّ"
@@ -1248,9 +1232,6 @@ msgstr "مركز المساعدة"
msgid "labels.hide-resolved-comments"
msgstr "إخفاء التعليقات التي تم حلها"
msgid "labels.images"
msgstr "الصور"
msgid "labels.installed-fonts"
msgstr "الخطوط المتوفرة"
@@ -1458,9 +1439,6 @@ msgstr "قائمة التعليقات"
msgid "labels.show-your-comments"
msgstr "إظهار تعليقاتك فقط"
msgid "labels.skip"
msgstr "تخطي"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.status"
msgstr "الحالة"
@@ -1884,15 +1862,6 @@ msgstr "أنشئ فريقًا وأرسل الدعوات"
msgid "onboarding.choice.team-up.roles"
msgstr "دعوة مع الدور:"
msgid "onboarding.contrib.alt"
msgstr "مصدر مفتوح"
msgid "onboarding.contrib.desc2.1"
msgstr "يمكنك الوصول إلى"
msgid "onboarding.contrib.link"
msgstr "مشروع على github"
msgid "onboarding.newsletter.accept"
msgstr "نعم ، اشترك"
@@ -1905,28 +1874,9 @@ msgstr "سياسة الخصوصية."
msgid "onboarding.newsletter.title"
msgstr "هل تريد تلقي أخبار Penpot؟"
msgid "onboarding.slide.0.desc1"
msgstr "قم بإنشاء واجهات مستخدم جميلة بالتعاون مع جميع أعضاء الفريق."
msgid "onboarding.slide.0.title"
msgstr "مكتبات التصميم والأنماط والمكونات"
msgid "onboarding.slide.1.desc1"
msgstr "أنشئ تفاعلات غنية لتقليد سلوك المنتج."
msgid "onboarding.slide.1.title"
msgstr "اجعل تصميماتك تنبض بالحياة من خلال التفاعلات"
msgid "onboarding.slide.2.desc1"
msgstr ""
"يعمل جميع أعضاء الفريق في وقت واحد مع تصميمات متعددة اللاعبين في الوقت "
"الفعلي وتعليقات وأفكار وتعليقات مركزية مباشرة على التصميمات."
msgid "onboarding.slide.3.desc1"
msgstr ""
"قم بمزامنة التصميم والرمز لجميع المكونات والأنماط الخاصة بك واحصل على "
"مقتطفات التعليمات البرمجية."
msgid "onboarding.team-modal.create-team"
msgstr "أنشئ فريقًا"
@@ -2343,14 +2293,6 @@ msgstr "التفاعلات"
msgid "viewer.header.share.copy-link"
msgstr "نسخ الرابط"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.placeholder"
msgstr "سيظهر رابط المشاركة هنا"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.subtitle"
msgstr "أي شخص لديه الرابط سيكون لديه حق الوصول"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.show-interactions"
msgstr "إظهار التفاعلات"
@@ -2697,15 +2639,6 @@ msgstr "تحديث"
msgid "workspace.libraries.updates"
msgstr "التحديثات"
msgid "workspace.library.libraries"
msgstr "المكتبات"
msgid "workspace.library.store"
msgstr "المكتبات المخزنة"
msgid "workspace.options.blur-options.layer-blur"
msgstr "طبقة"
#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
msgid "workspace.options.blur-options.title"
msgstr "الضبابية"
@@ -2906,4 +2839,71 @@ msgid "workspace.updates.update"
msgstr "تحديث"
msgid "workspace.viewport.click-to-close-path"
msgstr "انقر لإغلاق المسار"
msgstr "انقر لإغلاق المسار"
#, markdown
msgid "dashboard.fonts.warning-text"
msgstr ""
"لقد اكتشفنا مشكلة محتملة في الخطوط الخاصة بك تتعلق بالمقاييس الرأسية لأنظمة "
"التشغيل المختلفة. للتحقق من ذلك ، يمكنك استخدام خدمات المقاييس العمودية "
"للخطوط مثل [هذه] (https://vertical-metrics.netlify.app/). بالإضافة إلى ذلك ، "
"نوصي باستخدام [Transfonter] (https://transfonter.org/) لإنشاء خطوط الويب "
"وإصلاح الأخطاء. "
msgid "dashboard.webhooks.active"
msgstr "نشط"
msgid "dashboard.webhooks.active.explain"
msgstr "عندما يتم تشغيل هذا الخطاف ، سيتم تسليم تفاصيل الحدث"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs, src/app/main/ui/auth/recovery_request.cljs
msgid "errors.email-invalid"
msgstr "أدخل بريدًا إلكترونيًا صالحًا من فضلك"
#: src/app/main/errors.cljs
msgid "errors.feature-mismatch"
msgstr ""
"يبدو أنك تفتح ملفًا تم تمكين الميزة \"٪ s\" فيه ولكن الواجهة الأمامية لـ "
"penpot لا تدعمه أو تم تعطيله."
msgid "errors.webhooks.unexpected-status"
msgstr "حالة غير متوقعة٪ s"
#: src/app/main/ui/inspect/attributes/text.cljs
msgid "inspect.attributes.typography.font-weight"
msgstr "وزن الخط"
msgid "inspect.empty.help"
msgstr ""
"إذا كنت تريد معرفة المزيد عن فحص التصميم ، فتفضل بزيارة مركز مساعدة لPenpot"
msgid "dashboard.webhooks.create"
msgstr "إنشاء الرد التلقائي على الويب"
msgid "errors.bad-font"
msgstr "تعذر تحميل الخط٪ s"
msgid "errors.bad-font-plural"
msgstr "تعذر تحميل الخطوط٪ s"
msgid "dashboard.webhooks.content-type"
msgstr "نوع المحتوى"
#: src/app/main/errors.cljs
msgid "errors.feature-not-supported"
msgstr "الميزة '٪ s' غير مدعومة."
msgid "errors.profile-blocked"
msgstr "هذا الملف الشخصي محظور"
msgid "errors.webhooks.connection"
msgstr "خطأ في الاتصال ، عنوان إلكتروني لا يمكن الوصول إليه"
msgid "errors.webhooks.last-delivery"
msgstr "آخر تسليم لم يكن ناجحًا."
msgid "errors.webhooks.timeout"
msgstr "نفذ الوقت"
msgid "errors.webhooks.unexpected"
msgstr "خطأ غير متوقع في التحقق"

View File

@@ -522,10 +522,6 @@ msgstr "+ Projecte nou"
msgid "dashboard.new-project-prefix"
msgstr "Projecte nou"
#: src/app/main/ui/settings/profile.cljs
msgid "dashboard.newsletter-title"
msgstr "Subscripció al butlletí"
#: src/app/main/ui/dashboard/search.cljs
msgid "dashboard.no-matches-for"
msgstr "No s'ha trobat cap coincidència amb “%s“"
@@ -823,10 +819,6 @@ msgstr "El nom d'usuari o la contrasenya sembla incorrecte."
msgid "errors.wrong-old-password"
msgstr "La contrasenya anterior no és correcta"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.chat-subtitle"
msgstr "Voleu parlar? Xategeu amb nosaltres a Gitter"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.description"
msgstr "Descripció"
@@ -942,10 +934,6 @@ msgstr "Amplada"
msgid "inspect.attributes.shadow"
msgstr "Ombra"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.offset-x"
msgstr "X"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.spread"
msgstr "S"
@@ -1242,9 +1230,6 @@ msgstr "Centre d'ajuda"
msgid "labels.hide-resolved-comments"
msgstr "Amaga els comentaris resolts"
msgid "labels.images"
msgstr "Imatges"
msgid "labels.installed-fonts"
msgstr "Tipografies instal·lades"
@@ -1438,9 +1423,6 @@ msgstr "Mostra la llista de comentaris"
msgid "labels.show-your-comments"
msgstr "Mostra només els meus comentaris"
msgid "labels.skip"
msgstr "Omet"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.status"
msgstr "Estat"
@@ -1821,15 +1803,9 @@ msgstr "Crea l'equip ara i convida membres en un altre moment"
msgid "onboarding.choice.team-up.invite-members-submit"
msgstr "Crea l'equip i envia les invitacions"
msgid "onboarding.contrib.alt"
msgstr "Codi obert"
msgid "onboarding.contrib.desc2.1"
msgstr "Podeu accedir al"
msgid "onboarding.contrib.link"
msgstr "projecte a github"
msgid "onboarding.newsletter.accept"
msgstr "Sí, subscriu-m'hi"
@@ -1844,33 +1820,15 @@ msgstr "Política de privacitat."
msgid "onboarding.newsletter.title"
msgstr "Voleu rebre les novetats de Penpot?"
msgid "onboarding.slide.0.desc1"
msgstr ""
"Creeu interfícies d'usuari boniques en col·laboració amb tots els membres "
"de l'equip."
msgid "onboarding.slide.0.title"
msgstr "Biblioteques de disseny, estils i components"
msgid "onboarding.slide.1.desc1"
msgstr "Creeu interaccions enriquides per a imitar el comportament del producte."
msgid "onboarding.slide.1.title"
msgstr "Doneu vida als vostres dissenys amb interaccions"
msgid "onboarding.slide.2.desc1"
msgstr ""
"Tot l'equip treballant simultàniament amb disseny en temps real i "
"comentaris, idees i opinions sobre els dissenys de forma centralitzada."
msgid "onboarding.slide.3.alt"
msgstr "Lliurament i codi baix"
msgid "onboarding.slide.3.desc2"
msgstr ""
"Obteniu i proporcioneu especificacions de codi d'etiquetatge (SVG, HTML) o "
"d'estils (CSS, Less, Stylus...)."
msgid "onboarding.team-modal.create-team"
msgstr "Crea un equip"
@@ -2410,10 +2368,6 @@ msgstr "Interaccions (%s)"
msgid "viewer.header.share.copy-link"
msgstr "Copia l'enllaç"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.placeholder"
msgstr "L'enllaç compartit apareixerà aquí"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.subtitle"
msgstr "Qualsevol persona amb l'enllaç hi tindrà accés"
@@ -2850,9 +2804,6 @@ msgstr "Actualitza"
msgid "workspace.libraries.updates"
msgstr "ACTUALITZACIONS"
msgid "workspace.library.libraries"
msgstr "Biblioteques"
msgid "workspace.library.store"
msgstr "Predeterminades"
@@ -2860,9 +2811,6 @@ msgstr "Predeterminades"
msgid "workspace.options.add-interaction"
msgstr "Feu clic en el botó de + per a afegir interaccions."
msgid "workspace.options.blur-options.layer-blur"
msgstr "Capa"
#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
msgid "workspace.options.blur-options.title"
msgstr "Difuminat"
@@ -4149,4 +4097,4 @@ msgid "workspace.updates.update"
msgstr "Actualitza"
msgid "workspace.viewport.click-to-close-path"
msgstr "Feu clic per a tancar el camí"
msgstr "Feu clic per a tancar el camí"

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2023-02-08 14:36+0000\n"
"PO-Revision-Date: 2023-08-19 11:55+0000\n"
"Last-Translator: Stas Haas <stas@girafic.de>\n"
"Language-Team: German "
"<https://hosted.weblate.org/projects/penpot/frontend/de/>\n"
"Language-Team: German <https://hosted.weblate.org/projects/penpot/frontend/"
"de/>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.16-dev\n"
"X-Generator: Weblate 5.0-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
@@ -445,9 +445,6 @@ msgstr ""
"Beim Importieren der Datei ist ein Fehler aufgetreten. Die Datei wurde "
"nicht importiert."
msgid "dashboard.import.import-message"
msgstr "%s Dateien wurden erfolgreich importiert."
msgid "dashboard.import.import-warning"
msgstr "Einige Dateien enthielten ungültige Objekte, die entfernt wurden."
@@ -1182,7 +1179,7 @@ msgstr "Info"
#: src/app/main/ui/workspace/header.cljs
msgid "label.shortcuts"
msgstr "Tastenkürzel"
msgstr "Tastaturkürzel"
msgid "labels.accept"
msgstr "Akzeptieren"
@@ -1780,21 +1777,25 @@ msgstr[1] "Dateien löschen"
msgid "modals.delete-shared-confirm.hint"
msgid_plural "modals.delete-shared-confirm.hint"
msgstr[0] ""
"Wenn Sie es löschen, werden diese Assets in die lokale Bibliothek dieser "
"Datei verschoben. Unbenutzte Assets gehen verloren."
"Wenn Sie es löschen, werden diese Assets nicht mehr in anderen Dateien "
"verfügbar sein. Die bereits verwendeten Assets, bleiben in dieser Datei "
"erhalten (das Design wird dadurch nicht zerstört!)."
msgstr[1] ""
"Wenn Sie sie löschen, werden diese Assets in die lokale Bibliothek dieser "
"Datei verschoben. Unbenutzte Assets gehen verloren."
"Wenn Sie die Assets löschen, werden diese Assets nicht mehr in anderen "
"Dateien verfügbar sein. Die bereits verwendeten Assets, bleiben in dieser "
"Datei erhalten (das Design wird dadurch nicht zerstört!)."
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-shared-confirm.hint-many"
msgid_plural "modals.delete-shared-confirm.hint-many"
msgstr[0] ""
"Wenn Sie es löschen, werden diese Assets in die lokalen Bibliotheken dieser "
"Dateien verschoben. Unbenutzte Assets gehen verloren."
"Wenn Sie es löschen, werden diese Assets nicht mehr in anderen Dateien "
"verfügbar sein. Die bereits verwendeten Assets, bleiben in dieser Datei "
"erhalten (das Design wird dadurch nicht zerstört!)."
msgstr[1] ""
"Wenn Sie sie löschen, werden diese Assets in die lokalen Bibliotheken "
"dieser Dateien verschoben. Unbenutzte Assets gehen verloren."
"Wenn Sie die Assets löschen, werden diese Assets nicht mehr in anderen "
"Dateien verfügbar sein. Die bereits verwendeten Assets, bleiben in dieser "
"Datei erhalten (das Design wird dadurch nicht zerstört!)."
#: src/app/main/ui/workspace/header.cljs,
#: src/app/main/ui/dashboard/file_menu.cljs
@@ -1977,21 +1978,25 @@ msgstr "Minimal"
msgid "modals.unpublish-shared-confirm.hint"
msgid_plural "modals.unpublish-shared-confirm.hint"
msgstr[0] ""
"Wenn Sie die Veröffentlichung aufheben, werden diese Assets in die lokale "
"Bibliothek dieser Datei verschoben."
"Wenn Sie die Veröffentlichung aufheben, sind diese Assets nicht mehr in "
"anderen Dateien verfügbar. Bereits verwendete Assets bleiben in dieser Datei "
"erhalten (das Design wird nicht beeinträchtigt!)."
msgstr[1] ""
"Wenn Sie die Veröffentlichung aufheben, werden diese Assets in die lokale "
"Bibliothek dieser Datei verschoben."
"Wenn Sie die Veröffentlichung aufheben, sind diese Assets nicht mehr in "
"anderen Dateien verfügbar. Bereits verwendete Assets bleiben in dieser Datei "
"erhalten (das Design wird nicht beeinträchtigt!)."
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.hint-many"
msgid_plural "modals.unpublish-shared-confirm.hint-many"
msgstr[0] ""
"Wenn Sie die Veröffentlichung aufheben, werden diese Assets in die lokalen "
"Bibliotheken dieser Dateien verschoben."
"Wenn Sie die Veröffentlichung aufheben, sind diese Assets nicht mehr in "
"anderen Dateien verfügbar. Bereits verwendete Assets bleiben in diesen "
"Dateien erhalten (das Design wird nicht beeinträchtigt!)."
msgstr[1] ""
"Wenn Sie die Veröffentlichung aufheben, werden diese Assets in die lokalen "
"Bibliotheken dieser Dateien verschoben."
"Wenn Sie die Veröffentlichung aufheben, sind diese Assets nicht mehr in "
"anderen Dateien verfügbar. Bereits verwendete Assets bleiben in diesen "
"Dateien erhalten (das Design wird nicht beeinträchtigt!)."
#: src/app/main/ui/workspace/header.cljs,
#: src/app/main/ui/dashboard/file_menu.cljs
@@ -2494,7 +2499,7 @@ msgid "shortcuts.next-frame"
msgstr "Nächstes Board"
msgid "shortcuts.not-found"
msgstr "Kein Tastenkürzel gefunden"
msgstr "Kein Tastaturkürzel gefunden"
msgid "shortcuts.opacity-0"
msgstr "Deckkraft auf 100% setzen"
@@ -2563,7 +2568,7 @@ msgid "shortcuts.reset-zoom"
msgstr "Zoom zurücksetzen"
msgid "shortcuts.search-placeholder"
msgstr "Tastenkürzel suchen"
msgstr "Tastaturkürzel suchen"
msgid "shortcuts.select-all"
msgstr "Alles auswählen"
@@ -2575,7 +2580,7 @@ msgid "shortcuts.show-pixel-grid"
msgstr "Pixelraster ein-/ausblenden"
msgid "shortcuts.show-shortcuts"
msgstr "Tastenkürzel ein-/ausblenden"
msgstr "Tastaturkürzel ein-/ausblenden"
msgid "shortcuts.snap-nodes"
msgstr "An den Punkten ausrichten"
@@ -2597,7 +2602,7 @@ msgstr "Miniaturansichten festlegen"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs
msgid "shortcuts.title"
msgstr "Tastatürkürzel"
msgstr "Tastaturkürzel"
msgid "shortcuts.toggle-alignment"
msgstr "Dynamische Ausrichtung umschalten"
@@ -3800,7 +3805,7 @@ msgstr "Spalte"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.column-reverse"
msgstr "Spalte-umgekehrt"
msgstr "Spalte umkehren"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.row"
@@ -3808,7 +3813,7 @@ msgstr "Reihe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.row-reverse"
msgstr "Reihe-umgekehrt"
msgstr "Reihe umkehren"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.gap"
@@ -3891,7 +3896,7 @@ msgstr "Alle Ecken"
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.radius.single-corners"
msgstr "Individuelle Ecken"
msgstr "Ecken einzeln anpassen"
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.radius-top-left"
@@ -4515,7 +4520,7 @@ msgstr "Rechteck (%s)"
#: src/app/main/ui/workspace/left_toolbar.cljs
msgid "workspace.toolbar.shortcuts"
msgstr "Tastenkürzel (%s)"
msgstr "Tastaturkürzel (%s)"
#: src/app/main/ui/workspace/left_toolbar.cljs
msgid "workspace.toolbar.text"
@@ -4653,4 +4658,107 @@ msgid "workspace.updates.update"
msgstr "Aktualisieren"
msgid "workspace.viewport.click-to-close-path"
msgstr "Klicken Sie, um den Pfad zu schließen"
msgstr "Klicken Sie, um den Pfad zu schließen"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.success-duplicate-file"
msgid_plural "dashboard.success-delete-file"
msgstr[0] "Ihre Datei wurde erfolgreich dupliziert"
msgstr[1] "Ihre Dateien wurden erfolgreich dupliziert"
#: src/app/main/ui/inspect/attributes/text.cljs
msgid "inspect.attributes.typography.font-weight"
msgstr "Strichstärke"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.accept"
msgid_plural "modals.unpublish-shared-confirm.accept"
msgstr[0] "Veröffentlichung aufheben"
msgstr[1] "Veröffentlichung aufheben"
msgid "shortcuts.font-size-inc"
msgstr "Schriftgröße erhöhen"
msgid "shortcut-subsection.text-editor"
msgstr "Texte"
msgid "shortcuts.align-center"
msgstr "Zentrieren"
msgid "shortcuts.font-size-dec"
msgstr "Schriftgröße verkleinern"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.success-delete-file"
msgid_plural "dashboard.success-delete-file"
msgstr[0] "Ihre Datei wurde erfolgreich gelöscht"
msgstr[1] "Ihre Dateien wurden erfolgreich gelöscht"
msgid "workspace.header.menu.undo"
msgstr "Rückgängig"
msgid "workspace.header.menu.disable-scale-content"
msgstr "Proportionale Skalierung deaktivieren"
msgid "workspace.header.menu.enable-scale-content"
msgstr "Proportionale Skalierung aktivieren"
msgid "modals.invite-member.repeated-invitation"
msgstr ""
"Einige E-Mails stammen von aktuellen Teammitgliedern. Ihre Einladungen "
"werden nicht versendet."
msgid "shortcuts.letter-spacing-inc"
msgstr "Buchstabenabstand erhöhen"
msgid "shortcuts.select-prev"
msgstr "Vorherige Ebene auswählen"
msgid "shortcuts.select-next"
msgstr "Nächste Ebene auswählen"
msgid "shortcuts.align-justify"
msgstr "Blocksatz"
msgid "shortcuts.bold"
msgstr "Umschalten auf Fettdruck"
msgid "shortcuts.italic"
msgstr "Umschalten auf Kursivdruck"
msgid "shortcuts.letter-spacing-dec"
msgstr "Buchstabenabstand verringern"
msgid "shortcuts.line-height-inc"
msgstr "Zeilenhöhe erhöhen"
msgid "shortcuts.line-height-dec"
msgstr "Zeilenhöhe verringern"
msgid "workspace.header.menu.redo"
msgstr "Wiederherstellen"
#, markdown
msgid "dashboard.fonts.warning-text"
msgstr ""
"Wir haben ein mögliches Problem in Ihren Schriften festgestellt, das mit den "
"vertikalen Metriken für verschiedene Betriebssysteme zusammenhängt. Um dies "
"zu überprüfen, können Sie Online-Dienste wie [diesen](https://vertical-"
"metrics.netlify.app/) verwenden. Außerdem empfehlen wir die Verwendung von "
"[Transfonter](https://transfonter.org/), um Webfonts zu generieren und "
"Fehler zu beheben. "
msgid "shortcuts.line-through"
msgstr "Durchgestrichen"
msgid "shortcuts.underline"
msgstr "Unterstrichen"
msgid "shortcuts.zoom-lense-decrease"
msgstr "Ansicht mit Zoomwerkzeug verkleinern"
msgid "shortcuts.zoom-lense-increase"
msgstr "Ansicht mit Zoomwerkzeug vergrößern"
msgid "workspace.assets.duplicate-main"
msgstr "Hauptkomponente duplizieren"

View File

@@ -443,10 +443,6 @@ msgstr "Το όνομα χρήστη ή ο κωδικός πρόσβασης φ
msgid "errors.wrong-old-password"
msgstr "Ο παλιός κωδικός πρόσβασης είναι λάθος "
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.chat-subtitle"
msgstr "Νιώθετε σαν να μιλάτε; Συνομιλήστε μαζί μας στο Gitter"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.description"
msgstr "Περιγραφή"
@@ -538,10 +534,6 @@ msgstr "Πλάτος"
msgid "inspect.attributes.shadow"
msgstr "Σκιά "
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.offset-x"
msgstr "X"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.spread"
msgstr "S"
@@ -754,9 +746,6 @@ msgstr "Πίσω"
msgid "labels.hide-resolved-comments"
msgstr "Απόκρυψη επιλυμένων σχολίων"
msgid "labels.images"
msgstr "εικόνες"
#: src/app/main/ui/static.cljs
msgid "labels.internal-error.desc-message"
msgstr ""
@@ -1161,10 +1150,6 @@ msgstr "Πλήρης οθόνη"
msgid "viewer.header.share.copy-link"
msgstr "Αντιγραφή link"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.placeholder"
msgstr "Μοιραστείτε το link θα εμφανιστεί εδώ"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.subtitle"
msgstr "Όποιος έχει τον link θα έχει πρόσβαση"
@@ -1457,15 +1442,9 @@ msgstr "Ενημέρωση"
msgid "workspace.libraries.updates"
msgstr "ΕΝΗΜΕΡΩΣΕΙΣ"
msgid "workspace.library.libraries"
msgstr "βιβλιοθήκες"
msgid "workspace.library.store"
msgstr "Προκαθορισμένες"
msgid "workspace.options.blur-options.layer-blur"
msgstr "Στρώμα"
#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
msgid "workspace.options.blur-options.title"
msgstr "Θολούρα"
@@ -1815,10 +1794,6 @@ msgstr "Για ευθυγράμμιση προς τα δεξιά (%s)"
msgid "workspace.options.text-options.align-top"
msgstr "Ευθυγραμμίστε την κορυφή"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
msgid "workspace.options.text-options.google"
msgstr "Google"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.grow-auto-height"
msgstr "Αυτόματο ύψος"
@@ -2165,4 +2140,4 @@ msgid "workspace.updates.update"
msgstr "Ενημέρωση"
msgid "workspace.viewport.click-to-close-path"
msgstr "Κάντε κλικ για να κλείσετε τη διαδρομή"
msgstr "Κάντε κλικ για να κλείσετε τη διαδρομή"

View File

@@ -2704,6 +2704,9 @@ msgstr "Snap to pixel grid"
msgid "shortcuts.start-editing"
msgstr "Start editing"
msgid "shortcuts.select-parent-layer"
msgstr "Select parent layer"
msgid "shortcuts.start-measure"
msgstr "Start measurement"
@@ -4960,3 +4963,7 @@ msgstr "Marketing"
#: src/app/main/ui/onboarding/questions.cljs
msgid "questions.student-teacher"
msgstr "Student or teacher"
#: src/app/main/data/common.cljs
msgid "notifications.by-code.upgrade-version"
msgstr "A new version is available, please refresh the page"

View File

@@ -2778,6 +2778,9 @@ msgstr "Activar alineación a rejilla de pixel"
msgid "shortcuts.start-editing"
msgstr "Comenzar edición"
msgid "shortcuts.select-parent-layer"
msgstr "Seleccionar capa padre"
msgid "shortcuts.start-measure"
msgstr "Comenzar medida"

View File

@@ -1,15 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2023-01-24 14:27+0000\n"
"PO-Revision-Date: 2023-07-01 12:52+0000\n"
"Last-Translator: Mikel Larreategi <mlarreategi@codesyntax.com>\n"
"Language-Team: Basque "
"<https://hosted.weblate.org/projects/penpot/frontend/eu/>\n"
"Language-Team: Basque <https://hosted.weblate.org/projects/penpot/frontend/"
"eu/>\n"
"Language: eu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.16-dev\n"
"X-Generator: Weblate 5.0-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
@@ -77,11 +77,11 @@ msgstr "Google"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit"
msgstr "Sartu LDAP erabiliz"
msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit"
msgstr "OpenID Connect"
msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password"
@@ -231,7 +231,7 @@ msgstr "Taldearen kudeaketa"
msgid "dasboard.team-hero.text"
msgstr ""
"Penpot taldeentzat sortuta dago. Gonbidatu beste pertsona batzuk proiektu "
"eta fitxategietan batera lan egiteko."
"eta fitxategietan batera lan egiteko"
#: src/app/main/ui/dashboard/projects.cljs
msgid "dasboard.team-hero.title"
@@ -306,8 +306,8 @@ msgstr "%s fitxategi bizkoiztu"
msgid "dashboard.empty-placeholder-drafts"
msgstr ""
"Oh ez! Oraindik ez duzu fitxategirik! Txantiloi batekin proba egin nahi "
"baduzu joan [Liburutegi eta "
"txantiloiak](https://penpot.app/libraries-templates.html) atalera"
"baduzu joan [Liburutegi eta txantiloiak](https://penpot.app/libraries-"
"templates.html) atalera."
msgid "dashboard.export-binary-multi"
msgstr "Deskargatu %s Penpot fitxategi (.penpot)"
@@ -412,12 +412,11 @@ msgstr ""
#, markdown
msgid "dashboard.fonts.hero-text2"
msgstr ""
"Zurak diren edo Penpoten erabiltzeko lizentzia duzun letra-tipoak bakarrik "
"kargatu ditzakezu. Informazio gehiago lortzeko irakurri Edukiaren "
"eskubideen atala: [Penpoten erabilpen "
"baldintzak](https://penpot.app/terms.html). Letra-tipoen lizentzien "
"inguruan irakurtzea ere interesgarria izan daiteke: [letra-tipoen "
"lizentziak](https://www.typography.com/faq)."
"Zureak diren edo Penpoten erabiltzeko lizentzia duzun letra-tipoak bakarrik "
"kargatu ditzakezu. Informazio gehiago lortzeko irakurri Edukiaren eskubideen "
"atala: [Penpoten erabilpen baldintzak](https://penpot.app/terms.html). Letra-"
"tipoen lizentzien inguruan irakurtzea ere interesgarria izan daiteke: [letra-"
"tipoen lizentziak](https://www.typography.com/faq)."
#: src/app/main/ui/dashboard/fonts.cljs
msgid "dashboard.fonts.upload-all"
@@ -432,9 +431,6 @@ msgstr "Ezin izan dugu fitxategia inportatu"
msgid "dashboard.import.import-error"
msgstr "Errorea gertatu da fitxategia inportatzean. Ezin izan da inportatu."
msgid "dashboard.import.import-message"
msgstr "%s fitxategi ondo inportatu dira."
msgid "dashboard.import.import-warning"
msgstr "Fitxategi batzuk inportatu ez diren objektu akasdunak dituzte."
@@ -515,10 +511,6 @@ msgstr "+ Proiektu berria"
msgid "dashboard.new-project-prefix"
msgstr "Proiektu berria"
#: src/app/main/ui/settings/profile.cljs
msgid "dashboard.newsletter-title"
msgstr "Buletineko harpidetza"
#: src/app/main/ui/dashboard/search.cljs
msgid "dashboard.no-matches-for"
msgstr "Ez da \"%s\" aurkitu"
@@ -594,18 +586,10 @@ msgstr "Aukeratu gaia"
msgid "dashboard.show-all-files"
msgstr "Ikusi fitxategi guztiak"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.success-delete-file"
msgstr "Zure fitxategia ondo ezabatu da"
#: src/app/main/ui/dashboard/project_menu.cljs
msgid "dashboard.success-delete-project"
msgstr "Zure proiektua ondo ezabatu da"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.success-duplicate-file"
msgstr "Zure fitxategia ondo bikoiztu da"
#: src/app/main/ui/dashboard/project_menu.cljs
msgid "dashboard.success-duplicate-project"
msgstr "Zure proiektua ondo bikoiztu da"
@@ -820,10 +804,6 @@ msgstr "Izena edo pasahitza ez dira zuzenak."
msgid "errors.wrong-old-password"
msgstr "Aurreko pasahitza ez da zuzena"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.chat-subtitle"
msgstr "Hitz egin nahi duzu? Zatuz gure komunitatearen Gitter txatera"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.description"
msgstr "Deskribapena"
@@ -938,14 +918,6 @@ msgstr "Zabalera"
msgid "inspect.attributes.shadow"
msgstr "Itzala"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.offset-x"
msgstr "X"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.spread"
msgstr "S"
#: src/app/main/ui/inspect/attributes/stroke.cljs
msgid "inspect.attributes.stroke"
msgstr "Ertza"
@@ -1238,9 +1210,6 @@ msgstr "Laguntza zentroa"
msgid "labels.hide-resolved-comments"
msgstr "Ezkutatu ebatzitzako iruzkinak"
msgid "labels.images"
msgstr "Irudiak"
msgid "labels.installed-fonts"
msgstr "Instalatutako letra-tipoak"
@@ -1287,7 +1256,7 @@ msgstr "Pasahitz berria"
#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs
msgid "labels.no-comments-available"
msgstr "Ez duzu iruzkinen inguruko jakinarazpenik"
msgstr "Ez duzu iruzkinen inguruko jakinarazpenik."
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.no-invitations"
@@ -1430,9 +1399,6 @@ msgstr "Erakutsi iruzkinen zerrenda"
msgid "labels.show-your-comments"
msgstr "Erakutsi zure iruzkinak bakarrik"
msgid "labels.skip"
msgstr "Alde batera utzi"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.status"
msgstr "Egoera"
@@ -1758,19 +1724,17 @@ msgstr "Gehitu \"%s\" partekatutako liburutegi bezala"
msgid "modals.small-nudge"
msgstr "Gutxienekoa"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.accept"
msgstr "Argitaratzea atzera bota"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.hint"
msgid_plural "modals.unpublish-shared-confirm.hint"
msgstr[0] ""
"Argitaratzea atzera botatzen baduzu, elementuak fitxategiaren liburutegira "
"pasatuko dira."
"Argitaratzea atzera botatzen baduzu, elementu horiek ez dira beste "
"fitxategietan agertuko. Elementuak erabiltzen ari bazara fitxategi honetan "
"geldituko dira (diseinurik ez da apurtuko!)."
msgstr[1] ""
"Argitaratzea atzera botatzen baduzu, elementuak fitxategien liburutegietara "
"pasatuko dira."
"Argitaratzea atzera botatzen baduzu, elementu horiek ez dira beste "
"fitxategietan agertuko. Elementuak erabiltzen ari bazara fitxategi honetan "
"geldituko dira (diseinurik ez da apurtuko!)."
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.message"
@@ -1833,8 +1797,8 @@ msgstr "Profila ondo gorde da!"
#: src/app/main/ui/settings/change_email.cljs
msgid "notifications.validation-email-sent"
msgstr ""
"Posta elektronikoa egiaztatzeko mezua ondo bidali da %s helbidera. "
"Egiaztatu zure eposta."
"Posta elektronikoa egiaztatzeko mezua ondo bidali da %s helbidera. Egiaztatu "
"zure helbidea!"
msgid "onboarding-v2.before-start.desc1"
msgstr ""
@@ -1935,15 +1899,6 @@ msgstr "Sortu taldea eta bidali gonbidapenak"
msgid "onboarding.choice.team-up.roles"
msgstr "Gonbidatu rol honekin:"
msgid "onboarding.contrib.alt"
msgstr "Kode Irekia"
msgid "onboarding.contrib.desc2.1"
msgstr "Hona sar zaitezke"
msgid "onboarding.contrib.link"
msgstr "proiektua githuben"
msgid "onboarding.newsletter.accept"
msgstr "Bai, harpidetu"
@@ -1958,31 +1913,6 @@ msgstr "Pribatutasun politika."
msgid "onboarding.newsletter.title"
msgstr "Penpoti buruzko albisteak jaso nahi dituzu?"
msgid "onboarding.slide.0.desc1"
msgstr "Sortu interfaze ederrak taldeko beste kideekin batera."
msgid "onboarding.slide.0.title"
msgstr "Diseinu-, estilo- eta osagai-liburutegiak"
msgid "onboarding.slide.1.desc1"
msgstr "Sortu produktuaren portaera imitatzeko interakzio osoak."
msgid "onboarding.slide.1.title"
msgstr "Eman bizia zure diseinuen interakzioak erabiliz"
msgid "onboarding.slide.2.desc1"
msgstr ""
"Taldekide guztiak fitxategi berberen gainean lanean, aldi berean eta "
"diseinuen gainean iruzkinak egiteko aukerarekin."
msgid "onboarding.slide.3.alt"
msgstr "Kodearen ezarpenak"
msgid "onboarding.slide.3.desc2"
msgstr ""
"Lortu eta eman kode (SVG, HTML) eta estilorako (CSS, Less, Stylus...) "
"kodearen zehaztapenak."
msgid "onboarding.team-modal.create-team"
msgstr "Sortu talde bat"
@@ -2542,14 +2472,6 @@ msgstr "Interakzioak (%s)"
msgid "viewer.header.share.copy-link"
msgstr "Kopiatu esteka"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.placeholder"
msgstr "Partekatzeko esteka hemen agertuko da"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.subtitle"
msgstr "Esteka duen edonor sar daiteke"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.show-interactions"
msgstr "Erakutsi interakzioak"
@@ -2985,19 +2907,10 @@ msgstr "Eguneratu"
msgid "workspace.libraries.updates"
msgstr "EGUNERAKETAK"
msgid "workspace.library.libraries"
msgstr "Liburutegiak"
msgid "workspace.library.store"
msgstr "Gorde liburutegiak"
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.add-interaction"
msgstr "Sakatu + botoia interakzioak gehitzeko."
msgid "workspace.options.blur-options.layer-blur"
msgstr "Geruza"
#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
msgid "workspace.options.blur-options.title"
msgstr "Lausotu"
@@ -3633,7 +3546,7 @@ msgstr "Ertz guztiak"
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.radius.single-corners"
msgstr "Ertz bakarrak"
msgstr "Ertz independenteak"
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.radius-top-left"
@@ -4337,4 +4250,358 @@ msgid "workspace.updates.update"
msgstr "Eguneratu"
msgid "workspace.viewport.click-to-close-path"
msgstr "Egin klik bidea ixteko"
msgstr "Egin klik bidea ixteko"
msgid "errors.webhooks.invalid-uri"
msgstr "URLak ez du balidazioa gainditu."
msgid "dashboard.webhooks.content-type"
msgstr "Elementu mota"
#, markdown
msgid "dashboard.fonts.warning-text"
msgstr ""
"Zure letra-tipoek sistema eragile desberdinetan metrika bertikalekin arazoak "
"izan ditzaketela detektatu dugu. Zure letra-tipoa nola ikusten den zerbitzu "
"[hau](https://vertical-metrics.netlify.app) erabiliz egiaztatu dezakezu. "
"Gainera, weberako letra-tipoak sortzeko [Transfonter](https://transfonter."
"org/) erabiltzea gomendatzen dugu. "
msgid "dashboard.webhooks.active"
msgstr "Aktibo"
msgid "dashboard.webhooks.description"
msgstr ""
"Webhookak beste webgune batzuei Penpoten zerbait gertatu dela jakinarazteko "
"modu bat dira. Adierazitako URLtara POST eskaera bat bidaliko dugu."
#: src/app/main/errors.cljs
msgid "errors.feature-not-supported"
msgstr "Ezaugarria ezin da erabili: '%s'."
msgid "errors.webhooks.unexpected-status"
msgstr "Espero ez zen egoera %s"
#: src/app/main/errors.cljs
msgid "errors.max-quote-reached"
msgstr ""
"Kuotaren maximora heldu zara: '%s'. Jarri kontaktuan laguntza zerbitzuarekin."
msgid "errors.webhooks.ssl-validation"
msgstr "Errorea gertatu da SSL balidazioan."
msgid "errors.webhooks.unexpected"
msgstr "Errore ezezaguna balidazioan"
#: src/app/main/ui/inspect/attributes/layout.cljs
msgid "inspect.attributes.size"
msgstr "Tamaina eta posizioa"
#: src/app/main/ui/inspect/attributes/text.cljs
msgid "inspect.attributes.typography.font-weight"
msgstr "Letra tipoaren lodiera"
msgid "inspect.empty.help"
msgstr ""
"Diseinua ikuskatzeari buruz gehiago jakin nahi baduzu zoaz Penpoten laguntza "
"zentrora"
msgid "inspect.empty.more-info"
msgstr "Informazio gehiago ikuskatzeari buruz"
msgid "inspect.empty.select"
msgstr ""
"Aukeratu forma bat, taula bat edo talde bat beren propietateak eta kodea "
"ikuskatzeko"
msgid "labels.view-only"
msgstr "IKUSTEKO BAKARRIK"
msgid "modals.create-webhook.url.label"
msgstr "Informazioaren URLa"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-shared-confirm.hint"
msgid_plural "modals.delete-shared-confirm.hint"
msgstr[0] ""
"Ezabatzen baduzu, bere elementuak ezingo dira beste fitxategietan erabili. "
"Jada erabiltzen ari zaren elementuak fitxategi honetan geldituko dira (ez da "
"diseinurik apurtuko!)."
msgstr[1] ""
"Ezabatzen badituzu, bere elementuak ezingo dira beste fitxategietan erabili. "
"Jada erabiltzen ari zaren elementuak fitxategi honetan geldituko dira (ez da "
"diseinurik apurtuko!)."
msgid "labels.active"
msgstr "Aktibo"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-shared-confirm.hint-many"
msgid_plural "modals.delete-shared-confirm.hint-many"
msgstr[0] ""
"Ezabatzen baduzu, bere elementuak ezingo dira beste fitxategietan erabili. "
"Jada erabiltzen ari zaren elementuak fitxategi honetan geldituko dira (ez da "
"diseinurik apurtuko!)."
msgstr[1] ""
"Ezabatzen badituzu, bere elementuak ezingo dira beste fitxategietan erabili. "
"Jada erabiltzen ari zaren elementuak fitxategi honetan geldituko dira (ez da "
"diseinurik apurtuko!)."
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.copy-invitation-link"
msgstr "Kopiatu esteka"
#: src/app/main/ui/workspace.cljs
msgid "labels.reload-file"
msgstr "Birkargatu fitxategia"
msgid "labels.inactive"
msgstr "Inaktibo"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.hint-many"
msgid_plural "modals.unpublish-shared-confirm.hint-many"
msgstr[0] ""
"Argitaratzea atzera botatzen baduzu, elementu horiek ezingo dira beste "
"fitxategietan erabili. Jada erabiltzen diren elementuak fitxategi horietan "
"geldituko dira (diseinurik ez da apurtuko!)."
msgstr[1] ""
"Argitaratzea atzera botatzen baduzu, elementu horiek ezingo dira beste "
"fitxategietan erabili. Jada erabiltzen diren elementuak fitxategi horietan "
"geldituko dira (diseinurik ez da apurtuko!)."
msgid "shortcuts.align-center"
msgstr "Erdian lerrokatu"
msgid "shortcuts.toggle-layout-flex"
msgstr "Gehitu/kendu flex diseinua"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.scd-message-many"
msgid_plural "modals.unpublish-shared-confirm.scd-message-many"
msgstr[0] "Liburutegi honetako elementuak hemen ari dira erabiltzen:"
msgstr[1] "Liburutegi hauetako elementuak hemen ari dira erabiltzen:"
msgid "shortcut-subsection.text-editor"
msgstr "Testuak"
msgid "shortcuts.italic"
msgstr "Aktibatu/desaktibatu etzana"
msgid "shortcuts.letter-spacing-dec"
msgstr "Hizkien arteko espazioa txikitu"
msgid "shortcuts.letter-spacing-inc"
msgstr "Hizkien arteko espazioa handitu"
msgid "workspace.header.menu.redo"
msgstr "Berregin"
msgid "shortcuts.select-next"
msgstr "Aukeratu hurrengo geruza"
msgid "workspace.assets.duplicate-main"
msgstr "Bikoiztu nagusia"
msgid "shortcuts.zoom-lense-increase"
msgstr "Zooma handitu"
msgid "workspace.header.menu.disable-scale-content"
msgstr "Desaktibatu eskala proportzionala"
msgid "workspace.header.menu.undo"
msgstr "Desegin"
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.interaction-relative-to"
msgstr "Honekiko erlatiboa"
#: src/app/main/ui/workspace.cljs
msgid "workspace.remove-graphics.error-hint"
msgstr ""
"Berriz saiatzeko, fitxategi hau berriz kargatu dezakezu. Hala ere arazoa "
"izaten jarraitzen baduzu, begiratu zerrenda eta ezabatu apurtutako grafikoak."
#: src/app/main/ui/workspace.cljs
msgid "workspace.remove-graphics.text1"
msgstr ""
"Liburutegiko grafikoak osagaiak izango dira orain, horrek ahaltsuago egingo "
"ditu."
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.remove-flex"
msgstr "Ezabatu flex diseinua"
msgid "labels.webhooks"
msgstr "Webhookak"
msgid "title.team-webhooks"
msgstr "Webhookak - %s - Penpot"
msgid "webhooks.last-delivery.success"
msgstr "Azken bidalketa ondo joan da."
msgid "dashboard.webhooks.active.explain"
msgstr "Webhook hau aktibatzen denean gertaeraren xehetasunak bidaliko dira"
msgid "dashboard.webhooks.create"
msgstr "Sortu webhooka"
msgid "dashboard.webhooks.create.success"
msgstr "Webhooka ondo sortu da."
msgid "dashboard.webhooks.empty.add-one"
msgstr "Sakatu \"Sortu webhooka\" botoia bat gehitzeko."
msgid "dashboard.webhooks.empty.no-webhooks"
msgstr "Ez dago webhookik."
msgid "dashboard.webhooks.update.success"
msgstr "Webhooka ondo aldatu da."
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs, src/app/main/ui/auth/recovery_request.cljs
msgid "errors.email-invalid"
msgstr "Mesedez, idatzi eposta helbide zuzen bat"
#: src/app/main/errors.cljs
msgid "errors.feature-mismatch"
msgstr ""
"Badirudi '%s' ezaugarria aktibo duen fitxategi bat irekitzen ari zarela "
"baina zure penpot frontendak ezin du hori egin edo ezaugarri hori "
"desaktibatuta du."
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "labels.unpublish-multi-files"
msgstr "%s fitxategi argitaratzeari utzi"
msgid "modals.create-webhook.submit-label"
msgstr "Sortu webhooka"
msgid "modals.create-webhook.title"
msgstr "Sortu webhooka"
msgid "modals.create-webhook.url.placeholder"
msgstr "https://example.com/postreceive"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-shared-confirm.no-files-message"
msgid_plural "modals.delete-shared-confirm.no-files-message"
msgstr[0] ""
"Fitxategi honetako liburutegiko elementuak ez dira inon erabiltzen. "
"Fitxategiarekin batera ezabatuko dira."
msgstr[1] ""
"Fitxategi hautetako liburutegiko elementuak ez dira inon erabiltzen. "
"Fitxategiarekin batera ezabatuko dira."
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-shared-confirm.scd-message-many"
msgid_plural "modals.delete-shared-confirm.scd-message-many"
msgstr[0] "Fitxategi honen liburutegiko elementu batzuk hemen erabiltzen ari zara:"
msgstr[1] ""
"Fitxategi hauen liburutegietako elementu batzuk hemen erabiltzen ari zara:"
msgid "modals.delete-webhook.accept"
msgstr "Ezabatu webhooka"
msgid "modals.delete-webhook.message"
msgstr "Benetan webhook hau ezabatu egin nahi duzu?"
msgid "modals.delete-webhook.title"
msgstr "Webhooka ezabatzen"
msgid "modals.edit-webhook.submit-label"
msgstr "Aldatu webhooka"
msgid "modals.edit-webhook.title"
msgstr "Aldatu webhooka"
msgid "modals.invite-member.repeated-invitation"
msgstr ""
"Eposta helbide batzuk jada taldekideenak dira. Ez da gonbidapenik bidaliko."
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.no-files-message"
msgid_plural "modals.unpublish-shared-confirm.no-files-message"
msgstr[0] "Liburutegi honetako elementuak ez dira erabiltzen ari."
msgstr[1] "Liburutegi hauetako elementuak ez dira erabiltzen ari."
#: src/app/main/ui/dashboard/team.cljs
msgid "notifications.invitation-link-copied"
msgstr "Gonbidapenaren esteka kopiatu da"
msgid "shortcuts.align-justify"
msgstr "Justifikatuta lerrokatu"
msgid "shortcuts.bold"
msgstr "Aktibatu/desaktibatu beltza"
msgid "shortcuts.font-size-dec"
msgstr "Letra tipoaren tamaina txikitu"
msgid "shortcuts.font-size-inc"
msgstr "Letra tipoaren tamaina handitu"
msgid "shortcuts.line-height-dec"
msgstr "Lerroen arteko tartea txikitu"
msgid "shortcuts.line-height-inc"
msgstr "Lerroen arteko tartea handitu"
msgid "shortcuts.line-through"
msgstr "Aktibatu/desaktibatu marratzea"
msgid "shortcuts.open-inspect"
msgstr "Ikuskagailura joan"
msgid "shortcuts.select-prev"
msgstr "Aukeratu aurreko geruza"
msgid "shortcuts.underline"
msgstr "Aktibatu/desaktibatu azpimarraketa"
msgid "shortcuts.zoom-lense-decrease"
msgstr "Zooma txikitu"
msgid "viewer.header.inspect-section"
msgstr "Ikuskagailua (%s)"
msgid "workspace.assets.typography.text-styles"
msgstr "Testuen estiloak"
msgid "workspace.header.menu.enable-scale-content"
msgstr "Aktibatu eskala proportzionala"
msgid "workspace.options.inspect"
msgstr "Ikuskatu"
msgid "workspace.options.interaction-auto"
msgstr "automatikoa"
#: src/app/main/ui/workspace.cljs
msgid "workspace.remove-graphics.error-msg"
msgstr "Grafiko batzuk ezin izan dira eguneratu."
#: src/app/main/ui/workspace.cljs
msgid "workspace.remove-graphics.progress"
msgstr "Bihurtzen %s/%s"
#: src/app/main/ui/workspace.cljs
msgid "workspace.remove-graphics.text2"
msgstr "Eguneraketa hau behin bakarrik gertatuko da."
#: src/app/main/ui/workspace.cljs
msgid "workspace.remove-graphics.title"
msgstr "Eguneratzen %s..."
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.add-flex"
msgstr "Gehitu flex diseinua"
msgid "errors.webhooks.last-delivery"
msgstr "Errore bat gertatu da azken bidalketan."
msgid "errors.webhooks.timeout"
msgstr "Denbora muga gainditu da"
msgid "errors.webhooks.connection"
msgstr "Konexio errorea, URLa ezin da ireki"

View File

@@ -519,10 +519,6 @@ msgstr "+ پروژه جدید"
msgid "dashboard.new-project-prefix"
msgstr "پروژه جدید"
#: src/app/main/ui/settings/profile.cljs
msgid "dashboard.newsletter-title"
msgstr "اشتراک خبرنامه"
#: src/app/main/ui/dashboard/search.cljs
#, fuzzy
msgid "dashboard.no-matches-for"
@@ -866,10 +862,6 @@ msgstr "حساب پشتیبانی در توییتر"
msgid "generic.error"
msgstr "خطایی رخ داده است"
#, fuzzy
msgid "handoff.attributes.typography.text-transform.titlecase"
msgstr "مورد عنوان"
#: src/app/main/ui/inspect/attributes/blur.cljs
msgid "inspect.attributes.blur"
msgstr "محو"
@@ -938,10 +930,6 @@ msgstr "عرض"
msgid "inspect.attributes.shadow"
msgstr "سایه"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.offset-x"
msgstr "X"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.spread"
msgstr "S"
@@ -1232,9 +1220,6 @@ msgstr "مرکز کمک"
msgid "labels.hide-resolved-comments"
msgstr "پنهان کردن نظرات حل شده"
msgid "labels.images"
msgstr "تصاویر"
msgid "labels.installed-fonts"
msgstr "فونت‌های نصب‌شده"
@@ -1427,9 +1412,6 @@ msgstr "نمایش لیست نظرات"
msgid "labels.show-your-comments"
msgstr "فقط نظرات خودتان را نشان دهید"
msgid "labels.skip"
msgstr "رد"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.status"
msgstr "وضعیت"
@@ -1987,10 +1969,6 @@ msgstr "تعاملات (%s)"
msgid "viewer.header.share.copy-link"
msgstr "کپی کردن لینک"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.placeholder"
msgstr "لینک اشتراک‌گذاری در اینجا ظاهر می‌شود"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.subtitle"
msgstr "هر کسی که لینک را داشته باشد دسترسی خواهد داشت"
@@ -2212,9 +2190,6 @@ msgstr "به‌روزرسانی"
msgid "workspace.libraries.updates"
msgstr "به‌روزرسانی‌ها"
msgid "workspace.options.blur-options.background-blur"
msgstr "پس‌زمینه"
#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
msgid "workspace.options.blur-options.title"
msgstr "محو"
@@ -3050,4 +3025,4 @@ msgid "workspace.updates.update"
msgstr "به‌روزرسانی"
msgid "workspace.viewport.click-to-close-path"
msgstr "برای بستن مسیر کلیک کنید"
msgstr "برای بستن مسیر کلیک کنید"

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