Compare commits

...

103 Commits

Author SHA1 Message Date
Alejandro
d31138db72 Merge pull request #2868 from penpot/alotor-fix-layout-problems
Fix layout problems
2023-01-31 12:19:20 +01:00
Alejandro Alonso
2c5f35e192 🐛 Fix penpot.app urls 2023-01-31 12:10:06 +01:00
Alejandro Alonso
5a8f8ba349 🐛 Fix create team and invite 2023-01-31 12:09:13 +01:00
alonso.torres
3fe5cd3752 🐛 Fix problem when resizing layout to zero size 2023-01-31 12:07:17 +01:00
alonso.torres
da60911d81 🐛 Create new layouts without clip content 2023-01-31 12:03:23 +01:00
Alejandro
f4f1f80050 Merge pull request #2864 from penpot/alotor-fix-hug-compacting
🐛 Fix problem with size 100% and auto parent
2023-01-31 10:29:06 +01:00
alonso.torres
18445ea5f4 🐛 Fix problem with size 100% and auto parent 2023-01-31 09:40:01 +01:00
Alejandro
2d28e02742 Merge pull request #2865 from penpot/superalex-fix-onboarding-poll
🐛 Fix onboarding poll
2023-01-31 09:30:32 +01:00
Alejandro Alonso
b0b963fb7c 🐛 Fix onboarding poll 2023-01-31 09:24:50 +01:00
Alejandro Alonso
5cfee13956 🐛 Remove beta word 2023-01-30 17:35:18 +01:00
Alejandro
7271e98df3 Merge pull request #2862 from penpot/niwinz-multipart-encodign-bugfix
⬆️ Update yetti to v9.12
2023-01-30 15:38:44 +01:00
Andrey Antukh
f0386ef7b0 ⬆️ Update yetti to v9.12
Fixes encoding bug on multipart uploads
2023-01-30 15:29:53 +01:00
Alejandro
185cabb2fa Merge pull request #2861 from penpot/alotor-regenerate-empty-thumbnails
🐛 Try to refresh thumbnails on empty stored data in back
2023-01-30 14:05:57 +01:00
alonso.torres
3a19223264 🐛 Try to refresh thumbnails on empty stored data in back 2023-01-30 13:45:56 +01:00
Alejandro Alonso
2c38f31aa9 🐛 Fix clean archived auditlog 2023-01-30 13:11:50 +01:00
Alejandro
a1dcb11261 Merge pull request #2860 from penpot/palba-fix-paste-texts-typos
🐛 Fix copy paste texts with typography assets linked
2023-01-30 12:32:32 +01:00
Pablo Alba
9f8d86a80e 🐛 Fix copy paste texts with typography assets linked 2023-01-30 12:26:31 +01:00
Alejandro Alonso
c59fc87fc4 🐛 Fix styling info at the libraries modal 2023-01-30 12:22:50 +01:00
Alejandro Alonso
3421e6ef57 🐛 Fix viewer misalignment when expanding code tab 2023-01-30 11:54:18 +01:00
Alejandro Alonso
40349c8ece 🐛 Fix line-height inconsistent 2023-01-30 11:44:20 +01:00
Alejandro
5a53376b01 Merge pull request #2859 from penpot/alotor-fix-code-generator-hangs
🐛 Fix problem with code view hanging
2023-01-30 11:18:55 +01:00
Alejandro
d4dfdaff57 Merge pull request #2854 from penpot/palba-fix-incorrect-state-after-undo-page-creation
🐛 Fix incorrect state after undo page creation
2023-01-30 11:14:17 +01:00
Pablo Alba
c7f87d0f26 🐛 Fix incorrect state after undo page creation 2023-01-30 11:13:59 +01:00
alonso.torres
c7954990f0 🐛 Fix problem with code view hanging 2023-01-30 11:13:05 +01:00
Alejandro
fe118819ce Merge pull request #2858 from penpot/info-release-1.17
Info release 1.17
2023-01-30 10:42:39 +01:00
Alejandro
073ec9ea2b Merge pull request #2856 from penpot/alotor-fix-transform-precision
🐛 Fix problem with transform when a coordinate was very close to …
2023-01-30 09:57:50 +01:00
Alejandro
f85a731969 Merge pull request #2855 from penpot/alotor-migration-fix-frame-id
🐛 Add migration to fix problem with frame-id
2023-01-30 09:39:18 +01:00
Alejandro
a3a88d7a0a Merge pull request #2850 from penpot/alotor-fix-frame-overlay
🐛 Fix frame overlays in workspace
2023-01-30 07:19:08 +01:00
Alejandro
1660dd634e Merge pull request #2857 from penpot/palba-fix-resend-invitation-doesnt-reset-expiration
🐛 Fix resend invitation doesn't reset the expiration date
2023-01-30 06:55:47 +01:00
Pablo Alba
6e698110d6 🐛 Fix resend invitation doesn't reset the expiration date 2023-01-27 16:56:19 +01:00
alonso.torres
951c67a2d5 🐛 Fix problem with transform when a coordinate was very close to zero 2023-01-27 16:09:44 +01:00
Alejandro
50b7337b8c Merge pull request #2852 from penpot/eva-bugfixing-4
Eva bugfixing 4
2023-01-27 15:07:20 +01:00
Eva
15e62ff649 🐛 Remove copy all button in fills and strokes 2023-01-27 14:58:53 +01:00
Eva
e7ddd6055f ♻️ Improve a case 2023-01-27 14:53:32 +01:00
Eva
aa3438f800 ♻️ Improve some functions 2023-01-27 14:53:31 +01:00
Eva
a45380a91c 🐛 Fix typo 2023-01-27 14:53:08 +01:00
alonso.torres
86b68aeca4 🐛 Add migration to fix problem with frame-id 2023-01-27 14:43:55 +01:00
alonso.torres
d69d392362 🐛 Fix duplicate frames 2023-01-27 13:41:20 +01:00
Andrés Moya
506c2b8d7b 🔧 Add script to rename some layout attrs in existing files 2023-01-27 13:11:46 +01:00
Eva
b463ebc17b 🐛 Fix layout spec with proper names 2023-01-27 13:11:46 +01:00
alonso.torres
f90fda2c90 🐛 Fix frame overlays in workspace 2023-01-27 11:26:35 +01:00
Eva Marco
87c5aa71a3 Merge pull request #2847 from penpot/superalex-text-weight-inspect-code
🐛 Fix text weight on inspect code
2023-01-27 10:09:11 +01:00
Alejandro Alonso
4f82f6bde4 🐛 Fix text weight on inspect code 2023-01-27 10:00:54 +01:00
Alejandro
545b3860b4 Merge pull request #2844 from penpot/alotor-fix-transparent-thumbnails
🐛 Fix problem with transparent frame thumbnails
2023-01-27 09:29:26 +01:00
alonso.torres
d4921c8eb9 🐛 Fix problem with transparent frame thumbnails 2023-01-27 09:27:18 +01:00
Alejandro Alonso
18652d0b6f 🐛 Fix outline corner radius 2023-01-27 09:26:10 +01:00
Alejandro Alonso
2dbeda1d8f 🐛 Fix outline corner radius 2023-01-27 09:14:11 +01:00
elhombretecla
9422d1e9e2 Fix wording 2023-01-27 07:45:31 +01:00
Alejandro
e0441bc16a Merge pull request #2845 from penpot/palba-text-shortcuts-multi-layer
 Apply text format shortcuts to several layers
2023-01-27 07:03:11 +01:00
Pablo Alba
d7d6166232 Apply text format shortcuts to several layers (even inside groups) 2023-01-26 18:46:16 +01:00
Alejandro
6fd6205634 Merge pull request #2841 from penpot/alotor-polishing-11
Polishing
2023-01-26 16:27:05 +01:00
Eva
7cd6f5ba70 🐛 User icons are not centered 2023-01-26 15:56:21 +01:00
Eva
9cc3cceb06 💄 Change layout flex by flex layout text 2023-01-26 15:56:21 +01:00
Eva
6f6bcd2f7e 💄 Improve warning message css in fonts 2023-01-26 15:56:21 +01:00
Alejandro Alonso
f9f3b3951f 🐛 Fix external borders not considered for thumbnails 2023-01-26 15:43:11 +01:00
Andrés Moya
22ded62000 🐛 Fix paths not flagged as modified when resized 2023-01-26 15:29:20 +01:00
Alejandro Alonso
71d104f768 🐛 Fix fills and strokes on inspect code 2023-01-26 15:18:26 +01:00
alonso.torres
5a36cbceb7 Enter to select children allow for multiselection 2023-01-26 14:27:14 +01:00
Pablo Alba
f2033c46f3 🐛 Fix ctrl+z shows zoom icon 2023-01-26 14:07:21 +01:00
alonso.torres
6b225a10b5 🐛 Fix problem with align and flex layout 2023-01-26 13:44:40 +01:00
alonso.torres
38fe6e856a 🐛 Fix problems with content between/around and auto-width 2023-01-26 12:55:40 +01:00
alonso.torres
1984109436 🐛 Fix problem with change frame groups 2023-01-26 12:55:40 +01:00
alonso.torres
9f9d9277a6 🐛 Fix problem with space-around and auto-width/height 2023-01-26 12:55:40 +01:00
alonso.torres
e041f93680 🐛 Fix space-between preserves gap distances 2023-01-26 12:55:40 +01:00
alonso.torres
2d779a4414 🐛 Fix problem with empty text rendering 2023-01-26 12:55:40 +01:00
Alejandro
21fc9289a6 Merge pull request #2835 from penpot/palba-fix-multiplayer-shadow
🐛 Fix multiuser - "Shadow" element is not updating immediately
2023-01-26 07:33:47 +01:00
Pablo Alba
b40ea3fb2a 🐛 Fix multiuser - "Shadow" element is not updating immediately 2023-01-25 17:48:40 +01:00
Pablo Alba
444e9a3081 Merge pull request #2833 from penpot/hiru-fix-unwanted-popup
🐛 Fix unneeded popup when updating local components
2023-01-25 16:51:29 +01:00
Andrés Moya
f93d305545 🐛 Fix unneeded popup when updating local components 2023-01-25 16:50:41 +01:00
Pablo Alba
09a91c87be Merge pull request #2834 from penpot/superalex-fix-ctrl-c-in-inspect-code
🐛 Fix ctrl+c on inspect code
2023-01-25 16:23:38 +01:00
Alejandro Alonso
e71d569cda 🐛 Fix ctrl+c on inspect code 2023-01-25 16:11:58 +01:00
alonso.torres
a56a9868dc 🐛 Fix error on thumbnail generation 2023-01-25 13:20:06 +01:00
Pablo Alba
a09198b46e 🐛 Fix wrong pop on setup shortcuts 2023-01-25 13:05:03 +01:00
Alejandro
c7e9c658cd Merge pull request #2827 from penpot/eva-flex-bugfixing-2
🐛 Fix missing flex props on code generation
2023-01-25 11:46:34 +01:00
Alejandro
58d7bc5c14 Merge pull request #2831 from penpot/azazeln28-fix-viewer-all-mouse-wheel-issues
Fix all viewer mouse wheel issues
2023-01-25 11:38:31 +01:00
Alejandro
e939db927e Merge pull request #2825 from penpot/palba-text-formatting-shortcuts
🎉 Shortcuts for text formatting
2023-01-25 11:33:17 +01:00
Pablo Alba
efe50479de 🎉 Shortcuts for text formatting 2023-01-25 11:32:59 +01:00
Eva
ea1b3bd058 🐛 Fix missing flex props on code generation 2023-01-25 08:19:33 +01:00
Aitor
4751d7d385 🐛 Fix all viewer mouse wheel issues 2023-01-24 17:44:15 +01:00
Hosted Weblate
bc88e30efa Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2023-01-24 16:17:30 +01:00
Andrés Moya
9623dbfbd6 📚 Validate translations 2023-01-24 16:17:30 +01:00
Andrés Moya
f177de6661 Merge remote-tracking branch 'weblate/develop' into translations 2023-01-24 16:17:30 +01:00
Alejandro
43043e2dc1 Merge pull request #2830 from penpot/alotor-polishing-10
Small bugfixes
2023-01-24 15:53:16 +01:00
alonso.torres
05d21d7d07 🐛 Fix reorder layers with keys not refreshing layout 2023-01-24 15:30:20 +01:00
alonso.torres
02aab37ee7 🐛 Fix bold typefaces in thumbnails 2023-01-24 15:08:58 +01:00
elhombretecla
d3aee1afa3 Add new images 2023-01-24 15:01:18 +01:00
elhombretecla
ac361cdb36 Adds new 1.17 onboarding info 2023-01-24 14:53:12 +01:00
Aitor
7ac6f49c08 Merge pull request #2808 from penpot/superalex-fix-font-vertical-metrics
🐛 Fix font vertical metrics
2023-01-24 14:26:14 +01:00
Alejandro Alonso
d3e11433bf 🐛 Fix font vertical metrics 2023-01-24 14:21:16 +01:00
Pablo Alba
771d1d9194 🐛 Fix double click and lens zoom 2023-01-24 14:19:14 +01:00
Alejandro
4a3a53182b Merge pull request #2797 from penpot/palba-lens-tool
🎉 Zoom lense tool
2023-01-24 13:34:23 +01:00
Pablo Alba
c25cf043fa 🎉 Zoom lense tool 2023-01-24 13:34:04 +01:00
elhombretecla
7440d38c94 Add new login image 2023-01-24 13:19:16 +01:00
Alejandro
a8c0d437ce Merge pull request #2828 from penpot/superalex-update-changes
 Update changes
2023-01-24 12:55:35 +01:00
Alejandro
8d683beae4 Merge pull request #2829 from penpot/fix-safari-thumbnails
🐛 Fix thumbnails for Safari browsers
2023-01-24 12:36:15 +01:00
Alejandro Alonso
4007d8713c Update changes 2023-01-24 12:26:17 +01:00
alonso.torres
ead64a1820 🐛 Fix thumbnails for Safari browsers 2023-01-24 11:56:59 +01:00
Alejandro
88e2a5c56e Merge pull request #2826 from penpot/alotor-thumbnails-performance
Thumbnails performance
2023-01-24 09:59:20 +01:00
alonso.torres
9782d9077f Improved and fixed thumbnail generation 2023-01-24 09:44:56 +01:00
Alejandro
b4c4511d9d Merge pull request #2823 from penpot/alotor-polishing-9
Improved thumbnails behavior
2023-01-23 17:18:53 +01:00
alonso.torres
316b3d4539 🐛 Try to remove cases when the thumbnail could be empty 2023-01-23 14:07:51 +01:00
alonso.torres
1c54e9fa4d Allow debug in for release build 2023-01-23 14:03:28 +01:00
alonso.torres
3d064b804b Improve performance on multiple options 2023-01-23 14:03:02 +01:00
131 changed files with 14624 additions and 5730 deletions

View File

