Compare commits

..

124 Commits

Author SHA1 Message Date
Andrey Antukh
404297f837 Merge pull request #5537 from penpot/azazeln28-fix-text-editor-issue-9716
🐛 Fix text editor issue 9716
2025-01-10 10:24:23 +01:00
Andrey Antukh
33f853ff2e Merge pull request #5511 from penpot/alotor-fix-touched-import
🐛 Fix error when importing files with touched components
2025-01-09 17:13:22 +01:00
alonso.torres
d16513be9d 🐛 Fix error when importing files with touched components 2025-01-09 16:58:40 +01:00
AzazelN28
ad077696b0 🐛 Fix Unknown node type when replacing whole root node 2025-01-09 16:28:32 +01:00
Andrey Antukh
1cbeafe85c Merge pull request #5525 from penpot/palba-testabc-add-library
A/B/C test for rename and change look and feel of "libraries" button
2025-01-09 08:34:05 +01:00
Pablo Alba
76c8523f44 Add test ABC renaming "Libraries" to "Add library" 2025-01-08 13:20:52 +01:00
Pablo Alba
f277d8b125 Revert " Add test AB renaming "Libraries" to "Add library""
This reverts commit 664cacbe9d.
2025-01-08 11:05:50 +01:00
Andrey Antukh
60af8d0bcb Merge pull request #5520 from penpot/azazeln28-fix-text-editor-issue-9285
Fix Copy/Paste text into the text block
2025-01-07 14:26:11 +01:00
Juanfran
d652ed8e68 Merge pull request #5519 from penpot/alotor-fix-plugins-current-user
🐛 Fix problem with currentUser in plugins
2025-01-07 14:05:07 +01:00
AzazelN28
09d73a2f51 🐛 Fix pasting text changes opacity to 0 2025-01-07 13:48:32 +01:00
alonso.torres
7d4535ebd4 🐛 Fix problem with currentUser in plugins 2025-01-07 13:03:56 +01:00
Andrey Antukh
a5a53219bf Merge remote-tracking branch 'origin/main' into staging 2025-01-07 12:00:09 +01:00
Marina López
8716f81765 Merge pull request #5456 from daledesilva/patch-1
📚 Add submission form link to deployment page
2025-01-07 10:58:05 +01:00
Andrey Antukh
7aa46a1f62 🔥 Remove 2.4.1 from changelog 2025-01-07 10:51:46 +01:00
Andrey Antukh
d62eb3d3f4 Merge pull request #5504 from penpot/palba-add-feature-flags-to-events
 Add feature flags info to posthog events
2025-01-07 09:34:22 +01:00
Pablo Alba
e4c427609d Add feature flags info to posthog events 2025-01-07 09:19:04 +01:00
Aitor Moreno
883a26845a Merge pull request #5510 from penpot/alotor-fix-thumbnail-generation
🐛 Fix problem with thumbnail generation when changing between ver…
2025-01-02 12:46:59 +01:00
alonso.torres
bcdf5d86ae 🐛 Fix problem with thumbnail generation when changing between versions 2025-01-02 12:01:30 +01:00
Pablo Alba
3eab9da74e Merge pull request #5492 from penpot/alotor-christmas-bugfixes
🐛 Fix problem with component swap style
2024-12-26 16:39:23 +01:00
alonso.torres
2813fda136 🐛 Fix problem with component swap style 2024-12-23 12:37:24 +01:00
Andrey Antukh
a0022a804b Merge branch 'backrunner-patch/backrunner_nginx-conf' into staging 2024-12-23 10:28:34 +01:00
BackRunner
068acb4303 🐛 Fix assets proxy ssl handshake error 2024-12-23 10:28:10 +01:00
Andrey Antukh
d6f98a6c79 📎 Add externs for worker build 2024-12-23 10:03:10 +01:00
Andrey Antukh
7b6c2da6da 📎 Add externs for main build
With all combinations of small symbols for indicate
to compiler to not use them for internal renaming
2024-12-23 10:03:10 +01:00
Andrey Antukh
affed049ee Merge remote-tracking branch 'origin/main' into staging 2024-12-19 10:27:12 +01:00
alonso.torres
377f636b8e 🐛 Fix problem with reorder on grid layout layers 2024-12-18 13:32:30 +01:00
Alejandro Alonso
2d512ef273 🐛 Fix path edition for plugins 2024-12-18 10:12:01 +01:00
Andrey Antukh
b8ebbe8c3c Merge pull request #5468 from penpot/palba-abtest-add-library-button2
 Add test AB renaming "Libraries" to "Add library"
