Compare commits
153 Commits
1.13.2-bet
...
1.14.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95829ff3de | ||
|
|
2bed06de64 | ||
|
|
a08c1b1278 | ||
|
|
3053e867cb | ||
|
|
7cdbadc5b7 | ||
|
|
fb1dbd6f31 | ||
|
|
9dabe2959f | ||
|
|
529fb350fa | ||
|
|
0654741e28 | ||
|
|
c60c04f167 | ||
|
|
24d23d9e5a | ||
|
|
66cec51c44 | ||
|
|
5e2a7e76f3 | ||
|
|
e0bd3425bc | ||
|
|
667598a0eb | ||
|
|
310c322883 | ||
|
|
7f9d070692 | ||
|
|
206ffcc6e8 | ||
|
|
6b5ee24010 | ||
|
|
2132bad898 | ||
|
|
189d33221e | ||
|
|
a3fd5d6516 | ||
|
|
85f2804af8 | ||
|
|
424dd5c41a | ||
|
|
97f5f54d1c | ||
|
|
e6b80bf73e | ||
|
|
bc3914e7e0 | ||
|
|
801cdd940a | ||
|
|
28b6175943 | ||
|
|
ba85dcf1a3 | ||
|
|
c3486c566a | ||
|
|
71501d966c | ||
|
|
8bb2f20eae | ||
|
|
0fcd414792 | ||
|
|
9eba666c31 | ||
|
|
1764d965c1 | ||
|
|
a120630a7f | ||
|
|
f33ad5e8fa | ||
|
|
f04859f8a6 | ||
|
|
31aed2aaa4 | ||
|
|
18109b2387 | ||
|
|
a0cc8a06b6 | ||
|
|
4c1903b4e8 | ||
|
|
462ec0c12a | ||
|
|
2b61b1768f | ||
|
|
424630a67f | ||
|
|
14b1970a8a | ||
|
|
541168aee4 | ||
|
|
6e9a77edcd | ||
|
|
8d1cd2f56d | ||
|
|
65cda41245 | ||
|
|
c029948cce | ||
|
|
32540f1ba5 | ||
|
|
5d20815776 | ||
|
|
0b149dd302 | ||
|
|
662fc073df | ||
|
|
46be4ca6d1 | ||
|
|
23d3e88214 | ||
|
|
c356ae6de8 | ||
|
|
c5e872b81d | ||
|
|
0307e58fbe | ||
|
|
5c14c3fafc | ||
|
|
321c3fb34b | ||
|
|
4764674374 | ||
|
|
0416988913 | ||
|
|
72251f57b1 | ||
|
|
ec884787f1 | ||
|
|
3d8c41cd69 | ||
|
|
be7733a2cf | ||
|
|
8c51d1ac95 | ||
|
|
64e2fa9e2f | ||
|
|
fa5b0ed6ac | ||
|
|
89f485a674 | ||
|
|
68e38271fb | ||
|
|
066d53b81b | ||
|
|
ffbc098af8 | ||
|
|
e8f61df710 | ||
|
|
3604d0cfc9 | ||
|
|
393d959289 | ||
|
|
a453f1a648 | ||
|
|
cdd6801360 | ||
|
|
cca5ddb81a | ||
|
|
28e2d64ac6 | ||
|
|
6eb24bd1b7 | ||
|
|
79467b7b72 | ||
|
|
e14c6e5a6f | ||
|
|
2be432e1d4 | ||
|
|
eb07350cac | ||
|
|
235d3dbf3d | ||
|
|
684805067a | ||
|
|
db7761b742 | ||
|
|
08beb57ff1 | ||
|
|
accba56b89 | ||
|
|
c5ba399bcd | ||
|
|
fb879660d0 | ||
|
|
1729fe7312 | ||
|
|
bd7ea210f5 | ||
|
|
9cacca4802 | ||
|
|
9fab2fc24a | ||
|
|
4363e32aae | ||
|
|
28fc7178f1 | ||
|
|
151de33586 | ||
|
|
0afbf02443 | ||
|
|
eb143c8399 | ||
|
|
85f1cb47a7 | ||
|
|
f7dbb4f944 | ||
|
|
b90a308d66 | ||
|
|
b3847cafa8 | ||
|
|
a18e067d7a | ||
|
|
036fe44471 | ||
|
|
b008835d43 | ||
|
|
fc95443cc4 | ||
|
|
2061018742 | ||
|
|
121b5af5d0 | ||
|
|
1d69cb2580 | ||
|
|
e68689aa4f | ||
|
|
989ff8db7a | ||
|
|
b68fdee946 | ||
|
|
81df2ca355 | ||
|
|
56cdd1ffeb | ||
|
|
c9937f6b91 | ||
|
|
7e37aca5ee | ||
|
|
070886bbf6 | ||
|
|
c00168b61d | ||
|
|
0e9119d603 | ||
|
|
df39e9baf4 | ||
|
|
668aca725c | ||
|
|
c865082a6a | ||
|
|
395f23dec8 | ||
|
|
58905f0b99 | ||
|
|
bf70719899 | ||
|
|
18855ef2ef | ||
|
|
8ae05ff7b6 | ||
|
|
9e4650cbb6 | ||
|
|
3b75d9b362 | ||
|
|
c9ddc83eef | ||
|
|
5170634b90 | ||
|
|
01c92c04cf | ||
|
|
3cb15df08d | ||
|
|
87f5efeadb | ||
|
|
6897c0c3fe | ||
|
|
4010fb7d1e | ||
|
|
09c57bdb86 | ||
|
|
d28bbdaaf7 | ||
|
|
a44f1df0d4 | ||
|
|
3e745ff45d | ||
|
|
bc87e3d6d0 | ||
|
|
c1a67c0097 | ||
|
|
4ef9d4d5f6 | ||
|
|
4ed093f28f | ||
|
|
2e3addc6da | ||
|
|
80549bda9b | ||
|
|
1d5d597103 |
49
CHANGES.md
@@ -1,12 +1,52 @@
|
||||
# CHANGELOG
|
||||
|
||||
## :rocket: Next
|
||||
## 1.14.0-beta
|
||||
|
||||
### :boom: Breaking changes
|
||||
### :sparkles: New features
|
||||
|
||||
- Added shortcut panel in workspace [Taiga #36](https://tree.taiga.io/project/penpot/us/36)
|
||||
- Added selected colors widget in right sidebar [Taiga #2485](https://tree.taiga.io/project/penpot/us/2485)
|
||||
- Added fixed elements when scrolling [Taiga #1533](https://tree.taiga.io/project/penpot/us/1533)
|
||||
- Multiple team invitations on onboarding [Taiga #3084](https://tree.taiga.io/project/penpot/us/3084)
|
||||
- Change text properties position at the sidebar [Taiga #3047](https://tree.taiga.io/project/penpot/us/3047)
|
||||
- Group assets by drag and drop [Taiga #2831](https://tree.taiga.io/project/penpot/us/2831)
|
||||
- Navigate to the original link after log in [Taiga #3624](https://tree.taiga.io/project/penpot/issue/3624)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
### :arrow_up: Deps updates
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
- Fix menu file not accessible in certain conditions [Taiga #3385](https://tree.taiga.io/project/penpot/issue/3385)
|
||||
- Remove deprecated menu options [Taiga #3333](https://tree.taiga.io/project/penpot/issue/3333)
|
||||
- Prototype connection should be under the rules [Taiga #3384](https://tree.taiga.io/project/penpot/issue/3384)
|
||||
- Fix problem with empty text boxes events [Taiga #3627](https://tree.taiga.io/project/penpot/issue/3627)
|
||||
|
||||
|
||||
## 1.13.5-beta
|
||||
|
||||
### :bug: Bugs fixed
|
||||
- Fix orientation artboard preset not working with differently sized artboards [Taiga #3548](https://tree.taiga.io/project/penpot/issue/3548)
|
||||
- Fix background on export arboards [Taiga #1991](https://tree.taiga.io/project/penpot/issue/1991)
|
||||
|
||||
## 1.13.4-beta
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix undo when drawing curves [Taiga #3523](https://tree.taiga.io/project/penpot/issue/3523)
|
||||
- Fix issue with text edition and certain fonts (WorkSans, Raleway, ...) and foreign objects [Taiga #3521](https://tree.taiga.io/project/penpot/issue/3521)
|
||||
- Fix thumbnail generation when concurrent edition [Taiga #3522](https://tree.taiga.io/project/penpot/issue/3522)
|
||||
- Fix environment imporot for exporter in Docker
|
||||
- Fix auto scroll layers in Firefox [Taiga #3531](https://tree.taiga.io/project/penpot/issue/3531)
|
||||
- Fix base background not visible for imported SVG
|
||||
|
||||
## 1.13.3-beta
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix docker dependencies
|
||||
- Sets invitations expirations to 7 days
|
||||
- Add safety measure for text positions
|
||||
- Fix old texts with opacity and no fill
|
||||
- Remove default font on team change
|
||||
- Fix github auth without name
|
||||
- Fix problems with font loading in Firefox 95
|
||||
|
||||
## 1.13.2-beta
|
||||
|
||||
@@ -70,6 +110,7 @@
|
||||
- Duplicate artboards create new flows if needed [Taiga #2221](https://tree.taiga.io/project/penpot/issue/2221)
|
||||
- Round the size values on handoff to two decimals [Taiga #3227](https://tree.taiga.io/project/penpot/issue/3227)
|
||||
- Fix paste shapes while editing text [Taiga #2396](https://tree.taiga.io/project/penpot/issue/2396)
|
||||
- Round the size values on handoff to two decimals [Taiga #3227](https://tree.taiga.io/project/penpot/issue/3227)
|
||||
- Fix blend modes ignored in component updates [Taiga #2626](https://tree.taiga.io/project/penpot/issue/2626)
|
||||
- Fix internal error when hoverin over shape [Taiga #3237](https://tree.taiga.io/project/penpot/issue/3237)
|
||||
- Fix mouse leave in handoff close overlay animation breaks [Taiga #3173](https://tree.taiga.io/project/penpot/issue/3173)
|
||||
|
||||
5
SECURITY.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security issues to `support@penpot.app`
|
||||
@@ -25,7 +25,6 @@
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.spec.gen.alpha :as sgen]
|
||||
[clojure.test :as test]
|
||||
[clojure.test :as test]
|
||||
[clojure.tools.namespace.repl :as repl]
|
||||
[clojure.walk :refer [macroexpand-all]]
|
||||
[datoteka.core]
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<mj-section padding="24px 0 0 0">
|
||||
<mj-column width="425px">
|
||||
<mj-text align="center" font-size="14px" color="#64666A">
|
||||
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
|
||||
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<mj-section padding="24px 0 0 0">
|
||||
<mj-column width="425px">
|
||||
<mj-text align="center" font-size="14px" color="#64666A">
|
||||
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
|
||||
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<mj-section padding="24px 0 0 0">
|
||||
<mj-column width="425px">
|
||||
<mj-text align="center" font-size="14px" color="#64666A">
|
||||
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
|
||||
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<mj-section padding="24px 0 0 0">
|
||||
<mj-column width="425px">
|
||||
<mj-text align="center" font-size="14px" color="#64666A">
|
||||
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
|
||||
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -450,7 +450,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with <3 and Open Source</div>
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with <3 and Open Source</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -240,7 +240,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -440,7 +440,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with <3 and Open Source</div>
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with <3 and Open Source</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -245,7 +245,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -445,7 +445,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with <3 and Open Source</div>
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with <3 and Open Source</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -240,7 +240,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -440,7 +440,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with <3 and Open Source</div>
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with <3 and Open Source</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
(get-email info))]
|
||||
{:backend (:name provider)
|
||||
:email email
|
||||
:fullname (get-name info)
|
||||
:fullname (or (get-name info) email)
|
||||
:props (->> (dissoc info :name :email)
|
||||
(qualify-props provider))}))
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.loggers.audit :as audit]
|
||||
[app.metrics :as mtx]
|
||||
[app.rpc.permissions :as perms]
|
||||
[app.rpc.queries.files :as files]
|
||||
@@ -26,6 +27,7 @@
|
||||
[promesa.core :as p]))
|
||||
|
||||
(declare create-file)
|
||||
(declare retrieve-team-id)
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
@@ -47,8 +49,11 @@
|
||||
(sv/defmethod ::create-file
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(proj/check-edition-permissions! conn profile-id project-id)
|
||||
(create-file conn params)))
|
||||
(let [team-id (retrieve-team-id conn project-id)]
|
||||
(proj/check-edition-permissions! conn profile-id project-id)
|
||||
(with-meta
|
||||
(create-file conn params)
|
||||
{::audit/props {:team-id team-id}}))))
|
||||
|
||||
(defn create-file-role
|
||||
[conn {:keys [file-id profile-id role]}]
|
||||
@@ -245,7 +250,6 @@
|
||||
|
||||
(declare insert-change)
|
||||
(declare retrieve-lagged-changes)
|
||||
(declare retrieve-team-id)
|
||||
(declare send-notifications)
|
||||
(declare update-file)
|
||||
|
||||
@@ -279,10 +283,14 @@
|
||||
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(db/xact-lock! conn id)
|
||||
(let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true})]
|
||||
(let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true})
|
||||
team-id (retrieve-team-id conn (:project-id file))]
|
||||
(files/check-edition-permissions! conn profile-id id)
|
||||
(update-file (assoc cfg :conn conn)
|
||||
(assoc params :file file)))))
|
||||
(with-meta
|
||||
(update-file (assoc cfg :conn conn)
|
||||
(assoc params :file file))
|
||||
{::audit/props {:project-id (:project-id file)
|
||||
:team-id team-id}}))))
|
||||
|
||||
(defn- take-snapshot?
|
||||
"Defines the rule when file `data` snapshot should be saved."
|
||||
|
||||
@@ -400,7 +400,7 @@
|
||||
(defn- create-team-invitation
|
||||
[{:keys [conn tokens team profile role email] :as cfg}]
|
||||
(let [member (profile/retrieve-profile-data-by-email conn email)
|
||||
token-exp (dt/in-future "48h")
|
||||
token-exp (dt/in-future "168h") ;; 7 days
|
||||
itoken (tokens :generate
|
||||
{:iss :team-invitation
|
||||
:exp token-exp
|
||||
|
||||
@@ -53,8 +53,10 @@
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}]
|
||||
(p/let [slink (slnk/retrieve-share-link pool file-id share-id)
|
||||
perms (files/get-permissions pool profile-id file-id share-id)
|
||||
thumbs (files/retrieve-object-thumbnails cfg file-id)
|
||||
bundle (p/-> (retrieve-bundle cfg file-id)
|
||||
(assoc :permissions perms))]
|
||||
(assoc :permissions perms)
|
||||
(assoc-in [:file :thumbnails] thumbs))]
|
||||
|
||||
;; When we have neither profile nor share, we just return a not
|
||||
;; found response to the user.
|
||||
|
||||
@@ -23,7 +23,41 @@
|
||||
[expound.alpha :as expound]
|
||||
[fipp.edn :refer [pprint]]))
|
||||
|
||||
;; ==== Utility functions
|
||||
|
||||
(defn reset-file-data
|
||||
"Hardcode replace of the data of one file."
|
||||
[system id data]
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(db/update! conn :file
|
||||
{:data data}
|
||||
{:id id})))
|
||||
|
||||
(defn get-file
|
||||
"Get the migrated data of one file."
|
||||
[system id]
|
||||
(-> (:app.db/pool system)
|
||||
(db/get-by-id :file id)
|
||||
(update :data app.util.blob/decode)
|
||||
(update :data pmg/migrate-data)))
|
||||
|
||||
(defn duplicate-file
|
||||
"This is a raw version of duplication of file just only for forensic analysis."
|
||||
[system file-id email]
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(when-let [profile (some->> (prof/retrieve-profile-data-by-email conn (str/lower email))
|
||||
(prof/populate-additional-data conn))]
|
||||
(when-let [file (db/exec-one! conn (sql/select :file {:id file-id}))]
|
||||
(let [params (assoc file
|
||||
:id (uuid/next)
|
||||
:project-id (:default-project-id profile))]
|
||||
(db/insert! conn :file params)
|
||||
(:id file))))))
|
||||
|
||||
(defn update-file
|
||||
"Apply a function to the data of one file. Optionally save the changes or not.
|
||||
|
||||
The function receives the decoded and migrated file data."
|
||||
([system id f] (update-file system id f false))
|
||||
([system id f save?]
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
@@ -40,85 +74,115 @@
|
||||
{:id (:id file)}))
|
||||
(update file :data blob/decode)))))
|
||||
|
||||
(defn reset-file-data
|
||||
[system id data]
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(db/update! conn :file
|
||||
{:data data}
|
||||
{:id id})))
|
||||
(defn analyze-files
|
||||
"Apply a function to all files in the database, reading them in batches. Do not change data.
|
||||
|
||||
The function receives an object with some properties of the file and the decoded data, and
|
||||
an empty atom where it may accumulate statistics, if desired."
|
||||
[system {:keys [sleep chunk-size max-chunks on-file]
|
||||
:or {sleep 1000 chunk-size 10 max-chunks ##Inf}}]
|
||||
(let [stats (atom {})]
|
||||
(letfn [(retrieve-chunk [conn cursor]
|
||||
(let [sql (str "select id, name, modified_at, data from file "
|
||||
" where modified_at < ? and deleted_at is null "
|
||||
" order by modified_at desc limit ?")]
|
||||
(->> (db/exec! conn [sql cursor chunk-size])
|
||||
(map #(update % :data blob/decode)))))
|
||||
|
||||
(defn get-file
|
||||
[system id]
|
||||
(-> (:app.db/pool system)
|
||||
(db/get-by-id :file id)
|
||||
(update :data app.util.blob/decode)
|
||||
(update :data pmg/migrate-data)))
|
||||
(process-chunk [chunk]
|
||||
(loop [files chunk]
|
||||
(when-let [file (first files)]
|
||||
(on-file file stats)
|
||||
(recur (rest files)))))]
|
||||
|
||||
(defn duplicate-file
|
||||
"This is a raw version of duplication of file just only for forensic analysis"
|
||||
[system file-id email]
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(when-let [profile (some->> (prof/retrieve-profile-data-by-email conn (str/lower email))
|
||||
(prof/populate-additional-data conn))]
|
||||
(when-let [file (db/exec-one! conn (sql/select :file {:id file-id}))]
|
||||
(let [params (assoc file
|
||||
:id (uuid/next)
|
||||
:project-id (:default-project-id profile))]
|
||||
(db/insert! conn :file params)
|
||||
(:id file))))))
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(loop [cursor (dt/now)
|
||||
chunks 0]
|
||||
(when (< chunks max-chunks)
|
||||
(let [chunk (retrieve-chunk conn cursor)]
|
||||
(when-not (empty? chunk)
|
||||
(let [cursor (-> chunk last :modified-at)]
|
||||
(process-chunk chunk)
|
||||
(Thread/sleep (inst-ms (dt/duration sleep)))
|
||||
(recur cursor (inc chunks)))))))
|
||||
@stats))))
|
||||
|
||||
(defn repair-orphaned-components
|
||||
"We have detected some cases of component instances that are not nested, but
|
||||
however they have not the :component-root? attribute (so the system considers
|
||||
them nested). This script fixes this adding them the attribute.
|
||||
(defn update-pages
|
||||
"Apply a function to all pages of one file. The function receives a page and returns an updated page."
|
||||
[data f]
|
||||
(update data :pages-index d/update-vals f))
|
||||
|
||||
Use it with the update-file function above."
|
||||
[data]
|
||||
(let [update-page
|
||||
(fn [page]
|
||||
(prn "================= Page:" (:name page))
|
||||
(letfn [(is-nested? [object]
|
||||
(and (some? (:component-id object))
|
||||
(nil? (:component-root? object))))
|
||||
(defn update-shapes
|
||||
"Apply a function to all shapes of one page The function receives a shape and returns an updated shape"
|
||||
[page f]
|
||||
(update page :objects d/update-vals f))
|
||||
|
||||
(is-instance? [object]
|
||||
(some? (:shape-ref object)))
|
||||
|
||||
(get-parent [object]
|
||||
(get (:objects page) (:parent-id object)))
|
||||
;; ==== Specific fixes
|
||||
|
||||
(update-object [object]
|
||||
(if (and (is-nested? object)
|
||||
(not (is-instance? (get-parent object))))
|
||||
(do
|
||||
(prn "Orphan:" (:name object))
|
||||
(assoc object :component-root? true))
|
||||
object))]
|
||||
(defn repair-orphaned-shapes
|
||||
"There are some shapes whose parent has been deleted. This
|
||||
function detects them and puts them as children of the root node."
|
||||
([file _] ; to be called from analyze-files to search for files with the problem
|
||||
(repair-orphaned-shapes (:data file)))
|
||||
|
||||
(update page :objects d/update-vals update-object)))]
|
||||
([data]
|
||||
(let [is-orphan? (fn [shape objects]
|
||||
(and (some? (:parent-id shape))
|
||||
(nil? (get objects (:parent-id shape)))))
|
||||
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
update-page (fn [page]
|
||||
(let [objects (:objects page)
|
||||
orphans (set (filter #(is-orphan? % objects) (vals objects)))]
|
||||
(if (seq orphans)
|
||||
(do
|
||||
(prn (:id data) "file has" (count orphans) "broken shapes")
|
||||
(-> page
|
||||
(update-shapes (fn [shape]
|
||||
(if (orphans shape)
|
||||
(assoc shape :parent-id uuid/zero)
|
||||
shape)))
|
||||
(update-in [:objects uuid/zero :shapes]
|
||||
(fn [shapes] (into shapes (map :id orphans))))))
|
||||
page)))]
|
||||
|
||||
(defn repair-idless-components
|
||||
"There are some files that contains components with no :id attribute.
|
||||
This function detects them and repairs it.
|
||||
(update-pages data update-page))))
|
||||
|
||||
Use it with the update-file function above."
|
||||
[data]
|
||||
(letfn [(update-component [id component]
|
||||
(if (nil? (:id component))
|
||||
(do
|
||||
(prn (:id data) "Broken component" (:name component) id)
|
||||
(assoc component :id id))
|
||||
component))]
|
||||
|
||||
(update data :components #(d/mapm update-component %))))
|
||||
;; DO NOT DELETE already used scripts, could be taken as templates for easyly writing new ones
|
||||
;; -------------------------------------------------------------------------------------------
|
||||
|
||||
(defn analyze-idless-components
|
||||
"Scan all files to check if there are any one with idless components.
|
||||
(Does not save the changes, only used to detect affected files)."
|
||||
[file _]
|
||||
(repair-idless-components (:data file)))
|
||||
;; (defn repair-orphaned-components
|
||||
;; "We have detected some cases of component instances that are not nested, but
|
||||
;; however they have not the :component-root? attribute (so the system considers
|
||||
;; them nested). This script fixes this adding them the attribute.
|
||||
;;
|
||||
;; Use it with the update-file function above."
|
||||
;; [data]
|
||||
;; (let [update-page
|
||||
;; (fn [page]
|
||||
;; (prn "================= Page:" (:name page))
|
||||
;; (letfn [(is-nested? [object]
|
||||
;; (and (some? (:component-id object))
|
||||
;; (nil? (:component-root? object))))
|
||||
;;
|
||||
;; (is-instance? [object]
|
||||
;; (some? (:shape-ref object)))
|
||||
;;
|
||||
;; (get-parent [object]
|
||||
;; (get (:objects page) (:parent-id object)))
|
||||
;;
|
||||
;; (update-object [object]
|
||||
;; (if (and (is-nested? object)
|
||||
;; (not (is-instance? (get-parent object))))
|
||||
;; (do
|
||||
;; (prn "Orphan:" (:name object))
|
||||
;; (assoc object :component-root? true))
|
||||
;; object))]
|
||||
;;
|
||||
;; (update page :objects d/update-vals update-object)))]
|
||||
;;
|
||||
;; (update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; (defn check-image-shapes
|
||||
;; [{:keys [data] :as file} stats]
|
||||
@@ -138,32 +202,3 @@
|
||||
;; (when @affected?
|
||||
;; (swap! stats update :affected-files (fnil inc 0)))))
|
||||
|
||||
(defn analyze-files
|
||||
[system {:keys [sleep chunk-size max-chunks on-file]
|
||||
:or {sleep 1000 chunk-size 10 max-chunks ##Inf}}]
|
||||
(let [stats (atom {})]
|
||||
(letfn [(retrieve-chunk [conn cursor]
|
||||
(let [sql (str "select id, name, modified_at, data from file "
|
||||
" where modified_at < ? and deleted_at is null "
|
||||
" order by modified_at desc limit ?")]
|
||||
(->> (db/exec! conn [sql cursor chunk-size])
|
||||
(map #(update % :data blob/decode)))))
|
||||
|
||||
(process-chunk [chunk]
|
||||
(loop [items chunk]
|
||||
(when-let [item (first items)]
|
||||
(on-file item stats)
|
||||
(recur (rest items)))))]
|
||||
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(loop [cursor (dt/now)
|
||||
chunks 0]
|
||||
(when (< chunks max-chunks)
|
||||
(let [chunk (retrieve-chunk conn cursor)]
|
||||
(when-not (empty? chunk)
|
||||
(let [cursor (-> chunk last :modified-at)]
|
||||
(process-chunk chunk)
|
||||
(Thread/sleep (inst-ms (dt/duration sleep)))
|
||||
(recur cursor (inc chunks)))))))
|
||||
@stats))))
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
(dm/export gtr/update-group-selrect)
|
||||
(dm/export gtr/update-mask-selrect)
|
||||
(dm/export gtr/resize-modifiers)
|
||||
(dm/export gtr/change-orientation-modifiers)
|
||||
(dm/export gtr/rotation-modifiers)
|
||||
(dm/export gtr/merge-modifiers)
|
||||
(dm/export gtr/transform-shape)
|
||||
|
||||
@@ -28,3 +28,11 @@
|
||||
[shape]
|
||||
(gpr/points->selrect (position-data-points shape)))
|
||||
|
||||
(defn overlaps-position-data?
|
||||
"Checks if the given position data is inside the shape"
|
||||
[{:keys [points]} position-data]
|
||||
(let [bounding-box (gpr/points->selrect points)
|
||||
fix-rect #(assoc % :y (- (:y %) (:height %)))]
|
||||
(->> position-data
|
||||
(some #(gpr/overlaps-rects? bounding-box (fix-rect %)))
|
||||
(boolean))))
|
||||
|
||||
@@ -426,6 +426,31 @@
|
||||
:resize-transform shape-transform
|
||||
:resize-transform-inverse shape-transform-inv}))
|
||||
|
||||
(defn change-orientation-modifiers
|
||||
[shape orientation]
|
||||
(us/assert map? shape)
|
||||
(us/verify #{:horiz :vert} orientation)
|
||||
(let [width (:width shape)
|
||||
height (:height shape)
|
||||
new-width (if (= orientation :horiz) (max width height) (min width height))
|
||||
new-height (if (= orientation :horiz) (min width height) (max width height))
|
||||
|
||||
shape-transform (:transform shape)
|
||||
shape-transform-inv (:transform-inverse shape)
|
||||
shape-center (gco/center-shape shape)
|
||||
{sr-width :width sr-height :height} (:selrect shape)
|
||||
|
||||
origin (cond-> (gpt/point (:selrect shape))
|
||||
(some? shape-transform)
|
||||
(transform-point-center shape-center shape-transform))
|
||||
|
||||
scalev (gpt/divide (gpt/point new-width new-height)
|
||||
(gpt/point sr-width sr-height))]
|
||||
{:resize-vector scalev
|
||||
:resize-origin origin
|
||||
:resize-transform shape-transform
|
||||
:resize-transform-inverse shape-transform-inv}))
|
||||
|
||||
(defn rotation-modifiers
|
||||
[shape center angle]
|
||||
(let [displacement (let [shape-center (gco/center-shape shape)]
|
||||
|
||||
@@ -372,8 +372,8 @@
|
||||
(assert-page-id changes)
|
||||
(assert-objects changes)
|
||||
(let [page-id (::page-id (meta changes))
|
||||
objects (lookup-objects changes)
|
||||
|
||||
objects (lookup-objects changes)
|
||||
xform (comp
|
||||
(mapcat #(cons % (cph/get-parent-ids objects %)))
|
||||
(map (d/getf objects))
|
||||
@@ -409,7 +409,8 @@
|
||||
|
||||
resize-parent
|
||||
(fn [changes parent]
|
||||
(let [children (->> parent :shapes (map (d/getf objects)))
|
||||
(let [objects (lookup-objects changes)
|
||||
children (->> parent :shapes (map (d/getf objects)))
|
||||
resized-parent (cond
|
||||
(empty? children) ;; a parent with no children will be deleted,
|
||||
nil ;; so it does not need resize
|
||||
@@ -434,12 +435,12 @@
|
||||
(if (seq rops)
|
||||
(-> changes
|
||||
(update :redo-changes conj (assoc change :operations rops))
|
||||
(update :undo-changes d/preconj (assoc change :operations uops)))
|
||||
(update :undo-changes d/preconj (assoc change :operations uops))
|
||||
(apply-changes-local))
|
||||
changes))
|
||||
changes)))]
|
||||
|
||||
(-> (reduce resize-parent changes all-parents)
|
||||
(apply-changes-local))))
|
||||
(reduce resize-parent changes all-parents)))
|
||||
|
||||
;; Library changes
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
[app.common.colors :as clr]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(def file-version 18)
|
||||
(def file-version 19)
|
||||
(def default-color clr/gray-20)
|
||||
(def root uuid/zero)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.geom.shapes.text :as gsht]
|
||||
[app.common.logging :as l]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
@@ -415,5 +416,21 @@
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(update :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate 19
|
||||
[data]
|
||||
(letfn [(update-object [object]
|
||||
(cond-> object
|
||||
(and (cph/text-shape? object)
|
||||
(d/not-empty? (:position-data object))
|
||||
(not (gsht/overlaps-position-data? object (:position-data object))))
|
||||
(dissoc :position-data)))
|
||||
|
||||
(update-container [container]
|
||||
(update container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(update :components d/update-vals update-container))))
|
||||
|
||||
;; TODO: pending to do a migration for delete already not used fill
|
||||
;; and stroke props. This should be done for >1.14.x version.
|
||||
|
||||
@@ -13,19 +13,32 @@
|
||||
|
||||
;;; SHADOW EFFECT
|
||||
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::id (s/nilable uuid?))
|
||||
(s/def ::style #{:drop-shadow :inner-shadow})
|
||||
(s/def ::color ::color/color)
|
||||
(s/def ::offset-x ::us/safe-number)
|
||||
(s/def ::offset-y ::us/safe-number)
|
||||
(s/def ::blur ::us/safe-number)
|
||||
(s/def ::spread ::us/safe-number)
|
||||
(s/def ::hidden boolean?)
|
||||
|
||||
|
||||
(s/def ::color string?)
|
||||
(s/def ::opacity ::us/safe-number)
|
||||
(s/def ::gradient (s/nilable ::color/gradient))
|
||||
(s/def ::file-id (s/nilable uuid?))
|
||||
(s/def ::ref-id (s/nilable uuid?))
|
||||
|
||||
(s/def :shadow/color
|
||||
(s/keys :opt-un [::color
|
||||
::opacity
|
||||
::gradient
|
||||
::file-id
|
||||
::id]))
|
||||
|
||||
(s/def ::shadow-props
|
||||
(s/keys :req-un [:internal.shadow/id
|
||||
:internal.shadow/style
|
||||
:internal.shadow/color
|
||||
:shadow/color
|
||||
:internal.shadow/offset-x
|
||||
:internal.shadow/offset-y
|
||||
:internal.shadow/blur
|
||||
|
||||
@@ -28,6 +28,7 @@ RUN set -ex; \
|
||||
libasound2 \
|
||||
libatk1.0-0 \
|
||||
libatk-bridge2.0-0 \
|
||||
libatomic1 \
|
||||
libcairo2 \
|
||||
libcups2 \
|
||||
libdbus-1-3 \
|
||||
|
||||
@@ -79,3 +79,6 @@ PENPOT_FLAGS="enable-registration"
|
||||
# PENPOT_LDAP_ATTRS_FULLNAME=cn
|
||||
# PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto
|
||||
# PENPOT_LOGIN_WITH_LDAP=true
|
||||
|
||||
# Exporter
|
||||
PENPOT_DOMAIN_WHITE_LIST=localhost:9001
|
||||
|
||||
@@ -44,6 +44,8 @@ services:
|
||||
|
||||
penpot-exporter:
|
||||
image: "penpotapp/exporter:latest"
|
||||
env_file:
|
||||
- config.env
|
||||
environment:
|
||||
# Don't touch it; this uses internal docker network to
|
||||
# communicate with the frontend.
|
||||
|
||||
@@ -38,6 +38,8 @@ http {
|
||||
|
||||
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json;
|
||||
|
||||
resolver 127.0.0.11;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
:http-server-port 6061
|
||||
:http-server-host "localhost"
|
||||
:redis-uri "redis://redis/0"
|
||||
:exporter-domain-whitelist #{"localhost:3449"}})
|
||||
:domain-white-list #{"localhost:3449"}})
|
||||
|
||||
(s/def ::http-server-port ::us/integer)
|
||||
(s/def ::http-server-host ::us/string)
|
||||
@@ -45,7 +45,7 @@
|
||||
::http-server-host
|
||||
::browser-pool-max
|
||||
::browser-pool-min
|
||||
::domain-whitelist]))
|
||||
::domain-white-list]))
|
||||
|
||||
(defn- read-env
|
||||
[prefix]
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn render
|
||||
[{:keys [file-id page-id token scale type uri objects] :as params} on-object]
|
||||
[{:keys [file-id page-id token scale type objects] :as params} on-object]
|
||||
(letfn [(prepare-options [uri]
|
||||
#js {:screen #js {:width bw/default-viewport-width
|
||||
:height bw/default-viewport-height}
|
||||
@@ -61,7 +61,7 @@
|
||||
:page-id page-id
|
||||
:object-id (mapv :id objects)
|
||||
:route "objects"}
|
||||
uri (-> (or uri (cf/get :public-uri))
|
||||
uri (-> (cf/get :public-uri)
|
||||
(assoc :path "/render.html")
|
||||
(assoc :query (u/map->query-string params)))]
|
||||
(bw/exec! (prepare-options uri) (partial render uri)))))
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn render
|
||||
[{:keys [file-id page-id token scale type uri objects] :as params} on-object]
|
||||
[{:keys [file-id page-id token scale type objects] :as params} on-object]
|
||||
(letfn [(prepare-options [uri]
|
||||
#js {:screen #js {:width bw/default-viewport-width
|
||||
:height bw/default-viewport-height}
|
||||
@@ -63,6 +63,6 @@
|
||||
(on-object (assoc object :path path))
|
||||
(p/recur (rest objects))))))]
|
||||
|
||||
(let [base-uri (or uri (cf/get :public-uri))]
|
||||
(let [base-uri (cf/get :public-uri)]
|
||||
(bw/exec! (prepare-options base-uri)
|
||||
(partial render base-uri)))))
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
:height height}))
|
||||
|
||||
(defn render
|
||||
[{:keys [page-id file-id objects token scale suffix type uri]} on-object]
|
||||
[{:keys [page-id file-id objects token scale type]} on-object]
|
||||
(letfn [(convert-to-ppm [pngpath]
|
||||
(l/trace :fn :convert-to-ppm)
|
||||
(let [basepath (path/dirname pngpath)
|
||||
@@ -363,7 +363,7 @@
|
||||
:render-embed true
|
||||
:object-id (mapv :id objects)
|
||||
:route "objects"}
|
||||
uri (-> (or uri (cf/get :public-uri))
|
||||
uri (-> (cf/get :public-uri)
|
||||
(assoc :path "/render.html")
|
||||
(assoc :query (u/map->query-string params)))]
|
||||
(bw/exec! (prepare-options uri)
|
||||
|
||||
BIN
frontend/resources/images/features/1.14-color-group.gif
Normal file
|
After Width: | Height: | Size: 312 KiB |
BIN
frontend/resources/images/features/1.14-fix-on-scroll.gif
Normal file
|
After Width: | Height: | Size: 752 KiB |
BIN
frontend/resources/images/features/1.14-group-assets.gif
Normal file
|
After Width: | Height: | Size: 376 KiB |
BIN
frontend/resources/images/features/1.14-shortcuts.gif
Normal file
|
After Width: | Height: | Size: 374 KiB |
@@ -1,3 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
|
||||
<path d="M78.822.204l-3.3 3.3L56.69 22.335l2.698 9.296-17.315 17.316-18.998-12.4L0 59.624l32.865 32.865-31.33 31.33 6.598 6.599 31.33-31.33 33.001 33 23.076-23.075-12.128-18.726 17.316-17.315 9.432 2.834 22.132-22.13zm0 13.197l40.274 40.274-9.983 10.123-9.369-2.771-28.33 28.33 11.919 18.665-10.869 10.869-59.268-59.269 10.868-10.868 18.926 12.18 28.331-28.33-2.578-9.177z"/>
|
||||
<svg width="12" xmlns="http://www.w3.org/2000/svg" height="12" x="767" viewBox="0 0 500 499.172" y="920.67">
|
||||
<path d="M325.318.012c-21.828-.283-42.138 15.34-48.74 35.798-15.498 31.788-36.62 62.656-67.738 80.85-46.02 26.248-99.73 35.59-152.188 35.65-11.824-.026-24.336 1.564-34 9.065-18.02 12.488-27.52 36.96-20.133 58.034 4.15 13.85 14.838 24.125 25.197 33.582l86.5 86.5C84.488 381.137 30.993 456.543 1.26 498.187c41.623-29.727 117.01-83.215 158.632-112.942 37.542 36.62 68.373 70.025 106.956 105.56 23.195 15.987 59.377 8.335 73.035-16.723 7.6-12.733 8.225-27.804 7.5-42.23 1.366-50.12 11.25-101.404 37.675-144.67 20.658-31.714 53.59-52.978 87.635-68.023 20.972-10.427 31.76-36.413 25.57-58.77-2.664-11.118-9.64-20.31-17.897-27.873-37.25-36.595-73.2-74.495-110.7-110.84-7.227-6.593-13.718-14.767-23.268-17.904-6.682-2.644-13.898-3.908-21.08-3.758zm-.26 36.837c45.35 46.82 90.704 93.64 136.056 140.46-40.437 18.15-79.907 42.182-106.303 78.613-30.216 43.44-43.98 96.178-48.765 148.346-1.852 21.542-2.097 43.256-1.726 64.933-91.546-91.52-183.09-183.044-274.634-274.564 60.078 2.462 121.696-4.117 176.98-28.967 38.773-15.762 70.437-46.032 91.712-81.66 9.637-15.522 17.813-31.905 25.36-48.525z" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 481 B After Width: | Height: | Size: 1.2 KiB |
3
frontend/resources/images/icons/shortcut.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="48.265" style="-webkit-print-color-adjust:exact" viewBox="-3.536 -3.536 64 48.265">
|
||||
<path d="M-3.536 20.597V-3.536h64V44.73h-64zm59.37 0V1.096l-27.356.014-27.354.014L1.11 20.54c-.01 10.68 0 19.45.012 19.488.02.055 5.546.07 27.369.07h27.342V20.596zM12.701 31.254V28.94h31.525v4.631H12.702zM7.159 22.43c-.02-.02-.037-1.062-.037-2.316v-2.278H14.543v4.631H10.87c-2.02 0-3.69-.016-3.71-.037zm12.071-2.278v-2.316h7.422l-.015 2.302-.014 2.301-3.696.015-3.697.014zm12.052 0v-2.316h7.423l-.014 2.302-.015 2.301-3.697.015-3.697.014zm12.053 0v-2.316h7.42v4.631h-7.42zM7.135 9.005l.015-2.302 3.697-.014 3.696-.014v4.632H7.121zm12.095-.014V6.675l3.697.014 3.696.014.014 2.302.015 2.302H19.23zm12.052 0V6.675H38.703v4.632h-7.42zm12.053 0V6.675h7.42v4.632h-7.42z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 818 B |
@@ -3,8 +3,9 @@
|
||||
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
|
||||
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
|
||||
|
||||
var parent = this.parentNode,
|
||||
parentComputedStyle = window.getComputedStyle(parent, null),
|
||||
var parent = this.parentNode;
|
||||
if (parent) {
|
||||
var parentComputedStyle = window.getComputedStyle(parent, null),
|
||||
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
|
||||
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
|
||||
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
|
||||
@@ -13,14 +14,15 @@
|
||||
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
|
||||
alignWithTop = overTop && !overBottom;
|
||||
|
||||
if ((overTop || overBottom) && centerIfNeeded) {
|
||||
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
|
||||
}
|
||||
if ((overLeft || overRight) && centerIfNeeded) {
|
||||
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
|
||||
}
|
||||
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
|
||||
this.scrollIntoView(alignWithTop);
|
||||
if ((overTop || overBottom) && centerIfNeeded) {
|
||||
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
|
||||
}
|
||||
if ((overLeft || overRight) && centerIfNeeded) {
|
||||
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
|
||||
}
|
||||
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
|
||||
this.scrollIntoView(alignWithTop);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ textarea {
|
||||
max-width: 85%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 15px;
|
||||
line-height: 16px;
|
||||
font-size: 14px;
|
||||
color: $color-black;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
|
||||
.left-toolbar {
|
||||
background-color: $color-gray-50;
|
||||
}
|
||||
|
||||
.left-toolbar-inside {
|
||||
align-items: center;
|
||||
border-right: 1px solid $color-gray-60;
|
||||
display: flex;
|
||||
@@ -25,39 +22,46 @@
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
height: 48px;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 48px;
|
||||
color: $color-gray-20;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-20;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-primary;
|
||||
color: $color-gray-50;
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: inherit;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-50;
|
||||
fill: $color-gray-20;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: $color-gray-60;
|
||||
color: $color-primary;
|
||||
&:hover {
|
||||
background-color: $color-primary;
|
||||
color: $color-gray-50;
|
||||
|
||||
svg {
|
||||
fill: $color-primary;
|
||||
svg {
|
||||
fill: $color-gray-50;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: $color-gray-60;
|
||||
color: $color-primary;
|
||||
|
||||
svg {
|
||||
fill: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&.separator {
|
||||
border-top: 1px solid $color-gray-60;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1231,7 +1231,7 @@
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 117px;
|
||||
min-width: 117px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1254,7 +1254,18 @@
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
width: 118px;
|
||||
width: 180px;
|
||||
overflow: hidden;
|
||||
justify-content: normal;
|
||||
select {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
fill: $color-gray-20;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -323,12 +323,41 @@
|
||||
border: 2px solid $color-primary;
|
||||
}
|
||||
|
||||
.grid-placeholder {
|
||||
border: 2px solid $color-gray-20;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.drop-space {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.typography-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.drag-counter {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: $color-primary;
|
||||
border-radius: 50%;
|
||||
color: $color-black;
|
||||
font-size: $fs12;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.asset-title + .asset-enum {
|
||||
margin-top: $size-2;
|
||||
}
|
||||
|
||||
.asset-enum {
|
||||
.enum-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $size-2;
|
||||
@@ -370,6 +399,10 @@
|
||||
.enum-item.selected {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
.grid-placeholder {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: see if this is useful, or is better to leave only
|
||||
@@ -390,6 +423,7 @@
|
||||
font-size: $fs12;
|
||||
color: $color-white;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
& span {
|
||||
margin-left: $size-1;
|
||||
@@ -418,6 +452,15 @@
|
||||
background-color: $color-gray-60;
|
||||
}
|
||||
}
|
||||
|
||||
.dragging {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: color.adjust($color-primary, $alpha: -0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1450,7 +1450,8 @@
|
||||
|
||||
.input-select {
|
||||
font-size: $fs12;
|
||||
margin: 0 $size-1;
|
||||
margin: 0 $size-1 $size-2 $size-1;
|
||||
padding: 0 $size-1;
|
||||
}
|
||||
|
||||
svg {
|
||||
@@ -1471,6 +1472,7 @@
|
||||
.fix-when {
|
||||
font-size: $fs12;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
margin-left: $size-2;
|
||||
@@ -1540,3 +1542,44 @@
|
||||
margin-right: $size-2;
|
||||
}
|
||||
}
|
||||
|
||||
.expand-colors {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
||||
.text {
|
||||
color: $color-gray-30;
|
||||
font-size: 0.75rem;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: $color-gray-20;
|
||||
stroke: $color-gray-20;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-colors {
|
||||
.color-data {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 5px;
|
||||
|
||||
svg {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.percentil {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.color-data:hover {
|
||||
background-color: $color-gray-60;
|
||||
|
||||
svg {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,3 +292,183 @@ button.collapse-sidebar {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.shortcuts {
|
||||
.shortcuts-header {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
background-color: $color-gray-60;
|
||||
.shortcuts-title {
|
||||
color: $color-white;
|
||||
font-size: $fs12;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
svg {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transform: rotate(45deg);
|
||||
fill: $color-gray-20;
|
||||
}
|
||||
}
|
||||
.shortcuts-close-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 2px 0 2px 15px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
svg {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transform: rotate(45deg);
|
||||
fill: $color-gray-20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-field {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 12px 10px;
|
||||
.search-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border: 1px solid $color-gray-30;
|
||||
border-radius: 2px;
|
||||
width: 100%;
|
||||
.input-text {
|
||||
margin: 0;
|
||||
background: $color-gray-50;
|
||||
width: 100%;
|
||||
color: $color-white;
|
||||
&:focus {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
&.close {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 7px;
|
||||
cursor: pointer;
|
||||
fill: $color-gray-20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-list {
|
||||
border-top: 1px solid $color-gray-60;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
height: 90%;
|
||||
margin-bottom: 15px;
|
||||
.section-title {
|
||||
background-color: $color-gray-60;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.section-title,
|
||||
.subsection-title {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
font-size: $fs12;
|
||||
|
||||
.section-name {
|
||||
color: $color-white;
|
||||
}
|
||||
.collapesed-shortcuts {
|
||||
padding: 0 10px;
|
||||
svg {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
fill: $color-gray-20;
|
||||
}
|
||||
&.open {
|
||||
svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.shortcut-count {
|
||||
padding-left: 5px;
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
||||
.subsection-title {
|
||||
padding: 4px 0px;
|
||||
.subsection-name {
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title,
|
||||
.subsection-title {
|
||||
&:hover {
|
||||
background-color: $color-primary;
|
||||
.subsection-name,
|
||||
.section-name {
|
||||
color: $color-gray-60;
|
||||
}
|
||||
svg {
|
||||
fill: $color-gray-60;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-name {
|
||||
border: 1px solid $color-gray-60;
|
||||
border-radius: 4px;
|
||||
padding: 7px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 4px;
|
||||
color: $color-white;
|
||||
font-size: $fs12;
|
||||
.keys {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.char-box {
|
||||
min-width: 15px;
|
||||
background-color: $color-white;
|
||||
color: $color-black;
|
||||
border-radius: 3px;
|
||||
padding: 2px 5px;
|
||||
font-size: $fs11;
|
||||
font-weight: 600;
|
||||
margin: 0 2px;
|
||||
text-transform: capitalize;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
.space {
|
||||
margin: 0 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.not-found {
|
||||
background-color: $color-gray-60;
|
||||
padding: 4px 0;
|
||||
color: $color-white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 4px;
|
||||
font-size: $fs12;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 183px;
|
||||
width: 195px;
|
||||
z-index: 12;
|
||||
|
||||
background-color: $color-white;
|
||||
@@ -192,7 +192,7 @@
|
||||
background-color: $color-primary-lighter;
|
||||
}
|
||||
|
||||
&.feedback {
|
||||
&.info {
|
||||
border-top: 1px solid $color-gray-10;
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@
|
||||
|
||||
.sub-menu {
|
||||
position: absolute;
|
||||
left: 230px;
|
||||
left: 238px;
|
||||
width: 270px;
|
||||
z-index: 12;
|
||||
background-color: $color-white;
|
||||
@@ -234,6 +234,10 @@
|
||||
top: 150px;
|
||||
}
|
||||
|
||||
&.help-info {
|
||||
top: 186px;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
font-size: $fs14;
|
||||
@@ -254,6 +258,10 @@
|
||||
&:hover {
|
||||
background-color: $color-primary-lighter;
|
||||
}
|
||||
|
||||
&.separator {
|
||||
border-top: 1px solid $color-gray-10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@
|
||||
(dissoc :dashboard-shared-files)
|
||||
(dissoc :dashboard-recent-files)
|
||||
(dissoc :dashboard-team-members)
|
||||
(dissoc :dashboard-team-stats)))))
|
||||
(dissoc :dashboard-team-stats)
|
||||
(update :workspace-global dissoc :default-font)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
@@ -326,11 +327,9 @@
|
||||
|
||||
;; --- EVENT: create-team-with-invitations
|
||||
|
||||
;; NOTE: right now, it only handles a single email, in a near future
|
||||
;; this will be changed to the ability to specify multiple emails.
|
||||
|
||||
(defn create-team-with-invitations
|
||||
[{:keys [name email role] :as params}]
|
||||
[{:keys [name emails role] :as params}]
|
||||
(us/assert string? name)
|
||||
(ptk/reify ::create-team-with-invitations
|
||||
ptk/WatchEvent
|
||||
@@ -339,7 +338,7 @@
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
params {:name name
|
||||
:emails #{email}
|
||||
:emails #{emails}
|
||||
:role role}]
|
||||
(->> (rp/mutation! :create-team-and-invite-members params)
|
||||
(rx/tap on-success)
|
||||
|
||||
@@ -13,19 +13,23 @@
|
||||
(def shortcuts
|
||||
{:go-to-search {:tooltip (ds/meta "F")
|
||||
:command (ds/c-mod "f")
|
||||
:fn (st/emitf (dd/go-to-search))}
|
||||
:subsections [:navigation-dashboard]
|
||||
:fn #(st/emit! (dd/go-to-search))}
|
||||
|
||||
:go-to-drafts {:tooltip "G D"
|
||||
:command "g d"
|
||||
:fn (st/emitf (dd/go-to-drafts))}
|
||||
:subsections [:navigation-dashboard]
|
||||
:fn #(st/emit! (dd/go-to-drafts))}
|
||||
|
||||
:go-to-libs {:tooltip "G L"
|
||||
:command "g l"
|
||||
:fn (st/emitf (dd/go-to-libs))}
|
||||
:subsections [:navigation-dashboard]
|
||||
:fn #(st/emit! (dd/go-to-libs))}
|
||||
|
||||
:create-new-project {:tooltip "+"
|
||||
:command "+"
|
||||
:fn (st/emitf (dd/create-element))}})
|
||||
:subsections [:general-dashboard]
|
||||
:fn #(st/emit! (dd/create-element))}})
|
||||
|
||||
(defn get-tooltip [shortcut]
|
||||
(assert (contains? shortcuts shortcut) (str shortcut))
|
||||
|
||||
@@ -157,8 +157,13 @@
|
||||
accepting invitation, or third party auth signup or singin."
|
||||
[profile]
|
||||
(letfn [(get-redirect-event []
|
||||
(let [team-id (:default-team-id profile)]
|
||||
(rt/nav' :dashboard-projects {:team-id team-id})))]
|
||||
(let [team-id (:default-team-id profile)
|
||||
redirect-url (:redirect-url @storage)]
|
||||
(if (some? redirect-url)
|
||||
(do
|
||||
(swap! storage dissoc :redirect-url)
|
||||
(.replace js/location redirect-url))
|
||||
(rt/nav' :dashboard-projects {:team-id team-id}))))]
|
||||
(ptk/reify ::logged-in
|
||||
IDeref
|
||||
(-deref [_] profile)
|
||||
|
||||
@@ -13,50 +13,62 @@
|
||||
(def shortcuts
|
||||
{:increase-zoom {:tooltip "+"
|
||||
:command "+"
|
||||
:fn (st/emitf dv/increase-zoom)}
|
||||
:subsections [:zoom-viewer]
|
||||
:fn #(st/emit! dv/increase-zoom)}
|
||||
|
||||
:decrease-zoom {:tooltip "-"
|
||||
:command "-"
|
||||
:fn (st/emitf dv/decrease-zoom)}
|
||||
:subsections [:zoom-viewer]
|
||||
:fn #(st/emit! dv/decrease-zoom)}
|
||||
|
||||
:select-all {:tooltip (ds/meta "A")
|
||||
:command (ds/c-mod "a")
|
||||
:fn (st/emitf (dv/select-all))}
|
||||
:subsections [:general-viewer]
|
||||
:fn #(st/emit! (dv/select-all))}
|
||||
|
||||
:reset-zoom {:tooltip (ds/shift "0")
|
||||
:command "shift+0"
|
||||
:fn (st/emitf dv/reset-zoom)}
|
||||
:subsections [:zoom-viewer]
|
||||
:fn #(st/emit! dv/reset-zoom)}
|
||||
|
||||
:toggle-zoom-style {:tooltip "F"
|
||||
:command "f"
|
||||
:fn (st/emitf dv/toggle-zoom-style)}
|
||||
:subsections [:zoom-viewer]
|
||||
:fn #(st/emit! dv/toggle-zoom-style)}
|
||||
|
||||
:toogle-fullscreen {:tooltip (ds/shift "F")
|
||||
:command "shift+f"
|
||||
:fn (st/emitf dv/toggle-fullscreen)}
|
||||
:subsections [:zoom-viewer]
|
||||
:fn #(st/emit! dv/toggle-fullscreen)}
|
||||
|
||||
:next-frame {:tooltip ds/left-arrow
|
||||
:command "left"
|
||||
:fn (st/emitf dv/select-prev-frame)}
|
||||
:subsections [:general-viewer]
|
||||
:fn #(st/emit! dv/select-prev-frame)}
|
||||
|
||||
:prev-frame {:tooltip ds/right-arrow
|
||||
:command "right"
|
||||
:fn (st/emitf dv/select-next-frame)}
|
||||
:subsections [:general-viewer]
|
||||
:fn #(st/emit! dv/select-next-frame)}
|
||||
|
||||
:open-handoff {:tooltip "G H"
|
||||
:command "g h"
|
||||
:subsections [:navigation-viewer]
|
||||
:fn #(st/emit! (dv/go-to-section :handoff))}
|
||||
|
||||
:open-comments {:tooltip "G C"
|
||||
:command "g c"
|
||||
:subsections [:navigation-viewer]
|
||||
:fn #(st/emit! (dv/go-to-section :comments))}
|
||||
|
||||
:open-interactions {:tooltip "G V"
|
||||
:command "g v"
|
||||
:subsections [:navigation-viewer]
|
||||
:fn #(st/emit! (dv/go-to-section :interactions))}
|
||||
|
||||
:open-workspace {:tooltip "G W"
|
||||
:command "g w"
|
||||
:subsections [:navigation-viewer]
|
||||
:fn #(st/emit! (dv/go-to-workspace))}})
|
||||
|
||||
(defn get-tooltip [shortcut]
|
||||
|
||||
@@ -150,7 +150,11 @@
|
||||
:workspace-undo {}
|
||||
:workspace-project project
|
||||
:workspace-file (assoc file :initialized true)
|
||||
:workspace-data (:data file)
|
||||
:workspace-data (-> (:data file)
|
||||
;; DEBUG: Uncomment this to try out migrations in local without changing
|
||||
;; the version number
|
||||
#_(assoc :version 17)
|
||||
#_(app.common.pages.migrations/migrate-data 19))
|
||||
:workspace-libraries (d/index-by :id libraries)))
|
||||
|
||||
ptk/WatchEvent
|
||||
@@ -1694,6 +1698,7 @@
|
||||
|
||||
(dm/export dwt/start-resize)
|
||||
(dm/export dwt/update-dimensions)
|
||||
(dm/export dwt/change-orientation)
|
||||
(dm/export dwt/start-rotate)
|
||||
(dm/export dwt/increase-rotation)
|
||||
(dm/export dwt/start-move-selected)
|
||||
|
||||
@@ -284,6 +284,15 @@
|
||||
(assoc :strokes []))
|
||||
(assoc-in [:strokes index] new-attrs))))))))))
|
||||
|
||||
(defn change-shadow
|
||||
[ids attrs index]
|
||||
(ptk/reify ::change-shadow
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dch/update-shapes ids (fn [shape]
|
||||
(let [new-attrs (merge (get-in shape [:shadow index :color]) attrs)]
|
||||
(assoc-in shape [:shadow index :color] new-attrs))))))))
|
||||
|
||||
(defn add-stroke
|
||||
[ids stroke]
|
||||
(ptk/reify ::add-stroke
|
||||
@@ -396,6 +405,36 @@
|
||||
(-> state
|
||||
(assoc-in [:workspace-global :editing-stop] spot)))))
|
||||
|
||||
(defn color-att->text
|
||||
[color]
|
||||
{:fill-color (:color color)
|
||||
:fill-opacity (:opacity color)
|
||||
:fill-color-ref-id (:id color)
|
||||
:fill-color-ref-file (:file-id color)
|
||||
:fill-color-gradient (:gradient color)})
|
||||
|
||||
(defn change-text-color
|
||||
[old-color new-color index node]
|
||||
(let [fills (:fills node)
|
||||
parsed-color (d/without-nils (color-att->text old-color))
|
||||
parsed-new-color (d/without-nils (color-att->text new-color))
|
||||
has-color? (d/index-of fills parsed-color)]
|
||||
(cond-> node
|
||||
(some? has-color?)
|
||||
(assoc-in [:fills index] parsed-new-color))))
|
||||
|
||||
(defn change-color-in-selected
|
||||
[new-color shapes-by-color old-color]
|
||||
(ptk/reify ::change-color-in-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rx/from shapes-by-color)
|
||||
(rx/map (fn [shape] (case (:prop shape)
|
||||
:fill (change-fill [(:shape-id shape)] new-color (:index shape))
|
||||
:stroke (change-stroke [(:shape-id shape)] new-color (:index shape))
|
||||
:shadow (change-shadow [(:shape-id shape)] new-color (:index shape))
|
||||
:content (dwt/update-text-with-function (:shape-id shape) (partial change-text-color old-color new-color (:index shape))))))))))
|
||||
|
||||
(defn apply-color-from-palette
|
||||
[color is-alt?]
|
||||
(ptk/reify ::apply-color-from-palette
|
||||
@@ -415,4 +454,4 @@
|
||||
ids (mapcat #(select-shapes-for-color % objects) selected-obj)]
|
||||
(if is-alt?
|
||||
(rx/of (change-stroke ids (merge uc/empty-color color) 0))
|
||||
(rx/of (change-fill ids (merge uc/empty-color color) 0)))))))
|
||||
(rx/of (change-fill ids (merge uc/empty-color color) 0)))))))
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
(let [edition (get-in state [:workspace-local :edition])
|
||||
drawing (get state :workspace-drawing)]
|
||||
;; Editors handle their own undo's
|
||||
(when-not (or (some? edition) (and (not-empty drawing) (nil? (:object drawing))))
|
||||
(when (and (nil? edition) (nil? (:object drawing)))
|
||||
(let [undo (:workspace-undo state)
|
||||
items (:items undo)
|
||||
index (or (:index undo) (dec (count items)))]
|
||||
@@ -420,19 +420,26 @@
|
||||
(reverse)
|
||||
(into (d/ordered-set)))
|
||||
|
||||
empty-parents-xform
|
||||
(comp
|
||||
(map (fn [id] (get objects id)))
|
||||
(map (fn [{:keys [shapes type] :as obj}]
|
||||
(when (and (= :group type)
|
||||
(zero? (count (remove #(contains? ids %) shapes))))
|
||||
obj)))
|
||||
(take-while some?)
|
||||
(map :id))
|
||||
find-all-empty-parents (fn recursive-find-empty-parents [empty-parents]
|
||||
(let [all-ids (into empty-parents ids)
|
||||
empty-parents-xform
|
||||
(comp
|
||||
(map (fn [id] (get objects id)))
|
||||
(map (fn [{:keys [shapes type] :as obj}]
|
||||
(when (and (= :group type)
|
||||
(zero? (count (remove #(contains? all-ids %) shapes))))
|
||||
obj)))
|
||||
(take-while some?)
|
||||
(map :id))
|
||||
calculated-empty-parents (into #{} empty-parents-xform all-parents)]
|
||||
|
||||
(if (= empty-parents calculated-empty-parents)
|
||||
empty-parents
|
||||
(recursive-find-empty-parents calculated-empty-parents))))
|
||||
|
||||
empty-parents
|
||||
;; Any parent whose children are all deleted, must be deleted too.
|
||||
(into (d/ordered-set) empty-parents-xform all-parents)
|
||||
(into (d/ordered-set) (find-all-empty-parents #{}))
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-page page)
|
||||
@@ -448,13 +455,13 @@
|
||||
(pcb/update-shapes (map :id interacting-shapes)
|
||||
(fn [shape]
|
||||
(update shape :interactions
|
||||
(fn [interactions]
|
||||
(when interactions
|
||||
(d/removev #(and (csi/has-destination %)
|
||||
(contains? ids (:destination %)))
|
||||
interactions))))))
|
||||
(fn [interactions]
|
||||
(when interactions
|
||||
(d/removev #(and (csi/has-destination %)
|
||||
(contains? ids (:destination %)))
|
||||
interactions))))))
|
||||
(cond->
|
||||
(seq starting-flows)
|
||||
(seq starting-flows)
|
||||
(pcb/update-page-option :flows (fn [flows]
|
||||
(reduce #(csp/remove-flow %1 (:id %2))
|
||||
flows
|
||||
|
||||
@@ -126,23 +126,40 @@
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :color-for-rename] nil))))
|
||||
|
||||
(defn- do-update-color
|
||||
[it state color file-id]
|
||||
(let [data (get state :workspace-data)
|
||||
[path name] (cph/parse-path-name (:name color))
|
||||
color (assoc color :path path :name name)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/update-color color))]
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dch/commit-changes changes)
|
||||
(sync-file (:current-file-id state) file-id)
|
||||
(dwu/commit-undo-transaction))))
|
||||
|
||||
(defn update-color
|
||||
[color file-id]
|
||||
(us/assert ::spec.color/color color)
|
||||
(us/assert ::us/uuid file-id)
|
||||
(ptk/reify ::update-color
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(do-update-color it state color file-id))))
|
||||
|
||||
(defn rename-color
|
||||
[file-id id new-name]
|
||||
(us/assert ::us/uuid file-id)
|
||||
(us/assert ::us/uuid id)
|
||||
(us/assert ::us/string new-name)
|
||||
(ptk/reify ::rename-color
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [data (get state :workspace-data)
|
||||
[path name] (cph/parse-path-name (:name color))
|
||||
color (assoc color :path path :name name)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/update-color color))]
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dch/commit-changes changes)
|
||||
(sync-file (:current-file-id state) file-id)
|
||||
(dwu/commit-undo-transaction))))))
|
||||
object (get-in data [:colors id])
|
||||
new-object (assoc object :name new-name)]
|
||||
(do-update-color it state new-object file-id)))))
|
||||
|
||||
(defn delete-color
|
||||
[{:keys [id] :as params}]
|
||||
@@ -183,6 +200,7 @@
|
||||
(pcb/update-media new-object))]
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
|
||||
|
||||
(defn delete-media
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
@@ -213,26 +231,39 @@
|
||||
edit?
|
||||
(assoc-in [:workspace-global :rename-typography] (:id typography))))))))))
|
||||
|
||||
(defn- do-update-tipography
|
||||
[it state typography file-id]
|
||||
(let [data (get state :workspace-data)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/update-typography typography))]
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dch/commit-changes changes)
|
||||
(sync-file (:current-file-id state) file-id)
|
||||
(dwu/commit-undo-transaction))))
|
||||
|
||||
(defn update-typography
|
||||
[typography file-id]
|
||||
(us/assert ::spec.typography/typography typography)
|
||||
(us/assert ::us/uuid file-id)
|
||||
(ptk/reify ::update-typography
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(do-update-tipography it state typography file-id))))
|
||||
|
||||
(defn rename-typography
|
||||
[file-id id new-name]
|
||||
(us/assert ::us/uuid file-id)
|
||||
(us/assert ::us/uuid id)
|
||||
(us/assert ::us/string new-name)
|
||||
(ptk/reify ::rename-typography
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [data (get state :workspace-data)
|
||||
[path name] (cph/parse-path-name (:name typography))
|
||||
path (if (and (:path typography) (= "" path))
|
||||
(:path typography)
|
||||
path)
|
||||
typography (assoc typography :path path :name name)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/update-typography typography))]
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dch/commit-changes changes)
|
||||
(sync-file (:current-file-id state) file-id)
|
||||
(dwu/commit-undo-transaction))))))
|
||||
[path name] (cph/parse-path-name new-name)
|
||||
object (get-in data [:typographies id])
|
||||
new-object (assoc object :path path :name name)]
|
||||
(do-update-tipography it state new-object file-id)))))
|
||||
|
||||
(defn delete-typography
|
||||
[id]
|
||||
|
||||
@@ -189,10 +189,14 @@
|
||||
(s/def ::file-change-event
|
||||
(s/keys :req-un [::type ::profile-id ::file-id ::session-id ::revn ::changes]))
|
||||
|
||||
|
||||
(defn handle-file-change
|
||||
[{:keys [file-id changes] :as msg}]
|
||||
(us/assert ::file-change-event msg)
|
||||
(ptk/reify ::handle-file-change
|
||||
IDeref
|
||||
(-deref [_] {:changes changes})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [position-data-operation?
|
||||
|
||||
@@ -34,42 +34,52 @@
|
||||
(def shortcuts
|
||||
{:move-nodes {:tooltip "M"
|
||||
:command "m"
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/change-edit-mode :move))}
|
||||
|
||||
:draw-nodes {:tooltip "P"
|
||||
:command "p"
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/change-edit-mode :draw))}
|
||||
|
||||
:add-node {:tooltip (ds/shift "+")
|
||||
:command "shift++"
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/add-node))}
|
||||
|
||||
:delete-node {:tooltip (ds/supr)
|
||||
:command ["del" "backspace"]
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/remove-node))}
|
||||
|
||||
:merge-nodes {:tooltip (ds/meta "J")
|
||||
:command (ds/c-mod "j")
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/merge-nodes))}
|
||||
|
||||
:join-nodes {:tooltip "J"
|
||||
:command "j"
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/join-nodes))}
|
||||
|
||||
:separate-nodes {:tooltip "K"
|
||||
:command "k"
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/separate-nodes))}
|
||||
|
||||
:make-corner {:tooltip "X"
|
||||
:command "x"
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/make-corner))}
|
||||
|
||||
:make-curve {:tooltip "C"
|
||||
:command "c"
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/make-curve))}
|
||||
|
||||
:snap-nodes {:tooltip (ds/meta "'")
|
||||
:command (ds/c-mod "'")
|
||||
:subsections [:path-editor]
|
||||
:fn #(st/emit! (drp/toggle-snap))}
|
||||
|
||||
:escape {:tooltip (ds/esc)
|
||||
|
||||
@@ -34,356 +34,464 @@
|
||||
;; Shortcuts format https://github.com/ccampbell/mousetrap
|
||||
|
||||
(def base-shortcuts
|
||||
{:toggle-layers {:tooltip (ds/alt "L")
|
||||
:command (ds/a-mod "l")
|
||||
:fn #(st/emit! (dw/go-to-layout :layers))}
|
||||
|
||||
:toggle-assets {:tooltip (ds/alt "I")
|
||||
:command (ds/a-mod "i")
|
||||
:fn #(st/emit! (dw/go-to-layout :assets))}
|
||||
|
||||
:toggle-history {:tooltip (ds/alt "H")
|
||||
:command (ds/a-mod "h")
|
||||
:fn #(st/emit! (dw/go-to-layout :document-history))}
|
||||
|
||||
:toggle-colorpalette {:tooltip (ds/alt "P")
|
||||
:command (ds/a-mod "p")
|
||||
:fn #(do (r/set-resize-type! :bottom)
|
||||
(st/emit! (dw/remove-layout-flag :textpalette)
|
||||
(toggle-layout-flag :colorpalette)))}
|
||||
|
||||
:toggle-textpalette {:tooltip (ds/alt "T")
|
||||
:command (ds/a-mod "t")
|
||||
:fn #(do (r/set-resize-type! :bottom)
|
||||
(st/emit! (dw/remove-layout-flag :colorpalette)
|
||||
(toggle-layout-flag :textpalette)))}
|
||||
|
||||
:toggle-rules {:tooltip (ds/meta-shift "R")
|
||||
:command (ds/c-mod "shift+r")
|
||||
:fn #(st/emit! (toggle-layout-flag :rules))}
|
||||
|
||||
:export-shapes {:tooltip (ds/meta-shift "E")
|
||||
:command (ds/c-mod "shift+e")
|
||||
:fn #(st/emit!
|
||||
(de/show-workspace-export-dialog))}
|
||||
|
||||
:select-all {:tooltip (ds/meta "A")
|
||||
:command (ds/c-mod "a")
|
||||
:fn #(st/emit! (dw/select-all))}
|
||||
|
||||
:toggle-grid {:tooltip (ds/meta "'")
|
||||
:command (ds/c-mod "'")
|
||||
:fn #(st/emit! (toggle-layout-flag :display-grid))}
|
||||
|
||||
:toggle-snap-grid {:tooltip (ds/meta-shift "'")
|
||||
:command (ds/c-mod "shift+'")
|
||||
:fn #(st/emit! (toggle-layout-flag :snap-grid))}
|
||||
|
||||
:toggle-snap-guide {:tooltip (ds/meta-shift "G")
|
||||
:command (ds/c-mod "shift+G")
|
||||
:fn #(st/emit! (toggle-layout-flag :snap-guides))}
|
||||
|
||||
:toggle-alignment {:tooltip (ds/meta "\\")
|
||||
:command (ds/c-mod "\\")
|
||||
:fn #(st/emit! (toggle-layout-flag :dynamic-alignment))}
|
||||
|
||||
:toggle-scale-text {:tooltip "K"
|
||||
:command "k"
|
||||
:fn #(st/emit! (toggle-layout-flag :scale-text))}
|
||||
|
||||
:increase-zoom {:tooltip "+"
|
||||
:command ["+" "="]
|
||||
:fn #(st/emit! (dw/increase-zoom nil))}
|
||||
|
||||
:decrease-zoom {:tooltip "-"
|
||||
:command ["-" "_"]
|
||||
:fn #(st/emit! (dw/decrease-zoom nil))}
|
||||
|
||||
:group {:tooltip (ds/meta "G")
|
||||
:command (ds/c-mod "g")
|
||||
:fn #(st/emit! dw/group-selected)}
|
||||
|
||||
:ungroup {:tooltip (ds/shift "G")
|
||||
:command "shift+g"
|
||||
:fn #(st/emit! dw/ungroup-selected)}
|
||||
|
||||
:mask {:tooltip (ds/meta "M")
|
||||
:command (ds/c-mod "m")
|
||||
:fn #(st/emit! dw/mask-group)}
|
||||
|
||||
:unmask {:tooltip (ds/meta-shift "M")
|
||||
:command (ds/c-mod "shift+m")
|
||||
:fn #(st/emit! dw/unmask-group)}
|
||||
|
||||
:create-component {:tooltip (ds/meta "K")
|
||||
:command (ds/c-mod "k")
|
||||
:fn #(st/emit! (dwl/add-component))}
|
||||
|
||||
:detach-component {:tooltip (ds/meta-shift "K")
|
||||
:command (ds/c-mod "shift+k")
|
||||
:fn #(st/emit! dwl/detach-selected-components)}
|
||||
|
||||
:flip-vertical {:tooltip (ds/shift "V")
|
||||
:command "shift+v"
|
||||
:fn #(st/emit! (dw/flip-vertical-selected))}
|
||||
|
||||
:flip-horizontal {:tooltip (ds/shift "H")
|
||||
:command "shift+h"
|
||||
:fn #(st/emit! (dw/flip-horizontal-selected))}
|
||||
|
||||
:reset-zoom {:tooltip (ds/shift "0")
|
||||
:command "shift+0"
|
||||
:fn #(st/emit! dw/reset-zoom)}
|
||||
|
||||
:fit-all {:tooltip (ds/shift "1")
|
||||
:command "shift+1"
|
||||
:fn #(st/emit! dw/zoom-to-fit-all)}
|
||||
|
||||
:zoom-selected {:tooltip (ds/shift "2")
|
||||
:command ["shift+2" "@" "\""]
|
||||
:fn #(st/emit! dw/zoom-to-selected-shape)}
|
||||
|
||||
:duplicate {:tooltip (ds/meta "D")
|
||||
:command (ds/c-mod "d")
|
||||
:fn #(st/emit! (dw/duplicate-selected true))}
|
||||
|
||||
{;; EDIT
|
||||
:undo {:tooltip (ds/meta "Z")
|
||||
:command (ds/c-mod "z")
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! dwc/undo)}
|
||||
|
||||
:redo {:tooltip (ds/meta "Y")
|
||||
:command [(ds/c-mod "shift+z") (ds/c-mod "y")]
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! dwc/redo)}
|
||||
|
||||
:clear-undo {:tooltip (ds/meta "Q")
|
||||
:command (ds/c-mod "q")
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! dwu/reinitialize-undo)}
|
||||
|
||||
:draw-frame {:tooltip "A"
|
||||
:command "a"
|
||||
:fn #(st/emit! (dwd/select-for-drawing :frame))}
|
||||
|
||||
:move {:tooltip "V"
|
||||
:command "v"
|
||||
:fn #(st/emit! :interrupt)}
|
||||
|
||||
:draw-rect {:tooltip "R"
|
||||
:command "r"
|
||||
:fn #(st/emit! (dwd/select-for-drawing :rect))}
|
||||
|
||||
:draw-ellipse {:tooltip "E"
|
||||
:command "e"
|
||||
:fn #(st/emit! (dwd/select-for-drawing :circle))}
|
||||
|
||||
:draw-text {:tooltip "T"
|
||||
:command "t"
|
||||
:fn #(st/emit! dwtxt/start-edit-if-selected
|
||||
(dwd/select-for-drawing :text))}
|
||||
|
||||
:draw-path {:tooltip "P"
|
||||
:command "p"
|
||||
:fn #(st/emit! (dwd/select-for-drawing :path))}
|
||||
|
||||
:draw-curve {:tooltip (ds/shift "C")
|
||||
:command "shift+c"
|
||||
:fn #(st/emit! (dwd/select-for-drawing :curve))}
|
||||
|
||||
:add-comment {:tooltip "C"
|
||||
:command "c"
|
||||
:fn #(st/emit! (dwd/select-for-drawing :comments))}
|
||||
|
||||
:insert-image {:tooltip (ds/shift "K")
|
||||
:command "shift+k"
|
||||
:fn #(-> "image-upload" dom/get-element dom/click)}
|
||||
|
||||
:copy {:tooltip (ds/meta "C")
|
||||
:command (ds/c-mod "c")
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! (dw/copy-selected))}
|
||||
|
||||
:cut {:tooltip (ds/meta "X")
|
||||
:command (ds/c-mod "x")
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! (dw/copy-selected)
|
||||
(dw/delete-selected))}
|
||||
|
||||
:paste {:tooltip (ds/meta "V")
|
||||
:disabled true
|
||||
:command (ds/c-mod "v")
|
||||
:subsections [:edit]
|
||||
:fn (constantly nil)}
|
||||
|
||||
:delete {:tooltip (ds/supr)
|
||||
:command ["del" "backspace"]
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! (dw/delete-selected))}
|
||||
|
||||
:bring-forward {:tooltip (ds/meta ds/up-arrow)
|
||||
:command (ds/c-mod "up")
|
||||
:fn #(st/emit! (dw/vertical-order-selected :up))}
|
||||
|
||||
:bring-backward {:tooltip (ds/meta ds/down-arrow)
|
||||
:command (ds/c-mod "down")
|
||||
:fn #(st/emit! (dw/vertical-order-selected :down))}
|
||||
|
||||
:bring-front {:tooltip (ds/meta-shift ds/up-arrow)
|
||||
:command (ds/c-mod "shift+up")
|
||||
:fn #(st/emit! (dw/vertical-order-selected :top))}
|
||||
|
||||
:bring-back {:tooltip (ds/meta-shift ds/down-arrow)
|
||||
:command (ds/c-mod "shift+down")
|
||||
:fn #(st/emit! (dw/vertical-order-selected :bottom))}
|
||||
|
||||
:move-fast-up {:tooltip (ds/shift ds/up-arrow)
|
||||
:command "shift+up"
|
||||
:fn #(st/emit! (dwt/move-selected :up true))}
|
||||
|
||||
:move-fast-down {:tooltip (ds/shift ds/down-arrow)
|
||||
:command "shift+down"
|
||||
:fn #(st/emit! (dwt/move-selected :down true))}
|
||||
|
||||
:move-fast-right {:tooltip (ds/shift ds/right-arrow)
|
||||
:command "shift+right"
|
||||
:fn #(st/emit! (dwt/move-selected :right true))}
|
||||
|
||||
:move-fast-left {:tooltip (ds/shift ds/left-arrow)
|
||||
:command "shift+left"
|
||||
:fn #(st/emit! (dwt/move-selected :left true))}
|
||||
|
||||
:move-unit-up {:tooltip ds/up-arrow
|
||||
:command "up"
|
||||
:fn #(st/emit! (dwt/move-selected :up false))}
|
||||
|
||||
:move-unit-down {:tooltip ds/down-arrow
|
||||
:command "down"
|
||||
:fn #(st/emit! (dwt/move-selected :down false))}
|
||||
|
||||
:move-unit-left {:tooltip ds/right-arrow
|
||||
:command "right"
|
||||
:fn #(st/emit! (dwt/move-selected :right false))}
|
||||
|
||||
:move-unit-right {:tooltip ds/left-arrow
|
||||
:command "left"
|
||||
:fn #(st/emit! (dwt/move-selected :left false))}
|
||||
|
||||
:open-color-picker {:tooltip "I"
|
||||
:command "i"
|
||||
:fn #(st/emit! (mdc/picker-for-selected-shape))}
|
||||
|
||||
:open-viewer {:tooltip "G V"
|
||||
:command "g v"
|
||||
:fn #(st/emit! (dw/go-to-viewer))}
|
||||
|
||||
:open-handoff {:tooltip "G H"
|
||||
:command "g h"
|
||||
:fn #(st/emit! (dw/go-to-viewer {:section :handoff}))}
|
||||
|
||||
:open-comments {:tooltip "G C"
|
||||
:command "g c"
|
||||
:fn #(st/emit! (dw/go-to-viewer {:section :comments}))}
|
||||
|
||||
:open-dashboard {:tooltip "G D"
|
||||
:command "g d"
|
||||
:fn #(st/emit! (dw/go-to-dashboard))}
|
||||
|
||||
:escape {:tooltip (ds/esc)
|
||||
:command "escape"
|
||||
:fn #(st/emit! :interrupt (dw/deselect-all true))}
|
||||
:duplicate {:tooltip (ds/meta "D")
|
||||
:command (ds/c-mod "d")
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! (dw/duplicate-selected true))}
|
||||
|
||||
:start-editing {:tooltip (ds/enter)
|
||||
:command "enter"
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! (dw/start-editing-selected))}
|
||||
|
||||
:start-measure {:tooltip (ds/alt "")
|
||||
:command ["alt" "."]
|
||||
:type "keydown"
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! (dw/toggle-distances-display true))}
|
||||
|
||||
:stop-measure {:tooltip (ds/alt "")
|
||||
:command ["alt" "."]
|
||||
:type "keyup"
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! (dw/toggle-distances-display false))}
|
||||
|
||||
:bool-union {:tooltip (ds/meta (ds/alt "U"))
|
||||
:command (ds/c-mod "alt+u")
|
||||
:fn #(st/emit! (dw/create-bool :union))}
|
||||
:escape {:tooltip (ds/esc)
|
||||
:command "escape"
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! :interrupt (dw/deselect-all true))}
|
||||
|
||||
:bool-difference {:tooltip (ds/meta (ds/alt "D"))
|
||||
:command (ds/c-mod "alt+d")
|
||||
:fn #(st/emit! (dw/create-bool :difference))}
|
||||
;; MODIFY LAYERS
|
||||
|
||||
:bool-intersection {:tooltip (ds/meta (ds/alt "I"))
|
||||
:command (ds/c-mod "alt+i")
|
||||
:fn #(st/emit! (dw/create-bool :intersection))}
|
||||
|
||||
:bool-exclude {:tooltip (ds/meta (ds/alt "E"))
|
||||
:command (ds/c-mod "alt+e")
|
||||
:fn #(st/emit! (dw/create-bool :exclude))}
|
||||
:group {:tooltip (ds/meta "G")
|
||||
:command (ds/c-mod "g")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! dw/group-selected)}
|
||||
|
||||
:align-left {:tooltip (ds/alt "A")
|
||||
:command "alt+a"
|
||||
:fn #(st/emit! (dw/align-objects :hleft))}
|
||||
:ungroup {:tooltip (ds/shift "G")
|
||||
:command "shift+g"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! dw/ungroup-selected)}
|
||||
|
||||
:align-right {:tooltip (ds/alt "D")
|
||||
:command "alt+d"
|
||||
:fn #(st/emit! (dw/align-objects :hright))}
|
||||
:mask {:tooltip (ds/meta "M")
|
||||
:command (ds/c-mod "m")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! dw/mask-group)}
|
||||
|
||||
:align-top {:tooltip (ds/alt "W")
|
||||
:command "alt+w"
|
||||
:fn #(st/emit! (dw/align-objects :vtop))}
|
||||
:unmask {:tooltip (ds/meta-shift "M")
|
||||
:command (ds/c-mod "shift+m")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! dw/unmask-group)}
|
||||
|
||||
:align-hcenter {:tooltip (ds/alt "H")
|
||||
:command "alt+h"
|
||||
:fn #(st/emit! (dw/align-objects :hcenter))}
|
||||
:create-component {:tooltip (ds/meta "K")
|
||||
:command (ds/c-mod "k")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwl/add-component))}
|
||||
|
||||
:align-vcenter {:tooltip (ds/alt "V")
|
||||
:command "alt+v"
|
||||
:fn #(st/emit! (dw/align-objects :vcenter))}
|
||||
:detach-component {:tooltip (ds/meta-shift "K")
|
||||
:command (ds/c-mod "shift+k")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! dwl/detach-selected-components)}
|
||||
|
||||
:align-bottom {:tooltip (ds/alt "S")
|
||||
:command "alt+s"
|
||||
:fn #(st/emit! (dw/align-objects :vbottom))}
|
||||
:flip-vertical {:tooltip (ds/shift "V")
|
||||
:command "shift+v"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dw/flip-vertical-selected))}
|
||||
|
||||
:h-distribute {:tooltip (ds/meta-shift (ds/alt "H"))
|
||||
:command (ds/c-mod "shift+alt+h")
|
||||
:fn #(st/emit! (dw/distribute-objects :horizontal))}
|
||||
:flip-horizontal {:tooltip (ds/shift "H")
|
||||
:command "shift+h"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dw/flip-horizontal-selected))}
|
||||
:bring-forward {:tooltip (ds/meta ds/up-arrow)
|
||||
:command (ds/c-mod "up")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dw/vertical-order-selected :up))}
|
||||
|
||||
:v-distribute {:tooltip (ds/meta-shift (ds/alt "V"))
|
||||
:command (ds/c-mod "shift+alt+v")
|
||||
:fn #(st/emit! (dw/distribute-objects :vertical))}
|
||||
:bring-backward {:tooltip (ds/meta ds/down-arrow)
|
||||
:command (ds/c-mod "down")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dw/vertical-order-selected :down))}
|
||||
|
||||
:bring-front {:tooltip (ds/meta-shift ds/up-arrow)
|
||||
:command (ds/c-mod "shift+up")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dw/vertical-order-selected :top))}
|
||||
|
||||
:bring-back {:tooltip (ds/meta-shift ds/down-arrow)
|
||||
:command (ds/c-mod "shift+down")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dw/vertical-order-selected :bottom))}
|
||||
|
||||
:move-fast-up {:tooltip (ds/shift ds/up-arrow)
|
||||
:command "shift+up"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwt/move-selected :up true))}
|
||||
|
||||
:move-fast-down {:tooltip (ds/shift ds/down-arrow)
|
||||
:command "shift+down"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwt/move-selected :down true))}
|
||||
|
||||
:move-fast-right {:tooltip (ds/shift ds/right-arrow)
|
||||
:command "shift+right"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwt/move-selected :right true))}
|
||||
|
||||
:move-fast-left {:tooltip (ds/shift ds/left-arrow)
|
||||
:command "shift+left"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwt/move-selected :left true))}
|
||||
|
||||
:move-unit-up {:tooltip ds/up-arrow
|
||||
:command "up"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwt/move-selected :up false))}
|
||||
|
||||
:move-unit-down {:tooltip ds/down-arrow
|
||||
:command "down"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwt/move-selected :down false))}
|
||||
|
||||
:move-unit-left {:tooltip ds/right-arrow
|
||||
:command "right"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwt/move-selected :right false))}
|
||||
|
||||
:move-unit-right {:tooltip ds/left-arrow
|
||||
:command "left"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwt/move-selected :left false))}
|
||||
|
||||
:artboard-selection {:tooltip (ds/meta (ds/alt "G"))
|
||||
:command (ds/c-mod "alt+g")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dw/create-artboard-from-selection))}
|
||||
|
||||
;; TOOLS
|
||||
|
||||
:draw-frame {:tooltip "A"
|
||||
:command "a"
|
||||
:subsections [:tools :basics]
|
||||
:fn #(st/emit! (dwd/select-for-drawing :frame))}
|
||||
|
||||
:move {:tooltip "V"
|
||||
:command "v"
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! :interrupt)}
|
||||
|
||||
:draw-rect {:tooltip "R"
|
||||
:command "r"
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (dwd/select-for-drawing :rect))}
|
||||
|
||||
:draw-ellipse {:tooltip "E"
|
||||
:command "e"
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (dwd/select-for-drawing :circle))}
|
||||
|
||||
:draw-text {:tooltip "T"
|
||||
:command "t"
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! dwtxt/start-edit-if-selected
|
||||
(dwd/select-for-drawing :text))}
|
||||
|
||||
:draw-path {:tooltip "P"
|
||||
:command "p"
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (dwd/select-for-drawing :path))}
|
||||
|
||||
:draw-curve {:tooltip (ds/shift "C")
|
||||
:command "shift+c"
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (dwd/select-for-drawing :curve))}
|
||||
|
||||
:add-comment {:tooltip "C"
|
||||
:command "c"
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (dwd/select-for-drawing :comments))}
|
||||
|
||||
:insert-image {:tooltip (ds/shift "K")
|
||||
:command "shift+k"
|
||||
:subsections [:tools]
|
||||
:fn #(-> "image-upload" dom/get-element dom/click)}
|
||||
|
||||
:toggle-visibility {:tooltip (ds/meta-shift "H")
|
||||
:command (ds/c-mod "shift+h")
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (dw/toggle-visibility-selected))}
|
||||
|
||||
:toggle-lock {:tooltip (ds/meta-shift "L")
|
||||
:command (ds/c-mod "shift+l")
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (dw/toggle-lock-selected))}
|
||||
|
||||
:toggle-lock-size {:tooltip (ds/meta (ds/alt "L"))
|
||||
:command (ds/c-mod "alt+l")
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (dw/toggle-proportion-lock))}
|
||||
|
||||
:artboard-selection {:tooltip (ds/meta (ds/alt "G"))
|
||||
:command (ds/c-mod "alt+g")
|
||||
:fn #(st/emit! (dw/create-artboard-from-selection))}
|
||||
:toggle-scale-text {:tooltip "K"
|
||||
:command "k"
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (toggle-layout-flag :scale-text))}
|
||||
|
||||
:hide-ui {:tooltip "\\"
|
||||
:command "\\"
|
||||
:fn #(st/emit! (toggle-layout-flag :hide-ui))}
|
||||
:open-color-picker {:tooltip "I"
|
||||
:command "i"
|
||||
:subsections [:tools]
|
||||
:fn #(st/emit! (mdc/picker-for-selected-shape))}
|
||||
|
||||
:toggle-focus-mode {:command "f"
|
||||
:tooltip "F"
|
||||
:subsections [:basics :tools]
|
||||
:fn #(st/emit! (dw/toggle-focus-mode))}
|
||||
|
||||
:thumbnail-set {:tooltip (ds/shift "T")
|
||||
:command "shift+t"
|
||||
:fn #(st/emit! (dw/toggle-file-thumbnail-selected))}
|
||||
;; ITEM ALIGNMENT
|
||||
|
||||
:align-left {:tooltip (ds/alt "A")
|
||||
:command "alt+a"
|
||||
:subsections [:alignment]
|
||||
:fn #(st/emit! (dw/align-objects :hleft))}
|
||||
|
||||
:align-right {:tooltip (ds/alt "D")
|
||||
:command "alt+d"
|
||||
:subsections [:alignment]
|
||||
:fn #(st/emit! (dw/align-objects :hright))}
|
||||
|
||||
:align-top {:tooltip (ds/alt "W")
|
||||
:command "alt+w"
|
||||
:subsections [:alignment]
|
||||
:fn #(st/emit! (dw/align-objects :vtop))}
|
||||
|
||||
:align-hcenter {:tooltip (ds/alt "H")
|
||||
:command "alt+h"
|
||||
:subsections [:alignment]
|
||||
:fn #(st/emit! (dw/align-objects :hcenter))}
|
||||
|
||||
:align-vcenter {:tooltip (ds/alt "V")
|
||||
:command "alt+v"
|
||||
:subsections [:alignment]
|
||||
:fn #(st/emit! (dw/align-objects :vcenter))}
|
||||
|
||||
:align-bottom {:tooltip (ds/alt "S")
|
||||
:command "alt+s"
|
||||
:subsections [:alignment]
|
||||
:fn #(st/emit! (dw/align-objects :vbottom))}
|
||||
|
||||
:h-distribute {:tooltip (ds/meta-shift (ds/alt "H"))
|
||||
:command (ds/c-mod "shift+alt+h")
|
||||
:subsections [:alignment]
|
||||
:fn #(st/emit! (dw/distribute-objects :horizontal))}
|
||||
|
||||
:v-distribute {:tooltip (ds/meta-shift (ds/alt "V"))
|
||||
:command (ds/c-mod "shift+alt+v")
|
||||
:subsections [:alignment]
|
||||
:fn #(st/emit! (dw/distribute-objects :vertical))}
|
||||
|
||||
;; MAIN MENU
|
||||
|
||||
:toggle-rules {:tooltip (ds/meta-shift "R")
|
||||
:command (ds/c-mod "shift+r")
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :rules))}
|
||||
|
||||
:select-all {:tooltip (ds/meta "A")
|
||||
:command (ds/c-mod "a")
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (dw/select-all))}
|
||||
|
||||
:toggle-grid {:tooltip (ds/meta "'")
|
||||
:command (ds/c-mod "'")
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :display-grid))}
|
||||
|
||||
:toggle-snap-grid {:tooltip (ds/meta-shift "'")
|
||||
:command (ds/c-mod "shift+'")
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :snap-grid))}
|
||||
|
||||
:toggle-alignment {:tooltip (ds/meta "\\")
|
||||
:command (ds/c-mod "\\")
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :dynamic-alignment))}
|
||||
|
||||
:thumbnail-set {:tooltip (ds/shift "T")
|
||||
:command "shift+t"
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (dw/toggle-file-thumbnail-selected))}
|
||||
|
||||
:show-pixel-grid {:tooltip (ds/shift ",")
|
||||
:command "shift+,"
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :show-pixel-grid))}
|
||||
|
||||
:snap-pixel-grid {:command ","
|
||||
:tooltip ","
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :snap-pixel-grid))}
|
||||
})
|
||||
|
||||
:export-shapes {:tooltip (ds/meta-shift "E")
|
||||
:command (ds/c-mod "shift+e")
|
||||
:subsections [:basics :main-menu]
|
||||
:fn #(st/emit!
|
||||
(de/show-workspace-export-dialog))}
|
||||
|
||||
:toggle-snap-guide {:tooltip (ds/meta-shift "G")
|
||||
:command (ds/c-mod "shift+g")
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :snap-guides))}
|
||||
|
||||
:show-shortcuts {:tooltip "?"
|
||||
:command "?"
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :shortcuts)) }
|
||||
|
||||
;; PANELS
|
||||
|
||||
:toggle-layers {:tooltip (ds/alt "L")
|
||||
:command (ds/a-mod "l")
|
||||
:subsections [:panels]
|
||||
:fn #(st/emit! (dw/go-to-layout :layers))}
|
||||
|
||||
:toggle-assets {:tooltip (ds/alt "I")
|
||||
:command (ds/a-mod "i")
|
||||
:subsections [:panels]
|
||||
:fn #(st/emit! (dw/go-to-layout :assets))}
|
||||
|
||||
:toggle-history {:tooltip (ds/alt "H")
|
||||
:command (ds/a-mod "h")
|
||||
:subsections [:panels]
|
||||
:fn #(st/emit! (dw/go-to-layout :document-history))}
|
||||
|
||||
:toggle-colorpalette {:tooltip (ds/alt "P")
|
||||
:command (ds/a-mod "p")
|
||||
:subsections [:panels]
|
||||
:fn #(do (r/set-resize-type! :bottom)
|
||||
(st/emit! (dw/remove-layout-flag :textpalette)
|
||||
(toggle-layout-flag :colorpalette)))}
|
||||
|
||||
:toggle-textpalette {:tooltip (ds/alt "T")
|
||||
:command (ds/a-mod "t")
|
||||
:subsections [:panels]
|
||||
:fn #(do (r/set-resize-type! :bottom)
|
||||
(st/emit! (dw/remove-layout-flag :colorpalette)
|
||||
(toggle-layout-flag :textpalette)))}
|
||||
|
||||
:hide-ui {:tooltip "\\"
|
||||
:command "\\"
|
||||
:subsections [:panels :basics]
|
||||
:fn #(st/emit! (toggle-layout-flag :hide-ui))}
|
||||
|
||||
;; ZOOM-WORKSPACE
|
||||
|
||||
:increase-zoom {:tooltip "+"
|
||||
:command ["+" "="]
|
||||
:subsections [:zoom-workspace]
|
||||
:fn #(st/emit! (dw/increase-zoom nil))}
|
||||
|
||||
:decrease-zoom {:tooltip "-"
|
||||
:command ["-" "_"]
|
||||
:subsections [:zoom-workspace]
|
||||
:fn #(st/emit! (dw/decrease-zoom nil))}
|
||||
|
||||
:reset-zoom {:tooltip (ds/shift "0")
|
||||
:command "shift+0"
|
||||
:subsections [:zoom-workspace]
|
||||
:fn #(st/emit! dw/reset-zoom)}
|
||||
|
||||
:fit-all {:tooltip (ds/shift "1")
|
||||
:command "shift+1"
|
||||
:subsections [:zoom-workspace]
|
||||
:fn #(st/emit! dw/zoom-to-fit-all)}
|
||||
|
||||
:zoom-selected {:tooltip (ds/shift "2")
|
||||
:command ["shift+2" "@" "\""]
|
||||
:subsections [:zoom-workspace]
|
||||
:fn #(st/emit! dw/zoom-to-selected-shape)}
|
||||
|
||||
;; NAVIGATION
|
||||
|
||||
|
||||
:open-viewer {:tooltip "G V"
|
||||
:command "g v"
|
||||
:subsections [:navigation-workspace]
|
||||
:fn #(st/emit! (dw/go-to-viewer))}
|
||||
|
||||
:open-handoff {:tooltip "G H"
|
||||
:command "g h"
|
||||
:subsections [:navigation-workspace]
|
||||
:fn #(st/emit! (dw/go-to-viewer {:section :handoff}))}
|
||||
|
||||
:open-comments {:tooltip "G C"
|
||||
:command "g c"
|
||||
:subsections [:navigation-workspace]
|
||||
:fn #(st/emit! (dw/go-to-viewer {:section :comments}))}
|
||||
|
||||
:open-dashboard {:tooltip "G D"
|
||||
:command "g d"
|
||||
:subsections [:navigation-workspace]
|
||||
:fn #(st/emit! (dw/go-to-dashboard))}
|
||||
|
||||
;; SHAPE
|
||||
|
||||
:bool-union {:tooltip (ds/meta (ds/alt "U"))
|
||||
:command (ds/c-mod "alt+u")
|
||||
:subsections [:shape]
|
||||
:fn #(st/emit! (dw/create-bool :union))}
|
||||
|
||||
:bool-difference {:tooltip (ds/meta (ds/alt "D"))
|
||||
:command (ds/c-mod "alt+d")
|
||||
:subsections [:shape]
|
||||
:fn #(st/emit! (dw/create-bool :difference))}
|
||||
|
||||
:bool-intersection {:tooltip (ds/meta (ds/alt "I"))
|
||||
:command (ds/c-mod "alt+i")
|
||||
:subsections [:shape]
|
||||
:fn #(st/emit! (dw/create-bool :intersection))}
|
||||
|
||||
:bool-exclude {:tooltip (ds/meta (ds/alt "E"))
|
||||
:command (ds/c-mod "alt+e")
|
||||
:subsections [:shape]
|
||||
:fn #(st/emit! (dw/create-bool :exclude))}}
|
||||
)
|
||||
|
||||
(def opacity-shortcuts
|
||||
(into {} (->>
|
||||
@@ -391,6 +499,7 @@
|
||||
(map (fn [n] [(keyword (str "opacity-" n))
|
||||
{:tooltip (str n)
|
||||
:command (str n)
|
||||
:subsections [:modify-layers]
|
||||
:fn #(st/emit! (dwly/pressed-opacity n))}])))))
|
||||
|
||||
(def shortcuts
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
([state page-id]
|
||||
(get-in state [:workspace-data :pages-index page-id])))
|
||||
|
||||
(defn lookup-data-objects
|
||||
[data page-id]
|
||||
(dm/get-in data [:pages-index page-id :objects]))
|
||||
|
||||
(defn lookup-page-objects
|
||||
([state]
|
||||
(lookup-page-objects state (:current-page-id state)))
|
||||
|
||||
@@ -355,7 +355,7 @@
|
||||
(assoc :svg-attrs (dissoc attrs :x :y :width :height :href :xlink:href))))))
|
||||
|
||||
(defn parse-svg-element [frame-id svg-data element-data unames]
|
||||
(let [{:keys [tag attrs]} element-data
|
||||
(let [{:keys [tag attrs hidden]} element-data
|
||||
attrs (usvg/format-styles attrs)
|
||||
element-data (cond-> element-data (map? element-data) (assoc :attrs attrs))
|
||||
name (dwc/generate-unique-name unames (or (:id attrs) (tag->name tag)))
|
||||
@@ -402,6 +402,9 @@
|
||||
(setup-fill)
|
||||
(setup-stroke))
|
||||
|
||||
shape (cond-> shape
|
||||
hidden (assoc :hidden true))
|
||||
|
||||
children (cond->> (:content element-data)
|
||||
(or (= tag :g) (= tag :svg))
|
||||
(mapv #(usvg/inherit-attributes attrs %)))]
|
||||
@@ -471,6 +474,7 @@
|
||||
:height (str (:height root-shape))
|
||||
:fill "none"
|
||||
:id "base-background"}
|
||||
:hidden true
|
||||
:content []}
|
||||
|
||||
svg-data (-> svg-data
|
||||
|
||||
@@ -308,7 +308,7 @@
|
||||
(assoc-in [:workspace-local :edition] (-> selected first :id)))))))
|
||||
|
||||
(defn not-changed? [old-dim new-dim]
|
||||
(> (mth/abs (- old-dim new-dim)) 0.1))
|
||||
(> (mth/abs (- old-dim new-dim)) 1))
|
||||
|
||||
(defn resize-text
|
||||
[id new-width new-height]
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
@@ -31,7 +32,9 @@
|
||||
[object-id]
|
||||
(rx/create
|
||||
(fn [subs]
|
||||
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'" object-id))]
|
||||
;; We look in the DOM a canvas that 1) matches the id and 2) that it's not empty
|
||||
;; will be empty on first rendering before drawing the thumbnail and we don't want to store that
|
||||
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%']:not([data-empty])" object-id))]
|
||||
(if (some? node)
|
||||
(-> node
|
||||
(.toBlob (fn [blob]
|
||||
@@ -43,6 +46,14 @@
|
||||
(do (rx/push! subs nil)
|
||||
(rx/end! subs)))))))
|
||||
|
||||
(defn clear-thumbnail
|
||||
[page-id frame-id]
|
||||
(ptk/reify ::clear-thumbnail
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [object-id (dm/str page-id frame-id)]
|
||||
(assoc-in state [:workspace-file :thumbnails object-id] nil)))))
|
||||
|
||||
(defn update-thumbnail
|
||||
"Updates the thumbnail information for the given frame `id`"
|
||||
[page-id frame-id]
|
||||
@@ -71,50 +82,39 @@
|
||||
|
||||
(defn- extract-frame-changes
|
||||
"Process a changes set in a commit to extract the frames that are changing"
|
||||
[[event [old-objects new-objects]]]
|
||||
[[event [old-data new-data]]]
|
||||
(let [changes (-> event deref :changes)
|
||||
|
||||
extract-ids
|
||||
(fn [{type :type :as change}]
|
||||
(fn [{:keys [page-id type] :as change}]
|
||||
(case type
|
||||
:add-obj [(:id change)]
|
||||
:mod-obj [(:id change)]
|
||||
:del-obj [(:id change)]
|
||||
:reg-objects (:shapes change)
|
||||
:mov-objects (:shapes change)
|
||||
:add-obj [[page-id (:id change)]]
|
||||
:mod-obj [[page-id (:id change)]]
|
||||
:del-obj [[page-id (:id change)]]
|
||||
:mov-objects (->> (:shapes change) (map #(vector page-id %)))
|
||||
[]))
|
||||
|
||||
get-frame-id
|
||||
(fn [id]
|
||||
(let [shape (or (get new-objects id)
|
||||
(get old-objects id))]
|
||||
(or (and (cph/frame-shape? shape) id) (:frame-id shape))))
|
||||
(fn [[page-id id]]
|
||||
(let [old-objects (wsh/lookup-data-objects old-data page-id)
|
||||
new-objects (wsh/lookup-data-objects new-data page-id)
|
||||
|
||||
;; Extracts the frames and then removes nils and the root frame
|
||||
xform (comp (mapcat extract-ids)
|
||||
(map get-frame-id)
|
||||
(remove nil?)
|
||||
(filter #(not= uuid/zero %))
|
||||
(filter #(contains? new-objects %)))]
|
||||
new-shape (get new-objects id)
|
||||
old-shape (get old-objects id)
|
||||
|
||||
(into #{} xform changes)))
|
||||
old-frame-id (if (cph/frame-shape? old-shape) id (:frame-id old-shape))
|
||||
new-frame-id (if (cph/frame-shape? new-shape) id (:frame-id new-shape))]
|
||||
|
||||
(defn thumbnail-change?
|
||||
"Checks if a event is only updating thumbnails to ignore in the thumbnail generation process"
|
||||
[event]
|
||||
(let [changes (-> event deref :changes)
|
||||
(cond-> #{}
|
||||
(and old-frame-id (not= uuid/zero old-frame-id))
|
||||
(conj [page-id old-frame-id])
|
||||
|
||||
is-thumbnail-op?
|
||||
(fn [{type :type attr :attr}]
|
||||
(and (= type :set)
|
||||
(= attr :thumbnail)))
|
||||
|
||||
is-thumbnail-change?
|
||||
(fn [change]
|
||||
(and (= (:type change) :mod-obj)
|
||||
(->> change :operations (every? is-thumbnail-op?))))]
|
||||
|
||||
(->> changes (every? is-thumbnail-change?))))
|
||||
(and new-frame-id (not= uuid/zero new-frame-id))
|
||||
(conj [page-id new-frame-id]))))]
|
||||
(into #{}
|
||||
(comp (mapcat extract-ids)
|
||||
(mapcat get-frame-id))
|
||||
changes)))
|
||||
|
||||
(defn watch-state-changes
|
||||
"Watch the state for changes inside frames. If a change is detected will force a rendering
|
||||
@@ -123,32 +123,39 @@
|
||||
(ptk/reify ::watch-state-changes
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [stopper (->> stream
|
||||
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
|
||||
(= ::watch-state-changes (ptk/type %)))))
|
||||
(let [stopper
|
||||
(->> stream
|
||||
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
|
||||
(= ::watch-state-changes (ptk/type %)))))
|
||||
|
||||
objects-stream (->> (rx/concat
|
||||
(rx/of nil)
|
||||
(rx/from-atom refs/workspace-page-objects {:emit-current-value? true}))
|
||||
;; We need to keep the old-objects so we can check the frame for the
|
||||
;; deleted objects
|
||||
(rx/buffer 2 1))
|
||||
workspace-data-str
|
||||
(->> (rx/concat
|
||||
(rx/of nil)
|
||||
(rx/from-atom refs/workspace-data {:emit-current-value? true}))
|
||||
;; We need to keep the old-objects so we can check the frame for the
|
||||
;; deleted objects
|
||||
(rx/buffer 2 1))
|
||||
|
||||
frame-changes (->> stream
|
||||
(rx/filter dch/commit-changes?)
|
||||
change-str
|
||||
(->> stream
|
||||
(rx/filter #(or (dch/commit-changes? %)
|
||||
(= (ptk/type %) :app.main.data.workspace.notifications/handle-file-change)))
|
||||
(rx/observe-on :async))
|
||||
|
||||
;; Async so we wait for additional side-effects of commit-changes
|
||||
(rx/observe-on :async)
|
||||
(rx/filter (complement thumbnail-change?))
|
||||
(rx/with-latest-from objects-stream)
|
||||
(rx/map extract-frame-changes)
|
||||
(rx/share))]
|
||||
frame-changes-str
|
||||
(->> change-str
|
||||
(rx/with-latest-from workspace-data-str)
|
||||
(rx/flat-map extract-frame-changes)
|
||||
(rx/share))]
|
||||
|
||||
(->> frame-changes
|
||||
(rx/flat-map
|
||||
(fn [ids]
|
||||
(->> (rx/from ids)
|
||||
(rx/map #(ptk/data-event ::force-render %)))))
|
||||
(->> (rx/merge
|
||||
(->> frame-changes-str
|
||||
(rx/filter (fn [[page-id _]] (not= page-id (:current-page-id @st/state))))
|
||||
(rx/map (fn [[page-id frame-id]] (clear-thumbnail page-id frame-id))))
|
||||
|
||||
(->> frame-changes-str
|
||||
(rx/filter (fn [[page-id _]] (= page-id (:current-page-id @st/state))))
|
||||
(rx/map (fn [[_ frame-id]] (ptk/data-event ::force-render frame-id)))))
|
||||
(rx/take-until stopper))))))
|
||||
|
||||
(defn duplicate-thumbnail
|
||||
|
||||
@@ -556,6 +556,32 @@
|
||||
(watch [_ _ _]
|
||||
(rx/of (apply-modifiers ids)))))
|
||||
|
||||
(defn change-orientation
|
||||
"Change orientation of shapes, from the sidebar options form.
|
||||
Will ignore pixel snap used in the options side panel"
|
||||
[ids orientation]
|
||||
(us/verify (s/coll-of ::us/uuid) ids)
|
||||
(us/verify #{:horiz :vert} orientation)
|
||||
(ptk/reify ::change-orientation
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
layout (get state :workspace-layout)
|
||||
snap-pixel? (contains? layout :snap-pixel-grid)
|
||||
|
||||
update-modifiers
|
||||
(fn [state id]
|
||||
(let [shape (get objects id)
|
||||
modifiers (gsh/change-orientation-modifiers shape orientation)]
|
||||
(-> state
|
||||
(update :workspace-modifiers
|
||||
#(set-objects-modifiers % objects shape modifiers false snap-pixel?)))))]
|
||||
(reduce update-modifiers state ids)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (apply-modifiers ids)))))
|
||||
|
||||
;; -- Rotate --------------------------------------------------------
|
||||
|
||||
(defn start-rotate
|
||||
@@ -786,7 +812,7 @@
|
||||
|
||||
(rx/of (apply-modifiers selected)
|
||||
(finish-transform))))
|
||||
(rx/empty))))))
|
||||
(rx/empty))))))
|
||||
|
||||
(s/def ::x number?)
|
||||
(s/def ::y number?)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
[app.util.globals :as glob]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :refer [storage]]
|
||||
[app.util.timers :as ts]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
@@ -35,7 +36,7 @@
|
||||
:else
|
||||
(let [hint (ex-message error)
|
||||
msg (dm/str "Internal Error: " hint)]
|
||||
(ts/schedule (st/emitf (rt/assign-exception error)))
|
||||
(ts/schedule #(st/emit! (rt/assign-exception error)))
|
||||
|
||||
(js/console.group msg)
|
||||
(ex/ignoring (js/console.error error))
|
||||
@@ -49,9 +50,11 @@
|
||||
;; here and not in app.main.errors because of circular dependency.
|
||||
(defmethod ptk/handle-error :authentication
|
||||
[_]
|
||||
(let [msg (tr "errors.auth.unable-to-login")]
|
||||
(let [msg (tr "errors.auth.unable-to-login")
|
||||
uri (. (. js/document -location) -href)]
|
||||
(st/emit! (du/logout {:capture-redirect true}))
|
||||
(ts/schedule 500 (st/emitf (msg/warn msg)))))
|
||||
(ts/schedule 500 #(st/emit! (msg/warn msg)))
|
||||
(ts/schedule 1000 #(swap! storage assoc :redirect-url uri))))
|
||||
|
||||
;; Error that happens on an active business model validation does not
|
||||
;; passes an validation (example: profile can't leave a team). From
|
||||
@@ -154,7 +157,7 @@
|
||||
(defmethod ptk/handle-error ::exceptional-state
|
||||
[error]
|
||||
(ts/schedule
|
||||
(st/emitf (rt/assign-exception error))))
|
||||
#(st/emit! (rt/assign-exception error))))
|
||||
|
||||
;; This happens when the backed server fails to process the
|
||||
;; request. This can be caused by an internal assertion or any other
|
||||
@@ -188,7 +191,7 @@
|
||||
(-> error ex-data ptk/handle-error)
|
||||
(let [hint (ex-message error)
|
||||
msg (dm/str "Unhandled Internal Error: " hint)]
|
||||
(ts/schedule (st/emitf (rt/assign-exception error)))
|
||||
(ts/schedule #(st/emit! (rt/assign-exception error)))
|
||||
(js/console.group msg)
|
||||
(ex/ignoring (js/console.error error))
|
||||
(js/console.groupEnd msg))))
|
||||
|
||||
@@ -349,15 +349,18 @@
|
||||
|
||||
;; ---- Viewer refs
|
||||
|
||||
(def viewer-file
|
||||
(l/derived :viewer-file st/state))
|
||||
|
||||
(def viewer-project
|
||||
(l/derived :viewer-file st/state))
|
||||
|
||||
(def viewer-data
|
||||
(l/derived :viewer st/state))
|
||||
|
||||
(def viewer-file
|
||||
(l/derived :file viewer-data))
|
||||
|
||||
(def viewer-thumbnails
|
||||
(l/derived :thumbnails viewer-file))
|
||||
|
||||
(def viewer-project
|
||||
(l/derived :project viewer-data))
|
||||
|
||||
(def viewer-state
|
||||
(l/derived :viewer st/state))
|
||||
|
||||
|
||||
@@ -433,6 +433,7 @@
|
||||
(mf/defc component-symbol
|
||||
[{:keys [id data] :as props}]
|
||||
(let [name (:name data)
|
||||
path (:path data)
|
||||
objects (-> (:objects data)
|
||||
(adapt-objects-for-shape id))
|
||||
object (get objects id)
|
||||
@@ -448,7 +449,7 @@
|
||||
(mf/deps objects)
|
||||
(fn [] (group-wrapper-factory objects)))]
|
||||
|
||||
[:> "symbol" #js {:id (str id) :viewBox vbox}
|
||||
[:> "symbol" #js {:id (str id) :viewBox vbox "penpot:path" path}
|
||||
[:title name]
|
||||
[:> shape-container {:shape object}
|
||||
[:& group-wrapper {:shape object :view-box vbox}]]]))
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.store)
|
||||
|
||||
(defmacro emitf
|
||||
[& events]
|
||||
`(fn []
|
||||
(app.main.store/emit! ~@events)))
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.store
|
||||
(:require-macros [app.main.store])
|
||||
(:require
|
||||
[app.util.object :as obj]
|
||||
[beicon.core :as rx]
|
||||
@@ -56,10 +55,6 @@
|
||||
(apply ptk/emit! state (cons event events))
|
||||
nil))
|
||||
|
||||
(defn emitf
|
||||
[& events]
|
||||
#(apply ptk/emit! state events))
|
||||
|
||||
(defonce ongoing-tasks (l/atom #{}))
|
||||
|
||||
(add-watch ongoing-tasks ::ongoing-tasks
|
||||
|
||||
@@ -210,6 +210,6 @@
|
||||
[:div.links.demo
|
||||
[:div.link-entry
|
||||
[:span (tr "auth.create-demo-profile") " "]
|
||||
[:a {:on-click (st/emitf (du/create-demo-profile))
|
||||
[:a {:on-click #(st/emit! (du/create-demo-profile))
|
||||
:data-test "demo-account-link"}
|
||||
(tr "auth.create-demo-account")]]])]])
|
||||
|
||||
@@ -204,19 +204,19 @@
|
||||
on-delete-comment
|
||||
(mf/use-callback
|
||||
(mf/deps comment)
|
||||
(st/emitf (dcm/delete-comment comment)))
|
||||
#(st/emit! (dcm/delete-comment comment)))
|
||||
|
||||
delete-thread
|
||||
(mf/use-callback
|
||||
(mf/deps thread)
|
||||
(st/emitf (dcm/close-thread)
|
||||
(dcm/delete-comment-thread thread)))
|
||||
#(st/emit! (dcm/close-thread)
|
||||
(dcm/delete-comment-thread thread)))
|
||||
|
||||
|
||||
on-delete-thread
|
||||
(mf/use-callback
|
||||
(mf/deps thread)
|
||||
(st/emitf (modal/show
|
||||
#(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-comment-thread.title")
|
||||
:message (tr "modals.delete-comment-thread.message")
|
||||
@@ -292,11 +292,11 @@
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps thread)
|
||||
(st/emitf (dcm/retrieve-comments (:id thread))))
|
||||
#(st/emit! (dcm/retrieve-comments (:id thread))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps thread)
|
||||
(st/emitf (dcm/update-comment-thread-status thread)))
|
||||
#(st/emit! (dcm/update-comment-thread-status thread)))
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps thread comments-map)
|
||||
|
||||
@@ -35,39 +35,45 @@
|
||||
route (mf/deref refs/route)
|
||||
in-dashboard? (= :dashboard-projects (:name (:data route)))
|
||||
|
||||
local (mf/use-state {:offset 0
|
||||
local (mf/use-state {:offset-y 0
|
||||
:offset-x 0
|
||||
:levels nil})
|
||||
|
||||
on-local-close
|
||||
(mf/use-callback
|
||||
(fn []
|
||||
(swap! local assoc :levels [{:parent-option nil
|
||||
:options options}])
|
||||
(on-close)))
|
||||
(fn []
|
||||
(swap! local assoc :levels [{:parent-option nil
|
||||
:options options}])
|
||||
(on-close)))
|
||||
|
||||
check-menu-offscreen
|
||||
(mf/use-callback
|
||||
(mf/deps top (:offset @local))
|
||||
(mf/deps top (:offset-y @local) left (:offset-x @local))
|
||||
(fn [node]
|
||||
(when (some? node)
|
||||
(let [{node-height :height} (dom/get-bounding-rect node)
|
||||
{window-height :height} (dom/get-window-size)
|
||||
target-offset (if (> (+ top node-height) window-height)
|
||||
(let [bounding_rect (dom/get-bounding-rect node)
|
||||
window_size (dom/get-window-size)
|
||||
{node-height :height node-width :width} bounding_rect
|
||||
{window-height :height window-width :width} window_size
|
||||
target-offset-y (if (> (+ top node-height) window-height)
|
||||
(- node-height)
|
||||
0)]
|
||||
0)
|
||||
target-offset-x (if (> (+ left node-width) window-width)
|
||||
(- node-width)
|
||||
0)]
|
||||
|
||||
(when (not= target-offset (:offset @local))
|
||||
(swap! local assoc :offset target-offset))))))
|
||||
(when (or (not= target-offset-y (:offset-y @local)) (not= target-offset-x (:offset-x @local)))
|
||||
(swap! local assoc :offset-y target-offset-y :offset-x target-offset-x))))))
|
||||
|
||||
enter-submenu
|
||||
(mf/use-callback
|
||||
(mf/deps options)
|
||||
(fn [option-name sub-options]
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! local update :levels
|
||||
conj {:parent-option option-name
|
||||
:options sub-options}))))
|
||||
(mf/deps options)
|
||||
(fn [option-name sub-options]
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! local update :levels
|
||||
conj {:parent-option option-name
|
||||
:options sub-options}))))
|
||||
|
||||
exit-submenu
|
||||
(mf/use-callback
|
||||
@@ -87,8 +93,8 @@
|
||||
[:div.context-menu {:class (dom/classnames :is-open open?
|
||||
:fixed fixed?
|
||||
:is-selectable is-selectable)
|
||||
:style {:top (+ top (:offset @local))
|
||||
:left left}}
|
||||
:style {:top (+ top (:offset-y @local))
|
||||
:left (+ left (:offset-x @local))}}
|
||||
(let [level (-> @local :levels peek)]
|
||||
[:ul.context-menu-items {:class (dom/classnames :min-width min-width?)
|
||||
:ref check-menu-offscreen}
|
||||
|
||||
@@ -236,6 +236,14 @@
|
||||
(events/listen globals/window EventType.CLICK on-click)]]
|
||||
#(doseq [key keys]
|
||||
(events/unlistenByKey key)))))
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps handle-mouse-wheel)
|
||||
(fn []
|
||||
(let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:pasive false})]]
|
||||
#(doseq [key keys]
|
||||
(events/unlistenByKey key)))))
|
||||
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps handle-mouse-wheel)
|
||||
|
||||
@@ -21,3 +21,4 @@
|
||||
(def current-project-id (mf/create-context nil))
|
||||
(def current-page-id (mf/create-context nil))
|
||||
(def current-file-id (mf/create-context nil))
|
||||
(def scroll-ctx (mf/create-context nil))
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
(mf/defc dashboard-content
|
||||
[{:keys [team projects project section search-term profile] :as props}]
|
||||
[:div.dashboard-content {:on-click (st/emitf (dd/clear-selected-files))}
|
||||
[:div.dashboard-content {:on-click #(st/emit! (dd/clear-selected-files))}
|
||||
(case section
|
||||
:dashboard-projects
|
||||
[:& projects-section {:team team :projects projects}]
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
(filter #(not= (:label %) (:fullname profile))
|
||||
(map #(hash-map :label (:name %) :value (str (:id %))) members)))
|
||||
|
||||
on-cancel (st/emitf (modal/hide))
|
||||
on-cancel #(st/emit! (modal/hide))
|
||||
on-accept
|
||||
(fn [_]
|
||||
(let [member-id (get-in @form [:clean-data :member-id])]
|
||||
|
||||
@@ -126,10 +126,10 @@
|
||||
{:on-success #(on-move-success team-id project-id)}))))))
|
||||
|
||||
add-shared
|
||||
(st/emitf (dd/set-file-shared (assoc file :is-shared true)))
|
||||
#(st/emit! (dd/set-file-shared (assoc file :is-shared true)))
|
||||
|
||||
del-shared
|
||||
(st/emitf (dd/set-file-shared (assoc file :is-shared false)))
|
||||
#(st/emit! (dd/set-file-shared (assoc file :is-shared false)))
|
||||
|
||||
on-add-shared
|
||||
(fn [event]
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
toggle-pin
|
||||
(mf/use-callback
|
||||
(mf/deps project)
|
||||
(st/emitf (dd/toggle-project-pin project)))
|
||||
#(st/emit! (dd/toggle-project-pin project)))
|
||||
|
||||
on-create-clicked
|
||||
(mf/use-callback
|
||||
|
||||
@@ -41,12 +41,12 @@
|
||||
;; (let [go-fonts
|
||||
;; (mf/use-callback
|
||||
;; (mf/deps team)
|
||||
;; (st/emitf (rt/nav :dashboard-fonts {:team-id (:id team)})))
|
||||
;; #(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})))
|
||||
|
||||
;; go-providers
|
||||
;; (mf/use-callback
|
||||
;; (mf/deps team)
|
||||
;; (st/emitf (rt/nav :dashboard-font-providers {:team-id (:id team)})))]
|
||||
;; #(st/emit! (rt/nav :dashboard-font-providers {:team-id (:id team)})))]
|
||||
|
||||
(use-set-page-title team section)
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
(dom/set-html-title (tr "title.dashboard.shared-libraries" tname))))))
|
||||
|
||||
(mf/use-effect
|
||||
(st/emitf (dd/fetch-shared-files)
|
||||
(dd/clear-selected-files)))
|
||||
#(st/emit! (dd/fetch-shared-files)
|
||||
(dd/clear-selected-files)))
|
||||
|
||||
[:*
|
||||
[:header.dashboard-header
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
(with-meta project {:on-success on-duplicate-success}))))
|
||||
|
||||
toggle-pin
|
||||
(st/emitf (dd/toggle-project-pin project))
|
||||
#(st/emit! (dd/toggle-project-pin project))
|
||||
|
||||
on-move-success
|
||||
(fn [team-id]
|
||||
@@ -66,7 +66,7 @@
|
||||
(fn [team-id]
|
||||
(let [data {:id (:id project) :team-id team-id}
|
||||
mdata {:on-success #(on-move-success team-id)}]
|
||||
(st/emitf (dm/success (tr "dashboard.success-move-project"))
|
||||
#(st/emit! (dm/success (tr "dashboard.success-move-project"))
|
||||
(dd/move-project (with-meta data mdata)))))
|
||||
|
||||
delete-fn
|
||||
@@ -76,7 +76,7 @@
|
||||
(dd/go-to-projects (:team-id project))))
|
||||
|
||||
on-delete
|
||||
(st/emitf
|
||||
#(st/emit!
|
||||
(modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-project-confirm.title")
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
(mf/defc header
|
||||
{::mf/wrap [mf/memo]}
|
||||
[]
|
||||
(let [create (st/emitf (dd/create-project))]
|
||||
(let [create #(st/emit! (dd/create-project))]
|
||||
[:header.dashboard-header
|
||||
[:div.dashboard-title
|
||||
[:h1 (tr "dashboard.projects-title")]]
|
||||
@@ -49,13 +49,13 @@
|
||||
on-nav
|
||||
(mf/use-callback
|
||||
(mf/deps project)
|
||||
(st/emitf (rt/nav :dashboard-files {:team-id (:team-id project)
|
||||
:project-id (:id project)})))
|
||||
#(st/emit! (rt/nav :dashboard-files {:team-id (:team-id project)
|
||||
:project-id (:id project)})))
|
||||
|
||||
toggle-pin
|
||||
(mf/use-callback
|
||||
(mf/deps project)
|
||||
(st/emitf (dd/toggle-project-pin project)))
|
||||
#(st/emit! (dd/toggle-project-pin project)))
|
||||
|
||||
on-menu-click
|
||||
(mf/use-callback (fn [event]
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[goog.functions :as f]
|
||||
[potok.core :as ptk]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc sidebar-project
|
||||
@@ -101,8 +102,8 @@
|
||||
on-drop-success
|
||||
(mf/use-callback
|
||||
(mf/deps (:id item))
|
||||
(st/emitf (msg/success (tr "dashboard.success-move-file"))
|
||||
(dd/go-to-files (:id item))))
|
||||
#(st/emit! (msg/success (tr "dashboard.success-move-file"))
|
||||
(dd/go-to-files (:id item))))
|
||||
|
||||
on-drop
|
||||
(mf/use-callback
|
||||
@@ -208,7 +209,7 @@
|
||||
|
||||
on-create-clicked
|
||||
(mf/use-callback
|
||||
(st/emitf (modal/show :team-form {})))
|
||||
#(st/emit! (modal/show :team-form {})))
|
||||
|
||||
team-selected
|
||||
(mf/use-callback
|
||||
@@ -239,9 +240,9 @@
|
||||
|
||||
(mf/defc team-options-dropdown
|
||||
[{:keys [team profile] :as props}]
|
||||
(let [go-members (st/emitf (dd/go-to-team-members))
|
||||
go-invitations (st/emitf (dd/go-to-team-invitations))
|
||||
go-settings (st/emitf (dd/go-to-team-settings))
|
||||
(let [go-members #(st/emit! (dd/go-to-team-members))
|
||||
go-invitations #(st/emit! (dd/go-to-team-invitations))
|
||||
go-settings #(st/emit! (dd/go-to-team-settings))
|
||||
|
||||
members-map (mf/deref refs/dashboard-team-members)
|
||||
members (vals members-map)
|
||||
@@ -282,12 +283,12 @@
|
||||
(st/emit! (modal/show :team-form {:team team})))
|
||||
|
||||
on-leave-clicked
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.leave-confirm.title")
|
||||
:message (tr "modals.leave-confirm.message")
|
||||
:accept-label (tr "modals.leave-confirm.accept")
|
||||
:on-accept leave-fn}))
|
||||
#(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.leave-confirm.title")
|
||||
:message (tr "modals.leave-confirm.message")
|
||||
:accept-label (tr "modals.leave-confirm.accept")
|
||||
:on-accept leave-fn}))
|
||||
|
||||
on-leave-as-owner-clicked
|
||||
(fn []
|
||||
@@ -299,22 +300,22 @@
|
||||
:accept leave-fn})))
|
||||
|
||||
leave-and-close
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.leave-confirm.title")
|
||||
:message (tr "modals.leave-and-close-confirm.message" (:name team))
|
||||
:scd-message (tr "modals.leave-and-close-confirm.hint")
|
||||
:accept-label (tr "modals.leave-confirm.accept")
|
||||
:on-accept delete-fn}))
|
||||
#(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.leave-confirm.title")
|
||||
:message (tr "modals.leave-and-close-confirm.message" (:name team))
|
||||
:scd-message (tr "modals.leave-and-close-confirm.hint")
|
||||
:accept-label (tr "modals.leave-confirm.accept")
|
||||
:on-accept delete-fn}))
|
||||
|
||||
on-delete-clicked
|
||||
(st/emitf
|
||||
(modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-team-confirm.title")
|
||||
:message (tr "modals.delete-team-confirm.message")
|
||||
:accept-label (tr "modals.delete-team-confirm.accept")
|
||||
:on-accept delete-fn}))]
|
||||
#(st/emit!
|
||||
(modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-team-confirm.title")
|
||||
:message (tr "modals.delete-team-confirm.message")
|
||||
:accept-label (tr "modals.delete-team-confirm.accept")
|
||||
:on-accept delete-fn}))]
|
||||
|
||||
[:ul.dropdown.options-dropdown
|
||||
[:li {:on-click go-members :data-test "team-members"} (tr "labels.members")]
|
||||
@@ -390,12 +391,12 @@
|
||||
go-projects
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (rt/nav :dashboard-projects {:team-id (:id team)})))
|
||||
#(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)})))
|
||||
|
||||
go-fonts
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (rt/nav :dashboard-fonts {:team-id (:id team)})))
|
||||
#(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})))
|
||||
|
||||
go-drafts
|
||||
(mf/use-callback
|
||||
@@ -407,7 +408,7 @@
|
||||
go-libs
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (rt/nav :dashboard-libraries {:team-id (:id team)})))
|
||||
#(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)})))
|
||||
|
||||
pinned-projects
|
||||
(->> (vals projects)
|
||||
@@ -472,7 +473,16 @@
|
||||
(dom/stop-propagation event)
|
||||
(if (keyword? section)
|
||||
(st/emit! (rt/nav section))
|
||||
(st/emit! section))))]
|
||||
(st/emit! section))))
|
||||
|
||||
show-release-notes
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(let [version (:main @cf/version)]
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version}))
|
||||
(if (and (kbd/alt? event) (kbd/mod? event))
|
||||
(st/emit! (modal/show {:type :onboarding}))
|
||||
(st/emit! (modal/show {:type :release-notes :version version}))))))]
|
||||
|
||||
[:div.profile-section
|
||||
[:div.profile {:on-click #(reset! show true)
|
||||
@@ -485,26 +495,26 @@
|
||||
[:ul.dropdown
|
||||
[:li {:on-click (partial on-click :settings-profile)
|
||||
:data-test "profile-profile-opt"}
|
||||
[:span.icon i/user]
|
||||
[:span.text (tr "labels.your-account")]]
|
||||
[:li.separator {:on-click #(dom/open-new-window "https://help.penpot.app")
|
||||
:data-test "help-center-profile-opt"}
|
||||
[:span.icon i/help]
|
||||
[:span.text (tr "labels.help-center")]]
|
||||
[:li {:on-click #(dom/open-new-window "https://penpot.app/libraries-templates.html")
|
||||
:data-test "libraries-templates-profile-opt"}
|
||||
[:span.icon i/download]
|
||||
[:li {:on-click #(dom/open-new-window "https://www.youtube.com/c/Penpot")}
|
||||
[:span.text (tr "labels.tutorials")]]
|
||||
[:li {:on-click show-release-notes}
|
||||
[:span (tr "labels.release-notes")]]
|
||||
|
||||
[:li.separator {:on-click #(dom/open-new-window "https://penpot.app/libraries-templates.html")
|
||||
:data-test "libraries-templates-profile-opt"}
|
||||
[:span.text (tr "labels.libraries-and-templates")]]
|
||||
;;[:li {:on-click #(dom/open-new-window "https://penpot.app?no-redirect=1")
|
||||
[:li {:on-click #(dom/open-new-window "https://landing-next.penpot.app?no-redirect=1")
|
||||
:data-test "about-penpot-profile-opt"} ;; Parameter ?no-redirect is to force stay in landing page
|
||||
[:span.icon i/logo-icon] ;; instead of redirecting to app
|
||||
[:span.text (tr "labels.about-penpot")]]
|
||||
[:li {:on-click #(dom/open-new-window "https://github.com/penpot/penpot")}
|
||||
[:span (tr "labels.github-repo")]]
|
||||
[:li {:on-click #(dom/open-new-window "https://penpot.app/terms.html")}
|
||||
[:span (tr "auth.terms-of-service")]]
|
||||
|
||||
(when (contains? @cf/flags :user-feedback)
|
||||
[:li.separator {:on-click (partial on-click :settings-feedback)
|
||||
:data-test "feedback-profile-opt"}
|
||||
[:span.icon i/msg-info]
|
||||
[:span.text (tr "labels.give-feedback")]])
|
||||
|
||||
[:li.separator {:on-click #(on-click (du/logout) %)
|
||||
|
||||
@@ -592,8 +592,8 @@
|
||||
|
||||
|
||||
(mf/use-effect
|
||||
(st/emitf (dd/fetch-team-members)
|
||||
(dd/fetch-team-stats)))
|
||||
#(st/emit! (dd/fetch-team-members)
|
||||
(dd/fetch-team-stats)))
|
||||
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-settings
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
[:h2 (tr "labels.create-team")])]
|
||||
|
||||
[:div.modal-close-button
|
||||
{:on-click (st/emitf (modal/hide))} i/close]]
|
||||
{:on-click #(st/emit! (modal/hide))} i/close]]
|
||||
|
||||
[:div.modal-content.generic-form
|
||||
[:& fm/input {:type "text"
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
(def shape-valign-center (icon-xref :shape-valign-center))
|
||||
(def shape-valign-top (icon-xref :shape-valign-top))
|
||||
(def shape-vdistribute (icon-xref :shape-vdistribute))
|
||||
(def shortcut (icon-xref :shortcut))
|
||||
(def size-horiz (icon-xref :size-horiz))
|
||||
(def size-vert (icon-xref :size-vert))
|
||||
(def sort-ascending (icon-xref :sort-ascending))
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
(mf/defc notifications
|
||||
[]
|
||||
(let [message (mf/deref refs/message)
|
||||
on-close (st/emitf dm/hide)]
|
||||
on-close #(st/emit! dm/hide)]
|
||||
(when message
|
||||
[:& banner (assoc message
|
||||
:position (or (:position message) :fixed)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
(ns app.main.ui.onboarding.team-choice
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.spec :as us]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.messages :as dm]
|
||||
@@ -75,8 +76,8 @@
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.onboarding-team
|
||||
[:div.title
|
||||
[:h2 {:data-test "onboarding-choice-team-up"} (tr "onboarding.choice.team-up")]
|
||||
[:p (tr "onboarding.choice.team-up-desc")]]
|
||||
[:h2 {:data-test "onboarding-choice-team-up"} (tr "onboarding.choice.team-up.create-team")]
|
||||
[:p (tr "onboarding.choice.team-up.create-team-desc")]]
|
||||
|
||||
[:& fm/form {:form form
|
||||
:on-submit on-submit}
|
||||
@@ -84,14 +85,14 @@
|
||||
[:div.team-row
|
||||
[:& fm/input {:type "text"
|
||||
:name :name
|
||||
:label (tr "onboarding.team-input-placeholder")}]]
|
||||
:label (tr "onboarding.choice.team-up.create-team-placeholder")}]]
|
||||
|
||||
[:div.buttons
|
||||
[:button.btn-secondary.btn-large
|
||||
{:on-click #(st/emit! (modal/show {:type :onboarding-choice}))}
|
||||
(tr "labels.cancel")]
|
||||
(tr "labels.back")]
|
||||
[:& fm/submit-button
|
||||
{:label (tr "labels.next")}]]]
|
||||
{:label (tr "labels.continue")}]]]
|
||||
|
||||
[:img.deco {:src "images/deco-left.png" :border "0"}]
|
||||
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))
|
||||
@@ -101,10 +102,10 @@
|
||||
[{:value "editor" :label (tr "labels.editor")}
|
||||
{:value "admin" :label (tr "labels.admin")}])
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::emails (s/and ::us/set-of-emails d/not-empty?))
|
||||
(s/def ::role ::us/keyword)
|
||||
(s/def ::invite-form
|
||||
(s/keys :req-un [::role ::email]))
|
||||
(s/keys :req-un [::role ::emails]))
|
||||
|
||||
;; This is the final step of team creation, consists in provide a
|
||||
;; shortcut for invite users.
|
||||
@@ -157,27 +158,31 @@
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.onboarding-team
|
||||
[:div.title
|
||||
[:h2 (tr "onboarding.choice.team-up")]
|
||||
[:p (tr "onboarding.choice.team-up-desc")]]
|
||||
[:h2 (tr "onboarding.choice.team-up.invite-members")]
|
||||
[:p (tr "onboarding.choice.team-up.invite-members-desc")]]
|
||||
|
||||
[:& fm/form {:form form
|
||||
:on-submit on-submit}
|
||||
|
||||
[:div.invite-row
|
||||
[:& fm/input {:name :email
|
||||
:label (tr "labels.email")}]
|
||||
[:& fm/select {:name :role
|
||||
:options roles}]]
|
||||
|
||||
|
||||
[:div.invite-row
|
||||
[:& fm/multi-input {:type "email"
|
||||
:name :emails
|
||||
:auto-focus? true
|
||||
:trim true
|
||||
:valid-item-fn us/parse-email
|
||||
:label (tr "modals.invite-member.emails")}]
|
||||
[:& fm/select {:name :role :options roles}]]
|
||||
|
||||
[:div.buttons
|
||||
[:button.btn-secondary.btn-large
|
||||
{:on-click #(st/emit! (modal/show {:type :onboarding-choice}))}
|
||||
(tr "labels.cancel")]
|
||||
(tr "labels.back")]
|
||||
[:& fm/submit-button
|
||||
{:label (tr "labels.create")}]]
|
||||
{:label (tr "onboarding.choice.team-up.invite-members-submit")}]]
|
||||
[:div.skip-action
|
||||
{:on-click on-skip}
|
||||
[:div.action "Skip and invite later"]]]
|
||||
[:div.action (tr "onboarding.choice.team-up.invite-members-skip")]]]
|
||||
[:img.deco {:src "images/deco-left.png" :border "0"}]
|
||||
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[app.main.ui.releases.v1-11]
|
||||
[app.main.ui.releases.v1-12]
|
||||
[app.main.ui.releases.v1-13]
|
||||
[app.main.ui.releases.v1-14]
|
||||
[app.main.ui.releases.v1-4]
|
||||
[app.main.ui.releases.v1-5]
|
||||
[app.main.ui.releases.v1-6]
|
||||
@@ -44,14 +45,13 @@
|
||||
|
||||
finish
|
||||
(mf/use-callback
|
||||
(st/emitf (modal/hide)
|
||||
(du/mark-onboarding-as-viewed {:version version})))
|
||||
]
|
||||
#(st/emit! (modal/hide)
|
||||
(du/mark-onboarding-as-viewed {:version version})))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps)
|
||||
(fn []
|
||||
(st/emitf (du/mark-onboarding-as-viewed {:version version}))))
|
||||
#(st/emit! (du/mark-onboarding-as-viewed {:version version}))))
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps @slide)
|
||||
@@ -84,4 +84,4 @@
|
||||
|
||||
(defmethod rc/render-release-notes "0.0"
|
||||
[params]
|
||||
(rc/render-release-notes (assoc params :version "1.13")))
|
||||
(rc/render-release-notes (assoc params :version "1.14")))
|
||||
|
||||
108
frontend/src/app/main/ui/releases/v1_14.cljs
Normal file
@@ -0,0 +1,108 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.releases.v1-14
|
||||
(:require
|
||||
[app.main.ui.releases.common :as c]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defmethod c/render-release-notes "1.14"
|
||||
[{:keys [slide klass next finish navigate version]}]
|
||||
(mf/html
|
||||
(case @slide
|
||||
:start
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/login-on.jpg" :border "0" :alt "What's new Beta release 1.14"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "What's new?"]]
|
||||
[:span.release "Beta version " version]
|
||||
[:div.modal-content
|
||||
[:p "Penpot continues to grow with new features that improve performance, user experience and visual design."]
|
||||
[:p "We are happy to show you a sneak peak of the most important stuff that the Beta 1.14 version brings."]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click next} "Continue"]]]
|
||||
[:img.deco {:src "images/deco-left.png" :border "0"}]
|
||||
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]]
|
||||
|
||||
0
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/features/1.14-shortcuts.gif" :border "0" :alt "Shortcuts panel"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "Shortcuts panel"]]
|
||||
[:div.modal-content
|
||||
[:p "Shortcuts boost your productivity but are not easy to find and learn. A handy panel at your workspace will help you with that."]
|
||||
[:p "Categories and filters will help you to find the shortcut you need. One of the most requested features by the community!"]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click next} "Continue"]
|
||||
[:& c/navigation-bullets
|
||||
{:slide @slide
|
||||
:navigate navigate
|
||||
:total 4}]]]]]]
|
||||
|
||||
1
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/features/1.14-color-group.gif" :border "0" :alt "Colors selection"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "Colors selection"]]
|
||||
[:div.modal-content
|
||||
[:p "All of the colors that are contained within a selection of objects are showcased at the sidebar."]
|
||||
[:p "Play with the colors of a group without the hassles of individual selection!"]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click next} "Continue"]
|
||||
[:& c/navigation-bullets
|
||||
{:slide @slide
|
||||
:navigate navigate
|
||||
:total 4}]]]]]]
|
||||
|
||||
2
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/features/1.14-fix-on-scroll.gif" :border "0" :alt "Fix elements at scroll"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "Fix elements at scroll"]]
|
||||
[:div.modal-content
|
||||
[:p "A new option that allows you to fix the position of an object when scrolling at the presentation view."]
|
||||
[:p "Ideal for prototyping fixed headers, navbars and floating buttons."]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click next} "Continue"]
|
||||
[:& c/navigation-bullets
|
||||
{:slide @slide
|
||||
:navigate navigate
|
||||
:total 4}]]]]]]
|
||||
|
||||
3
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/features/1.14-group-assets.gif" :border "0" :alt "Group library assets with drag & drop"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "Group library assets with drag & drop"]]
|
||||
[:div.modal-content
|
||||
[:p "We have improved the way to manage asset groups at libraries."]
|
||||
[:p "Until now you could only do it by renaming the groups, now with drag & drop it is much more user friendly."]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click finish} "Start!"]
|
||||
[:& c/navigation-bullets
|
||||
{:slide @slide
|
||||
:navigate navigate
|
||||
:total 4}]]]]]])))
|
||||
@@ -77,7 +77,7 @@
|
||||
:validators [email-equality]
|
||||
:initial profile)
|
||||
on-close
|
||||
(mf/use-callback (st/emitf (modal/hide)))]
|
||||
(mf/use-callback #(st/emit! (modal/hide)))]
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.change-email-modal.form-container
|
||||
|
||||
@@ -28,13 +28,13 @@
|
||||
::mf/register-as :delete-account}
|
||||
[]
|
||||
(let [on-close
|
||||
(mf/use-callback (st/emitf (modal/hide)))
|
||||
(mf/use-callback #(st/emit! (modal/hide)))
|
||||
|
||||
on-accept
|
||||
(mf/use-callback
|
||||
(st/emitf (modal/hide)
|
||||
(du/request-account-deletion
|
||||
(with-meta {} {:on-error on-error}))))]
|
||||
#(st/emit! (modal/hide)
|
||||
(du/request-account-deletion
|
||||
(with-meta {} {:on-error on-error}))))]
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.change-email-modal
|
||||
|
||||
@@ -29,27 +29,27 @@
|
||||
go-dashboard
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(st/emitf (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)})))
|
||||
#(st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)})))
|
||||
|
||||
go-settings-profile
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(st/emitf (rt/nav :settings-profile)))
|
||||
#(st/emit! (rt/nav :settings-profile)))
|
||||
|
||||
go-settings-feedback
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(st/emitf (rt/nav :settings-feedback)))
|
||||
#(st/emit! (rt/nav :settings-feedback)))
|
||||
|
||||
go-settings-password
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(st/emitf (rt/nav :settings-password)))
|
||||
#(st/emit! (rt/nav :settings-password)))
|
||||
|
||||
go-settings-options
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(st/emitf (rt/nav :settings-options)))
|
||||
#(st/emit! (rt/nav :settings-options)))
|
||||
|
||||
show-release-notes
|
||||
(mf/use-callback
|
||||
|
||||
@@ -56,49 +56,53 @@
|
||||
attrs))
|
||||
|
||||
(defn add-fill
|
||||
([attrs shape render-id]
|
||||
(add-fill attrs shape render-id nil))
|
||||
([attrs fill-data render-id type]
|
||||
(add-fill attrs fill-data render-id nil type))
|
||||
|
||||
([attrs shape render-id index]
|
||||
([attrs fill-data render-id index type]
|
||||
(let [fill-attrs
|
||||
(cond
|
||||
(contains? shape :fill-image)
|
||||
(contains? fill-data :fill-image)
|
||||
(let [fill-image-id (str "fill-image-" render-id)]
|
||||
{:fill (str "url(#" fill-image-id ")")})
|
||||
|
||||
(and (contains? shape :fill-color-gradient) (some? (:fill-color-gradient shape)))
|
||||
(and (contains? fill-data :fill-color-gradient) (some? (:fill-color-gradient fill-data)))
|
||||
(let [fill-color-gradient-id (str "fill-color-gradient_" render-id (if index (str "_" index) ""))]
|
||||
{:fill (str "url(#" fill-color-gradient-id ")")})
|
||||
|
||||
(contains? shape :fill-color)
|
||||
{:fill (:fill-color shape)}
|
||||
(contains? fill-data :fill-color)
|
||||
{:fill (:fill-color fill-data)}
|
||||
|
||||
:else
|
||||
{:fill "none"})
|
||||
|
||||
fill-attrs (cond-> fill-attrs
|
||||
(contains? shape :fill-opacity)
|
||||
(assoc :fillOpacity (:fill-opacity shape)))]
|
||||
(contains? fill-data :fill-opacity)
|
||||
(assoc :fillOpacity (:fill-opacity fill-data))
|
||||
|
||||
;; Old texts with only an opacity set are black by default
|
||||
(and (= type :text) (nil? (:fill-color-gradient fill-data)) (nil? (:fill-color fill-data)))
|
||||
(assoc :fill "black"))]
|
||||
|
||||
(obj/merge! attrs (clj->js fill-attrs)))))
|
||||
|
||||
(defn add-stroke [attrs shape render-id index]
|
||||
(let [stroke-style (:stroke-style shape :none)
|
||||
(defn add-stroke [attrs stroke-data render-id index]
|
||||
(let [stroke-style (:stroke-style stroke-data :none)
|
||||
stroke-color-gradient-id (str "stroke-color-gradient_" render-id "_" index)
|
||||
stroke-width (:stroke-width shape 1)]
|
||||
stroke-width (:stroke-width stroke-data 1)]
|
||||
(if (not= stroke-style :none)
|
||||
(let [stroke-attrs
|
||||
(cond-> {:strokeWidth stroke-width}
|
||||
(:stroke-color-gradient shape)
|
||||
(:stroke-color-gradient stroke-data)
|
||||
(assoc :stroke (str/format "url(#%s)" stroke-color-gradient-id))
|
||||
|
||||
(and (not (:stroke-color-gradient shape))
|
||||
(:stroke-color shape nil))
|
||||
(assoc :stroke (:stroke-color shape nil))
|
||||
(and (not (:stroke-color-gradient stroke-data))
|
||||
(:stroke-color stroke-data nil))
|
||||
(assoc :stroke (:stroke-color stroke-data nil))
|
||||
|
||||
(and (not (:stroke-color-gradient shape))
|
||||
(:stroke-opacity shape nil))
|
||||
(assoc :strokeOpacity (:stroke-opacity shape nil))
|
||||
(and (not (:stroke-color-gradient stroke-data))
|
||||
(:stroke-opacity stroke-data nil))
|
||||
(assoc :strokeOpacity (:stroke-opacity stroke-data nil))
|
||||
|
||||
(not= stroke-style :svg)
|
||||
(assoc :strokeDasharray (stroke-type->dasharray stroke-width stroke-style))
|
||||
@@ -106,29 +110,29 @@
|
||||
;; For simple line caps we use svg stroke-line-cap attribute. This
|
||||
;; only works if all caps are the same and we are not using the tricks
|
||||
;; for inner or outer strokes.
|
||||
(and (stroke-caps-line (:stroke-cap-start shape))
|
||||
(= (:stroke-cap-start shape) (:stroke-cap-end shape))
|
||||
(not (#{:inner :outer} (:stroke-alignment shape)))
|
||||
(and (stroke-caps-line (:stroke-cap-start stroke-data))
|
||||
(= (:stroke-cap-start stroke-data) (:stroke-cap-end stroke-data))
|
||||
(not (#{:inner :outer} (:stroke-alignment stroke-data)))
|
||||
(not= :dotted stroke-style))
|
||||
(assoc :strokeLinecap (:stroke-cap-start shape))
|
||||
(assoc :strokeLinecap (:stroke-cap-start stroke-data))
|
||||
|
||||
(= :dotted stroke-style)
|
||||
(assoc :strokeLinecap "round")
|
||||
|
||||
;; For other cap types we use markers.
|
||||
(and (or (stroke-caps-marker (:stroke-cap-start shape))
|
||||
(and (stroke-caps-line (:stroke-cap-start shape))
|
||||
(not= (:stroke-cap-start shape) (:stroke-cap-end shape))))
|
||||
(not (#{:inner :outer} (:stroke-alignment shape))))
|
||||
(and (or (stroke-caps-marker (:stroke-cap-start stroke-data))
|
||||
(and (stroke-caps-line (:stroke-cap-start stroke-data))
|
||||
(not= (:stroke-cap-start stroke-data) (:stroke-cap-end stroke-data))))
|
||||
(not (#{:inner :outer} (:stroke-alignment stroke-data))))
|
||||
(assoc :markerStart
|
||||
(str/format "url(#marker-%s-%s)" render-id (name (:stroke-cap-start shape))))
|
||||
(str/format "url(#marker-%s-%s)" render-id (name (:stroke-cap-start stroke-data))))
|
||||
|
||||
(and (or (stroke-caps-marker (:stroke-cap-end shape))
|
||||
(and (stroke-caps-line (:stroke-cap-end shape))
|
||||
(not= (:stroke-cap-start shape) (:stroke-cap-end shape))))
|
||||
(not (#{:inner :outer} (:stroke-alignment shape))))
|
||||
(and (or (stroke-caps-marker (:stroke-cap-end stroke-data))
|
||||
(and (stroke-caps-line (:stroke-cap-end stroke-data))
|
||||
(not= (:stroke-cap-start stroke-data) (:stroke-cap-end stroke-data))))
|
||||
(not (#{:inner :outer} (:stroke-alignment stroke-data))))
|
||||
(assoc :markerEnd
|
||||
(str/format "url(#marker-%s-%s)" render-id (name (:stroke-cap-end shape)))))]
|
||||
(str/format "url(#marker-%s-%s)" render-id (name (:stroke-cap-end stroke-data)))))]
|
||||
|
||||
(obj/merge! attrs (clj->js stroke-attrs)))
|
||||
attrs)))
|
||||
@@ -195,7 +199,7 @@
|
||||
(obj/set! "fill" (or (obj/get (:wrapper-styles shape) "fill") clr/black)))
|
||||
|
||||
(d/not-empty? (:fills shape))
|
||||
(add-fill styles (d/without-nils (get-in shape [:fills 0])) render-id 0)
|
||||
(add-fill styles (d/without-nils (get-in shape [:fills 0])) render-id 0 (:type shape))
|
||||
|
||||
:else
|
||||
styles)]
|
||||
@@ -211,16 +215,16 @@
|
||||
(add-style-attrs shape)))
|
||||
|
||||
(defn extract-fill-attrs
|
||||
[shape render-id index]
|
||||
(let [fill-styles (-> (obj/get shape "style" (obj/new))
|
||||
(add-fill shape render-id index))]
|
||||
[fill-data render-id index type]
|
||||
(let [fill-styles (-> (obj/get fill-data "style" (obj/new))
|
||||
(add-fill fill-data render-id index type))]
|
||||
(-> (obj/new)
|
||||
(obj/set! "style" fill-styles))))
|
||||
|
||||
(defn extract-stroke-attrs
|
||||
[shape index render-id]
|
||||
(let [stroke-styles (-> (obj/get shape "style" (obj/new))
|
||||
(add-stroke shape render-id index))]
|
||||
[stroke-data index render-id]
|
||||
(let [stroke-styles (-> (obj/get stroke-data "style" (obj/new))
|
||||
(add-stroke stroke-data render-id index))]
|
||||
(-> (obj/new)
|
||||
(obj/set! "style" stroke-styles))))
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@
|
||||
|
||||
(d/not-empty? (:fills shape))
|
||||
(let [fill-props
|
||||
(attrs/extract-fill-attrs (get-in shape [:fills 0]) render-id 0)
|
||||
(attrs/extract-fill-attrs (get-in shape [:fills 0]) render-id 0 (:type shape))
|
||||
|
||||
style (-> (obj/get props "style")
|
||||
(obj/clone)
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
:width width
|
||||
:data-loading loading?}
|
||||
(= :path (:type shape))
|
||||
(obj/set! "patternTransform" transform))]
|
||||
(obj/set! "patternTransform" transform))
|
||||
type (:type shape)]
|
||||
|
||||
(for [[shape-index shape] (d/enumerate (or (:position-data shape) [shape]))]
|
||||
[:* {:key (dm/str shape-index)}
|
||||
@@ -73,7 +74,7 @@
|
||||
(obj/set! "id" fill-id))
|
||||
[:g
|
||||
(for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)]
|
||||
[:> :rect (-> (attrs/extract-fill-attrs value render-id fill-index)
|
||||
[:> :rect (-> (attrs/extract-fill-attrs value render-id fill-index type)
|
||||
(obj/set! "key" (dm/str fill-index))
|
||||
(obj/set! "width" width)
|
||||
(obj/set! "height" height))])
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
objects (unchecked-get props "objects")
|
||||
render-id (mf/use-ctx muc/render-ctx)
|
||||
masked-group? (:masked-group? shape)
|
||||
|
||||
@@ -41,12 +42,21 @@
|
||||
(if masked-group?
|
||||
["g" (-> (obj/new)
|
||||
(obj/set! "mask" (mask-url render-id mask)))]
|
||||
[mf/Fragment nil])]
|
||||
[mf/Fragment nil])
|
||||
|
||||
;; This factory is generic, it's used for viewer, workspace and handoff.
|
||||
;; These props are generated in viewer wrappers only, in the rest of the
|
||||
;; cases these props will be nil, not affecting the code.
|
||||
delta (unchecked-get props "delta")
|
||||
fixed? (unchecked-get props "fixed?")]
|
||||
|
||||
[:> clip-wrapper clip-props
|
||||
[:> mask-wrapper mask-props
|
||||
(when masked-group?
|
||||
[:> render-mask #js {:mask mask}])
|
||||
[:> render-mask #js {:mask mask
|
||||
:objects objects
|
||||
:delta delta
|
||||
:fixed? fixed?}])
|
||||
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape item
|
||||
|
||||
@@ -46,11 +46,19 @@
|
||||
(mf/fnc mask-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [mask (unchecked-get props "mask")
|
||||
render-id (mf/use-ctx muc/render-ctx)
|
||||
svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
|
||||
(let [mask (unchecked-get props "mask")
|
||||
render-id (mf/use-ctx muc/render-ctx)
|
||||
svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
|
||||
|
||||
mask-bb (-> (gsh/transform-shape mask) (:points))
|
||||
;; This factory is generic, it's used for viewer, workspace and handoff.
|
||||
;; These props are generated in viewer wrappers only, in the rest of the
|
||||
;; cases these props will be nil, not affecting the code.
|
||||
fixed? (unchecked-get props "fixed?")
|
||||
delta (unchecked-get props "delta")
|
||||
mask-bb (-> (gsh/transform-shape mask)
|
||||
(cond-> fixed? (gsh/move delta))
|
||||
(:points))
|
||||
|
||||
mask-bb-rect (gsh/points->rect mask-bb)]
|
||||
[:defs
|
||||
[:filter {:id (filter-id render-id mask)}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
[:div.desc-message (tr "labels.bad-gateway.desc-message")]
|
||||
[:div.sign-info
|
||||
[:a.btn-primary.btn-small
|
||||
{:on-click (st/emitf #(dissoc % :exception))}
|
||||
{:on-click (fn [] (st/emit! #(dissoc % :exception)))}
|
||||
(tr "labels.retry")]]])
|
||||
|
||||
(mf/defc service-unavailable
|
||||
@@ -52,7 +52,7 @@
|
||||
[:div.desc-message (tr "labels.service-unavailable.desc-message")]
|
||||
[:div.sign-info
|
||||
[:a.btn-primary.btn-small
|
||||
{:on-click (st/emitf #(dissoc % :exception))}
|
||||
{:on-click (fn [] (st/emit! #(dissoc % :exception)))}
|
||||
(tr "labels.retry")]]])
|
||||
|
||||
(mf/defc internal-error
|
||||
@@ -63,7 +63,7 @@
|
||||
[:div.desc-message (tr "labels.internal-error.desc-message")]
|
||||
[:div.sign-info
|
||||
[:a.btn-primary.btn-small
|
||||
{:on-click (st/emitf (rt/assign-exception nil))}
|
||||
{:on-click (fn [] (st/emit! (rt/assign-exception nil)))}
|
||||
(tr "labels.retry")]]])
|
||||
|
||||
(mf/defc exception-page
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
@@ -70,6 +71,7 @@
|
||||
nav-scroll (:nav-scroll local)
|
||||
orig-viewport-ref (mf/use-ref nil)
|
||||
current-viewport-ref (mf/use-ref nil)
|
||||
viewer-section-ref (mf/use-ref nil)
|
||||
current-animation (:current-animation local)
|
||||
|
||||
page-id (or page-id (-> file :data :pages first))
|
||||
@@ -90,6 +92,7 @@
|
||||
frame (get frames index)
|
||||
fullscreen? (mf/deref refs/viewer-fullscreen?)
|
||||
overlays (:overlays local)
|
||||
scroll (mf/use-state nil)
|
||||
|
||||
orig-frame
|
||||
(when (:orig-frame-id current-animation)
|
||||
@@ -127,7 +130,11 @@
|
||||
(fn [_]
|
||||
(let [viewer-section (dom/get-element "viewer-section")
|
||||
size (dom/get-client-size viewer-section)]
|
||||
(st/emit! (dv/set-viewport-size {:size size})))))]
|
||||
(st/emit! (dv/set-viewport-size {:size size})))))
|
||||
|
||||
on-scroll
|
||||
(fn [event]
|
||||
(reset! scroll (dom/get-target-scroll event)))]
|
||||
|
||||
(hooks/use-shortcuts ::viewer sc/shortcuts)
|
||||
|
||||
@@ -144,9 +151,11 @@
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(dom/set-html-theme-color clr/gray-50 "dark")
|
||||
(let [key1 (events/listen js/window "click" on-click)]
|
||||
(let [key1 (events/listen js/window "click" on-click)
|
||||
key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)]
|
||||
(fn []
|
||||
(events/unlistenByKey key1)))))
|
||||
(events/unlistenByKey key1)
|
||||
(events/unlistenByKey key2)))))
|
||||
|
||||
(mf/use-layout-effect
|
||||
(fn []
|
||||
@@ -258,8 +267,10 @@
|
||||
[:& thumbnails-panel {:frames frames
|
||||
:show? (:show-thumbnails local false)
|
||||
:page page
|
||||
:index index}]
|
||||
:index index
|
||||
:thumbnail-data (:thumbnails file)}]
|
||||
[:section.viewer-section {:id "viewer-section"
|
||||
:ref viewer-section-ref
|
||||
:class (if fullscreen? "fullscreen" "")}
|
||||
(cond
|
||||
(empty? frames)
|
||||
@@ -284,79 +295,79 @@
|
||||
[:div.viewer-wrapper
|
||||
{:style {:width (:width wrapper-size)
|
||||
:height (:height wrapper-size)}}
|
||||
[:& (mf/provider ctx/scroll-ctx) {:value @scroll}
|
||||
[:div.viewer-clipper
|
||||
[:*
|
||||
(when orig-frame
|
||||
[:div.viewport-container
|
||||
{:ref orig-viewport-ref
|
||||
:style {:width (:width orig-size)
|
||||
:height (:height orig-size)
|
||||
:position "relative"}}
|
||||
|
||||
[:div.viewer-clipper
|
||||
[:*
|
||||
(when orig-frame
|
||||
[:div.viewport-container
|
||||
{:ref orig-viewport-ref
|
||||
:style {:width (:width orig-size)
|
||||
:height (:height orig-size)
|
||||
:position "relative"}}
|
||||
[:& interactions/viewport
|
||||
{:frame orig-frame
|
||||
:base-frame orig-frame
|
||||
:frame-offset (gpt/point 0 0)
|
||||
:size orig-size
|
||||
:page page
|
||||
:file file
|
||||
:users users
|
||||
:interactions-mode :hide}]])
|
||||
|
||||
[:& interactions/viewport
|
||||
{:frame orig-frame
|
||||
:base-frame orig-frame
|
||||
:frame-offset (gpt/point 0 0)
|
||||
:size orig-size
|
||||
:page page
|
||||
:file file
|
||||
:users users
|
||||
:interactions-mode :hide}]])
|
||||
[:div.viewport-container
|
||||
{:ref current-viewport-ref
|
||||
:style {:width (:width size)
|
||||
:height (:height size)
|
||||
:position "relative"}}
|
||||
|
||||
[:div.viewport-container
|
||||
{:ref current-viewport-ref
|
||||
:style {:width (:width size)
|
||||
:height (:height size)
|
||||
:position "relative"}}
|
||||
[:& interactions/viewport
|
||||
{:frame frame
|
||||
:base-frame frame
|
||||
:frame-offset (gpt/point 0 0)
|
||||
:size size
|
||||
:page page
|
||||
:file file
|
||||
:users users
|
||||
:interactions-mode interactions-mode}]
|
||||
|
||||
[:& interactions/viewport
|
||||
{:frame frame
|
||||
:base-frame frame
|
||||
:frame-offset (gpt/point 0 0)
|
||||
:size size
|
||||
:page page
|
||||
:file file
|
||||
:users users
|
||||
:interactions-mode interactions-mode}]
|
||||
(for [overlay overlays]
|
||||
(let [size-over (calculate-size (:frame overlay) zoom)]
|
||||
[:*
|
||||
(when (or (:close-click-outside overlay)
|
||||
(:background-overlay overlay))
|
||||
[:div.viewer-overlay-background
|
||||
{:class (dom/classnames
|
||||
:visible (:background-overlay overlay))
|
||||
:style {:width (:width wrapper-size)
|
||||
:height (:height wrapper-size)
|
||||
:position "absolute"
|
||||
:left 0
|
||||
:top 0}
|
||||
:on-click #(when (:close-click-outside overlay)
|
||||
(close-overlay (:frame overlay)))}])
|
||||
[:div.viewport-container.viewer-overlay
|
||||
{:id (str "overlay-" (str (:id (:frame overlay))))
|
||||
:style {:width (:width size-over)
|
||||
:height (:height size-over)
|
||||
:left (* (:x (:position overlay)) zoom)
|
||||
:top (* (:y (:position overlay)) zoom)}}
|
||||
[:& interactions/viewport
|
||||
{:frame (:frame overlay)
|
||||
:base-frame frame
|
||||
:frame-offset (:position overlay)
|
||||
:size size-over
|
||||
:page page
|
||||
:file file
|
||||
:users users
|
||||
:interactions-mode interactions-mode}]]]))]]
|
||||
|
||||
(for [overlay overlays]
|
||||
(let [size-over (calculate-size (:frame overlay) zoom)]
|
||||
[:*
|
||||
(when (or (:close-click-outside overlay)
|
||||
(:background-overlay overlay))
|
||||
[:div.viewer-overlay-background
|
||||
{:class (dom/classnames
|
||||
:visible (:background-overlay overlay))
|
||||
:style {:width (:width wrapper-size)
|
||||
:height (:height wrapper-size)
|
||||
:position "absolute"
|
||||
:left 0
|
||||
:top 0}
|
||||
:on-click #(when (:close-click-outside overlay)
|
||||
(close-overlay (:frame overlay)))}])
|
||||
[:div.viewport-container.viewer-overlay
|
||||
{:id (str "overlay-" (str (:id (:frame overlay))))
|
||||
:style {:width (:width size-over)
|
||||
:height (:height size-over)
|
||||
:left (* (:x (:position overlay)) zoom)
|
||||
:top (* (:y (:position overlay)) zoom)}}
|
||||
[:& interactions/viewport
|
||||
{:frame (:frame overlay)
|
||||
:base-frame frame
|
||||
:frame-offset (:position overlay)
|
||||
:size size-over
|
||||
:page page
|
||||
:file file
|
||||
:users users
|
||||
:interactions-mode interactions-mode}]]]))]]
|
||||
|
||||
(when (= section :comments)
|
||||
[:& comments-layer {:file file
|
||||
:users users
|
||||
:frame frame
|
||||
:page page
|
||||
:zoom zoom}])]]]))]]]))
|
||||
(when (= section :comments)
|
||||
[:& comments-layer {:file file
|
||||
:users users
|
||||
:frame frame
|
||||
:page page
|
||||
:zoom zoom}])]]]]))]]]))
|
||||
|
||||
;; --- Component: Viewer Page
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
on-draft-cancel
|
||||
(mf/use-callback
|
||||
(mf/deps cstate)
|
||||
(st/emitf (dcm/close-thread)))
|
||||
#(st/emit! (dcm/close-thread)))
|
||||
|
||||
on-draft-submit
|
||||
(mf/use-callback
|
||||
|
||||
@@ -92,11 +92,11 @@
|
||||
[:& export-progress-widget]
|
||||
[:& zoom-widget
|
||||
{:zoom zoom
|
||||
:on-increase (st/emitf dv/increase-zoom)
|
||||
:on-decrease (st/emitf dv/decrease-zoom)
|
||||
:on-zoom-reset (st/emitf dv/reset-zoom)
|
||||
:on-zoom-fill (st/emitf dv/zoom-to-fill)
|
||||
:on-zoom-fit (st/emitf dv/zoom-to-fit)
|
||||
:on-increase #(st/emit! dv/increase-zoom)
|
||||
:on-decrease #(st/emit! dv/decrease-zoom)
|
||||
:on-zoom-reset #(st/emit! dv/reset-zoom)
|
||||
:on-zoom-fill #(st/emit! dv/zoom-to-fill)
|
||||
:on-zoom-fit #(st/emit! dv/zoom-to-fit)
|
||||
:on-fullscreen toggle-fullscreen}]
|
||||
|
||||
[:span.btn-icon-dark.btn-small.tooltip.tooltip-bottom-left
|
||||
@@ -172,7 +172,7 @@
|
||||
(mf/defc header
|
||||
[{:keys [project file page frame zoom section permissions index]}]
|
||||
(let [go-to-dashboard
|
||||
(st/emitf (dv/go-to-dashboard))
|
||||
#(st/emit! (dv/go-to-dashboard))
|
||||
|
||||
go-to-handoff
|
||||
(fn []
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
(ns app.main.ui.viewer.shapes
|
||||
"The main container for a frame in viewer mode"
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec.interactions :as cti]
|
||||
[app.main.data.viewer :as dv]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.shapes.bool :as bool]
|
||||
[app.main.ui.shapes.circle :as circle]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
@@ -214,7 +216,8 @@
|
||||
childs (unchecked-get props "childs")
|
||||
frame (unchecked-get props "frame")
|
||||
objects (unchecked-get props "objects")
|
||||
|
||||
fixed? (unchecked-get props "fixed?")
|
||||
delta (unchecked-get props "delta")
|
||||
base-frame (mf/use-ctx base-frame-ctx)
|
||||
frame-offset (mf/use-ctx frame-offset-ctx)
|
||||
|
||||
@@ -241,7 +244,10 @@
|
||||
[:& component {:shape shape
|
||||
:frame frame
|
||||
:childs childs
|
||||
:is-child-selected? true}]
|
||||
:is-child-selected? true
|
||||
:objects objects
|
||||
:fixed? fixed?
|
||||
:delta delta}]
|
||||
|
||||
[:& interaction {:shape shape
|
||||
:interactions interactions
|
||||
@@ -250,7 +256,8 @@
|
||||
;; Don't wrap svg elements inside a <g> otherwise some can break
|
||||
[:& component {:shape shape
|
||||
:frame frame
|
||||
:childs childs}]))))
|
||||
:childs childs
|
||||
:objects objects}]))))
|
||||
|
||||
(defn frame-wrapper
|
||||
[shape-container]
|
||||
@@ -313,12 +320,12 @@
|
||||
(mf/fnc group-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
props (obj/merge! #js {} props
|
||||
#js {:childs childs
|
||||
:objects objects})]
|
||||
[:> group-wrapper props]))))
|
||||
(let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape")))
|
||||
props (obj/merge! #js {} props
|
||||
#js {:childs childs
|
||||
:objects objects})]
|
||||
(when (not-empty childs)
|
||||
[:> group-wrapper props])))))
|
||||
|
||||
(defn bool-container-factory
|
||||
[objects]
|
||||
@@ -327,8 +334,7 @@
|
||||
(mf/fnc bool-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (->> (cph/get-children-ids objects (:id shape))
|
||||
(let [childs (->> (cph/get-children-ids objects (:id (unchecked-get props "shape")))
|
||||
(select-keys objects))
|
||||
props (obj/merge! #js {} props
|
||||
#js {:childs childs
|
||||
@@ -342,8 +348,7 @@
|
||||
(mf/fnc svg-raw-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
(let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape")))
|
||||
props (obj/merge! #js {} props
|
||||
#js {:childs childs
|
||||
:objects objects})]
|
||||
@@ -359,7 +364,15 @@
|
||||
(mf/fnc shape-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [group-container
|
||||
(let [scroll (mf/use-ctx ctx/scroll-ctx)
|
||||
local (mf/deref refs/viewer-local)
|
||||
zoom (:zoom local)
|
||||
shape (unchecked-get props "shape")
|
||||
parents (map (d/getf objects) (cph/get-parent-ids objects (:id shape)))
|
||||
fixed? (or (:fixed-scroll shape) (some :fixed-scroll parents))
|
||||
frame (unchecked-get props "frame")
|
||||
delta {:x (/ (:scroll-left scroll) zoom) :y (/ (:scroll-top scroll) zoom)}
|
||||
group-container
|
||||
(mf/use-memo (mf/deps objects)
|
||||
#(group-container-factory objects))
|
||||
|
||||
@@ -369,12 +382,12 @@
|
||||
|
||||
svg-raw-container
|
||||
(mf/use-memo (mf/deps objects)
|
||||
#(svg-raw-container-factory objects))
|
||||
shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")]
|
||||
#(svg-raw-container-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (-> (geom/transform-shape shape)
|
||||
(geom/translate-to-frame frame))
|
||||
(geom/translate-to-frame frame)
|
||||
(cond-> fixed? (geom/move delta)))
|
||||
|
||||
opts #js {:shape shape
|
||||
:objects objects}]
|
||||
(case (:type shape)
|
||||
@@ -384,7 +397,7 @@
|
||||
:path [:> path-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:group [:> group-container {:shape shape :frame frame :objects objects}]
|
||||
:group [:> group-container {:shape shape :frame frame :objects objects :fixed? fixed? :delta delta}]
|
||||
:bool [:> bool-container {:shape shape :frame frame :objects objects}]
|
||||
:svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}])))))))
|
||||
|
||||
|
||||