@@ -10,6 +10,16 @@
- Handoff visual improvements [Taiga #3124](https://tree.taiga.io/project/penpot/us/3124)
- Dynamic alignment only in sight [Github 1971](https://github.com/penpot/penpot/issues/1971)
- Add some accessibility to shortcut panel [Taiga #4713](https://tree.taiga.io/project/penpot/issue/4713)
- Add shortcuts for text editing [Taiga #2052](https://tree.taiga.io/project/penpot/us/2052)
- Second level boards treated as groups in terms of selection [Taiga #4269](https://tree.taiga.io/project/penpot/us/4269)
- Performance improvements both for backend and frontend
- Accessibility improvements for login area [Taiga #4353](https://tree.taiga.io/project/penpot/us/4353)
- Outbound webhooks [Taiga #4577](https://tree.taiga.io/project/penpot/us/4577)
- Add copy invitation link to the invitation options [Taiga #4213](https://tree.taiga.io/project/penpot/us/4213)
- Dynamic alignment only in sight [Taiga #3537](https://tree.taiga.io/project/penpot/us/3537)
- Improve naming of layers [Taiga #4036](https://tree.taiga.io/project/penpot/us/4036)
- Add zoom lense [Taiga #4691](https://tree.taiga.io/project/penpot/us/4691)
- Detect potential problems with custom font vertical metrics [Taiga #4697](https://tree.taiga.io/project/penpot/us/4697)
### :bug: Bugs fixed
@@ -50,6 +60,18 @@
- Fix hidden layers inside groups become visible after the group visibility is changed[Taiga #4710](https://tree.taiga.io/project/penpot/issue/4710)
- Fix format of HSLA color on viewer [Taiga #4393](https://tree.taiga.io/project/penpot/issue/4393)
- Fix some typos [Taiga #4724](https://tree.taiga.io/project/penpot/issue/4724)
- Fix ctrl+c for inspect code [Taiga #4739](https://tree.taiga.io/project/penpot/issue/4739)
- Fix text in custom font is not at the expected position at export [Taiga #4394](https://tree.taiga.io/project/penpot/issue/4394)
- Fix unneeded popup when updating local components [Taiga #4430](https://tree.taiga.io/project/penpot/issue/4430)
- Fix multiuser - "Shadow" element is not updating immediately [Taiga #4709](https://tree.taiga.io/project/penpot/issue/4709)
- Fix paths not flagged as modified when resized [Taiga #4742](https://tree.taiga.io/project/penpot/issue/4742)
- Fix resend invitation doesn't reset the expiration date [Taiga #4741](https://tree.taiga.io/project/penpot/issue/4741)
- Fix incorrect state after undo page creation [Taiga #4690](https://tree.taiga.io/project/penpot/issue/4690)
- Fix copy paste texts with typography assets linked [Taiga #4750](https://tree.taiga.io/project/penpot/issue/4750)
### :heart: Community contributions by (Thank you!)
- To @iprithvitharun: let's make UX Writing contributions in Open Source a trend!
## 1.16.2-beta

View File

@@ -22,8 +22,8 @@
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
funcool/yetti
{:git/tag "v9.11"
:git/sha "6f9197a"
{:git/tag "v9.12"
:git/sha "51646d8"
:git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]}

View File

@@ -323,7 +323,7 @@
where archived_at is not null")
(defn- clean-archived
[{:keys [pool]}]
[{:keys [::db/pool]}]
(let [result (db/exec-one! pool [sql:clean-archived])
result (:next.jdbc/update-count result)]
(l/debug :hint "delete archived audit log entries" :deleted result)

View File

@@ -492,6 +492,7 @@
(library-summary [{:keys [id data] :as file}]
(binding [pmap/*load-fn* (partial load-pointer conn id)]
{:components (assets-sample (:components data) 4)
:media (assets-sample (:media data) 3)
:colors (assets-sample (:colors data) 3)
:typographies (assets-sample (:typographies data) 3)}))]

View File

@@ -641,7 +641,7 @@
"insert into team_invitation(team_id, email_to, role, valid_until)
values (?, ?, ?, ?)
on conflict(team_id, email_to) do
update set role = ?, updated_at = now();")
update set role = ?, valid_until = ?, updated_at = now();")
(defn- create-invitation-token
[cfg {:keys [profile-id valid-until team-id member-id member-email role]}]
@@ -712,7 +712,7 @@
{:id (:id member)})))
(do
(db/exec-one! conn [sql:upsert-team-invitation
(:id team) (str/lower email) (name role) expire (name role)])
(:id team) (str/lower email) (name role) expire (name role) expire])
(eml/send! {::eml/conn conn
::eml/factory eml/invite-to-team
:public-uri (cf/get :public-uri)
@@ -786,7 +786,8 @@
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id emails role] :as params}]
(db/with-atomic [conn pool]
(let [team (create-team conn params)
(let [params (assoc params :profile-id profile-id)
team (create-team conn params)
profile (db/get-by-id conn :profile profile-id)
cfg (assoc cfg ::conn conn)]

View File

@@ -41,3 +41,35 @@
([file state]
(repair-orphaned-shapes (:data file))
(update state :total (fnil inc 0))))
(defn rename-layout-attrs
([file]
(let [found? (volatile! false)]
(letfn [(update-shape
[shape]
(when (or (= (:layout-flex-dir shape) :reverse-row)
(= (:layout-flex-dir shape) :reverse-column)
(= (:layout-wrap-type shape) :no-wrap))
(vreset! found? true))
(cond-> shape
(= (:layout-flex-dir shape) :reverse-row)
(assoc :layout-flex-dir :row-reverse)
(= (:layout-flex-dir shape) :reverse-column)
(assoc :layout-flex-dir :column-reverse)
(= (:layout-wrap-type shape) :no-wrap)
(assoc :layout-wrap-type :nowrap)))
(update-page
[page]
(h/update-shapes page update-shape))]
(let [new-file (update file :data h/update-pages update-page)]
(when @found?
(l/info :hint "Found attrs to rename in file"
:id (:id file)
:name (:name file)))
new-file))))
([file state]
(rename-layout-attrs file)
(update state :total (fnil inc 0))))

View File

@@ -13,6 +13,7 @@
[app.common.geom.shapes.flex-layout.modifiers :as fmo]))
(dm/export fbo/layout-content-bounds)
(dm/export fbo/layout-content-points)
(dm/export fbo/child-layout-bound-points)
(dm/export fdr/get-drop-index)
(dm/export fdr/get-drop-areas)

View File

@@ -6,6 +6,7 @@
(ns app.common.geom.shapes.flex-layout.bounds
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.points :as gpo]
[app.common.types.shape.layout :as ctl]))
@@ -27,16 +28,19 @@
h-center? (ctl/h-center? parent)
h-end? (ctl/h-end? parent)
fill-w? (ctl/fill-width? child)
fill-h? (ctl/fill-height? child)
base-p (gpo/origin child-bounds)
width (gpo/width-points child-bounds)
height (gpo/height-points child-bounds)
min-width (if (ctl/fill-width? child)
min-width (if fill-w?
(ctl/child-min-width child)
width)
min-height (if (ctl/fill-height? child)
min-height (if fill-h?
(ctl/child-min-height child)
height)
@@ -60,26 +64,49 @@
min-width (max min-width 0.01)
min-height (max min-height 0.01)]
(-> [base-p]
(conj (cond-> base-p
(or row? h-start?)
(gpt/add (hv min-width))
(cond-> [base-p]
(or col? h-start?)
(conj (gpt/add base-p (hv min-width)))
(and col? h-center?)
(gpt/add (hv (/ min-width 2)))
(and col? h-center?)
(conj (gpt/add base-p (hv (/ min-width 2))))
(and col? h-center?)
(gpt/subtract (hv min-width))))
(and col? h-center?)
(conj (gpt/subtract base-p (hv min-width)))
(conj (cond-> base-p
(or col? v-start?)
(gpt/add (vv min-height))
(or row? v-start?)
(conj (gpt/add base-p (vv min-height)))
(and row? v-center?)
(gpt/add (vv (/ min-height 2)))
(and row? v-center?)
(conj (gpt/add base-p (vv (/ min-height 2))))
(and row? v-end?)
(gpt/subtract (vv min-height)))))))
(and row? v-end?)
(conj (gpt/subtract base-p (vv min-height))))))
(defn layout-content-points
[bounds parent children]
(let [parent-id (:id parent)
parent-bounds @(get bounds parent-id)
get-child-bounds
(fn [child]
(let [child-id (:id child)
child-bounds @(get bounds child-id)
[margin-top margin-right margin-bottom margin-left] (ctl/child-margins child)
child-bounds
(if (or (ctl/fill-width? child) (ctl/fill-height? child))
(child-layout-bound-points parent child parent-bounds child-bounds)
child-bounds)
child-bounds
(when (d/not-empty? child-bounds)
(-> (gpo/parent-coords-bounds child-bounds parent-bounds)
(gpo/pad-points (- margin-top) (- margin-right) (- margin-bottom) (- margin-left))))]
child-bounds))]
(->> children (map get-child-bounds))))
(defn layout-content-bounds
[bounds {:keys [layout-padding] :as parent} children]
@@ -87,27 +114,34 @@
(let [parent-id (:id parent)
parent-bounds @(get bounds parent-id)
row? (ctl/row? parent)
col? (ctl/col? parent)
space-around? (ctl/space-around? parent)
content-around? (ctl/content-around? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
row-pad (if (or (and col? space-around?)
(and row? content-around?))
layout-gap-row
0)
col-pad (if (or(and row? space-around?)
(and col? content-around?))
layout-gap-col
0)
{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding
pad-top (or pad-top 0)
pad-right (or pad-right 0)
pad-bottom (or pad-bottom 0)
pad-left (or pad-left 0)
pad-top (+ (or pad-top 0) row-pad)
pad-right (+ (or pad-right 0) col-pad)
pad-bottom (+ (or pad-bottom 0) row-pad)
pad-left (+ (or pad-left 0) col-pad)
child-bounds
(fn [child]
(let [child-id (:id child)
child-bounds @(get bounds child-id)
child-bounds
(if (or (ctl/fill-height? child) (ctl/fill-height? child))
(child-layout-bound-points parent child parent-bounds child-bounds)
child-bounds)
[margin-top margin-right margin-bottom margin-left] (ctl/child-margins child)]
(-> (gpo/parent-coords-bounds child-bounds parent-bounds)
(gpo/pad-points (- margin-top) (- margin-right) (- margin-bottom) (- margin-left)))))]
(as-> children $
(map child-bounds $)
(gpo/merge-parent-coords-bounds $ parent-bounds)
(gpo/pad-points $ (- pad-top) (- pad-right) (- pad-bottom) (- pad-left)))))
layout-points
(layout-content-points bounds parent children)]
(if (d/not-empty? layout-points)
(-> layout-points
(gpo/merge-parent-coords-bounds parent-bounds)
(gpo/pad-points (- pad-top) (- pad-right) (- pad-bottom) (- pad-left)))
;; Cannot create some bounds from the children so we return the parent's
parent-bounds)))

View File

@@ -26,6 +26,7 @@
(let [col? (ctl/col? shape)
row? (ctl/row? shape)
space-around? (ctl/space-around? shape)
wrap? (and (ctl/wrap? shape)
(or col? (not (ctl/auto-width? shape)))
@@ -77,8 +78,18 @@
next-max-width (+ child-margin-width (if fill-width? child-max-width child-width))
next-max-height (+ child-margin-height (if fill-height? child-max-height child-height))
next-line-min-width (+ line-min-width next-min-width (* layout-gap-col num-children))
next-line-min-height (+ line-min-height next-min-height (* layout-gap-row num-children))]
total-gap-col (if space-around?
(* layout-gap-col (+ num-children 2))
(* layout-gap-col num-children))
total-gap-row (if space-around?
(* layout-gap-row (+ num-children 2))
(* layout-gap-row num-children))
next-line-min-width (+ line-min-width next-min-width total-gap-col)
next-line-min-height (+ line-min-height next-min-height total-gap-row)
]
(if (and (some? line-data)
(or (not wrap?)
@@ -141,6 +152,9 @@
(let [row? (ctl/row? parent)
col? (ctl/col? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
space-around? (ctl/space-around? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
@@ -168,48 +182,61 @@
(let [[total-min-width total-min-height total-max-width total-max-height]
(->> layout-lines (reduce add-ranges [0 0 0 0]))
get-layout-width (fn [{:keys [num-children]}] (- layout-width (* layout-gap-col (dec num-children))))
get-layout-height (fn [{:keys [num-children]}] (- layout-height (* layout-gap-row (dec num-children))))
get-layout-width (fn [{:keys [num-children]}]
(let [num-gap (if space-around? (inc num-children) (dec num-children))]
(- layout-width (* layout-gap-col num-gap))))
get-layout-height (fn [{:keys [num-children]}]
(let [num-gap (if space-around? (inc num-children) (dec num-children))]
(- layout-height (* layout-gap-row num-gap))))
num-lines (count layout-lines)
;; When align-items is stretch we need to adjust the main axis size to grow for the full content
stretch-width-fix
(if (and col? (ctl/content-stretch? parent))
(if (and col? (ctl/content-stretch? parent) (not auto-width?))
(/ (- layout-width (* layout-gap-col (dec num-lines)) total-max-width) num-lines)
0)
stretch-height-fix
(if (and row? (ctl/content-stretch? parent))
(if (and row? (ctl/content-stretch? parent) (not auto-height?))
(/ (- layout-height (* layout-gap-row (dec num-lines)) total-max-height) num-lines)
0)
rest-layout-height (- layout-height (* (dec num-lines) layout-gap-row))
rest-layout-width (- layout-width (* (dec num-lines) layout-gap-col))
;; Distributes the space between the layout lines based on its max/min constraints
layout-lines
(cond->> layout-lines
row?
(map #(assoc % :line-width (max (:line-min-width %) (min (get-layout-width %) (:line-max-width %)))))
(map #(assoc % :line-width
(if (ctl/auto-width? parent)
(:line-min-width %)
(max (:line-min-width %) (min (get-layout-width %) (:line-max-width %))))))
col?
(map #(assoc % :line-height (max (:line-min-height %) (min (get-layout-height %) (:line-max-height %)))))
(map #(assoc % :line-height
(if (ctl/auto-height? parent)
(:line-min-height %)
(max (:line-min-height %) (min (get-layout-height %) (:line-max-height %))))))
(and row? (>= total-min-height layout-height))
(and row? (or (>= total-min-height rest-layout-height) (ctl/auto-height? parent)))
(map #(assoc % :line-height (:line-min-height %)))
(and row? (<= total-max-height layout-height))
(and row? (<= total-max-height rest-layout-height) (not (ctl/auto-height? parent)))
(map #(assoc % :line-height (+ (:line-max-height %) stretch-height-fix)))
(and row? (< total-min-height layout-height total-max-height))
(distribute-space :line-height :line-min-height :line-max-height total-min-height (- layout-height (* (dec num-lines) layout-gap-row)))
(and row? (< total-min-height rest-layout-height total-max-height) (not (ctl/auto-height? parent)))
(distribute-space :line-height :line-min-height :line-max-height total-min-height rest-layout-height)
(and col? (>= total-min-width layout-width))
(and col? (or (>= total-min-width rest-layout-width) (ctl/auto-width? parent)))
(map #(assoc % :line-width (:line-min-width %)))
(and col? (<= total-max-width layout-width))
(and col? (<= total-max-width rest-layout-width) (not (ctl/auto-width? parent)))
(map #(assoc % :line-width (+ (:line-max-width %) stretch-width-fix)))
(and col? (< total-min-width layout-width total-max-width))
(distribute-space :line-width :line-min-width :line-max-width total-min-width (- layout-width (* (dec num-lines) layout-gap-col))))
(and col? (< total-min-width rest-layout-width total-max-width) (not (ctl/auto-width? parent)))
(distribute-space :line-width :line-min-width :line-max-width total-min-width rest-layout-width))
[total-width total-height] (->> layout-lines (reduce add-lines [0 0]))
@@ -226,17 +253,39 @@
row? (ctl/row? shape)
col? (ctl/col? shape)
auto-height? (ctl/auto-height? shape)
auto-width? (ctl/auto-width? shape)
space-between? (ctl/space-between? shape)
space-around? (ctl/space-around? shape)
[layout-gap-row layout-gap-col] (ctl/gaps shape)
margin-x
(cond (and row? space-around? (not auto-width?))
(max layout-gap-col (/ (- width line-width) (inc num-children)))
(and row? space-around? auto-width?)
layout-gap-col
:else
0)
margin-y
(cond (and col? space-around? (not auto-height?))
(max layout-gap-row (/ (- height line-height) (inc num-children)))
(and col? space-around? auto-height?)
layout-gap-row
:else
0)
layout-gap-col
(cond (and row? space-around?)
0
(and row? space-between?)
(/ (- width line-width) (dec num-children))
(and row? space-between? (not auto-width?))
(max layout-gap-col (/ (- width line-width) (dec num-children)))
:else
layout-gap-col)
@@ -245,21 +294,11 @@
(cond (and col? space-around?)
0
(and col? space-between?)
(/ (- height line-height) (dec num-children))
(and col? space-between? (not auto-height?))
(max layout-gap-row (/ (- height line-height) (dec num-children)))
:else
layout-gap-row)
margin-x
(if (and row? space-around?)
(/ (- width line-width) (inc num-children))
0)
margin-y
(if (and col? space-around?)
(/ (- height line-height) (inc num-children))
0)]
layout-gap-row)]
(assoc line-data
:layout-bounds layout-bounds
:layout-gap-row layout-gap-row
@@ -308,4 +347,3 @@
{:layout-lines layout-lines
:layout-bounds layout-bounds
:reverse? reverse?}))

View File

@@ -68,7 +68,7 @@
child-height (gpo/height-points child-bounds)
[_ transform transform-inverse]
(when (or (ctl/fill-width? child) (ctl/fill-width? child))
(when (or (ctl/fill-width? child) (ctl/fill-height? child))
(gtr/calculate-geometry @parent-bounds))
fill-width (when (ctl/fill-width? child) (calc-fill-width-data parent transform transform-inverse child child-origin child-width layout-line))

View File

@@ -43,7 +43,7 @@
(gpt/add (vv free-height-gap))
around?
(gpt/add (vv (/ free-height (inc num-lines)))))
(gpt/add (vv (max lines-gap-row (/ free-height (inc num-lines))))))
col?
(cond-> center?
@@ -53,7 +53,7 @@
(gpt/add (hv free-width-gap))
around?
(gpt/add (hv (/ free-width (inc num-lines))))))))
(gpt/add (hv (max lines-gap-col (/ free-width (inc num-lines)))))))))
(defn get-next-line
[parent layout-bounds {:keys [line-width line-height]} base-p total-width total-height num-lines]
@@ -63,6 +63,9 @@
row? (ctl/row? parent)
col? (ctl/col? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
hv #(gpo/start-hv layout-bounds %)
@@ -75,8 +78,11 @@
free-width (- layout-width total-width)
free-height (- layout-height total-height)
line-gap-row
line-gap-col
(cond
auto-width?
layout-gap-col
stretch?
(/ free-width num-lines)
@@ -89,8 +95,11 @@
:else
layout-gap-col)
line-gap-col
line-gap-row
(cond
auto-height?
layout-gap-row
stretch?
(/ free-height num-lines)
@@ -105,10 +114,10 @@
(cond-> base-p
row?
(gpt/add (vv (+ line-height (max layout-gap-row line-gap-col))))
(gpt/add (vv (+ line-height (max layout-gap-row line-gap-row))))
col?
(gpt/add (hv (+ line-width (max layout-gap-col line-gap-row)))))))
(gpt/add (hv (+ line-width (max layout-gap-col line-gap-col)))))))
(defn get-start-line
"Cross axis line. It's position is fixed along the different lines"
@@ -126,18 +135,20 @@
v-center? (ctl/v-center? parent)
v-end? (ctl/v-end? parent)
content-stretch? (ctl/content-stretch? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
hv (partial gpo/start-hv layout-bounds)
vv (partial gpo/start-vv layout-bounds)
children-gap-width (* layout-gap-col (dec num-children))
children-gap-height (* layout-gap-row (dec num-children))
line-height
(if (and row? content-stretch?)
(if (and row? content-stretch? (not auto-height?))
(+ line-height (/ (- layout-height total-height) num-lines))
line-height)
line-width
(if (and col? content-stretch?)
(if (and col? content-stretch? (not auto-width?))
(+ line-width (/ (- layout-width total-width) num-lines))
line-width)
@@ -263,7 +274,7 @@
col?
(-> (gpt/add (vv (+ margin-top margin-bottom)))
(gpt/add (vv (+ child-height layout-gap-row))))
(some? margin-x)
(gpt/add (hv margin-x))

View File

@@ -363,10 +363,10 @@
c2 (+ (* a2 (:x c)) (* b2 (:y c)))
;; Cramer's rule
det (- (* a1 b2) (* a2 b1))]
det (- (* a1 b2) (* a2 b1))
det (if (mth/almost-zero? det) 0.001 det)
;; If almost zero the lines are parallel
(when (not (mth/almost-zero? det))
(let [x (/ (- (* b2 c1) (* b1 c2)) det)
y (/ (- (* c2 a1) (* c1 a2)) det)]
(gpt/point x y)))))
x (/ (- (* b2 c1) (* b1 c2)) det)
y (/ (- (* c2 a1) (* c1 a2)) det)]
(gpt/point x y)))

View File

@@ -131,6 +131,7 @@
i2 (gsi/line-line-intersect minv-start minv-end maxh-start maxh-end)
i3 (gsi/line-line-intersect maxv-start maxv-end maxh-start maxh-end)
i4 (gsi/line-line-intersect maxv-start maxv-end minh-start minh-end)]
[i1 i2 i3 i4])))
(defn merge-parent-coords-bounds
@@ -143,3 +144,8 @@
height (height-points points)
center (gco/center-points points)]
(gre/center->selrect center width height)))
(defn move
[bounds vector]
(->> bounds
(map #(gpt/add % vector))))

View File

@@ -17,6 +17,7 @@
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.path :as gpa]
[app.common.geom.shapes.rect :as gpr]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]))
@@ -159,67 +160,79 @@
"Calculate the transform matrix to convert from the selrect to the points bounds
TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)"
[{:keys [x1 y1 x2 y2]} [d1 d2 _ d4]]
#?(:clj
;; NOTE: the source matrix may not be invertible we can't
;; calculate the transform, so on exception we return `nil`
(ex/ignoring
(let [target-points-matrix
(->> (list (:x d1) (:x d2) (:x d4)
(:y d1) (:y d2) (:y d4)
1 1 1 )
(into-array Double/TYPE)
(Matrix/from1DArray 3 3))
;; If the coordinates are very close to zero (but not zero) the rounding can mess with the
;; transforms. So we round to zero the values
(let [x1 (mth/round-to-zero x1)
y1 (mth/round-to-zero y1)
x2 (mth/round-to-zero x2)
y2 (mth/round-to-zero y2)
d1x (mth/round-to-zero (:x d1))
d1y (mth/round-to-zero (:y d1))
d2x (mth/round-to-zero (:x d2))
d2y (mth/round-to-zero (:y d2))
d4x (mth/round-to-zero (:x d4))
d4y (mth/round-to-zero (:y d4))]
#?(:clj
;; NOTE: the source matrix may not be invertible we can't
;; calculate the transform, so on exception we return `nil`
(ex/ignoring
(let [target-points-matrix
(->> (list d1x d2x d4x
d1y d2y d4y
1 1 1)
(into-array Double/TYPE)
(Matrix/from1DArray 3 3))
source-points-matrix
(->> (list x1 x2 x1
y1 y1 y2
1 1 1)
(into-array Double/TYPE)
(Matrix/from1DArray 3 3))
source-points-matrix
(->> (list x1 x2 x1
y1 y1 y2
1 1 1)
(into-array Double/TYPE)
(Matrix/from1DArray 3 3))
;; May throw an exception if the matrix is not invertible
source-points-matrix-inv
(.. source-points-matrix
(withInverter LinearAlgebra/GAUSS_JORDAN)
(inverse))
;; May throw an exception if the matrix is not invertible
source-points-matrix-inv
(.. source-points-matrix
(withInverter LinearAlgebra/GAUSS_JORDAN)
(inverse))
transform-jvm
(.. target-points-matrix
(multiply source-points-matrix-inv))]
transform-jvm
(.. target-points-matrix
(multiply source-points-matrix-inv))]
(gmt/matrix (.get transform-jvm 0 0)
(.get transform-jvm 1 0)
(.get transform-jvm 0 1)
(.get transform-jvm 1 1)
(.get transform-jvm 0 2)
(.get transform-jvm 1 2))))
(gmt/matrix (.get transform-jvm 0 0)
(.get transform-jvm 1 0)
(.get transform-jvm 0 1)
(.get transform-jvm 1 1)
(.get transform-jvm 0 2)
(.get transform-jvm 1 2))))
:cljs
(let [target-points-matrix
(Matrix. #js [#js [(:x d1) (:x d2) (:x d4)]
#js [(:y d1) (:y d2) (:y d4)]
#js [ 1 1 1]])
:cljs
(let [target-points-matrix
(Matrix. #js [#js [d1x d2x d4x]
#js [d1y d2y d4y]
#js [ 1 1 1]])
source-points-matrix
(Matrix. #js [#js [x1 x2 x1]
#js [y1 y1 y2]
#js [ 1 1 1]])
source-points-matrix
(Matrix. #js [#js [x1 x2 x1]
#js [y1 y1 y2]
#js [ 1 1 1]])
;; returns nil if not invertible
source-points-matrix-inv (.getInverse source-points-matrix)
;; returns nil if not invertible
source-points-matrix-inv (.getInverse source-points-matrix)
;; TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)
transform-js
(when source-points-matrix-inv
(.multiply target-points-matrix source-points-matrix-inv))]
;; TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)
transform-js
(when source-points-matrix-inv
(.multiply target-points-matrix source-points-matrix-inv))]
(when transform-js
(gmt/matrix (.getValueAt transform-js 0 0)
(.getValueAt transform-js 1 0)
(.getValueAt transform-js 0 1)
(.getValueAt transform-js 1 1)
(.getValueAt transform-js 0 2)
(.getValueAt transform-js 1 2))))))
(when transform-js
(gmt/matrix (.getValueAt transform-js 0 0)
(.getValueAt transform-js 1 0)
(.getValueAt transform-js 0 1)
(.getValueAt transform-js 1 1)
(.getValueAt transform-js 0 2)
(.getValueAt transform-js 1 2)))))))
(defn calculate-geometry
[points]

View File

@@ -174,6 +174,13 @@
(defn almost-zero? [num]
(< (abs (double num)) 1e-4))
(defn round-to-zero
"Given a number if it's close enough to zero round to the zero to avoid precision problems"
[num]
(if (almost-zero? num)
0
num))
(defonce float-equal-precision 0.001)
(defn close?

View File

@@ -9,7 +9,8 @@
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
(def valid-font-types #{"font/ttf" "font/woff", "application/font-woff", "font/otf"})
;; We have added ".ttf" as string to solve a problem with chrome input selector
(def valid-font-types #{"font/ttf", ".ttf", "font/woff", "application/font-woff", "font/otf"})
(def valid-image-types #{"image/jpeg", "image/png", "image/webp", "image/gif", "image/svg+xml"})
(def str-image-types (str/join "," valid-image-types))
(def str-font-types (str/join "," valid-font-types))

View File

@@ -387,6 +387,10 @@
is-geometry? (and (or (= group :geometry-group)
(and (= group :content-group) (= (:type shape) :path)))
(not (#{:width :height} attr))) ;; :content in paths are also considered geometric
;; TODO: the check of :width and :height probably may be removed
;; after the check added in data/workspace/modifiers/check-delta
;; function. Better check it and test toroughly when activating
;; components-v2 mode.
shape-ref (:shape-ref shape)
root-name? (and (= group :name-group)
(:component-root? shape))

View File

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

View File

@@ -7,11 +7,12 @@
(ns app.common.pages.migrations
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.text :as gsht]
[app.common.logging :as l]
[app.common.logging :as log]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
@@ -23,13 +24,15 @@
(defmulti migrate :version)
(log/set-level! :info)
(defn migrate-data
([data] (migrate-data data cp/file-version))
([data to-version]
(if (= (:version data) to-version)
data
(let [migrate-fn #(do
(l/trace :hint "migrate file" :id (:id %) :version-from %2 :version-to (inc %2))
(log/trace :hint "migrate file" :id (:id %) :version-from %2 :version-to (inc %2))
(migrate (assoc %1 :version (inc %2))))]
(reduce migrate-fn data (range (:version data 0) to-version))))))
@@ -427,5 +430,31 @@
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(defmethod migrate 20
[data]
(letfn [(update-object [objects object]
(let [frame-id (:frame-id object)
calculated-frame-id
(or (->> (cph/get-parent-ids objects (:id object))
(map (d/getf objects))
(d/seek cph/frame-shape?)
:id)
;; If we cannot find any we let the frame-id as it was before
frame-id)]
(when (not= frame-id calculated-frame-id)
(log/info :hint "Fix wrong frame-id"
:shape (:name object)
:id (:id object)
:current (dm/get-in objects [frame-id :name])
:calculated (get-in objects [calculated-frame-id :name])))
(assoc object :frame-id calculated-frame-id)))
(update-container [container]
(update container :objects #(update-vals % (partial update-object %))))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
;; TODO: pending to do a migration for delete already not used fill
;; and stroke props. This should be done for >1.14.x version.

View File

@@ -11,13 +11,13 @@
[clojure.spec.alpha :as s]))
;; :layout ;; :flex, :grid in the future
;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
;; :layout-flex-dir ;; :row, :row-reverse, :column, :column-reverse
;; :layout-gap-type ;; :simple, :multiple
;; :layout-gap ;; {:row-gap number , :column-gap number}
;; :layout-align-items ;; :start :end :center :stretch
;; :layout-justify-content ;; :start :center :end :space-between :space-around
;; :layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
;; :layout-wrap-type ;; :wrap, :no-wrap
;; :layout-wrap-type ;; :wrap, :nowrap
;; :layout-padding-type ;; :simple, :multiple
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
@@ -32,13 +32,13 @@
;; :layout-item-min-w ;; num
(s/def ::layout #{:flex :grid})
(s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column})
(s/def ::layout-flex-dir #{:row :reverse-row :row-reverse :column :reverse-column :column-reverse}) ;;TODO remove reverse-column and reverse-row after script
(s/def ::layout-gap-type #{:simple :multiple})
(s/def ::layout-gap ::us/safe-number)
(s/def ::layout-align-items #{:start :end :center :stretch})
(s/def ::layout-align-content #{:start :end :center :space-between :space-around :stretch})
(s/def ::layout-justify-content #{:start :center :end :space-between :space-around})
(s/def ::layout-wrap-type #{:wrap :no-wrap})
(s/def ::layout-wrap-type #{:wrap :nowrap :no-wrap}) ;;TODO remove no-wrap after script
(s/def ::layout-padding-type #{:simple :multiple})
(s/def ::p1 ::us/safe-number)
@@ -148,11 +148,11 @@
(defn col?
[{:keys [layout-flex-dir]}]
(or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir)))
(or (= :column layout-flex-dir) (= :column-reverse layout-flex-dir)))
(defn row?
[{:keys [layout-flex-dir]}]
(or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir)))
(or (= :row layout-flex-dir) (= :row-reverse layout-flex-dir)))
(defn gaps
[{:keys [layout-gap]}]
@@ -278,8 +278,8 @@
(defn reverse?
[{:keys [layout-flex-dir]}]
(or (= :reverse-row layout-flex-dir)
(= :reverse-column layout-flex-dir)))
(or (= :row-reverse layout-flex-dir)
(= :column-reverse layout-flex-dir)))
(defn space-between?
[{:keys [layout-justify-content]}]

View File

@@ -155,21 +155,19 @@
[base index-base-a index-base-b]))
(defn is-shape-over-shape?
[objects base-shape-id over-shape-id {:keys [top-frames?]}]
[objects base-shape-id over-shape-id]
(let [[base index-a index-b] (get-base objects base-shape-id over-shape-id)]
(cond
(= base base-shape-id)
(and (not top-frames?)
(let [object (get objects base-shape-id)]
(or (cph/frame-shape? object)
(cph/root-frame? object))))
(let [object (get objects base-shape-id)]
(or (cph/frame-shape? object)
(cph/root-frame? object)))
(= base over-shape-id)
(or top-frames?
(let [object (get objects over-shape-id)]
(or (not (cph/frame-shape? object))
(not (cph/root-frame? object)))))
(let [object (get objects over-shape-id)]
(or (not (cph/frame-shape? object))
(not (cph/root-frame? object))))
:else
(< index-a index-b))))
@@ -183,20 +181,20 @@
(let [type-a (dm/get-in objects [id-a :type])
type-b (dm/get-in objects [id-b :type])]
(cond
(and (= :frame type-a) (not= :frame type-b))
(if bottom-frames? 1 -1)
(and (not= :frame type-a) (= :frame type-b))
(if bottom-frames? -1 1)
(and (= :frame type-a) (not= :frame type-b))
(if bottom-frames? 1 -1)
(= id-a id-b)
0
(is-shape-over-shape? objects id-a id-b options)
1
(is-shape-over-shape? objects id-b id-a)
-1
:else
-1)))]
1)))]
(sort comp ids))))
(defn frame-id-by-position
@@ -268,7 +266,7 @@
(if all-frames?
identity
(remove :hide-in-viewer)))
(sort-z-index objects (get-frames-ids objects) {:top-frames? true}))))
(sort-z-index objects (get-frames-ids objects)))))
(defn start-page-index
[objects]

View File

@@ -70,3 +70,14 @@
remap-typography
content)))))
(defn remove-external-typographies
"Change the shape so that any use of an external typography now is removed"
[shape file-id]
(let [remove-ref-file #(dissoc % :typography-ref-file :typography-ref-id)]
(update shape :content
(fn [content]
(txt/transform-nodes #(not= (:typography-ref-file %) file-id)
remove-ref-file
content)))))

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 261 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View File

@@ -129,6 +129,7 @@ $width-settings-bar: 256px;
overflow: hidden;
flex-direction: column;
justify-content: flex-start;
position: relative;
}
.inspect-svg-container {

View File

@@ -23,9 +23,9 @@
flex-direction: column;
align-items: center;
justify-content: flex-start;
background-color: #feecfd;
background-image: url("/images/login-pink.svg");
background-position: center;
background-color: #151035;
background-image: url("/images/login-penpot.svg");
background-position: center 30vh;
background-size: 96%;
background-repeat: no-repeat;
@@ -34,12 +34,12 @@
width: 280px;
font-size: $fs18;
margin-top: 2vh;
color: #2c233e;
color: white;
}
.logo {
svg {
fill: #2c233e;
fill: white;
max-width: 11vw;
height: 80px;
}

View File

@@ -132,6 +132,7 @@
.options {
display: flex;
justify-content: flex-end;
min-width: 180px;
.icon {
width: $size-5;
@@ -140,6 +141,12 @@
margin-left: 10px;
justify-content: center;
align-items: center;
&.failure {
margin-right: 10px;
svg {
fill: $color-warning;
}
}
svg {
width: 16px;
height: 16px;
@@ -171,7 +178,6 @@
.dashboard-fonts-hero {
font-size: $fs14;
padding: $size-6;
background-color: $color-white;
margin-top: $size-6;
@@ -179,17 +185,29 @@
justify-content: space-between;
.banner {
background-color: unset;
display: flex;
background-color: $color-info-lighter;
display: grid;
grid-template-columns: 40px 1fr;
&:not(:last-child) {
margin-bottom: 10px;
}
.icon {
display: flex;
align-items: center;
padding-left: 0px;
padding-right: 10px;
align-items: start;
justify-content: center;
padding-top: 10px;
background-color: $color-info;
svg {
fill: $color-info;
fill: $color-white;
}
}
.content {
margin: 10px;
}
&.warning {
background-color: $color-warning-lighter;
.icon {
background-color: $color-warning;
}
}
}

View File

@@ -314,11 +314,15 @@
}
}
.attributes-shadow-block {
.attributes-shadow-block,
.attributes-stroke-block,
.attributes-fill-block {
border-top: 1px solid $color-gray-60;
}
.attributes-shadow-blocks :first-child {
.attributes-shadow-blocks :first-child,
.attributes-stroke-blocks :first-child,
.attributes-fill-blocks :first-child {
border-top: none;
}
}

View File

@@ -693,9 +693,7 @@
.section-list-item {
padding: $size-4 0;
border-bottom: 1px solid $color-gray-20;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
.item-name {
color: $color-gray-60;
@@ -720,6 +718,8 @@
color: $color-black;
padding: $size-2;
margin-bottom: 0;
position: absolute;
top: 1rem;
&:hover {
color: $color-primary;
@@ -801,6 +801,7 @@
background-color: $color-white;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.2);
display: flex;
max-height: 710px;
min-height: 420px;
flex-direction: row;
font-family: "sourcesanspro", sans-serif;
@@ -1195,9 +1196,10 @@
}
&.right {
left: 731px;
top: 100px;
left: auto;
color: $color-primary;
top: 100px;
right: -40px;
}
&.square {

View File

@@ -1707,13 +1707,13 @@
cursor: pointer;
border-right: 1px solid $color-gray-60;
padding: 2px;
&.reverse-row {
&.row-reverse {
svg {
transform: rotate(180deg);
}
}
&.reverse-column {
&.column-reverse {
svg {
transform: rotate(-90deg);
}

View File

@@ -85,7 +85,7 @@
}
& .viewer-go-next.right-bar {
right: 264px;
right: 0;
}
& .viewer-go-prev {
@@ -97,7 +97,7 @@
}
& .viewer-go-prev.left-bar {
left: 256px;
left: 0;
}
& .viewer-bottom {
@@ -111,7 +111,7 @@
z-index: 2;
&.left-bar {
width: calc(100% - 512px);
width: 100%;
}
.reset {

View File

@@ -15,7 +15,7 @@
display: grid;
grid-template-areas: "left center right";
grid-template-columns: auto 1fr auto;
grid-template-columns: 1fr auto 1fr;
grid-template-rows: 100%;
padding: 0;
@@ -32,6 +32,7 @@
.right-area {
grid-area: right;
display: flex;
justify-content: flex-end;
height: 100%;
}

View File

@@ -273,7 +273,6 @@ $height-palette-max: 80px;
.frame-thumbnail-wrapper {
.fills,
.strokes,
.frame-clip-def {
opacity: 0;
}

View File

@@ -84,16 +84,44 @@
map with temporal ID's associated to each font entry."
[blobs team-id]
(letfn [(prepare [{:keys [font type name data] :as params}]
(let [family (or (.getEnglishName ^js font "preferredFamily")
(.getEnglishName ^js font "fontFamily"))
variant (or (.getEnglishName ^js font "preferredSubfamily")
(.getEnglishName ^js font "fontSubfamily"))]
(let [family (or (.getEnglishName ^js font "preferredFamily")
(.getEnglishName ^js font "fontFamily"))
variant (or (.getEnglishName ^js font "preferredSubfamily")
(.getEnglishName ^js font "fontSubfamily"))
;; Vertical metrics determine the baseline in a text and the space between lines of text.
;; For historical reasons, there are three pairs of ascender/descender values, known as hhea, OS/2 and uSWin metrics.
;; Depending on the font, operating system and application a different set will be used to render text on the screen.
;; On Mac, Safari and Chrome use the hhea values to render text. Firefox will respect the useTypoMetrics setting and will use the OS/2 if it is set.
;; If the useTypoMetrics is not set, Firefox will also use metrics from the hhea table.
;; On Windows, all browsers use the usWin metrics, but respect the useTypoMetrics setting and if set will use the OS/2 values.
hhea-ascender (abs (-> font .-tables .-hhea .-ascender))
hhea-descender (abs (-> font .-tables .-hhea .-descender))
win-ascent (abs (-> font .-tables .-os2 .-usWinAscent))
win-descent (abs (-> font .-tables .-os2 .-usWinDescent))
os2-ascent (abs (-> font .-tables .-os2 .-sTypoAscender))
os2-descent (abs (-> font .-tables .-os2 .-sTypoDescender))
;; useTypoMetrics can be read from the 7th bit
f-selection (-> (-> font .-tables .-os2 .-fsSelection)
(bit-test 7))
height-warning? (or (not= hhea-ascender win-ascent)
(not= hhea-descender win-descent)
(and f-selection (or
(not= hhea-ascender os2-ascent)
(not= hhea-descender os2-descent))))]
{:content {:data (js/Uint8Array. data)
:name name
:type type}
:font-family (or family "")
:font-weight (cm/parse-font-weight variant)
:font-style (cm/parse-font-style variant)}))
:font-style (cm/parse-font-style variant)
:height-warning? height-warning?}))
(join [res {:keys [content] :as font}]
(let [key-fn (juxt :font-family :font-weight :font-style)

View File

@@ -89,6 +89,10 @@
[key]
(-> key meta alt))
(defn alt-shift
[key]
(-> key alt shift))
(defn supr
[]
(if (cf/check-platform? :macos)
@@ -133,17 +137,23 @@
[key cb]
(fn [event]
(log/debug :msg (str "Shortcut" key))
(.preventDefault event)
(when (aget event "preventDefault")
(.preventDefault event))
(cb event)))
(defn- bind!
[shortcuts]
(let [msbind (fn [command callback type]
(if type
(mousetrap/bind command callback type)
(mousetrap/bind command callback)))]
(->> shortcuts
(remove #(:disabled (second %)))
(run! (fn [[key {:keys [command fn type]}]]
(if (vector? command)
(run! #(mousetrap/bind % (wrap-cb key fn) type) command)
(mousetrap/bind command (wrap-cb key fn) type))))))
(let [callback (wrap-cb key fn)]
(if (vector? command)
(run! #(msbind % callback type) command)
(msbind command callback type))))))))
(defn- reset!
([]

View File

@@ -27,6 +27,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.common.types.typography :as ctt]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.comments :as dcm]
@@ -609,6 +610,7 @@
objects (wsh/lookup-page-objects state page-id)
selected-ids (wsh/lookup-selected state)
selected-shapes (map (d/getf objects) selected-ids)
undo-id (js/Symbol)
move-shape
(fn [changes shape]
@@ -631,7 +633,10 @@
(pcb/with-objects objects))
selected-shapes)]
(rx/of (dch/commit-changes changes))))))
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(ptk/data-event :layout/update selected-ids)
(dwu/commit-undo-transaction undo-id))))))
;; --- Change Shape Order (D&D Ordering)
@@ -835,13 +840,22 @@
(ptk/reify ::start-editing-selected
ptk/WatchEvent
(watch [_ state _]
(let [selected (wsh/lookup-selected state)]
(if-not (= 1 (count selected))
(rx/empty)
(let [selected (wsh/lookup-selected state)
objects (wsh/lookup-page-objects state)]
(let [objects (wsh/lookup-page-objects state)
{:keys [id type shapes]} (get objects (first selected))]
(if (> (count selected) 1)
(let [shapes-to-select
(->> selected
(reduce
(fn [result shape-id]
(let [children (dm/get-in objects [shape-id :shapes])]
(if (empty? children)
(conj result shape-id)
(into result children))))
(d/ordered-set)))]
(rx/of (dws/select-shapes shapes-to-select)))
(let [{:keys [id type shapes]} (get objects (first selected))]
(case type
:text
(rx/of (dwe/start-edition-mode id))
@@ -895,9 +909,13 @@
(align-objects-list objects selected axis))
moved-objects (->> moved (group-by :id))
ids (keys moved-objects)
update-fn (fn [shape] (first (get moved-objects (:id shape))))]
update-fn (fn [shape] (first (get moved-objects (:id shape))))
undo-id (js/Symbol)]
(when (can-align? selected objects)
(rx/of (dch/update-shapes ids update-fn {:reg-objects? true})))))))
(rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes ids update-fn {:reg-objects? true})
(ptk/data-event :layout/update ids)
(dwu/commit-undo-transaction undo-id)))))))
(defn align-object-to-parent
[objects object-id axis]
@@ -1307,16 +1325,22 @@
:file-id (:current-file-id state)
:selected selected
:objects {}
:images #{}}]
:images #{}}
selected_text (.. js/window getSelection toString)]
(->> (rx/from (seq (vals pdata)))
(rx/merge-map (partial prepare-object objects selected+children))
(rx/reduce collect-data initial)
(rx/map (partial sort-selected state))
(rx/map t/encode-str)
(rx/map wapi/write-to-clipboard)
(rx/catch on-copy-error)
(rx/ignore)))))))
(if (not-empty selected_text)
(try
(wapi/write-to-clipboard selected_text)
(catch :default e
(on-copy-error e)))
(->> (rx/from (seq (vals pdata)))
(rx/merge-map (partial prepare-object objects selected+children))
(rx/reduce collect-data initial)
(rx/map (partial sort-selected state))
(rx/map t/encode-str)
(rx/map wapi/write-to-clipboard)
(rx/catch on-copy-error)
(rx/ignore))))))))
(declare paste-shape)
(declare paste-text)
@@ -1527,7 +1551,8 @@
;; Proceed with the standard shape paste process.
(do-paste [it state mouse-pos media]
(let [page (wsh/lookup-page state)
(let [file-id (:current-file-id state)
page (wsh/lookup-page state)
media-idx (d/index-by :prev-id media)
;; Calculate position for the pasted elements
@@ -1542,7 +1567,10 @@
;; if foreign instance, detach the shape
(cond-> (foreign-instance? shape paste-objects state)
(dissoc :component-id :component-file :component-root?
:remote-synced? :shape-ref :touched))))
:remote-synced? :shape-ref :touched))
;; if is a text, remove references to external typographies
(cond-> (= (:type shape) :text)
(ctt/remove-external-typographies file-id))))
paste-objects (->> paste-objects (d/mapm process-shape))
@@ -1564,7 +1592,7 @@
(into (d/ordered-set)))
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(dws/select-shapes selected)
(ptk/data-event :layout/update [frame-id])

View File

@@ -9,6 +9,7 @@
[app.common.logging :as log]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.undo :as dwu]
[app.util.router :as rt]
[beicon.core :as rx]
[potok.core :as ptk]))
@@ -26,6 +27,25 @@
(defn interrupt? [e] (= e :interrupt))
(defn- assure-valid-current-page
[]
(ptk/reify ::assure-valid-current-page
ptk/WatchEvent
(watch [_ state _]
(let [current_page (:current-page-id state)
pages (get-in state [:workspace-data :pages])
exists? (some #(= current_page %) pages)
project-id (:current-project-id state)
file-id (:current-file-id state)
pparams {:file-id file-id :project-id project-id}
qparams {:page-id (first pages)}]
(if exists?
(rx/empty)
(rx/of (rt/nav :workspace pparams qparams)))))))
;; These functions should've been in `src/app/main/data/workspace/undo.cljs` but doing that causes
;; a circular dependency with `src/app/main/data/workspace/changes.cljs`
(def undo
@@ -45,7 +65,8 @@
(dch/commit-changes {:redo-changes changes
:undo-changes []
:save-undo? false
:origin it}))))))))))
:origin it})
(assure-valid-current-page))))))))))
(def redo
(ptk/reify ::redo

View File

@@ -79,15 +79,22 @@
(gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root))
(- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root))))
;; There are cases in that the coordinates change slightly (e.g. when
;; rounding to pixel, or when recalculating text positions in different
;; zoom levels). To take this into account, we ignore movements smaller
;; than 1 pixel.
distance (if (and shape-delta transformed-shape-delta)
(gpt/distance-vector shape-delta transformed-shape-delta)
(gpt/point 0 0))
ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))]
selrect (:selrect shape)
transformed-selrect (:selrect transformed-shape)
;; There are cases in that the coordinates change slightly (e.g. when rounding
;; to pixel, or when recalculating text positions in different zoom levels).
;; To take this into account, we ignore movements smaller than 1 pixel.
;;
;; When the change is a resize, also has a transformation that may have the
;; shape position unchanged. But in this case we do not want to ignore it.
ignore-geometry? (and (and (< (:x distance) 1) (< (:y distance) 1))
(mth/close? (:width selrect) (:width transformed-selrect))
(mth/close? (:height selrect) (:height transformed-selrect)))]
[root transformed-root ignore-geometry?]))
@@ -157,6 +164,15 @@
(us/verify (s/coll-of uuid?) ids)
(into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids))
(defn modifier-remove-from-parent
[modif-tree objects shapes]
(->> shapes
(reduce
(fn [modif-tree child-id]
(let [parent-id (get-in objects [child-id :parent-id])]
(update-in modif-tree [parent-id :modifiers] ctm/remove-children [child-id])))
modif-tree)))
(defn build-change-frame-modifiers
[modif-tree objects selected target-frame drop-index]
@@ -186,7 +202,7 @@
(filterv #(contains? child-set %)))]
(cond-> modif-tree
(not= original-frame target-frame)
(-> (update-in [original-frame :modifiers] ctm/remove-children shapes)
(-> (modifier-remove-from-parent objects shapes)
(update-in [target-frame :modifiers] ctm/add-children shapes drop-index)
(set-parent-ids shapes target-frame))

View File

@@ -281,8 +281,6 @@
;; --- Duplicate Shapes
(declare prepare-duplicate-change)
(declare prepare-duplicate-frame-change)
(declare prepare-duplicate-shape-change)
(declare prepare-duplicate-flows)
(declare prepare-duplicate-guides)
@@ -304,91 +302,59 @@
changes
(->> shapes
(reduce #(prepare-duplicate-change %1
all-objects
page
unames
update-unames!
ids-map
%2
delta)
(reduce #(prepare-duplicate-shape-change %1
all-objects
page
unames
update-unames!
ids-map
%2
delta)
init-changes))]
(-> changes
(prepare-duplicate-flows shapes page ids-map)
(prepare-duplicate-guides shapes page ids-map delta))))
(defn- prepare-duplicate-change
[changes objects page unames update-unames! ids-map shape delta]
(if (cph/frame-shape? shape)
(prepare-duplicate-frame-change changes objects page unames update-unames! ids-map shape delta)
(prepare-duplicate-shape-change changes objects page unames update-unames! ids-map shape delta (:frame-id shape) (:parent-id shape))))
(defn- prepare-duplicate-frame-change
[changes objects page unames update-unames! ids-map obj delta]
(let [new-id (ids-map (:id obj))
frame-name (:name obj)
new-frame (-> obj
(assoc :id new-id
:name frame-name
:shapes [])
(dissoc :use-for-thumbnail?)
(gsh/move delta)
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)))
changes (-> (pcb/add-object changes new-frame)
(pcb/amend-last-change #(assoc % :old-id (:id obj))))
changes (reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page
unames
update-unames!
ids-map
child
delta
new-id
new-id))
changes
(map (d/getf objects) (:shapes obj)))]
changes))
(defn- prepare-duplicate-shape-change
[changes objects page unames update-unames! ids-map obj delta frame-id parent-id]
(if (some? obj)
(let [new-id (ids-map (:id obj))
parent-id (or parent-id frame-id)
name (:name obj)
([changes objects page unames update-unames! ids-map obj delta]
(prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta (:frame-id obj) (:parent-id obj)))
new-obj (-> obj
(assoc :id new-id
:name name
:parent-id parent-id
:frame-id frame-id)
(dissoc :shapes
:main-instance?)
(gsh/move delta)
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)))
([changes objects page unames update-unames! ids-map obj delta frame-id parent-id]
(if (some? obj)
(let [frame? (cph/frame-shape? obj)
new-id (ids-map (:id obj))
parent-id (or parent-id frame-id)
name (:name obj)
changes (-> (pcb/add-object changes new-obj {:ignore-touched true})
(pcb/amend-last-change #(assoc % :old-id (:id obj))))]
new-obj (-> obj
(assoc :id new-id
:name name
:parent-id parent-id
:frame-id frame-id)
(dissoc :shapes
:main-instance?
:use-for-thumbnail?)
(gsh/move delta)
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)))
(reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page
unames
update-unames!
ids-map
child
delta
frame-id
new-id))
changes
(map (d/getf objects) (:shapes obj))))
changes))
changes (-> (pcb/add-object changes new-obj {:ignore-touched true})
(pcb/amend-last-change #(assoc % :old-id (:id obj))))]
(reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page
unames
update-unames!
ids-map
child
delta
(if frame? new-id frame-id)
new-id))
changes
(map (d/getf objects) (:shapes obj))))
changes)))
(defn- prepare-duplicate-flows
[changes shapes page ids-map]

View File

@@ -48,7 +48,7 @@
:layout-align-items :start
:layout-justify-content :start
:layout-align-content :stretch
:layout-wrap-type :no-wrap
:layout-wrap-type :nowrap
:layout-padding-type :simple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}})
@@ -56,14 +56,17 @@
{:layout :grid})
(defn get-layout-initializer
[type]
[type from-frame?]
(let [initial-layout-data (if (= type :flex) initial-flex-layout initial-grid-layout)]
(fn [shape]
(-> shape
(merge shape initial-layout-data)))))
(merge initial-layout-data)
;; If the original shape is not a frame we set clip content and show-viewer to false
(cond-> (not from-frame?)
(assoc :show-content true :hide-in-viewer true))))))
(defn create-layout-from-id
[ids type]
[ids type from-frame?]
(ptk/reify ::create-layout-from-id
ptk/WatchEvent
(watch [_ state _]
@@ -71,7 +74,7 @@
children-ids (into [] (mapcat #(get-in objects [% :shapes])) ids)
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dwc/update-shapes ids (get-layout-initializer type))
(dwc/update-shapes ids (get-layout-initializer type from-frame?))
(ptk/data-event :layout/update ids)
(dwc/update-shapes children-ids #(dissoc % :constraints-h :constraints-v))
(dwu/commit-undo-transaction undo-id))))))
@@ -136,8 +139,8 @@
direction
(cond
(mth/close? tmin t1) :row
(mth/close? tmin t2) :reverse-column
(mth/close? tmin t3) :reverse-row
(mth/close? tmin t2) :column-reverse
(mth/close? tmin t3) :row-reverse
(mth/close? tmin t4) :column)]
{:layout-flex-dir direction}))
@@ -173,7 +176,7 @@
(dws/create-artboard-from-selection new-shape-id parent-id group-index)
(cl/remove-all-fills [new-shape-id] {:color clr/black
:opacity 1})
(create-layout-from-id [new-shape-id] type)
(create-layout-from-id [new-shape-id] type false)
(dwc/update-shapes
[new-shape-id]
(fn [shape]
@@ -193,7 +196,7 @@
(dws/create-artboard-from-selection new-shape-id)
(cl/remove-all-fills [new-shape-id] {:color clr/black
:opacity 1})
(create-layout-from-id [new-shape-id] type)
(create-layout-from-id [new-shape-id] type false)
(dwc/update-shapes
[new-shape-id]
(fn [shape]
@@ -233,7 +236,7 @@
(if (and single? is-frame?)
(rx/of
(dwu/start-undo-transaction undo-id)
(create-layout-from-id [(first selected)] :flex)
(create-layout-from-id [(first selected)] :flex true)
(dwu/commit-undo-transaction undo-id))
(rx/of
(dwu/start-undo-transaction undo-id)

View File

@@ -17,6 +17,7 @@
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dws]
[app.main.data.workspace.text.shortcuts :as dwtxts]
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
@@ -107,6 +108,7 @@
:subsections [:edit]
:fn #(st/emit! :interrupt (dw/deselect-all true))}
;; MODIFY LAYERS
@@ -245,7 +247,7 @@
:command "t"
:subsections [:tools]
:fn #(emit-when-no-readonly dwtxt/start-edit-if-selected
(dwd/select-for-drawing :text))}
(dwd/select-for-drawing :text))}
:draw-path {:tooltip "P"
:command "p"
@@ -419,14 +421,14 @@
:subsections [:panels]
:fn #(do (r/set-resize-type! :bottom)
(emit-when-no-readonly (dw/remove-layout-flag :textpalette)
(toggle-layout-flag :colorpalette)))}
(toggle-layout-flag :colorpalette)))}
:toggle-textpalette {:tooltip (ds/alt "T")
:command (ds/a-mod "t")
:subsections [:panels]
:fn #(do (r/set-resize-type! :bottom)
(emit-when-no-readonly (dw/remove-layout-flag :colorpalette)
(toggle-layout-flag :textpalette)))}
(toggle-layout-flag :textpalette)))}
:hide-ui {:tooltip "\\"
:command "\\"
@@ -460,6 +462,16 @@
:subsections [:zoom-workspace]
:fn #(st/emit! dw/zoom-to-selected-shape)}
:zoom-lense-increase {:tooltip "Z"
:command "z"
:subsections [:zoom-workspace]
:fn identity}
:zoom-lense-decrease {:tooltip (ds/alt "Z")
:command "alt+z"
:subsections [:zoom-workspace]
:fn identity}
;; NAVIGATION
@@ -516,7 +528,7 @@
:fn #(emit-when-no-readonly (dwly/pressed-opacity n))}])))))
(def shortcuts
(merge base-shortcuts opacity-shortcuts))
(merge base-shortcuts opacity-shortcuts dwtxts/shortcuts))
(defn get-tooltip [shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))

View File

@@ -0,0 +1,273 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.text.shortcuts
(:require
[app.common.data :as d]
[app.main.data.shortcuts :as ds]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.fonts :as fonts]
[app.main.refs :as refs]
[app.main.store :as st]
[cuerdas.core :as str]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts format https://github.com/ccampbell/mousetrap
(defn- is-bold? [variant-id]
(some #(str/includes? variant-id %) ["bold" "black" "700"]))
(defn- is-italic? [variant-id]
(some #(str/includes? variant-id %) ["italic" "cursive"]))
(defn- generate-variant-props
[text-values variant-id]
(let [first-intersection (fn [list1 list2] (first (filter (set list1) list2)))
current-variant (:font-variant-id text-values)
bold-options (cond
(str/includes? current-variant "black")
["black" "bold" "700"]
(str/includes? current-variant "700")
["700" "black" "bold"]
:else
["bold" "black" "700"])
current-variant-no-italic (cond
(str/includes? current-variant "italic")
(subs current-variant 0 (- (count current-variant) 6))
(str/includes? current-variant "cursive")
(subs current-variant 0 (- (count current-variant) 7))
:else nil)
regular-options [current-variant-no-italic "regular" "normal" "400"]
italic-options [(when (and (not (str/includes? current-variant "bold"))
(not (str/includes? current-variant "black"))
(not (str/includes? current-variant "700")))
(str current-variant "italic"))
"italic" "cursive"]
bold-italic-options (cond
(str/includes? current-variant "black")
["blackitalic" "blackcursive" "bolditalic" "700italic" "boldcursive" "700cursive"]
(str/includes? current-variant "700")
["700italic" "700cursive" "bolditalic" "blackitalic" "boldcursive" "blackcursive"]
:else
["bolditalic" "700italic" "blackitalic" "boldcursive" "700cursive" "blackcursive"])
font-id (:font-id text-values)
fonts (deref fonts/fontsdb)
font (get fonts font-id)
variants (map :id (:variants font))
choose-regular (fn [] (first-intersection variants regular-options))
choose-bold (fn [] (first-intersection variants bold-options))
choose-italic (fn [] (first-intersection variants italic-options))
choose-bold-italic (fn [] (or (first-intersection variants bold-italic-options) (choose-bold)))
choose-italic-bold (fn [] (or (first-intersection variants bold-italic-options) (choose-italic)))
new-variant (let [bold? (is-bold? current-variant)
italic? (is-italic? current-variant)
add-bold? (and (not bold?)
(or (= variant-id "add-bold")
(= variant-id "toggle-bold")))
remove-bold? (and bold?
(or (= variant-id "remove-bold")
(= variant-id "toggle-bold")))
add-italic? (and (not italic?)
(or (= variant-id "add-italic")
(= variant-id "toggle-italic")))
remove-italic? (and italic?
(or (= variant-id "remove-italic")
(= variant-id "toggle-italic")))]
(cond
(and add-bold? italic?) ;; it is italic, set it to bold+italic
(choose-bold-italic)
(and add-bold? (not italic?)) ;; it is regular, set it to bold
(choose-bold)
(and remove-bold? italic?) ;; it is bold+italic, set it to italic
(choose-italic)
(and remove-bold? (not italic?)) ;; it is bold set it to regular
(choose-regular)
(and add-italic? bold?) ;; it is bold, set it to italic+bold
(choose-italic-bold)
(and add-italic? (not bold?)) ;; it is regular, set it to italic
(choose-italic)
(and remove-italic? bold?) ;; it is bold+italic, set it to bold
(choose-bold)
(and remove-italic? (not bold?)) ;; it is italic, set it to regular
(choose-regular)))
new-weight (when new-variant
(->> (:variants font)
(filter #(= (:id %) new-variant))
first
:weight))]
(when new-variant
{:font-variant-id new-variant,
:font-weight new-weight})))
(defn calculate-text-values
[shape]
(let [state-map (deref refs/workspace-editor-state)
editor-state (get state-map (:id shape))]
(d/merge
(dwt/current-root-values
{:shape shape
:attrs dwt/root-attrs})
(dwt/current-paragraph-values
{:editor-state editor-state
:shape shape
:attrs dwt/paragraph-attrs})
(dwt/current-text-values
{:editor-state editor-state
:shape shape
:attrs dwt/text-attrs}))))
(defn- update-attrs [shape props]
(let [
text-values (calculate-text-values shape)
font-size (d/parse-double (:font-size text-values))
line-height (d/parse-double (:line-height text-values))
letter-spacing (d/parse-double (:letter-spacing text-values))
props (cond
(:font-size-inc props)
{:font-size (str (inc font-size))}
(:font-size-dec props)
{:font-size (str (dec font-size))}
(:line-height-inc props)
{:line-height (str (+ line-height 0.1))}
(:line-height-dec props)
{:line-height (str (- line-height 0.1))}
(:letter-spacing-inc props)
{:letter-spacing (str (+ letter-spacing 0.1))}
(:letter-spacing-dec props)
{:letter-spacing (str (- letter-spacing 0.1))}
(= (:text-decoration props) "toggle-underline") ;;toggle
(if (= (:text-decoration text-values) "underline")
{:text-decoration "none"}
{:text-decoration "underline"})
(= (:text-decoration props) "toggle-line-through") ;;toggle
(if (= (:text-decoration text-values) "line-through")
{:text-decoration "none"}
{:text-decoration "line-through"})
(:font-variant-id props)
(generate-variant-props text-values (:font-variant-id props))
:else props)]
(when (and shape props)
(st/emit! (dwt/update-attrs (:id shape) props)))))
(defn blend-props
[shapes props]
(let [text-values (map calculate-text-values shapes)
all-underline? (every? #(= (:text-decoration %) "underline") text-values)
all-line-through? (every? #(= (:text-decoration %) "line-through") text-values)
all-bold? (every? #(is-bold? (:font-variant-id %)) text-values)
all-italic? (every? #(is-italic? (:font-variant-id %)) text-values)
]
(cond
(= (:text-decoration props) "toggle-underline")
(if all-underline?
{:text-decoration "none"}
{:text-decoration "underline"})
(= (:text-decoration props) "toggle-line-through")
(if all-line-through?
{:text-decoration "none"}
{:text-decoration "line-through"})
(= (:font-variant-id props) "toggle-bold")
(if all-bold?
{:font-variant-id "remove-bold"}
{:font-variant-id "add-bold"})
(= (:font-variant-id props) "toggle-italic")
(if all-italic?
{:font-variant-id "remove-italic"}
{:font-variant-id "add-italic"})
:else
props)))
(defn- update-attrs-when-no-readonly [props]
(let [undo-id (js/Symbol)
read-only? (deref refs/workspace-read-only?)
shapes-with-children (deref refs/selected-shapes-with-children)
text-shapes (filter #(= (:type %) :text) shapes-with-children)
props (if (> (count text-shapes) 1)
(blend-props text-shapes props)
props)]
(when (and (not read-only?) text-shapes)
(st/emit! (dwu/start-undo-transaction undo-id))
(run! #(update-attrs % props) text-shapes)
(st/emit! (dwu/commit-undo-transaction undo-id)))))
(def shortcuts
{:align-left {:tooltip (ds/meta (ds/alt "l"))
:command (ds/c-mod "alt+l")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-align "left"})}
:align-right {:tooltip (ds/meta (ds/alt "r"))
:command (ds/c-mod "alt+r")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-align "right"})}
:align-center {:tooltip (ds/meta (ds/alt "t"))
:command (ds/c-mod "alt+t")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-align "center"})}
:align-justify {:tooltip (ds/meta (ds/alt "j"))
:command (ds/c-mod "alt+j")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-align "justify"})}
:underline {:tooltip (ds/meta "u")
:command (ds/c-mod "u")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-decoration "toggle-underline"})}
:line-through {:tooltip (ds/alt (ds/meta-shift "5"))
:command "alt+shift+5"
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-decoration "toggle-line-through"})}
:font-size-inc {:tooltip (ds/meta-shift ds/up-arrow)
:command (ds/c-mod "shift+up")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:font-size-inc true})}
:font-size-dec {:tooltip (ds/meta-shift ds/down-arrow)
:command (ds/c-mod "shift+down")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:font-size-dec true})}
:line-height-inc {:tooltip (ds/alt-shift ds/up-arrow)
:command (ds/a-mod "shift+up")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:line-height-inc true})}
:line-height-dec {:tooltip (ds/alt-shift ds/down-arrow)
:command (ds/a-mod "shift+down")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:line-height-dec true})}
:letter-spacing-inc {:tooltip (ds/alt ds/up-arrow)
:command (ds/a-mod "up")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:letter-spacing-inc true})}
:letter-spacing-dec {:tooltip (ds/alt ds/down-arrow)
:command (ds/a-mod "down")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:letter-spacing-dec true})}
:bold {:tooltip (ds/meta "b")
:command (ds/c-mod "b")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:font-variant-id "toggle-bold"})}
:italic {:tooltip (ds/meta "i")
:command (ds/c-mod "i")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:font-variant-id "toggle-italic"})}})

View File

@@ -28,6 +28,68 @@
[beicon.core :as rx]
[potok.core :as ptk]))
;; -- Attrs
(def text-typography-attrs
[:typography-ref-id
:typography-ref-file])
(def text-fill-attrs
[:fill-color
:fill-opacity
:fill-color-ref-id
:fill-color-ref-file
:fill-color-gradient])
(def text-font-attrs
[:font-id
:font-family
:font-variant-id
:font-size
:font-weight
:font-style])
(def text-align-attrs
[:text-align])
(def text-direction-attrs
[:text-direction])
(def text-spacing-attrs
[:line-height
:letter-spacing])
(def text-valign-attrs
[:vertical-align])
(def text-decoration-attrs
[:text-decoration])
(def text-transform-attrs
[:text-transform])
(def shape-attrs
[:grow-type])
(def root-attrs text-valign-attrs)
(def paragraph-attrs
(d/concat-vec
text-align-attrs
text-direction-attrs))
(def text-attrs
(d/concat-vec
text-typography-attrs
text-font-attrs
text-spacing-attrs
text-decoration-attrs
text-transform-attrs))
(def attrs (d/concat-set shape-attrs root-attrs paragraph-attrs text-attrs))
;; -- Editor
(defn update-editor
[editor]
(ptk/reify ::update-editor
@@ -182,8 +244,8 @@
(defn- update-text-content
[shape pred-fn update-fn attrs]
(let [update-attrs #(update-fn % attrs)
transform #(txt/transform-nodes pred-fn update-attrs %)]
(let [update-attrs-fn #(update-fn % attrs)
transform #(txt/transform-nodes pred-fn update-attrs-fn %)]
(-> shape
(update :content transform))))
@@ -525,3 +587,24 @@
(rx/take-until stopper))
(rx/of (update-position-data id position-data))))
(rx/empty))))))
(defn update-attrs
[id attrs]
(ptk/reify ::update-attrs
ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(let [attrs (select-keys attrs root-attrs)]
(if-not (empty? attrs)
(rx/of (update-root-attrs {:id id :attrs attrs}))
(rx/empty)))
(let [attrs (select-keys attrs paragraph-attrs)]
(if-not (empty? attrs)
(rx/of (update-paragraph-attrs {:id id :attrs attrs}))
(rx/empty)))
(let [attrs (select-keys attrs text-attrs)]
(if-not (empty? attrs)
(rx/of (update-text-attrs {:id id :attrs attrs}))
(rx/empty)))))))

View File

@@ -15,6 +15,7 @@
[app.main.repo :as rp]
[app.main.store :as st]
[app.util.dom :as dom]
[app.util.timers :as ts]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[potok.core :as ptk]))
@@ -28,22 +29,27 @@
(rx/filter #(= % id))
(rx/take 1)))
(defn thumbnail-stream
(defn thumbnail-canvas-blob-stream
[object-id]
(rx/create
(fn [subs]
;; We look in the DOM a canvas that 1) matches the id and 2) that it's not empty
;; will be empty on first rendering before drawing the thumbnail and we don't want to store that
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-ready='true']" object-id))]
(if (some? node)
(.toBlob node (fn [blob]
(rx/push! subs blob)
(rx/end! subs))
"image/png")
;; Look for the thumbnail canvas to send the data to the backend
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-ready='true']" object-id))
stopper (->> st/stream
(rx/filter (ptk/type? :app.main.data.workspace/finalize-page))
(rx/take 1))]
(if (some? node)
;; Success: we generate the blob (async call)
(rx/create
(fn [subs]
(ts/raf
#(.toBlob node (fn [blob]
(rx/push! subs blob)
(rx/end! subs))
"image/png"))))
;; If we cannot find the node we send `nil` and the upsert will delete the thumbnail
(do (rx/push! subs nil)
(rx/end! subs)))))))
;; Not found, we retry after delay
(->> (rx/timer 250)
(rx/flat-map #(thumbnail-canvas-blob-stream object-id))
(rx/take-until stopper)))))
(defn clear-thumbnail
[page-id frame-id]
@@ -64,7 +70,7 @@
(watch [_ state _]
(let [object-id (dm/str page-id frame-id)
file-id (or file-id (:current-file-id state))
blob-result (thumbnail-stream object-id)
blob-result (thumbnail-canvas-blob-stream object-id)
params {:file-id file-id :object-id object-id :data nil}]
(rx/concat
@@ -80,7 +86,7 @@
(rx/merge-map
(fn [data]
(if (some? file-id)
(if (and (some? data) (some? file-id))
(let [params (assoc params :data data)]
(rx/merge
;; Update the local copy of the thumbnails so we don't need to request it again
@@ -89,7 +95,9 @@
(rx/catch #(rx/empty))
(rx/ignore))))
(rx/empty)))))))))))
(rx/empty))))
(rx/catch #(do (.error js/console %)
(rx/empty))))))))))
(defn- extract-frame-changes
"Process a changes set in a commit to extract the frames that are changing"

View File

@@ -283,7 +283,7 @@
(let [variant (d/seek #(= (:id %) font-variant-id) variants)]
(-> (generate-gfonts-url
{:family family
:variants [{:id variant}]})
:variants [variant]})
(http/fetch-text)))
(= :custom backend)

View File

@@ -194,3 +194,14 @@
(rx/dedupe))]
(rx/subscribe-with ob sub)
sub))
(defonce keyboard-z
(let [sub (rx/behavior-subject nil)
ob (->> st/stream
(rx/filter keyboard-event?)
(rx/filter kbd/z?)
(rx/filter (comp not kbd/editing?))
(rx/map #(= :down (:type %)))
(rx/dedupe))]
(rx/subscribe-with ob sub)
sub))

View File

@@ -252,9 +252,9 @@
[:span
(tr "auth.terms-privacy-agreement")
[:div
[:a {:href "https://penpot.app/terms.html" :target "_blank"} (tr "auth.terms-of-service")]
[:a {:href "https://penpot.app/terms" :target "_blank"} (tr "auth.terms-of-service")]
[:span ",\u00A0"]
[:a {:href "https://penpot.app/privacy.html" :target "_blank"} (tr "auth.privacy-policy")]]]]])
[:a {:href "https://penpot.app/privacy" :target "_blank"} (tr "auth.privacy-policy")]]]]])
[:& fm/submit-button
{:label (tr "auth.register-submit")

View File

@@ -169,7 +169,7 @@
[:div.template-card
[:div.img-container
[:a {:tab-index "0"
:href "https://penpot.app/libraries-templates.html" :target "_blank" :on-click handle-template-link}
:href "https://penpot.app/libraries-templates" :target "_blank" :on-click handle-template-link}
[:div.template-link
[:div.template-link-title (tr "dashboard.libraries-and-templates")]
[:div.template-link-text (tr "dashboard.libraries-and-templates.explore")]]]]]]]

View File

@@ -120,7 +120,9 @@
on-dismiss-all
(fn [items]
(run! on-delete items))]
(run! on-delete items))
problematic-fonts? (some :height-warning? (vals @fonts))]
[:div.dashboard-fonts-upload
[:div.dashboard-fonts-hero
@@ -132,7 +134,14 @@
[:div.icon i/msg-info]
[:div.content
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.hero-text2"}]]]]
:label "dashboard.fonts.hero-text2"}]]]
(when problematic-fonts?
[:div.banner.warning
[:div.icon i/msg-warning]
[:div.content
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.warning-text"}]]])]
[:button.btn-primary
{:on-click on-click
@@ -171,6 +180,8 @@
[:span item])]
[:div.table-field.options
(when (:height-warning? item)
[:span.icon.failure i/msg-warning])
[:button.btn-primary.upload-button
{:on-click #(on-upload item)
:class (dom/classnames :disabled uploading?)

View File

@@ -746,10 +746,10 @@
[:li.separator {:tab-index (if show
"0"
"-1")
:on-click #(dom/open-new-window "https://penpot.app/libraries-templates.html")
:on-click #(dom/open-new-window "https://penpot.app/libraries-templates")
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/open-new-window "https://penpot.app/libraries-templates.html")))
(dom/open-new-window "https://penpot.app/libraries-templates")))
:data-test "libraries-templates-profile-opt"}
[:span.text (tr "labels.libraries-and-templates")]]
[:li {:tab-index (if show
@@ -763,10 +763,10 @@
[:li {:tab-index (if show
"0"
"-1")
:on-click #(dom/open-new-window "https://penpot.app/terms.html")
:on-click #(dom/open-new-window "https://penpot.app/terms")
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/open-new-window "https://penpot.app/terms.html")))}
(dom/open-new-window "https://penpot.app/terms")))}
[:span (tr "auth.terms-of-service")]]
(when (contains? @cf/flags :user-feedback)

View File

@@ -460,7 +460,8 @@
(mf/use-fn
(fn []
(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
(modal/hide))))
(modal/hide)
(dd/fetch-team-invitations))))
on-copy-success
(mf/use-fn

View File

@@ -55,7 +55,7 @@
{:p1 p1 :p2 p2 :p3 p3 :p4 p4}
(and (= p1 p3) (= p2 p4))
{:p1 p1 :p3 p3}
{:p1 p1 :p2 p2}
(and (not= p1 p3) (= p2 p4))
{:p1 p1 :p2 p2 :p3 p3}
@@ -67,9 +67,11 @@
(let [sizing (if (= type :width)
(:layout-item-h-sizing shape)
(:layout-item-v-sizing shape))]
(if (= sizing :fill)
"100%"
(str (format-pixels value)))))
(cond
(= sizing :fill) "100%"
(= sizing :auto) "auto"
(number? value) (format-pixels value)
:else value)))
(defn format-padding
[padding-values type]

View File

@@ -38,7 +38,7 @@
[:div.modal-left.welcome
[:img {:src "images/onboarding-welcome.jpg" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:div.modal-right
[:div.release-container [:span.release "Beta " (:main @cf/version)]]
[:div.release-container [:span.release "Version " (:main @cf/version)]]
[:div.right-content
[:div.modal-title
[:h2 {:data-test "onboarding-welcome"} (tr "onboarding-v2.welcome.title")]]
@@ -73,7 +73,7 @@
[:div.modal-left.welcome
[:img {:src "images/onboarding-people.jpg" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:div.modal-right
[:div.release-container [:span.release "Beta " (:main @cf/version)]]
[:div.release-container [:span.release "Version " (:main @cf/version)]]
[:div.right-content
[:div.modal-title
[:h2 {:data-test "onboarding-welcome"} (tr "onboarding-v2.before-start.title")]]

View File

@@ -52,7 +52,7 @@
:id "newsletter-news"
:on-change #(toggle newsletter-news)}]
[:label {:for "newsletter-news"} (tr "onboarding-v2.newsletter.news")]]]
[:p (tr "onboarding-v2.newsletter.privacy1") [:a {:target "_blank" :href "https://penpot.app/privacy.html"} (tr "onboarding.newsletter.policy")]]
[:p (tr "onboarding-v2.newsletter.privacy1") [:a {:target "_blank" :href "https://penpot.app/privacy"} (tr "onboarding.newsletter.policy")]]
[:p (tr "onboarding-v2.newsletter.privacy2")]]
[:div.modal-footer
[:button.btn-primary {:on-click accept} (tr "labels.continue")]]

View File

@@ -17,6 +17,7 @@
[app.main.ui.releases.v1-14]
[app.main.ui.releases.v1-15]
[app.main.ui.releases.v1-16]
[app.main.ui.releases.v1-17]
[app.main.ui.releases.v1-4]
[app.main.ui.releases.v1-5]
[app.main.ui.releases.v1-6]
@@ -86,4 +87,4 @@
(defmethod rc/render-release-notes "0.0"
[params]
(rc/render-release-notes (assoc params :version "1.16")))
(rc/render-release-notes (assoc params :version "1.17")))

View File

@@ -24,7 +24,7 @@
[:div.modal-content
[:p "Penpots officially beta!"]
[:p "We carefully analyzed everything important to us before taking this step. And now were ready to move forward onto the beta version. Have a play around if you havent yet."]
[:a {:href "https://penpot.app/why-beta.html" :target "_blank"} "Learn why we made this decision."]]
[:a {:href "https://penpot.app/why-beta" :target "_blank"} "Learn why we made this decision."]]
[:div.modal-navigation
[:button.btn-secondary {:on-click finish} "Explore Penpot Beta 1.10"]]]
[:img.deco {:src "images/deco-left.png" :border "0"}]

View File

@@ -0,0 +1,108 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.releases.v1-17
(:require
[app.main.ui.releases.common :as c]
[rumext.v2 :as mf]))
(defmethod c/render-release-notes "1.17"
[{:keys [slide klass next finish navigate version]}]
(mf/html
(case @slide
:start
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/onboarding-version.jpg" :border "0" :alt "What's new release 1.17"}]]
[:div.modal-right
[:div.modal-title
[:h2 "What's new?"]]
[:span.release "Version " version]
[:div.modal-content
[:p "This is the first release in which Penpot is no longer Beta (hooray!) and it comes with very special features, starring the long awaited Flex Layout."]
[:p "On this 1.17 release, youll also be able to inspect the code and properties of your designs right from the workspace and to manage webhooks. Weve also implemented a lot of accessibility improvements."]]
[:div.modal-navigation
[:button.btn-secondary {:on-click next} "Continue"]]]
[:img.deco {:src "images/deco-left.png" :border "0"}]
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]]
0
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/features/1.17-flex-layout.gif" :border "0" :alt "Flex-Layout"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Flex-Layout"]]
[:div.modal-content
[:p "The Flex Layout allows you to automatically adapt your designs. Resize, fit, and fill content and containers without the need to do it manually."]
[:p "Penpot brings a layout system like no other. As described by one of our beta testers: 'I love the fact that Penpot is following the CSS FlexBox, which is making UI Design a step closer to the logic and behavior behind how things will be actually built after design.'"]]
[:div.modal-navigation
[:button.btn-secondary {:on-click next} "Continue"]
[:& c/navigation-bullets
{:slide @slide
:navigate navigate
:total 4}]]]]]]
1
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/features/1.17-inspect.gif" :border "0" :alt "Inspect at the workspace"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Inspect at the workspace"]]
[:div.modal-content
[:p "Now you can inspect designs to get measures, properties and production-ready code right at the workspace, so designers and developers can share the same space while working."]
[:p "Also, inspect mode provides a safer view-only mode and other improvements."]]
[:div.modal-navigation
[:button.btn-secondary {:on-click next} "Continue"]
[:& c/navigation-bullets
{:slide @slide
:navigate navigate
:total 4}]]]]]]
2
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/features/1.17-webhook.gif" :border "0" :alt "Webhooks"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Webhooks"]]
[:div.modal-content
[:p "Webhooks allow other websites and apps to be notified when certain events happen at Penpot, ensuring to create integrations with other services."]
[:p "While we are still working on a plugin system, this is a great and simple way to create integrations with other services."]]
[:div.modal-navigation
[:button.btn-secondary {:on-click next} "Continue"]
[:& c/navigation-bullets
{:slide @slide
:navigate navigate
:total 4}]]]]]]
3
[:div.modal-overlay
[:div.animated {:class @klass}
[:div.modal-container.onboarding.feature
[:div.modal-left
[:img {:src "images/features/1.17-ally.gif" :border "0" :alt "Accessibility improvements"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Accessibility improvements"]]
[:div.modal-content
[:p "We're working to ensure that people with visual or physical impairments can use Penpot in the same conditions."]
[:p "This release comes with improvements on color contrasts, alt texts, semantic labels, focusable items and keyboard navigation at login and dashboard, but more will come."]]
[:div.modal-navigation
[:button.btn-secondary {:on-click finish} "Start!"]
[:& c/navigation-bullets
{:slide @slide
:navigate navigate
:total 4}]]]]]])))

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.config :as cf]
[app.main.ui.context :as muc]
[app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.custom-stroke :refer [shape-fills shape-strokes]]
@@ -90,15 +91,25 @@
bounds (or (obj/get props "bounds") (gsh/points->selrect (:points shape)))]
(when (:thumbnail shape)
[:image.frame-thumbnail
{:id (dm/str "thumbnail-" (:id shape))
:href (:thumbnail shape)
:x (:x bounds)
:y (:y bounds)
:width (:width bounds)
:height (:height bounds)
;; DEBUG
:style {:filter (when (debug? :thumbnails) "sepia(1)")}}])))
[:*
[:image.frame-thumbnail
{:id (dm/str "thumbnail-" (:id shape))
:href (:thumbnail shape)
:x (:x bounds)
:y (:y bounds)
:width (:width bounds)
:height (:height bounds)
;; DEBUG
:style {:filter (when (and (not (cf/check-browser? :safari))(debug? :thumbnails)) "sepia(1)")}}]
;; Safari don't support filters so instead we add a rectangle around the thumbnail
(when (and (cf/check-browser? :safari) (debug? :thumbnails))
[:rect {:x (+ (:x bounds) 4)
:y (+ (:y bounds) 4)
:width (- (:width bounds) 8)
:height (- (:height bounds) 8)
:stroke "red"
:stroke-width 2}])])))
(mf/defc frame-thumbnail
{::mf/wrap-props false}

View File

@@ -8,7 +8,6 @@
(:require
[app.common.text :as txt]
[app.main.fonts :as fonts]
[app.main.ui.shapes.text.fo-text :as fo]
[app.main.ui.shapes.text.svg-text :as svg]
[app.util.object :as obj]
[rumext.v2 :as mf]))
@@ -28,6 +27,5 @@
(mf/with-memo [content]
(load-fonts! content))
(if (some? position-data)
[:> svg/text-shape props]
[:> fo/text-shape props])))
(when (some? position-data)
[:> svg/text-shape props])))

View File

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

View File

@@ -318,7 +318,7 @@
mod? (kbd/mod? event)
shift? (kbd/shift? event)
delta (.-pixelY norm-event)
viewer-section (mf/ref-val viewer-section-ref)
viewer-section (.target event)
scroll-pos (if shift?
(dom/get-h-scroll-pos viewer-section)
(dom/get-scroll-pos viewer-section))

View File

@@ -46,30 +46,14 @@
(.-deltaX ^js event))]
(if (pos? delta)
(st/emit! dv/decrease-zoom)
(st/emit! dv/increase-zoom))))
(when-not (kbd/mod? event)
(let [event (.getBrowserEvent ^js event)
shift? (kbd/shift? event)
inspect-svg-container (mf/ref-val inspect-svg-container-ref)
delta (+ (.-deltaY ^js event)
(.-deltaX ^js event))
scroll-pos (if shift?
(dom/get-h-scroll-pos inspect-svg-container)
(dom/get-scroll-pos inspect-svg-container))
new-scroll-pos (+ scroll-pos delta)]
(do
(dom/prevent-default event)
(dom/stop-propagation event)
(if shift?
(dom/set-h-scroll-pos! inspect-svg-container new-scroll-pos)
(dom/set-scroll-pos! inspect-svg-container new-scroll-pos))))))
(st/emit! dv/increase-zoom)))))
on-mount
(fn []
;; bind with passive=false to allow the event to be cancelled
;; https://stackoverflow.com/a/57582286/3219895
(let [key1 (events/listen goog/global EventType.WHEEL
on-mouse-wheel #js {"passive" false "capture" true})]
on-mouse-wheel #js {"passive" false})]
(fn []
(events/unlistenByKey key1))))]

View File

@@ -27,7 +27,7 @@
:group [:layout :svg :layout-flex-item]
:rect [:layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:circle [:layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:path [:layout :fill :stroke :shadow :blur :svg]
:path [:layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:image [:image :layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:text [:layout :text :shadow :blur :stroke :layout-flex-item]})

View File

@@ -6,7 +6,6 @@
(ns app.main.ui.viewer.inspect.attributes.fill
(:require
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.viewer.inspect.attributes.common :refer [color-row]]
[app.util.code-gen :as cg]
[app.util.color :as uc]
@@ -40,10 +39,11 @@
(let [color-format (mf/use-state :hex)
color (shape->color shape)]
[:& color-row {:color color
:format @color-format
:on-change-format #(reset! color-format %)
:copy-data (copy-data shape)}]))
[:div.attributes-fill-block
[:& color-row {:color color
:format @color-format
:on-change-format #(reset! color-format %)
:copy-data (copy-data shape)}]]))
(mf/defc fill-panel
[{:keys [shapes]}]
@@ -51,14 +51,13 @@
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (tr "inspect.attributes.fill")]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-data (first shapes))}])]
[:div.attributes-block-title-text (tr "inspect.attributes.fill")]]
(for [shape shapes]
[:div.attributes-fill-blocks
(for [shape shapes]
(if (seq (:fills shape))
(for [value (:fills shape [])]
[:& fill-block {:key (str "fill-block-" (:id shape) value)
:shape value}])
[:& fill-block {:key (str "fill-block-only" (:id shape))
:shape shape}]))])))
:shape shape}]))]])))

View File

@@ -23,31 +23,31 @@
:rx "border-radius"
:r1 "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)
:width (partial fmt/format-size :width)
:height (partial fmt/format-size :height)}
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)
:width #(cg/get-size :width %)
:height #(cg/get-size :height %)}
:multi {:r1 [:r1 :r2 :r3 :r4]}})
(defn copy-data
([shape]
(apply copy-data shape properties))
([shape & properties]
([shape & properties]
(cg/generate-css-props shape properties params)))
(mf/defc layout-block
[{:keys [shape]}]
(let [selrect (:selrect shape)
{:keys [x y]} selrect]
{:keys [x y width height]} selrect]
[:*
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.width")]
[:div.attributes-value (fmt/format-size :width (:width shape) shape)]
[:& copy-button {:data (copy-data shape :width)}]]
[:div.attributes-value (fmt/format-size :width width shape)]
[:& copy-button {:data (copy-data selrect :width)}]]
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.height")]
[:div.attributes-value (fmt/format-size :height (:height shape) shape)]
[:& copy-button {:data (copy-data shape :height)}]]
[:div.attributes-value (fmt/format-size :height height shape)]
[:& copy-button {:data (copy-data selrect :height)}]]
(when (not= (:x shape) 0)
[:div.attributes-unit-row
@@ -73,7 +73,7 @@
[:div.attributes-value
(fmt/format-number (:r1 shape)) ", "
(fmt/format-number (:r2 shape)) ", "
(fmt/format-number (:r3 shape))", "
(fmt/format-number (:r3 shape)) ", "
(fmt/format-pixels (:r4 shape))]
[:& copy-button {:data (copy-data shape :r1)}]])

View File

@@ -38,18 +38,18 @@
:layout-wrap-type "flex-wrap"
:layout-gap "gap"
:layout-padding "padding"}
:format {:layout name
:layout-flex-dir name
:layout-align-items name
:layout-justify-content name
:layout-wrap-type name
:format {:layout d/name
:layout-flex-dir d/name
:layout-align-items d/name
:layout-justify-content d/name
:layout-wrap-type d/name
:layout-gap fm/format-gap
:layout-padding fm/format-padding}})
(def layout-align-content-params
{:props [:layout-align-content]
:to-prop {:layout-align-content "align-content"}
:format {:layout-align-content name}})
:format {:layout-align-content d/name}})
(defn copy-data
([shape]
@@ -72,7 +72,7 @@
(for [[k v] values]
[:span.items {:key (str type "-" k "-" v)} v "px"])]))
(mf/defc layout-block
(mf/defc layout-flex-block
[{:keys [shape]}]
[:*
[:div.attributes-unit-row
@@ -129,11 +129,11 @@
(let [shapes (->> shapes (filter has-flex?))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Layout"]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-data (first shapes))}])]
[:div.attributes-block-title
[:div.attributes-block-title-text "Layout"]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-data (first shapes))}])]
(for [shape shapes]
[:& layout-block {:shape shape
:key (:id shape)}])])))
(for [shape shapes]
[:& layout-flex-block {:shape shape
:key (:id shape)}])])))

View File

@@ -43,7 +43,7 @@
:layout-item-max-w "max-width"
:layout-item-min-w "min-width"}
:format {:layout-item-margin format-margin
:layout-item-align-self name}})
:layout-item-align-self d/name}})
(defn copy-data
([shape]

View File

@@ -55,12 +55,7 @@
[{:keys [shape]}]
(let [color-format (mf/use-state :hex)
color (shape->color shape)]
[:*
[:& color-row {:color color
:format @color-format
:copy-data (copy-color-data shape)
:on-change-format #(reset! color-format %)}]
[:div.attributes-stroke-block
(let [{:keys [stroke-style stroke-alignment]} shape
stroke-style (if (= stroke-style :svg) :solid stroke-style)
stroke-alignment (or stroke-alignment :center)]
@@ -78,7 +73,11 @@
;; inspect.attributes.stroke.alignment.inner
;; inspect.attributes.stroke.alignment.outer
[:div.attributes-label (->> stroke-alignment d/name (str "inspect.attributes.stroke.alignment.") (tr))]
[:& copy-button {:data (copy-stroke-data shape)}]])]))
[:& copy-button {:data (copy-stroke-data shape)}]])
[:& color-row {:color color
:format @color-format
:copy-data (copy-color-data shape)
:on-change-format #(reset! color-format %)}]]))
(mf/defc stroke-panel
[{:keys [shapes]}]
@@ -86,14 +85,13 @@
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (tr "inspect.attributes.stroke")]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-stroke-data (first shapes))}])]
[:div.attributes-block-title-text (tr "inspect.attributes.stroke")]]
(for [shape shapes]
[:div.attributes-stroke-blocks
(for [shape shapes]
(if (seq (:strokes shape))
(for [value (:strokes shape [])]
[:& stroke-block {:key (str "stroke-color-" (:id shape) value)
:shape value}])
[:& stroke-block {:key (str "stroke-color-only" (:id shape))
:shape shape}]))])))
:shape shape}]))]])))

View File

@@ -7,6 +7,7 @@
(ns app.main.ui.viewer.inspect.attributes.text
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.text :as txt]
[app.main.fonts :as fonts]
[app.main.store :as st]
@@ -42,6 +43,7 @@
:font-family
:font-style
:font-size
:font-weight
:line-height
:letter-spacing
:text-decoration
@@ -57,13 +59,14 @@
(def params
{:to-prop {:fill-color "color"
:fill-color-gradient "color"}
:format {:font-family #(str "'" % "'")
:font-style #(str "'" % "'")
:font-size #(str (format-number %) "px")
:format {:font-family #(dm/str "'" % "'")
:font-style #(dm/str % )
:font-size #(dm/str (format-number %) "px")
:font-weight d/name
:line-height #(format-number %)
:letter-spacing #(str (format-number %) "px")
:text-decoration name
:text-transform name
:letter-spacing #(dm/str (format-number %) "px")
:text-decoration d/name
:text-transform d/name
:fill-color #(-> %2 shape->color uc/color->background)
:fill-color-gradient #(-> %2 shape->color uc/color->background)}})
@@ -131,6 +134,12 @@
[:div.attributes-value (str (format-number (:font-size style))) "px"]
[:& copy-button {:data (copy-style-data style :font-size)}]])
(when (:font-weight style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.font-weight")]
[:div.attributes-value (str (:font-weight style))]
[:& copy-button {:data (copy-style-data style :font-weight)}]])
(when (:line-height style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.line-height")]

View File

@@ -7,37 +7,34 @@
(ns app.main.ui.viewer.inspect.code
(:require
["js-beautify" :as beautify]
["react-dom/server" :as rds]
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.uuid :as uuid]
[app.main.data.events :as ev]
[app.main.refs :as refs]
[app.main.render :as render]
[app.main.store :as st]
[app.main.ui.components.code-block :refer [code-block]]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.util.code-gen :as cg]
[app.util.dom :as dom]
[cuerdas.core :as str]
[potok.core :as ptk]
[rumext.v2 :as mf]))
(defn generate-markup-code [_type shapes from]
(let [frame (if (= from :workspace)
(dom/query js/document (dm/str "#shape-" uuid/zero))
(dom/query js/document "#svg-frame"))
markup-shape
(fn [shape]
(let [selector (str "#shape-" (:id shape) (when (= :text (:type shape)) " .root"))]
(when-let [el (and frame (dom/query frame selector))]
(str
(str/fmt "<!-- %s -->" (:name shape))
(.-outerHTML el)))))]
(->> shapes
(map markup-shape )
(remove nil?)
(str/join "\n\n"))))
(defn generate-markup-code [objects shapes]
;; Here we can render specific HTML code
(->> shapes
(map (fn [shape]
(dm/str
"<!-- Shape: " (:name shape) " -->"
(rds/renderToStaticMarkup
(mf/element
render/object-svg
#js {:objects objects
:object-id (-> shape :id)})))))
(str/join "\n\n")))
(defn format-code [code type]
(let [code (-> code
@@ -55,6 +52,16 @@
(mf/deref get-layout-children-refs)))
(defn get-objects [from]
(let [page-objects-ref
(mf/use-memo
(mf/deps from)
(fn []
(if (= from :workspace)
refs/workspace-page-objects
(refs/get-viewer-objects))))]
(mf/deref page-objects-ref)))
(mf/defc code
[{:keys [shapes frame on-expand from]}]
(let [style-type (mf/use-state "css")
@@ -64,12 +71,14 @@
route (mf/deref refs/route)
page-id (:page-id (:query-params route))
flex-items (get-flex-elements page-id shapes from)
objects (get-objects from)
shapes (map #(assoc % :flex-items flex-items) shapes)
style-code (-> (cg/generate-style-code @style-type shapes)
(format-code "css"))
markup-code (-> (mf/use-memo (mf/deps shapes) #(generate-markup-code @markup-type shapes from))
(format-code "svg"))
markup-code
(-> (mf/use-memo (mf/deps shapes) #(generate-markup-code objects shapes))
(format-code "svg"))
on-markup-copied
(mf/use-callback

View File

@@ -375,7 +375,7 @@
is-flex-container? (and is-frame? (= :flex (:layout (first shapes))))
ids (->> shapes (map :id))
add-flex #(st/emit! (if is-frame?
(dwsl/create-layout-from-id ids :flex)
(dwsl/create-layout-from-id ids :flex true)
(dwsl/create-layout-from-selection :flex)))
remove-flex #(st/emit! (dwsl/remove-layout ids))]
@@ -411,6 +411,8 @@
current-file-id (mf/use-ctx ctx/current-file-id)
local-component? (= component-file current-file-id)
remote-components (filter #(not= (:component-file %) current-file-id)
component-shapes)
workspace-data (deref refs/workspace-data)
workspace-libraries (deref refs/workspace-libraries)
@@ -442,16 +444,19 @@
:accept-style :primary
:on-accept do-update-component}))
do-update-in-bulk #(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.update-remote-component-in-bulk.message")
:hint (tr "modals.update-remote-component-in-bulk.hint")
:items component-shapes
:cancel-label (tr "modals.update-remote-component.cancel")
:accept-label (tr "modals.update-remote-component.accept")
:accept-style :primary
:on-accept do-update-component-in-bulk}))]
do-update-in-bulk (fn []
(if (empty? remote-components)
(do-update-component-in-bulk)
#(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.update-remote-component-in-bulk.message")
:hint (tr "modals.update-remote-component-in-bulk.hint")
:items remote-components
:cancel-label (tr "modals.update-remote-component.cancel")
:accept-label (tr "modals.update-remote-component.accept")
:accept-style :primary
:on-accept do-update-component-in-bulk}))))]
[:*
[:*
(when (or (not is-non-root?) (and has-component? (not single?)))

View File

@@ -416,11 +416,11 @@
[:span (tr "labels.tutorials")]]
[:li {:on-click show-release-notes}
[:span (tr "labels.release-notes")]]
[:li.separator {:on-click #(dom/open-new-window "https://penpot.app/libraries-templates.html")}
[:li.separator {:on-click #(dom/open-new-window "https://penpot.app/libraries-templates")}
[:span (tr "labels.libraries-and-templates")]]
[:li {:on-click #(dom/open-new-window "https://github.com/penpot/penpot")}
[:span (tr "labels.github-repo")]]
[:li {:on-click #(dom/open-new-window "https://penpot.app/terms.html")}
[:li {:on-click #(dom/open-new-window "https://penpot.app/terms")}
[:span (tr "auth.terms-of-service")]]
[:li.separator {:on-click #(st/emit! (when (contains? layout :collapse-left-sidebar) (dw/toggle-layout-flag :collapse-left-sidebar))
(-> (dw/toggle-layout-flag :shortcuts)

View File

@@ -12,7 +12,7 @@
[app.main.data.workspace.libraries :as dwl]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.assets :as a]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
@@ -24,31 +24,42 @@
(def workspace-file
(l/derived :workspace-file st/state))
(defn contents-str
(defn library-str
[components-count graphics-count colors-count typography-count]
(str
(str/join " · "
(cond-> []
(< 0 components-count)
(conj (tr "workspace.libraries.components" components-count))
(< 0 graphics-count)
(conj (tr "workspace.libraries.graphics" graphics-count))
(< 0 colors-count)
(conj (tr "workspace.libraries.colors" colors-count))
(< 0 typography-count)
(conj (tr "workspace.libraries.typography" typography-count))))
"\u00A0"))
(defn local-library-str
[library]
(let [components-count (count (get-in library [:data :components] []))
graphics-count (count (get-in library [:data :media] []))
colors-count (count (get-in library [:data :colors] []))
typography-count (count (get-in library [:data :typographies] []))]
;; Include a &nbsp; so this block has always some content
(str
(str/join " · "
(cond-> []
(< 0 components-count)
(conj (tr "workspace.libraries.components" components-count))
(library-str components-count graphics-count colors-count typography-count)))
(< 0 graphics-count)
(conj (tr "workspace.libraries.graphics" graphics-count))
(< 0 colors-count)
(conj (tr "workspace.libraries.colors" colors-count))
(< 0 typography-count)
(conj (tr "workspace.libraries.typography" typography-count))))
"\u00A0")))
(defn external-library-str
[library]
(let [components-count (get-in library [:library-summary :components :count] 0)
graphics-count (get-in library [:library-summary :media :count] 0)
colors-count (get-in library [:library-summary :colors :count] 0)
typography-count (get-in library [:library-summary :typographies :count] 0)]
(library-str components-count graphics-count colors-count typography-count)))
(mf/defc libraries-tab
[{:keys [file libraries shared-files] :as props}]
[{:keys [file colors typographies media components libraries shared-files] :as props}]
(let [search-term (mf/use-state "")
sorted-libraries (->> (vals libraries)
@@ -116,21 +127,21 @@
[:div.section-list
[:div.section-list-item
[:div
[:div.item-name (tr "workspace.libraries.file-library")]
[:div.item-contents (contents-str file)]]
[:div
(if (:is-shared file)
[:input.item-button {:type "button"
:value (tr "common.unpublish")
:on-click del-shared}]
[:input.item-button {:type "button"
:value (tr "common.publish")
:on-click add-shared}])]]
[:div.item-name (tr "workspace.libraries.file-library")]
[:div.item-contents (library-str (count components) (count media) (count colors) (count typographies) )]]
[:div
(if (:is-shared file)
[:input.item-button {:type "button"
:value (tr "common.unpublish")
:on-click del-shared}]
[:input.item-button {:type "button"
:value (tr "common.publish")
:on-click add-shared}])]]
(for [library sorted-libraries]
[:div.section-list-item {:key (:id library)}
[:div.item-name (:name library)]
[:div.item-contents (contents-str library)]
[:div.item-contents (local-library-str library)]
[:input.item-button {:type "button"
:value (tr "labels.remove")
:on-click #(unlink-library (:id library))}]])
@@ -155,7 +166,7 @@
(for [file filtered-files]
[:div.section-list-item {:key (:id file)}
[:div.item-name (:name file)]
[:div.item-contents (contents-str file)]
[:div.item-contents (external-library-str file)]
[:input.item-button {:type "button"
:value (tr "workspace.libraries.add")
:on-click #(link-library (:id file))}]])]
@@ -163,10 +174,10 @@
[:div.section-list-empty
(if (nil? shared-files)
i/loader-pencil
[:* i/library
(if (str/empty? @search-term)
(tr "workspace.libraries.no-shared-libraries-available")
(tr "workspace.libraries.no-matches-for" @search-term))])])]]))
[:* i/library
(if (str/empty? @search-term)
(tr "workspace.libraries.no-shared-libraries-available")
(tr "workspace.libraries.no-matches-for" @search-term))])])]]))
(mf/defc updates-tab
@@ -185,7 +196,7 @@
(for [library libraries-need-sync]
[:div.section-list-item {:key (:id library)}
[:div.item-name (:name library)]
[:div.item-contents (contents-str library)]
[:div.item-contents (external-library-str library)]
[:input.item-button {:type "button"
:value (tr "workspace.libraries.update")
:on-click #(update-library (:id library))}]])]])]))
@@ -197,10 +208,23 @@
(let [selected-tab (mf/use-state :libraries)
project (mf/deref refs/workspace-project)
file (mf/deref workspace-file)
libraries (->> (mf/deref refs/workspace-libraries)
(d/removem (fn [[_ val]] (:is-indirect val))))
shared-files (mf/deref refs/workspace-shared-files)
colors-ref (mf/use-memo (mf/deps (:id file)) #(a/file-colors-ref (:id file)))
colors (mf/deref colors-ref)
typography-ref (mf/use-memo (mf/deps (:id file)) #(a/file-typography-ref (:id file)))
typographies (mf/deref typography-ref)
media-ref (mf/use-memo (mf/deps (:id file)) #(a/file-media-ref (:id file)))
media (mf/deref media-ref)
components-ref (mf/use-memo (mf/deps (:id file)) #(a/file-components-ref (:id file)))
components (mf/deref components-ref)
change-tab #(reset! selected-tab %)
close #(modal/hide!)]
@@ -227,6 +251,10 @@
(case @selected-tab
:libraries
[:& libraries-tab {:file file
:colors colors
:typographies typographies
:media media
:components components
:libraries libraries
:shared-files shared-files}]
:updates

View File

@@ -10,33 +10,40 @@
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.config :as cf]
[app.main.data.workspace.thumbnails :as dwt]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.hooks :as hooks]
[app.main.ui.shapes.frame :as frame]
[app.util.dom :as dom]
[app.util.object :as obj]
[app.util.timers :as ts]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[cuerdas.core :as str]
[debug :refer [debug?]]
[promesa.core :as p]
[rumext.v2 :as mf]))
(defn- draw-thumbnail-canvas!
[canvas-node img-node]
(try
(when (and (some? canvas-node) (some? img-node))
(let [canvas-context (.getContext canvas-node "2d")
canvas-width (.-width canvas-node)
canvas-height (.-height canvas-node)]
(.clearRect canvas-context 0 0 canvas-width canvas-height)
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
(dom/set-property! canvas-node "data-ready" "true")
true))
(catch :default err
(.error js/console err)
false)))
(ts/raf
(fn []
(try
(when (and (some? canvas-node) (some? img-node))
(let [canvas-context (.getContext canvas-node "2d")
canvas-width (.-width canvas-node)
canvas-height (.-height canvas-node)]
(.clearRect canvas-context 0 0 canvas-width canvas-height)
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
;; Set a true on the next animation frame, we make sure the drawImage is completed
(ts/raf
#(dom/set-data! canvas-node "ready" "true"))
true))
(catch :default err
(.error js/console err)
false)))))
(defn- remove-image-loading
"Remove the changes related to change a url for its embed value. This is necessary
@@ -98,42 +105,52 @@
(mf/use-callback
(mf/deps @show-frame-thumbnail)
(fn []
(ts/raf
#(let [canvas-node (mf/ref-val frame-canvas-ref)
img-node (mf/ref-val frame-image-ref)]
(when (draw-thumbnail-canvas! canvas-node img-node)
(reset! image-url nil)
(when @show-frame-thumbnail
(reset! show-frame-thumbnail false))
;; If we don't have the thumbnail data saved (normally the first load) we update the data
;; when available
(when (not @thumbnail-data-ref)
(st/emit! (dwt/update-thumbnail page-id id) ))
(let [canvas-node (mf/ref-val frame-canvas-ref)
img-node (mf/ref-val frame-image-ref)]
(when (draw-thumbnail-canvas! canvas-node img-node)
(reset! image-url nil)
(when @show-frame-thumbnail
(reset! show-frame-thumbnail false))
;; If we don't have the thumbnail data saved (normally the first load) we update the data
;; when available
(when (not @thumbnail-data-ref)
(st/emit! (dwt/update-thumbnail page-id id) ))
(reset! render-frame? false))))))
(reset! render-frame? false)))))
generate-thumbnail
(mf/use-callback
(fn []
(let [node @node-ref
frame-html (dom/node->xml node)
(fn generate-thumbnail []
(try
;; When starting generating the canvas we mark it as not ready so its not send to back until
;; we have time to update it
(let [node @node-ref]
(if (dom/has-children? node)
;; The frame-content need to have children in order to generate the thumbnail
(let [style-node (dom/query (dm/str "#frame-container-" (:id shape) " style"))
{:keys [x y width height]} @shape-bb-ref
{:keys [x y width height]} @shape-bb-ref
viewbox (dm/str x " " y " " width " " height)
style-node (dom/query (dm/str "#frame-container-" (:id shape) " style"))
style-str (or (-> style-node dom/node->xml) "")
;; This is way faster than creating a node through the DOM API
svg-data
(dm/fmt "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"%\" width=\"%\" height=\"%\" fill=\"none\">% %</svg>"
viewbox
width
height
(if (some? style-node) (dom/node->xml style-node) "")
(dom/node->xml node))
svg-node
(-> (dom/make-node "http://www.w3.org/2000/svg" "svg")
(dom/set-property! "version" "1.1")
(dom/set-property! "viewBox" (dm/str x " " y " " width " " height))
(dom/set-property! "width" width)
(dom/set-property! "height" height)
(dom/set-property! "fill" "none")
(obj/set! "innerHTML" (dm/str style-str frame-html)))
img-src (-> svg-node dom/node->xml dom/svg->data-uri)]
blob (js/Blob. #js [svg-data] #js {:type "image/svg+xml;charset=utf-8"})
(reset! image-url img-src))))
img-src (.createObjectURL js/URL blob)]
(reset! image-url img-src))
;; Node not yet ready, we schedule a new generation
(ts/schedule generate-thumbnail)))
(catch :default e
(.error js/console e)))))
on-change-frame
(mf/use-callback
@@ -148,6 +165,9 @@
on-update-frame
(mf/use-callback
(fn []
(let [canvas-node (mf/ref-val frame-canvas-ref)]
(when (not= "false" (dom/get-data canvas-node "ready"))
(dom/set-data! canvas-node "ready" "false")))
(when (not @disable-ref?)
(reset! render-frame? true)
(reset! regenerate-thumbnail true))))
@@ -211,6 +231,16 @@
(.disconnect @observer-ref)
(reset! observer-ref nil)))))
;; When the thumbnail-data is empty we regenerate the thumbnail
(mf/use-effect
(mf/deps (:selrect shape) thumbnail-data)
(fn []
(let [{:keys [width height]} (:selrect shape)]
(p/then (wapi/empty-png-size width height)
(fn [data]
(when (<= (count thumbnail-data) (+ 100 (count data)))
(rx/push! updates-str :update)))))))
[on-load-frame-dom
@render-frame?
(mf/html
@@ -239,15 +269,26 @@
:width fixed-width
:height fixed-height
;; DEBUG
:style {:filter (when (debug? :thumbnails) "invert(1)")
:style {:filter (when (and (not (cf/check-browser? :safari)) (debug? :thumbnails)) "invert(1)")
:width "100%"
:height "100%"}}]]
;; Safari don't support filters so instead we add a rectangle around the thumbnail
(when (and (cf/check-browser? :safari) (debug? :thumbnails))
[:rect {:x (+ x 2)
:y (+ y 2)
:width (- width 4)
:height (- height 4)
:stroke "blue"
:stroke-width 2}])
(when (some? @image-url)
[:image {:ref frame-image-ref
:x x
:y y
:href @image-url
:width width
:height height
:on-load on-image-load}])])]))
[:foreignObject {:x x
:y y
:width width
:height height}
[:img {:ref frame-image-ref
:src @image-url
:width width
:height height
:on-load on-image-load}]])])]))

View File

@@ -188,6 +188,7 @@
(fn [editor]
(st/emit! (dwt/update-editor editor))
(when editor
(dom/add-class! (dom/get-element-by-class "public-DraftEditor-content") "mousetrap")
(.focus ^js editor))))
handle-return

View File

@@ -432,7 +432,8 @@
(or
(= uuid/zero id)
(and
(str/includes? (str/lower (:name shape)) (str/lower search))
(or (str/includes? (str/lower (:name shape)) (str/lower search))
(str/includes? (dm/str (:id shape)) (str/lower search)))
(or
(empty? filters)
(and

View File

@@ -17,13 +17,13 @@
(def layout-container-flex-attrs
[:layout ;; :flex, :grid in the future
:layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
:layout-flex-dir ;; :row, :row-reverse, :column, :column-reverse
:layout-gap-type ;; :simple, :multiple
:layout-gap ;; {:row-gap number , :column-gap number}
:layout-align-items ;; :start :end :center :stretch
:layout-justify-content ;; :start :center :end :space-between :space-around
:layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
:layout-wrap-type ;; :wrap, :no-wrap
:layout-wrap-type ;; :wrap, :nowrap
:layout-padding-type ;; :simple, :multiple
:layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
])
@@ -101,8 +101,8 @@
[:button.dir.tooltip.tooltip-bottom
{:class (dom/classnames :active (= saved-dir dir)
:row (= :row dir)
:reverse-row (= :reverse-row dir)
:reverse-column (= :reverse-column dir)
:row-reverse (= :row-reverse dir)
:column-reverse (= :column-reverse dir)
:column (= :column dir))
:key (dm/str "direction-" dir)
:alt (str/replace (str/capital (d/name dir)) "-" " ")
@@ -113,9 +113,9 @@
[{:keys [wrap-type set-wrap] :as props}]
[:*
[:button.tooltip.tooltip-bottom
{:class (dom/classnames :active (= wrap-type :no-wrap))
:alt "No-wrap"
:on-click #(set-wrap :no-wrap)
{:class (dom/classnames :active (= wrap-type :nowrap))
:alt "Nowrap"
:on-click #(set-wrap :nowrap)
:style {:padding 0}}
[:span.no-wrap i/minus]]
[:button.wrap.tooltip.tooltip-bottom
@@ -252,10 +252,10 @@
:on-click (fn [event]
(reset! gap-selected? :column-gap)
(dom/select-target event))
:on-change (partial set-gap (= :no-wrap wrap-type) :column-gap)
:on-change (partial set-gap (= :nowrap wrap-type) :column-gap)
:on-blur #(reset! gap-selected? :none)
:value (:column-gap gap-value)
:disabled (and (= :no-wrap wrap-type) is-col?)}]]
:disabled (and (= :nowrap wrap-type) is-col?)}]]
[:div.gap-row.tooltip.tooltip-bottom-left
{:alt "Row gap"}
@@ -266,10 +266,10 @@
:on-click (fn [event]
(reset! gap-selected? :row-gap)
(dom/select-target event))
:on-change (partial set-gap (= :no-wrap wrap-type) :row-gap)
:on-change (partial set-gap (= :nowrap wrap-type) :row-gap)
:on-blur #(reset! gap-selected? :none)
:value (:row-gap gap-value)
:disabled (and (= :no-wrap wrap-type) (not is-col?))}]]]])
:disabled (and (= :nowrap wrap-type) (not is-col?))}]]]])
(mf/defc layout-container-menu
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "multiple"]))]}
@@ -302,7 +302,7 @@
;; Flex-direction
saved-dir (:layout-flex-dir values)
is-col? (or (= :column saved-dir) (= :reverse-column saved-dir))
is-col? (or (= :column saved-dir) (= :column-reverse saved-dir))
set-direction
(fn [dir]
(st/emit! (dwsl/update-layout ids {:layout-flex-dir dir})))
@@ -386,7 +386,7 @@
[:div.btn-wrapper
[:div.direction
[:*
(for [dir [:row :reverse-row :column :reverse-column]]
(for [dir [:row :row-reverse :column :column-reverse]]
[:& direction-btn {:key (d/name dir)
:dir dir
:saved-dir saved-dir

View File

@@ -49,6 +49,8 @@
adv-blur-ref (mf/use-ref nil)
adv-spread-ref (mf/use-ref nil)
shadow-style (str (:style value))
remove-shadow-by-index
(fn [values index] (->> (d/enumerate values)
(filterv (fn [[idx _]] (not= idx index)))
@@ -116,12 +118,12 @@
;; :value (:blur value)}]
[:select.input-select
{:default-value (str (:style value))
{:default-value shadow-style
:on-change (fn [event]
(let [value (-> event dom/get-target dom/get-value d/read-string)]
(st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))}
[:option {:value ":drop-shadow"} (tr "workspace.options.shadow-options.drop-shadow")]
[:option {:value ":inner-shadow"} (tr "workspace.options.shadow-options.inner-shadow")]]
[:option {:value ":drop-shadow" :selected (when (= shadow-style ":drop-shadow") "selected")} (tr "workspace.options.shadow-options.drop-shadow")]
[:option {:value ":inner-shadow" :selected (when (= shadow-style ":inner-shadow") "selected")} (tr "workspace.options.shadow-options.inner-shadow")]]
[:div.element-set-actions
[:div.element-set-actions-button {:on-click (toggle-visibility index)}

View File

@@ -25,63 +25,7 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(def text-typography-attrs
[:typography-ref-id
:typography-ref-file])
(def text-fill-attrs
[:fill-color
:fill-opacity
:fill-color-ref-id
:fill-color-ref-file
:fill-color-gradient])
(def text-font-attrs
[:font-id
:font-family
:font-variant-id
:font-size
:font-weight
:font-style])
(def text-align-attrs
[:text-align])
(def text-direction-attrs
[:text-direction])
(def text-spacing-attrs
[:line-height
:letter-spacing])
(def text-valign-attrs
[:vertical-align])
(def text-decoration-attrs
[:text-decoration])
(def text-transform-attrs
[:text-transform])
(def shape-attrs
[:grow-type])
(def root-attrs text-valign-attrs)
(def paragraph-attrs
(d/concat-vec
text-align-attrs
text-direction-attrs))
(def text-attrs
(d/concat-vec
text-typography-attrs
text-font-attrs
text-spacing-attrs
text-decoration-attrs
text-transform-attrs))
(def attrs (d/concat-set shape-attrs root-attrs paragraph-attrs text-attrs))
(mf/defc text-align-options
[{:keys [values on-change on-blur] :as props}]
@@ -237,20 +181,9 @@
(mf/use-callback
(mf/deps values)
(fn [id attrs]
(st/emit! (dwt/save-font (-> (merge txt/default-text-attrs values attrs)
(select-keys text-attrs))))
(let [attrs (select-keys attrs root-attrs)]
(when-not (empty? attrs)
(st/emit! (dwt/update-root-attrs {:id id :attrs attrs}))))
(let [attrs (select-keys attrs paragraph-attrs)]
(when-not (empty? attrs)
(st/emit! (dwt/update-paragraph-attrs {:id id :attrs attrs}))))
(let [attrs (select-keys attrs text-attrs)]
(when-not (empty? attrs)
(st/emit! (dwt/update-text-attrs {:id id :attrs attrs}))))))
(st/emit! (dwt/save-font (-> (merge txt/default-text-attrs values attrs)
(select-keys dwt/text-attrs)))
(dwt/update-attrs id attrs))))
on-change
(mf/use-callback
@@ -279,9 +212,9 @@
(fn [_]
(let [set-values (-> (d/without-nils values)
(select-keys
(d/concat-vec text-font-attrs
text-spacing-attrs
text-transform-attrs)))
(d/concat-vec dwt/text-font-attrs
dwt/text-spacing-attrs
dwt/text-transform-attrs)))
typography (merge txt/default-typography set-values)
typography (generate-typography-name typography)
id (uuid/next)]

View File

@@ -11,6 +11,7 @@
[app.common.geom.shapes :as gsh]
[app.common.pages.common :as cpc]
[app.common.text :as txt]
[app.main.data.workspace.texts :as dwt]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-attrs blur-menu]]
@@ -158,7 +159,7 @@
:shadow shadow-attrs
:blur blur-attrs
:stroke stroke-attrs
:text ot/attrs
:text dwt/attrs
:exports exports-attrs
:layout-container layout-container-flex-attrs
:layout-item layout-item-attrs})
@@ -248,16 +249,15 @@
(dissoc :content)))
(defn- is-bool-descendant?
[shape all-shapes selected-shape-ids]
[[_ shape] objects selected-shape-ids]
(let [parent-id (:parent-id shape)
parent (->> all-shapes
(filter #(= (:id %) parent-id))
first)]
parent (get objects parent-id)]
(cond
(nil? shape) false ;; failsafe
(some #{(:id shape)} selected-shape-ids) false ;; if it is one of the selected shapes, it is considerer not a bool descendant
(contains? selected-shape-ids (:id shape)) false ;; if it is one of the selected shapes, it is considerer not a bool descendant
(= :bool (:type parent)) true ;; if its parent is of type bool, it is a bool descendant
:else (is-bool-descendant? parent all-shapes selected-shape-ids)))) ;; else, check its parent
:else (recur [parent-id parent] objects selected-shape-ids)))) ;; else, check its parent
(mf/defc options
{::mf/wrap [#(mf/memo' % (mf/check-props ["shapes" "shapes-with-children" "page-id" "file-id"]))]
@@ -267,8 +267,13 @@
shapes-with-children (unchecked-get props "shapes-with-children")
;; remove children from bool shapes
shape-ids (map :id shapes)
shapes-with-children (filter #(not (is-bool-descendant? % shapes-with-children shape-ids)) shapes-with-children)
shape-ids (into #{} (map :id) shapes)
objects (->> shapes-with-children (group-by :id) (d/mapm (fn [_ v] (first v))))
objects
(into {}
(filter #(not (is-bool-descendant? % objects shape-ids)))
objects)
workspace-modifiers (mf/deref refs/workspace-modifiers)
shapes (map #(gsh/transform-shape % (get-in workspace-modifiers [(:id %) :modifiers])) shapes)
@@ -276,7 +281,7 @@
page-id (unchecked-get props "page-id")
file-id (unchecked-get props "file-id")
shared-libs (unchecked-get props "shared-libs")
objects (->> shapes-with-children (group-by :id) (d/mapm (fn [_ v] (first v))))
show-caps (some #(and (= :path (:type %)) (gsh/open-path? %)) shapes)
;; Selrect/points only used for measures and it's the one that changes the most. We separate it

View File

@@ -7,7 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.shapes.text
(:require
[app.common.data :as d]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.texts :as dwt :refer [text-fill-attrs root-attrs paragraph-attrs text-attrs]]
[app.main.refs :as refs]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]]
@@ -19,7 +19,7 @@
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]]
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]]
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]]
[app.main.ui.workspace.sidebar.options.menus.text :refer [text-menu text-fill-attrs root-attrs paragraph-attrs text-attrs]]
[app.main.ui.workspace.sidebar.options.menus.text :refer [text-menu]]
[rumext.v2 :as mf]))
(mf/defc options

View File

@@ -106,6 +106,7 @@
alt? (mf/use-state false)
mod? (mf/use-state false)
space? (mf/use-state false)
z? (mf/use-state false)
cursor (mf/use-state (utils/get-cursor :pointer-inner))
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
@@ -154,9 +155,9 @@
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
mode-inspect? (= options-mode :inspect)
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect)
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?)
on-context-menu (actions/on-context-menu hover hover-ids workspace-read-only?)
on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition workspace-read-only?)
on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition drawing-tool z? workspace-read-only?)
on-drag-enter (actions/on-drag-enter)
on-drag-over (actions/on-drag-over)
on-drop (actions/on-drop file)
@@ -212,11 +213,11 @@
(hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport? workspace-read-only?)
(hooks/setup-viewport-size viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? workspace-read-only?)
(hooks/setup-keyboard alt? mod? space?)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? z? workspace-read-only?)
(hooks/setup-keyboard alt? mod? space? z?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
(hooks/setup-viewport-modifiers modifiers base-objects)
(hooks/setup-shortcuts node-editing? drawing-path?)
(hooks/setup-shortcuts node-editing? drawing-path? text-editing?)
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
[:div.viewport
@@ -505,6 +506,7 @@
(when show-prototypes?
[:& interactions/interactions
{:selected selected
:page-id page-id
:zoom zoom
:objects objects-modified
:current-transform transform

View File

@@ -34,8 +34,7 @@
(defn on-mouse-down
[{:keys [id blocked hidden type]} selected edition drawing-tool text-editing?
node-editing? drawing-path? create-comment? space? panning
workspace-read-only?]
node-editing? drawing-path? create-comment? space? panning workspace-read-only?]
(mf/use-callback
(mf/deps id blocked hidden type selected edition drawing-tool text-editing?
node-editing? drawing-path? create-comment? @space?
@@ -140,9 +139,9 @@
(reset! frame-hover nil))))
(defn on-click
[hover selected edition drawing-path? drawing-tool space? selrect]
[hover selected edition drawing-path? drawing-tool space? selrect z?]
(mf/use-callback
(mf/deps @hover selected edition drawing-path? drawing-tool @space? selrect)
(mf/deps @hover selected edition drawing-path? drawing-tool @space? selrect @z?)
(fn [event]
(when (and (nil? selrect)
(or (dom/class? (dom/get-target event) "viewport-controls")
@@ -151,7 +150,9 @@
shift? (kbd/shift? event)
alt? (kbd/alt? event)
meta? (kbd/meta? event)
hovering? (some? @hover)]
hovering? (some? @hover)
raw-pt (dom/get-client-position event)
pt (uwvv/point->viewport raw-pt)]
(st/emit! (ms/->MouseEvent :click ctrl? shift? alt? meta?))
(when (and hovering?
@@ -159,42 +160,52 @@
(not edition)
(not drawing-path?)
(not drawing-tool))
(st/emit! (dw/select-shape (:id @hover) shift?))))))))
(st/emit! (dw/select-shape (:id @hover) shift?)))
(when (and @z?
(not @space?)
(not edition)
(not drawing-path?)
(not drawing-tool))
(if alt?
(st/emit! (dw/decrease-zoom pt))
(st/emit! (dw/increase-zoom pt)))))))))
(defn on-double-click
[hover hover-ids drawing-path? objects edition workspace-read-only?]
[hover hover-ids drawing-path? objects edition drawing-tool z? workspace-read-only?]
(mf/use-callback
(mf/deps @hover @hover-ids drawing-path? edition workspace-read-only?)
(mf/deps @hover @hover-ids drawing-path? edition drawing-tool @z? workspace-read-only?)
(fn [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
alt? (kbd/alt? event)
meta? (kbd/meta? event)
(when-not @z?
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
alt? (kbd/alt? event)
meta? (kbd/meta? event)
{:keys [id type] :as shape} (or @hover (get objects (first @hover-ids)))
{:keys [id type] :as shape} (or @hover (get objects (first @hover-ids)))
editable? (contains? #{:text :rect :path :image :circle} type)]
editable? (contains? #{:text :rect :path :image :circle} type)]
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?))
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?))
;; Emit asynchronously so the double click to exit shapes won't break
(timers/schedule
(fn []
(when (and (not drawing-path?) shape)
(cond
(and editable? (not= id edition) (not workspace-read-only?))
(st/emit! (dw/select-shape id)
(dw/start-editing-selected))
(timers/schedule
(fn []
(when (and (not drawing-path?) shape)
(cond
(and editable? (not= id edition) (not workspace-read-only?))
(st/emit! (dw/select-shape id)
(dw/start-editing-selected))
:else
(let [;; We only get inside childrens of the hovering shape
hover-ids (->> @hover-ids (filter (partial cph/is-child? objects id)))
selected (get objects (first hover-ids))]
(when (some? selected)
(reset! hover selected)
(st/emit! (dw/select-shape (:id selected)))))))))))))
:else
(let [;; We only get inside childrens of the hovering shape
hover-ids (->> @hover-ids (filter (partial cph/is-child? objects id)))
selected (get objects (first hover-ids))]
(when (some? selected)
(reset! hover selected)
(st/emit! (dw/select-shape (:id selected))))))))))))))
(defn on-context-menu
[hover hover-ids workspace-read-only?]

View File

@@ -25,6 +25,7 @@
[props]
(let [objects (unchecked-get props "objects")
zoom (unchecked-get props "zoom")
selected-shapes (unchecked-get props "selected-shapes")
hover-top-frame-id (unchecked-get props "hover-top-frame-id")
@@ -37,10 +38,19 @@
(when (and shape (:layout shape))
(let [children (->> (cph/get-immediate-children objects (:id shape))
(remove :hidden))
layout-bounds (gsl/layout-content-bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points])) shape children)]
bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points]))
layout-bounds (gsl/layout-content-bounds bounds shape children)
layout-points (flatten (gsl/layout-content-points bounds shape children))]
[:g.debug-layout {:pointer-events "none"}
[:polygon {:points (->> layout-bounds (map #(dm/fmt "%, %" (:x %) (:y %))) (str/join " "))
:style {:stroke "red" :fill "none"}}]]))))
:style {:stroke "red" :fill "none"}}]
[:*
(for [p layout-points]
[:circle {:cx (:x p)
:cy (:y p)
:r (/ 4 zoom)
:style {:fill "red"}}])]]))))
(mf/defc debug-layout-lines
"Debug component to show the auto-layout drop areas"

View File

@@ -16,6 +16,7 @@
[app.main.data.workspace :as dw]
[app.main.data.workspace.path.shortcuts :as psc]
[app.main.data.workspace.shortcuts :as wsc]
[app.main.data.workspace.text.shortcuts :as tsc]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.ui.hooks :as hooks]
@@ -71,13 +72,19 @@
;; We schedule the event so it fires after `initialize-page` event
(timers/schedule #(st/emit! (dw/initialize-viewport size)))))))
(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing? workspace-read-only?]
(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing? z? workspace-read-only?]
(mf/use-effect
(mf/deps @cursor @alt? @mod? @space? panning drawing-tool drawing-path? path-editing? workspace-read-only?)
(mf/deps @cursor @alt? @mod? @space? panning drawing-tool drawing-path? path-editing? z? workspace-read-only?)
(fn []
(let [show-pen? (or (= drawing-tool :path)
(and drawing-path?
(not= drawing-tool :curve)))
show-zoom? (and @z?
(not @space?)
(not @mod?)
(not drawing-path?)
(not drawing-tool))
new-cursor
(cond
(and @mod? @space?) (utils/get-cursor :zoom)
@@ -86,6 +93,8 @@
(= drawing-tool :frame) (utils/get-cursor :create-artboard)
(= drawing-tool :rect) (utils/get-cursor :create-rectangle)
(= drawing-tool :circle) (utils/get-cursor :create-ellipse)
(and show-zoom? (not @alt?)) (utils/get-cursor :zoom-in)
(and show-zoom? @alt?) (utils/get-cursor :zoom-out)
show-pen? (utils/get-cursor :pen)
(= drawing-tool :curve) (utils/get-cursor :pencil)
drawing-tool (utils/get-cursor :create-shape)
@@ -98,10 +107,11 @@
(when (not= @cursor new-cursor)
(reset! cursor new-cursor))))))
(defn setup-keyboard [alt? mod? space?]
(defn setup-keyboard [alt? mod? space? z?]
(hooks/use-stream ms/keyboard-alt #(reset! alt? %))
(hooks/use-stream ms/keyboard-mod #(reset! mod? %))
(hooks/use-stream ms/keyboard-space #(reset! space? %)))
(hooks/use-stream ms/keyboard-space #(reset! space? %))
(hooks/use-stream ms/keyboard-z #(reset! z? %)))
(defn group-empty-space?
"Given a group `group-id` check if `hover-ids` contains any of its children. If it doesn't means
@@ -233,6 +243,7 @@
(filter #(or (empty? focus) (cp/is-in-focus? objects focus %)))
(first)
(get objects))]
(reset! hover hover-shape)
(reset! hover-ids ids)
(reset! hover-top-frame-id (ctt/top-nested-frame objects (deref last-point-ref))))))))
@@ -328,11 +339,15 @@
;; this shortcuts outside the viewport?
(defn setup-shortcuts
[path-editing? drawing-path?]
[path-editing? drawing-path? text-editing?]
(hooks/use-shortcuts ::workspace wsc/shortcuts)
(mf/use-effect
(mf/deps path-editing? drawing-path?)
(fn []
(when (or drawing-path? path-editing?)
(st/emit! (dsc/push-shortcuts ::path psc/shortcuts))
#(st/emit! (dsc/pop-shortcuts ::path))))))
(cond
(or drawing-path? path-editing?)
(do (st/emit! (dsc/push-shortcuts ::path psc/shortcuts))
#(st/emit! (dsc/pop-shortcuts ::path)))
text-editing?
(do (st/emit! (dsc/push-shortcuts ::text tsc/shortcuts))
#(st/emit! (dsc/pop-shortcuts ::text)))))))

View File

@@ -9,11 +9,16 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.pages.helpers :as cph]
[app.common.types.shape.interactions :as ctsi]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.render :as render]
[app.main.store :as st]
[app.main.ui.context :as muc]
[app.main.ui.shapes.embed :as embed]
[app.main.ui.workspace.viewport.outline :refer [outline]]
[app.util.dom :as dom]
[cuerdas.core :as str]
@@ -214,7 +219,7 @@
(mf/defc overlay-marker
[{:keys [index orig-shape dest-shape position objects hover-disabled?] :as props}]
[{:keys [page-id index orig-shape dest-shape position objects hover-disabled?] :as props}]
(let [start-move-position
(fn [_]
(st/emit! (dw/start-move-overlay-pos index)))]
@@ -226,13 +231,28 @@
width (:width dest-shape)
height (:height dest-shape)
dest-x (:x dest-shape)
dest-y (:y dest-shape)]
dest-y (:y dest-shape)
shape-wrapper
(mf/use-memo
(mf/deps objects)
#(render/shape-wrapper-factory objects))
dest-shape-id (:id dest-shape)
thumbnail-data-ref (mf/use-memo (mf/deps page-id dest-shape-id) #(refs/thumbnail-frame-data page-id dest-shape-id))
thumbnail-data (mf/deref thumbnail-data-ref)
dest-shape (cond-> dest-shape
(some? thumbnail-data)
(assoc :thumbnail thumbnail-data))]
[:g {:on-mouse-down start-move-position
:on-mouse-enter #(reset! hover-disabled? true)
:on-mouse-leave #(reset! hover-disabled? false)}
[:use {:href (str "#shape-" (:id dest-shape))
:x (- marker-x dest-x)
:y (- marker-y dest-y)}]
[:g {:transform (gmt/translate-matrix (gpt/point (- marker-x dest-x) (- marker-y dest-y))) }
[:& (mf/provider muc/render-thumbnails) {:value true}
[:& (mf/provider embed/context) {:value false}
[:& shape-wrapper {:shape dest-shape}]]]]
[:path {:stroke "var(--color-primary)"
:fill "var(--color-black)"
:fill-opacity 0.5
@@ -251,7 +271,7 @@
:fill "var(--color-primary)"}]]))))
(mf/defc interactions
[{:keys [current-transform objects zoom selected hover-disabled?] :as props}]
[{:keys [current-transform objects zoom selected hover-disabled? page-id] :as props}]
(let [active-shapes (into []
(comp (filter #(seq (:interactions %))))
(vals objects))
@@ -323,13 +343,15 @@
(= (:overlay-pos-type interaction) :manual))
(if (and (some? move-overlay-to)
(= move-overlay-index index))
[:& overlay-marker {:index index
[:& overlay-marker {:page-id page-id
:index index
:orig-shape shape
:dest-shape dest-shape
:position move-overlay-to
:objects objects
:hover-disabled? hover-disabled?}]
[:& overlay-marker {:index index
[:& overlay-marker {:page-id page-id
:index index
:orig-shape shape
:dest-shape dest-shape
:position (:overlay-position interaction)
@@ -343,4 +365,3 @@
:shape shape
:selected selected
:zoom zoom}])))]]))

View File

@@ -35,11 +35,11 @@
(or (ex/ignoring (upf/format-path (:content shape)))
"")))
{:keys [x y width height selrect rx ry]} shape
{:keys [x y width height selrect]} shape
border-radius-attrs (.-d (attrs/extract-border-radius shape))
border-radius-attrs (attrs/extract-border-radius shape)
path? (some? border-radius-attrs)
path? (some? (.-d border-radius-attrs))
outline-type (case (:type shape)
:circle "ellipse"
@@ -67,9 +67,9 @@
:y (:y selrect)
:width (:width selrect)
:height (:height selrect)
:rx rx
:ry ry
:d border-radius-attrs})]
:rx (.-rx border-radius-attrs)
:ry (.-ry border-radius-attrs)
:d (.-d border-radius-attrs)})]
[:> outline-type (map->obj (merge common props))]))

View File

@@ -31,7 +31,7 @@
:duplicate cur/duplicate
:zoom cur/zoom
:zoom-in cur/zoom-in
:zooom-out cur/zoom-out
:zoom-out cur/zoom-out
cur/pointer-inner))
;; Ensure that the label has always the same font

View File

@@ -7,6 +7,8 @@
(ns app.util.code-gen
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
[app.main.ui.formats :as fmt]
[app.util.color :as uc]
@@ -15,7 +17,7 @@
(defn shadow->css [shadow]
(let [{:keys [style offset-x offset-y blur spread]} shadow
css-color (uc/color->background (:color shadow))]
(str
(dm/str
(if (= style :inner-shadow) "inset " "")
(str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color))))
@@ -25,40 +27,78 @@
(str/fmt "%spx" row-gap)
(str/fmt "%spx %spx" row-gap column-gap)))
(defn fill-color->background
[fill]
(uc/color->background {:color (:fill-color fill)
:opacity (:fill-opacity fill)
:gradient (:fill-color-gradient fill)}))
(defn format-fill-color [_ shape]
(let [color {:color (:fill-color shape)
:opacity (:fill-opacity shape)
:gradient (:fill-color-gradient shape)}]
(uc/color->background color)))
(let [fills (:fills shape)
first-fill (first fills)
colors (if (> (count fills) 1)
(map (fn [fill]
(let [color (fill-color->background fill)]
(if (some? (:fill-color-gradient fill))
color
(str/format "linear-gradient(%s,%s)" color color))))
(:fills shape))
[(fill-color->background first-fill)])]
(str/join ", " colors)))
(defn format-stroke [_ shape]
(let [width (:stroke-width shape)
style (let [style (:stroke-style shape)]
(when (keyword? style) (name style)))
color {:color (:stroke-color shape)
:opacity (:stroke-opacity shape)
:gradient (:stroke-color-gradient shape)}]
(when-not (= :none (:stroke-style shape))
(let [first-stroke (first (:strokes shape))
width (:stroke-width first-stroke)
style (let [style (:stroke-style first-stroke)]
(when (keyword? style) (d/name style)))
color {:color (:stroke-color first-stroke)
:opacity (:stroke-opacity first-stroke)
:gradient (:stroke-color-gradient first-stroke)}]
(when-not (= :none (:stroke-style first-stroke))
(str/format "%spx %s %s" width style (uc/color->background color)))))
(def styles-data
{:layout {:props [:width :height :x :y :radius :rx :r1]
(defn format-position [_ shape]
(cond
(cph/frame-shape? shape) "relative"
(empty? (:flex-items shape)) "absolute"
:else "static"))
(defn get-size
[type values]
(let [value (cond
(number? values) values
(string? values) values
(type values) (type values)
:else (type (:selrect values)))]
(if (= :width type)
(fmt/format-size :width value values)
(fmt/format-size :heigth value values))))
(defn styles-data
[shape]
{:position {:props [:type]
:to-prop {:type "position"}
:format {:type format-position}}
:layout {:props (if (empty? (:flex-items shape))
[:width :height :x :y :radius :rx :r1]
[:width :height :radius :rx :r1])
:to-prop {:x "left"
:y "top"
:rotation "transform"
:rx "border-radius"
:r1 "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)
:width (partial fmt/format-size :width)
:height (partial fmt/format-size :height)}
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)
:width #(get-size :width %)
:height #(get-size :height %)}
:multi {:r1 [:r1 :r2 :r3 :r4]}}
:fill {:props [:fill-color :fill-color-gradient]
:to-prop {:fill-color "background" :fill-color-gradient "background"}
:format {:fill-color format-fill-color :fill-color-gradient format-fill-color}}
:stroke {:props [:stroke-style]
:to-prop {:stroke-style "border"}
:format {:stroke-style format-stroke}}
:fill {:props [:fills]
:to-prop {:fills (if (> (count (:fills shape)) 1) "background-image" "background")}
:format {:fills format-fill-color}}
:stroke {:props [:strokes]
:to-prop {:strokes "border"}
:format {:strokes format-stroke}}
:shadow {:props [:shadow]
:to-prop {:shadow :box-shadow}
:format {:shadow #(str/join ", " (map shadow->css %1))}}
@@ -66,8 +106,8 @@
:to-prop {:blur "filter"}
:format {:blur #(str/fmt "blur(%spx)" (:value %))}}
:layout-flex {:props [:layout
:layout-align-items
:layout-flex-dir
:layout-align-items
:layout-justify-content
:layout-gap
:layout-padding
@@ -79,32 +119,34 @@
:layout-wrap-type "flex-wrap"
:layout-gap "gap"
:layout-padding "padding"}
:format {:layout name
:layout-flex-dir name
:layout-align-items name
:layout-justify-content name
:layout-wrap-type name
:format {:layout d/name
:layout-flex-dir d/name
:layout-align-items d/name
:layout-justify-content d/name
:layout-wrap-type d/name
:layout-gap format-gap
:layout-padding fmt/format-padding}}})
(def style-text
{:props [:fill-color
{:props [:fills
:font-family
:font-style
:font-size
:font-weight
:line-height
:letter-spacing
:text-decoration
:text-transform]
:to-prop {:fill-color "color"}
:format {:font-family #(str "'" % "'")
:font-style #(str "'" % "'")
:font-size #(str % "px")
:line-height #(str % "px")
:letter-spacing #(str % "px")
:text-decoration name
:text-transform name
:fill-color format-fill-color}})
:to-prop {:fills "color"}
:format {:font-family #(dm/str "'" % "'")
:font-style #(dm/str %)
:font-size #(dm/str % "px")
:font-weight #(dm/str %)
:line-height #(dm/str %)
:letter-spacing #(dm/str % "px")
:text-decoration d/name
:text-transform d/name
:fills format-fill-color}})
(def layout-flex-item-params
{:props [:layout-item-margin
@@ -120,11 +162,30 @@
:layout-item-min-w "min-width"
:layout-item-align-self "align-self"}
:format {:layout-item-margin fmt/format-margin
:layout-item-max-h #(str % "px")
:layout-item-min-h #(str % "px")
:layout-item-max-w #(str % "px")
:layout-item-min-w #(str % "px")
:layout-item-align-self name}})
:layout-item-max-h #(dm/str % "px")
:layout-item-min-h #(dm/str % "px")
:layout-item-max-w #(dm/str % "px")
:layout-item-min-w #(dm/str % "px")
:layout-item-align-self d/name}})
(def layout-align-content
{:props [:layout-align-content]
:to-prop {:layout-align-content "align-content"}
:format {:layout-align-content d/name}})
(defn get-specific-value
[values prop]
(let [result (if (get values prop)
(get values prop)
(get (:selrect values) prop))
result (if (= :width prop)
(get-size :width values)
result)
result (if (= :height prop)
(get-size :height values)
result)]
result))
(defn generate-css-props
([values properties]
@@ -150,20 +211,20 @@
get-value (fn [prop]
(if-let [props (prop multi)]
(map #(get values %) props)
(get values prop)))
(get-specific-value values prop)))
null? (fn [value]
(if (coll? value)
(every? #(or (nil? %) (= % 0)) value)
(or (nil? value) (= value 0))))
default-format (fn [value] (str (fmt/format-pixels value)))
default-format (fn [value] (dm/str (fmt/format-pixels value)))
format-property (fn [prop]
(let [css-prop (or (prop to-prop) (name prop))
(let [css-prop (or (prop to-prop) (d/name prop))
format-fn (or (prop format) default-format)
css-val (format-fn (get-value prop) values)]
(when css-val
(str
(dm/str
(str/repeat " " tab-size)
(str/fmt "%s: %s;" css-prop css-val)))))]
@@ -178,20 +239,19 @@
;; it will come with a vector of flex-items if any.
;; If there are none it will continue as usual.
flex-items (:flex-items shape)
props (->> styles-data vals (mapcat :props))
to-prop (->> styles-data vals (map :to-prop) (reduce merge))
format (->> styles-data vals (map :format) (reduce merge))
multi (->> styles-data vals (map :multi) (reduce merge))
props (if (seq flex-items)
(concat props (:props layout-flex-item-params))
props)
to-prop (if (seq flex-items)
(merge to-prop (:to-prop layout-flex-item-params))
to-prop)
format (if (seq flex-items)
(merge format (:format layout-flex-item-params))
format)]
props (->> (styles-data shape) vals (mapcat :props))
to-prop (->> (styles-data shape) vals (map :to-prop) (reduce merge))
format (->> (styles-data shape) vals (map :format) (reduce merge))
multi (->> (styles-data shape) vals (map :multi) (reduce merge))
props (cond-> props
(seq flex-items) (concat (:props layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (concat (:props layout-align-content)))
to-prop (cond-> to-prop
(seq flex-items) (merge (:to-prop layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (merge (:to-prop layout-align-content)))
format (cond-> format
(seq flex-items) (merge (:format layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (merge (:format layout-align-content)))]
(generate-css-props shape props {:to-prop to-prop
:format format
:multi multi
@@ -208,40 +268,48 @@
(defn parse-style-text-blocks
[node attrs]
(letfn
[(rec-style-text-map [acc node style]
(let [node-style (merge style (select-keys node attrs))
head (or (-> acc first) [{} ""])
[head-style head-text] head
[(rec-style-text-map [acc node style]
(let [node-style (merge style (select-keys node attrs))
head (or (-> acc first) [{} ""])
[head-style head-text] head
new-acc
(cond
(:children node)
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
new-acc
(cond
(:children node)
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
(not= head-style node-style)
(cons [node-style (:text node "")] acc)
(not= head-style node-style)
(cons [node-style (:text node "")] acc)
:else
(cons [node-style (str head-text "" (:text node))] (rest acc)))
:else
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
;; We add an end-of-line when finish a paragraph
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]
(cons [hs (str ht "\n")] (rest new-acc)))
new-acc)]
new-acc))]
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]
(cons [hs (dm/str ht "\n")] (rest new-acc)))
new-acc)]
new-acc))]
(-> (rec-style-text-map [] node {})
reverse)))
(defn text->properties [shape]
(let [text-shape-style (select-keys styles-data [:layout :shadow :blur])
(let [flex-items (:flex-items shape)
text-shape-style (select-keys (styles-data shape) [:layout :shadow :blur])
shape-props (->> text-shape-style vals (mapcat :props))
shape-to-prop (->> text-shape-style vals (map :to-prop) (reduce merge))
shape-format (->> text-shape-style vals (map :format) (reduce merge))
shape-props (cond-> shape-props
(seq flex-items) (concat (:props layout-flex-item-params)))
shape-to-prop (cond-> shape-to-prop
(seq flex-items) (merge (:to-prop layout-flex-item-params)))
shape-format (cond-> shape-format
(seq flex-items) (merge (:format layout-flex-item-params)))
text-values (->> (search-text-attrs
(:content shape)
(conj (:props style-text) :fill-color-gradient :fill-opacity))
@@ -257,16 +325,13 @@
(:props style-text)
{:to-prop (:to-prop style-text)
:format (:format style-text)
:tab-size 2})]))
)
:tab-size 2})])))
(defn generate-css [shape]
(let [name (:name shape)
properties (if (= :text (:type shape))
(text->properties shape)
(shape->properties shape))
selector (str/css-selector name)
selector (if (str/starts-with? selector "-") (subs selector 1) selector)]
(str/join "\n" [(str/fmt "/* %s */" name)

View File

@@ -264,12 +264,14 @@
(defn append-child!
[^js el child]
(when (some? el)
(.appendChild ^js el child)))
(.appendChild ^js el child))
el)
(defn remove-child!
[^js el child]
(when (some? el)
(.removeChild ^js el child)))
(.removeChild ^js el child))
el)
(defn get-first-child
[^js el]
@@ -476,7 +478,11 @@
(defn get-data [^js node ^string attr]
(when (some? node)
(.getAttribute node (str "data-" attr))))
(.getAttribute node (dm/str "data-" attr))))
(defn set-data! [^js node ^string attr value]
(when (some? node)
(.setAttribute node (dm/str "data-" attr) (dm/str value))))
(defn set-attribute! [^js node ^string attr value]
(when (some? node)
@@ -639,3 +645,13 @@
{:ascent (.-fontBoundingBoxAscent measure)
:descent (.-fontBoundingBoxDescent measure)}))
(defn clone-node
([^js node]
(clone-node node true))
([^js node deep?]
(.cloneNode node deep?)))
(defn has-children?
[^js node]
(> (-> node .-children .-length) 0))

View File

@@ -6,13 +6,19 @@
(ns app.util.keyboard
(:require
[app.config :as cfg]))
[app.config :as cfg]
[cuerdas.core :as str]))
(defn is-key?
[^string key]
(fn [^js e]
(= (.-key e) key)))
(defn is-key-ignore-case?
[^string key]
(fn [^js e]
(= (str/upper (.-key e)) (str/upper key))))
(defn ^boolean alt?
[^js event]
(.-altKey event))
@@ -38,6 +44,7 @@
(def esc? (is-key? "Escape"))
(def enter? (is-key? "Enter"))
(def space? (is-key? " "))
(def z? (is-key-ignore-case? "z"))
(def up-arrow? (is-key? "ArrowUp"))
(def down-arrow? (is-key? "ArrowDown"))
(def left-arrow? (is-key? "ArrowLeft"))

View File

@@ -11,7 +11,8 @@
[app.common.logging :as log]
[app.util.object :as obj]
[beicon.core :as rx]
[cuerdas.core :as str]))
[cuerdas.core :as str]
[promesa.core :as p]))
(log/set-level! :warn)
@@ -144,3 +145,24 @@
(.observe ^js obs node)
(fn []
(.disconnect ^js obs))))))
(defn empty-png-size*
[width height]
(p/create
(fn [resolve reject]
(try
(let [canvas (.createElement js/document "canvas")
_ (set! (.-width canvas) width)
_ (set! (.-height canvas) height)
_ (set! (.-background canvas) "white")
canvas-context (.getContext canvas "2d")]
(.fillRect canvas-context 0 0 width height)
(.toBlob canvas
(fn [blob]
(->> (read-file-as-data-url blob)
(rx/catch (fn [err] (reject err)))
(rx/subs (fn [result] (resolve result)))))))
(catch :default e (reject e))))))
(def empty-png-size (memoize empty-png-size*))

View File

@@ -131,9 +131,7 @@
(defn ^:export ^boolean debug?
[option]
(if *assert*
(boolean (@*debug* option))
false))
(boolean (@*debug* option)))
(defn ^:export toggle-debug [name] (let [option (keyword name)]
(if (debug? option)
@@ -269,6 +267,30 @@
[]
(dump-selected' @st/state))
(defn ^:export parent
[]
(let [state @st/state
page-id (get state :current-page-id)
objects (get-in state [:workspace-data :pages-index page-id :objects])
selected (first (get-in state [:workspace-local :selected]))
parent-id (get-in objects [selected :parent-id])
parent (get objects parent-id)]
(when parent
(prn (str (:name parent) " - " (:id parent))))
nil))
(defn ^:export frame
[]
(let [state @st/state
page-id (get state :current-page-id)
objects (get-in state [:workspace-data :pages-index page-id :objects])
selected (first (get-in state [:workspace-local :selected]))
frame-id (get-in objects [selected :frame-id])
frame (get objects frame-id)]
(when frame
(prn (str (:name frame) " - " (:id frame))))
nil))
(defn dump-tree'
([state] (dump-tree' state false false))
([state show-ids] (dump-tree' state show-ids false))

View File

@@ -85,10 +85,6 @@ msgstr "OpenID"
msgid "auth.new-password"
msgstr "اكتب كلمة مرور جديدة"
#: src/app/main/ui/auth/register.cljs
msgid "auth.newsletter-subscription"
msgstr "أوافق على الاشتراك في قائمة Penpot البريدية."
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.notifications.invalid-token-error"
msgstr "رمز الاسترداد غير صالح."
@@ -190,9 +186,6 @@ msgstr "خذ رابطا إلكتروني"
msgid "common.share-link.link-copied-success"
msgstr "تم نسخ الرابط بنجاح"
msgid "common.share-link.link-deleted-success"
msgstr "تم حذف الرابط بنجاح"
msgid "common.share-link.manage-ops"
msgstr "إدارة التصاريح"
@@ -299,9 +292,6 @@ msgstr "تنزيل ملف Penpot (.penpot)"
msgid "dashboard.download-standard-file"
msgstr "تنزيل ملف قياسي (.svg + .json)"
msgid "dashboard.draft-title"
msgstr "مسودة"
#: src/app/main/ui/dashboard/project_menu.cljs,
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.duplicate"
@@ -311,10 +301,6 @@ msgstr "تكرير"
msgid "dashboard.duplicate-multi"
msgstr "تكرير %s الملفات"
#: src/app/main/ui/dashboard/grid.cljs
msgid "dashboard.empty-files"
msgstr "لا يزال لديك 0 ملفات هنا"
#: src/app/main/ui/dashboard/grid.cljs
msgid "dashboard.empty-placeholder-drafts"
msgstr ""
@@ -360,9 +346,6 @@ msgstr "لا يوجد عناصر بإعدادت التصدير."
msgid "dashboard.export-shapes.title"
msgstr "إختيار التصدير"
msgid "dashboard.export-single"
msgstr "تصدير الملف"
msgid "dashboard.export-standard-multi"
msgstr "تحميل %s ملفات أساسية (.svg + .json)"
@@ -530,10 +513,6 @@ msgstr "+ مشروع جديد"
msgid "dashboard.new-project-prefix"
msgstr "مشروع جديد"
#: src/app/main/ui/settings/profile.cljs
msgid "dashboard.newsletter-msg"
msgstr "أرسل لي الأخبار وتحديثات المنتجات والتوصيات حول Penpot."
#: src/app/main/ui/settings/profile.cljs
msgid "dashboard.newsletter-title"
msgstr "الإشتراك في"
@@ -722,10 +701,6 @@ msgstr "حسنا"
msgid "ds.confirm-title"
msgstr "هل أنت متأكد؟"
#: src/app/main/ui/dashboard/grid.cljs
msgid "ds.updated-at"
msgstr "محدث: %s"
#: src/app/main/ui/auth/login.cljs
msgid "errors.auth-provider-not-configured"
msgstr "موفر المصادقة غير معد ومسجل."
@@ -737,10 +712,6 @@ msgstr "يبدوا أنك غير مصرح لك أو أن الجلسة إنتهت
msgid "errors.clipboard-not-implemented"
msgstr "لا يمكن للمتصفح إجراء هذه العملية"
#: src/app/main/data/workspace/persistence.cljs
msgid "errors.components-v2"
msgstr "تم استخدام هذا الملف بالفعل مع تمكين المكونات V2."
#: src/app/main/ui/auth/verify_token.cljs,
#: src/app/main/ui/settings/change_email.cljs
msgid "errors.email-already-exists"
@@ -772,10 +743,6 @@ msgstr "تم الإبلاغ عن البريد الإلكتروني «٪ s» كب
msgid "errors.generic"
msgstr "حدث خطأ ما."
#: src/app/main/ui/auth/login.cljs
msgid "errors.google-auth-not-enabled"
msgstr "المصادقة مع جوجل تعطلت في الخلفية"
#: src/app/main/ui/components/color_input.cljs
msgid "errors.invalid-color"
msgstr "لون غير صالح"
@@ -791,9 +758,6 @@ msgstr "هذه الدعوة قد تلغى أو قد تنتهي."
msgid "errors.ldap-disabled"
msgstr "تم تعطيل مصادقة LDAP."
msgid "errors.media-format-unsupported"
msgstr "تنسيق الصورة غير مدعوم (يجب أن يكون svg أو jpg أو png)."
#: src/app/main/data/workspace/persistence.cljs
msgid "errors.media-too-large"
msgstr "الصورة كبيرة جدا بحيث لا يمكن إدراجها."
@@ -812,9 +776,6 @@ msgstr ""
"يحتوي الملف الشخصي الذي تدعوه على رسائل بريد إلكتروني مكتومة (تقارير البريد "
"المزعج أو الارتدادات العالية)."
msgid "errors.network"
msgstr "تعذر الاتصال بخادم الواجهة الخلفية."
#: src/app/main/ui/settings/password.cljs
msgid "errors.password-invalid-confirmation"
msgstr "يجب أن تتطابق كلمة مرور التأكيد"
@@ -844,9 +805,6 @@ msgstr "العضو الذي تحاول تعيينه غير موجود."
msgid "errors.team-leave.owner-cant-leave"
msgstr "لا يمكن للمالك مغادرة الفريق ، يجب إعادة تعيين دور المالك."
msgid "errors.terms-privacy-agreement-invalid"
msgstr "يجب أن تقبل شروط الخدمة وسياسة الخصوصية الخاصة بنا."
#: src/app/main/data/media.cljs,
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
#: src/app/main/ui/inspect/exports.cljs
@@ -865,10 +823,6 @@ msgstr "يبدو أن اسم المستخدم أو كلمة المرور خاط
msgid "errors.wrong-old-password"
msgstr "كلمة المرور القديمة غير صحيحة"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.chat-start"
msgstr "انضم إلى الدردشة"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.chat-subtitle"
msgstr "ترغب في الكلام؟ تحدث معنا في Gitter"
@@ -988,18 +942,10 @@ msgstr "عرض"
msgid "inspect.attributes.shadow"
msgstr "ظل"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.blur"
msgstr "B"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.offset-x"
msgstr "X"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.offset-y"
msgstr "Y"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.spread"
msgstr "S"
@@ -1131,17 +1077,10 @@ msgstr "نص"
msgid "inspect.tabs.info"
msgstr "معلومات"
msgid "history.alert-message"
msgstr "أنت ترى الإصدار %s"
#: src/app/main/ui/workspace/header.cljs
msgid "label.shortcuts"
msgstr "الاختصارات"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.about-penpot"
msgstr "حول Penpot"
msgid "labels.accept"
msgstr "إقبل"
@@ -1176,9 +1115,6 @@ msgstr "مدخل خاطأ"
msgid "labels.cancel"
msgstr "الغاء"
msgid "labels.centered"
msgstr "توسيط"
msgid "labels.close"
msgstr "غلق"
@@ -1194,9 +1130,6 @@ msgstr "مجتمع"
msgid "labels.confirm-password"
msgstr "تأكيد كلمة المرور"
msgid "labels.content"
msgstr "محتوى"
msgid "labels.continue"
msgstr "استمر"
@@ -1226,9 +1159,6 @@ msgstr "خطوط مخصصة"
msgid "labels.dashboard"
msgstr "لوحة التحكم"
msgid "labels.default"
msgstr "إفتراضي"
#: src/app/main/ui/dashboard/project_menu.cljs,
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "labels.delete"
@@ -1268,10 +1198,6 @@ msgstr "تعديل ملف"
msgid "labels.editor"
msgstr "محرر"
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
msgid "labels.email"
msgstr "البريد الإلكتروني"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.expired-invitation"
msgstr "منتهي الصلاحية"
@@ -1320,9 +1246,6 @@ msgstr "مركز المساعدة"
msgid "labels.hide-resolved-comments"
msgstr "إخفاء التعليقات التي تم حلها"
msgid "labels.icons"
msgstr "الأيقونات"
msgid "labels.images"
msgstr "الصور"
@@ -1350,9 +1273,6 @@ msgstr "اللغة"
msgid "labels.libraries-and-templates"
msgstr "المكتبات والقوالب"
msgid "labels.link"
msgstr "رابط"
msgid "labels.log-or-sign"
msgstr "تسجيل الدخول أو الاشتراك"
@@ -1360,9 +1280,6 @@ msgstr "تسجيل الدخول أو الاشتراك"
msgid "labels.logout"
msgstr "تسجيل خروج"
msgid "labels.manage-fonts"
msgstr "إدارة الخطوط"
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.member"
msgstr "عضو"
@@ -1371,17 +1288,10 @@ msgstr "عضو"
msgid "labels.members"
msgstr "الأعضاء"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.name"
msgstr "الاسم"
#: src/app/main/ui/settings/password.cljs
msgid "labels.new-password"
msgstr "كلمة مرور جديدة"
msgid "labels.next"
msgstr "التالي"
#: src/app/main/ui/workspace/comments.cljs,
#: src/app/main/ui/dashboard/comments.cljs
msgid "labels.no-comments-available"
@@ -1395,10 +1305,6 @@ msgstr "لا توجد دعوات."
msgid "labels.no-invitations-hint"
msgstr "اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق."
#: src/app/main/ui/static.cljs
msgid "labels.not-found.auth-info"
msgstr "لقد سجلت الدخول باعتبارك"
#: src/app/main/ui/static.cljs
msgid "labels.not-found.desc-message"
msgstr "قد لا تكون هذه الصفحة موجودة أو ليس لديك أذونات للوصول إليها."
@@ -1460,10 +1366,6 @@ msgstr "كلمة المرور"
msgid "labels.pending-invitation"
msgstr "قيد الانتظار"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.permissions"
msgstr "اذونات"
#: src/app/main/ui/settings/sidebar.cljs,
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.profile"
@@ -1473,9 +1375,6 @@ msgstr "الملف الشخصي"
msgid "labels.projects"
msgstr "المشاريع"
msgid "labels.recent"
msgstr "الأخيرة"
#: src/app/main/ui/settings/sidebar.cljs
msgid "labels.release-notes"
msgstr "ملاحظات الإصدار"
@@ -1556,16 +1455,9 @@ msgstr "قائمة التعليقات"
msgid "labels.show-your-comments"
msgstr "إظهار تعليقاتك فقط"
#: src/app/main/ui/static.cljs
msgid "labels.sign-out"
msgstr "خروج"
msgid "labels.skip"
msgstr "تخطي"
msgid "labels.start"
msgstr "ابدأ"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.status"
msgstr "الحالة"
@@ -1595,9 +1487,6 @@ msgstr "جارٍ الرفع …"
msgid "labels.viewer"
msgstr "مشاهد"
msgid "labels.workspace"
msgstr "مساحة العمل"
#: src/app/main/ui/comments.cljs
msgid "labels.write-new-comment"
msgstr "كتابة تعليق جديد"
@@ -1655,10 +1544,6 @@ msgstr "تغيير البريد الإلكتروني"
msgid "modals.change-email.title"
msgstr "تغيير بريدك الإلكتروني"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.change-owner-and-leave-confirm.message"
msgstr "أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة."
#: src/app/main/ui/settings/delete_account.cljs
msgid "modals.delete-account.cancel"
msgstr "إلغاء والاحتفاظ بحسابي"
@@ -1747,10 +1632,6 @@ msgstr "هل أنت متأكد أنك تريد حذف هذا المشروع؟"
msgid "modals.delete-project-confirm.title"
msgstr "حذف المشروع"
#: src/app/main/ui/delete_shared.cljs
msgid "modals.delete-shared.title"
msgstr "حذف الملف"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.delete-team-confirm.accept"
msgstr "حذف الفريق"
@@ -2000,97 +1881,49 @@ msgstr "أنشئ فريقًا وأرسل الدعوات"
msgid "onboarding.choice.team-up.roles"
msgstr "دعوة مع الدور:"
msgid "onboarding.choice.title"
msgstr "مرحبًا بك في Penpot"
msgid "onboarding.contrib.alt"
msgstr "مصدر مفتوح"
msgid "onboarding.contrib.desc1"
msgstr ""
"Penpot هو برنامج مفتوح المصدر ، تم إنشاؤه بواسطة المجتمع ومن أجله. إذا كنت "
"ترغب في التعاون ، فأنت أكثر من موضع ترحيب!"
msgid "onboarding.contrib.desc2.1"
msgstr "يمكنك الوصول إلى"
msgid "onboarding.contrib.desc2.2"
msgstr "واتبع تعليمات المساهمة :)"
msgid "onboarding.contrib.link"
msgstr "مشروع على github"
msgid "onboarding.contrib.title"
msgstr "مساهم في المصدر المفتوح؟"
msgid "onboarding.newsletter.accept"
msgstr "نعم ، اشترك"
msgid "onboarding.newsletter.acceptance-message"
msgstr "تم إرسال طلب الاشتراك الخاص بك ، وسوف نرسل لك بريدًا إلكترونيًا لتأكيده."
msgid "onboarding.newsletter.decline"
msgstr "لا شكرا"
msgid "onboarding.newsletter.policy"
msgstr "سياسة الخصوصية."
msgid "onboarding.newsletter.privacy2"
msgstr ""
"سوف نرسل لك رسائل البريد الإلكتروني ذات الصلة فقط. يمكنك إلغاء الاشتراك في "
"أي وقت في ملف تعريف المستخدم الخاص بك أو عبر رابط إلغاء الاشتراك في أي من "
"رسائلنا الإخبارية."
msgid "onboarding.newsletter.title"
msgstr "هل تريد تلقي أخبار Penpot؟"
msgid "onboarding.slide.0.alt"
msgstr "خلق التصاميم"
msgid "onboarding.slide.0.desc1"
msgstr "قم بإنشاء واجهات مستخدم جميلة بالتعاون مع جميع أعضاء الفريق."
msgid "onboarding.slide.0.desc2"
msgstr "الحفاظ على الاتساق على نطاق واسع مع المكونات والمكتبات وأنظمة التصميم."
msgid "onboarding.slide.0.title"
msgstr "مكتبات التصميم والأنماط والمكونات"
msgid "onboarding.slide.1.alt"
msgstr "النماذج التفاعلية"
msgid "onboarding.slide.1.desc1"
msgstr "أنشئ تفاعلات غنية لتقليد سلوك المنتج."
msgid "onboarding.slide.1.desc2"
msgstr ""
"شارك مع أصحاب المصلحة ، وقدم مقترحات إلى فريقك وابدأ في اختبار المستخدم "
"بتصميماتك ، كل ذلك في مكان واحد."
msgid "onboarding.slide.1.title"
msgstr "اجعل تصميماتك تنبض بالحياة من خلال التفاعلات"
msgid "onboarding.slide.2.alt"
msgstr "احصل على تعليقات"
msgid "onboarding.slide.2.desc1"
msgstr ""
"يعمل جميع أعضاء الفريق في وقت واحد مع تصميمات متعددة اللاعبين في الوقت "
"الفعلي وتعليقات وأفكار وتعليقات مركزية مباشرة على التصميمات."
msgid "onboarding.slide.2.title"
msgstr "الحصول على ردود الفعل ، وتقديم ومشاركة عملك"
msgid "onboarding.slide.3.desc1"
msgstr ""
"قم بمزامنة التصميم والرمز لجميع المكونات والأنماط الخاصة بك واحصل على "
"مقتطفات التعليمات البرمجية."
msgid "onboarding.slide.3.desc2"
msgstr ""
"احصل على مواصفات التعليمات البرمجية وقدمها مثل الترميز (SVG ، HTML) أو "
"الأنماط (CSS ، Less ، Stylus…)."
msgid "onboarding.team-modal.create-team"
msgstr "أنشئ فريقًا"
@@ -2123,9 +1956,6 @@ msgstr "إبدأ التصميم"
msgid "onboarding.welcome.alt"
msgstr "Penpot"
msgid "onboarding.welcome.title"
msgstr "مرحبًا بك في Penpot"
#: src/app/main/ui/auth/recovery.cljs
msgid "profile.recovery.go-to-login"
msgstr "اذهب إلى تسجيل الدخول"
@@ -2510,18 +2340,10 @@ msgstr "التفاعلات"
msgid "viewer.header.share.copy-link"
msgstr "نسخ الرابط"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.create-link"
msgstr "إنشاء رابط"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.placeholder"
msgstr "سيظهر رابط المشاركة هنا"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.remove-link"
msgstr "إزالة الرابط"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.subtitle"
msgstr "أي شخص لديه الرابط سيكون لديه حق الوصول"
@@ -2578,9 +2400,6 @@ msgstr "أصول رقمية"
msgid "workspace.assets.box-filter-all"
msgstr "كل الأصول الرقمية"
msgid "workspace.assets.box-filter-graphics"
msgstr "الرسومات"
#: src/app/main/ui/workspace/sidebar/assets.cljs,
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.colors"
@@ -2797,10 +2616,6 @@ msgstr "أضف"
msgid "workspace.libraries.colors"
msgstr "%s الألوان"
#: src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.big-thumbnails"
msgstr "صور مصغرة كبيرة"
#: src/app/main/ui/workspace/colorpicker/libraries.cljs,
#: src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.file-library"
@@ -2815,10 +2630,6 @@ msgstr "الألوان الحديثة"
msgid "workspace.libraries.colors.save-color"
msgstr "حفظ نمط اللون"
#: src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.small-thumbnails"
msgstr "صور مصغرة صغيرة"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.components"
msgstr "%s المكونات"
@@ -2883,21 +2694,12 @@ msgstr "تحديث"
msgid "workspace.libraries.updates"
msgstr "التحديثات"
msgid "workspace.library.all"
msgstr "جميع المكتبات"
msgid "workspace.library.libraries"
msgstr "المكتبات"
msgid "workspace.library.own"
msgstr "مكتباتي"
msgid "workspace.library.store"
msgstr "المكتبات المخزنة"
msgid "workspace.options.blur-options.background-blur"
msgstr "خلفية"
msgid "workspace.options.blur-options.layer-blur"
msgstr "طبقة"

View File

@@ -0,0 +1,88 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2022-12-03 14:47+0000\n"
"Last-Translator: Salman Hossain Saif <penpot.weblate@inbx.anonaddy.com>\n"
"Language-Team: Bengali "
"<https://hosted.weblate.org/projects/penpot/frontend/bn/>\n"
"Language: bn\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.15-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
msgstr "অ্যাকাউন্ট আছে?"
#: src/app/main/ui/auth/register.cljs
msgid "auth.check-your-email"
msgstr ""
"আপনার ইমেইল চেক করুন এবং লিংকে ক্লিক করে ভেরিফাই করে Penpot ব্যবহার শুরু "
"করুন।"
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.confirm-password"
msgstr "পাসওয়ার্ড নিশ্চিত করুন"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.create-demo-account"
msgstr "ডেমো অ্যাকাউন্ট তৈরী করুন"
#: src/app/main/ui/auth/register.cljs
msgid "auth.demo-warning"
msgstr ""
"এটি একটি ডেমো সার্ভিস। প্রয়োজনীয় কোনো কাজে ব্যবহার করবেন না। কিছু সময় পর "
"প্রজেক্টগুলো মুছে ফেলা হবে।"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.email"
msgstr "ইমেইল"
#: src/app/main/ui/auth/login.cljs
msgid "auth.forgot-password"
msgstr "পাসওয়ার্ড ভুলে গেছেন?"
#: src/app/main/ui/auth/register.cljs
msgid "auth.fullname"
msgstr "পুরো নাম"
#: src/app/main/ui/auth/register.cljs
msgid "auth.login-here"
msgstr "এখানে লগিন করুন"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-submit"
msgstr "লগিন"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-title"
msgstr "আপনাকে আবার দেখে ভালো লাগছে!"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit"
msgstr "গিটহাব"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit"
msgstr "গিটল্যাব"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit"
msgstr "গুগল"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit"
msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit"
msgstr "ওপেনআইডি"
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password"
msgstr "নতুন পাসওয়ার্ড টাইপ করুন"
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.notifications.invalid-token-error"
msgstr "রিকভারি টোকেন সঠিক নয়।"

View File

@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2022-10-04 18:22+0000\n"
"Last-Translator: Antonio <antonio@fesomia.cat>\n"
"PO-Revision-Date: 2023-01-24 14:27+0000\n"
"Last-Translator: Rubén <ruben@users.noreply.hosted.weblate.org>\n"
"Language-Team: Catalan "
"<https://hosted.weblate.org/projects/penpot/frontend/ca/>\n"
"Language: ca\n"
@@ -9,7 +9,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.14.1\n"
"X-Generator: Weblate 4.16-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
@@ -87,10 +87,6 @@ msgstr "OpenID"
msgid "auth.new-password"
msgstr "Escriviu una contrasenya nova"
#: src/app/main/ui/auth/register.cljs
msgid "auth.newsletter-subscription"
msgstr "Accepto subscriure'm a la llista de correu de Penpot."
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.notifications.invalid-token-error"
msgstr "El codi de recuperació no és vàlid."
@@ -194,9 +190,6 @@ msgstr "Obtén l'enllaç"
msgid "common.share-link.link-copied-success"
msgstr "S'ha copiat l'enllaç correctament"
msgid "common.share-link.link-deleted-success"
msgstr "S'ha eliminat l'enllaç correctament"
msgid "common.share-link.manage-ops"
msgstr "Gestió de permisos"
@@ -302,9 +295,6 @@ msgstr "Baixa el fitxer Penpot (.penpot)"
msgid "dashboard.download-standard-file"
msgstr "Baixa fitxer estàndard (.svg + .json)"
msgid "dashboard.draft-title"
msgstr "Esborrany"
#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.duplicate"
msgstr "Duplica"
@@ -313,10 +303,6 @@ msgstr "Duplica"
msgid "dashboard.duplicate-multi"
msgstr "Duplica %s fitxers"
#: src/app/main/ui/dashboard/grid.cljs
msgid "dashboard.empty-files"
msgstr "Encara no teniu cap arxiu aquí"
#: src/app/main/ui/dashboard/grid.cljs
#, markdown
msgid "dashboard.empty-placeholder-drafts"
@@ -364,9 +350,6 @@ msgstr "No hi ha elements amb configuració d'exportació."
msgid "dashboard.export-shapes.title"
msgstr "Selecció d'exportació"
msgid "dashboard.export-single"
msgstr "Exporta el fitxer de Penpot"
msgid "dashboard.export-standard-multi"
msgstr "Baixa %s fitxers estàndard (.svg + .json)"
@@ -539,10 +522,6 @@ msgstr "+ Projecte nou"
msgid "dashboard.new-project-prefix"
msgstr "Projecte nou"
#: src/app/main/ui/settings/profile.cljs
msgid "dashboard.newsletter-msg"
msgstr "Envia'm novetats, actualitzacions de producte i recomanacions de Penpot."
#: src/app/main/ui/settings/profile.cljs
msgid "dashboard.newsletter-title"
msgstr "Subscripció al butlletí"
@@ -722,10 +701,6 @@ msgstr "D'acord"
msgid "ds.confirm-title"
msgstr "N'esteu segur?"
#: src/app/main/ui/dashboard/grid.cljs
msgid "ds.updated-at"
msgstr "Actualitzat: %s"
#: src/app/main/ui/auth/login.cljs
msgid "errors.auth-provider-not-configured"
msgstr "L'autenticació del proveïdor no està configurada."
@@ -737,10 +712,6 @@ msgstr "Sembla que no esteu autenticat o que la sessió ha caducat."
msgid "errors.clipboard-not-implemented"
msgstr "El vostre navegador no pot fer aquesta operació"
#: src/app/main/data/workspace/persistence.cljs
msgid "errors.components-v2"
msgstr "Aquest fitxer ja s'ha utilitzat amb els Components V2 habilitats."
#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs
msgid "errors.email-already-exists"
msgstr "Aquest correu ja està en ús"
@@ -767,10 +738,6 @@ msgstr "El correu «%s» s'ha marcat com a brossa o rebot permanent."
msgid "errors.generic"
msgstr "Alguna cosa ha anat malament."
#: src/app/main/ui/auth/login.cljs
msgid "errors.google-auth-not-enabled"
msgstr "L'autenticació amb Google està desactivada en aquest servidor"
#: src/app/main/ui/components/color_input.cljs
msgid "errors.invalid-color"
msgstr "El color no és vàlid"
@@ -786,9 +753,6 @@ msgstr "Aquesta invitació pot estar cancel·lada o caducada."
msgid "errors.ldap-disabled"
msgstr "L'autenticació LDAP està inhabilitada."
msgid "errors.media-format-unsupported"
msgstr "El format d'imatge no està suportat (ha de ser SVG, JPG o PNG)."
#: src/app/main/data/workspace/persistence.cljs
msgid "errors.media-too-large"
msgstr "La imatge és massa gran."
@@ -809,9 +773,6 @@ msgstr ""
"El perfil que estàs convidant té els missatges de correu silenciats (per "
"informes de correu brossa o de retorns alts)."
msgid "errors.network"
msgstr "No es pot connectar amb el servidor principal."
#: src/app/main/ui/settings/password.cljs
msgid "errors.password-invalid-confirmation"
msgstr "La contrasenya de confirmació ha de coincidir"
@@ -820,6 +781,9 @@ msgstr "La contrasenya de confirmació ha de coincidir"
msgid "errors.password-too-short"
msgstr "La contrasenya ha de tenir 8 caràcters com a mínim"
msgid "errors.profile-blocked"
msgstr "El perfil està bloquejat"
#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs
msgid "errors.profile-is-muted"
msgstr ""
@@ -843,11 +807,6 @@ msgstr ""
"El propietari no pot abandonar l'equip, heu de reassignar el rol de "
"propietat."
msgid "errors.terms-privacy-agreement-invalid"
msgstr ""
"Heu d'acceptar les nostres condicions del servei i la política de "
"privacitat."
#: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/inspect/exports.cljs
msgid "errors.unexpected-error"
msgstr "S'ha produït un error inesperat."
@@ -864,10 +823,6 @@ msgstr "El nom d'usuari o la contrasenya sembla incorrecte."
msgid "errors.wrong-old-password"
msgstr "La contrasenya anterior no és correcta"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.chat-start"
msgstr "Uneix-me al xat"
#: src/app/main/ui/settings/feedback.cljs
msgid "feedback.chat-subtitle"
msgstr "Voleu parlar? Xategeu amb nosaltres a Gitter"
@@ -987,18 +942,10 @@ msgstr "Amplada"
msgid "inspect.attributes.shadow"
msgstr "Ombra"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.blur"
msgstr "B"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.offset-x"
msgstr "X"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.offset-y"
msgstr "Y"
#: src/app/main/ui/inspect/attributes/shadow.cljs
msgid "inspect.attributes.shadow.shorthand.spread"
msgstr "S"
@@ -1133,17 +1080,10 @@ msgstr "Text"
msgid "inspect.tabs.info"
msgstr "Informació"
msgid "history.alert-message"
msgstr "Esteu veient la versió %s"
#: src/app/main/ui/workspace/header.cljs
msgid "label.shortcuts"
msgstr "Dreceres"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.about-penpot"
msgstr "Sobre Penpot"
msgid "labels.accept"
msgstr "Acceptar"
@@ -1178,9 +1118,6 @@ msgstr "Error del servidor (Bad Gateway)"
msgid "labels.cancel"
msgstr "Cancel·la"
msgid "labels.centered"
msgstr "Centrat"
msgid "labels.close"
msgstr "Tanca"
@@ -1196,9 +1133,6 @@ msgstr "Comunitat"
msgid "labels.confirm-password"
msgstr "Confirmeu la contrasenya"
msgid "labels.content"
msgstr "Contingut"
msgid "labels.continue"
msgstr "Continua"
@@ -1227,9 +1161,6 @@ msgstr "Tipografies personalitzades"
msgid "labels.dashboard"
msgstr "Tauler"
msgid "labels.default"
msgstr "predeterminat"
#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "labels.delete"
msgstr "Elimina"
@@ -1265,10 +1196,6 @@ msgstr "Edita'l"
msgid "labels.editor"
msgstr "Editor"
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
msgid "labels.email"
msgstr "Correu electrònic"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.expired-invitation"
msgstr "Ha caducat"
@@ -1315,9 +1242,6 @@ msgstr "Centre d'ajuda"
msgid "labels.hide-resolved-comments"
msgstr "Amaga els comentaris resolts"
msgid "labels.icons"
msgstr "Icones"
msgid "labels.images"
msgstr "Imatges"
@@ -1346,9 +1270,6 @@ msgstr "Llengua"
msgid "labels.libraries-and-templates"
msgstr "Biblioteques i plantilles"
msgid "labels.link"
msgstr "Enllaç"
msgid "labels.log-or-sign"
msgstr "Inicia sessió o registra'm"
@@ -1356,9 +1277,6 @@ msgstr "Inicia sessió o registra'm"
msgid "labels.logout"
msgstr "Tanca la sessió"
msgid "labels.manage-fonts"
msgstr "Gestiona les tipografies"
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.member"
msgstr "Membre"
@@ -1367,17 +1285,10 @@ msgstr "Membre"
msgid "labels.members"
msgstr "Membres"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.name"
msgstr "Nom"
#: src/app/main/ui/settings/password.cljs
msgid "labels.new-password"
msgstr "Contrasenya nova"
msgid "labels.next"
msgstr "Següent"
#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs
msgid "labels.no-comments-available"
msgstr "No teniu notificacions de comentaris pendents"
@@ -1392,10 +1303,6 @@ msgstr ""
"Feu clic al botó «Convida a l'equip» per convidar més membres a aquest "
"equip."
#: src/app/main/ui/static.cljs
msgid "labels.not-found.auth-info"
msgstr "Sessió iniciada com a"
#: src/app/main/ui/static.cljs
msgid "labels.not-found.desc-message"
msgstr ""
@@ -1446,10 +1353,6 @@ msgstr "Contrasenya"
msgid "labels.pending-invitation"
msgstr "Pendent"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.permissions"
msgstr "Permisos"
#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.profile"
msgstr "Perfil"
@@ -1458,9 +1361,6 @@ msgstr "Perfil"
msgid "labels.projects"
msgstr "Projectes"
msgid "labels.recent"
msgstr "Recent"
#: src/app/main/ui/settings/sidebar.cljs
msgid "labels.release-notes"
msgstr "Notes de la versió"
@@ -1537,16 +1437,9 @@ msgstr "Mostra la llista de comentaris"
msgid "labels.show-your-comments"
msgstr "Mostra només els meus comentaris"
#: src/app/main/ui/static.cljs
msgid "labels.sign-out"
msgstr "Tanca la sessió"
msgid "labels.skip"
msgstr "Omet"
msgid "labels.start"
msgstr "Inicia"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.status"
msgstr "Estat"
@@ -1576,9 +1469,6 @@ msgstr "S'està pujant…"
msgid "labels.viewer"
msgstr "Visor"
msgid "labels.workspace"
msgstr "Espai de treball"
#: src/app/main/ui/comments.cljs
msgid "labels.write-new-comment"
msgstr "Escriu un comentari nou"
@@ -1636,12 +1526,6 @@ msgstr "Canvia el correu"
msgid "modals.change-email.title"
msgstr "Canvieu el vostre correu electrònic"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.change-owner-and-leave-confirm.message"
msgstr ""
"Sou qui té la propietat d'aquest equip. Abans d'abandonar-lo, ascendiu un "
"membre a propietari."
#: src/app/main/ui/settings/delete_account.cljs
msgid "modals.delete-account.cancel"
msgstr "Cancel·la i conserva el meu compte"
@@ -1777,6 +1661,10 @@ msgstr "Envia una invitació"
msgid "modals.invite-member.emails"
msgstr "Correus electrònics, separats per una coma"
#: src/app/main/ui/dashboard/team.cljs
msgid "modals.invite-team-member.title"
msgstr "Convida membres a l'equip"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-and-close-confirm.hint"
msgstr ""
@@ -1908,6 +1796,12 @@ msgstr "El perfil s'ha desat correctament!"
msgid "notifications.validation-email-sent"
msgstr "S'ha enviat un correu electrònic de verificació a %s. Reviseu el correu!"
msgid "onboarding-v2.before-start.desc2.title"
msgstr "Guia d'ús"
msgid "onboarding-v2.welcome.title"
msgstr "Us donem la benvinguda a Penpot!"
msgid "onboarding.choice.team-up.create-team"
msgstr "Nom de l'equip"
@@ -1926,29 +1820,15 @@ msgstr "Crea l'equip ara i convida membres en un altre moment"
msgid "onboarding.choice.team-up.invite-members-submit"
msgstr "Crea l'equip i envia les invitacions"
msgid "onboarding.choice.title"
msgstr "Us donem la benvinguda a Penpot"
msgid "onboarding.contrib.alt"
msgstr "Codi obert"
msgid "onboarding.contrib.desc1"
msgstr ""
"Penpot és de codi obert, fet per i per a la comunitat. Si voleu "
"col·laborar, sou més que benvinguts!"
msgid "onboarding.contrib.desc2.1"
msgstr "Podeu accedir al"
msgid "onboarding.contrib.desc2.2"
msgstr "i seguir les instruccions de contribució :)"
msgid "onboarding.contrib.link"
msgstr "projecte a github"
msgid "onboarding.contrib.title"
msgstr "Sou contribuïdor de codi obert?"
msgid "onboarding.newsletter.accept"
msgstr "Sí, subscriu-m'hi"
@@ -1957,74 +1837,41 @@ msgstr ""
"S'ha enviat la sol·licitud de subscripció. Us enviarem un correu electrònic "
"per confirmar-ho."
msgid "onboarding.newsletter.decline"
msgstr "No, gràcies"
msgid "onboarding.newsletter.policy"
msgstr "Política de privacitat."
msgid "onboarding.newsletter.title"
msgstr "Voleu rebre les novetats de Penpot?"
msgid "onboarding.slide.0.alt"
msgstr "Creeu dissenys"
msgid "onboarding.slide.0.desc1"
msgstr ""
"Creeu interfícies d'usuari boniques en col·laboració amb tots els membres "
"de l'equip."
msgid "onboarding.slide.0.desc2"
msgstr ""
"Manteniu la consistència a escala amb components, biblioteques i sistemes "
"de disseny."
msgid "onboarding.slide.0.title"
msgstr "Biblioteques de disseny, estils i components"
msgid "onboarding.slide.1.alt"
msgstr "Prototips interactius"
msgid "onboarding.slide.1.desc1"
msgstr "Creeu interaccions enriquides per a imitar el comportament del producte."
msgid "onboarding.slide.1.desc2"
msgstr ""
"Compartiu amb les parts interessades, presenteu propostes a l'equip i "
"comenceu a fer proves d'usuari amb els vostres dissenys, tot en un sol lloc."
msgid "onboarding.slide.1.title"
msgstr "Doneu vida als vostres dissenys amb interaccions"
msgid "onboarding.slide.2.alt"
msgstr "Obteniu opinions"
msgid "onboarding.slide.2.desc1"
msgstr ""
"Tot l'equip treballant simultàniament amb disseny en temps real i "
"comentaris, idees i opinions sobre els dissenys de forma centralitzada."
msgid "onboarding.slide.2.title"
msgstr "Obteniu opinions, presenteu i compartiu el vostre treball"
msgid "onboarding.slide.3.alt"
msgstr "Lliurament i codi baix"
msgid "onboarding.slide.3.desc1"
msgstr ""
"Sincronitzeu el disseny i el codi de tots els components i estils i obteniu "
"fragments de codi."
msgid "onboarding.slide.3.desc2"
msgstr ""
"Obteniu i proporcioneu especificacions de codi d'etiquetatge (SVG, HTML) o "
"d'estils (CSS, Less, Stylus...)."
msgid "onboarding.slide.3.title"
msgstr "Una font compartida de veritat"
msgid "onboarding.team.start.button"
msgstr "Comenceu de seguida"
msgid "onboarding.team-modal.create-team"
msgstr "Crea un equip"
msgid "onboarding.templates.subtitle"
msgstr "Aquí teniu algunes plantilles."
@@ -2035,9 +1882,6 @@ msgstr "Comenceu a dissenyar"
msgid "onboarding.welcome.alt"
msgstr "Penpot"
msgid "onboarding.welcome.title"
msgstr "Et donem la benvinguda a Penpot"
#: src/app/main/ui/auth/recovery.cljs
msgid "profile.recovery.go-to-login"
msgstr "Vés a l'inici de sessió"
@@ -2409,6 +2253,9 @@ msgstr "Commuta paleta de colors"
msgid "shortcuts.toggle-focus-mode"
msgstr "Activa/desactiva el mode de concentració"
msgid "shortcuts.toggle-fullscreen"
msgstr "Activa/desactiva la pantalla completa"
msgid "shortcuts.toggle-grid"
msgstr "Mostra/Amaga la graella"
@@ -2445,9 +2292,6 @@ msgstr "Mostra/amaga l'element"
msgid "shortcuts.toggle-zoom-style"
msgstr "Commuta l'estil de zoom"
msgid "shortcuts.toggle-fullscreen"
msgstr "Activa/desactiva la pantalla completa"
msgid "shortcuts.undo"
msgstr "Desfés"
@@ -2565,18 +2409,10 @@ msgstr "Interaccions (%s)"
msgid "viewer.header.share.copy-link"
msgstr "Copia l'enllaç"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.create-link"
msgstr "Crea un enllaç"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.placeholder"
msgstr "L'enllaç compartit apareixerà aquí"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.remove-link"
msgstr "Elimina l'enllaç"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.subtitle"
msgstr "Qualsevol persona amb l'enllaç hi tindrà accés"
@@ -2633,9 +2469,6 @@ msgstr "Recursos"
msgid "workspace.assets.box-filter-all"
msgstr "Tots els recursos"
msgid "workspace.assets.box-filter-graphics"
msgstr "Gràfics"
#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.colors"
msgstr "Colors"
@@ -2928,10 +2761,6 @@ msgstr "Afegeix"
msgid "workspace.libraries.colors"
msgstr "%s colors"
#: src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.big-thumbnails"
msgstr "Miniatures grans"
#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.file-library"
msgstr "Biblioteca del fitxer"
@@ -2956,10 +2785,6 @@ msgstr "RGBA"
msgid "workspace.libraries.colors.save-color"
msgstr "Desa l'estil de color"
#: src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.small-thumbnails"
msgstr "Miniatures petites"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.components"
msgstr "%s components"
@@ -3024,15 +2849,9 @@ msgstr "Actualitza"
msgid "workspace.libraries.updates"
msgstr "ACTUALITZACIONS"
msgid "workspace.library.all"
msgstr "Totes les biblioteques"
msgid "workspace.library.libraries"
msgstr "Biblioteques"
msgid "workspace.library.own"
msgstr "Les meves biblioteques"
msgid "workspace.library.store"
msgstr "Predeterminades"
@@ -3040,9 +2859,6 @@ msgstr "Predeterminades"
msgid "workspace.options.add-interaction"
msgstr "Feu clic en el botó de + per a afegir interaccions."
msgid "workspace.options.blur-options.background-blur"
msgstr "Fons"
msgid "workspace.options.blur-options.layer-blur"
msgstr "Capa"
@@ -3547,14 +3363,6 @@ msgstr "Capes seleccionades"
msgid "workspace.options.layout-item.advanced-ops"
msgstr "Opcions avançades"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.min-w"
msgstr "Amplada mín."
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.title.min-w"
msgstr "Amplada mínima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.bottom"
msgstr "Baix"
@@ -3564,11 +3372,11 @@ msgid "workspace.options.layout.direction.column"
msgstr "Columna"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-column"
msgid "workspace.options.layout.direction.column-reverse"
msgstr "Columna invertida"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-row"
msgid "workspace.options.layout.direction.row-reverse"
msgstr "Fila invertida"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -3669,10 +3477,6 @@ msgstr ""
"Seleccioneu una forma, un tauler o un grup per a arrossegar una connexió a "
"un altre tauler."
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.select-artboard"
msgstr "Selecciona el tauler"
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs
msgid "workspace.options.selection-color"
msgstr "Colors seleccionats"
@@ -3837,9 +3641,6 @@ msgstr "Alinea a la dreta"
msgid "workspace.options.text-options.align-top"
msgstr "Alinea a dalt"
msgid "workspace.options.text-options.decoration"
msgstr "Decoració"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.direction-ltr"
msgstr "LTR"
@@ -3848,10 +3649,6 @@ msgstr "LTR"
msgid "workspace.options.text-options.direction-rtl"
msgstr "RTL"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
msgid "workspace.options.text-options.google"
msgstr "Google"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.grow-auto-height"
msgstr "Alt automàtic"
@@ -3880,17 +3677,10 @@ msgstr "Minúscules"
msgid "workspace.options.text-options.none"
msgstr "Cap"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
msgid "workspace.options.text-options.preset"
msgstr "Predefinit"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.strikethrough"
msgstr "Ratllat"
msgid "workspace.options.text-options.text-case"
msgstr "Majús./Minús."
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.title"
msgstr "Text"
@@ -3915,9 +3705,6 @@ msgstr "Subratllat"
msgid "workspace.options.text-options.uppercase"
msgstr "Majúscules"
msgid "workspace.options.text-options.vertical-align"
msgstr "Alineació vertical"
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.use-play-button"
msgstr ""

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