2024-12-13 12:20:56 +01:00
Pablo Alba
09c184200d Merge pull request #5471 from penpot/niwinz-bugfix-5
🐛 Fix unhandled exception on accepting invitation
2024-12-13 12:15:35 +01:00
Andrey Antukh
bbe0b22a8b 🐛 Fix unhandled exception on accepting invitation
In an interaction with the audit log; happens when an old invitation
(with created-by as nil) is accepted.
2024-12-13 11:48:37 +01:00
Andrey Antukh
8603085a69 Merge pull request #5470 from penpot/palba-add-config-not-show-release
🎉 Add config flag to hide release info
2024-12-13 11:28:27 +01:00
Andrey Antukh
29ec44482d Merge pull request #5463 from penpot/andy-docs-adjustments
📚 Remove broken video about History versions
2024-12-13 11:17:27 +01:00
Andrey Antukh
fd4d4ec6e3 Merge pull request #5465 from penpot/palba-fix-viewer-role-thumbnails
🐛 Fix viewer role is unable to generate thumbnails on dashboard
2024-12-13 11:16:32 +01:00
Pablo Alba
0945dd2920 🎉 Add config flag to hide release info 2024-12-13 11:14:59 +01:00
Pablo Alba
664cacbe9d Add test AB renaming "Libraries" to "Add library" 2024-12-13 10:51:41 +01:00
Pablo Alba
08516ac7ca Revert " Add test AB renaming "Libraries" to "Add library""
This reverts commit ec7f8a6aa7.
2024-12-13 10:51:25 +01:00
Pablo Alba
24e51eef5b 🐛 Fix viewer role is unable to generate thumbnails on dashboard 2024-12-12 15:15:27 +01:00
Andrey Antukh
ee62016c34 Merge pull request #5462 from penpot/palba-dont-show-release-info
🐛 Fix a new user shouldn't see the "What's new" popup
2024-12-12 12:27:14 +01:00
andy
7c10f20b95 📚 Remove deprecated video at History version 2024-12-12 11:57:08 +01:00
Pablo Alba
4958da63e5 🐛 Fix a new user shouldn't see the "What's new" popup 2024-12-12 11:45:58 +01:00
andy
f39a994fed 📚 Import/export images replacements and other adjustments 2024-12-12 08:38:00 +01:00
andy
5ef59d5e2e 📚 New penpot file format 2024-12-12 08:37:53 +01:00
andy
98221c6b51 📚 Images and text adjustments 2024-12-12 08:37:40 +01:00
Madalena Melo
74713cde63 File history versions
https://tree.taiga.io/project/penpot/task/9377
2024-12-12 08:37:32 +01:00
andrés gonzález
a5084c35b5 Merge pull request #5461 from penpot/andy-docs-blending
📚 Info about layers blend and opacity
2024-12-12 08:29:41 +01:00
andrés gonzález
cdce1df919 Merge pull request #5460 from penpot/andy-shortcuts-update
📚 History shortcut removed
2024-12-12 08:27:48 +01:00
andy
c75b886548 📚 Info about layers blend and opacity 2024-12-12 07:03:54 +01:00
andy
abd41e825e 📚 History shortcut removed 2024-12-12 06:22:51 +01:00
Andrey Antukh
97b9a7d31c 🐛 Remove hardcoded limit of 20 on the snapshot list rpc method 2024-12-11 14:13:04 +01:00
Dale de Silva
dbeebf181f 📚 Add submission form link to deployment page
The plugin submission page is hard to find while looking at the plugin help docs (As it's not linked from there). It should eventually be a page of its own but there isn't enough content yet (or an illustration) to support it.
2024-12-11 23:51:11 +11:00
Andrey Antukh
0eec09acbf Merge pull request #5441 from penpot/juan-slides-2.4
Juan slides 2.4
2024-12-11 12:49:31 +01:00
Elhombretecla
81e250e27d 🎉 Add slides for 2.4 release 2024-12-11 12:36:29 +01:00
Alejandro
9f1f8cc80c Merge pull request #5438 from penpot/andy-docs-selfhostsettings
📚 Recommended settings for selfhost
2024-12-11 07:57:23 +01:00
andy
2440c81b42 📚 Recommended settings for selfhost 2024-12-11 07:42:01 +01:00
Alejandro
ada078abab Merge pull request #5439 from penpot/niwinz-delete-xlog-gc-task
🔥 Remove file-xlog-gc task
2024-12-11 07:29:39 +01:00
Andrey Antukh
31319a0d04 Merge pull request #5442 from penpot/alotor-view-only-menu
🐛 Remove file history versions menu options on view mode
2024-12-10 15:20:57 +01:00
alonso.torres
b9cb415507 🐛 Remove file history versions menu options on view mode 2024-12-10 14:41:00 +01:00
Andrey Antukh
36121d862d Merge pull request #5440 from penpot/palba-ab-share-in-workspace
 Fix test A/B add share button to the workspace
2024-12-10 13:48:49 +01:00
Pablo Alba
d8964a69bc Fix test A/B add share button to the workspace 2024-12-10 13:33:04 +01:00
Andrey Antukh
39da7d7ab6 Merge pull request #5429 from penpot/palba-bugfixing-01
🐛 Palba bugfixing 01
2024-12-10 12:33:38 +01:00
Andrey Antukh
1bb25bb89d 🔥 Remove file-xlog-gc task
It is no longer necessary because snapshots are managed by the objects-gc task
2024-12-10 12:19:12 +01:00
Pablo Alba
b0a3f2b72a 🐛 Fix history panel remains open after restoring a version 2024-12-10 11:00:54 +01:00
Pablo Alba
f2f3d9f7eb 💄 Fix css format (spacing) 2024-12-10 10:53:12 +01:00
Pablo Alba
cf72b35e73 🐛 Fix separator lines have no color at some menus 2024-12-10 10:53:12 +01:00
Andrey Antukh
fe8d9cf159 Merge pull request #5418 from penpot/palba-abtest-add-sugested-libraries
 Add test AB for adding a few "Suggested" libraries
2024-12-10 09:50:41 +01:00
Andrey Antukh
4cfe33bc5c Merge pull request #5436 from penpot/azazeln28-fix-text-editor-v2-copy-paste-issues
🐛 Fix copy/paste issues
2024-12-10 09:29:31 +01:00
Andrey Antukh
e5d8bc91fb 💄 Fix describe-library-blocks component syntax decl style 2024-12-10 09:26:07 +01:00
Andrey Antukh
ce1ba3f28f 💄 Fix sample-library-entry component syntax style 2024-12-10 09:21:45 +01:00
Pablo Alba
257d72ee9d Add test AB for adding a few "Suggested" libraries 2024-12-10 09:16:55 +01:00
Alejandro
0766b341bd Merge pull request #5432 from penpot/niwinz-bugfix-2
🐛 Bug fixes
2024-12-10 08:05:00 +01:00
AzazelN28
4ef631fd6a 🐛 Fix copy/paste issues 2024-12-09 16:10:39 +01:00
Andrey Antukh
a923d39603 🐛 Fix incorrect teams query on profile deletion
The current approach prevents profile deletion when
there are some extra (soft)deleted teams where the profile
is owner
2024-12-09 10:15:13 +01:00
Andrey Antukh
2f79d71262 🐛 Fix incorrect event handling on file-menu
Don't wait team to be present for open the menu,
because with slow connection speed it can cause
unexpected ux glitche showing menu when the component
inner request is resoved
2024-12-09 10:15:13 +01:00
Andrey Antukh
4881bf3619 Merge pull request #5412 from penpot/palba-abtest-add-library-button
 Add test AB renaming "Libraries" to "Add library"
2024-12-09 09:53:35 +01:00
Eva Marco
69df69c4bb Merge pull request #5424 from penpot/superalex-bugfixing-30
🐛 Bug fixing
2024-12-05 16:12:16 +01:00
Alejandro
2c36a4076f Merge pull request #5427 from penpot/eva-fix-firefox-scrollbar
🐛 Fix horizontal scroll on firefox
2024-12-05 13:30:52 +01:00
Andrey Antukh
3ac6f59b7b Merge pull request #5428 from penpot/qol-versions-limit-warning
 Add autosaved versions warning
2024-12-05 13:26:59 +01:00
alonso.torres
c68a0d3967 Add autosaved versions warning 2024-12-05 13:18:03 +01:00
Andrey Antukh
aeb1ac41da 🐛 Prevent upload media objects to deleted files 2024-12-05 12:39:43 +01:00
Eva Marco
b58830260c 🐛 Fix horizontal scroll on firefox 2024-12-05 12:21:22 +01:00
Alejandro Alonso
4114d9b56f 🐛 Fix shortcut for history not working 2024-12-05 10:52:59 +01:00
Alejandro
553b9eb4bb Merge pull request #5425 from penpot/ladybenko-9477-backport
[backport] 🐛 Fix missing rename shortcut translation
2024-12-05 10:43:37 +01:00
María Valderrama
12e97c73f3 🐛 Fix missing rename shortcut translation 2024-12-05 09:59:00 +01:00
Alejandro
bd1286aace Merge pull request #5422 from penpot/eva-fix-demote-owner
🐛  Fix admin can demote owner
2024-12-05 09:37:22 +01:00
Alejandro Alonso
630f42f7ac 🐛 Fix internal error when creating a guide 2024-12-05 09:30:10 +01:00
Eva Marco
bf40cd98e8 🐛 Fix admin can demote owner 2024-12-05 09:27:32 +01:00
Pablo Alba
ec7f8a6aa7 Add test AB renaming "Libraries" to "Add library" 2024-12-04 13:55:55 +01:00
andrés gonzález
4d3192546c 📚 Update changelog with some links format 2024-12-04 09:49:33 +01:00
andrés gonzález
2184926bbb 📚 Update changelog with "New .penpot file format" 2024-12-04 09:42:20 +01:00
Aitor Moreno
fffc3b1b58 Merge pull request #5396 from penpot/niwinz-bugfix-1
🐛 Fix regression on drawing text with auto-size
2024-11-29 15:55:34 +01:00
Andrey Antukh
e0cc999345 🐛 Fix regression on drawing text with auto-size
Using editor v1
Caused by 2ed743b6be
2024-11-29 13:23:39 +01:00
Andrey Antukh
8e836f79fb Merge pull request #5390 from penpot/palba-fix-bad-redirect-remove-from-team
🐛 Fix bad redirect after being removed from a team
2024-11-29 13:01:27 +01:00
Pablo Alba
093a58b9ec 🐛 Fix bad redirect after being removed from a team 2024-11-29 10:55:17 +01:00
Andrey Antukh
5e2b847202 Merge pull request #5383 from penpot/azazeln28-fix-text-editor-more-issues
🐛 Fix text editor issues
2024-11-29 10:17:43 +01:00
Andrey Antukh
ef207cfe70 Merge pull request #5386 from penpot/palba-create-events
Create several events
2024-11-28 12:10:45 +01:00
Pablo Alba
f72c37a198 Add events for use library assets 2024-11-28 11:25:12 +01:00
Pablo Alba
dbf3d0d7c1 Add event "navigate-to-library-file" 2024-11-28 11:23:28 +01:00
Pablo Alba
903c8c021d Add event "change-inspect-tab" 2024-11-27 13:51:21 +01:00
Pablo Alba
9b8ef0a2e5 Add event "duplicate-page" 2024-11-27 13:51:21 +01:00
Pablo Alba
22e64c1c81 Add event "resolve-comment-thread" 2024-11-27 13:51:21 +01:00
Pablo Alba
14c917d003 Add event "create-prototype" 2024-11-27 13:51:21 +01:00
AzazelN28
4377a0dcc4 🐛 Fix an error that kept empty inlines 2024-11-27 12:29:38 +01:00
AzazelN28
db6ca6f905 🐛 Fix wrong line-height value 2024-11-27 12:05:42 +01:00
Aitor Moreno
ede8ee6a78 Merge pull request #5384 from penpot/alotor-fix-editor
🐛 Fix problem with editor line-height
2024-11-27 12:01:58 +01:00
alonso.torres
37b50497f3 🐛 Fix problem with editor line-height 2024-11-27 11:51:13 +01:00
Aitor Moreno
e3d2b99acc Merge pull request #5381 from penpot/niwinz-bugfix-1
🐛 Fix incorrect thumbnail rendering on dashboard
2024-11-27 11:48:25 +01:00
AzazelN28
7101b94557 🐛 Fix selectAll when editor is empty 2024-11-27 11:42:18 +01:00
Aitor Moreno
5ffab1953d Merge pull request #5382 from penpot/niwinz-bugfix-2
🐛 Fix regression with component thumbnails
2024-11-27 11:34:50 +01:00
Andrey Antukh
75e7cfb69e 🐛 Fix regression with component thumbnails 2024-11-27 11:15:58 +01:00
Andrey Antukh
65b7e5c3a5 🐛 Fix incorrect thumbnail rendering on dashboard 2024-11-27 10:42:56 +01:00
Andrey Antukh
30a06249ff Merge pull request #5377 from penpot/azazeln28-fix-text-editor-issues
🐛 Fix text editor issues
2024-11-27 09:08:39 +01:00
AzazelN28
59ca09c24e 🐛 Fix text editor issues 2024-11-27 08:56:52 +01:00
Andrey Antukh
1aeafdfca7 Merge pull request #5378 from penpot/yms-fix-k8s-documentation
📚 Add links to Kubernetes documentation
2024-11-27 08:54:37 +01:00
Andrey Antukh
a714085523 Merge branch 'niwinz-plugins-reify' into staging 2024-11-27 08:32:54 +01:00
alonso.torres
eccc4226c7 Migrate proxies to new format 2024-11-27 08:32:07 +01:00
Yamila Moreno
4d6d85b3de 📚 Add links to Kubernetes documentation 2024-11-26 15:25:51 +01:00
Aitor Moreno
c607b61af6 Merge pull request #5349 from penpot/palba-add-event-for-pdf
 Add export format info to export-shapes event
2024-11-26 12:53:52 +01:00
Andrey Antukh
e16ec9c719 Add facility for create anonymous objects
Speciailly designed to be work in plugins where code
is submited to hard deep freeze on the sandboxing
process.
2024-11-25 16:35:44 +01:00
Pablo Alba
59e5656bd7 Merge pull request #5370 from penpot/niwinz-bugfix-4
🐛 Fix incorrect access to flows on add-new-interaction event
2024-11-25 15:41:13 +01:00
Andrey Antukh
723eef9565 🐛 Fix incorrect access to flows on add-new-interaction event 2024-11-25 13:32:45 +01:00
Belén Albeza
8448036d67 Merge pull request #5368 from penpot/niwinz-bugfix-3
🐛 Fix import format detection and error handling
2024-11-25 13:30:46 +01:00
Andrey Antukh
e1c9691567 Improve scss compilation error handling
Don't stop watch scss process on compilation error
2024-11-25 12:44:10 +01:00
Andrey Antukh
577b731b22 🐛 Fix import format detection and error handling 2024-11-25 12:29:59 +01:00
Pablo Alba
53f55444cd Add export format info to export-shapes event 2024-11-21 16:33:06 +01:00
163 changed files with 9655 additions and 5019 deletions

View File

@@ -1,5 +1,11 @@
# CHANGELOG
## 2.4.1
### :bug: Bugs fixed
- Fix error when importing files with touched components [Taiga #9625](https://tree.taiga.io/project/penpot/issue/9625)
## 2.4.0
### :rocket: Epics and highlights
@@ -23,13 +29,16 @@
### :sparkles: New features
- Viewer role for team members [Taiga #1056 & #6590](https://tree.taiga.io/project/penpot/us/1056 & https://tree.taiga.io/project/penpot/us/6590)
- Viewer role for team members [Taiga #1056](https://tree.taiga.io/project/penpot/us/1056) & [Taiga #6590](https://tree.taiga.io/project/penpot/us/6590)
- File history versions management [Taiga #187](https://tree.taiga.io/project/penpot/us/187?milestone=411120)
- Rename selected layer via keyboard shortcut and context menu option [Taiga #8882](https://tree.taiga.io/project/penpot/us/8882)
- New .penpot file format [Taiga #8657](https://tree.taiga.io/project/penpot/us/8657)
### :bug: Bugs fixed
- Fix problem with some texts desynchronization [Taiga #9379](https://tree.taiga.io/project/penpot/issue/9379)
- Fix problem with reoder grid layers [#5446](https://github.com/penpot/penpot/issues/5446)
- Fix problem with swap component style [#9542](https://tree.taiga.io/project/penpot/issue/9542)
## 2.3.3

View File

@@ -42,7 +42,6 @@
:rpc-rlimit-config "resources/rlimit.edn"
:rpc-climit-config "resources/climit.edn"
:auto-file-snapshot-total 10
:auto-file-snapshot-every 5
:auto-file-snapshot-timeout "3h"
@@ -101,7 +100,6 @@
[:telemetry-uri {:optional true} :string]
[:telemetry-with-taiga {:optional true} ::sm/boolean] ;; DELETE
[:auto-file-snapshot-total {:optional true} ::sm/int]
[:auto-file-snapshot-every {:optional true} ::sm/int]
[:auto-file-snapshot-timeout {:optional true} ::dt/duration]

View File

@@ -349,7 +349,6 @@
:file-gc (ig/ref :app.tasks.file-gc/handler)
:file-gc-scheduler (ig/ref :app.tasks.file-gc-scheduler/handler)
:offload-file-data (ig/ref :app.tasks.offload-file-data/handler)
:file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler)
:tasks-gc (ig/ref :app.tasks.tasks-gc/handler)
:telemetry (ig/ref :app.tasks.telemetry/handler)
:storage-gc-deleted (ig/ref ::sto.gc-deleted/handler)
@@ -405,10 +404,6 @@
{::db/pool (ig/ref ::db/pool)
::sto/storage (ig/ref ::sto/storage)}
:app.tasks.file-xlog-gc/handler
{::db/pool (ig/ref ::db/pool)
::sto/storage (ig/ref ::sto/storage)}
:app.tasks.telemetry/handler
{::db/pool (ig/ref ::db/pool)
::http.client/client (ig/ref ::http.client/client)

View File

@@ -273,7 +273,8 @@
(merge {:viewed-tutorial? false
:viewed-walkthrough? false
:nudge {:big 10 :small 1}
:v2-info-shown true})
:v2-info-shown true
:release-notes-viewed (:main cf/version)})
(db/tjson))
password (or (:password params) "!")

View File

@@ -698,11 +698,7 @@
(defn get-team-recent-files
[conn team-id]
(->> (db/exec! conn [sql:team-recent-files team-id])
(mapv (fn [row]
(if-let [media-id (:thumbnail-id row)]
(assoc row :thumbnail-uri (resolve-public-uri media-id))
(dissoc row :media-id))))))
(db/exec! conn [sql:team-recent-files team-id]))
(def ^:private schema:get-team-recent-files
[:map {:title "get-team-recent-files"}

View File

@@ -28,13 +28,19 @@
[cuerdas.core :as str]))
(def sql:get-file-snapshots
"SELECT id, label, revn, created_at, created_by, profile_id
FROM file_change
WHERE file_id = ?
AND data IS NOT NULL
AND (deleted_at IS NULL OR deleted_at > now())
ORDER BY created_at DESC
LIMIT 20")
"WITH changes AS (
SELECT id, label, revn, created_at, created_by, profile_id
FROM file_change
WHERE file_id = ?
AND data IS NOT NULL
AND (deleted_at IS NULL OR deleted_at > now())
), versions AS (
(SELECT * FROM changes WHERE created_by = 'system' LIMIT 1000)
UNION ALL
(SELECT * FROM changes WHERE created_by != 'system' LIMIT 1000)
)
SELECT * FROM versions
ORDER BY created_at DESC;")
(defn get-file-snapshots
[conn file-id]

View File

@@ -402,7 +402,10 @@
[cfg {:keys [::rpc/profile-id file-id] :as params}]
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
(files/check-edition-permissions! conn profile-id file-id)
;; TODO For now we check read permissions instead of write,
;; to allow viewer users to update thumbnails. We might
;; review this approach on the future.
(files/check-read-permissions! conn profile-id file-id)
(when-not (db/read-only? conn)
(let [media (create-file-thumbnail! cfg params)]
{:uri (files/resolve-public-uri (:id media))

View File

@@ -223,15 +223,6 @@
(let [storage (sto/resolve cfg ::db/reuse-conn true)]
(some->> (:data-ref-id file) (sto/touch-object! storage))))
(-> cfg
(assoc ::wrk/task :file-xlog-gc)
(assoc ::wrk/label (str "xlog:" (:id file)))
(assoc ::wrk/params {:file-id (:id file)})
(assoc ::wrk/delay (dt/duration "5m"))
(assoc ::wrk/dedupe true)
(assoc ::wrk/priority 1)
(wrk/submit!))
(persist-file! cfg file)
(let [params (assoc params :file file)

View File

@@ -60,15 +60,25 @@
(media/validate-media-type! content)
(media/validate-media-size! content)
(db/run! cfg (fn [cfg]
(let [object (create-file-media-object cfg params)
props {:name (:name params)
:file-id file-id
:is-local (:is-local params)
:size (:size content)
:mtype (:mtype content)}]
(with-meta object
{::audit/replace-props props})))))
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
;; We get the minimal file for proper checking if
;; file is not already deleted
(let [_ (files/get-minimal-file conn file-id)
mobj (create-file-media-object cfg params)]
(db/update! conn :file
{:modified-at (dt/now)
:has-media-trimmed false}
{:id file-id}
{::db/return-keys false})
(with-meta mobj
{::audit/replace-props
{:name (:name params)
:file-id file-id
:is-local (:is-local params)
:size (:size content)
:mtype (:mtype content)}})))))
(defn- big-enough-for-thumbnail?
"Checks if the provided image info is big enough for
@@ -142,20 +152,14 @@
:always
(assoc ::image (process-main-image info)))))
(defn create-file-media-object
[{:keys [::sto/storage ::db/conn ::wrk/executor]}
(defn- create-file-media-object
[{:keys [::sto/storage ::db/conn ::wrk/executor] :as cfg}
{:keys [id file-id is-local name content]}]
(let [result (px/invoke! executor (partial process-image content))
image (sto/put-object! storage (::image result))
thumb (when-let [params (::thumb result)]
(sto/put-object! storage params))]
(db/update! conn :file
{:modified-at (dt/now)
:has-media-trimmed false}
{:id file-id})
(db/exec-one! conn [sql:create-file-media-object
(or id (uuid/next))
file-id is-local name
@@ -182,7 +186,18 @@
::sm/params schema:create-file-media-object-from-url}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(files/check-edition-permissions! pool profile-id file-id)
(create-file-media-object-from-url cfg (assoc params :profile-id profile-id)))
;; We get the minimal file for proper checking if file is not
;; already deleted
(let [_ (files/get-minimal-file cfg file-id)
mobj (create-file-media-object-from-url cfg (assoc params :profile-id profile-id))]
(db/update! pool :file
{:modified-at (dt/now)
:has-media-trimmed false}
{:id file-id}
{::db/return-keys false})
mobj))
(defn download-image
[{:keys [::http/client]} uri]

View File

@@ -422,7 +422,9 @@
:deleted-at deleted-at
:id profile-id}})
(rph/with-transform {} (session/delete-fn cfg)))))
(-> (rph/wrap nil)
(rph/with-transform (session/delete-fn cfg))))))
;; --- HELPERS
@@ -431,8 +433,11 @@
"WITH owner_teams AS (
SELECT tpr.team_id AS id
FROM team_profile_rel AS tpr
JOIN team AS t ON (t.id = tpr.team_id)
WHERE tpr.is_owner IS TRUE
AND tpr.profile_id = ?
AND (t.deleted_at IS NULL OR
t.deleted_at > now())
)
SELECT tpr.team_id AS id,
count(tpr.profile_id) - 1 AS participants

View File

@@ -166,23 +166,26 @@
;; invited team.
(let [props {:team-id (:team-id claims)
:role (:role claims)
:invitation-id (:id invitation)}
:invitation-id (:id invitation)}]
accept-invitation-event
(-> (audit/event-from-rpc-params params)
(assoc ::audit/name "accept-team-invitation")
(assoc ::audit/props props))
(audit/submit!
cfg
(-> (audit/event-from-rpc-params params)
(assoc ::audit/name "accept-team-invitation")
(assoc ::audit/props props)))
accept-invitation-from-event
(-> (audit/event-from-rpc-params params)
(assoc ::audit/profile-id (:created-by invitation))
(assoc ::audit/name "accept-team-invitation-from")
(assoc ::audit/props (assoc props
:profile-id (:id profile)
:email (:email profile))))]
(audit/submit! cfg accept-invitation-event)
(audit/submit! cfg accept-invitation-from-event)
;; NOTE: Backward compatibility; old invitations can
;; have the `created-by` to be nil; so in this case we
;; don't submit this event to the audit-log
(when-let [created-by (:created-by invitation)]
(audit/submit!
cfg
(-> (audit/event-from-rpc-params params)
(assoc ::audit/profile-id created-by)
(assoc ::audit/name "accept-team-invitation-from")
(assoc ::audit/props (assoc props
:profile-id (:id profile)
:email (:email profile))))))
(accept-invitation cfg claims invitation profile)
(assoc claims :state :created))

View File

@@ -1,64 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.tasks.file-xlog-gc
(:require
[app.common.logging :as l]
[app.config :as cf]
[app.db :as db]
[integrant.core :as ig]))
;; Get the latest available snapshots without exceeding the total
;; snapshot limit
(def ^:private sql:get-latest-snapshots
"SELECT fch.id, fch.created_at
FROM file_change AS fch
WHERE fch.file_id = ?
AND fch.created_by = 'system'
AND fch.data IS NOT NULL
AND fch.deleted_at > now()
ORDER BY fch.created_at DESC
LIMIT ?")
;; Mark all snapshots that are outside the allowed total threshold
;; available for the GC
(def ^:private sql:delete-snapshots
"UPDATE file_change
SET deleted_at = now()
WHERE file_id = ?
AND deleted_at > now()
AND data IS NOT NULL
AND created_by = 'system'
AND created_at < ?")
(defn- get-alive-snapshots
[conn file-id]
(let [total (cf/get :auto-file-snapshot-total 10)
snapshots (db/exec! conn [sql:get-latest-snapshots file-id total])]
(not-empty snapshots)))
(defn- delete-old-snapshots!
[{:keys [::db/conn] :as cfg} file-id]
(when-let [snapshots (get-alive-snapshots conn file-id)]
(let [last-date (-> snapshots peek :created-at)
result (db/exec-one! conn [sql:delete-snapshots file-id last-date])]
(l/inf :hint "delete old file snapshots"
:file-id (str file-id)
:current (count snapshots)
:deleted (db/get-update-count result)))))
(defmethod ig/assert-key ::handler
[_ params]
(assert (db/pool? (::db/pool params)) "expected a valid database pool"))
(defmethod ig/init-key ::handler
[_ cfg]
(fn [{:keys [props] :as task}]
(let [file-id (:file-id props)]
(assert (uuid? file-id) "expected file-id on props")
(-> cfg
(assoc ::db/rollback (:rollback props false))
(db/tx-run! delete-old-snapshots! file-id)))))

View File

@@ -10,6 +10,7 @@
[app.db :as db]
[app.rpc :as-alias rpc]
[app.storage :as sto]
[app.util.time :as dt]
[backend-tests.helpers :as th]
[clojure.test :as t]
[datoteka.fs :as fs]))
@@ -245,3 +246,35 @@
(t/is (= "image/jpeg" (:mtype result)))
(t/is (uuid? (:media-id result)))
(t/is (uuid? (:thumbnail-id result))))))
(t/deftest media-object-upload-command-when-file-is-deleted
(let [prof (th/create-profile* 1)
proj (th/create-project* 1 {:profile-id (:id prof)
:team-id (:default-team-id prof)})
file (th/create-file* 1 {:profile-id (:id prof)
:project-id (:default-project-id prof)
:is-shared false})
_ (th/db-update! :file
{:deleted-at (dt/now)}
{:id (:id file)})
mfile {:filename "sample.jpg"
:path (th/tempfile "backend_tests/test_files/sample.jpg")
:mtype "image/jpeg"
:size 312043}
params {::th/type :upload-file-media-object
::rpc/profile-id (:id prof)
:file-id (:id file)
:is-local true
:name "testfile"
:content mfile}
out (th/command! params)]
(let [error (:error out)
error-data (ex-data error)]
(t/is (th/ex-info? error))
(t/is (= (:type error-data) :not-found)))))

View File

@@ -203,7 +203,24 @@
edata (ex-data error)]
(t/is (th/ex-info? error))
(t/is (= (:type edata) :validation))
(t/is (= (:code edata) :owner-teams-with-people))))))
(t/is (= (:code edata) :owner-teams-with-people)))
(let [params {::th/type :delete-team
::rpc/profile-id (:id prof1)
:id (:id team1)}
out (th/command! params)]
;; (th/print-result! out)
(let [team (th/db-get :team {:id (:id team1)} {::db/remove-deleted false})]
(t/is (dt/instant? (:deleted-at team)))))
;; Request profile to be deleted
(let [params {::th/type :delete-profile
::rpc/profile-id (:id prof1)}
out (th/command! params)]
;; (th/print-result! out)
(t/is (nil? (:result out)))
(t/is (nil? (:error out)))))))
(t/deftest profile-deletion-3
(let [prof1 (th/create-profile* 1)
@@ -291,7 +308,7 @@
out (th/command! params)]
;; (th/print-result! out)
(t/is (= {} (:result out)))
(t/is (nil? (:result out)))
(t/is (nil? (:error out))))
;; query files after profile soft deletion
@@ -336,7 +353,7 @@
::rpc/profile-id (:id prof1)}
out (th/command! params)]
;; (th/print-result! out)
(t/is (= {} (:result out)))
(t/is (nil? (:result out)))
(t/is (nil? (:error out))))
(th/run-pending-tasks!)

View File

@@ -6,4 +6,4 @@
(ns app.common.files.defaults)
(def version 57)
(def version 59)

View File

@@ -1130,6 +1130,21 @@
(update :pages-index dissoc nil)
(update :pages-index update-vals update-page))))
(defn migrate-up-59
[data]
(letfn [(fix-touched [elem]
(cond-> elem (string? elem) keyword))
(update-shape [shape]
(d/update-when shape :touched #(into #{} (map fix-touched) %)))
(update-container [container]
(d/update-when container :objects update-vals update-shape))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(def migrations
"A vector of all applicable migrations"
[{:id 2 :migrate-up migrate-up-2}
@@ -1178,5 +1193,6 @@
{:id 54 :migrate-up migrate-up-54}
{:id 55 :migrate-up migrate-up-55}
{:id 56 :migrate-up migrate-up-56}
{:id 57 :migrate-up migrate-up-57}])
{:id 57 :migrate-up migrate-up-57}
{:id 59 :migrate-up migrate-up-59}])

View File

@@ -304,7 +304,9 @@
(->> ids
(mapcat #(ctn/get-child-heads objects %))
(map :id)))
cell (or cell (ctl/get-cell-by-index parent to-index))]
index-cell-data (when to-index (ctl/get-cell-by-index parent to-index))
cell (or cell (and index-cell-data [(:row index-cell-data) (:column index-cell-data)]))]
(-> changes
(pcb/with-page-id page-id)
@@ -409,12 +411,14 @@
;; Resize parent containers that need to
(pcb/resize-parents parents))))
(defn change-show-in-viewer [shape hide?]
(defn change-show-in-viewer
[shape hide?]
(assoc shape :hide-in-viewer hide?))
(defn add-new-interaction [shape interaction]
(-> shape
(update :interactions ctsi/add-interaction interaction)))
(defn add-new-interaction
[shape interaction]
(update shape :interactions ctsi/add-interaction interaction))
(defn show-in-viewer [shape]
(defn show-in-viewer
[shape]
(dissoc shape :hide-in-viewer))

View File

@@ -33,7 +33,7 @@
[:id ::sm/uuid]
[:axis [::sm/one-of #{:x :y}]]
[:position ::sm/safe-number]
[:frame-id {:optional true} ::sm/uuid]])
[:frame-id {:optional true} [:maybe ::sm/uuid]]])
(def schema:guides
[:map-of {:gen/max 2} ::sm/uuid schema:guide])

View File

@@ -191,7 +191,8 @@
[:grow-type {:optional true}
[::sm/one-of grow-types]]
[:applied-tokens {:optional true} ::cto/applied-tokens]
[:plugin-data {:optional true} ::ctpg/plugin-data]])
[:plugin-data {:optional true} ::ctpg/plugin-data]
[:touched {:optional true} [:maybe [:set :keyword]]]])
(def schema:group-attrs
[:map {:title "GroupAttrs"}

View File

@@ -1479,7 +1479,7 @@
(defn get-cell-by-index
[parent to-index]
(let [cells (get-cells parent {:sort? true :remove-empty? true})
to-index (- (count cells) to-index)]
to-index (- (count cells) to-index 1)]
(nth cells to-index nil)))
(defn add-children-to-index

View File

@@ -90,6 +90,7 @@ http {
proxy_hide_header x-amz-meta-server-side-encryption;
proxy_hide_header x-amz-server-side-encryption;
proxy_pass $redirect_uri;
proxy_ssl_server_name on;
add_header x-internal-redirect "$redirect_uri";
add_header x-cache-control "$redirect_cache_control";

View File

@@ -92,6 +92,7 @@ http {
proxy_hide_header x-amz-request-id;
proxy_hide_header x-amz-meta-server-side-encryption;
proxy_hide_header x-amz-server-side-encryption;
proxy_ssl_server_name on;
proxy_pass $redirect_uri;
add_header x-internal-redirect "$redirect_uri";

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Binary file not shown.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -216,3 +216,9 @@ Success! - Published to example-plugin-penpot.surge.sh
```
5. Done!
## 3.5. Submitting to Penpot
To make your finished plugin available in our catalog, submit in on the [plugin submission page](https://penpot.app/penpothub/plugins/create-plugin). Once it becomes available any Penpot user will be able to install and use it.

View File

@@ -4,7 +4,8 @@ title: 1. Self-hosting Guide
# Self-hosting Guide
This guide explains how to get your own Penpot instance, running on a machine you control, to test it, use it by you or your team, or even customize and extend it any way you like.
This guide explains how to get your own Penpot instance, running on a machine you control,
to test it, use it by you or your team, or even customize and extend it any way you like.
If you need more context you can look at the <a
href="https://community.penpot.app/t/self-hosting-penpot-i/2336" target="_blank">post
@@ -14,18 +15,30 @@ about self-hosting</a> in Penpot community.
href="https://design.penpot.app">our SaaS offer</a> for Penpot and your
self-hosted Penpot platform!**
There are two main options for creating a Penpot instance:
There are three main options for creating a Penpot instance:
1. Using the platform of our partner <a href="https://elest.io/open-source/penpot" target="_blank">Elestio</a>.
2. Using <a href="https://docker.com" target="_blank">Docker</a> tool.
3. Using <a href="https://kubernetes.io/" target="_blank">Kubernetes</a>.
<p class="advice">
The recommended way is to use Elestio, since it's simpler, fully automatic and still greatly flexible. Use Docker if you already know the tool, if need full control of the process or have extra requirements and do not want to depend on any external provider, or need to do any special customization.
The recommended way is to use Elestio, since it's simpler, fully automatic and still greatly flexible.
Use Docker if you already know the tool, if need full control of the process or have extra requirements
and do not want to depend on any external provider, or need to do any special customization.
</p>
Or you can try <a href="#unofficial-self-host-options">other options</a>,
offered by Penpot community.
## Recommended settings
To self-host Penpot, youll need a server with the following specifications:
* **CPU:** 1-2 CPUs
* **RAM:** 4 GiB of RAM
* **Disk Space:** Disk requirements depend on your usage. Disk usage primarily involves the database and any files uploaded by users.
This setup should be sufficient for a smooth experience with typical usage (your mileage may vary).
## Install with Elestio
This section explains how to get Penpot up and running using <a href="https://elest.io/open-source/penpot"
@@ -261,7 +274,7 @@ itself.
This section details everything you need to know to get Penpot up and running in
production environments using a Kubernetes cluster of your choice. To do this, we have
created a <a href="https://helm.sh/" target="_blank">Helm<a> repository with everything
created a <a href="https://helm.sh/" target="_blank">Helm</a> repository with everything
you need.
Therefore, your prerequisite will be to have a Kubernetes cluster on which we can install
@@ -287,7 +300,7 @@ in turn have its own release name.
With these concepts in mind, we can now explain Helm like this:
> Helm installs charts into Kubernetes clusters, creating a new release for each
> installation. And to find new charts, you can search Helm chart repositories.
> installation. To find new charts, you can search Helm chart repositories.
### Install Helm

View File

@@ -20,6 +20,8 @@ machine.
* In the [Install with Docker][2] section, you can find the official Docker installation guide.
* In the [Install with Kubernetes][7] section, you can find the official Kubernetes installation guide.
* In the [Configuration][3] section, you can find all the customization options you can set up after installing.
* Or you can try other, not supported by Penpot, [Unofficial options][4].
@@ -28,9 +30,11 @@ machine.
The [Integration Guide][5] explains how to connect Penpot with external apps, so they get notified
when certain events occur and may create your own interconnections and collaboration features.
## Developing Penpot
Also, if you are a developer, you can get into the code, to explore it, learn how it is made, or extend it and contribute with new functionality. For this, we have a different Docker installation.
Also, if you are a developer, you can get into the code, to explore it, learn how it is made,
or extend it and contribute with new functionality. For this, we have a different Docker installation.
In the [Developer Guide][6] you can find how to setup a development environment and many other dev-oriented documentation.
[1]: /technical-guide/getting-started/#install-with-elestio
@@ -39,3 +43,4 @@ In the [Developer Guide][6] you can find how to setup a development environment
[4]: /technical-guide/getting-started/#unofficial-self-host-options
[5]: /technical-guide/integration/
[6]: /technical-guide/developer/
[7]: /technical-guide/getting-started/#install-with-kubernetes

View File

@@ -5,45 +5,25 @@ title: 14· Import/export files
<h1 id="import-export">Import and export files</h1>
<p class="main-paragraph">You can export Penpot files to your computer and import them from your computer to your projects.</p>
<h2 id="penpot-formats">Penpot file formats</h2>
<p>There are two different formats in which you can import/export Penpot files. A standard one and a binary one. You always have the chance to use both for any file.</p>
<h3>Penpot file (.penpot).</h3>
<p>The fast one. Binary Penpot specific.</p>
<ul>
<li>✅ Highly efficient in terms of memory and transfer time when exporting and importing.</li>
<li>❌ It can be opened only in Penpot.</li>
<li>❌ Not transparent, code difficult to explore.</li>
</ul>
<h3>Standard file (.zip).</h3>
<p>The open one. A compressed file that includes SVG and JSON.</p>
<ul>
<li>✅ Allows the file to be opened by other softwares (still, for those cases export to SVG seems to be the common practice).</li>
<li>✅ Allows some automations and integrations.</li>
<li>✅ Is a transparent, existing, open standard format.</li>
<li>❌ Highly inefficient in terms of memory and transfer time when exporting and importing (this is because SVG).</li>
</ul>
<h2 id="files-export">Export Penpot files</h2>
<p>Exporting files is useful for many reasons. Sometimes you want to have a backup of your files and sometimes it is useful to share Penpot files with a user that does not belong to one of your teams, or you want to have a backup of your files outside Penpot, both SaaS (design.penpot.app) or at a self-hosted instance.</p>
<h3 id="export-penpot-files">How to export Penpot files</h3>
<h4>Export a single file</h4>
<p>You can download (export) files from the workspace and from the dashboard.</p>
<ul>
<li>
<strong>From the <a href="/user-guide/the-interface/#interface-workspace">workspace</a></strong>: Select the download option at the main menu.
<figure><img src="/img/import-export/export-card.webp" alt="Export penpot file" /></figure>
</li>
<li>
<strong>From the <a href="/user-guide/the-interface/#interface-dashboard">dashboard</a></strong>: Select the download option at the file card menu.
<figure><img src="/img/import-export/export-menu.webp" alt="Export penpot file" /></figure>
</li>
</ul>
<p>
<strong>From the <a href="/user-guide/the-interface/#interface-dashboard">dashboard</a></strong>: Select the download option at the file card menu.
<figure><img src="/img/import-export/export-card.webp" alt="Export penpot file" /></figure>
</p>
<p>
<strong>From the <a href="/user-guide/the-interface/#interface-workspace">workspace</a></strong>: Select the download option at the main menu.
<figure><img src="/img/import-export/export-menu.webp" alt="Export penpot file" /></figure>
</p>
<h4>Export multiple files</h4>
<p>Select multiple files to export them at the same time. An overlay will show you the progress of the different exports.</p>
<figure>
<video title="Export multiple files" muted="" playsinline="" controls="" width="100%" poster="/img/import-export/export-multiple.webp" height="auto">
<video title="Export multiple files" muted="" playsinline="" controls="" width="auto" poster="/img/import-export/export-multiple.webp" height="auto">
<source src="/img/import-export/export-multiple.mp4" type="video/mp4">
</video>
</figure>
@@ -63,4 +43,27 @@ title: 14· Import/export files
<p>The import option is at the projects menu. Press “Import files” and then select one or more .penpot files to import. You can import a .zip file as well.</p>
<figure><img src="/img/import-export/import-menu.webp" alt="Import penpot file" /></figure>
<p>Right before importing the files to your project, youll still have the opportunity to review the items to be imported, have the information about the ones that can not be imported and also the chance to discard files.</p>
<figure><img src="/img/import-export/import-selection.webp" alt="Import penpot file" /></figure
<figure><img src="/img/import-export/import-selection.webp" alt="Import penpot file" /></figure>
<h2 id="penpot-formats">Penpot file format</h2>
<p>Penpot export to a unique format that streamline the import and export of files and assets by being more efficient and interoperable.</p>
<p>Unlike other design tools, <strong>Penpot's format is built on standard languages</strong>. The exported file is essentially a ZIP archive containing binary assets (such as bitmap and vector images) alongside a readable JSON structure. By avoiding proprietary formats, Penpot empowers users with autonomy from specific tools while enabling seamless third-party integrations.</p>
<h3>Deprecated Penpot file formats</h3>
<p class="advice">These formats can only be exported from version 2.3 or earlier versions, but can be imported to any Penpot version</p>
<p>There are two different deprecated Penpot file formats in which you can import/export Penpot files. A standard one and a binary one. You always have the chance to use both for any file.</p>
<h4>[Deprecated] Penpot file (.penpot).</h4>
<p>The fast one. Binary Penpot specific.</p>
<ul>
<li>✅ Highly efficient in terms of memory and transfer time when exporting and importing.</li>
<li>❌ It can be opened only in Penpot.</li>
<li>❌ Not transparent, code difficult to explore.</li>
</ul>
<h4>[Deprecated] Standard file (.zip).</h4>
<p>The open one. A compressed file that includes SVG and JSON.</p>
<ul>
<li>✅ Allows the file to be opened by other softwares (still, for those cases export to SVG seems to be the common practice).</li>
<li>✅ Allows some automations and integrations.</li>
<li>✅ Is a transparent, existing, open standard format.</li>
<li>❌ Highly inefficient in terms of memory and transfer time when exporting and importing (this is because SVG).</li>
</ul>

View File

@@ -424,11 +424,6 @@ title: Shortcuts
<td style="text-align: center;"><kbd>Alt</kbd><kbd>P</kbd></td>
<td style="text-align: center;"><kbd>⌥</kbd><kbd>P</kbd></td>
</tr>
<tr>
<td>History</td>
<td style="text-align: center;"><kbd>Alt</kbd><kbd>H</kbd></td>
<td style="text-align: center;"><kbd>⌥</kbd><kbd>H</kbd></td>
</tr>
<tr>
<td>Layers</td>
<td style="text-align: center;"><kbd>Alt</kbd><kbd>L</kbd></td>

View File

@@ -155,4 +155,30 @@ title: 06· Styling
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/blur.webp" height="auto">
<source src="/img/styling/blur.mp4" type="video/mp4">
</video>
</figure>
</figure>
<h2 id="blend">Opacity and blend</h2>
<p>Set the overal opacity for layers and their blend mode.</p>
<p>Blend allows you to control how a layer interacts with the layers beneath it, determining how pixels from the current layer are combined with pixels in the underlying layers. Use blend to achive various effects, such as shading, highlights, or creative visual styles.</p>
<figure>
<img alt="Layer blend and opacity" src="/img/styling/blend-opacity.webp"/>
</figure>
<p>Blend options available:</p>
<ul>
<li><strong>Normal</strong></li>
<li><strong>Darken</strong></li>
<li><strong>Multiply</strong></li>
<li><strong>Color burn</strong></li>
<li><strong>Lighten</strong></li>
<li><strong>Screen</strong></li>
<li><strong>Color dodge</strong></li>
<li><strong>Overlay</strong></li>
<li><strong>Soft light</strong></li>
<li><strong>Hard light</strong></li>
<li><strong>Difference</strong></li>
<li><strong>Exclusion</strong></li>
<li><strong>Hue</strong></li>
<li><strong>Saturation</strong></li>
<li><strong>Color</strong></li>
<li><strong>Luminosity</strong></li>
</ul>

View File

@@ -199,26 +199,58 @@ geometric structure. In Penpot there are three types of guides:
<img src="/img/workspace-basics/shortcuts.webp" alt="Shortcuts panel" />
</figure>
<h2 id="history">History</h2>
<p>The history panel keeps track of the latest changes on an opened file.</p>
<h2 id="history">File history versions</h2>
<p>The history panel keeps track of the latest changes on an opened file as well as the different versions of the file, making it easier to track changes, revert to previous states and collaborate.</p>
<h4>View history</h4>
<p>To view the recent history of a file at the workspace press <kbd>Ctrl/⌘</kbd> + <kbd>H</kbd> or click at the history icon on the toolbar at the left.</p>
<p>At the history you can see items with information about the last changes. At first sight you have object type (rectangle, text, image...) and type of change (New, Modified, Deleted...). If you press the item further details are shown.</p>
<h3>View history</h3>
<p>To view the recent history of a file at the workspace click the history icon on the navbar at the left:</p>
<ul>
<li>To see the history of file versions go to the <strong>History</strong> tab.</li>
<li>To see the history of item changes go to the <strong>Actions</strong> tab.</li>
</ul>
<figure>
<img src="/img/workspace-basics/history.webp" alt="History panel" />
<img src="/img/workspace-basics/history-view.webp" alt="History versions button" />
</figure>
<p><strong>Note:</strong> History panel is still in a very early state and shows only a limited list of changes at a current browser tab session. Refreshing the browser means refreshing the History as well. Eventually, Penpot will have a proper version history capacity.</p>
<h4>Navigate history</h4>
<p>To navigate through the history press <kbd>Ctrl/⌘</kbd> + <kbd>Z</kbd> to go backwards and <kbd>Ctrl/⌘</kbd> + <kbd>Shift/⇧</kbd> + <kbd>Z</kbd> to go forward.</p>
<p>You can also press any item of the history list to get to this specific state.</p>
<h3>History panel</h3>
<p>At the History panel, you can save the current version of your file, as well as access previous versions for up to 7 days.</p>
<h4>Restore versions</h4>
<p>All saved versions of the file—whether manually saved, autosaved, or pinned—can be restored, reverting the file back to its state at the selected time.</p>
<figure>
<video title="Navigate history" muted="" playsinline="" controls="" width="auto" poster="/img/workspace-basics/history-navigate.webp" height="auto">
<source src="/img/workspace-basics/history-navigate.mp4" type="video/mp4">
</video>
<img src="/img/workspace-basics/history-restore.webp" alt="Restore versions" />
</figure>
<h4>Saved versions</h4>
<p>You can save the current version of your file by clicking the pin icon at the History tab. This will allow the version to be named and it will add it to your list of versions.</p>
<figure>
<img src="/img/workspace-basics/history-save.webp" alt="Saved versions" />
</figure>
<h4>Autosaved versions</h4>
<p>When you start working on a file, Penpot will start to automatically save versions of that file across time so that you can later restore them as needed.</p>
<p>In the History tab, if you click on the autosaved versions, youll see a list of the exact date and time when the version was automatically saved.</p>
<figure>
<img src="/img/workspace-basics/history-autosaved.webp" alt="Autosaved versions" />
</figure>
<h4>Pinned versions</h4>
<p>File versions can also be pinned. Pinning a file version will allow you to name it, making it easier to access at the History tab. Pinned file versions will be saved forever and can be renamed, restored or deleted at any time.</p>
<figure>
<img src="/img/workspace-basics/history-pin.webp" alt="Pin versions" />
</figure>
<h3>Actions panel</h3>
<p>At the Actions panel, you have the object type (rectangle, text, image...) and type of change (New, Modified, Deleted...). If you press the item, it will be reverted to its state before that specific action was performed.</p>
<figure>
<img src="/img/workspace-basics/history-actions.webp" alt="Actions panel" />
</figure>
<p class="advice">The Actions panel shows only a limited list of changes at a current browser tab session. Refreshing the browser means refreshing the history of actions as well.</p>
<h4>Navigate actions</h4>
<p>To navigate through the actions press <kbd>Ctrl/⌘</kbd> + <kbd>Z</kbd> to go backwards and <kbd>Ctrl/⌘</kbd> + <kbd>Shift/⇧</kbd> + <kbd>Z</kbd> to go forward.</p>
<p>You can also press any item of the actions list to get to this specific state.</p>
<h2 id="comments">Comments</h2>
<p>Comments allow the team to have one priceless conversation getting and providing feedback right over the designs and prototypes.<p>

View File

@@ -43,7 +43,9 @@
{:extra-paths ["dev"]
:extra-deps
{thheller/shadow-cljs {:mvn/version "2.28.18"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "0.48.0"}}}
:shadow-cljs

34
frontend/dev/user.clj Normal file
View File

@@ -0,0 +1,34 @@
;; 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 user
(:require
[app.common.data :as d]
[app.common.pprint :as pp]
[clojure.java.io :as io]
[clojure.tools.namespace.repl :as repl]
[clojure.pprint :refer [pprint print-table]]
[clojure.repl :refer :all]
[clojure.walk :refer [macroexpand-all]]
[criterium.core :as crit]))
;; --- Benchmarking Tools
(defmacro run-quick-bench
[& exprs]
`(crit/with-progress-reporting (crit/quick-bench (do ~@exprs) :verbose)))
(defmacro run-quick-bench'
[& exprs]
`(crit/quick-bench (do ~@exprs)))
(defmacro run-bench
[& exprs]
`(crit/with-progress-reporting (crit/bench (do ~@exprs) :verbose)))
(defmacro run-bench'
[& exprs]
`(crit/bench (do ~@exprs)))

3392
frontend/externs/main.txt Normal file
View File

File diff suppressed because it is too large Load Diff

1
frontend/externs/worker.txt Symbolic link
View File

@@ -0,0 +1 @@
main.txt

View File

@@ -0,0 +1,30 @@
[
{
"~:is-admin": true,
"~:email": "bar@example.com",
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
"~:name": "Han Solo",
"~:fullname": "Han Solo",
"~:is-owner": true,
"~:modified-at": "~m1713533116365",
"~:can-edit": true,
"~:is-active": true,
"~:id": "~u1e162163-87b7-805b-8005-5fd05514b6d3",
"~:profile-id": "~u1e162163-87b7-805b-8005-5fd05514b6d3",
"~:created-at": "~m1733324626956"
},
{
"~:is-admin": true,
"~:email": "foo@example.com",
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
"~:name": "Princesa Leia",
"~:fullname": "Princesa Leia",
"~:is-owner": false,
"~:modified-at": "~m1713533116365",
"~:can-edit": true,
"~:is-active": true,
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
"~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b",
"~:created-at": "~m1713533116365"
}
]

View File

@@ -35,7 +35,7 @@
},
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-owner": false,
"~:is-admin": true,
"~:can-edit": true
},

View File

@@ -52,3 +52,20 @@ test("Lists files in the drafts page", async ({ page }) => {
dashboardPage.page.getByRole("button", { name: /New File 2/ }),
).toBeVisible();
});
test("Bug 9443, Admin can not demote owner", async ({ page }) => {
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-team-members?team-id=*",
"dashboard/get-team-members-admin.json",
);
await dashboardPage.goToSecondTeamMembersSection();
await expect(page.getByRole("heading", { name: "Members" })).toBeVisible();
await expect(page.getByRole("combobox", { name: "Admin" })).toBeVisible();
await expect(page.getByText("Owner")).toBeVisible();
await expect(page.getByRole("combobox", { name: "Owner" })).toHaveCount(0);
});

View File

@@ -30,7 +30,7 @@ test("Save and restore version", async ({ page }) => {
"workspace/versions-snapshot-1.json",
);
await page.getByLabel("History (Alt+H)").click();
await page.getByLabel("History").click();
await workspacePage.mockRPC(
"create-file-snapshot",
@@ -71,4 +71,7 @@ test("Save and restore version", async ({ page }) => {
);
await page.getByRole("button", { name: "Restore" }).click();
// check that the history panel is closed after restore
await expect(page.getByRole("tab", { name: "design" })).toBeVisible();
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -41,13 +41,6 @@ body {
scrollbar-width: thin;
}
// Firefox-only hack
@-moz-document url-prefix() {
* {
scrollbar-width: auto;
}
}
img {
height: auto;
width: 100%;

View File

@@ -508,6 +508,7 @@ export async function compileStyles() {
const start = process.hrtime();
log.info("init: compile styles");
let result = await compileSassAll(worker);
result = concatSass(result);

6
frontend/scripts/repl Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
export OPTIONS="-A:dev -J-XX:-OmitStackTraceInFastThrow";
set -ex
exec clojure $OPTIONS -M -m rebel-readline.main

View File

@@ -24,15 +24,22 @@ async function compileSassAll() {
async function compileSass(path) {
const start = process.hrtime();
log.info("changed:", path);
const result = await h.compileSass(worker, path, { modules: true });
sass.index[result.outputPath] = result.css;
const output = h.concatSass(sass);
try {
const result = await h.compileSass(worker, path, { modules: true });
sass.index[result.outputPath] = result.css;
await fs.writeFile("./resources/public/css/main.css", output);
const output = h.concatSass(sass);
const end = process.hrtime(start);
log.info("done:", `(${ppt(end)})`);
await fs.writeFile("./resources/public/css/main.css", output);
const end = process.hrtime(start);
log.info("done:", `(${ppt(end)})`);
} catch (cause) {
console.error(cause);
const end = process.hrtime(start);
log.error("error:", `(${ppt(end)})`);
}
}
await fs.mkdir("./resources/public/css/", { recursive: true });

View File

@@ -143,6 +143,11 @@
(let [f (obj/get global "externalSessionId")]
(when (fn? f) (f))))
(defn external-context-info
[]
(let [f (obj/get global "externalContextInfo")]
(when (fn? f) (f))))
;; --- Helper Functions
(defn ^boolean check-browser? [candidate]

View File

@@ -195,13 +195,16 @@
ptk/WatchEvent
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rp/cmd! :update-comment-thread {:id id :is-resolved is-resolved :share-id share-id})
(rx/catch (fn [{:keys [type code] :as cause}]
(if (and (= type :restriction)
(= code :max-quote-reached))
(rx/throw cause)
(rx/throw {:type :comment-error}))))
(rx/ignore))))))
(rx/concat
(when is-resolved (rx/of
(ptk/event ::ev/event {::ev/name "resolve-comment-thread" :thread-id id})))
(->> (rp/cmd! :update-comment-thread {:id id :is-resolved is-resolved :share-id share-id})
(rx/catch (fn [{:keys [type code] :as cause}]
(if (and (= type :restriction)
(= code :max-quote-reached))
(rx/throw cause)
(rx/throw {:type :comment-error}))))
(rx/ignore)))))))
(defn add-comment
[thread content]

View File

@@ -224,6 +224,11 @@
[{:keys [team-id team-name change]}]
(dm/assert! (uuid? team-id))
(ptk/reify ::team-membership-change
ptk/UpdateEvent
(update [_ state]
;; FIXME: Remove on 2.5
(assoc state :current-team-id (dm/get-in state [:profile :default-team-id])))
ptk/WatchEvent
(watch [_ state _]
(when (= :removed change)

View File

@@ -1122,6 +1122,7 @@
(ptk/reify ::go-to-projects-1
ptk/UpdateEvent
(update [_ state]
;; FIXME: Remove on 2.5
(assoc state :current-team-id team-id))
ptk/WatchEvent
(watch [_ _ _]

View File

@@ -8,6 +8,7 @@
(:require
["ua-parser-js" :as ua]
[app.common.data :as d]
[app.common.json :as json]
[app.common.logging :as l]
[app.config :as cf]
[app.main.repo :as rp]
@@ -93,6 +94,11 @@
data
data))
(defn add-external-context-info
[context]
(let [external-context-info (json/->clj (cf/external-context-info))]
(merge context external-context-info)))
(defn- process-event-by-proto
[event]
(let [data (d/deep-merge (-data event) (meta event))
@@ -102,6 +108,7 @@
(assoc :event-origin (::origin data))
(assoc :event-namespace (namespace type))
(assoc :event-symbol ev-name)
(add-external-context-info)
(d/without-nils))
props (-> data d/without-qualified simplify-props)]
@@ -119,6 +126,7 @@
(let [type (::type data "action")
context (-> (::context data)
(assoc :event-origin (::origin data))
(add-external-context-info)
(d/without-nils))
props (-> data d/without-qualified simplify-props)]
{:type type

View File

@@ -7,6 +7,7 @@
(ns app.main.data.exports.assets
(:require
[app.common.uuid :as uuid]
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.data.persistence :as dwp]
[app.main.data.workspace.state-helpers :as wsh]
@@ -247,6 +248,12 @@
(rx/map #(clear-export-state @resource-id))
(rx/take-until (rx/delay 6000 stopper))))))))
(defn request-export
[{:keys [exports] :as params}]
(if (= 1 (count exports))
(request-simple-export (assoc params :export (first exports)))
(request-multiple-export params)))
(defn retry-last-export
[]
(ptk/reify ::retry-last-export
@@ -256,3 +263,16 @@
(when (seq params)
(rx/of (request-multiple-export params)))))))
(defn export-shapes-event
[exports origin]
(let [types (reduce (fn [counts {:keys [type]}]
(if (#{:png :pdf :svg :jpeg} type)
(update counts type inc)
counts))
{:png 0, :pdf 0, :svg 0, :jpeg 0}
exports)]
(ptk/event
::ev/event (merge types
{::ev/name "export-shapes"
::ev/origin origin
:num-shapes (count exports)}))))

View File

@@ -65,6 +65,7 @@
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.thumbnails :as dwth]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
@@ -83,6 +84,7 @@
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt]
[app.util.storage :as storage]
[app.util.text.content :as tc]
[app.util.timers :as tm]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
@@ -1639,6 +1641,7 @@
(rx/ignore))))))))))
(declare ^:private paste-transit)
(declare ^:private paste-html-text)
(declare ^:private paste-text)
(declare ^:private paste-image)
(declare ^:private paste-svg-text)
@@ -1706,6 +1709,7 @@
(let [pdata (wapi/read-from-paste-event event)
image-data (some-> pdata wapi/extract-images)
text-data (some-> pdata wapi/extract-text)
html-data (some-> pdata wapi/extract-html-text)
transit-data (ex/ignoring (some-> text-data t/decode-str))]
(cond
(and (string? text-data) (re-find #"<svg\s" text-data))
@@ -1718,6 +1722,9 @@
(coll? transit-data)
(rx/of (paste-transit (assoc transit-data :in-viewport in-viewport?)))
(string? html-data)
(rx/of (paste-html-text html-data text-data))
(string? text-data)
(rx/of (paste-text text-data))
@@ -2016,6 +2023,8 @@
(map :id)
(pcb/resize-parents changes))
orig-shapes (map (d/getf all-objects) selected)
selected (into (d/ordered-set)
(comp
(filter add-obj?)
@@ -2028,13 +2037,22 @@
(some? drop-cell)
(pcb/update-shapes [parent-id]
#(ctl/add-children-to-cell % selected all-objects drop-cell)))
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(dws/select-shapes selected)
(ptk/data-event :layout/update {:ids [frame-id]})
(dwu/commit-undo-transaction undo-id)))))))
(rx/concat
(->> (filter ctk/instance-head? orig-shapes)
(map (fn [{:keys [component-file]}]
(ptk/event ::ev/event
{::ev/name "use-library-component"
::ev/origin "paste"
:external-library (not= file-id component-file)})))
(rx/from))
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(dws/select-shapes selected)
(ptk/data-event :layout/update {:ids [frame-id]})
(dwu/commit-undo-transaction undo-id))))))))
(defn as-content [text]
(let [paragraphs (->> (str/lines text)
@@ -2059,6 +2077,34 @@
:else
(deref ms/mouse-position)))
(defn- paste-html-text
[html text]
(dm/assert! (string? html))
(ptk/reify ::paste-html-text
ptk/WatchEvent
(watch [_ state _]
(let [root (dwtxt/create-root-from-html html)
content (tc/dom->cljs root)
id (uuid/next)
width (max 8 (min (* 7 (count text)) 700))
height 16
{:keys [x y]} (calculate-paste-position state)
shape {:id id
:type :text
:name (txt/generate-shape-name text)
:x x
:y y
:width width
:height height
:grow-type (if (> (count text) 100) :auto-height :auto-width)
:content content}
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dwsh/create-and-add-shape :text x y shape)
(dwu/commit-undo-transaction undo-id))))))
(defn- paste-text
[text]
(dm/assert! (string? text))

View File

@@ -17,6 +17,7 @@
[app.common.types.shape.interactions :as ctsi]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.events :as ev]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
@@ -168,7 +169,8 @@
objects (get page :objects)
frame (cfh/get-root-frame objects (:id shape))
flows (get page :objects)
first? (not-any? #(seq (:interactions %)) (vals objects))
flows (get page :flows)
flow (ctp/get-frame-flow flows (:id frame))]
(rx/concat
(rx/of (dwsh/update-shapes
@@ -177,14 +179,17 @@
(let [new-interaction (-> ctsi/default-interaction
(ctsi/set-destination destination)
(assoc :position-relative-to (:id shape)))]
(cls/add-new-interaction shape new-interaction))))
(cls/add-new-interaction shape new-interaction)))))
(when destination
(dwsh/update-shapes [destination] cls/show-in-viewer))
(when destination
(rx/of (dwsh/update-shapes [destination] cls/show-in-viewer)))
(when (and (not (connected-frame? objects (:id frame)))
(nil? flow))
(add-flow (:id frame))))))))))
(when (and (not (connected-frame? objects (:id frame)))
(nil? flow))
(rx/of (add-flow (:id frame))))
(when first?
;; When the first interaction of the page is created we emit the event "create-prototype"
(rx/of (ptk/event ::ev/event {::ev/name "create-prototype"})))))))))
(defn remove-interaction
([shape index]

View File

@@ -586,7 +586,7 @@
in the given file library. Then selects the newly created instance."
([file-id component-id position]
(instantiate-component file-id component-id position nil))
([file-id component-id position {:keys [start-move? initial-point id-ref]}]
([file-id component-id position {:keys [start-move? initial-point id-ref origin]}]
(dm/assert! (uuid? file-id))
(dm/assert! (uuid? component-id))
(dm/assert! (gpt/point? position))
@@ -600,6 +600,8 @@
changes (-> (pcb/empty-changes it (:id page))
(pcb/with-objects objects))
current-file-id (:current-file-id state)
[new-shape changes]
(cll/generate-instantiate-component changes
objects
@@ -608,12 +610,18 @@
position
page
libraries)
undo-id (js/Symbol)]
(when id-ref
(reset! id-ref (:id new-shape)))
(rx/of (dwu/start-undo-transaction undo-id)
(rx/of (ptk/event
::ev/event
{::ev/name "use-library-component"
::ev/origin origin
:external-library (not= file-id current-file-id)})
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(ptk/data-event :layout/update {:ids [(:id new-shape)]})
(dws/select-shapes (d/ordered-set (:id new-shape)))
@@ -881,6 +889,12 @@
(rx/of
(dwu/start-undo-transaction undo-id)
(update-component shape-id undo-group)
;; These two calls are necessary for properly sync thumbnails
;; when a main component does not live in the same page
(update-component-thumbnail-sync state component-id file-id "frame")
(update-component-thumbnail-sync state component-id file-id "component")
(sync-file current-file-id file-id :components component-id undo-group)
(when (not current-file?)
(sync-file file-id file-id :components component-id undo-group))

View File

@@ -456,22 +456,30 @@
id-duplicated (first new-ids)
frames (into #{}
(map #(get-in objects [% :frame-id]))
ids)
undo-id (js/Symbol)]
frames (into #{}
(map #(get-in objects [% :frame-id]))
ids)
undo-id (js/Symbol)]
(rx/concat
(->> (map (d/getf objects) ids)
(filter ctk/instance-head?)
(map (fn [{:keys [component-file]}]
(ptk/event ::ev/event
{::ev/name "use-library-component"
::ev/origin "duplicate"
:external-library (not= file-id component-file)})))
(rx/from))
;; Warning: This order is important for the focus mode.
(->> (rx/of
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(when change-selection?
(select-shapes new-ids))
(ptk/data-event :layout/update {:ids frames})
(memorize-duplicated id-original id-duplicated)
(dwu/commit-undo-transaction undo-id))
(rx/tap #(when (some? return-ref)
(reset! return-ref id-duplicated))))))))))
(->> (rx/of
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(when change-selection?
(select-shapes new-ids))
(ptk/data-event :layout/update {:ids frames})
(memorize-duplicated id-original id-duplicated)
(dwu/commit-undo-transaction undo-id))
(rx/tap #(when (some? return-ref)
(reset! return-ref id-duplicated)))))))))))
(defn duplicate-selected
([move-delta?]

View File

@@ -447,11 +447,6 @@
:subsections [:panels]
:fn #(st/emit! (dw/go-to-layout :assets))}
:toggle-history {:tooltip (ds/alt "H")
:command (ds/a-mod "h")
:subsections [:panels]
:fn #(emit-when-no-readonly (dw/go-to-layout :document-history))}
:toggle-colorpalette {:tooltip (ds/alt "P")
:command (ds/a-mod "p")
:subsections [:panels]

View File

@@ -37,6 +37,8 @@
;; -- V2 Editor Helpers
(def ^function create-root-from-string editor.v2/createRootFromString)
(def ^function create-root-from-html editor.v2/createRootFromHTML)
(def ^function create-editor editor.v2/create)
(def ^function set-editor-root! editor.v2/setRoot)
(def ^function get-editor-root editor.v2/getRoot)

View File

@@ -52,6 +52,11 @@
(defonce queue
(q/create find-request (/ 1000 30)))
(defn clear-queue!
[]
(l/dbg :hint "clearing thumbnail queue")
(q/clear! queue))
;; This function first renders the HTML calling `render/render-frame` that
;; returns HTML as a string, then we send that data to the iframe rasterizer
;; that returns the image as a Blob. Finally we create a URI for that blob.

View File

@@ -11,6 +11,7 @@
[app.main.data.events :as ev]
[app.main.data.persistence :as dwp]
[app.main.data.workspace :as dw]
[app.main.data.workspace.thumbnails :as th]
[app.main.refs :as refs]
[app.main.repo :as rp]
[app.util.time :as dt]
@@ -126,11 +127,13 @@
ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(rx/of ::dwp/force-persist)
(rx/of ::dwp/force-persist
(dw/remove-layout-flag :document-history))
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
(rx/filter #(or (nil? %) (= :saved %)))
(rx/take 1)
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
(rx/tap #(th/clear-queue!))
(rx/map #(dw/initialize-file project-id file-id)))
(case origin
:version

View File

@@ -99,7 +99,7 @@
[{:keys [shape]}]
(let [thumbnails? (mf/use-ctx muc/render-thumbnails)
childs (mapv (d/getf objects) (:shapes shape))]
(if (and thumbnails? (some? (:thumbnail shape)))
(if (and thumbnails? (some? (:thumbnail-id shape)))
[:& frame/frame-thumbnail {:shape shape :bounds (:children-bounds shape)}]
[:& frame-shape {:shape shape :childs childs}])))))

View File

@@ -67,6 +67,7 @@
show-release-modal?
(and (contains? cf/flags :onboarding)
(not (contains? cf/flags :hide-release-modal))
(:onboarding-viewed props)
(not= (:release-notes-viewed props) (:main cf/version))
(not= "0.0" (:main cf/version)))]
@@ -106,7 +107,7 @@
:dashboard-team-webhooks
:dashboard-team-settings)
[:?
#_[:& app.main.ui.releases/release-notes-modal {:version "2.3"}]
#_[:& app.main.ui.releases/release-notes-modal {:version "2.4"}]
#_[:& app.main.ui.onboarding/onboarding-templates-modal]
#_[:& app.main.ui.onboarding/onboarding-modal]
#_[:& app.main.ui.onboarding.team-choice/onboarding-team-modal]

View File

@@ -18,6 +18,7 @@
opacity: $op-10;
visibility: visible;
}
&.fixed {
position: fixed;
}
@@ -35,8 +36,9 @@
border: $s-2 solid var(--panel-border-color);
background-color: var(--menu-background-color);
overflow: auto;
& .separator {
height: $s-12;
border-top: solid $s-1 var(--color-background-quaternary);
}
&.min-width {
@@ -45,6 +47,7 @@
.context-menu-item {
display: flex;
.context-menu-action {
@include bodySmallTypography;
display: flex;
@@ -56,12 +59,15 @@
border-radius: $br-8;
white-space: nowrap;
color: var(--menu-foreground-color);
&.submenu {
display: flex;
align-items: center;
justify-content: space-between;
.submenu-icon {
margin-left: 0.5rem;
svg {
@extend .button-icon-small;
stroke: var(--menu-foreground-color);
@@ -76,6 +82,7 @@
background: none;
border: none;
cursor: pointer;
.submenu-icon-back svg {
@extend .button-icon-small;
stroke: var(--menu-foreground-color);
@@ -83,27 +90,34 @@
}
}
}
&:hover .context-menu-action {
background-color: var(--menu-background-color-hover);
text-decoration: none;
color: var(--menu-foreground-color);
&.submenu .submenu-icon svg {
stroke: var(--menu-foreground-color);
}
&.submenu-back .submenu-icon-back svg {
stroke: var(--menu-foreground-color);
}
}
&:focus {
outline: none;
}
&:focus-visible {
outline: none;
.context-menu-action {
border: 1px solid var(--menu-border-color-focus);
background-color: var(--menu-background-color-focus);
text-decoration: none;
color: var(--menu-foreground-color-focus);
&.submenu .submenu-icon svg {
stroke: var(--menu-foreground-color-focus);
}
@@ -113,15 +127,18 @@
}
}
}
&.selected {
.context-menu-action {
justify-content: space-between;
color: var(--menu-foreground-color-focus);
}
.selected-icon {
@extend .button-tag;
border-radius: $br-8;
height: 100%;
svg {
@extend .button-icon-small;
stroke: var(--menu-foreground-color-focus);
@@ -129,6 +146,7 @@
}
}
}
.is-selected .context-menu-action {
padding-left: $s-28;
background-image: url(/images/icons/tick.svg);
@@ -138,6 +156,7 @@
font-weight: $fw700;
}
}
&.is-selectable {
.context-menu-action {
padding-left: 1.5rem;

View File

@@ -13,7 +13,6 @@
[app.main.data.exports.files :as fexp]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
[app.main.refs :as refs]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
@@ -57,9 +56,8 @@
(mf/defc file-menu*
{::mf/props :obj}
[{:keys [files show on-edit on-menu-close top left navigate origin parent-id can-edit]}]
[{:keys [files on-edit on-menu-close top left navigate origin parent-id can-edit]}]
(assert (seq files) "missing `files` prop")
(assert (boolean? show) "missing `show` prop")
(assert (fn? on-edit) "missing `on-edit` prop")
(assert (fn? on-menu-close) "missing `on-menu-close` prop")
(assert (boolean? navigate) "missing `navigate` prop")
@@ -74,12 +72,11 @@
multi? (> file-count 1)
current-team-id (mf/use-ctx ctx/current-team-id)
teams (mf/use-state nil)
default-team (-> (mf/deref refs/teams)
(get current-team-id))
teams* (mf/use-state nil)
teams (deref teams*)
current-team (or (get @teams current-team-id) default-team)
other-teams (remove #(= (:id %) current-team-id) (vals @teams))
current-team (get teams current-team-id)
other-teams (remove #(= (:id %) current-team-id) (vals teams))
current-projects (remove #(= (:id %) (:project-id file))
(:projects current-team))
@@ -207,142 +204,134 @@
on-export-standard-files
(mf/use-fn
(mf/deps on-export-files)
(partial on-export-files :legacy-zip))
(partial on-export-files :legacy-zip))]
;; NOTE: this is used for detect if component is still mounted
mounted-ref (mf/use-ref true)]
(mf/with-effect []
(->> (rp/cmd! :get-all-projects)
(rx/map group-by-team)
(rx/subs! #(reset! teams* %))))
(mf/use-effect
(mf/deps show)
(fn []
(when show
(->> (rp/cmd! :get-all-projects)
(rx/map group-by-team)
(rx/subs! #(when (mf/ref-val mounted-ref)
(reset! teams %)))))))
(let [sub-options
(concat
(for [project current-projects]
{:name (get-project-name project)
:id (get-project-id project)
:handler (on-move (:id current-team)
(:id project))})
(when (seq other-teams)
[{:name (tr "dashboard.move-to-other-team")
:id "move-to-other-team"
:options
(for [team other-teams]
{:name (get-team-name team)
:id (get-project-id team)
:options
(for [sub-project (:projects team)]
{:name (get-project-name sub-project)
:id (get-project-id sub-project)
:handler (on-move (:id team)
(:id sub-project))})})}]))
(when current-team
(let [sub-options
(concat
(for [project current-projects]
{:name (get-project-name project)
:id (get-project-id project)
:handler (on-move (:id current-team)
(:id project))})
(when (seq other-teams)
[{:name (tr "dashboard.move-to-other-team")
:id "move-to-other-team"
:options
(for [team other-teams]
{:name (get-team-name team)
:id (get-project-id team)
:options
(for [sub-project (:projects team)]
{:name (get-project-name sub-project)
:id (get-project-id sub-project)
:handler (on-move (:id team)
(:id sub-project))})})}]))
options
(if multi?
[(when can-edit
{:name (tr "dashboard.duplicate-multi" file-count)
:id "duplicate-multi"
:handler on-duplicate})
options
(if multi?
[(when can-edit
{:name (tr "dashboard.duplicate-multi" file-count)
:id "duplicate-multi"
:handler on-duplicate})
(when (and (or (seq current-projects) (seq other-teams)) can-edit)
{:name (tr "dashboard.move-to-multi" file-count)
:id "file-move-multi"
:options sub-options})
(when (and (or (seq current-projects) (seq other-teams)) can-edit)
{:name (tr "dashboard.move-to-multi" file-count)
:id "file-move-multi"
:options sub-options})
(when-not (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.export-binary-multi" file-count)
:id "file-binary-export-multi"
:handler on-export-binary-files})
(when-not (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.export-binary-multi" file-count)
:id "file-binary-export-multi"
:handler on-export-binary-files})
(when (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.export-binary-multi" file-count)
:id "file-binary-export-multi"
:handler on-export-binary-files-v3})
(when (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.export-binary-multi" file-count)
:id "file-binary-export-multi"
:handler on-export-binary-files-v3})
(when-not (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.export-standard-multi" file-count)
:id "file-standard-export-multi"
:handler on-export-standard-files})
(when-not (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.export-standard-multi" file-count)
:id "file-standard-export-multi"
:handler on-export-standard-files})
(when (and (:is-shared file) can-edit)
{:name (tr "labels.unpublish-multi-files" file-count)
:id "file-unpublish-multi"
:handler on-del-shared})
(when (and (:is-shared file) can-edit)
{:name (tr "labels.unpublish-multi-files" file-count)
:id "file-unpublish-multi"
:handler on-del-shared})
(when (and (not is-lib-page?) can-edit)
{:name :separator}
{:name (tr "labels.delete-multi-files" file-count)
:id "file-delete-multi"
:handler on-delete})]
(when (and (not is-lib-page?) can-edit)
{:name :separator}
{:name (tr "labels.delete-multi-files" file-count)
:id "file-delete-multi"
:handler on-delete})]
[{:name (tr "dashboard.open-in-new-tab")
:id "file-open-new-tab"
:handler on-new-tab}
(when (and (not is-search-page?) can-edit)
{:name (tr "labels.rename")
:id "file-rename"
:handler on-edit})
[{:name (tr "dashboard.open-in-new-tab")
:id "file-open-new-tab"
:handler on-new-tab}
(when (and (not is-search-page?) can-edit)
{:name (tr "labels.rename")
:id "file-rename"
:handler on-edit})
(when (and (not is-search-page?) can-edit)
{:name (tr "dashboard.duplicate")
:id "file-duplicate"
:handler on-duplicate})
(when (and (not is-search-page?) can-edit)
{:name (tr "dashboard.duplicate")
:id "file-duplicate"
:handler on-duplicate})
(when (and (not is-lib-page?)
(not is-search-page?)
(or (seq current-projects) (seq other-teams))
can-edit)
{:name (tr "dashboard.move-to")
:id "file-move-to"
:options sub-options})
(when (and (not is-lib-page?)
(not is-search-page?)
(or (seq current-projects) (seq other-teams))
can-edit)
{:name (tr "dashboard.move-to")
:id "file-move-to"
:options sub-options})
(when (and (not is-search-page?)
can-edit)
(if (:is-shared file)
{:name (tr "dashboard.unpublish-shared")
:id "file-del-shared"
:handler on-del-shared}
{:name (tr "dashboard.add-shared")
:id "file-add-shared"
:handler on-add-shared}))
(when (and (not is-search-page?)
can-edit)
(if (:is-shared file)
{:name (tr "dashboard.unpublish-shared")
:id "file-del-shared"
:handler on-del-shared}
{:name (tr "dashboard.add-shared")
:id "file-add-shared"
:handler on-add-shared}))
{:name :separator}
{:name :separator}
(when-not (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.download-binary-file")
:id "download-binary-file"
:handler on-export-binary-files})
(when-not (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.download-binary-file")
:id "download-binary-file"
:handler on-export-binary-files})
(when (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.download-binary-file")
:id "download-binary-file"
:handler on-export-binary-files-v3})
(when (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.download-binary-file")
:id "download-binary-file"
:handler on-export-binary-files-v3})
(when-not (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.download-standard-file")
:id "download-standard-file"
:handler on-export-standard-files})
(when-not (contains? cf/flags :export-file-v3)
{:name (tr "dashboard.download-standard-file")
:id "download-standard-file"
:handler on-export-standard-files})
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name :separator})
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name :separator})
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name (tr "labels.delete")
:id "file-delete"
:handler on-delete})])]
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name (tr "labels.delete")
:id "file-delete"
:handler on-delete})])]
[:> context-menu*
{:on-close on-menu-close
:show show
:fixed (or (not= top 0) (not= left 0))
:min-width true
:top top
:left left
:options options
:origin parent-id}]))))
[:> context-menu*
{:on-close on-menu-close
:fixed (or (not= top 0) (not= left 0))
:show true
:min-width true
:top top
:left left
:options options
:origin parent-id}])))

View File

@@ -406,7 +406,6 @@
;; so the menu can be handled
[:div {:style {:pointer-events "all"}}
[:> file-menu* {:files (vals selected-files)
:show (:menu-open dashboard-local)
:left (+ 24 (:x (:menu-pos dashboard-local)))
:top (:y (:menu-pos dashboard-local))
:can-edit can-edit

View File

@@ -64,6 +64,7 @@
}
.file-entry {
display: flex;
.file-name {
@include flexRow;
.file-icon {
@@ -114,6 +115,8 @@
}
.error-message,
.progress-message {
display: flex;
align-items: center;
height: $s-32;
color: var(--modal-text-foreground-color);
}

View File

@@ -78,6 +78,7 @@
// This icon still use the old svg
.penpot-icon {
@include flexCenter;
svg {
fill: var(--icon-foreground);
width: $s-24;
@@ -138,6 +139,7 @@
.action {
--sidebar-action-icon-color: var(--icon-foreground);
--sidebar-icon-backgroun-color: var(--color-background-secondary);
&:hover {
--sidebar-action-icon-color: var(--color-background-secondary);
--sidebar-icon-backgroun-color: var(--color-accent-primary);
@@ -175,6 +177,7 @@
right: $s-2;
top: $s-52;
max-height: $s-480;
&:not(.teams-dropdown) {
min-width: $s-160;
}
@@ -204,8 +207,10 @@
.sidebar-nav-item {
cursor: pointer;
&:hover {
background-color: var(--sidebar-element-background-color-hover);
span {
color: var(--sidebar-element-foreground-color-hover);
}
@@ -213,6 +218,7 @@
&.current {
background-color: var(--sidebar-element-background-color-selected);
.element-title {
color: var(--sidebar-element-foreground-color-selected);
}
@@ -228,6 +234,7 @@
padding: $s-8 $s-8 $s-8 $s-24;
font-weight: $fw400;
width: 100%;
&:hover {
text-decoration: none;
}
@@ -293,6 +300,7 @@
outline: none;
border: $s-1 solid var(--search-bar-input-border-color-focus);
}
::placeholder {
color: var(--search-bar-placeholder-foreground-color);
}
@@ -370,6 +378,7 @@
.profile-separator {
height: $s-6;
border-top: solid $s-1 var(--color-background-quaternary);
}
.item-with-icon {

View File

@@ -278,27 +278,30 @@
is-you (= (:id profile) (:id member))
can-change-rol (or is-owner is-admin)
not-superior (or is-admin (and can-change-rol (or member-is-admin member-is-editor member-is-viewer)))
not-superior (or (and (not member-is-owner) is-admin) (and can-change-rol (or member-is-admin member-is-editor member-is-viewer)))
role (cond
member-is-owner "labels.owner"
member-is-admin "labels.admin"
member-is-editor "labels.editor"
:else "labels.viewer")
:else "labels.viewer")
on-show (mf/use-fn #(reset! show? true))
on-hide (mf/use-fn #(reset! show? false))]
[:*
(if (and can-change-rol not-superior (not (and is-you is-owner)))
[:div {:class (stl/css :rol-selector :has-priv)
:role "combobox"
:aria-labelledby "role-label-id"
:on-click on-show}
[:span {:class (stl/css :rol-label)} (tr role)]
[:span {:class (stl/css :rol-label)
:id "role-label-id"} (tr role)]
arrow-icon]
[:div {:class (stl/css :rol-selector)}
[:span {:class (stl/css :rol-label)} (tr role)]])
[:& dropdown {:show @show? :on-close on-hide}
[:ul {:class (stl/css :roles-dropdown)}
[:ul {:class (stl/css :roles-dropdown)
:role "listbox"}
[:li {:on-click on-set-viewer
:class (stl/css :rol-dropdown-item)}
(tr "labels.viewer")]

View File

@@ -12,7 +12,6 @@
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.data.events :as ev]
[app.main.data.exports.assets :as de]
[app.main.data.modal :as modal]
[app.main.refs :as refs]
@@ -23,7 +22,6 @@
[app.util.i18n :as i18n :refer [tr c]]
[app.util.strings :as ust]
[cuerdas.core :as str]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def ^:private neutral-icon
@@ -59,13 +57,8 @@
(fn [event]
(dom/prevent-default event)
(st/emit! (modal/hide)
(de/request-multiple-export
{:exports enabled-exports
:cmd cmd})
(ptk/event
::ev/event {::ev/name "export-shapes"
::ev/origin origin
:num-shapes (count enabled-exports)})))
(de/request-multiple-export {:exports enabled-exports :cmd cmd})
(de/export-shapes-event enabled-exports origin)))
on-toggle-enabled
(mf/use-fn

View File

@@ -30,6 +30,7 @@
[app.main.ui.releases.v2-1]
[app.main.ui.releases.v2-2]
[app.main.ui.releases.v2-3]
[app.main.ui.releases.v2-4]
[app.util.object :as obj]
[app.util.timers :as tm]
[rumext.v2 :as mf]))
@@ -94,4 +95,4 @@
(defmethod rc/render-release-notes "0.0"
[params]
(rc/render-release-notes (assoc params :version "2.2")))
(rc/render-release-notes (assoc params :version "2.4")))

View File

@@ -0,0 +1,141 @@
;; 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.ui.releases.v2-4
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.ui.releases.common :as c]
[rumext.v2 :as mf]))
;; TODO: Review all copies and alt text
(defmethod c/render-release-notes "2.4"
[{:keys [slide klass next finish navigate version]}]
(mf/html
(case slide
:start
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.4-slide-0.jpg"
:class (stl/css :start-image)
:border "0"
:alt "A graphic illustration with Penpot style"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Whats new in Penpot?"]
[:div {:class (stl/css :version-tag)}
(dm/str "Version " version)]]
[:div {:class (stl/css :features-block)}
[:span {:class (stl/css :feature-title)}
"At Penpot we are at full speed!"]
[:p {:class (stl/css :feature-content)}
"With the release of the long-awaited Plugins System still fresh, this 2.4 brings improvements in a wide range of areas that will serve a variety of use cases."]
[:p {:class (stl/css :feature-content)}
"This release combines some of the most requested features—such as versioning and the viewer-only role—with performance improvements and a new .penpot format that will streamline the export of files and assets."]
[:p {:class (stl/css :feature-content)}
"Lets dive in!"]]
[:div {:class (stl/css :navigation)}
[:button {:class (stl/css :next-btn)
:on-click next} "Continue"]]]]]]
0
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.4-viewer.gif"
:class (stl/css :start-image)
:border "0"
:alt "Viewer role, designed to enhance collaboration"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Viewer role, designed to enhance collaboration"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"Collaboration takes many forms, and sometimes the risk of making unwanted or accidental adjustments can be a barrier to engaging with a design file."]
[:p {:class (stl/css :feature-content)}
"Now, you can invite members to your teams who only need to view and comment on files. Team members, stakeholders, developers… pick your case. Anyone who doesn't need to edit can participate confidently."]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 3}]
[:button {:on-click next
:class (stl/css :next-btn)} "Continue"]]]]]]
1
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.4-history.gif"
:class (stl/css :start-image)
:border "0"
:alt "A timeline for your design process"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"A timeline for your design process"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"Version History allows you to save different stages of your design process, so you can revisit them whenever needed."]
[:p {:class (stl/css :feature-content)}
"Some versions are saved automatically, serving as an invaluable emergency backup. Additionally, you can manually save versions, giving you full control over the timeline associated with a file. This way, you can always restore specific versions that you've intentionally saved."]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 3}]
[:button {:on-click next
:class (stl/css :next-btn)} "Continue"]]]]]]
2
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.4-format.gif"
:class (stl/css :start-image)
:border "0"
:alt "New export format: fast and open"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"New export format: fast and open"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"The new .penpot format will streamline the import and export of files and assets by being more efficient and interoperable."]
[:p {:class (stl/css :feature-content)}
"This format replaces the previous two—so no more choosing between them or accidentally picking the wrong one! It's better for both scenarios: if you just need to import or export files quickly, itll be a bit faster. And if you want to extract data (like a list of color assets), this new format is much easier to read."]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 3}]
[:button {:on-click finish
:class (stl/css :next-btn)} "Let's go"]]]]]])))

View File

@@ -0,0 +1,102 @@
// 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
@import "refactor/common-refactor.scss";
.modal-overlay {
@extend .modal-overlay-base;
}
.modal-container {
display: grid;
grid-template-columns: $s-324 1fr;
height: $s-500;
width: $s-888;
border-radius: $br-8;
background-color: var(--modal-background-color);
border: $s-2 solid var(--modal-border-color);
}
.start-image {
width: $s-324;
border-radius: $br-8 0 0 $br-8;
}
.modal-content {
padding: $s-40;
display: grid;
grid-template-rows: auto 1fr $s-32;
gap: $s-24;
a {
color: var(--button-primary-background-color-rest);
}
}
.modal-header {
display: grid;
gap: $s-8;
}
.version-tag {
@include flexCenter;
@include headlineSmallTypography;
height: $s-32;
width: $s-96;
background-color: var(--communication-tag-background-color);
color: var(--communication-tag-foreground-color);
border-radius: $br-8;
}
.modal-title {
@include headlineLargeTypography;
color: var(--modal-title-foreground-color);
}
.features-block {
display: flex;
flex-direction: column;
gap: $s-16;
width: $s-440;
}
.feature {
display: flex;
flex-direction: column;
gap: $s-8;
}
.feature-title {
@include bodyLargeTypography;
color: var(--modal-title-foreground-color);
}
.feature-content {
@include bodyMediumTypography;
margin: 0;
color: var(--modal-text-foreground-color);
}
.feature-list {
@include bodyMediumTypography;
color: var(--modal-text-foreground-color);
list-style: disc;
display: grid;
gap: $s-8;
}
.navigation {
width: 100%;
display: grid;
grid-template-areas: "bullets button";
}
.next-btn {
@extend .button-primary;
width: $s-100;
justify-self: flex-end;
grid-area: button;
}

View File

@@ -60,7 +60,6 @@
(mf/defc frame-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
children (unchecked-get props "children")

View File

@@ -318,7 +318,7 @@
hr {
margin: $s-20 0;
border-top: solid 1px var(--modal-separator-backogrund-color);
border-top: solid $s-1 var(--modal-separator-backogrund-color);
}
.separator {

View File

@@ -8,7 +8,6 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.data.events :as ev]
[app.main.data.exports.assets :as de]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -18,7 +17,6 @@
[app.util.dom :as dom]
[app.util.i18n :refer [tr c]]
[app.util.keyboard :as kbd]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc exports
@@ -63,15 +61,9 @@
:object-id (-> shapes first :id)}
(cond-> share-id (assoc :share-id share-id)))
exports (mapv #(merge % defaults) @exports)]
(if (= 1 (count exports))
(st/emit!
(de/request-simple-export {:export (first exports)})
(ptk/event
::ev/event {::ev/name "export-shapes" ::ev/origin "viewer" :num-shapes 1}))
(st/emit!
(de/request-multiple-export {:exports exports})
(ptk/event
::ev/event {::ev/name "export-shapes" ::ev/origin "viewer" :num-shapes (count exports)}))))))
(st/emit!
(de/request-export {:exports exports})
(de/export-shapes-event exports "viewer")))))
add-export
(mf/use-callback

View File

@@ -9,7 +9,9 @@
(:require
[app.common.data.macros :as dm]
[app.common.types.component :as ctk]
[app.main.data.events :as ev]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.shape-icon :as sir]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
[app.main.ui.icons :as i]
@@ -18,6 +20,7 @@
[app.main.ui.viewer.inspect.selection-feedback :refer [resolve-shapes]]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(defn- get-libraries
@@ -63,7 +66,9 @@
(fn [new-section]
(reset! section (keyword new-section))
(when on-change-section
(on-change-section (keyword new-section)))))
(on-change-section (keyword new-section))
(st/emit!
(ptk/event ::ev/event {::ev/name "change-inspect-tab" :tab new-section})))))
handle-expand
(mf/use-fn

View File

@@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.data.events :as ev]
[app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.libraries :as dwl]
[app.main.refs :as refs]
@@ -20,15 +21,22 @@
[app.util.i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc palette-item
{::mf/wrap [mf/memo]}
[{:keys [color size]}]
[{:keys [color size selected]}]
(letfn [(select-color [event]
(st/emit!
(dwl/add-recent-color color)
(mdc/apply-color-from-palette color (kbd/alt? event))))]
(mdc/apply-color-from-palette color (kbd/alt? event))
(when (not= selected :recent)
(ptk/event
::ev/event
{::ev/name "use-library-color"
::ev/origin "color-palette"
:external-library (not= selected :file)}))))]
[:div {:class (stl/css-case :color-cell true
:is-not-library-color (nil? (:id color))
:no-text (<= size 64))
@@ -39,7 +47,7 @@
(mf/defc palette
[{:keys [current-colors size width]}]
[{:keys [current-colors size width selected]}]
(let [;; We had to do this due to a bug that leave some bugged colors
current-colors (h/use-equal-memo (filter #(or (:gradient %) (:color %) (:image %)) current-colors))
state (mf/use-state {:show-menu false})
@@ -132,7 +140,7 @@
:max-width (str width "px")
:right (str (* offset-step offset) "px")}}
(for [[idx item] (map-indexed vector current-colors)]
[:& palette-item {:color item :key idx :size size}])])]
[:& palette-item {:color item :key idx :size size :selected selected}])])]
(when show-arrows?
[:button {:class (stl/css :right-arrow)
:disabled (= offset max-offset)
@@ -170,4 +178,5 @@
[:& palette {:current-colors @colors
:size size
:width width}]))
:width width
:selected selected}]))

View File

@@ -22,6 +22,7 @@
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc libraries
@@ -76,8 +77,14 @@
on-color-click
(mf/use-fn
(mf/deps state)
(mf/deps state @selected)
(fn [event]
(when-not (= :recent @selected)
(st/emit! (ptk/event
::ev/event
{::ev/name "use-library-color"
::ev/origin "colorpicker"
:external-library (not= :file @selected)})))
(on-select-color state event)))]
;; Load library colors when the select is changed

View File

@@ -38,6 +38,7 @@
[app.util.i18n :refer [tr] :as i18n]
[app.util.timers :as timers]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def menu-ref
@@ -532,7 +533,9 @@
:title (tr "modals.delete-page.title")
:message (tr "modals.delete-page.body")
:on-accept delete-fn}))
do-duplicate #(st/emit! (dw/duplicate-page id))
do-duplicate #(st/emit!
(dw/duplicate-page id)
(ptk/event ::ev/event {::ev/name "duplicate-page"}))
do-rename #(st/emit! (dw/start-rename-page-item id))]
[:*

View File

@@ -15,7 +15,9 @@
[app.common.types.typographies-list :as ctyl]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.dashboard :as dd]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
[app.main.data.users :as du]
[app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.libraries :as dwl]
@@ -33,6 +35,7 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.strings :refer [matches-search]]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[okulary.core :as l]
[rumext.v2 :as mf]))
@@ -84,8 +87,10 @@
(conj (tr "workspace.libraries.typography" typography-count))))
"\u00A0")))
(mf/defc describe-library-blocks
[{:keys [components-count graphics-count colors-count typography-count] :as props}]
(mf/defc describe-library-blocks*
{::mf/props :obj
::mf/private true}
[{:keys [components-count graphics-count colors-count typography-count]}]
[:*
(when (pos? components-count)
[:li {:class (stl/css :element-count)}
@@ -104,9 +109,49 @@
(tr "workspace.libraries.typography" typography-count)])])
(mf/defc sample-library-entry*
{::mf/props :obj
::mf/private true}
[{:keys [library project-id team-id importing]}]
(let [id (:id library)
importing? (deref importing)
on-error
(mf/use-fn
(fn [_]
(reset! importing nil)
(rx/of (ntf/error (tr "dashboard.libraries-and-templates.import-error")))))
on-success
(mf/use-fn
(mf/deps team-id)
(fn [_]
(st/emit! (dwl/fetch-shared-files {:team-id team-id}))))
import-library
(mf/use-fn
(fn [_]
(reset! importing id)
(st/emit! (dd/clone-template
(with-meta {:project-id project-id
:template-id id}
{:on-success on-success
:on-error on-error})))))]
[:div {:class (stl/css :sample-library-item)
:key (dm/str id)}
[:div {:class (stl/css :sample-library-item-name)} (:name library)]
[:input {:class (stl/css-case :sample-library-button true
:sample-library-add (nil? importing?)
:sample-library-adding (some? importing?))
:type "button"
:value (if (= importing? id) (tr "labels.adding") (tr "labels.add"))
:on-click import-library}]]))
(mf/defc libraries-tab
{::mf/wrap-props false}
[{:keys [file-id shared? linked-libraries shared-libraries]}]
[{:keys [file-id shared? linked-libraries shared-libraries team-id]}]
(let [search-term* (mf/use-state "")
search-term (deref search-term*)
library-ref (mf/with-memo [file-id]
@@ -138,6 +183,12 @@
(->> (vals linked-libraries)
(sort-by (comp str/lower :name))))
importing* (mf/use-state nil)
project-id (mf/deref refs/current-project-id)
sample-libraries [{:id "penpot-design-system", :name "Design system example"}
{:id "wireframing-kit", :name "Wireframe library"}
{:id "whiteboarding-kit", :name "Whiteboarding Kit"}]
change-search-term
(mf/use-fn
(fn [event]
@@ -215,10 +266,10 @@
[:div {:class (stl/css :item-content)}
[:div {:class (stl/css :item-name)} (tr "workspace.libraries.file-library")]
[:ul {:class (stl/css :item-contents)}
[:& describe-library-blocks {:components-count (count components)
:graphics-count (count media)
:colors-count (count colors)
:typography-count (count typographies)}]]]
[:> describe-library-blocks* {:components-count (count components)
:graphics-count (count media)
:colors-count (count colors)
:typography-count (count typographies)}]]]
(if ^boolean shared?
[:input {:class (stl/css :item-unpublish)
:type "button"
@@ -240,10 +291,10 @@
graphics-count (count (dm/get-in library [:data :media] []))
colors-count (count (dm/get-in library [:data :colors] []))
typography-count (count (dm/get-in library [:data :typographies] []))]
[:& describe-library-blocks {:components-count components-count
:graphics-count graphics-count
:colors-count colors-count
:typography-count typography-count}])]]
[:> describe-library-blocks* {:components-count components-count
:graphics-count graphics-count
:colors-count colors-count
:typography-count typography-count}])]]
[:button {:class (stl/css :item-button)
:type "button"
@@ -274,10 +325,10 @@
graphics-count (dm/get-in library [:library-summary :media :count] 0)
colors-count (dm/get-in library [:library-summary :colors :count] 0)
typography-count (dm/get-in library [:library-summary :typographies :count] 0)]
[:& describe-library-blocks {:components-count components-count
:graphics-count graphics-count
:colors-count colors-count
:typography-count typography-count}])]]
[:> describe-library-blocks* {:components-count components-count
:graphics-count graphics-count
:colors-count colors-count
:typography-count typography-count}])]]
[:button {:class (stl/css :item-button-shared)
:data-library-id (dm/str id)
:title (tr "workspace.libraries.shared-library-btn")
@@ -290,6 +341,23 @@
(nil? shared-libraries)
(tr "workspace.libraries.loading")
(and (str/empty? search-term) (cf/external-feature-flag "templates-03" "test"))
[:*
[:div {:class (stl/css :sample-libraries-info)}
(tr "workspace.libraries.empty.no-libraries")
[:a {:target "_blank"
:class (stl/css :sample-libraries-link)
:href "https://penpot.app/libraries-templates"}
(tr "workspace.libraries.empty.some-templates")]]
[:div {:class (stl/css :sample-libraries-container)}
(tr "workspace.libraries.empty.add-some")
(for [library sample-libraries]
[:> sample-library-entry*
{:library library
:project-id project-id
:team-id team-id
:importing importing*}])]]
(str/empty? search-term)
[:*
[:span {:class (stl/css :empty-state-icon)}
@@ -519,7 +587,8 @@
:content (mf/html [:& libraries-tab {:file-id file-id
:shared? shared?
:linked-libraries libraries
:shared-libraries shared-libraries}])}
:shared-libraries shared-libraries
:team-id team-id}])}
#js {:label (tr "workspace.libraries.updates")
:id "updates"

View File

@@ -126,6 +126,7 @@
@include flexCenter;
width: $s-20;
padding: 0 0 0 $s-8;
svg {
@extend .button-icon-small;
stroke: var(--icon-foreground);
@@ -231,6 +232,7 @@
padding: $s-8 $s-24;
margin-inline-end: $s-2;
border-radius: $br-8;
&:disabled {
@extend .button-disabled;
}
@@ -333,3 +335,62 @@
text-decoration: underline;
font-weight: $fw400;
}
.sample-libraries-info {
display: flex;
flex-direction: column;
font-size: $fs-12;
margin: $s-32;
color: var(--color-foreground-secondary);
}
.sample-libraries-link {
color: var(--color-accent-primary);
text-decoration: underline;
font-weight: $fw400;
}
.sample-libraries-container {
display: flex;
flex-direction: column;
font-size: $fs-12;
width: 100%;
align-items: start;
color: var(--color-foreground-secondary);
}
.sample-library-item {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-top: $s-8;
}
.sample-library-item-name {
font-size: $fs-14;
color: var(--color-foreground-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: $s-232;
}
.sample-library-add {
@extend .button-secondary;
}
.sample-library-adding {
@extend .button-disabled;
}
.sample-library-button {
@include uppercaseTitleTipography;
height: $s-32;
width: $s-80;
margin: 0;
border-radius: $br-8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@@ -613,23 +613,25 @@
[:span {:class (stl/css :item-name)}
(tr "dashboard.add-shared")]]))
[:div {:class (stl/css :separator)}]
(when can-edit
[:*
[:div {:class (stl/css :separator)}]
[:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-pin-version
:on-key-down on-pin-version-key-down
:id "file-menu-show-version-history"}
[:span {:class (stl/css :item-name)}
(tr "dashboard.create-version-menu")]]
[:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-pin-version
:on-key-down on-pin-version-key-down
:id "file-menu-show-version-history"}
[:span {:class (stl/css :item-name)}
(tr "dashboard.create-version-menu")]]
[:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-show-version-history
:on-key-down on-show-version-history-key-down
:id "file-menu-show-version-history"}
[:span {:class (stl/css :item-name)}
(tr "dashboard.show-version-history")]]
[:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-show-version-history
:on-key-down on-show-version-history-key-down
:id "file-menu-show-version-history"}
[:span {:class (stl/css :item-name)}
(tr "dashboard.show-version-history")]]
[:div {:class (stl/css :separator)}]
[:div {:class (stl/css :separator)}]])
[:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-export-shapes

View File

@@ -243,14 +243,16 @@
(when-not ^boolean read-only?
[:div {:class (stl/css :history-section)}
[:button
{:title (tr "workspace.sidebar.history" (sc/get-tooltip :toggle-history))
:aria-label (tr "workspace.sidebar.history" (sc/get-tooltip :toggle-history))
{:title (tr "workspace.sidebar.history")
:aria-label (tr "workspace.sidebar.history")
:class (stl/css-case :selected (contains? layout :document-history)
:history-button true)
:on-click toggle-history}
i/history]])
(when (cf/external-feature-flag "share-01" "test")
(when (and
(not (:is-default team))
(cf/external-feature-flag "share-01" "test"))
[:a {:class (stl/css :viewer-btn)
:title (tr "workspace.header.share")
:on-click open-share-dialog}

View File

@@ -235,7 +235,11 @@
(when (some? modifiers)
(d/mapm (fn [id {current-modifiers :modifiers}]
(let [shape (get objects id)
adapt-text? (and (= :text (:type shape)) (not (ctm/only-move? current-modifiers)))
adapt-text?
(and (= :text (:type shape))
(ctm/has-geometry? current-modifiers)
(not (ctm/only-move? current-modifiers)))
current-modifiers
(cond-> current-modifiers

View File

@@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.config :as cf]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.assets :as dwa]
@@ -86,6 +87,13 @@
section (:section filters)
ordering (:ordering filters)
reverse-sort? (= :desc ordering)
num-libs (count (mf/deref refs/workspace-libraries))
show-templates-04-test1?
(and (cf/external-feature-flag "templates-04" "test1") (zero? num-libs))
show-templates-04-test2?
(and (cf/external-feature-flag "templates-04" "test2") (zero? num-libs))
toggle-ordering
(mf/use-fn
@@ -155,12 +163,25 @@
[:article {:class (stl/css :assets-bar)}
[:div {:class (stl/css :assets-header)}
(when-not ^boolean read-only?
[:button {:class (stl/css :libraries-button)
:on-click show-libraries-dialog
:data-testid "libraries"}
[:span {:class (stl/css :libraries-icon)}
i/library]
(tr "workspace.assets.libraries")])
(cond
show-templates-04-test1?
[:button {:class (stl/css :libraries-button)
:on-click show-libraries-dialog
:data-testid "libraries"}
(tr "workspace.assets.add-library")]
show-templates-04-test2?
[:button {:class (stl/css :add-library-button)
:on-click show-libraries-dialog
:data-testid "libraries"}
(tr "workspace.assets.add-library")]
:else
[:button {:class (stl/css :libraries-button)
:on-click show-libraries-dialog
:data-testid "libraries"}
[:span {:class (stl/css :libraries-icon)}
i/library]
(tr "workspace.assets.libraries")]))
[:div {:class (stl/css :search-wrapper)}
[:& search-bar {:on-change on-search-term-change

View File

@@ -25,34 +25,50 @@
width: 100%;
margin-bottom: $s-4;
border-radius: $s-8;
.libraries-icon {
@include flexCenter;
width: $s-24;
height: 100%;
svg {
@include flexCenter;
@extend .button-icon;
stroke: var(--icon-foreground);
}
}
&:hover {
background-color: var(--button-secondary-background-color-hover);
color: var(--button-secondary-foreground-color-hover);
border: $s-1 solid var(--button-secondary-border-color-hover);
svg {
stroke: var(--button-secondary-foreground-color-hover);
}
}
&:focus {
background-color: var(--button-secondary-background-color-focus);
color: var(--button-secondary-foreground-color-focus);
border: $s-1 solid var(--button-secondary-border-color-focus);
svg {
stroke: var(--button-secondary-foreground-color-focus);
}
}
}
.add-library-button {
@extend .button-primary;
text-transform: uppercase;
gap: $s-2;
height: $s-32;
width: 100%;
margin-bottom: $s-4;
border-radius: $s-8;
}
.section-button {
@include flexCenter;
@include buttonStyle;
@@ -62,32 +78,39 @@
border: $s-1 solid var(--input-border-color-rest);
border-radius: $br-8 $br-2 $br-2 $br-8;
background-color: var(--input-background-color-rest);
svg {
height: $s-16;
width: $s-16;
stroke: var(--icon-foreground);
}
&:focus {
border: $s-1 solid var(--input-border-color-focus);
outline: 0;
background-color: var(--input-background-color-focus);
color: var(--input-foreground-color-focus);
svg {
background-color: var(--input-background-color-focus);
}
}
&:hover {
border: $s-1 solid var(--input-border-color-hover);
background-color: var(--input-background-color-hover);
svg {
background-color: var(--input-background-color-hover);
stroke: var(--button-foreground-hover);
}
&:focus {
border: $s-1 solid var(--input-border-color-focus);
outline: 0;
background-color: var(--input-background-color-focus);
color: var(--input-foreground-color-focus);
svg {
background-color: var(--input-background-color-focus);
}

View File

@@ -70,7 +70,12 @@
(fn [event]
(st/emit!
(dwl/add-recent-color color)
(dc/apply-color-from-palette color (kbd/alt? event)))))
(dc/apply-color-from-palette color (kbd/alt? event))
(ptk/event
::ev/event
{::ev/name "use-library-color"
::ev/origin "sidebar"
:external-library (not local?)}))))
rename-color
(mf/use-fn

View File

@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.data.events :as ev]
[app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.undo :as dwu]
@@ -29,6 +30,7 @@
[app.util.router :as rt]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def lens:open-status
@@ -50,7 +52,15 @@
(mf/use-fn
(mf/deps file-id open?)
(fn []
(st/emit! (dw/set-assets-section-open file-id :library (not open?)))))]
(st/emit! (dw/set-assets-section-open file-id :library (not open?)))))
on-click
(mf/use-fn
(fn [ev]
(dom/stop-propagation ev)
(st/emit!
(ptk/event ::ev/event {::ev/name "navigate-to-library-file"}))))]
[:div {:class (stl/css-case :library-title true
:open open?)}
[:& title-bar {:collapsable true
@@ -65,11 +75,11 @@
(mf/html [:div {:class (stl/css :special-title)}
file-name]))}
(when-not local?
[:span {:title "Open library file"}
[:span {:title (tr "workspace.assets.open-library")}
[:a {:class (stl/css :file-link)
:href (str "#" url)
:target "_blank"
:on-click dom/stop-propagation}
:on-click on-click}
i/open-link]])]]))
(mf/defc file-library-content

View File

@@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl]
@@ -26,6 +27,7 @@
[app.util.i18n :as i18n :refer [tr]]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def lens:typography-section-state
@@ -97,7 +99,18 @@
on-asset-click
(mf/use-fn
(mf/deps typography apply-typography on-asset-click)
(partial on-asset-click typography-id apply-typography))]
(partial on-asset-click typography-id apply-typography))
on-click
(mf/use-fn
(mf/deps typography apply-typography on-asset-click)
(fn [ev]
(st/emit! (ptk/event
::ev/event
{::ev/name "use-library-typography"
::ev/origin "sidebar"
:external-library (not local?)}))
(on-asset-click ev)))]
[:div {:class (stl/css :typography-item)
:ref item-ref
@@ -113,7 +126,7 @@
:typography typography
:local? local?
:selected? (contains? selected typography-id)
:on-click on-asset-click
:on-click on-click
:on-change handle-change
:on-context-menu on-context-menu
:editing? editing?

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