mirror of
https://github.com/penpot/penpot.git
synced 2026-01-08 06:19:02 -05:00
Compare commits
158 Commits
nitrate-mo
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
795f65632a | ||
|
|
952f622ce9 | ||
|
|
a6c6f97f47 | ||
|
|
88424eb54a | ||
|
|
919f78daeb | ||
|
|
b5c30f8c41 | ||
|
|
60aa426753 | ||
|
|
86f7d6b26b | ||
|
|
36732a4bd3 | ||
|
|
eff572d3bb | ||
|
|
d470d96833 | ||
|
|
cab70773d2 | ||
|
|
de9a21121a | ||
|
|
32ca42a093 | ||
|
|
523a97a4ec | ||
|
|
260f6861a3 | ||
|
|
edd53b419a | ||
|
|
cea10308b7 | ||
|
|
078a3d5a5c | ||
|
|
c4e57427ac | ||
|
|
5223c9c881 | ||
|
|
be62fa10c4 | ||
|
|
7a6405481c | ||
|
|
218f34380a | ||
|
|
47aaa2b5fa | ||
|
|
6c6b3db87e | ||
|
|
6eb32cfb79 | ||
|
|
dbba3496af | ||
|
|
55752d361f | ||
|
|
fe94ee4526 | ||
|
|
e39f292499 | ||
|
|
52b8560b70 | ||
|
|
75860afe57 | ||
|
|
824ca1bbca | ||
|
|
5b6f9c1741 | ||
|
|
19853b832b | ||
|
|
d20c011db2 | ||
|
|
9431ae6858 | ||
|
|
96356c1b89 | ||
|
|
b7b68eeb47 | ||
|
|
9bbeb657f8 | ||
|
|
ec1af4ad96 | ||
|
|
23e7116b24 | ||
|
|
48e3f35bb3 | ||
|
|
6b794c9d12 | ||
|
|
d3ee50daf5 | ||
|
|
22a36d59d8 | ||
|
|
a948e49e51 | ||
|
|
d635f5a8dc | ||
|
|
ab3a3ef43b | ||
|
|
9c21fd3359 | ||
|
|
7b5817f407 | ||
|
|
e3405eacca | ||
|
|
44b70cf1d4 | ||
|
|
a8bd74b392 | ||
|
|
3d3e3582d6 | ||
|
|
de052b5161 | ||
|
|
e01654ba43 | ||
|
|
6ebd48b94c | ||
|
|
8a3b33797f | ||
|
|
13fd20f76f | ||
|
|
417cd80564 | ||
|
|
a57011ec7b | ||
|
|
69c880d00e | ||
|
|
9eebc467ef | ||
|
|
b77712ce73 | ||
|
|
3d3e81f314 | ||
|
|
fe6441bb24 | ||
|
|
e15f0baf30 | ||
|
|
c040cbb784 | ||
|
|
7f674b78a9 | ||
|
|
099b78affd | ||
|
|
78cc3f0aa4 | ||
|
|
76f5f12808 | ||
|
|
cb325282ec | ||
|
|
01ecde3bfa | ||
|
|
047483a70a | ||
|
|
4000ec8762 | ||
|
|
8cb2f27de8 | ||
|
|
0433336fc9 | ||
|
|
ce234fbeda | ||
|
|
fc4d31eed7 | ||
|
|
c670aac339 | ||
|
|
1d3fb5434f | ||
|
|
f478399ae0 | ||
|
|
6a1854f180 | ||
|
|
0858e297e5 | ||
|
|
bd580ab159 | ||
|
|
5780a43fe0 | ||
|
|
737eceda3a | ||
|
|
923c3c2dbd | ||
|
|
a14b4561e7 | ||
|
|
bb5568e15a | ||
|
|
5cbcec3db6 | ||
|
|
105e1fe86c | ||
|
|
3e0a916883 | ||
|
|
4f80238bc2 | ||
|
|
5156cc5d9a | ||
|
|
42c46b6cfc | ||
|
|
8b3c40b35e | ||
|
|
d3996e5fb1 | ||
|
|
0c42bca866 | ||
|
|
e5685c1f1c | ||
|
|
2784209bde | ||
|
|
024f460e99 | ||
|
|
1d9b76b62a | ||
|
|
7e17a75b7d | ||
|
|
ca093d6fae | ||
|
|
0f0b7562b5 | ||
|
|
9cdc694697 | ||
|
|
b972a4033b | ||
|
|
cbe9f4da51 | ||
|
|
c583bde9e3 | ||
|
|
3911ebdc4e | ||
|
|
3e3b18667b | ||
|
|
ed81c9b8df | ||
|
|
fbdf98d29c | ||
|
|
e603825a55 | ||
|
|
1d724783e6 | ||
|
|
e0abe7dcb5 | ||
|
|
5c1bbf5be8 | ||
|
|
bbb0d58190 | ||
|
|
88dcf9d1fe | ||
|
|
fe44c14bac | ||
|
|
20061067ad | ||
|
|
336173645e | ||
|
|
2acf15958b | ||
|
|
08267de242 | ||
|
|
35fb376a78 | ||
|
|
83bb4bf221 | ||
|
|
dba6ae2820 | ||
|
|
ada101c236 | ||
|
|
ea48fb5825 | ||
|
|
15ed25ca79 | ||
|
|
9aa387a473 | ||
|
|
67ba91b4b9 | ||
|
|
f67f1a6a0e | ||
|
|
82d3e2024e | ||
|
|
4bd846c16d | ||
|
|
8fde6b28ed | ||
|
|
63325ec796 | ||
|
|
84415476d0 | ||
|
|
94f95ca6b8 | ||
|
|
507bf7445b | ||
|
|
81b72c5acd | ||
|
|
974495e08f | ||
|
|
2ed39e43c3 | ||
|
|
b45bdd723f | ||
|
|
8696044620 | ||
|
|
4f3ca6422c | ||
|
|
1c03457fda | ||
|
|
74d4b9b045 | ||
|
|
60df56caa3 | ||
|
|
34da754357 | ||
|
|
39eafae251 | ||
|
|
e1e09b7f96 | ||
|
|
3b39980f2f | ||
|
|
223b12d2c7 |
14
.github/workflows/build-staging-render.yml
vendored
Normal file
14
.github/workflows/build-staging-render.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: _STAGING RENDER
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '36 5-20 * * 1-5'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-bundle:
|
||||||
|
uses: ./.github/workflows/build-bundle.yml
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
gh_ref: "staging-render"
|
||||||
|
build_wasm: "yes"
|
||||||
|
build_storybook: "yes"
|
||||||
2
.github/workflows/build-tag.yml
vendored
2
.github/workflows/build-tag.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
MATTERMOST_CHANNEL: bot-alerts-cicd
|
||||||
TEXT: |
|
TEXT: |
|
||||||
🐳 *[PENPOT] Docker image available.*
|
🐳 *[PENPOT] Docker image available: ${{ github.ref_name }}*
|
||||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
@infra
|
@infra
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/commit-checker.yml
vendored
2
.github/workflows/commit-checker.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Check Commit Type
|
- name: Check Commit Type
|
||||||
uses: gsactions/commit-message-checker@v2
|
uses: gsactions/commit-message-checker@v2
|
||||||
with:
|
with:
|
||||||
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert).+[^.])$'
|
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert|Reapply).+[^.])$'
|
||||||
flags: 'gm'
|
flags: 'gm'
|
||||||
error: 'Commit should match CONTRIBUTING.md guideline'
|
error: 'Commit should match CONTRIBUTING.md guideline'
|
||||||
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
|
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
|
||||||
|
|||||||
47
.github/workflows/tests.yml
vendored
47
.github/workflows/tests.yml
vendored
@@ -51,6 +51,51 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./scripts/test
|
./scripts/test
|
||||||
|
|
||||||
|
test-plugins:
|
||||||
|
name: Plugins Runtime Linter & Tests
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
id: setup-node
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
|
||||||
|
- name: Install deps
|
||||||
|
working-directory: ./plugins
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
corepack enable;
|
||||||
|
corepack install;
|
||||||
|
pnpm install;
|
||||||
|
|
||||||
|
- name: Run Lint
|
||||||
|
working-directory: ./plugins
|
||||||
|
run: pnpm run lint
|
||||||
|
|
||||||
|
- name: Run Format Check
|
||||||
|
working-directory: ./plugins
|
||||||
|
run: pnpm run format:check
|
||||||
|
|
||||||
|
- name: Run Test
|
||||||
|
working-directory: ./plugins
|
||||||
|
run: pnpm run test
|
||||||
|
|
||||||
|
- name: Build runtime
|
||||||
|
working-directory: ./plugins
|
||||||
|
run: pnpm run build
|
||||||
|
|
||||||
|
- name: Build plugins
|
||||||
|
working-directory: ./plugins
|
||||||
|
run: pnpm run build:plugins
|
||||||
|
|
||||||
|
- name: Build styles
|
||||||
|
working-directory: ./plugins
|
||||||
|
run: pnpm run build:styles-example
|
||||||
|
|
||||||
test-frontend:
|
test-frontend:
|
||||||
name: "Frontend Tests"
|
name: "Frontend Tests"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
@@ -67,6 +112,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Component Tests
|
- name: Component Tests
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
|
env:
|
||||||
|
VITEST_BROWSER_TIMEOUT: 120000
|
||||||
run: |
|
run: |
|
||||||
./scripts/test-components
|
./scripts/test-components
|
||||||
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@
|
|||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
.pnpm-store
|
||||||
*-init.clj
|
*-init.clj
|
||||||
*.css.json
|
*.css.json
|
||||||
*.jar
|
*.jar
|
||||||
|
|||||||
30
CHANGES.md
30
CHANGES.md
@@ -1,5 +1,18 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## 2.14.0 (Unreleased)
|
||||||
|
|
||||||
|
### :boom: Breaking changes & Deprecations
|
||||||
|
|
||||||
|
### :rocket: Epics and highlights
|
||||||
|
|
||||||
|
### :heart: Community contributions (Thank you!)
|
||||||
|
|
||||||
|
### :sparkles: New features & Enhancements
|
||||||
|
|
||||||
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
|
||||||
## 2.13.0 (Unreleased)
|
## 2.13.0 (Unreleased)
|
||||||
|
|
||||||
### :boom: Breaking changes & Deprecations
|
### :boom: Breaking changes & Deprecations
|
||||||
@@ -12,16 +25,29 @@
|
|||||||
|
|
||||||
### :sparkles: New features & Enhancements
|
### :sparkles: New features & Enhancements
|
||||||
|
|
||||||
|
- Add new Box Shadow Tokens [Taiga #10201](https://tree.taiga.io/project/penpot/us/10201)
|
||||||
- Make i18n translation files load on-demand [Taiga #11474](https://tree.taiga.io/project/penpot/us/11474)
|
- Make i18n translation files load on-demand [Taiga #11474](https://tree.taiga.io/project/penpot/us/11474)
|
||||||
|
- Add deleted files to dashboard [Taiga #8149](https://tree.taiga.io/project/penpot/us/8149)
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
|
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
|
||||||
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
|
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
|
||||||
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
|
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
|
||||||
|
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
|
||||||
|
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
|
||||||
|
|
||||||
|
|
||||||
## 2.12.0 (Unreleased)
|
## 2.12.1
|
||||||
|
|
||||||
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
- Fix setting a portion of text as bold or underline messes things up [Github #7980](https://github.com/penpot/penpot/issues/7980)
|
||||||
|
- Fix problem with style in fonts input [Taiga #12935](https://tree.taiga.io/project/penpot/issue/12935)
|
||||||
|
- Fix problem with path editor and right click [Github #7917](https://github.com/penpot/penpot/issues/7917)
|
||||||
|
|
||||||
|
|
||||||
|
## 2.12.0
|
||||||
|
|
||||||
### :boom: Breaking changes & Deprecations
|
### :boom: Breaking changes & Deprecations
|
||||||
|
|
||||||
@@ -83,6 +109,7 @@ example. It's still usable as before, we just removed the example.
|
|||||||
|
|
||||||
- Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
|
- Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
|
||||||
- Fix crash in token grid view due to tooltip validation (by @dfelinto) [Github #7887](https://github.com/penpot/penpot/pull/7887)
|
- Fix crash in token grid view due to tooltip validation (by @dfelinto) [Github #7887](https://github.com/penpot/penpot/pull/7887)
|
||||||
|
- Enable Hindi translations on the application
|
||||||
|
|
||||||
### :sparkles: New features & Enhancements
|
### :sparkles: New features & Enhancements
|
||||||
|
|
||||||
@@ -116,6 +143,7 @@ example. It's still usable as before, we just removed the example.
|
|||||||
- Fix switch variants with paths [Taiga #12841](https://tree.taiga.io/project/penpot/issue/12841)
|
- Fix switch variants with paths [Taiga #12841](https://tree.taiga.io/project/penpot/issue/12841)
|
||||||
- Fix referencing typography tokens on font-family tokens [Taiga #12492](https://tree.taiga.io/project/penpot/issue/12492)
|
- Fix referencing typography tokens on font-family tokens [Taiga #12492](https://tree.taiga.io/project/penpot/issue/12492)
|
||||||
- Fix horizontal scroll on layer panel [Taiga #12843](https://tree.taiga.io/project/penpot/issue/12843)
|
- Fix horizontal scroll on layer panel [Taiga #12843](https://tree.taiga.io/project/penpot/issue/12843)
|
||||||
|
- Fix unicode handling on email template abbreviation filter [Github #7966](https://github.com/penpot/penpot/pull/7966)
|
||||||
|
|
||||||
## 2.11.1
|
## 2.11.1
|
||||||
|
|
||||||
|
|||||||
@@ -240,4 +240,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -36,17 +36,6 @@
|
|||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
[yetti.response :as-alias yres]))
|
[yetti.response :as-alias yres]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; HELPERS
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn obfuscate-string
|
|
||||||
[s]
|
|
||||||
(if (< (count s) 10)
|
|
||||||
(apply str (take (count s) (repeat "*")))
|
|
||||||
(str (subs s 0 5)
|
|
||||||
(apply str (take (- (count s) 5) (repeat "*"))))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; OIDC PROVIDER (GENERIC)
|
;; OIDC PROVIDER (GENERIC)
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -177,7 +166,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@@ -222,7 +211,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@@ -299,7 +288,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@@ -341,7 +330,7 @@
|
|||||||
:provider "gitlab"
|
:provider "gitlab"
|
||||||
:base-uri (:base-uri provider)
|
:base-uri (:base-uri provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(ex/raise :type ::internal
|
(ex/raise :type ::internal
|
||||||
@@ -361,7 +350,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@@ -459,7 +448,7 @@
|
|||||||
(l/trc :hint "fetch access token"
|
(l/trc :hint "fetch access token"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider))
|
:client-secret (d/obfuscate-string (:client-secret provider))
|
||||||
:grant-type (:grant_type params)
|
:grant-type (:grant_type params)
|
||||||
:redirect-uri (:redirect_uri params))
|
:redirect-uri (:redirect_uri params))
|
||||||
|
|
||||||
@@ -512,7 +501,7 @@
|
|||||||
[cfg provider tdata]
|
[cfg provider tdata]
|
||||||
(l/trc :hint "fetch user info"
|
(l/trc :hint "fetch user info"
|
||||||
:uri (:user-uri provider)
|
:uri (:user-uri provider)
|
||||||
:token (obfuscate-string (:token/access tdata)))
|
:token (d/obfuscate-string (:token/access tdata)))
|
||||||
|
|
||||||
(let [params {:uri (:user-uri provider)
|
(let [params {:uri (:user-uri provider)
|
||||||
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}
|
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}
|
||||||
|
|||||||
@@ -331,6 +331,81 @@
|
|||||||
(set/difference cfeat/backend-only-features))
|
(set/difference cfeat/backend-only-features))
|
||||||
#{}))))
|
#{}))))
|
||||||
|
|
||||||
|
(defn check-file-exists
|
||||||
|
[cfg id & {:keys [include-deleted?]
|
||||||
|
:or {include-deleted? false}
|
||||||
|
:as options}]
|
||||||
|
(db/get-with-sql cfg [sql:get-minimal-file id]
|
||||||
|
{:db/remove-deleted (not include-deleted?)}))
|
||||||
|
|
||||||
|
(def ^:private sql:file-permissions
|
||||||
|
"select fpr.is_owner,
|
||||||
|
fpr.is_admin,
|
||||||
|
fpr.can_edit
|
||||||
|
from file_profile_rel as fpr
|
||||||
|
inner join file as f on (f.id = fpr.file_id)
|
||||||
|
where fpr.file_id = ?
|
||||||
|
and fpr.profile_id = ?
|
||||||
|
union all
|
||||||
|
select tpr.is_owner,
|
||||||
|
tpr.is_admin,
|
||||||
|
tpr.can_edit
|
||||||
|
from team_profile_rel as tpr
|
||||||
|
inner join project as p on (p.team_id = tpr.team_id)
|
||||||
|
inner join file as f on (p.id = f.project_id)
|
||||||
|
where f.id = ?
|
||||||
|
and tpr.profile_id = ?
|
||||||
|
union all
|
||||||
|
select ppr.is_owner,
|
||||||
|
ppr.is_admin,
|
||||||
|
ppr.can_edit
|
||||||
|
from project_profile_rel as ppr
|
||||||
|
inner join file as f on (f.project_id = ppr.project_id)
|
||||||
|
where f.id = ?
|
||||||
|
and ppr.profile_id = ?")
|
||||||
|
|
||||||
|
(defn- get-file-permissions*
|
||||||
|
[conn profile-id file-id]
|
||||||
|
(when (and profile-id file-id)
|
||||||
|
(db/exec! conn [sql:file-permissions
|
||||||
|
file-id profile-id
|
||||||
|
file-id profile-id
|
||||||
|
file-id profile-id])))
|
||||||
|
|
||||||
|
(defn get-file-permissions
|
||||||
|
([conn profile-id file-id]
|
||||||
|
(let [rows (get-file-permissions* conn profile-id file-id)
|
||||||
|
is-owner (boolean (some :is-owner rows))
|
||||||
|
is-admin (boolean (some :is-admin rows))
|
||||||
|
can-edit (boolean (some :can-edit rows))]
|
||||||
|
(when (seq rows)
|
||||||
|
{:type :membership
|
||||||
|
:is-owner is-owner
|
||||||
|
:is-admin (or is-owner is-admin)
|
||||||
|
:can-edit (or is-owner is-admin can-edit)
|
||||||
|
:can-read true
|
||||||
|
:is-logged (some? profile-id)})))
|
||||||
|
|
||||||
|
([conn profile-id file-id share-id]
|
||||||
|
(let [perms (get-file-permissions conn profile-id file-id)
|
||||||
|
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
|
||||||
|
(dissoc :flags)
|
||||||
|
(update :pages db/decode-pgarray #{}))]
|
||||||
|
|
||||||
|
;; NOTE: in a future when share-link becomes more powerful and
|
||||||
|
;; will allow us specify which parts of the app is available, we
|
||||||
|
;; will probably need to tweak this function in order to expose
|
||||||
|
;; this flags to the frontend.
|
||||||
|
(cond
|
||||||
|
(some? perms) perms
|
||||||
|
(some? ldata) {:type :share-link
|
||||||
|
:can-read true
|
||||||
|
:pages (:pages ldata)
|
||||||
|
:is-logged (some? profile-id)
|
||||||
|
:who-comment (:who-comment ldata)
|
||||||
|
:who-inspect (:who-inspect ldata)}))))
|
||||||
|
|
||||||
|
|
||||||
(defn get-project
|
(defn get-project
|
||||||
[cfg project-id]
|
[cfg project-id]
|
||||||
(db/get cfg :project {:id project-id}))
|
(db/get cfg :project {:id project-id}))
|
||||||
|
|||||||
@@ -821,9 +821,10 @@
|
|||||||
entries (keep (match-storage-entry-fn) entries)]
|
entries (keep (match-storage-entry-fn) entries)]
|
||||||
|
|
||||||
(doseq [{:keys [id entry]} entries]
|
(doseq [{:keys [id entry]} entries]
|
||||||
(let [object (->> (read-entry input entry)
|
(let [object (-> (read-entry input entry)
|
||||||
(decode-storage-object)
|
(decode-storage-object)
|
||||||
(validate-storage-object))
|
(update :bucket d/nilv sto/default-bucket)
|
||||||
|
(validate-storage-object))
|
||||||
|
|
||||||
ext (cmedia/mtype->extension (:content-type object))
|
ext (cmedia/mtype->extension (:content-type object))
|
||||||
path (str "objects/" id ext)
|
path (str "objects/" id ext)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
(defn- get-file-media-object
|
(defn- get-file-media-object
|
||||||
[pool id]
|
[pool id]
|
||||||
(db/get pool :file-media-object {:id id}))
|
(db/get pool :file-media-object {:id id} {::db/remove-deleted false}))
|
||||||
|
|
||||||
(defn- serve-object-from-s3
|
(defn- serve-object-from-s3
|
||||||
[{:keys [::sto/storage] :as cfg} obj]
|
[{:keys [::sto/storage] :as cfg} obj]
|
||||||
|
|||||||
@@ -309,7 +309,7 @@
|
|||||||
(fn [request]
|
(fn [request]
|
||||||
(let [key (yreq/get-header request "x-shared-key")]
|
(let [key (yreq/get-header request "x-shared-key")]
|
||||||
(if (= key shared-key)
|
(if (= key shared-key)
|
||||||
(handler request)
|
(handler (assoc request ::http/auth-with-shared-key true))
|
||||||
{::yres/status 403}))))
|
{::yres/status 403}))))
|
||||||
(fn [_ _]
|
(fn [_ _]
|
||||||
{::yres/status 403})))
|
{::yres/status 403})))
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.common.uri :as u]
|
[app.common.uri :as u]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http :as-alias http]
|
[app.http :as-alias http]
|
||||||
@@ -92,7 +93,11 @@
|
|||||||
(let [handler-name (:type path-params)
|
(let [handler-name (:type path-params)
|
||||||
etag (yreq/get-header request "if-none-match")
|
etag (yreq/get-header request "if-none-match")
|
||||||
profile-id (or (::session/profile-id request)
|
profile-id (or (::session/profile-id request)
|
||||||
(::actoken/profile-id request))
|
(::actoken/profile-id request)
|
||||||
|
(if (::http/auth-with-shared-key request)
|
||||||
|
uuid/zero
|
||||||
|
nil))
|
||||||
|
|
||||||
ip-addr (inet/parse-request request)
|
ip-addr (inet/parse-request request)
|
||||||
|
|
||||||
data (-> params
|
data (-> params
|
||||||
|
|||||||
@@ -307,7 +307,8 @@
|
|||||||
:content-type (:mtype input)})]
|
:content-type (:mtype input)})]
|
||||||
(:id sobject))
|
(:id sobject))
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(l/err :hint "unable to import profile picture"
|
(l/wrn :hint "unable to import profile picture"
|
||||||
|
:uri uri
|
||||||
:cause cause)
|
:cause cause)
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
|
|||||||
@@ -79,85 +79,14 @@
|
|||||||
|
|
||||||
;; --- FILE PERMISSIONS
|
;; --- FILE PERMISSIONS
|
||||||
|
|
||||||
|
|
||||||
(def ^:private sql:file-permissions
|
|
||||||
"select fpr.is_owner,
|
|
||||||
fpr.is_admin,
|
|
||||||
fpr.can_edit
|
|
||||||
from file_profile_rel as fpr
|
|
||||||
inner join file as f on (f.id = fpr.file_id)
|
|
||||||
where fpr.file_id = ?
|
|
||||||
and fpr.profile_id = ?
|
|
||||||
and f.deleted_at is null
|
|
||||||
union all
|
|
||||||
select tpr.is_owner,
|
|
||||||
tpr.is_admin,
|
|
||||||
tpr.can_edit
|
|
||||||
from team_profile_rel as tpr
|
|
||||||
inner join project as p on (p.team_id = tpr.team_id)
|
|
||||||
inner join file as f on (p.id = f.project_id)
|
|
||||||
where f.id = ?
|
|
||||||
and tpr.profile_id = ?
|
|
||||||
and f.deleted_at is null
|
|
||||||
union all
|
|
||||||
select ppr.is_owner,
|
|
||||||
ppr.is_admin,
|
|
||||||
ppr.can_edit
|
|
||||||
from project_profile_rel as ppr
|
|
||||||
inner join file as f on (f.project_id = ppr.project_id)
|
|
||||||
where f.id = ?
|
|
||||||
and ppr.profile_id = ?
|
|
||||||
and f.deleted_at is null")
|
|
||||||
|
|
||||||
(defn get-file-permissions
|
|
||||||
[conn profile-id file-id]
|
|
||||||
(when (and profile-id file-id)
|
|
||||||
(db/exec! conn [sql:file-permissions
|
|
||||||
file-id profile-id
|
|
||||||
file-id profile-id
|
|
||||||
file-id profile-id])))
|
|
||||||
|
|
||||||
(defn get-permissions
|
|
||||||
([conn profile-id file-id]
|
|
||||||
(let [rows (get-file-permissions conn profile-id file-id)
|
|
||||||
is-owner (boolean (some :is-owner rows))
|
|
||||||
is-admin (boolean (some :is-admin rows))
|
|
||||||
can-edit (boolean (some :can-edit rows))]
|
|
||||||
(when (seq rows)
|
|
||||||
{:type :membership
|
|
||||||
:is-owner is-owner
|
|
||||||
:is-admin (or is-owner is-admin)
|
|
||||||
:can-edit (or is-owner is-admin can-edit)
|
|
||||||
:can-read true
|
|
||||||
:is-logged (some? profile-id)})))
|
|
||||||
|
|
||||||
([conn profile-id file-id share-id]
|
|
||||||
(let [perms (get-permissions conn profile-id file-id)
|
|
||||||
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
|
|
||||||
(dissoc :flags)
|
|
||||||
(update :pages db/decode-pgarray #{}))]
|
|
||||||
|
|
||||||
;; NOTE: in a future when share-link becomes more powerful and
|
|
||||||
;; will allow us specify which parts of the app is available, we
|
|
||||||
;; will probably need to tweak this function in order to expose
|
|
||||||
;; this flags to the frontend.
|
|
||||||
(cond
|
|
||||||
(some? perms) perms
|
|
||||||
(some? ldata) {:type :share-link
|
|
||||||
:can-read true
|
|
||||||
:pages (:pages ldata)
|
|
||||||
:is-logged (some? profile-id)
|
|
||||||
:who-comment (:who-comment ldata)
|
|
||||||
:who-inspect (:who-inspect ldata)}))))
|
|
||||||
|
|
||||||
(def has-edit-permissions?
|
(def has-edit-permissions?
|
||||||
(perms/make-edition-predicate-fn get-permissions))
|
(perms/make-edition-predicate-fn bfc/get-file-permissions))
|
||||||
|
|
||||||
(def has-read-permissions?
|
(def has-read-permissions?
|
||||||
(perms/make-read-predicate-fn get-permissions))
|
(perms/make-read-predicate-fn bfc/get-file-permissions))
|
||||||
|
|
||||||
(def has-comment-permissions?
|
(def has-comment-permissions?
|
||||||
(perms/make-comment-predicate-fn get-permissions))
|
(perms/make-comment-predicate-fn bfc/get-file-permissions))
|
||||||
|
|
||||||
(def check-edition-permissions!
|
(def check-edition-permissions!
|
||||||
(perms/make-check-fn has-edit-permissions?))
|
(perms/make-check-fn has-edit-permissions?))
|
||||||
@@ -170,7 +99,7 @@
|
|||||||
|
|
||||||
(defn check-comment-permissions!
|
(defn check-comment-permissions!
|
||||||
[conn profile-id file-id share-id]
|
[conn profile-id file-id share-id]
|
||||||
(let [perms (get-permissions conn profile-id file-id share-id)
|
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
|
||||||
can-read (has-read-permissions? perms)
|
can-read (has-read-permissions? perms)
|
||||||
can-comment (has-comment-permissions? perms)]
|
can-comment (has-comment-permissions? perms)]
|
||||||
(when-not (or can-read can-comment)
|
(when-not (or can-read can-comment)
|
||||||
@@ -222,7 +151,7 @@
|
|||||||
(defn- get-minimal-file-with-perms
|
(defn- get-minimal-file-with-perms
|
||||||
[cfg {:keys [:id ::rpc/profile-id]}]
|
[cfg {:keys [:id ::rpc/profile-id]}]
|
||||||
(let [mfile (get-minimal-file cfg id)
|
(let [mfile (get-minimal-file cfg id)
|
||||||
perms (get-permissions cfg profile-id id)]
|
perms (bfc/get-file-permissions cfg profile-id id)]
|
||||||
(assoc mfile :permissions perms)))
|
(assoc mfile :permissions perms)))
|
||||||
|
|
||||||
(defn get-file-etag
|
(defn get-file-etag
|
||||||
@@ -248,7 +177,7 @@
|
|||||||
;; will be already prefetched and we just reuse them instead
|
;; will be already prefetched and we just reuse them instead
|
||||||
;; of making an additional database queries.
|
;; of making an additional database queries.
|
||||||
(let [perms (or (:permissions (::cond/object params))
|
(let [perms (or (:permissions (::cond/object params))
|
||||||
(get-permissions conn profile-id id))]
|
(bfc/get-file-permissions conn profile-id id))]
|
||||||
(check-read-permissions! perms)
|
(check-read-permissions! perms)
|
||||||
|
|
||||||
(let [team (teams/get-team conn
|
(let [team (teams/get-team conn
|
||||||
@@ -311,7 +240,7 @@
|
|||||||
::sm/result schema:file-fragment}
|
::sm/result schema:file-fragment}
|
||||||
[cfg {:keys [::rpc/profile-id file-id fragment-id share-id]}]
|
[cfg {:keys [::rpc/profile-id file-id fragment-id share-id]}]
|
||||||
(db/run! cfg (fn [cfg]
|
(db/run! cfg (fn [cfg]
|
||||||
(let [perms (get-permissions cfg profile-id file-id share-id)]
|
(let [perms (bfc/get-file-permissions cfg profile-id file-id share-id)]
|
||||||
(check-read-permissions! perms)
|
(check-read-permissions! perms)
|
||||||
(-> (get-file-fragment cfg file-id fragment-id)
|
(-> (get-file-fragment cfg file-id fragment-id)
|
||||||
(rph/with-http-cache long-cache-duration))))))
|
(rph/with-http-cache long-cache-duration))))))
|
||||||
@@ -456,8 +385,7 @@
|
|||||||
:code :params-validation
|
:code :params-validation
|
||||||
:hint "page-id is required when object-id is provided"))
|
:hint "page-id is required when object-id is provided"))
|
||||||
|
|
||||||
(let [perms (get-permissions conn profile-id file-id share-id)
|
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
|
||||||
|
|
||||||
file (bfc/get-file cfg file-id :read-only? true)
|
file (bfc/get-file cfg file-id :read-only? true)
|
||||||
|
|
||||||
proj (db/get conn :project {:id (:project-id file)})
|
proj (db/get conn :project {:id (:project-id file)})
|
||||||
@@ -688,11 +616,10 @@
|
|||||||
"Get libraries used by the specified file."
|
"Get libraries used by the specified file."
|
||||||
{::doc/added "1.17"
|
{::doc/added "1.17"
|
||||||
::sm/params schema:get-file-libraries}
|
::sm/params schema:get-file-libraries}
|
||||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id]}]
|
[cfg {:keys [::rpc/profile-id file-id]}]
|
||||||
(dm/with-open [conn (db/open pool)]
|
(bfc/check-file-exists cfg file-id)
|
||||||
(check-read-permissions! conn profile-id file-id)
|
(check-read-permissions! cfg profile-id file-id)
|
||||||
(bfc/get-file-libraries conn file-id)))
|
(bfc/get-file-libraries cfg file-id))
|
||||||
|
|
||||||
|
|
||||||
;; --- COMMAND QUERY: Files that use this File library
|
;; --- COMMAND QUERY: Files that use this File library
|
||||||
|
|
||||||
@@ -777,7 +704,6 @@
|
|||||||
f.created_at,
|
f.created_at,
|
||||||
f.modified_at,
|
f.modified_at,
|
||||||
f.name,
|
f.name,
|
||||||
f.is_shared,
|
|
||||||
f.deleted_at AS will_be_deleted_at,
|
f.deleted_at AS will_be_deleted_at,
|
||||||
ft.media_id AS thumbnail_id,
|
ft.media_id AS thumbnail_id,
|
||||||
row_number() OVER w AS row_num,
|
row_number() OVER w AS row_num,
|
||||||
@@ -785,8 +711,7 @@
|
|||||||
FROM file AS f
|
FROM file AS f
|
||||||
INNER JOIN project AS p ON (p.id = f.project_id)
|
INNER JOIN project AS p ON (p.id = f.project_id)
|
||||||
LEFT JOIN file_thumbnail AS ft on (ft.file_id = f.id
|
LEFT JOIN file_thumbnail AS ft on (ft.file_id = f.id
|
||||||
AND ft.revn = f.revn
|
AND ft.revn = f.revn)
|
||||||
AND ft.deleted_at is null)
|
|
||||||
WHERE p.team_id = ?
|
WHERE p.team_id = ?
|
||||||
AND (p.deleted_at > ?::timestamptz OR
|
AND (p.deleted_at > ?::timestamptz OR
|
||||||
f.deleted_at > ?::timestamptz)
|
f.deleted_at > ?::timestamptz)
|
||||||
@@ -888,7 +813,7 @@
|
|||||||
AND (f.deleted_at IS NULL OR f.deleted_at > now())
|
AND (f.deleted_at IS NULL OR f.deleted_at > now())
|
||||||
ORDER BY f.created_at ASC;")
|
ORDER BY f.created_at ASC;")
|
||||||
|
|
||||||
(defn- absorb-library-by-file!
|
(defn- absorb-library-by-file
|
||||||
[cfg ldata file-id]
|
[cfg ldata file-id]
|
||||||
|
|
||||||
(assert (db/connection-map? cfg)
|
(assert (db/connection-map? cfg)
|
||||||
@@ -912,7 +837,7 @@
|
|||||||
:modified-at (ct/now)
|
:modified-at (ct/now)
|
||||||
:has-media-trimmed false}))))
|
:has-media-trimmed false}))))
|
||||||
|
|
||||||
(defn- absorb-library
|
(defn- absorb-library*
|
||||||
"Find all files using a shared library, and absorb all library assets
|
"Find all files using a shared library, and absorb all library assets
|
||||||
into the file local libraries"
|
into the file local libraries"
|
||||||
[cfg {:keys [id data] :as library}]
|
[cfg {:keys [id data] :as library}]
|
||||||
@@ -927,10 +852,10 @@
|
|||||||
:library-id (str id)
|
:library-id (str id)
|
||||||
:files (str/join "," (map str ids)))
|
:files (str/join "," (map str ids)))
|
||||||
|
|
||||||
(run! (partial absorb-library-by-file! cfg data) ids)
|
(run! (partial absorb-library-by-file cfg data) ids)
|
||||||
library))
|
library))
|
||||||
|
|
||||||
(defn absorb-library!
|
(defn absorb-library
|
||||||
[{:keys [::db/conn] :as cfg} id]
|
[{:keys [::db/conn] :as cfg} id]
|
||||||
(let [file (-> (bfc/get-file cfg id
|
(let [file (-> (bfc/get-file cfg id
|
||||||
:realize? true
|
:realize? true
|
||||||
@@ -947,7 +872,7 @@
|
|||||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||||
(cfeat/check-file-features! (:features file)))
|
(cfeat/check-file-features! (:features file)))
|
||||||
|
|
||||||
(absorb-library cfg file)))
|
(absorb-library* cfg file)))
|
||||||
|
|
||||||
(defn- set-file-shared
|
(defn- set-file-shared
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}]
|
[{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}]
|
||||||
@@ -960,14 +885,14 @@
|
|||||||
;; file, we need to perform more complex operation,
|
;; file, we need to perform more complex operation,
|
||||||
;; so in this case we retrieve the complete file and
|
;; so in this case we retrieve the complete file and
|
||||||
;; perform all required validations.
|
;; perform all required validations.
|
||||||
(let [file (-> (absorb-library! cfg id)
|
(let [file (-> (absorb-library cfg id)
|
||||||
(assoc :is-shared false))]
|
(assoc :is-shared false))]
|
||||||
(db/delete! conn :file-library-rel {:library-file-id id})
|
(db/delete! conn :file-library-rel {:library-file-id id})
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:is-shared false
|
{:is-shared false
|
||||||
:modified-at (ct/now)}
|
:modified-at (ct/now)}
|
||||||
{:id id})
|
{:id id})
|
||||||
(select-keys file [:id :name :is-shared]))
|
file)
|
||||||
|
|
||||||
(and (false? (:is-shared file))
|
(and (false? (:is-shared file))
|
||||||
(true? (:is-shared params)))
|
(true? (:is-shared params)))
|
||||||
@@ -1014,6 +939,11 @@
|
|||||||
{:id file-id}
|
{:id file-id}
|
||||||
{::db/return-keys [:id :name :is-shared :deleted-at
|
{::db/return-keys [:id :name :is-shared :deleted-at
|
||||||
:project-id :created-at :modified-at]})]
|
:project-id :created-at :modified-at]})]
|
||||||
|
|
||||||
|
;; Remove all possible relations for that file
|
||||||
|
(db/delete! conn :file-library-rel
|
||||||
|
{:library-file-id file-id})
|
||||||
|
|
||||||
(wrk/submit! {::db/conn conn
|
(wrk/submit! {::db/conn conn
|
||||||
::wrk/task :delete-object
|
::wrk/task :delete-object
|
||||||
::wrk/params {:object :file
|
::wrk/params {:object :file
|
||||||
@@ -1164,47 +1094,53 @@
|
|||||||
|
|
||||||
;; --- MUTATION COMMAND: delete-files-immediatelly
|
;; --- MUTATION COMMAND: delete-files-immediatelly
|
||||||
|
|
||||||
(def ^:private sql:delete-team-files
|
(def ^:private sql:get-delete-team-files-candidates
|
||||||
"UPDATE file AS uf SET deleted_at = ?::timestamptz
|
"SELECT f.id
|
||||||
FROM (
|
FROM file AS f
|
||||||
SELECT f.id
|
JOIN project AS p ON (p.id = f.project_id)
|
||||||
FROM file AS f
|
JOIN team AS t ON (t.id = p.team_id)
|
||||||
JOIN project AS p ON (p.id = f.project_id)
|
WHERE t.deleted_at IS NULL
|
||||||
JOIN team AS t ON (t.id = p.team_id)
|
AND t.id = ?
|
||||||
WHERE t.deleted_at IS NULL
|
AND f.id = ANY(?::uuid[])")
|
||||||
AND t.id = ?
|
|
||||||
AND f.id = ANY(?::uuid[])
|
|
||||||
) AS subquery
|
|
||||||
WHERE uf.id = subquery.id
|
|
||||||
RETURNING uf.id, uf.deleted_at;")
|
|
||||||
|
|
||||||
(def ^:private schema:permanently-delete-team-files
|
(def ^:private schema:permanently-delete-team-files
|
||||||
[:map {:title "permanently-delete-team-files"}
|
[:map {:title "permanently-delete-team-files"}
|
||||||
[:team-id ::sm/uuid]
|
[:team-id ::sm/uuid]
|
||||||
[:ids [::sm/set ::sm/uuid]]])
|
[:ids [::sm/set ::sm/uuid]]])
|
||||||
|
|
||||||
|
(defn- permanently-delete-team-files
|
||||||
|
[{:keys [::db/conn]} {:keys [::rpc/request-at team-id ids]}]
|
||||||
|
(let [ids (into #{}
|
||||||
|
d/xf:map-id
|
||||||
|
(db/exec! conn [sql:get-delete-team-files-candidates team-id
|
||||||
|
(db/create-array conn "uuid" ids)]))]
|
||||||
|
|
||||||
|
(reduce (fn [acc id]
|
||||||
|
(events/tap :progress {:file-id id :index (inc (count acc)) :total (count ids)})
|
||||||
|
(db/update! conn :file
|
||||||
|
{:deleted-at request-at}
|
||||||
|
{:id id}
|
||||||
|
{::db/return-keys false})
|
||||||
|
(wrk/submit! {::db/conn conn
|
||||||
|
::wrk/task :delete-object
|
||||||
|
::wrk/params {:object :file
|
||||||
|
:deleted-at request-at
|
||||||
|
:id id}})
|
||||||
|
(conj acc id))
|
||||||
|
#{}
|
||||||
|
ids)))
|
||||||
|
|
||||||
(sv/defmethod ::permanently-delete-team-files
|
(sv/defmethod ::permanently-delete-team-files
|
||||||
"Mark the specified files to be deleted immediatelly on the
|
"Mark the specified files to be deleted immediatelly on the
|
||||||
specified team. The team-id on params will be used to filter and
|
specified team. The team-id on params will be used to filter and
|
||||||
check writable permissons on team."
|
check writable permissons on team."
|
||||||
|
|
||||||
{::doc/added "2.12"
|
{::doc/added "2.13"
|
||||||
::sm/params schema:permanently-delete-team-files
|
::sm/params schema:permanently-delete-team-files}
|
||||||
::db/transaction true}
|
|
||||||
|
|
||||||
[{:keys [::db/conn]} {:keys [::rpc/profile-id ::rpc/request-at team-id ids]}]
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id] :as params}]
|
||||||
(teams/check-edition-permissions! conn profile-id team-id)
|
(teams/check-edition-permissions! pool profile-id team-id)
|
||||||
|
(sse/response #(db/tx-run! cfg permanently-delete-team-files params)))
|
||||||
(reduce (fn [acc {:keys [id deleted-at]}]
|
|
||||||
(wrk/submit! {::db/conn conn
|
|
||||||
::wrk/task :delete-object
|
|
||||||
::wrk/params {:object :file
|
|
||||||
:deleted-at deleted-at
|
|
||||||
:id id}})
|
|
||||||
(conj acc id))
|
|
||||||
#{}
|
|
||||||
(db/plan conn [sql:delete-team-files request-at team-id
|
|
||||||
(db/create-array conn "uuid" ids)])))
|
|
||||||
|
|
||||||
;; --- MUTATION COMMAND: restore-files-immediatelly
|
;; --- MUTATION COMMAND: restore-files-immediatelly
|
||||||
|
|
||||||
@@ -1268,7 +1204,7 @@
|
|||||||
{:keys [files projects]}
|
{:keys [files projects]}
|
||||||
(reduce (fn [result {:keys [id project-id]}]
|
(reduce (fn [result {:keys [id project-id]}]
|
||||||
(let [index (-> result :files count)]
|
(let [index (-> result :files count)]
|
||||||
(events/tap :progress {:file-id id :index index :total total-files})
|
(events/tap :progress {:file-id id :index (inc index) :total total-files})
|
||||||
(restore-file conn id)
|
(restore-file conn id)
|
||||||
|
|
||||||
(-> result
|
(-> result
|
||||||
@@ -1291,7 +1227,7 @@
|
|||||||
(sv/defmethod ::restore-deleted-team-files
|
(sv/defmethod ::restore-deleted-team-files
|
||||||
"Removes the deletion mark from the specified files (and respective
|
"Removes the deletion mark from the specified files (and respective
|
||||||
projects) on the specified team."
|
projects) on the specified team."
|
||||||
{::doc/added "2.12"
|
{::doc/added "2.13"
|
||||||
::sse/stream? true
|
::sse/stream? true
|
||||||
::sm/params schema:restore-deleted-team-files}
|
::sm/params schema:restore-deleted-team-files}
|
||||||
[cfg params]
|
[cfg params]
|
||||||
|
|||||||
@@ -199,15 +199,13 @@
|
|||||||
[cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}]
|
[cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}]
|
||||||
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
||||||
(files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
|
|
||||||
(let [team (teams/get-team conn
|
(let [team (teams/get-team conn
|
||||||
:profile-id profile-id
|
:profile-id profile-id
|
||||||
:file-id file-id)
|
:file-id file-id)
|
||||||
|
|
||||||
file (bfc/get-file cfg file-id
|
file (bfc/get-file cfg file-id
|
||||||
|
:include-deleted? true
|
||||||
:realize? true
|
:realize? true
|
||||||
:read-only? true)
|
:read-only? true)
|
||||||
|
|
||||||
strip-frames-with-thumbnails
|
strip-frames-with-thumbnails
|
||||||
(or (nil? strip-frames-with-thumbnails) ;; if not present, default to true
|
(or (nil? strip-frames-with-thumbnails) ;; if not present, default to true
|
||||||
(true? strip-frames-with-thumbnails))]
|
(true? strip-frames-with-thumbnails))]
|
||||||
@@ -333,12 +331,16 @@
|
|||||||
|
|
||||||
;; --- MUTATION COMMAND: create-file-thumbnail
|
;; --- MUTATION COMMAND: create-file-thumbnail
|
||||||
|
|
||||||
(defn- create-file-thumbnail!
|
(defn- create-file-thumbnail
|
||||||
[{:keys [::db/conn ::sto/storage]} {:keys [file-id revn props media] :as params}]
|
[{:keys [::db/conn ::sto/storage] :as cfg} {:keys [file-id revn props media] :as params}]
|
||||||
(media/validate-media-type! media)
|
(media/validate-media-type! media)
|
||||||
(media/validate-media-size! media)
|
(media/validate-media-size! media)
|
||||||
|
|
||||||
(let [props (db/tjson (or props {}))
|
(let [file (bfc/get-file cfg file-id
|
||||||
|
:include-deleted? true
|
||||||
|
:load-data? false)
|
||||||
|
|
||||||
|
props (db/tjson (or props {}))
|
||||||
path (:path media)
|
path (:path media)
|
||||||
mtype (:mtype media)
|
mtype (:mtype media)
|
||||||
hash (sto/calculate-hash path)
|
hash (sto/calculate-hash path)
|
||||||
@@ -367,7 +369,7 @@
|
|||||||
|
|
||||||
(db/update! conn :file-thumbnail
|
(db/update! conn :file-thumbnail
|
||||||
{:media-id (:id media)
|
{:media-id (:id media)
|
||||||
:deleted-at nil
|
:deleted-at (:deleted-at file)
|
||||||
:updated-at tnow
|
:updated-at tnow
|
||||||
:props props}
|
:props props}
|
||||||
{:file-id file-id
|
{:file-id file-id
|
||||||
@@ -378,6 +380,7 @@
|
|||||||
:revn revn
|
:revn revn
|
||||||
:created-at tnow
|
:created-at tnow
|
||||||
:updated-at tnow
|
:updated-at tnow
|
||||||
|
:deleted-at (:deleted-at file)
|
||||||
:props props
|
:props props
|
||||||
:media-id (:id media)}))
|
:media-id (:id media)}))
|
||||||
|
|
||||||
@@ -402,6 +405,8 @@
|
|||||||
::rtry/when rtry/conflict-exception?
|
::rtry/when rtry/conflict-exception?
|
||||||
::sm/params schema:create-file-thumbnail}
|
::sm/params schema:create-file-thumbnail}
|
||||||
|
|
||||||
|
;; FIXME: do not run the thumbnail upload inside a transaction
|
||||||
|
|
||||||
[cfg {:keys [::rpc/profile-id file-id] :as params}]
|
[cfg {:keys [::rpc/profile-id file-id] :as params}]
|
||||||
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
||||||
;; TODO For now we check read permissions instead of write,
|
;; TODO For now we check read permissions instead of write,
|
||||||
@@ -409,6 +414,6 @@
|
|||||||
;; review this approach on the future.
|
;; review this approach on the future.
|
||||||
(files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
(when-not (db/read-only? conn)
|
(when-not (db/read-only? conn)
|
||||||
(let [media (create-file-thumbnail! cfg params)]
|
(let [media (create-file-thumbnail cfg params)]
|
||||||
{:uri (files/resolve-public-uri (:id media))
|
{:uri (files/resolve-public-uri (:id media))
|
||||||
:id (:id media)})))))
|
:id (:id media)})))))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
(ns app.rpc.commands.fonts
|
(ns app.rpc.commands.fonts
|
||||||
(:require
|
(:require
|
||||||
|
[app.binfile.common :as bfc]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
(uuid? file-id)
|
(uuid? file-id)
|
||||||
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
|
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
|
||||||
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})
|
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})
|
||||||
perms (files/get-permissions conn profile-id file-id share-id)]
|
perms (bfc/get-file-permissions conn profile-id file-id share-id)]
|
||||||
(files/check-read-permissions! perms)
|
(files/check-read-permissions! perms)
|
||||||
(db/query conn :team-font-variant
|
(db/query conn :team-font-variant
|
||||||
{:team-id (:team-id project)
|
{:team-id (:team-id project)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.commands.files :as files]
|
|
||||||
[app.rpc.commands.teams :as teams]
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.cond :as-alias cond]
|
[app.rpc.cond :as-alias cond]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
@@ -121,7 +120,7 @@
|
|||||||
[system {:keys [::rpc/profile-id file-id share-id] :as params}]
|
[system {:keys [::rpc/profile-id file-id share-id] :as params}]
|
||||||
(db/run! system
|
(db/run! system
|
||||||
(fn [{:keys [::db/conn] :as system}]
|
(fn [{:keys [::db/conn] :as system}]
|
||||||
(let [perms (files/get-permissions conn profile-id file-id share-id)
|
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
|
||||||
params (-> params
|
params (-> params
|
||||||
(assoc ::perms perms)
|
(assoc ::perms perms)
|
||||||
(assoc :profile-id profile-id))]
|
(assoc :profile-id profile-id))]
|
||||||
|
|||||||
@@ -104,28 +104,29 @@
|
|||||||
(def ^:private schema:limit
|
(def ^:private schema:limit
|
||||||
[:and
|
[:and
|
||||||
[:map
|
[:map
|
||||||
[::name :any]
|
[::name :keyword]
|
||||||
[::strategy schema:strategy]
|
[::strategy schema:strategy]
|
||||||
[::key :string]
|
[::key :string]
|
||||||
[::opts :string]]
|
[::opts :string]
|
||||||
[:or
|
[::capacity {:optional true} ::sm/int]
|
||||||
[:map
|
[::rate {:optional true} ::sm/int]
|
||||||
[::capacity ::sm/int]
|
[::interval {:optional true} ::ct/duration]
|
||||||
[::rate ::sm/int]
|
[::params {:optional true} [::sm/vec :any]]
|
||||||
[::internal ::ct/duration]
|
[::permits {:optional true} ::sm/int]
|
||||||
[::params [::sm/vec :any]]]
|
[::unit {:optional true} [:enum :days :hours :minutes :seconds :weeks]]]
|
||||||
[:map
|
[:fn (fn [attrs]
|
||||||
[::nreq ::sm/int]
|
(let [contains-fn (partial contains? attrs)]
|
||||||
[::unit [:enum :days :hours :minutes :seconds :weeks]]]]])
|
(or (every? contains-fn [::capacity ::rate ::interval])
|
||||||
|
(every? contains-fn [::permits ::unit]))))]])
|
||||||
|
|
||||||
(def ^:private schema:limits
|
(def ^:private schema:limits
|
||||||
[:map-of :keyword [::sm/vec schema:limit]])
|
[:map-of :keyword [::sm/vec schema:limit]])
|
||||||
|
|
||||||
(def ^:private valid-limit-tuple?
|
(def ^:private valid-limit-tuple?
|
||||||
(sm/lazy-validator schema:limit-tuple))
|
(sm/validator schema:limit-tuple))
|
||||||
|
|
||||||
(def ^:private valid-rlimit-instance?
|
(def ^:private valid-rlimit-instance?
|
||||||
(sm/lazy-validator ::rpc/rlimit))
|
(sm/validator ::rpc/rlimit))
|
||||||
|
|
||||||
(defmethod parse-limit :window
|
(defmethod parse-limit :window
|
||||||
[[name strategy opts :as vlimit]]
|
[[name strategy opts :as vlimit]]
|
||||||
@@ -134,16 +135,16 @@
|
|||||||
(merge
|
(merge
|
||||||
{::name name
|
{::name name
|
||||||
::strategy strategy}
|
::strategy strategy}
|
||||||
(if-let [[_ nreq unit] (re-find window-opts-re opts)]
|
(if-let [[_ permits unit] (re-find window-opts-re opts)]
|
||||||
(let [nreq (parse-long nreq)]
|
(let [permits (parse-long permits)]
|
||||||
{::nreq nreq
|
{::permits permits
|
||||||
::unit (case unit
|
::unit (case unit
|
||||||
"d" :days
|
"d" :days
|
||||||
"h" :hours
|
"h" :hours
|
||||||
"m" :minutes
|
"m" :minutes
|
||||||
"s" :seconds
|
"s" :seconds
|
||||||
"w" :weeks)
|
"w" :weeks)
|
||||||
::key (str "ratelimit.window." (d/name name))
|
::key (str "penpot.rlimit." (cf/get :tenant) ".window." (d/name name))
|
||||||
::opts opts})
|
::opts opts})
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :invalid-window-limit-opts
|
:code :invalid-window-limit-opts
|
||||||
@@ -164,15 +165,15 @@
|
|||||||
::interval interval
|
::interval interval
|
||||||
::opts opts
|
::opts opts
|
||||||
::params [(->seconds interval) rate capacity]
|
::params [(->seconds interval) rate capacity]
|
||||||
::key (str "ratelimit.bucket." (d/name name))})
|
::key (str "penpot.rlimit." (cf/get :tenant) ".bucket." (d/name name))})
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :invalid-bucket-limit-opts
|
:code :invalid-bucket-limit-opts
|
||||||
:hint (str/ffmt "looks like '%' does not have a valid format" opts))))
|
:hint (str/ffmt "looks like '%' does not have a valid format" opts))))
|
||||||
|
|
||||||
(defmethod process-limit :bucket
|
(defmethod process-limit :bucket
|
||||||
[rconn user-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
|
[rconn profile-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
|
||||||
(let [script (-> bucket-rate-limit-script
|
(let [script (-> bucket-rate-limit-script
|
||||||
(assoc ::rscript/keys [(str key "." service "." user-id)])
|
(assoc ::rscript/keys [(str key "." service "." profile-id)])
|
||||||
(assoc ::rscript/vals (conj params (->seconds now))))
|
(assoc ::rscript/vals (conj params (->seconds now))))
|
||||||
result (rds/eval rconn script)
|
result (rds/eval rconn script)
|
||||||
allowed? (boolean (nth result 0))
|
allowed? (boolean (nth result 0))
|
||||||
@@ -192,18 +193,18 @@
|
|||||||
(assoc ::lresult/remaining remaining))))
|
(assoc ::lresult/remaining remaining))))
|
||||||
|
|
||||||
(defmethod process-limit :window
|
(defmethod process-limit :window
|
||||||
[rconn user-id now {:keys [::nreq ::unit ::key ::service] :as limit}]
|
[rconn profile-id now {:keys [::permits ::unit ::key ::service] :as limit}]
|
||||||
(let [ts (ct/truncate now unit)
|
(let [ts (ct/truncate now unit)
|
||||||
ttl (ct/diff now (ct/plus ts {unit 1}))
|
ttl (ct/diff now (ct/plus ts {unit 1}))
|
||||||
script (-> window-rate-limit-script
|
script (-> window-rate-limit-script
|
||||||
(assoc ::rscript/keys [(str key "." service "." user-id "." (ct/format-inst ts))])
|
(assoc ::rscript/keys [(str key "." service "." profile-id "." (ct/format-inst ts))])
|
||||||
(assoc ::rscript/vals [nreq (->seconds ttl)]))
|
(assoc ::rscript/vals [permits (->seconds ttl)]))
|
||||||
result (rds/eval rconn script)
|
result (rds/eval rconn script)
|
||||||
allowed? (boolean (nth result 0))
|
allowed? (boolean (nth result 0))
|
||||||
remaining (nth result 1)]
|
remaining (nth result 1)]
|
||||||
(l/trace :hint "limit processed"
|
(l/trace :hint "limit processed"
|
||||||
:service service
|
:service service
|
||||||
:limit (name (::name limit))
|
:name (name (::name limit))
|
||||||
:strategy (name (::strategy limit))
|
:strategy (name (::strategy limit))
|
||||||
:opts (::opts limit)
|
:opts (::opts limit)
|
||||||
:allowed allowed?
|
:allowed allowed?
|
||||||
@@ -214,8 +215,8 @@
|
|||||||
(assoc ::lresult/reset (ct/plus ts {unit 1})))))
|
(assoc ::lresult/reset (ct/plus ts {unit 1})))))
|
||||||
|
|
||||||
(defn- process-limits
|
(defn- process-limits
|
||||||
[rconn user-id limits now]
|
[rconn profile-id limits now]
|
||||||
(let [results (into [] (map (partial process-limit rconn user-id now)) limits)
|
(let [results (into [] (map (partial process-limit rconn profile-id now)) limits)
|
||||||
remaining (->> results
|
remaining (->> results
|
||||||
(d/index-by ::name ::lresult/remaining)
|
(d/index-by ::name ::lresult/remaining)
|
||||||
(uri/map->query-string))
|
(uri/map->query-string))
|
||||||
@@ -227,7 +228,7 @@
|
|||||||
|
|
||||||
(when rejected
|
(when rejected
|
||||||
(l/warn :hint "rejected rate limit"
|
(l/warn :hint "rejected rate limit"
|
||||||
:user-id (str user-id)
|
:profile-id (str profile-id)
|
||||||
:limit-service (-> rejected ::service name)
|
:limit-service (-> rejected ::service name)
|
||||||
:limit-name (-> rejected ::name name)
|
:limit-name (-> rejected ::name name)
|
||||||
:limit-strategy (-> rejected ::strategy name)))
|
:limit-strategy (-> rejected ::strategy name)))
|
||||||
@@ -371,12 +372,9 @@
|
|||||||
(defn- on-refresh-error
|
(defn- on-refresh-error
|
||||||
[_ cause]
|
[_ cause]
|
||||||
(when-not (instance? java.util.concurrent.RejectedExecutionException cause)
|
(when-not (instance? java.util.concurrent.RejectedExecutionException cause)
|
||||||
(if-let [explain (-> cause ex-data ex/explain)]
|
(l/warn :hint "unexpected exception on loading config"
|
||||||
(l/warn ::l/raw (str "unable to refresh config, invalid format:\n" explain)
|
:cause cause
|
||||||
::l/sync? true)
|
::l/sync? true)))
|
||||||
(l/warn :hint "unexpected exception on loading config"
|
|
||||||
:cause cause
|
|
||||||
::l/sync? true))))
|
|
||||||
|
|
||||||
(defn- get-config-path
|
(defn- get-config-path
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ local allowed = filled >= requested
|
|||||||
local newTokens = filled
|
local newTokens = filled
|
||||||
if allowed then
|
if allowed then
|
||||||
newTokens = filled - requested
|
newTokens = filled - requested
|
||||||
|
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
|
||||||
end
|
end
|
||||||
|
|
||||||
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
|
|
||||||
redis.call("expire", tokensKey, ttl)
|
redis.call("expire", tokensKey, ttl)
|
||||||
|
|
||||||
return { allowed, newTokens }
|
return { allowed, newTokens }
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
:assets-s3 :s3
|
:assets-s3 :s3
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
|
(def default-bucket
|
||||||
|
"file-media-object")
|
||||||
|
|
||||||
(def valid-buckets
|
(def valid-buckets
|
||||||
#{"file-media-object"
|
#{"file-media-object"
|
||||||
"team-font-variant"
|
"team-font-variant"
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.storage :as-alias sto]
|
[app.storage :as sto]
|
||||||
[app.storage.impl :as impl]
|
[app.storage.impl :as impl]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
[{:keys [metadata]}]
|
[{:keys [metadata]}]
|
||||||
(or (some-> metadata :bucket)
|
(or (some-> metadata :bucket)
|
||||||
(some-> metadata :reference d/name)
|
(some-> metadata :reference d/name)
|
||||||
"file-media-object"))
|
sto/default-bucket))
|
||||||
|
|
||||||
(defn- process-objects!
|
(defn- process-objects!
|
||||||
[conn has-refs? bucket objects]
|
[conn has-refs? bucket objects]
|
||||||
|
|||||||
@@ -45,7 +45,8 @@
|
|||||||
:deleted-at (ct/format-inst deleted-at))
|
:deleted-at (ct/format-inst deleted-at))
|
||||||
|
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:deleted-at deleted-at}
|
{:deleted-at deleted-at
|
||||||
|
:is-shared false}
|
||||||
{:id id}
|
{:id id}
|
||||||
{::db/return-keys false})
|
{::db/return-keys false})
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@
|
|||||||
(not *team-deletion*))
|
(not *team-deletion*))
|
||||||
;; NOTE: we don't prevent file deletion on absorb operation failure
|
;; NOTE: we don't prevent file deletion on absorb operation failure
|
||||||
(try
|
(try
|
||||||
(db/tx-run! cfg files/absorb-library! id)
|
(db/tx-run! cfg files/absorb-library id)
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(l/warn :hint "error on absorbing library"
|
(l/warn :hint "error on absorbing library"
|
||||||
:file-id id
|
:file-id id
|
||||||
|
|||||||
@@ -7,10 +7,18 @@
|
|||||||
(ns app.util.template
|
(ns app.util.template
|
||||||
(:require
|
(:require
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[selmer.filters :as sf]
|
||||||
[selmer.parser :as sp]))
|
[selmer.parser :as sp]))
|
||||||
|
|
||||||
;; (sp/cache-off!)
|
;; (sp/cache-off!)
|
||||||
|
|
||||||
|
(sf/add-filter! :abbreviate
|
||||||
|
(fn [s n]
|
||||||
|
(let [n (parse-long n)]
|
||||||
|
(str/abbreviate s n))))
|
||||||
|
|
||||||
|
|
||||||
(defn render
|
(defn render
|
||||||
[path context]
|
[path context]
|
||||||
(try
|
(try
|
||||||
|
|||||||
@@ -137,33 +137,34 @@ RETURNING task.id, task.queue")
|
|||||||
::wait)))
|
::wait)))
|
||||||
|
|
||||||
(run-batch []
|
(run-batch []
|
||||||
(let [rconn (rds/connect cfg)]
|
(try
|
||||||
(try
|
(let [rconn (rds/connect cfg)]
|
||||||
(-> cfg
|
(try
|
||||||
(assoc ::rds/conn rconn)
|
(-> cfg
|
||||||
(db/tx-run! run-batch'))
|
(assoc ::rds/conn rconn)
|
||||||
|
(db/tx-run! run-batch'))
|
||||||
|
(finally
|
||||||
|
(.close ^AutoCloseable rconn))))
|
||||||
|
|
||||||
(catch InterruptedException cause
|
(catch InterruptedException cause
|
||||||
(throw cause))
|
(throw cause))
|
||||||
(catch Exception cause
|
|
||||||
(cond
|
|
||||||
(rds/exception? cause)
|
|
||||||
(do
|
|
||||||
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
|
|
||||||
(px/sleep timeout))
|
|
||||||
|
|
||||||
(db/sql-exception? cause)
|
(catch Exception cause
|
||||||
(do
|
(cond
|
||||||
(l/wrn :hint "database exception (will retry in an instant)" :cause cause)
|
(rds/exception? cause)
|
||||||
(px/sleep timeout))
|
(do
|
||||||
|
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
|
||||||
|
(px/sleep timeout))
|
||||||
|
|
||||||
:else
|
(db/sql-exception? cause)
|
||||||
(do
|
(do
|
||||||
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause)
|
(l/wrn :hint "database exception (will retry in an instant)" :cause cause)
|
||||||
(px/sleep timeout))))
|
(px/sleep timeout))
|
||||||
|
|
||||||
(finally
|
:else
|
||||||
(.close ^AutoCloseable rconn)))))
|
(do
|
||||||
|
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause)
|
||||||
|
(px/sleep timeout))))))
|
||||||
|
|
||||||
(dispatcher []
|
(dispatcher []
|
||||||
(l/inf :hint "started")
|
(l/inf :hint "started")
|
||||||
@@ -176,7 +177,7 @@ RETURNING task.id, task.queue")
|
|||||||
(catch InterruptedException _
|
(catch InterruptedException _
|
||||||
(l/trc :hint "interrupted"))
|
(l/trc :hint "interrupted"))
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(l/err :hint " unexpected exception" :cause cause))
|
(l/err :hint "unexpected exception" :cause cause))
|
||||||
(finally
|
(finally
|
||||||
(l/inf :hint "terminated"))))]
|
(l/inf :hint "terminated"))))]
|
||||||
|
|
||||||
|
|||||||
@@ -595,8 +595,8 @@
|
|||||||
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
|
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
|
||||||
(into []
|
(into []
|
||||||
(map (fn [{:keys [event data]}]
|
(map (fn [{:keys [event data]}]
|
||||||
[(keyword event)
|
(d/vec2 (keyword event)
|
||||||
(tr/decode-str data)]))
|
(tr/decode-str data))))
|
||||||
(parse-sse (slurp' input)))
|
(parse-sse (slurp' input)))
|
||||||
(finally
|
(finally
|
||||||
(.close input)))))
|
(.close input)))))
|
||||||
|
|||||||
@@ -1921,7 +1921,11 @@
|
|||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(let [result (:result out)]
|
(let [result (:result out)]
|
||||||
(t/is (= (:ids data) result)))
|
(t/is (fn? result))
|
||||||
|
|
||||||
|
(let [[ev1 ev2 :as events] (th/consume-sse result)]
|
||||||
|
(t/is (= 2 (count events)))
|
||||||
|
(t/is (= (:ids data) (val ev2)))))
|
||||||
|
|
||||||
(let [row (th/db-exec-one! ["select * from file where id = ?" file-id])]
|
(let [row (th/db-exec-one! ["select * from file where id = ?" file-id])]
|
||||||
(t/is (= (:deleted-at row) now)))))))
|
(t/is (= (:deleted-at row) now)))))))
|
||||||
|
|||||||
@@ -29,8 +29,7 @@
|
|||||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||||
integrant/integrant {:mvn/version "1.0.0"}
|
integrant/integrant {:mvn/version "1.0.0"}
|
||||||
|
|
||||||
funcool/tubax {:mvn/version "2021.05.20-0"}
|
funcool/cuerdas {:mvn/version "2026.415"}
|
||||||
funcool/cuerdas {:mvn/version "2025.06.16-414"}
|
|
||||||
funcool/promesa
|
funcool/promesa
|
||||||
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
|
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
|
||||||
:git/url "https://github.com/funcool/promesa"}
|
:git/url "https://github.com/funcool/promesa"}
|
||||||
|
|||||||
@@ -1024,6 +1024,26 @@
|
|||||||
:clj
|
:clj
|
||||||
(sort comp-fn items))))
|
(sort comp-fn items))))
|
||||||
|
|
||||||
|
(defn obfuscate-string
|
||||||
|
"Obfuscates potentially sensitive values.
|
||||||
|
|
||||||
|
- One-arg arity:
|
||||||
|
* For strings shorter than 10 characters, all characters are replaced by `*`.
|
||||||
|
* For longer strings, the first 5 characters are preserved and the rest obfuscated.
|
||||||
|
- Two-arg arity accepts a boolean `full?` that, when true, replaces the whole value
|
||||||
|
by `*`, preserving only the length."
|
||||||
|
([v]
|
||||||
|
(obfuscate-string v false))
|
||||||
|
([v full?]
|
||||||
|
(let [s (str v)
|
||||||
|
n (count s)]
|
||||||
|
(cond
|
||||||
|
(zero? n) s
|
||||||
|
full? (apply str (repeat n "*"))
|
||||||
|
(< n 10) (apply str (repeat n "*"))
|
||||||
|
:else (str (subs s 0 5)
|
||||||
|
(apply str (repeat (- n 5) "*")))))))
|
||||||
|
|
||||||
(defn reorder
|
(defn reorder
|
||||||
"Reorder a vector by moving one of their items from some position to some space between positions.
|
"Reorder a vector by moving one of their items from some position to some space between positions.
|
||||||
It clamps the position numbers to a valid range."
|
It clamps the position numbers to a valid range."
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
(:refer-clojure :exclude [instance?])
|
(:refer-clojure :exclude [instance?])
|
||||||
(:require
|
(:require
|
||||||
#?(:clj [clojure.stacktrace :as strace])
|
#?(:clj [clojure.stacktrace :as strace])
|
||||||
|
[app.common.data :refer [obfuscate-string]]
|
||||||
[app.common.pprint :as pp]
|
[app.common.pprint :as pp]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[clojure.core :as c]
|
[clojure.core :as c]
|
||||||
@@ -19,6 +20,10 @@
|
|||||||
(:import
|
(:import
|
||||||
clojure.lang.IPersistentMap)))
|
clojure.lang.IPersistentMap)))
|
||||||
|
|
||||||
|
(def ^:private sensitive-fields
|
||||||
|
"Keys whose values must be obfuscated in validation explains."
|
||||||
|
#{:password :old-password :token :invitation-token})
|
||||||
|
|
||||||
#?(:clj (set! *warn-on-reflection* true))
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
(def ^:dynamic *data-length* 8)
|
(def ^:dynamic *data-length* 8)
|
||||||
@@ -110,7 +115,25 @@
|
|||||||
(explain (:explain data) opts)
|
(explain (:explain data) opts)
|
||||||
|
|
||||||
(contains? data ::sm/explain)
|
(contains? data ::sm/explain)
|
||||||
(sm/humanize-explain (::sm/explain data) opts)))
|
(let [exp (::sm/explain data)
|
||||||
|
sanitize-map (fn sanitize-map [m]
|
||||||
|
(reduce-kv
|
||||||
|
(fn [acc k v]
|
||||||
|
(let [k* (if (string? k) (keyword k) k)]
|
||||||
|
(cond
|
||||||
|
(contains? sensitive-fields k*)
|
||||||
|
(assoc acc k (if (map? v)
|
||||||
|
(sanitize-map v)
|
||||||
|
(obfuscate-string v true)))
|
||||||
|
|
||||||
|
(map? v) (assoc acc k (sanitize-map v))
|
||||||
|
:else (assoc acc k v))))
|
||||||
|
{}
|
||||||
|
m))
|
||||||
|
sanitize-explain (fn [exp]
|
||||||
|
(cond-> exp
|
||||||
|
(:value exp) (update :value sanitize-map)))]
|
||||||
|
(sm/humanize-explain (sanitize-explain exp) opts))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(defn format-throwable
|
(defn format-throwable
|
||||||
|
|||||||
@@ -169,6 +169,7 @@
|
|||||||
:enable-component-thumbnails
|
:enable-component-thumbnails
|
||||||
:enable-render-wasm-dpr
|
:enable-render-wasm-dpr
|
||||||
:enable-token-color
|
:enable-token-color
|
||||||
|
:enable-token-shadow
|
||||||
:enable-inspect-styles
|
:enable-inspect-styles
|
||||||
:enable-feature-fdata-objects-map])
|
:enable-feature-fdata-objects-map])
|
||||||
|
|
||||||
|
|||||||
@@ -340,7 +340,7 @@
|
|||||||
(dfn-diff t2 t1)))
|
(dfn-diff t2 t1)))
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(defn set-default-locale!
|
(defn set-default-locale
|
||||||
[locale]
|
[locale]
|
||||||
(when-let [locale (unchecked-get locales locale)]
|
(when-let [locale (unchecked-get locales locale)]
|
||||||
(dfn-set-default-options #js {:locale locale}))))
|
(dfn-set-default-options #js {:locale locale}))))
|
||||||
|
|||||||
@@ -269,8 +269,8 @@
|
|||||||
"Remove flex children properties except the fit-content for flex layouts. These are properties
|
"Remove flex children properties except the fit-content for flex layouts. These are properties
|
||||||
that we don't have to propagate to copies but will be respected when swapping components"
|
that we don't have to propagate to copies but will be respected when swapping components"
|
||||||
[shape]
|
[shape]
|
||||||
(let [layout-item-h-sizing (when (and (ctl/flex-layout? shape) (ctl/auto-width? shape)) :auto)
|
(let [layout-item-h-sizing (when (and (ctl/any-layout? shape) (ctl/auto-width? shape)) :auto)
|
||||||
layout-item-v-sizing (when (and (ctl/flex-layout? shape) (ctl/auto-height? shape)) :auto)]
|
layout-item-v-sizing (when (and (ctl/any-layout? shape) (ctl/auto-height? shape)) :auto)]
|
||||||
(-> shape
|
(-> shape
|
||||||
(d/without-keys ctk/swap-keep-attrs)
|
(d/without-keys ctk/swap-keep-attrs)
|
||||||
(cond-> (some? layout-item-h-sizing)
|
(cond-> (some? layout-item-h-sizing)
|
||||||
|
|||||||
@@ -362,24 +362,24 @@
|
|||||||
component (ctkl/get-component component-file (:component-id top-instance) true)
|
component (ctkl/get-component component-file (:component-id top-instance) true)
|
||||||
remote-shape (get-ref-shape component-file component shape)
|
remote-shape (get-ref-shape component-file component shape)
|
||||||
component-container (get-component-container component-file component)
|
component-container (get-component-container component-file component)
|
||||||
[remote-shape component-container]
|
[remote-shape component-container component-file]
|
||||||
(if (some? remote-shape)
|
(if (some? remote-shape)
|
||||||
[remote-shape component-container]
|
[remote-shape component-container component-file]
|
||||||
;; If not found, try the case of this being a fostered or swapped children
|
;; If not found, try the case of this being a fostered or swapped children
|
||||||
(let [head-instance (ctn/get-head-shape (:objects container) shape)
|
(let [head-instance (ctn/get-head-shape (:objects container) shape)
|
||||||
component-file (get-in libraries [(:component-file head-instance) :data])
|
component-file (get-in libraries [(:component-file head-instance) :data])
|
||||||
head-component (ctkl/get-component component-file (:component-id head-instance) true)
|
head-component (ctkl/get-component component-file (:component-id head-instance) true)
|
||||||
remote-shape' (get-ref-shape component-file head-component shape)
|
remote-shape' (get-ref-shape component-file head-component shape)
|
||||||
component-container (get-component-container component-file component)]
|
component-container' (get-component-container component-file head-component)]
|
||||||
[remote-shape' component-container]))]
|
[remote-shape' component-container' component-file]))]
|
||||||
|
|
||||||
(if (nil? remote-shape)
|
(if (nil? remote-shape)
|
||||||
nil
|
nil
|
||||||
(if (nil? (:shape-ref remote-shape))
|
(if (nil? (:shape-ref remote-shape))
|
||||||
(cond-> remote-shape
|
(cond-> remote-shape
|
||||||
(and remote-shape with-context?)
|
(and remote-shape with-context?)
|
||||||
(with-meta {:file {:id (:id file-data)
|
(with-meta {:file {:id (:id component-file)
|
||||||
:data file-data}
|
:data component-file}
|
||||||
:container component-container}))
|
:container component-container}))
|
||||||
(find-remote-shape component-container libraries remote-shape :with-context? with-context?)))))
|
(find-remote-shape component-container libraries remote-shape :with-context? with-context?)))))
|
||||||
|
|
||||||
|
|||||||
@@ -112,8 +112,10 @@
|
|||||||
(:c2y params) (update-in [index :params :c2y] + (:c2y params)))
|
(:c2y params) (update-in [index :params :c2y] + (:c2y params)))
|
||||||
content))]
|
content))]
|
||||||
|
|
||||||
(impl/path-data
|
(if (some? modifiers)
|
||||||
(reduce apply-to-index (vec content) modifiers))))
|
(impl/path-data
|
||||||
|
(reduce apply-to-index (vec content) modifiers))
|
||||||
|
content)))
|
||||||
|
|
||||||
(defn transform-content
|
(defn transform-content
|
||||||
"Applies a transformation matrix over content and returns a new
|
"Applies a transformation matrix over content and returns a new
|
||||||
|
|||||||
29
common/src/app/common/types/project.cljc
Normal file
29
common/src/app/common/types/project.cljc
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.common.types.project
|
||||||
|
(:require
|
||||||
|
[app.common.schema :as sm]
|
||||||
|
[app.common.time :as cm]))
|
||||||
|
|
||||||
|
(def schema:project
|
||||||
|
[:map {:title "Profile"}
|
||||||
|
[:id ::sm/uuid]
|
||||||
|
[:created-at {:optional true} ::cm/inst]
|
||||||
|
[:modified-at {:optional true} ::cm/inst]
|
||||||
|
[:name :string]
|
||||||
|
[:is-default {:optional true} ::sm/boolean]
|
||||||
|
[:is-pinned {:optional true} ::sm/boolean]
|
||||||
|
[:count {:optional true} ::sm/int]
|
||||||
|
[:total-count {:optional true} ::sm/int]
|
||||||
|
[:team-id ::sm/uuid]])
|
||||||
|
|
||||||
|
(def valid-project?
|
||||||
|
(sm/lazy-validator schema:project))
|
||||||
|
|
||||||
|
(def check-project
|
||||||
|
(sm/check-fn schema:project))
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
:dimensions "dimension"
|
:dimensions "dimension"
|
||||||
:font-family "fontFamilies"
|
:font-family "fontFamilies"
|
||||||
:font-size "fontSizes"
|
:font-size "fontSizes"
|
||||||
|
:font-weight "fontWeights"
|
||||||
:letter-spacing "letterSpacing"
|
:letter-spacing "letterSpacing"
|
||||||
:number "number"
|
:number "number"
|
||||||
:opacity "opacity"
|
:opacity "opacity"
|
||||||
@@ -70,7 +71,6 @@
|
|||||||
:stroke-width "borderWidth"
|
:stroke-width "borderWidth"
|
||||||
:text-case "textCase"
|
:text-case "textCase"
|
||||||
:text-decoration "textDecoration"
|
:text-decoration "textDecoration"
|
||||||
:font-weight "fontWeights"
|
|
||||||
:typography "typography"})
|
:typography "typography"})
|
||||||
|
|
||||||
(def dtcg-token-type->token-type
|
(def dtcg-token-type->token-type
|
||||||
|
|||||||
@@ -1410,8 +1410,8 @@ Will return a value that matches this schema:
|
|||||||
;; NOTE: we can't assign statically at eval time the value of a
|
;; NOTE: we can't assign statically at eval time the value of a
|
||||||
;; function that is declared but not defined; so we need to pass
|
;; function that is declared but not defined; so we need to pass
|
||||||
;; an anonymous function and delegate the resolution to runtime
|
;; an anonymous function and delegate the resolution to runtime
|
||||||
{:encode/json #(export-dtcg-json %)
|
{:encode/json #(some-> % export-dtcg-json)
|
||||||
:decode/json #(read-multi-set-dtcg %)
|
:decode/json #(some-> % read-multi-set-dtcg)
|
||||||
;; FIXME: add better, more reallistic generator
|
;; FIXME: add better, more reallistic generator
|
||||||
:gen/gen (->> (sg/small-int)
|
:gen/gen (->> (sg/small-int)
|
||||||
(sg/fmap (fn [_]
|
(sg/fmap (fn [_]
|
||||||
@@ -1545,7 +1545,7 @@ Will return a value that matches this schema:
|
|||||||
(and (not (contains? decoded-json "$metadata"))
|
(and (not (contains? decoded-json "$metadata"))
|
||||||
(not (contains? decoded-json "$themes"))))
|
(not (contains? decoded-json "$themes"))))
|
||||||
|
|
||||||
(defn- convert-dtcg-font-family
|
(defn convert-dtcg-font-family
|
||||||
"Convert font-family token value from DTCG format to internal format.
|
"Convert font-family token value from DTCG format to internal format.
|
||||||
- If value is a string, split it into a collection of font families
|
- If value is a string, split it into a collection of font families
|
||||||
- If value is already an array, keep it as is
|
- If value is already an array, keep it as is
|
||||||
@@ -1556,7 +1556,7 @@ Will return a value that matches this schema:
|
|||||||
(sequential? value) value
|
(sequential? value) value
|
||||||
:else value))
|
:else value))
|
||||||
|
|
||||||
(defn- convert-dtcg-typography-composite
|
(defn convert-dtcg-typography-composite
|
||||||
"Convert typography token value keys from DTCG format to internal format."
|
"Convert typography token value keys from DTCG format to internal format."
|
||||||
[value]
|
[value]
|
||||||
(if (map? value)
|
(if (map? value)
|
||||||
@@ -1568,7 +1568,7 @@ Will return a value that matches this schema:
|
|||||||
;; Reference value
|
;; Reference value
|
||||||
value))
|
value))
|
||||||
|
|
||||||
(defn- convert-dtcg-shadow-composite
|
(defn convert-dtcg-shadow-composite
|
||||||
"Convert shadow token value from DTCG format to internal format."
|
"Convert shadow token value from DTCG format to internal format."
|
||||||
[value]
|
[value]
|
||||||
(let [process-shadow (fn [shadow]
|
(let [process-shadow (fn [shadow]
|
||||||
|
|||||||
@@ -10,3 +10,7 @@ localhost:3449 {
|
|||||||
http://localhost:3450 {
|
http://localhost:3450 {
|
||||||
reverse_proxy localhost:4449
|
reverse_proxy localhost:4449
|
||||||
}
|
}
|
||||||
|
|
||||||
|
http://penpot-devenv-main:3450 {
|
||||||
|
reverse_proxy localhost:4449
|
||||||
|
}
|
||||||
|
|||||||
@@ -145,8 +145,8 @@ http {
|
|||||||
proxy_pass http://127.0.0.1:3000/;
|
proxy_pass http://127.0.0.1:3000/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /playground {
|
location /wasm-playground {
|
||||||
alias /home/penpot/penpot/experiments/;
|
alias /home/penpot/penpot/frontend/resources/public/wasm-playground/;
|
||||||
add_header Cache-Control "no-cache, max-age=0";
|
add_header Cache-Control "no-cache, max-age=0";
|
||||||
autoindex on;
|
autoindex on;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,10 @@ source ~/.bashrc
|
|||||||
|
|
||||||
echo "[start-tmux.sh] Installing node dependencies"
|
echo "[start-tmux.sh] Installing node dependencies"
|
||||||
pushd ~/penpot/frontend/
|
pushd ~/penpot/frontend/
|
||||||
corepack install;
|
./scripts/setup;
|
||||||
yarn install;
|
|
||||||
yarn playwright install chromium
|
|
||||||
popd
|
popd
|
||||||
pushd ~/penpot/exporter/
|
pushd ~/penpot/exporter/
|
||||||
corepack install;
|
./scripts/setup;
|
||||||
yarn install
|
|
||||||
yarn playwright install chromium
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
tmux -2 new-session -d -s penpot
|
tmux -2 new-session -d -s penpot
|
||||||
@@ -23,30 +19,25 @@ tmux -2 new-session -d -s penpot
|
|||||||
tmux rename-window -t penpot:0 'frontend watch'
|
tmux rename-window -t penpot:0 'frontend watch'
|
||||||
tmux select-window -t penpot:0
|
tmux select-window -t penpot:0
|
||||||
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
|
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
|
||||||
tmux send-keys -t penpot 'yarn run watch' enter
|
tmux send-keys -t penpot './scripts/watch app' enter
|
||||||
|
|
||||||
tmux new-window -t penpot:1 -n 'frontend shadow'
|
tmux new-window -t penpot:1 -n 'frontend storybook'
|
||||||
tmux select-window -t penpot:1
|
tmux select-window -t penpot:1
|
||||||
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
|
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
|
||||||
tmux send-keys -t penpot 'yarn run watch:app' enter
|
tmux send-keys -t penpot './scripts/watch storybook' enter
|
||||||
|
|
||||||
tmux new-window -t penpot:2 -n 'frontend storybook'
|
tmux new-window -t penpot:2 -n 'exporter'
|
||||||
tmux select-window -t penpot:2
|
tmux select-window -t penpot:2
|
||||||
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
|
|
||||||
tmux send-keys -t penpot 'yarn run watch:storybook' enter
|
|
||||||
|
|
||||||
tmux new-window -t penpot:3 -n 'exporter'
|
|
||||||
tmux select-window -t penpot:3
|
|
||||||
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
|
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
|
||||||
tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l
|
tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l
|
||||||
tmux send-keys -t penpot 'yarn run watch' enter
|
tmux send-keys -t penpot './scripts/watch' enter
|
||||||
|
|
||||||
tmux split-window -v
|
tmux split-window -v
|
||||||
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
|
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
|
||||||
tmux send-keys -t penpot './scripts/wait-and-start.sh' enter
|
tmux send-keys -t penpot './scripts/wait-and-start.sh' enter
|
||||||
|
|
||||||
tmux new-window -t penpot:4 -n 'backend'
|
tmux new-window -t penpot:3 -n 'backend'
|
||||||
tmux select-window -t penpot:4
|
tmux select-window -t penpot:3
|
||||||
tmux send-keys -t penpot 'cd penpot/backend' enter C-l
|
tmux send-keys -t penpot 'cd penpot/backend' enter C-l
|
||||||
tmux send-keys -t penpot './scripts/start-dev' enter
|
tmux send-keys -t penpot './scripts/start-dev' enter
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"author": "Kaleidos INC",
|
"author": "Kaleidos INC",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
|
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/penpot/penpot"
|
"url": "https://github.com/penpot/penpot"
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"generic-pool": "^3.9.0",
|
"generic-pool": "^3.9.0",
|
||||||
"inflation": "^2.1.0",
|
"inflation": "^2.1.0",
|
||||||
"ioredis": "^5.8.1",
|
"ioredis": "^5.8.2",
|
||||||
"playwright": "^1.55.1",
|
"playwright": "^1.57.0",
|
||||||
"raw-body": "^3.0.1",
|
"raw-body": "^3.0.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"svgo": "penpot/svgo#v3.1",
|
"svgo": "penpot/svgo#v3.1",
|
||||||
"undici": "^7.16.0",
|
"undici": "^7.16.0",
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target",
|
"clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target",
|
||||||
"watch:app": "clojure -M:dev:shadow-cljs watch main",
|
"watch:app": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main",
|
||||||
"watch": "yarn run clear:shadow-cache && yarn run watch:app",
|
"watch": "yarn run watch:app",
|
||||||
"build:app": "clojure -M:dev:shadow-cljs release main",
|
"build:app": "clojure -M:dev:shadow-cljs release main",
|
||||||
"build": "yarn run clear:shadow-cache && yarn run build:app",
|
"build": "yarn run clear:shadow-cache && yarn run build:app",
|
||||||
"fmt:clj:check": "cljfmt check --parallel=false src/",
|
"fmt:clj:check": "cljfmt check --parallel=false src/",
|
||||||
|
|||||||
8
exporter/scripts/setup
Executable file
8
exporter/scripts/setup
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e;
|
||||||
|
|
||||||
|
corepack enable;
|
||||||
|
corepack install;
|
||||||
|
yarn install;
|
||||||
|
yarn playwright install chromium
|
||||||
7
exporter/scripts/watch
Executable file
7
exporter/scripts/watch
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
TARGET=${1:-app};
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
exec yarn run watch:$TARGET
|
||||||
@@ -243,7 +243,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"bytes@npm:3.1.2":
|
"bytes@npm:~3.1.2":
|
||||||
version: 3.1.2
|
version: 3.1.2
|
||||||
resolution: "bytes@npm:3.1.2"
|
resolution: "bytes@npm:3.1.2"
|
||||||
checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e
|
checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e
|
||||||
@@ -442,7 +442,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"depd@npm:2.0.0, depd@npm:~2.0.0":
|
"depd@npm:~2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "depd@npm:2.0.0"
|
resolution: "depd@npm:2.0.0"
|
||||||
checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c
|
checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c
|
||||||
@@ -577,9 +577,9 @@ __metadata:
|
|||||||
date-fns: "npm:^4.1.0"
|
date-fns: "npm:^4.1.0"
|
||||||
generic-pool: "npm:^3.9.0"
|
generic-pool: "npm:^3.9.0"
|
||||||
inflation: "npm:^2.1.0"
|
inflation: "npm:^2.1.0"
|
||||||
ioredis: "npm:^5.8.1"
|
ioredis: "npm:^5.8.2"
|
||||||
playwright: "npm:^1.55.1"
|
playwright: "npm:^1.57.0"
|
||||||
raw-body: "npm:^3.0.1"
|
raw-body: "npm:^3.0.2"
|
||||||
source-map-support: "npm:^0.5.21"
|
source-map-support: "npm:^0.5.21"
|
||||||
svgo: "penpot/svgo#v3.1"
|
svgo: "penpot/svgo#v3.1"
|
||||||
undici: "npm:^7.16.0"
|
undici: "npm:^7.16.0"
|
||||||
@@ -683,16 +683,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"http-errors@npm:2.0.0":
|
"http-errors@npm:~2.0.1":
|
||||||
version: 2.0.0
|
version: 2.0.1
|
||||||
resolution: "http-errors@npm:2.0.0"
|
resolution: "http-errors@npm:2.0.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
depd: "npm:2.0.0"
|
depd: "npm:~2.0.0"
|
||||||
inherits: "npm:2.0.4"
|
inherits: "npm:~2.0.4"
|
||||||
setprototypeof: "npm:1.2.0"
|
setprototypeof: "npm:~1.2.0"
|
||||||
statuses: "npm:2.0.1"
|
statuses: "npm:~2.0.2"
|
||||||
toidentifier: "npm:1.0.1"
|
toidentifier: "npm:~1.0.1"
|
||||||
checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19
|
checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -716,15 +716,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"iconv-lite@npm:0.7.0":
|
|
||||||
version: 0.7.0
|
|
||||||
resolution: "iconv-lite@npm:0.7.0"
|
|
||||||
dependencies:
|
|
||||||
safer-buffer: "npm:>= 2.1.2 < 3.0.0"
|
|
||||||
checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"iconv-lite@npm:^0.6.2":
|
"iconv-lite@npm:^0.6.2":
|
||||||
version: 0.6.3
|
version: 0.6.3
|
||||||
resolution: "iconv-lite@npm:0.6.3"
|
resolution: "iconv-lite@npm:0.6.3"
|
||||||
@@ -734,6 +725,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"iconv-lite@npm:~0.7.0":
|
||||||
|
version: 0.7.0
|
||||||
|
resolution: "iconv-lite@npm:0.7.0"
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: "npm:>= 2.1.2 < 3.0.0"
|
||||||
|
checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ieee754@npm:^1.2.1":
|
"ieee754@npm:^1.2.1":
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
resolution: "ieee754@npm:1.2.1"
|
resolution: "ieee754@npm:1.2.1"
|
||||||
@@ -755,16 +755,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"inherits@npm:2.0.4, inherits@npm:~2.0.3":
|
"inherits@npm:~2.0.3, inherits@npm:~2.0.4":
|
||||||
version: 2.0.4
|
version: 2.0.4
|
||||||
resolution: "inherits@npm:2.0.4"
|
resolution: "inherits@npm:2.0.4"
|
||||||
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
|
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ioredis@npm:^5.8.1":
|
"ioredis@npm:^5.8.2":
|
||||||
version: 5.8.1
|
version: 5.8.2
|
||||||
resolution: "ioredis@npm:5.8.1"
|
resolution: "ioredis@npm:5.8.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ioredis/commands": "npm:1.4.0"
|
"@ioredis/commands": "npm:1.4.0"
|
||||||
cluster-key-slot: "npm:^1.1.0"
|
cluster-key-slot: "npm:^1.1.0"
|
||||||
@@ -775,7 +775,7 @@ __metadata:
|
|||||||
redis-errors: "npm:^1.2.0"
|
redis-errors: "npm:^1.2.0"
|
||||||
redis-parser: "npm:^3.0.0"
|
redis-parser: "npm:^3.0.0"
|
||||||
standard-as-callback: "npm:^2.1.0"
|
standard-as-callback: "npm:^2.1.0"
|
||||||
checksum: 10c0/4ed66444017150da027bce940a24bf726994691e2a7b3aa11d52f8aeb37f258068cc171af4d9c61247acafc28eb086fa8a7c79420b8e8d2907d2f74f39584465
|
checksum: 10c0/305e385f811d49908899e32c2de69616cd059f909afd9e0a53e54f596b1a5835ee3449bfc6a3c49afbc5a2fd27990059e316cc78f449c94024957bd34c826d88
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1106,27 +1106,27 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"playwright-core@npm:1.55.1":
|
"playwright-core@npm:1.57.0":
|
||||||
version: 1.55.1
|
version: 1.57.0
|
||||||
resolution: "playwright-core@npm:1.55.1"
|
resolution: "playwright-core@npm:1.57.0"
|
||||||
bin:
|
bin:
|
||||||
playwright-core: cli.js
|
playwright-core: cli.js
|
||||||
checksum: 10c0/39837a8c1232ec27486eac8c3fcacc0b090acc64310f7f9004b06715370fc426f944e3610fe8c29f17cd3d68280ed72c75f660c02aa5b5cf0eb34bab0031308f
|
checksum: 10c0/798e35d83bf48419a8c73de20bb94d68be5dde68de23f95d80a0ebe401e3b83e29e3e84aea7894d67fa6c79d2d3d40cc5bcde3e166f657ce50987aaa2421b6a9
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"playwright@npm:^1.55.1":
|
"playwright@npm:^1.57.0":
|
||||||
version: 1.55.1
|
version: 1.57.0
|
||||||
resolution: "playwright@npm:1.55.1"
|
resolution: "playwright@npm:1.57.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
fsevents: "npm:2.3.2"
|
fsevents: "npm:2.3.2"
|
||||||
playwright-core: "npm:1.55.1"
|
playwright-core: "npm:1.57.0"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
fsevents:
|
fsevents:
|
||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
playwright: cli.js
|
playwright: cli.js
|
||||||
checksum: 10c0/b84a97b0d764403df512f5bbb10c7343974e151a28202cc06f90883a13e8a45f4491a0597f0ae5fb03a026746cbc0d200f0f32195bfaa381aee5ca5770626771
|
checksum: 10c0/ab03c99a67b835bdea9059f516ad3b6e42c21025f9adaa161a4ef6bc7ca716dcba476d287140bb240d06126eb23f889a8933b8f5f1f1a56b80659d92d1358899
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1161,15 +1161,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"raw-body@npm:^3.0.1":
|
"raw-body@npm:^3.0.2":
|
||||||
version: 3.0.1
|
version: 3.0.2
|
||||||
resolution: "raw-body@npm:3.0.1"
|
resolution: "raw-body@npm:3.0.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
bytes: "npm:3.1.2"
|
bytes: "npm:~3.1.2"
|
||||||
http-errors: "npm:2.0.0"
|
http-errors: "npm:~2.0.1"
|
||||||
iconv-lite: "npm:0.7.0"
|
iconv-lite: "npm:~0.7.0"
|
||||||
unpipe: "npm:1.0.0"
|
unpipe: "npm:~1.0.0"
|
||||||
checksum: 10c0/892f4fbd21ecab7e2fed0f045f7af9e16df7e8050879639d4e482784a2f4640aaaa33d916a0e98013f23acb82e09c2e3c57f84ab97104449f728d22f65a7d79a
|
checksum: 10c0/d266678d08e1e7abea62c0ce5864344e980fa81c64f6b481e9842c5beaed2cdcf975f658a3ccd67ad35fc919c1f6664ccc106067801850286a6cbe101de89f29
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1270,7 +1270,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"setprototypeof@npm:1.2.0":
|
"setprototypeof@npm:~1.2.0":
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
resolution: "setprototypeof@npm:1.2.0"
|
resolution: "setprototypeof@npm:1.2.0"
|
||||||
checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc
|
checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc
|
||||||
@@ -1368,10 +1368,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"statuses@npm:2.0.1":
|
"statuses@npm:~2.0.2":
|
||||||
version: 2.0.1
|
version: 2.0.2
|
||||||
resolution: "statuses@npm:2.0.1"
|
resolution: "statuses@npm:2.0.2"
|
||||||
checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0
|
checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1500,7 +1500,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"toidentifier@npm:1.0.1":
|
"toidentifier@npm:~1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "toidentifier@npm:1.0.1"
|
resolution: "toidentifier@npm:1.0.1"
|
||||||
checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1
|
checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1
|
||||||
@@ -1539,7 +1539,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"unpipe@npm:1.0.0":
|
"unpipe@npm:~1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "unpipe@npm:1.0.0"
|
resolution: "unpipe@npm:1.0.0"
|
||||||
checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c
|
checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
/** @type { import('@storybook/react-vite').StorybookConfig } */
|
/** @type { import('@storybook/react-vite').StorybookConfig } */
|
||||||
const config = {
|
const config = {
|
||||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||||
@@ -5,18 +7,38 @@ const config = {
|
|||||||
addons: [
|
addons: [
|
||||||
"@storybook/addon-themes",
|
"@storybook/addon-themes",
|
||||||
"@storybook/addon-docs",
|
"@storybook/addon-docs",
|
||||||
"@storybook/addon-vitest"
|
"@storybook/addon-vitest",
|
||||||
],
|
],
|
||||||
core: {
|
|
||||||
builder: "@storybook/builder-vite",
|
|
||||||
options: {
|
|
||||||
viteConfigPath: "../vite.config.js",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
framework: {
|
framework: {
|
||||||
name: "@storybook/react-vite",
|
name: "@storybook/react-vite",
|
||||||
options: {},
|
options: {
|
||||||
|
// fastRefresh: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
docs: {},
|
docs: {},
|
||||||
|
|
||||||
|
async viteFinal(config) {
|
||||||
|
return defineConfig({
|
||||||
|
...config,
|
||||||
|
plugins: [
|
||||||
|
...(config.plugins ?? []),
|
||||||
|
{
|
||||||
|
name: 'force-full-reload-always',
|
||||||
|
apply: 'serve',
|
||||||
|
enforce: 'post',
|
||||||
|
|
||||||
|
handleHotUpdate(ctx) {
|
||||||
|
ctx.server.ws.send({
|
||||||
|
type: 'full-reload',
|
||||||
|
path: '*',
|
||||||
|
});
|
||||||
|
|
||||||
|
// returning [] tells Vite: “no modules handled”
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { withThemeByClassName } from "@storybook/addon-themes";
|
import { withThemeByClassName } from "@storybook/addon-themes";
|
||||||
|
|
||||||
|
|
||||||
import Components from "@target/components";
|
import Components from "@target/components";
|
||||||
import translations from "@public/translation.en.js";
|
import translations from "@public/translation.en.js";
|
||||||
Components.setDefaultTranslations(translations);
|
Components.setDefaultTranslations(translations);
|
||||||
|
|||||||
@@ -8,6 +8,11 @@
|
|||||||
metosin/reitit-core {:mvn/version "0.9.1"}
|
metosin/reitit-core {:mvn/version "0.9.1"}
|
||||||
funcool/okulary {:mvn/version "2022.04.11-16"}
|
funcool/okulary {:mvn/version "2022.04.11-16"}
|
||||||
|
|
||||||
|
funcool/tubax
|
||||||
|
{:git/tag "v2025.11.28"
|
||||||
|
:git/sha "2d9a986"
|
||||||
|
:git/url "https://github.com/funcool/tubax.git"}
|
||||||
|
|
||||||
funcool/potok2
|
funcool/potok2
|
||||||
{:git/tag "v2.2"
|
{:git/tag "v2.2"
|
||||||
:git/sha "0f7e15a"
|
:git/sha "0f7e15a"
|
||||||
@@ -45,7 +50,7 @@
|
|||||||
{thheller/shadow-cljs {:mvn/version "3.2.2"}
|
{thheller/shadow-cljs {:mvn/version "3.2.2"}
|
||||||
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
||||||
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
||||||
criterium/criterium {:mvn/version "RELEASE"}
|
criterium/criterium {:mvn/version "0.4.6"}
|
||||||
cider/cider-nrepl {:mvn/version "0.57.0"}}}
|
cider/cider-nrepl {:mvn/version "0.57.0"}}}
|
||||||
|
|
||||||
:shadow-cljs
|
:shadow-cljs
|
||||||
|
|||||||
@@ -47,89 +47,81 @@
|
|||||||
"watch:app:libs": "node ./scripts/build-libs.js --watch",
|
"watch:app:libs": "node ./scripts/build-libs.js --watch",
|
||||||
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
|
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
|
||||||
"clear:shadow-cache": "rm -rf .shadow-cljs",
|
"clear:shadow-cache": "rm -rf .shadow-cljs",
|
||||||
"watch:app": "yarn run clear:shadow-cache && concurrently \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
|
"watch": "exit 0",
|
||||||
"watch": "yarn run watch:app:assets",
|
"watch:app": "yarn run clear:shadow-cache && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
|
||||||
"watch:storybook": "yarn run build:storybook:assets && concurrently \"storybook dev -p 6006 --no-open\" \"yarn run watch:storybook:assets\"",
|
"watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\""
|
||||||
"watch:storybook:assets": "node ./scripts/watch-storybook.js"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.52.0",
|
"@penpot/draft-js": "portal:./packages/draft-js",
|
||||||
"@storybook/addon-docs": "10.0.4",
|
"@penpot/mousetrap": "portal:./packages/mousetrap",
|
||||||
"@storybook/addon-themes": "10.0.4",
|
"@penpot/plugins-runtime": "1.3.2",
|
||||||
"@storybook/addon-vitest": "10.0.4",
|
"@penpot/svgo": "penpot/svgo#v3.2",
|
||||||
"@storybook/react-vite": "10.0.4",
|
"@penpot/text-editor": "portal:./text-editor",
|
||||||
"@types/node": "^22.15.21",
|
"@playwright/test": "1.57.0",
|
||||||
"@vitest/browser": "3.2.4",
|
"@storybook/addon-docs": "10.1.11",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@storybook/addon-themes": "10.1.11",
|
||||||
|
"@storybook/addon-vitest": "10.1.11",
|
||||||
|
"@storybook/react-vite": "10.1.11",
|
||||||
|
"@tokens-studio/sd-transforms": "1.2.11",
|
||||||
|
"@types/node": "^22.19.3",
|
||||||
|
"@vitest/browser": "4.0.16",
|
||||||
|
"@vitest/browser-playwright": "^4.0.16",
|
||||||
|
"@vitest/coverage-v8": "4.0.16",
|
||||||
|
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
|
"compression": "^1.8.1",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"esbuild": "^0.25.9",
|
"esbuild": "^0.25.9",
|
||||||
|
"eventsource-parser": "^3.0.6",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"fancy-log": "^2.0.0",
|
"fancy-log": "^2.0.0",
|
||||||
"getopts": "^2.3.0",
|
"getopts": "^2.3.0",
|
||||||
"gettext-parser": "^8.0.0",
|
"gettext-parser": "^8.0.0",
|
||||||
"gulp-concat": "^2.6.1",
|
"highlight.js": "^11.10.0",
|
||||||
"gulp-gzip": "^1.4.2",
|
"js-beautify": "^1.15.4",
|
||||||
"gulp-mustache": "^5.0.0",
|
"jsdom": "^27.4.0",
|
||||||
"gulp-postcss": "^10.0.0",
|
"lodash": "^4.17.21",
|
||||||
"gulp-rename": "^2.0.0",
|
"lodash.debounce": "^4.0.8",
|
||||||
"gulp-sourcemaps": "^3.0.0",
|
|
||||||
"gulp-svg-sprite": "^2.0.3",
|
|
||||||
"jsdom": "^27.0.0",
|
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"marked": "^15.0.12",
|
"marked": "^15.0.12",
|
||||||
"mkdirp": "^3.0.1",
|
"mkdirp": "^3.0.1",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
"opentype.js": "^1.3.4",
|
||||||
"p-limit": "^6.2.0",
|
"p-limit": "^6.2.0",
|
||||||
"playwright": "1.56.1",
|
"playwright": "1.56.1",
|
||||||
"postcss": "^8.5.4",
|
"postcss": "^8.5.4",
|
||||||
"postcss-clean": "^1.2.2",
|
"postcss-clean": "^1.2.2",
|
||||||
|
"postcss-modules": "^6.0.1",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
"pretty-time": "^1.1.0",
|
"pretty-time": "^1.1.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"rimraf": "^6.0.1",
|
|
||||||
"sass": "^1.89.0",
|
|
||||||
"sass-embedded": "^1.89.0",
|
|
||||||
"storybook": "10.0.4",
|
|
||||||
"svg-sprite": "^2.0.4",
|
|
||||||
"typescript": "^5.9.2",
|
|
||||||
"vite": "^6.3.5",
|
|
||||||
"vitest": "^3.2.0",
|
|
||||||
"wasm-pack": "^0.13.1",
|
|
||||||
"watcher": "^2.3.1",
|
|
||||||
"workerpool": "^9.3.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@penpot/draft-js": "portal:./vendor/draft-js",
|
|
||||||
"@penpot/hljs": "portal:./vendor/hljs",
|
|
||||||
"@penpot/mousetrap": "portal:./vendor/mousetrap",
|
|
||||||
"@penpot/plugins-runtime": "1.3.2",
|
|
||||||
"@penpot/svgo": "penpot/svgo#v3.2",
|
|
||||||
"@penpot/text-editor": "portal:./text-editor",
|
|
||||||
"@tokens-studio/sd-transforms": "1.2.11",
|
|
||||||
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
|
|
||||||
"compression": "^1.8.1",
|
|
||||||
"date-fns": "^4.1.0",
|
|
||||||
"eventsource-parser": "^3.0.6",
|
|
||||||
"js-beautify": "^1.15.4",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"lodash.debounce": "^4.0.8",
|
|
||||||
"opentype.js": "^1.3.4",
|
|
||||||
"postcss-modules": "^6.0.1",
|
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"react-error-boundary": "^6.0.0",
|
"react-error-boundary": "^6.0.0",
|
||||||
"react-virtualized": "^9.22.6",
|
"react-virtualized": "^9.22.6",
|
||||||
|
"rimraf": "^6.0.1",
|
||||||
"rxjs": "8.0.0-alpha.14",
|
"rxjs": "8.0.0-alpha.14",
|
||||||
|
"sass": "^1.89.0",
|
||||||
|
"sass-embedded": "^1.89.0",
|
||||||
"sax": "^1.4.1",
|
"sax": "^1.4.1",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
|
"storybook": "10.1.11",
|
||||||
"style-dictionary": "5.0.0-rc.1",
|
"style-dictionary": "5.0.0-rc.1",
|
||||||
|
"svg-sprite": "^2.0.4",
|
||||||
"tdigest": "^0.1.2",
|
"tdigest": "^0.1.2",
|
||||||
"tinycolor2": "^1.6.0",
|
"tinycolor2": "^1.6.0",
|
||||||
|
"typescript": "^5.9.2",
|
||||||
"ua-parser-js": "2.0.5",
|
"ua-parser-js": "2.0.5",
|
||||||
|
"vite": "^7.3.0",
|
||||||
|
"vitest": "^4.0.16",
|
||||||
|
"wait-on": "^9.0.3",
|
||||||
|
"wasm-pack": "^0.13.1",
|
||||||
|
"watcher": "^2.3.1",
|
||||||
|
"workerpool": "^9.3.2",
|
||||||
"xregexp": "^5.1.2"
|
"xregexp": "^5.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export const {
|
|||||||
RichTextEditorUtil,
|
RichTextEditorUtil,
|
||||||
SelectionState,
|
SelectionState,
|
||||||
convertFromRaw,
|
convertFromRaw,
|
||||||
convertToRaw
|
convertToRaw,
|
||||||
|
EditorBlock,
|
||||||
|
Editor
|
||||||
} = pkg;
|
} = pkg;
|
||||||
|
|
||||||
import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor.js';
|
import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor.js';
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
"author": "Andrey Antukh",
|
"author": "Andrey Antukh",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
|
"draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0",
|
||||||
|
"immutable": "^5.1.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=0.17.0",
|
"react": ">=0.17.0",
|
||||||
@@ -173,12 +173,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@penpot/draft-js-wrapper@workspace:.":
|
"@penpot/draft-js@workspace:.":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@penpot/draft-js-wrapper@workspace:."
|
resolution: "@penpot/draft-js@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
draft-js: "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
|
draft-js: "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
|
||||||
esbuild: "npm:^0.24.0"
|
esbuild: "npm:^0.24.0"
|
||||||
|
immutable: "npm:^5.1.4"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ">=0.17.0"
|
react: ">=0.17.0"
|
||||||
react-dom: ">=0.17.0"
|
react-dom: ">=0.17.0"
|
||||||
@@ -320,6 +321,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"immutable@npm:^5.1.4":
|
||||||
|
version: 5.1.4
|
||||||
|
resolution: "immutable@npm:5.1.4"
|
||||||
|
checksum: 10c0/f1c98382e4cde14a0b218be3b9b2f8441888da8df3b8c064aa756071da55fbed6ad696e5959982508456332419be9fdeaf29b2e58d0eadc45483cc16963c0446
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"immutable@npm:~3.7.4":
|
"immutable@npm:~3.7.4":
|
||||||
version: 3.7.6
|
version: 3.7.6
|
||||||
resolution: "immutable@npm:3.7.6"
|
resolution: "immutable@npm:3.7.6"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"~:id": "~uc7ce0794-0992-8105-8004-38e630f41234",
|
||||||
|
"~:revn": 1,
|
||||||
|
"~:vern": 1,
|
||||||
|
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||||
|
"~:created-at": "~m1705307400000",
|
||||||
|
"~:modified-at": "~m1732111500000",
|
||||||
|
"~:deleted-at": "~m1732111500000",
|
||||||
|
"~:name": "Deleted Design File 1",
|
||||||
|
"~:is-shared": false,
|
||||||
|
"~:will-be-deleted-at": "~m1732716300000",
|
||||||
|
"~:thumbnail-id": null,
|
||||||
|
"~:row-num": 1,
|
||||||
|
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:id": "~uc7ce0794-0992-8105-8004-38e630f41235",
|
||||||
|
"~:revn": 2,
|
||||||
|
"~:vern": 2,
|
||||||
|
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||||
|
"~:created-at": "~m1704875700000",
|
||||||
|
"~:modified-at": "~m1732025400000",
|
||||||
|
"~:deleted-at": "~m1732025400000",
|
||||||
|
"~:name": "Deleted Design File 2",
|
||||||
|
"~:is-shared": true,
|
||||||
|
"~:will-be-deleted-at": "~m1732630200000",
|
||||||
|
"~:thumbnail-id": null,
|
||||||
|
"~:row-num": 2,
|
||||||
|
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:id": "~uc7ce0794-0992-8105-8004-38e630f41236",
|
||||||
|
"~:revn": 3,
|
||||||
|
"~:vern": 3,
|
||||||
|
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920c",
|
||||||
|
"~:created-at": "~m1706792400000",
|
||||||
|
"~:modified-at": "~m1731939600000",
|
||||||
|
"~:deleted-at": "~m1731939600000",
|
||||||
|
"~:name": "Old Project Design",
|
||||||
|
"~:is-shared": false,
|
||||||
|
"~:will-be-deleted-at": "~m1732544400000",
|
||||||
|
"~:thumbnail-id": null,
|
||||||
|
"~:row-num": 3,
|
||||||
|
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -106,6 +106,13 @@ export class DashboardPage extends BaseWebSocketPage {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setupDeletedFiles() {
|
||||||
|
await this.mockRPC(
|
||||||
|
"get-team-deleted-files?team-id=*",
|
||||||
|
"dashboard/get-team-deleted-files.json",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async setupDrafts() {
|
async setupDrafts() {
|
||||||
await this.mockRPC(
|
await this.mockRPC(
|
||||||
"get-project-files?project-id=*",
|
"get-project-files?project-id=*",
|
||||||
@@ -160,6 +167,10 @@ export class DashboardPage extends BaseWebSocketPage {
|
|||||||
});
|
});
|
||||||
await this.mockRPC("search-files", "dashboard/search-files.json");
|
await this.mockRPC("search-files", "dashboard/search-files.json");
|
||||||
await this.mockRPC("get-teams", "logged-in-user/get-teams-complete.json");
|
await this.mockRPC("get-teams", "logged-in-user/get-teams-complete.json");
|
||||||
|
await this.mockRPC(
|
||||||
|
"get-team-deleted-files?team-id=*",
|
||||||
|
"dashboard/get-team-deleted-files.json",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setupAccessTokensEmpty() {
|
async setupAccessTokensEmpty() {
|
||||||
@@ -289,6 +300,13 @@ export class DashboardPage extends BaseWebSocketPage {
|
|||||||
await expect(this.mainHeading).toHaveText("Libraries");
|
await expect(this.mainHeading).toHaveText("Libraries");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async goToDeleted() {
|
||||||
|
await this.page.goto(
|
||||||
|
`#/dashboard/deleted?team-id=${DashboardPage.anyTeamId}`,
|
||||||
|
);
|
||||||
|
await expect(this.mainHeading).toHaveText("Projects");
|
||||||
|
}
|
||||||
|
|
||||||
async openProfileMenu() {
|
async openProfileMenu() {
|
||||||
await this.userAccount.click();
|
await this.userAccount.click();
|
||||||
}
|
}
|
||||||
|
|||||||
31
frontend/playwright/ui/specs/dashboard-deleted.spec.js
Normal file
31
frontend/playwright/ui/specs/dashboard-deleted.spec.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import DashboardPage from "../pages/DashboardPage";
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await DashboardPage.init(page);
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-profile",
|
||||||
|
"logged-in-user/get-profile-logged-in-no-onboarding.json",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Dashboard Deleted Page", () => {
|
||||||
|
test("User can navigate to deleted page", async ({ page }) => {
|
||||||
|
const dashboardPage = new DashboardPage(page);
|
||||||
|
|
||||||
|
// Setup mock for deleted files API
|
||||||
|
await dashboardPage.setupDeletedFiles();
|
||||||
|
|
||||||
|
// Navigate directly to deleted page
|
||||||
|
await dashboardPage.goToDeleted();
|
||||||
|
|
||||||
|
// Check for the restore all and clear trash buttons
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Restore All" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Clear trash" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -305,7 +305,7 @@ test.describe("Inspect tab - Styles", () => {
|
|||||||
);
|
);
|
||||||
await openInspectTab(workspacePage);
|
await openInspectTab(workspacePage);
|
||||||
|
|
||||||
const panel = await getPanelByTitle(workspacePage, "Size & position");
|
const panel = await getPanelByTitle(workspacePage, "Size and position");
|
||||||
await expect(panel).toBeVisible();
|
await expect(panel).toBeVisible();
|
||||||
|
|
||||||
const propertyRow = panel.getByTestId("property-row");
|
const propertyRow = panel.getByTestId("property-row");
|
||||||
@@ -335,7 +335,7 @@ test.describe("Inspect tab - Styles", () => {
|
|||||||
);
|
);
|
||||||
await openInspectTab(workspacePage);
|
await openInspectTab(workspacePage);
|
||||||
|
|
||||||
const panel = await getPanelByTitle(workspacePage, "Size & position");
|
const panel = await getPanelByTitle(workspacePage, "Size and position");
|
||||||
await expect(panel).toBeVisible();
|
await expect(panel).toBeVisible();
|
||||||
|
|
||||||
const propertyRow = panel.getByTestId("property-row");
|
const propertyRow = panel.getByTestId("property-row");
|
||||||
@@ -375,7 +375,7 @@ test.describe("Inspect tab - Styles", () => {
|
|||||||
);
|
);
|
||||||
await openInspectTab(workspacePage);
|
await openInspectTab(workspacePage);
|
||||||
|
|
||||||
const panel = await getPanelByTitle(workspacePage, "Size & position");
|
const panel = await getPanelByTitle(workspacePage, "Size and position");
|
||||||
await expect(panel).toBeVisible();
|
await expect(panel).toBeVisible();
|
||||||
|
|
||||||
const propertyRow = panel.getByTestId("property-row");
|
const propertyRow = panel.getByTestId("property-row");
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
|
|
||||||
<link rel="icon" href="images/favicon.png" />
|
<link rel="icon" href="images/favicon.png" />
|
||||||
|
|
||||||
|
<script type="importmap">{{& manifest.importmap }}</script>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
globalThis.penpotVersion = "{{& version}}";
|
globalThis.penpotVersion = "{{& version}}";
|
||||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||||
@@ -33,7 +35,6 @@
|
|||||||
{{# manifest}}
|
{{# manifest}}
|
||||||
<script src="{{& config}}"></script>
|
<script src="{{& config}}"></script>
|
||||||
<script src="{{& polyfills}}"></script>
|
<script src="{{& polyfills}}"></script>
|
||||||
<script type="importmap">{{& importmap }}</script>
|
|
||||||
{{/manifest}}
|
{{/manifest}}
|
||||||
|
|
||||||
<!--cookie-consent-->
|
<!--cookie-consent-->
|
||||||
@@ -49,7 +50,9 @@
|
|||||||
<script type="module" src="{{& libs}}"></script>
|
<script type="module" src="{{& libs}}"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { init } from "{{& app_main}}";
|
import { init } from "{{& app_main}}";
|
||||||
init();
|
import defaultTranslations from "{{& default_translations}}";
|
||||||
|
|
||||||
|
init({defaultTranslations});
|
||||||
</script>
|
</script>
|
||||||
{{/manifest}}
|
{{/manifest}}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function isJsFile(path) {
|
|||||||
export async function compileSass(worker, path, options) {
|
export async function compileSass(worker, path, options) {
|
||||||
path = ph.resolve(path);
|
path = ph.resolve(path);
|
||||||
|
|
||||||
log.info("compile:", path);
|
// log.info("compile:", path);
|
||||||
return worker.exec("compileSass", [path, options]);
|
return worker.exec("compileSass", [path, options]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ async function readManifestFile(resource) {
|
|||||||
return JSON.parse(content);
|
return JSON.parse(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readShadowManifest() {
|
async function generateManifest() {
|
||||||
const index = {
|
const index = {
|
||||||
app_main: "./js/main.js",
|
app_main: "./js/main.js",
|
||||||
render_main: "./js/render.js",
|
render_main: "./js/render.js",
|
||||||
@@ -197,6 +197,7 @@ async function readShadowManifest() {
|
|||||||
polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION,
|
polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION,
|
||||||
libs: "./js/libs.js?version=" + CURRENT_VERSION,
|
libs: "./js/libs.js?version=" + CURRENT_VERSION,
|
||||||
worker_main: "./js/worker/main.js?version=" + CURRENT_VERSION,
|
worker_main: "./js/worker/main.js?version=" + CURRENT_VERSION,
|
||||||
|
default_translations: "./js/translation.en.js?version=" + CURRENT_VERSION,
|
||||||
|
|
||||||
importmap: JSON.stringify({
|
importmap: JSON.stringify({
|
||||||
"imports": {
|
"imports": {
|
||||||
@@ -276,6 +277,7 @@ export async function compileTranslations() {
|
|||||||
"id",
|
"id",
|
||||||
"ru",
|
"ru",
|
||||||
"tr",
|
"tr",
|
||||||
|
"hi",
|
||||||
"zh_CN",
|
"zh_CN",
|
||||||
"zh_Hant",
|
"zh_Hant",
|
||||||
"hr",
|
"hr",
|
||||||
@@ -391,7 +393,7 @@ async function generateTemplates() {
|
|||||||
const isDebug = process.env.NODE_ENV !== "production";
|
const isDebug = process.env.NODE_ENV !== "production";
|
||||||
await fs.mkdir("./resources/public/", { recursive: true });
|
await fs.mkdir("./resources/public/", { recursive: true });
|
||||||
|
|
||||||
const manifest = await readShadowManifest();
|
const manifest = await generateManifest();
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
const iconsSprite = await fs.readFile(
|
const iconsSprite = await fs.readFile(
|
||||||
|
|||||||
6
frontend/scripts/setup
Executable file
6
frontend/scripts/setup
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
corepack enable;
|
||||||
|
corepack install;
|
||||||
|
yarn install;
|
||||||
|
yarn playwright install chromium;
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(dirname $0);
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
yarn install;
|
|
||||||
|
|
||||||
yarn run playwright install chromium --with-deps;
|
$SCRIPT_DIR/setup;
|
||||||
|
|
||||||
yarn run build:storybook
|
yarn run build:storybook
|
||||||
|
yarn run test:storybook
|
||||||
exec npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
|
|
||||||
"npx http-server storybook-static --port 6006 --silent" \
|
|
||||||
"npx wait-on tcp:6006 && yarn test:storybook"
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(dirname $0);
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
$SCRIPT_DIR/setup;
|
||||||
yarn install;
|
|
||||||
yarn run playwright install chromium --with-deps;
|
|
||||||
yarn run test:e2e -x --workers=2 --reporter=list "$@";
|
yarn run test:e2e -x --workers=2 --reporter=list "$@";
|
||||||
|
|||||||
7
frontend/scripts/watch
Executable file
7
frontend/scripts/watch
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
TARGET=${1:-app};
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
exec yarn run watch:$TARGET
|
||||||
@@ -121,24 +121,22 @@
|
|||||||
:storybook
|
:storybook
|
||||||
{:target :esm
|
{:target :esm
|
||||||
:output-dir "target/storybook/"
|
:output-dir "target/storybook/"
|
||||||
:devtools {:enabled false}
|
:devtools {:enabled false
|
||||||
|
:console-support false}
|
||||||
:js-options
|
:js-options
|
||||||
{:js-provider :import
|
{:js-provider :import
|
||||||
:entry-keys ["module" "browser" "main"]
|
:entry-keys ["module" "browser" "main"]
|
||||||
:export-conditions ["module" "import", "browser" "require" "default"]}
|
:export-conditions ["module" "import", "browser" "require" "default"]}
|
||||||
|
|
||||||
:modules
|
:modules
|
||||||
{:base
|
{:components
|
||||||
{:entries []}
|
|
||||||
|
|
||||||
:components
|
|
||||||
{:exports {default app.main.ui.ds/default
|
{:exports {default app.main.ui.ds/default
|
||||||
helpers app.main.ui.ds.helpers/default}
|
helpers app.main.ui.ds.helpers/default}
|
||||||
:prepend-js ";(globalThis.goog.provide = globalThis.goog.constructNamespace_);(globalThis.goog.require = globalThis.goog.module.get);"
|
:prepend-js ";(globalThis.goog.provide = globalThis.goog.constructNamespace_);(globalThis.goog.require = globalThis.goog.module.get);"
|
||||||
:depends-on #{:base}}}
|
:depends-on #{}}}
|
||||||
|
|
||||||
:compiler-options
|
:compiler-options
|
||||||
{:output-feature-set :es2020
|
{:output-feature-set :es-next
|
||||||
:output-wrapper false
|
:output-wrapper false
|
||||||
:warnings {:fn-deprecated false}}}
|
:warnings {:fn-deprecated false}}}
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,10 @@
|
|||||||
(rx/map #(ws/initialize)))))))
|
(rx/map #(ws/initialize)))))))
|
||||||
|
|
||||||
(defn ^:export init
|
(defn ^:export init
|
||||||
[]
|
[options]
|
||||||
|
(some-> (unchecked-get options "defaultTranslations")
|
||||||
|
(i18n/set-default-translations))
|
||||||
|
|
||||||
(mw/init!)
|
(mw/init!)
|
||||||
(i18n/init)
|
(i18n/init)
|
||||||
(cur/init-styles)
|
(cur/init-styles)
|
||||||
|
|||||||
@@ -302,3 +302,9 @@
|
|||||||
:height 720}])
|
:height 720}])
|
||||||
|
|
||||||
(def max-input-length 255)
|
(def max-input-length 255)
|
||||||
|
|
||||||
|
(def ^:const default-slow-progress-threshold
|
||||||
|
"A constant value that represents a threshold in milliseconds when a
|
||||||
|
normal progress becomes tagged as slow if no event received in the
|
||||||
|
specified amount of time"
|
||||||
|
1000)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
|
[app.common.time :as ct]
|
||||||
[app.common.types.team :as ctt]
|
[app.common.types.team :as ctt]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
@@ -229,6 +230,91 @@
|
|||||||
;; Delay so the navigation can finish
|
;; Delay so the navigation can finish
|
||||||
(rx/delay 250))))))))
|
(rx/delay 250))))))))
|
||||||
|
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; PROGRESS EVENTS
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(def noop-fn
|
||||||
|
(constantly nil))
|
||||||
|
|
||||||
|
(def ^:private schema:progress-params
|
||||||
|
[:map {:title "Progress"}
|
||||||
|
[:key {:optional true} ::sm/text]
|
||||||
|
[:index {:optional true} ::sm/int]
|
||||||
|
[:total ::sm/int]
|
||||||
|
[:hints
|
||||||
|
[:map-of :keyword fn?]]
|
||||||
|
[:slow-progress-threshold {:optional true} ::sm/int]])
|
||||||
|
|
||||||
|
(def ^:private check-progress-params
|
||||||
|
(sm/check-fn schema:progress-params))
|
||||||
|
|
||||||
|
(defn initialize-progress
|
||||||
|
[& {:keys [key index total hints slow-progress-threshold] :as params}]
|
||||||
|
|
||||||
|
(assert (check-progress-params params))
|
||||||
|
|
||||||
|
(ptk/reify ::initialize-progress
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update state :progress
|
||||||
|
(fn [_]
|
||||||
|
(let [hint ((:normal hints noop-fn) params)]
|
||||||
|
{:threshold (or slow-progress-threshold 5000)
|
||||||
|
:key key
|
||||||
|
:last-update (ct/now)
|
||||||
|
:healthy true
|
||||||
|
:visible true
|
||||||
|
:hints hints
|
||||||
|
:progress (d/nilv index 0)
|
||||||
|
:total total
|
||||||
|
:hint hint}))))))
|
||||||
|
|
||||||
|
(defn update-progress
|
||||||
|
[{:keys [index total] :as params}]
|
||||||
|
|
||||||
|
(assert (check-progress-params params))
|
||||||
|
|
||||||
|
(ptk/reify ::update-progress
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update state :progress
|
||||||
|
(fn [state]
|
||||||
|
(let [last-update (get state :last-update)
|
||||||
|
hints (get state :hints)
|
||||||
|
threshold (get state :slow-progress-threshold)
|
||||||
|
|
||||||
|
time-diff (ct/diff-ms last-update (ct/now))
|
||||||
|
healthy? (< time-diff threshold)
|
||||||
|
|
||||||
|
hint (if healthy?
|
||||||
|
((:normal hints noop-fn) params)
|
||||||
|
((:slow hints noop-fn) params))]
|
||||||
|
|
||||||
|
(-> state
|
||||||
|
(assoc :progress index)
|
||||||
|
(assoc :total total)
|
||||||
|
(assoc :last-update (ct/now))
|
||||||
|
(assoc :healthy healthy?)
|
||||||
|
(assoc :hint hint))))))))
|
||||||
|
|
||||||
|
(defn toggle-progress-visibility
|
||||||
|
[]
|
||||||
|
(ptk/reify ::toggle-progress-visibility
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update state :progress
|
||||||
|
(fn [state]
|
||||||
|
(update state :visible not))))))
|
||||||
|
|
||||||
|
(defn clear-progress
|
||||||
|
[]
|
||||||
|
(ptk/reify ::clear-progress
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(dissoc state :progress))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; NAVEGATION EVENTS
|
;; NAVEGATION EVENTS
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -386,3 +472,21 @@
|
|||||||
(rx/of ::dps/force-persist
|
(rx/of ::dps/force-persist
|
||||||
(rt/nav :viewer params options))))))
|
(rt/nav :viewer params options))))))
|
||||||
|
|
||||||
|
(defn go-to-dashboard-deleted
|
||||||
|
[& {:keys [team-id] :as options}]
|
||||||
|
(ptk/reify ::go-to-dashboard-deleted
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [profile (get state :profile)
|
||||||
|
team-id (cond
|
||||||
|
(= :default team-id)
|
||||||
|
(:default-team-id profile)
|
||||||
|
|
||||||
|
(uuid? team-id)
|
||||||
|
team-id
|
||||||
|
|
||||||
|
:else
|
||||||
|
(:current-team-id state))
|
||||||
|
params {:team-id team-id}]
|
||||||
|
(rx/of (modal/hide)
|
||||||
|
(rt/nav :dashboard-deleted params options))))))
|
||||||
|
|||||||
@@ -13,14 +13,18 @@
|
|||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
|
[app.common.types.project :refer [valid-project?]]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
[app.main.constants :as mconst]
|
||||||
[app.main.data.common :as dcm]
|
[app.main.data.common :as dcm]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.fonts :as df]
|
[app.main.data.fonts :as df]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
|
[app.main.data.notifications :as ntf]
|
||||||
[app.main.data.websocket :as dws]
|
[app.main.data.websocket :as dws]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
|
[app.main.store :as st]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
[app.util.sse :as sse]
|
[app.util.sse :as sse]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
@@ -76,7 +80,8 @@
|
|||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(reduce (fn [state {:keys [id] :as project}]
|
(reduce (fn [state {:keys [id] :as project}]
|
||||||
(update-in state [:projects id] merge project))
|
;; Replace completely instead of merge to ensure deleted-at is removed
|
||||||
|
(assoc-in state [:projects id] project))
|
||||||
state
|
state
|
||||||
projects))))
|
projects))))
|
||||||
|
|
||||||
@@ -152,6 +157,34 @@
|
|||||||
(->> (rp/cmd! :get-builtin-templates)
|
(->> (rp/cmd! :get-builtin-templates)
|
||||||
(rx/map builtin-templates-fetched)))))
|
(rx/map builtin-templates-fetched)))))
|
||||||
|
|
||||||
|
;; --- EVENT: deleted-files
|
||||||
|
|
||||||
|
(defn- deleted-files-fetched
|
||||||
|
[files]
|
||||||
|
(ptk/reify ::deleted-files-fetched
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [now (ct/now)
|
||||||
|
filtered-files (filterv (fn [file]
|
||||||
|
(let [will-be-deleted-at (:will-be-deleted-at file)]
|
||||||
|
(or (nil? will-be-deleted-at)
|
||||||
|
(ct/is-after? will-be-deleted-at now))))
|
||||||
|
files)
|
||||||
|
files (d/index-by :id filtered-files)]
|
||||||
|
(-> state
|
||||||
|
(assoc :deleted-files files)
|
||||||
|
(update :files d/merge files))))))
|
||||||
|
|
||||||
|
(defn fetch-deleted-files
|
||||||
|
([] (fetch-deleted-files nil))
|
||||||
|
([team-id]
|
||||||
|
(ptk/reify ::fetch-deleted-files
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(when-let [team-id (or team-id (:current-team-id state))]
|
||||||
|
(->> (rp/cmd! :get-team-deleted-files {:team-id team-id})
|
||||||
|
(rx/map deleted-files-fetched)))))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Data Selection
|
;; Data Selection
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -460,6 +493,7 @@
|
|||||||
(-> state
|
(-> state
|
||||||
(d/update-in-when [:files file-id] assoc :thumbnail-id thumbnail-id)
|
(d/update-in-when [:files file-id] assoc :thumbnail-id thumbnail-id)
|
||||||
(d/update-in-when [:recent-files file-id] assoc :thumbnail-id thumbnail-id)
|
(d/update-in-when [:recent-files file-id] assoc :thumbnail-id thumbnail-id)
|
||||||
|
(d/update-in-when [:deleted-files file-id] assoc :thumbnail-id thumbnail-id)
|
||||||
(d/update-when :dashboard-search-result update-search-files))))))
|
(d/update-when :dashboard-search-result update-search-files))))))
|
||||||
|
|
||||||
;; --- EVENT: create-file
|
;; --- EVENT: create-file
|
||||||
@@ -656,3 +690,251 @@
|
|||||||
:team-role-change (handle-change-team-role msg)
|
:team-role-change (handle-change-team-role msg)
|
||||||
:team-membership-change (dcm/team-membership-change msg)
|
:team-membership-change (dcm/team-membership-change msg)
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Delete files immediately
|
||||||
|
|
||||||
|
(defn- delete-files
|
||||||
|
[{:keys [team-id ids on-success on-error]}]
|
||||||
|
(assert (uuid? team-id))
|
||||||
|
(assert (set? ids))
|
||||||
|
(assert (every? uuid? ids))
|
||||||
|
(assert (fn? on-success))
|
||||||
|
(assert (fn? on-error))
|
||||||
|
|
||||||
|
(ptk/reify ::delete-files
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ _ _]
|
||||||
|
(let [progress-hint #(tr "dashboard.progress-notification.deleting-files")
|
||||||
|
slow-hint #(tr "dashboard.progress-notification.slow-delete")
|
||||||
|
stream (->> (rp/cmd! ::sse/permanently-delete-team-files {:team-id team-id :ids ids})
|
||||||
|
(rx/share))]
|
||||||
|
(rx/merge
|
||||||
|
(rx/of (dcm/initialize-progress
|
||||||
|
{:slow-progress-threshold
|
||||||
|
mconst/default-slow-progress-threshold
|
||||||
|
:total (count ids)
|
||||||
|
:hints {:progress progress-hint
|
||||||
|
:slow slow-hint}}))
|
||||||
|
|
||||||
|
(->> stream
|
||||||
|
(rx/filter sse/progress?)
|
||||||
|
(rx/mapcat (fn [event]
|
||||||
|
(if-let [payload (sse/get-payload event)]
|
||||||
|
(let [{:keys [index total]} payload]
|
||||||
|
(if (and index total)
|
||||||
|
(rx/of (dcm/update-progress {:index index :total total}))
|
||||||
|
(rx/empty)))
|
||||||
|
(rx/empty))))
|
||||||
|
(rx/catch rx/empty))
|
||||||
|
|
||||||
|
(->> stream
|
||||||
|
(rx/filter sse/end-of-stream?)
|
||||||
|
(rx/map sse/get-payload)
|
||||||
|
(rx/merge-map (fn [_]
|
||||||
|
(rx/concat
|
||||||
|
(rx/of (dcm/clear-progress)
|
||||||
|
(fetch-projects team-id)
|
||||||
|
(fetch-deleted-files team-id)
|
||||||
|
(fetch-projects team-id))
|
||||||
|
(on-success))))
|
||||||
|
|
||||||
|
(rx/catch (fn [error]
|
||||||
|
(rx/concat
|
||||||
|
(rx/of (dcm/clear-progress))
|
||||||
|
(on-error error))))))))))
|
||||||
|
|
||||||
|
(defn delete-files-immediately
|
||||||
|
[{:keys [team-id ids] :as params}]
|
||||||
|
(assert (uuid? team-id))
|
||||||
|
(assert (set? ids))
|
||||||
|
(assert (every? uuid? ids))
|
||||||
|
|
||||||
|
(ptk/reify ::delete-files-immediately
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [deleted-files
|
||||||
|
(get state :deleted-files)
|
||||||
|
|
||||||
|
on-success
|
||||||
|
(fn []
|
||||||
|
(if (= 1 (count ids))
|
||||||
|
(let [fname (get-in deleted-files [(first ids) :name])]
|
||||||
|
(rx/of (ntf/success (tr "dashboard.delete-success-notification" fname))))
|
||||||
|
(rx/of (ntf/success (tr "dashboard.delete-files-success-notification" (count ids))))))
|
||||||
|
|
||||||
|
on-error
|
||||||
|
#(rx/of (ntf/error (tr "dashboard.errors.error-on-delete-files")))]
|
||||||
|
|
||||||
|
(rx/of (ev/event
|
||||||
|
{::ev/name "delete-files"
|
||||||
|
::ev/origin "dashboard:trash"
|
||||||
|
:team-id team-id
|
||||||
|
:num-files (count ids)})
|
||||||
|
(delete-files
|
||||||
|
{:team-id team-id
|
||||||
|
:ids ids
|
||||||
|
:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn delete-project-immediately
|
||||||
|
[{:keys [team-id id name] :as project}]
|
||||||
|
(assert (valid-project? project))
|
||||||
|
|
||||||
|
(ptk/reify ::delete-project-immediately
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [ids
|
||||||
|
(reduce-kv (fn [acc file-id file]
|
||||||
|
(if (= (:project-id file) id)
|
||||||
|
(conj acc file-id)
|
||||||
|
acc))
|
||||||
|
#{}
|
||||||
|
(get state :deleted-files))
|
||||||
|
|
||||||
|
on-success
|
||||||
|
#(rx/of (ntf/success (tr "dashboard.delete-success-notification" name)))
|
||||||
|
|
||||||
|
on-error
|
||||||
|
#(rx/of (ntf/error (tr "dashboard.errors.error-on-delete-project" name)))]
|
||||||
|
|
||||||
|
(rx/of (ev/event
|
||||||
|
{::ev/name "delete-files"
|
||||||
|
::ev/origin "dashboard:trash"
|
||||||
|
:team-id team-id
|
||||||
|
:project-id id
|
||||||
|
:num-files (count ids)})
|
||||||
|
(delete-files
|
||||||
|
{:team-id team-id
|
||||||
|
:ids ids
|
||||||
|
:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Restore deleted files immediately
|
||||||
|
|
||||||
|
|
||||||
|
(defn- restore-files
|
||||||
|
[{:keys [team-id ids on-success on-error]}]
|
||||||
|
(assert (uuid? team-id))
|
||||||
|
(assert (set? ids))
|
||||||
|
(assert (every? uuid? ids))
|
||||||
|
(assert (fn? on-success))
|
||||||
|
(assert (fn? on-error))
|
||||||
|
|
||||||
|
(ptk/reify ::restore-files
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ _ _]
|
||||||
|
(let [progress-hint #(tr "dashboard.progress-notification.restoring-files")
|
||||||
|
slow-hint #(tr "dashboard.progress-notification.slow-restore")]
|
||||||
|
|
||||||
|
(rx/merge
|
||||||
|
(rx/of (dcm/initialize-progress
|
||||||
|
{:slow-progress-threshold
|
||||||
|
mconst/default-slow-progress-threshold
|
||||||
|
:total (count ids)
|
||||||
|
:hints {:progress progress-hint
|
||||||
|
:slow slow-hint}}))
|
||||||
|
|
||||||
|
(let [stream (->> (rp/cmd! ::sse/restore-deleted-team-files {:team-id team-id :ids ids})
|
||||||
|
(rx/share))]
|
||||||
|
|
||||||
|
(rx/merge
|
||||||
|
(->> stream
|
||||||
|
(rx/filter sse/progress?)
|
||||||
|
(rx/mapcat (fn [event]
|
||||||
|
(if-let [payload (sse/get-payload event)]
|
||||||
|
(let [{:keys [index total]} payload]
|
||||||
|
(if (and index total)
|
||||||
|
(rx/of (dcm/update-progress {:index index :total total}))
|
||||||
|
(rx/empty)))
|
||||||
|
(rx/empty))))
|
||||||
|
(rx/catch rx/empty))
|
||||||
|
|
||||||
|
(->> stream
|
||||||
|
(rx/filter sse/end-of-stream?)
|
||||||
|
(rx/map sse/get-payload)
|
||||||
|
(rx/mapcat (fn [_]
|
||||||
|
(rx/concat
|
||||||
|
(rx/of (dcm/clear-progress)
|
||||||
|
;; (ntf/success (tr "dashboard.restore-success-notification"))
|
||||||
|
(fetch-projects team-id)
|
||||||
|
(fetch-deleted-files team-id)
|
||||||
|
(fetch-projects team-id))
|
||||||
|
(on-success))))
|
||||||
|
(rx/catch (fn [error]
|
||||||
|
(rx/concat
|
||||||
|
(rx/of (dcm/clear-progress))
|
||||||
|
(on-error error))))))))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn restore-files-immediately
|
||||||
|
[{:keys [team-id ids]}]
|
||||||
|
(assert (uuid? team-id))
|
||||||
|
(assert (set? ids))
|
||||||
|
|
||||||
|
(ptk/reify ::restore-files-immediately
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [deleted-files
|
||||||
|
(get state :deleted-files)
|
||||||
|
|
||||||
|
on-success
|
||||||
|
(fn []
|
||||||
|
(if (= 1 (count ids))
|
||||||
|
(let [fname (get-in deleted-files [(first ids) :name])]
|
||||||
|
(rx/of (ntf/success (tr "dashboard.restore-success-notification" fname))))
|
||||||
|
(rx/of (ntf/success (tr "dashboard.restore-files-success-notification" (count ids))))))
|
||||||
|
|
||||||
|
on-error
|
||||||
|
(fn [_cause]
|
||||||
|
(if (= 1 (count ids))
|
||||||
|
(let [fname (get-in deleted-files [(first ids) :name])]
|
||||||
|
(rx/of (ntf/error (tr "dashboard.errors.error-on-restore-file" fname))))
|
||||||
|
(rx/of (ntf/error (tr "dashboard.errors.error-on-restore-files")))))]
|
||||||
|
|
||||||
|
(rx/of (ev/event
|
||||||
|
{::ev/name "restore-files"
|
||||||
|
::ev/origin "dashboard:trash"
|
||||||
|
:team-id team-id
|
||||||
|
:num-files (count ids)})
|
||||||
|
(restore-files
|
||||||
|
{:team-id team-id
|
||||||
|
:ids ids
|
||||||
|
:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|
||||||
|
(defn restore-project-immediately
|
||||||
|
[{:keys [team-id id name] :as project}]
|
||||||
|
(assert (valid-project? project))
|
||||||
|
|
||||||
|
(ptk/reify ::restore-project-immediately
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [ids
|
||||||
|
(reduce-kv (fn [acc file-id file]
|
||||||
|
(if (= (:project-id file) id)
|
||||||
|
(conj acc file-id)
|
||||||
|
acc))
|
||||||
|
#{}
|
||||||
|
(get state :deleted-files))
|
||||||
|
|
||||||
|
on-success
|
||||||
|
#(st/emit! (ntf/success (tr "dashboard.restore-success-notification" name)))
|
||||||
|
|
||||||
|
on-error
|
||||||
|
#(st/emit! (ntf/error (tr "dashboard.errors.error-on-restoring-project" name)))]
|
||||||
|
|
||||||
|
(rx/of (ev/event
|
||||||
|
{::ev/name "restore-files"
|
||||||
|
::ev/origin "dashboard:trash"
|
||||||
|
:team-id team-id
|
||||||
|
:project-id id
|
||||||
|
:num-files (count ids)})
|
||||||
|
(restore-files
|
||||||
|
{:team-id team-id
|
||||||
|
:ids ids
|
||||||
|
:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|||||||
@@ -98,9 +98,7 @@
|
|||||||
(def context
|
(def context
|
||||||
(atom (d/without-nils (collect-context))))
|
(atom (d/without-nils (collect-context))))
|
||||||
|
|
||||||
(add-watch i18n/state "events"
|
(add-watch i18n/locale "events" #(swap! context assoc :locale %4))
|
||||||
(fn [_ _ _ v]
|
|
||||||
(swap! context assoc :locale (get v :locale))))
|
|
||||||
|
|
||||||
;; --- EVENT TRANSLATION
|
;; --- EVENT TRANSLATION
|
||||||
|
|
||||||
|
|||||||
@@ -270,8 +270,12 @@
|
|||||||
(ptk/reify ::process-wasm-object
|
(ptk/reify ::process-wasm-object
|
||||||
ptk/EffectEvent
|
ptk/EffectEvent
|
||||||
(effect [_ state _]
|
(effect [_ state _]
|
||||||
(let [objects (dsh/lookup-page-objects state)]
|
(let [objects (dsh/lookup-page-objects state)
|
||||||
(wasm.api/process-object (get objects id))))))
|
shape (get objects id)]
|
||||||
|
;; Only process objects that exist in the current page
|
||||||
|
;; This prevents errors when processing changes from other pages
|
||||||
|
(when shape
|
||||||
|
(wasm.api/process-object shape))))))
|
||||||
|
|
||||||
(defn initialize-workspace
|
(defn initialize-workspace
|
||||||
[team-id file-id]
|
[team-id file-id]
|
||||||
@@ -428,10 +432,12 @@
|
|||||||
:val position-data
|
:val position-data
|
||||||
:ignore-touched true
|
:ignore-touched true
|
||||||
:ignore-geometry true}]})))]
|
:ignore-geometry true}]})))]
|
||||||
(dch/commit-changes
|
(when (d/not-empty? changes)
|
||||||
{:redo-changes changes :undo-changes []
|
(dch/commit-changes
|
||||||
:save-undo? false
|
{:redo-changes changes :undo-changes []
|
||||||
:tags #{:position-data}})))))))
|
:save-undo? false
|
||||||
|
:tags #{:position-data}})))))
|
||||||
|
(rx/take-until stoper-s))))
|
||||||
|
|
||||||
(->> stream
|
(->> stream
|
||||||
(rx/filter dch/commit?)
|
(rx/filter dch/commit?)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
[app.common.types.fills :as types.fills]
|
[app.common.types.fills :as types.fills]
|
||||||
[app.common.types.library :as ctl]
|
[app.common.types.library :as ctl]
|
||||||
[app.common.types.shape :as shp]
|
[app.common.types.shape :as shp]
|
||||||
[app.common.types.shape.shadow :refer [check-shadow]]
|
[app.common.types.shape.shadow :as types.shadow]
|
||||||
[app.common.types.text :as txt]
|
[app.common.types.text :as txt]
|
||||||
[app.main.broadcast :as mbc]
|
[app.main.broadcast :as mbc]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
@@ -406,30 +406,30 @@
|
|||||||
|
|
||||||
(defn change-shadow
|
(defn change-shadow
|
||||||
[ids attrs index]
|
[ids attrs index]
|
||||||
(ptk/reify ::change-shadow
|
(letfn [(update-shadow [shape]
|
||||||
ptk/WatchEvent
|
(let [;; If we try to set a gradient to a shadow (for
|
||||||
(watch [_ _ _]
|
;; example using the color selection from
|
||||||
(rx/of (dwsh/update-shapes
|
;; multiple shapes) let's use the first stop
|
||||||
ids
|
;; color
|
||||||
(fn [shape]
|
attrs (cond-> attrs
|
||||||
(let [;; If we try to set a gradient to a shadow (for
|
(:gradient attrs)
|
||||||
;; example using the color selection from
|
(-> (dm/get-in [:gradient :stops 0])
|
||||||
;; multiple shapes) let's use the first stop
|
(select-keys types.shadow/color-attrs)))
|
||||||
;; color
|
|
||||||
attrs (cond-> attrs
|
|
||||||
(:gradient attrs)
|
|
||||||
(dm/get-in [:gradient :stops 0]))
|
|
||||||
|
|
||||||
attrs' (-> (dm/get-in shape [:shadow index :color])
|
attrs' (-> (dm/get-in shape [:shadow index :color])
|
||||||
(merge attrs)
|
(merge attrs)
|
||||||
(d/without-nils))]
|
(d/without-nils))]
|
||||||
(assoc-in shape [:shadow index :color] attrs'))))))))
|
(assoc-in shape [:shadow index :color] attrs')))]
|
||||||
|
(ptk/reify ::change-shadow
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ _ _]
|
||||||
|
(rx/of (dwsh/update-shapes ids update-shadow))))))
|
||||||
|
|
||||||
(defn add-shadow
|
(defn add-shadow
|
||||||
[ids shadow]
|
[ids shadow]
|
||||||
|
|
||||||
(assert
|
(assert
|
||||||
(check-shadow shadow)
|
(types.shadow/check-shadow shadow)
|
||||||
"expected a valid shadow struct")
|
"expected a valid shadow struct")
|
||||||
|
|
||||||
(assert
|
(assert
|
||||||
@@ -1146,16 +1146,16 @@
|
|||||||
(defn- shadow->color-attr
|
(defn- shadow->color-attr
|
||||||
"Given a stroke map enriched with :shape-id, :index, and optionally
|
"Given a stroke map enriched with :shape-id, :index, and optionally
|
||||||
:has-token-applied / :token-name, returns a color attribute map.
|
:has-token-applied / :token-name, returns a color attribute map.
|
||||||
|
|
||||||
If :has-token-applied is true, adds token metadata to :attrs:
|
If :has-token-applied is true, adds token metadata to :attrs:
|
||||||
{:has-token-applied true
|
{:has-token-applied true
|
||||||
:token-name <token-name>}
|
:token-name <token-name>}
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- stroke: map with stroke info, including :shape-id and :index
|
- stroke: map with stroke info, including :shape-id and :index
|
||||||
- file-id: current file UUID
|
- file-id: current file UUID
|
||||||
- libraries: map of shared color libraries
|
- libraries: map of shared color libraries
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A map like:
|
A map like:
|
||||||
{:attrs {...color data...}
|
{:attrs {...color data...}
|
||||||
@@ -1260,12 +1260,12 @@
|
|||||||
will include extra attributes in its :attrs map:
|
will include extra attributes in its :attrs map:
|
||||||
{:has-token-applied true
|
{:has-token-applied true
|
||||||
:token-name <token-name>}
|
:token-name <token-name>}
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- shapes: vector of shape maps
|
- shapes: vector of shape maps
|
||||||
- file-id: current file UUID
|
- file-id: current file UUID
|
||||||
- libraries: map of shared color libraries
|
- libraries: map of shared color libraries
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A vector of color attribute maps with metadata for each shape."
|
A vector of color attribute maps with metadata for each shape."
|
||||||
[shapes file-id libraries]
|
[shapes file-id libraries]
|
||||||
|
|||||||
@@ -47,32 +47,31 @@
|
|||||||
(ptk/reify ::apply-content-modifiers
|
(ptk/reify ::apply-content-modifiers
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [it state _]
|
(watch [it state _]
|
||||||
(let [page-id (get state :current-page-id state)
|
(let [id (st/get-path-id state)
|
||||||
objects (dsh/lookup-page-objects state)
|
shape (st/get-path state)
|
||||||
|
|
||||||
id (st/get-path-id state)
|
|
||||||
|
|
||||||
shape
|
|
||||||
(st/get-path state)
|
|
||||||
|
|
||||||
content-modifiers
|
content-modifiers
|
||||||
(dm/get-in state [:workspace-local :edit-path id :content-modifiers])
|
(dm/get-in state [:workspace-local :edit-path id :content-modifiers])]
|
||||||
|
(if (or (nil? shape) (nil? content-modifiers))
|
||||||
|
(rx/of (dwe/clear-edition-mode))
|
||||||
|
(let [page-id (get state :current-page-id state)
|
||||||
|
objects (dsh/lookup-page-objects state)
|
||||||
|
|
||||||
content (get shape :content)
|
content (get shape :content)
|
||||||
new-content (path/apply-content-modifiers content content-modifiers)
|
new-content (path/apply-content-modifiers content content-modifiers)
|
||||||
|
|
||||||
old-points (path.segment/get-points content)
|
old-points (path.segment/get-points content)
|
||||||
new-points (path.segment/get-points new-content)
|
new-points (path.segment/get-points new-content)
|
||||||
point-change (->> (map hash-map old-points new-points) (reduce merge))]
|
point-change (->> (map hash-map old-points new-points) (reduce merge))]
|
||||||
|
|
||||||
(when (and (some? new-content) (some? shape))
|
(when (and (some? new-content) (some? shape))
|
||||||
(let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
|
(let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
|
||||||
(if (empty? new-content)
|
(if (empty? new-content)
|
||||||
(rx/of (dch/commit-changes changes)
|
(rx/of (dch/commit-changes changes)
|
||||||
(dwe/clear-edition-mode))
|
(dwe/clear-edition-mode))
|
||||||
(rx/of (dch/commit-changes changes)
|
(rx/of (dch/commit-changes changes)
|
||||||
(selection/update-selection point-change)
|
(selection/update-selection point-change)
|
||||||
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))))))
|
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))))))))
|
||||||
|
|
||||||
(defn modify-content-point
|
(defn modify-content-point
|
||||||
[content {dx :x dy :y} modifiers point]
|
[content {dx :x dy :y} modifiers point]
|
||||||
|
|||||||
@@ -554,7 +554,7 @@
|
|||||||
(when (features/active-feature? state "text-editor/v2")
|
(when (features/active-feature? state "text-editor/v2")
|
||||||
(let [instance (:workspace-editor state)
|
(let [instance (:workspace-editor state)
|
||||||
styles (some-> (editor.v2/getCurrentStyle instance)
|
styles (some-> (editor.v2/getCurrentStyle instance)
|
||||||
(styles/get-styles-from-style-declaration)
|
(styles/get-styles-from-style-declaration :removed-mixed true)
|
||||||
((comp update-node-fn migrate-node))
|
((comp update-node-fn migrate-node))
|
||||||
(styles/attrs->styles))]
|
(styles/attrs->styles))]
|
||||||
(editor.v2/applyStylesToSelection instance styles)))))))
|
(editor.v2/applyStylesToSelection instance styles)))))))
|
||||||
|
|||||||
@@ -238,12 +238,12 @@
|
|||||||
:always
|
:always
|
||||||
(ctm/resize scalev resize-origin shape-transform shape-transform-inverse)
|
(ctm/resize scalev resize-origin shape-transform shape-transform-inverse)
|
||||||
|
|
||||||
(and (ctl/any-layout-immediate-child? objects shape)
|
(and (or (ctl/any-layout-immediate-child? objects shape) (ctl/any-layout? shape))
|
||||||
(not= (:layout-item-h-sizing shape) :fix)
|
(not= (:layout-item-h-sizing shape) :fix)
|
||||||
^boolean change-width?)
|
^boolean change-width?)
|
||||||
(ctm/change-property :layout-item-h-sizing :fix)
|
(ctm/change-property :layout-item-h-sizing :fix)
|
||||||
|
|
||||||
(and (ctl/any-layout-immediate-child? objects shape)
|
(and (or (ctl/any-layout-immediate-child? objects shape) (ctl/any-layout? shape))
|
||||||
(not= (:layout-item-v-sizing shape) :fix)
|
(not= (:layout-item-v-sizing shape) :fix)
|
||||||
^boolean change-height?)
|
^boolean change-height?)
|
||||||
(ctm/change-property :layout-item-v-sizing :fix)
|
(ctm/change-property :layout-item-v-sizing :fix)
|
||||||
|
|||||||
@@ -636,3 +636,6 @@
|
|||||||
|
|
||||||
(def persistence-state
|
(def persistence-state
|
||||||
(l/derived (comp :status :persistence) st/state))
|
(l/derived (comp :status :persistence) st/state))
|
||||||
|
|
||||||
|
(def progress
|
||||||
|
(l/derived :progress st/state))
|
||||||
|
|||||||
@@ -87,6 +87,12 @@
|
|||||||
{:stream? true
|
{:stream? true
|
||||||
:form-data? true}
|
:form-data? true}
|
||||||
|
|
||||||
|
::sse/permanently-delete-team-files
|
||||||
|
{:stream? true}
|
||||||
|
|
||||||
|
::sse/restore-deleted-team-files
|
||||||
|
{:stream? true}
|
||||||
|
|
||||||
:export-binfile {:response-type :blob}
|
:export-binfile {:response-type :blob}
|
||||||
:retrieve-list-of-builtin-templates {:query-params :all}})
|
:retrieve-list-of-builtin-templates {:query-params :all}})
|
||||||
|
|
||||||
|
|||||||
@@ -224,7 +224,8 @@
|
|||||||
:dashboard-members
|
:dashboard-members
|
||||||
:dashboard-invitations
|
:dashboard-invitations
|
||||||
:dashboard-webhooks
|
:dashboard-webhooks
|
||||||
:dashboard-settings)
|
:dashboard-settings
|
||||||
|
:dashboard-deleted)
|
||||||
(let [params (get params :query)
|
(let [params (get params :query)
|
||||||
team-id (some-> params :team-id uuid/parse*)
|
team-id (some-> params :team-id uuid/parse*)
|
||||||
project-id (some-> params :project-id uuid/parse*)
|
project-id (some-> params :project-id uuid/parse*)
|
||||||
|
|||||||
@@ -50,7 +50,8 @@
|
|||||||
touched? (and (contains? (:data @form) input-name)
|
touched? (and (contains? (:data @form) input-name)
|
||||||
(get-in @form [:touched input-name]))
|
(get-in @form [:touched input-name]))
|
||||||
|
|
||||||
error (get-in @form [:errors input-name])
|
error (or (get-in @form [:errors input-name])
|
||||||
|
(get-in @form [:extra-errors input-name]))
|
||||||
|
|
||||||
value (get-in @form [:data input-name] "")
|
value (get-in @form [:data input-name] "")
|
||||||
|
|
||||||
|
|||||||
103
frontend/src/app/main/ui/components/progress.cljs
Normal file
103
frontend/src/app/main/ui/components/progress.cljs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.main.ui.components.progress
|
||||||
|
"Assets exportation common components."
|
||||||
|
(:require-macros [app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.types.color :as clr]
|
||||||
|
[app.main.data.common :as dcm]
|
||||||
|
[app.main.refs :as refs]
|
||||||
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.icons :as deprecated-icons]
|
||||||
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
|
[app.util.theme :as theme]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(def ^:private neutral-icon
|
||||||
|
(deprecated-icons/icon-xref :msg-neutral (stl/css :icon)))
|
||||||
|
|
||||||
|
(def ^:private error-icon
|
||||||
|
(deprecated-icons/icon-xref :delete-text (stl/css :icon)))
|
||||||
|
|
||||||
|
(def ^:private close-icon
|
||||||
|
(deprecated-icons/icon-xref :close (stl/css :close-icon)))
|
||||||
|
|
||||||
|
(mf/defc progress-notification-widget*
|
||||||
|
[]
|
||||||
|
(let [state (mf/deref refs/progress)
|
||||||
|
profile (mf/deref refs/profile)
|
||||||
|
theme (get profile :theme theme/default)
|
||||||
|
default-theme? (= theme/default theme)
|
||||||
|
error? (:error state)
|
||||||
|
healthy? (:healthy state)
|
||||||
|
visible? (:visible state)
|
||||||
|
progress (:progress state)
|
||||||
|
hint (:hint state)
|
||||||
|
total (:total state)
|
||||||
|
|
||||||
|
pwidth
|
||||||
|
(if error?
|
||||||
|
280
|
||||||
|
(/ (* progress 280) total))
|
||||||
|
|
||||||
|
color
|
||||||
|
(cond
|
||||||
|
error? clr/new-danger
|
||||||
|
healthy? (if default-theme?
|
||||||
|
clr/new-primary
|
||||||
|
clr/new-primary-light)
|
||||||
|
(not healthy?) clr/new-warning)
|
||||||
|
|
||||||
|
background-clr
|
||||||
|
(if default-theme?
|
||||||
|
clr/background-quaternary
|
||||||
|
clr/background-quaternary-light)
|
||||||
|
|
||||||
|
toggle-detail-visibility
|
||||||
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(st/emit! (dcm/toggle-progress-visibility))))]
|
||||||
|
|
||||||
|
[:*
|
||||||
|
(when visible?
|
||||||
|
[:div {:class (stl/css-case :progress-modal true
|
||||||
|
:has-error error?)}
|
||||||
|
(if error?
|
||||||
|
error-icon
|
||||||
|
neutral-icon)
|
||||||
|
|
||||||
|
[:div {:class (stl/css :title)}
|
||||||
|
[:div {:class (stl/css :title-text)} hint]
|
||||||
|
(if error?
|
||||||
|
[:button {:class (stl/css :retry-btn)
|
||||||
|
;; :on-click retry-last-operation
|
||||||
|
}
|
||||||
|
(tr "labels.retry")]
|
||||||
|
|
||||||
|
[:span {:class (stl/css :progress)}
|
||||||
|
(dm/str progress " / " total)])]
|
||||||
|
|
||||||
|
[:button {:class (stl/css :progress-close-button)
|
||||||
|
:on-click toggle-detail-visibility}
|
||||||
|
close-icon]
|
||||||
|
|
||||||
|
(when-not error?
|
||||||
|
[:svg {:class (stl/css :progress-bar)
|
||||||
|
:height 4
|
||||||
|
:width 280}
|
||||||
|
[:g
|
||||||
|
[:path {:d "M0 0 L280 0"
|
||||||
|
:stroke background-clr
|
||||||
|
:stroke-width 30}]
|
||||||
|
[:path {:d (dm/str "M0 0 L280 0")
|
||||||
|
:stroke color
|
||||||
|
:stroke-width 30
|
||||||
|
:fill "transparent"
|
||||||
|
:stroke-dasharray 280
|
||||||
|
:stroke-dashoffset (- 280 pwidth)
|
||||||
|
:style {:transition "stroke-dashoffset 1s ease-in-out"}}]]])])]))
|
||||||
101
frontend/src/app/main/ui/components/progress.scss
Normal file
101
frontend/src/app/main/ui/components/progress.scss
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
|
||||||
|
// PROGRESS WIDGET
|
||||||
|
.progress-widget {
|
||||||
|
@include deprecated.flexCenter;
|
||||||
|
width: deprecated.$s-28;
|
||||||
|
height: deprecated.$s-28;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PROGRESS MODAL
|
||||||
|
.progress-modal {
|
||||||
|
--export-modal-bg-color: var(--alert-background-color-default);
|
||||||
|
--export-modal-fg-color: var(--alert-text-foreground-color-default);
|
||||||
|
--export-modal-icon-color: var(--alert-icon-foreground-color-default);
|
||||||
|
--export-modal-border-color: var(--alert-border-color-default);
|
||||||
|
position: absolute;
|
||||||
|
right: deprecated.$s-16;
|
||||||
|
top: deprecated.$s-48;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: deprecated.$s-24 1fr deprecated.$s-24;
|
||||||
|
grid-template-areas:
|
||||||
|
"icon text close"
|
||||||
|
"bar bar bar";
|
||||||
|
gap: deprecated.$s-4 deprecated.$s-8;
|
||||||
|
padding-block-start: deprecated.$s-8;
|
||||||
|
background-color: var(--export-modal-bg-color);
|
||||||
|
border: deprecated.$s-1 solid var(--export-modal-border-color);
|
||||||
|
border-radius: deprecated.$br-8;
|
||||||
|
z-index: deprecated.$z-index-modal;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-error {
|
||||||
|
--export-modal-bg-color: var(--alert-background-color-error);
|
||||||
|
--export-modal-fg-color: var(--alert-text-foreground-color-error);
|
||||||
|
--export-modal-icon-color: var(--alert-icon-foreground-color-error);
|
||||||
|
--export-modal-border-color: var(--alert-border-color-error);
|
||||||
|
grid-template-areas: "icon text close";
|
||||||
|
gap: deprecated.$s-8;
|
||||||
|
padding-block: deprecated.$s-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@extend .button-icon;
|
||||||
|
grid-area: icon;
|
||||||
|
align-self: center;
|
||||||
|
margin-inline-start: deprecated.$s-8;
|
||||||
|
stroke: var(--export-modal-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include deprecated.bodyMediumTypography;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: deprecated.$s-8;
|
||||||
|
grid-area: text;
|
||||||
|
align-self: center;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--export-modal-fg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
@include deprecated.bodyMediumTypography;
|
||||||
|
padding-left: deprecated.$s-8;
|
||||||
|
margin: 0;
|
||||||
|
align-self: center;
|
||||||
|
color: var(--modal-text-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-btn {
|
||||||
|
@include deprecated.buttonStyle;
|
||||||
|
@include deprecated.bodySmallTypography;
|
||||||
|
display: inline;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--modal-link-foreground-color);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-close-button {
|
||||||
|
@include deprecated.buttonStyle;
|
||||||
|
padding: 0;
|
||||||
|
margin-inline-end: deprecated.$s-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
@extend .button-icon;
|
||||||
|
stroke: var(--export-modal-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
margin-top: 0;
|
||||||
|
grid-area: bar;
|
||||||
|
}
|
||||||
@@ -53,6 +53,6 @@
|
|||||||
|
|
||||||
|
|
||||||
(mf/defc inspect-title-bar*
|
(mf/defc inspect-title-bar*
|
||||||
[{:keys [class title]}]
|
[{:keys [class title title-class]}]
|
||||||
[:div {:class [(stl/css :title-bar) class]}
|
[:div {:class [(stl/css :title-bar) class]}
|
||||||
[:div {:class (stl/css :title-only :inspect-title)} title]])
|
[:div {:class [title-class (stl/css :title-only :inspect-title)]} title]])
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.router :as rt]
|
[app.main.router :as rt]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.components.progress :refer [progress-notification-widget*]]
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
|
[app.main.ui.dashboard.deleted :refer [deleted-section*]]
|
||||||
[app.main.ui.dashboard.files :refer [files-section*]]
|
[app.main.ui.dashboard.files :refer [files-section*]]
|
||||||
[app.main.ui.dashboard.fonts :refer [fonts-page* font-providers-page*]]
|
[app.main.ui.dashboard.fonts :refer [fonts-page* font-providers-page*]]
|
||||||
[app.main.ui.dashboard.import]
|
[app.main.ui.dashboard.import]
|
||||||
@@ -73,7 +75,13 @@
|
|||||||
|
|
||||||
show-templates?
|
show-templates?
|
||||||
(and (contains? cf/flags :dashboard-templates-section)
|
(and (contains? cf/flags :dashboard-templates-section)
|
||||||
(:can-edit permissions))]
|
(:can-edit permissions))
|
||||||
|
|
||||||
|
show-deleted? (:can-edit permissions)
|
||||||
|
|
||||||
|
section (if (and (not show-deleted?) (= section :dashboard-deleted))
|
||||||
|
:dashboard-recent
|
||||||
|
section)]
|
||||||
|
|
||||||
(mf/with-effect []
|
(mf/with-effect []
|
||||||
(let [key1 (events/listen js/window "resize" on-resize)]
|
(let [key1 (events/listen js/window "resize" on-resize)]
|
||||||
@@ -84,6 +92,9 @@
|
|||||||
[:div {:class (stl/css :dashboard-content)
|
[:div {:class (stl/css :dashboard-content)
|
||||||
:on-click clear-selected-fn
|
:on-click clear-selected-fn
|
||||||
:ref container}
|
:ref container}
|
||||||
|
|
||||||
|
[:> progress-notification-widget*]
|
||||||
|
|
||||||
(case section
|
(case section
|
||||||
:dashboard-recent
|
:dashboard-recent
|
||||||
(when (seq projects)
|
(when (seq projects)
|
||||||
@@ -140,6 +151,11 @@
|
|||||||
:dashboard-settings
|
:dashboard-settings
|
||||||
[:> team-settings-page* {:team team :profile profile}]
|
[:> team-settings-page* {:team team :profile profile}]
|
||||||
|
|
||||||
|
:dashboard-deleted
|
||||||
|
[:> deleted-section* {:team team
|
||||||
|
:projects projects
|
||||||
|
:profile profile}]
|
||||||
|
|
||||||
nil)]))
|
nil)]))
|
||||||
|
|
||||||
(def ref:dashboard-initialized
|
(def ref:dashboard-initialized
|
||||||
|
|||||||
309
frontend/src/app/main/ui/dashboard/deleted.cljs
Normal file
309
frontend/src/app/main/ui/dashboard/deleted.cljs
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.main.ui.dashboard.deleted
|
||||||
|
(:require-macros [app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.main.data.common :as dcm]
|
||||||
|
[app.main.data.dashboard :as dd]
|
||||||
|
[app.main.data.modal :as modal]
|
||||||
|
[app.main.refs :as refs]
|
||||||
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||||
|
[app.main.ui.dashboard.grid :refer [grid*]]
|
||||||
|
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||||
|
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||||
|
[app.main.ui.hooks :as hooks]
|
||||||
|
[app.main.ui.icons :as deprecated-icon]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
|
[app.util.keyboard :as kbd]
|
||||||
|
[okulary.core :as l]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(def ^:private ref:deleted-files
|
||||||
|
(l/derived :deleted-files st/state))
|
||||||
|
|
||||||
|
(def ^:private menu-icon
|
||||||
|
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
|
||||||
|
|
||||||
|
(mf/defc header*
|
||||||
|
{::mf/props :obj
|
||||||
|
::mf/private true}
|
||||||
|
[]
|
||||||
|
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
|
||||||
|
[:div#dashboard-deleted-title {:class (stl/css :dashboard-title)}
|
||||||
|
[:h1 (tr "dashboard.projects-title")]]])
|
||||||
|
|
||||||
|
(mf/defc deleted-project-menu*
|
||||||
|
[{:keys [project show on-close top left]}]
|
||||||
|
(let [top (d/nilv top 0)
|
||||||
|
left (d/nilv left 0)
|
||||||
|
|
||||||
|
on-restore-project
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps project)
|
||||||
|
(fn []
|
||||||
|
(let [on-accept #(st/emit! (dd/restore-project-immediately project))]
|
||||||
|
(st/emit! (modal/show {:type :confirm
|
||||||
|
:title (tr "dashboard.restore-project-confirmation.title")
|
||||||
|
:message (tr "dashboard.restore-project-confirmation.description" (:name project))
|
||||||
|
:accept-style :primary
|
||||||
|
:accept-label (tr "labels.continue")
|
||||||
|
:on-accept on-accept})))))
|
||||||
|
|
||||||
|
on-delete-project
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps project)
|
||||||
|
(fn []
|
||||||
|
(let [accept-fn #(st/emit! (dd/delete-project-immediately project))]
|
||||||
|
(st/emit! (modal/show {:type :confirm
|
||||||
|
:title (tr "dashboard.delete-forever-confirmation.title")
|
||||||
|
:message (tr "dashboard.delete-project-forever-confirmation.description" (:name project))
|
||||||
|
:accept-label (tr "dashboard.delete-forever-confirmation.title")
|
||||||
|
:on-accept accept-fn})))))
|
||||||
|
options
|
||||||
|
(mf/with-memo [on-restore-project on-delete-project]
|
||||||
|
[{:name (tr "dashboard.restore-project-button")
|
||||||
|
:id "project-restore"
|
||||||
|
:handler on-restore-project}
|
||||||
|
{:name (tr "dashboard.delete-project-button")
|
||||||
|
:id "project-delete"
|
||||||
|
:handler on-delete-project}])]
|
||||||
|
|
||||||
|
[:> context-menu*
|
||||||
|
{:on-close on-close
|
||||||
|
:show show
|
||||||
|
:fixed (or (not= top 0) (not= left 0))
|
||||||
|
:min-width true
|
||||||
|
:top top
|
||||||
|
:left left
|
||||||
|
:options options}]))
|
||||||
|
|
||||||
|
(mf/defc deleted-project-item*
|
||||||
|
{::mf/private true}
|
||||||
|
[{:keys [project files]}]
|
||||||
|
(let [project-files (filterv #(= (:project-id %) (:id project)) files)
|
||||||
|
|
||||||
|
empty? (empty? project-files)
|
||||||
|
selected-files (mf/deref refs/selected-files)
|
||||||
|
|
||||||
|
dstate (mf/deref refs/dashboard-local)
|
||||||
|
edit-id (:project-for-edit dstate)
|
||||||
|
|
||||||
|
local (mf/use-state
|
||||||
|
#(do {:menu-open false
|
||||||
|
:menu-pos nil
|
||||||
|
:edition (= (:id project) edit-id)}))
|
||||||
|
|
||||||
|
[rowref limit] (hooks/use-dynamic-grid-item-width)
|
||||||
|
|
||||||
|
on-menu-click
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [event]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
|
||||||
|
(let [client-position (dom/get-client-position event)
|
||||||
|
position (if (and (nil? (:y client-position)) (nil? (:x client-position)))
|
||||||
|
(let [target-element (dom/get-target event)
|
||||||
|
points (dom/get-bounding-rect target-element)
|
||||||
|
y (:top points)
|
||||||
|
x (:left points)]
|
||||||
|
(gpt/point x y))
|
||||||
|
client-position)]
|
||||||
|
(swap! local assoc
|
||||||
|
:menu-open true
|
||||||
|
:menu-pos position))))
|
||||||
|
|
||||||
|
on-menu-close
|
||||||
|
(mf/use-fn #(swap! local assoc :menu-open false))
|
||||||
|
|
||||||
|
handle-menu-click
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps on-menu-click)
|
||||||
|
(fn [event]
|
||||||
|
(when (kbd/enter? event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(on-menu-click event))))]
|
||||||
|
|
||||||
|
[:article {:class (stl/css-case :dashboard-project-row true)}
|
||||||
|
[:header {:class (stl/css :project)}
|
||||||
|
[:div {:class (stl/css :project-name-wrapper)}
|
||||||
|
[:h2 {:class (stl/css :project-name)
|
||||||
|
:title (:name project)}
|
||||||
|
(:name project)]
|
||||||
|
|
||||||
|
(when (:deleted-at project)
|
||||||
|
[:div {:class (stl/css :info-wrapper)}
|
||||||
|
[:div {:class (stl/css-case :project-actions true)}
|
||||||
|
|
||||||
|
[:button {:class (stl/css :options-btn)
|
||||||
|
:on-click on-menu-click
|
||||||
|
:title (tr "dashboard.options")
|
||||||
|
:aria-label (tr "dashboard.options")
|
||||||
|
:data-testid "project-options"
|
||||||
|
:on-key-down handle-menu-click}
|
||||||
|
menu-icon]]
|
||||||
|
|
||||||
|
(when (:menu-open @local)
|
||||||
|
[:> deleted-project-menu*
|
||||||
|
{:project project
|
||||||
|
:show (:menu-open @local)
|
||||||
|
:left (+ 24 (:x (:menu-pos @local)))
|
||||||
|
:top (:y (:menu-pos @local))
|
||||||
|
:on-close on-menu-close}])])]]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :grid-container) :ref rowref}
|
||||||
|
(if ^boolean empty?
|
||||||
|
[:> empty-placeholder* {:title (tr "dashboard.empty-placeholder-files-title")
|
||||||
|
:class (stl/css :placeholder-placement)
|
||||||
|
:type 1
|
||||||
|
:subtitle (tr "dashboard.empty-placeholder-files-subtitle")}]
|
||||||
|
|
||||||
|
[:> grid*
|
||||||
|
{:project project
|
||||||
|
:files project-files
|
||||||
|
:origin :deleted
|
||||||
|
:can-edit false
|
||||||
|
:can-restore true
|
||||||
|
:limit limit
|
||||||
|
:selected-files selected-files}])]]))
|
||||||
|
|
||||||
|
|
||||||
|
(mf/defc menu*
|
||||||
|
[{:keys [team-id section]}]
|
||||||
|
(let [on-recent-click
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps team-id)
|
||||||
|
(fn []
|
||||||
|
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id))))
|
||||||
|
|
||||||
|
on-deleted-click
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps team-id)
|
||||||
|
(fn []
|
||||||
|
(st/emit! (dcm/go-to-dashboard-deleted :team-id team-id))))]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :nav)}
|
||||||
|
[:div {:class (stl/css :nav-inside)}
|
||||||
|
[:div {:class [(stl/css :nav-option)
|
||||||
|
(stl/css-case :selected (= section :dashboard-recent))]
|
||||||
|
:data-testid "recent-tab"
|
||||||
|
:on-click on-recent-click}
|
||||||
|
(tr "labels.recent")]
|
||||||
|
[:div {:class [(stl/css :nav-option)
|
||||||
|
(stl/css-case :selected (= section :dashboard-deleted))]
|
||||||
|
:variant "ghost"
|
||||||
|
:type "button"
|
||||||
|
:data-testid "deleted-tab"
|
||||||
|
:on-click on-deleted-click}
|
||||||
|
(tr "labels.deleted")]]]))
|
||||||
|
|
||||||
|
(mf/defc deleted-section*
|
||||||
|
[{:keys [team projects]}]
|
||||||
|
(let [deleted-map
|
||||||
|
(mf/deref ref:deleted-files)
|
||||||
|
|
||||||
|
projects
|
||||||
|
(mf/with-memo [projects deleted-map]
|
||||||
|
(->> projects
|
||||||
|
(filter (fn [project]
|
||||||
|
(or (:deleted-at project)
|
||||||
|
(when deleted-map
|
||||||
|
(some #(= (:id project) (:project-id %))
|
||||||
|
(vals deleted-map))))))
|
||||||
|
(filter (fn [project]
|
||||||
|
(when deleted-map
|
||||||
|
(some #(= (:id project) (:project-id %))
|
||||||
|
(vals deleted-map)))))
|
||||||
|
(sort-by :modified-at)
|
||||||
|
(reverse)))
|
||||||
|
|
||||||
|
team-id
|
||||||
|
(get team :id)
|
||||||
|
|
||||||
|
;; Calculate deletion days based on team subscription
|
||||||
|
deletion-days
|
||||||
|
(let [subscription (get team :subscription)
|
||||||
|
sub-type (get subscription :type)
|
||||||
|
sub-status (get subscription :status)
|
||||||
|
canceled? (contains? #{"canceled" "unpaid"} sub-status)]
|
||||||
|
(cond
|
||||||
|
(and (= "unlimited" sub-type) (not canceled?)) 30
|
||||||
|
(and (= "enterprise" sub-type) (not canceled?)) 90
|
||||||
|
:else 7))
|
||||||
|
|
||||||
|
on-delete-all
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps team-id deleted-map)
|
||||||
|
(fn []
|
||||||
|
(when-let [ids (not-empty (into #{} (map key) deleted-map))]
|
||||||
|
(let [on-accept #(st/emit! (dd/delete-files-immediately
|
||||||
|
{:team-id team-id
|
||||||
|
:ids ids}))]
|
||||||
|
(st/emit! (modal/show {:type :confirm
|
||||||
|
:title (tr "dashboard.delete-forever-confirmation.title")
|
||||||
|
:message (tr "dashboard.delete-all-forever-confirmation.description" (count ids))
|
||||||
|
:accept-label (tr "dashboard.delete-forever-confirmation.title")
|
||||||
|
:on-accept on-accept}))))))
|
||||||
|
|
||||||
|
on-restore-all
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps team-id deleted-map)
|
||||||
|
(fn []
|
||||||
|
(when-let [ids (not-empty (into #{} (map key) deleted-map))]
|
||||||
|
(let [on-accept #(st/emit! (dd/restore-files-immediately {:team-id team-id :ids ids}))]
|
||||||
|
(st/emit! (modal/show {:type :confirm
|
||||||
|
:title (tr "dashboard.restore-all-confirmation.title")
|
||||||
|
:message (tr "dashboard.restore-all-confirmation.description" (count ids))
|
||||||
|
:accept-label (tr "labels.continue")
|
||||||
|
:accept-style :primary
|
||||||
|
:on-accept on-accept}))))))]
|
||||||
|
|
||||||
|
|
||||||
|
(mf/with-effect [team-id]
|
||||||
|
(st/emit! (dd/fetch-projects team-id)
|
||||||
|
(dd/fetch-deleted-files team-id)
|
||||||
|
(dd/clear-selected-files)))
|
||||||
|
|
||||||
|
[:*
|
||||||
|
[:> header* {:team team}]
|
||||||
|
[:section {:class (stl/css :dashboard-container :no-bg)}
|
||||||
|
[:*
|
||||||
|
[:div {:class (stl/css :no-bg)}
|
||||||
|
|
||||||
|
[:> menu* {:team-id team-id :section :dashboard-deleted}]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :deleted-info-content)}
|
||||||
|
[:p {:class (stl/css :deleted-info)}
|
||||||
|
(tr "dashboard.trash-info-text-part1")
|
||||||
|
[:span {:class (stl/css :info-text-highlight)}
|
||||||
|
(tr "dashboard.trash-info-text-part2" deletion-days)]
|
||||||
|
(tr "dashboard.trash-info-text-part3")
|
||||||
|
[:br]
|
||||||
|
(tr "dashboard.trash-info-text-part4")]
|
||||||
|
[:div {:class (stl/css :deleted-options)}
|
||||||
|
[:> button* {:variant "ghost"
|
||||||
|
:type "button"
|
||||||
|
:on-click on-restore-all}
|
||||||
|
(tr "dashboard.restore-all-deleted-button")]
|
||||||
|
[:> button* {:variant "destructive"
|
||||||
|
:type "button"
|
||||||
|
:icon "delete"
|
||||||
|
:on-click on-delete-all}
|
||||||
|
(tr "dashboard.clear-trash-button")]]]
|
||||||
|
|
||||||
|
(when (seq projects)
|
||||||
|
(for [{:keys [id] :as project} projects]
|
||||||
|
(let [files (when deleted-map
|
||||||
|
(->> (vals deleted-map)
|
||||||
|
(filterv #(= id (:project-id %)))
|
||||||
|
(sort-by :modified-at #(compare %2 %1))))]
|
||||||
|
[:> deleted-project-item* {:project project
|
||||||
|
:files files
|
||||||
|
:key id}])))]]]]))
|
||||||
139
frontend/src/app/main/ui/dashboard/deleted.scss
Normal file
139
frontend/src/app/main/ui/dashboard/deleted.scss
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "common/refactor/common-dashboard";
|
||||||
|
@use "../ds/typography.scss" as t;
|
||||||
|
@use "../ds/_borders.scss" as *;
|
||||||
|
@use "../ds/spacing.scss" as *;
|
||||||
|
@use "../ds/_sizes.scss" as *;
|
||||||
|
@use "../ds/z-index.scss" as *;
|
||||||
|
|
||||||
|
.dashboard-container {
|
||||||
|
flex: 1 0 0;
|
||||||
|
width: 100%;
|
||||||
|
margin-inline-end: var(--sp-l);
|
||||||
|
border-top: $b-1 solid var(--panel-border-color);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-block-end: var(--sp-xxxl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted-info-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--sp-s) var(--sp-xxl) var(--sp-s) var(--sp-xxl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted-info {
|
||||||
|
display: block;
|
||||||
|
height: fit-content;
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
@include t.use-typography("body-large");
|
||||||
|
line-height: 0.8;
|
||||||
|
height: var(--sp-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-text-highlight {
|
||||||
|
color: var(--color-accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted-options {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
background: var(--color-background-default);
|
||||||
|
padding: var(--sp-xxl) var(--sp-xxl) var(--sp-s) var(--sp-xxl);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
// We need to use the the deprecated z-index so it won't clash with the dashboard
|
||||||
|
// onboarding modals
|
||||||
|
z-index: deprecated.$z-index-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-inside {
|
||||||
|
border-bottom: $b-1 solid var(--panel-border-color);
|
||||||
|
display: flex;
|
||||||
|
gap: var(--sp-l);
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-option {
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: $b-1 solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
color: var(--color-foreground-primary);
|
||||||
|
border-bottom: $b-1 solid var(--color-foreground-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--sp-s);
|
||||||
|
width: 99%;
|
||||||
|
max-height: $sz-40;
|
||||||
|
padding: var(--sp-s) var(--sp-s) var(--sp-s) var(--sp-l);
|
||||||
|
margin-block-start: var(--sp-l);
|
||||||
|
border-radius: $br-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
min-height: var(--sp-xxxl);
|
||||||
|
margin-inline-start: var(--sp-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
@include t.use-typography("body-large");
|
||||||
|
width: fit-content;
|
||||||
|
margin-inline-end: var(--sp-m);
|
||||||
|
line-height: 0.8;
|
||||||
|
color: var(--title-foreground-color-hover);
|
||||||
|
height: var(--sp-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-actions {
|
||||||
|
display: flex;
|
||||||
|
opacity: var(--actions-opacity);
|
||||||
|
margin-inline-start: var(--sp-xxxl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-file-btn,
|
||||||
|
.options-btn {
|
||||||
|
@extend .button-tertiary;
|
||||||
|
height: var(--sp-xxxl);
|
||||||
|
width: var(--sp-xxxl);
|
||||||
|
margin: 0 var(--sp-s);
|
||||||
|
padding: var(--sp-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-icon,
|
||||||
|
.menu-icon {
|
||||||
|
@extend .button-icon;
|
||||||
|
stroke: var(--icon-foreground);
|
||||||
|
}
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
projects))
|
projects))
|
||||||
|
|
||||||
(mf/defc file-menu*
|
(mf/defc file-menu*
|
||||||
[{:keys [files on-edit on-close top left navigate origin parent-id can-edit]}]
|
[{:keys [files on-edit on-close top left navigate origin parent-id can-edit can-restore]}]
|
||||||
|
|
||||||
(assert (seq files) "missing `files` prop")
|
(assert (seq files) "missing `files` prop")
|
||||||
(assert (fn? on-edit) "missing `on-edit` prop")
|
(assert (fn? on-edit) "missing `on-edit` prop")
|
||||||
@@ -187,7 +187,39 @@
|
|||||||
on-export-binary-files
|
on-export-binary-files
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (-> (fexp/open-export-dialog files)
|
(st/emit! (-> (fexp/open-export-dialog files)
|
||||||
(with-meta {::ev/origin "dashboard"}))))]
|
(with-meta {::ev/origin "dashboard"}))))
|
||||||
|
|
||||||
|
restore-fn
|
||||||
|
(fn [_]
|
||||||
|
(st/emit! (dd/restore-files-immediately
|
||||||
|
(with-meta {:team-id (:id current-team)
|
||||||
|
:ids #{(:id file)}}
|
||||||
|
{:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file)))
|
||||||
|
(dd/fetch-projects (:id current-team))
|
||||||
|
(dd/fetch-deleted-files (:id current-team)))
|
||||||
|
:on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))}))))
|
||||||
|
|
||||||
|
on-restore-immediately
|
||||||
|
(fn []
|
||||||
|
(st/emit!
|
||||||
|
(modal/show {:type :confirm
|
||||||
|
:title (tr "dashboard-restore-file-confirmation.title")
|
||||||
|
:message (tr "dashboard-restore-file-confirmation.description" (:name file))
|
||||||
|
:accept-label (tr "labels.continue")
|
||||||
|
:accept-style :primary
|
||||||
|
:on-accept restore-fn})))
|
||||||
|
|
||||||
|
on-delete-immediately
|
||||||
|
(fn []
|
||||||
|
(let [accept-fn #(st/emit! (dd/delete-files-immediately
|
||||||
|
{:team-id (:id current-team)
|
||||||
|
:ids #{(:id file)}}))]
|
||||||
|
(st/emit!
|
||||||
|
(modal/show {:type :confirm
|
||||||
|
:title (tr "dashboard.delete-forever-confirmation.title")
|
||||||
|
:message (tr "dashboard.delete-file-forever-confirmation.description" (:name file))
|
||||||
|
:accept-label (tr "dashboard.delete-forever-confirmation.title")
|
||||||
|
:on-accept accept-fn}))))]
|
||||||
|
|
||||||
(mf/with-effect []
|
(mf/with-effect []
|
||||||
(->> (rp/cmd! :get-all-projects)
|
(->> (rp/cmd! :get-all-projects)
|
||||||
@@ -227,76 +259,85 @@
|
|||||||
(:id sub-project))})})}]))
|
(:id sub-project))})})}]))
|
||||||
|
|
||||||
options
|
options
|
||||||
(if multi?
|
(if can-restore
|
||||||
[(when can-edit
|
[(when can-restore
|
||||||
{:name (tr "dashboard.duplicate-multi" file-count)
|
{:name (tr "dashboard.restore-file-button")
|
||||||
:id "duplicate-multi"
|
:id "restore-file"
|
||||||
:handler on-duplicate})
|
:handler on-restore-immediately})
|
||||||
|
(when can-restore
|
||||||
|
{:name (tr "dashboard.delete-file-button")
|
||||||
|
:id "delete-file"
|
||||||
|
:handler on-delete-immediately})]
|
||||||
|
(if multi?
|
||||||
|
[(when can-edit
|
||||||
|
{:name (tr "dashboard.duplicate-multi" file-count)
|
||||||
|
:id "duplicate-multi"
|
||||||
|
:handler on-duplicate})
|
||||||
|
|
||||||
(when (and (or (seq current-projects) (seq other-teams)) can-edit)
|
(when (and (or (seq current-projects) (seq other-teams)) can-edit)
|
||||||
{:name (tr "dashboard.move-to-multi" file-count)
|
{:name (tr "dashboard.move-to-multi" file-count)
|
||||||
:id "file-move-multi"
|
:id "file-move-multi"
|
||||||
:options sub-options})
|
:options sub-options})
|
||||||
|
|
||||||
{:name (tr "dashboard.export-binary-multi" file-count)
|
{:name (tr "dashboard.export-binary-multi" file-count)
|
||||||
:id "file-binary-export-multi"
|
:id "file-binary-export-multi"
|
||||||
:handler on-export-binary-files}
|
:handler on-export-binary-files}
|
||||||
|
|
||||||
(when (and (:is-shared file) can-edit)
|
(when (and (:is-shared file) can-edit)
|
||||||
{:name (tr "labels.unpublish-multi-files" file-count)
|
{:name (tr "labels.unpublish-multi-files" file-count)
|
||||||
:id "file-unpublish-multi"
|
:id "file-unpublish-multi"
|
||||||
:handler on-del-shared})
|
:handler on-del-shared})
|
||||||
|
|
||||||
(when (and (not is-lib-page?) can-edit)
|
(when (and (not is-lib-page?) can-edit)
|
||||||
{:name :separator}
|
{:name :separator}
|
||||||
{:name (tr "labels.delete-multi-files" file-count)
|
{:name (tr "labels.delete-multi-files" file-count)
|
||||||
:id "file-delete-multi"
|
:id "file-delete-multi"
|
||||||
:handler on-delete})]
|
:handler on-delete})]
|
||||||
|
|
||||||
[{:name (tr "dashboard.open-in-new-tab")
|
[{:name (tr "dashboard.open-in-new-tab")
|
||||||
:id "file-open-new-tab"
|
:id "file-open-new-tab"
|
||||||
:handler on-new-tab}
|
:handler on-new-tab}
|
||||||
(when (and (not is-search-page?) can-edit)
|
(when (and (not is-search-page?) can-edit)
|
||||||
{:name (tr "labels.rename")
|
{:name (tr "labels.rename")
|
||||||
:id "file-rename"
|
:id "file-rename"
|
||||||
:handler on-edit})
|
:handler on-edit})
|
||||||
|
|
||||||
(when (and (not is-search-page?) can-edit)
|
(when (and (not is-search-page?) can-edit)
|
||||||
{:name (tr "dashboard.duplicate")
|
{:name (tr "dashboard.duplicate")
|
||||||
:id "file-duplicate"
|
:id "file-duplicate"
|
||||||
:handler on-duplicate})
|
:handler on-duplicate})
|
||||||
|
|
||||||
(when (and (not is-lib-page?)
|
(when (and (not is-lib-page?)
|
||||||
(not is-search-page?)
|
(not is-search-page?)
|
||||||
(or (seq current-projects) (seq other-teams))
|
(or (seq current-projects) (seq other-teams))
|
||||||
can-edit)
|
can-edit)
|
||||||
{:name (tr "dashboard.move-to")
|
{:name (tr "dashboard.move-to")
|
||||||
:id "file-move-to"
|
:id "file-move-to"
|
||||||
:options sub-options})
|
:options sub-options})
|
||||||
|
|
||||||
(when (and (not is-search-page?)
|
(when (and (not is-search-page?)
|
||||||
can-edit)
|
can-edit)
|
||||||
(if (:is-shared file)
|
(if (:is-shared file)
|
||||||
{:name (tr "dashboard.unpublish-shared")
|
{:name (tr "dashboard.unpublish-shared")
|
||||||
:id "file-del-shared"
|
:id "file-del-shared"
|
||||||
:handler on-del-shared}
|
:handler on-del-shared}
|
||||||
{:name (tr "dashboard.add-shared")
|
{:name (tr "dashboard.add-shared")
|
||||||
:id "file-add-shared"
|
:id "file-add-shared"
|
||||||
:handler on-add-shared}))
|
:handler on-add-shared}))
|
||||||
|
|
||||||
{:name :separator}
|
{:name :separator}
|
||||||
|
|
||||||
{:name (tr "dashboard.download-binary-file")
|
{:name (tr "dashboard.download-binary-file")
|
||||||
:id "download-binary-file"
|
:id "download-binary-file"
|
||||||
:handler on-export-binary-files}
|
:handler on-export-binary-files}
|
||||||
|
|
||||||
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
|
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
|
||||||
{:name :separator})
|
{:name :separator})
|
||||||
|
|
||||||
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
|
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
|
||||||
{:name (tr "labels.delete")
|
{:name (tr "labels.delete")
|
||||||
:id "file-delete"
|
:id "file-delete"
|
||||||
:handler on-delete})])]
|
:handler on-delete})]))]
|
||||||
|
|
||||||
[:> context-menu*
|
[:> context-menu*
|
||||||
{:on-close on-close
|
{:on-close on-close
|
||||||
|
|||||||
@@ -360,7 +360,9 @@
|
|||||||
(st/emit! (modal/show options)))))]
|
(st/emit! (modal/show options)))))]
|
||||||
|
|
||||||
[:div {:class (stl/css :font-item :table-row)}
|
[:div {:class (stl/css :font-item :table-row)}
|
||||||
[:div {:class (stl/css :table-field :family)}
|
[:div {:class (stl/css-case :table-field true
|
||||||
|
:family true
|
||||||
|
:is-edition edition?)}
|
||||||
(if ^boolean edition?
|
(if ^boolean edition?
|
||||||
[:input {:type "text"
|
[:input {:type "text"
|
||||||
:auto-focus true
|
:auto-focus true
|
||||||
|
|||||||
@@ -126,6 +126,9 @@
|
|||||||
@include twoLineTextEllipsis;
|
@include twoLineTextEllipsis;
|
||||||
min-width: $sz-200;
|
min-width: $sz-200;
|
||||||
width: $sz-200;
|
width: $sz-200;
|
||||||
|
&.is-edition {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .filenames {
|
> .filenames {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
(mf/defc grid-item-thumbnail*
|
(mf/defc grid-item-thumbnail*
|
||||||
{::mf/props :obj
|
{::mf/props :obj
|
||||||
::mf/private true}
|
::mf/private true}
|
||||||
[{:keys [can-edit file]}]
|
[{:keys [can-edit file can-restore]}]
|
||||||
(let [file-id (get file :id)
|
(let [file-id (get file :id)
|
||||||
revn (get file :revn)
|
revn (get file :revn)
|
||||||
thumbnail-id (get file :thumbnail-id)
|
thumbnail-id (get file :thumbnail-id)
|
||||||
@@ -109,7 +109,8 @@
|
|||||||
:message (ex-message cause)))))]
|
:message (ex-message cause)))))]
|
||||||
(partial rx/dispose! subscription))))
|
(partial rx/dispose! subscription))))
|
||||||
|
|
||||||
[:div {:class (stl/css :grid-item-th)
|
[:div {:class (stl/css-case :grid-item-th true
|
||||||
|
:deleted-item can-restore)
|
||||||
:style {:background-color bg-color}
|
:style {:background-color bg-color}
|
||||||
:ref container}
|
:ref container}
|
||||||
(when visible?
|
(when visible?
|
||||||
@@ -131,13 +132,15 @@
|
|||||||
|
|
||||||
(mf/defc grid-item-library*
|
(mf/defc grid-item-library*
|
||||||
{::mf/props :obj}
|
{::mf/props :obj}
|
||||||
[{:keys [file]}]
|
[{:keys [file can-restore]}]
|
||||||
(mf/with-effect [file]
|
(mf/with-effect [file]
|
||||||
(when file
|
(when file
|
||||||
(let [font-ids (map :font-id (get-in file [:library-summary :typographies :sample] []))]
|
(let [font-ids (map :font-id (get-in file [:library-summary :typographies :sample] []))]
|
||||||
(run! fonts/ensure-loaded! font-ids))))
|
(run! fonts/ensure-loaded! font-ids))))
|
||||||
|
|
||||||
[:div {:class (stl/css :grid-item-th :library)}
|
[:div {:class (stl/css-case :grid-item-th true
|
||||||
|
:library true
|
||||||
|
:deleted-item can-restore)}
|
||||||
(if (nil? file)
|
(if (nil? file)
|
||||||
[:> loader* {:class (stl/css :grid-loader)
|
[:> loader* {:class (stl/css :grid-loader)
|
||||||
:overlay true
|
:overlay true
|
||||||
@@ -237,10 +240,13 @@
|
|||||||
|
|
||||||
;; --- Grid Item
|
;; --- Grid Item
|
||||||
|
|
||||||
(mf/defc grid-item-metadata
|
(mf/defc grid-item-metadata*
|
||||||
[{:keys [modified-at]}]
|
[{:keys [file]}]
|
||||||
(let [time (ct/timeago modified-at)]
|
(let [time (ct/timeago (or (:will-be-deleted-at file)
|
||||||
[:span {:class (stl/css :date)} time]))
|
(:modified-at file)))]
|
||||||
|
[:span {:class (stl/css :date)
|
||||||
|
:title (tr "dashboard.deleted.will-be-deleted-at" time)}
|
||||||
|
time]))
|
||||||
|
|
||||||
(defn create-counter-element
|
(defn create-counter-element
|
||||||
[_element file-count]
|
[_element file-count]
|
||||||
@@ -250,7 +256,7 @@
|
|||||||
counter-el))
|
counter-el))
|
||||||
|
|
||||||
(mf/defc grid-item*
|
(mf/defc grid-item*
|
||||||
[{:keys [file origin can-edit selected-files]}]
|
[{:keys [file origin can-edit selected-files can-restore]}]
|
||||||
(let [file-id (get file :id)
|
(let [file-id (get file :id)
|
||||||
state (mf/deref refs/dashboard-local)
|
state (mf/deref refs/dashboard-local)
|
||||||
|
|
||||||
@@ -289,12 +295,13 @@
|
|||||||
|
|
||||||
on-navigate
|
on-navigate
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps file-id)
|
(mf/deps file-id can-restore)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [menu-icon (mf/ref-val menu-ref)
|
(when-not can-restore
|
||||||
target (dom/get-target event)]
|
(let [menu-icon (mf/ref-val menu-ref)
|
||||||
(when-not (dom/child? target menu-icon)
|
target (dom/get-target event)]
|
||||||
(st/emit! (dcm/go-to-workspace :file-id file-id))))))
|
(when-not (dom/child? target menu-icon)
|
||||||
|
(st/emit! (dcm/go-to-workspace :file-id file-id)))))))
|
||||||
|
|
||||||
on-drag-start
|
on-drag-start
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@@ -412,8 +419,8 @@
|
|||||||
[:div {:class (stl/css :overlay)}]
|
[:div {:class (stl/css :overlay)}]
|
||||||
|
|
||||||
(if ^boolean is-library-view?
|
(if ^boolean is-library-view?
|
||||||
[:> grid-item-library* {:file file}]
|
[:> grid-item-library* {:file file :can-restore can-restore}]
|
||||||
[:> grid-item-thumbnail* {:file file :can-edit can-edit}])
|
[:> grid-item-thumbnail* {:file file :can-edit can-edit :can-restore can-restore}])
|
||||||
|
|
||||||
(when (and (:is-shared file) (not is-library-view?))
|
(when (and (:is-shared file) (not is-library-view?))
|
||||||
[:div {:class (stl/css :item-badge)} deprecated-icon/library])
|
[:div {:class (stl/css :item-badge)} deprecated-icon/library])
|
||||||
@@ -425,7 +432,7 @@
|
|||||||
:on-end edit
|
:on-end edit
|
||||||
:max-length 250}]
|
:max-length 250}]
|
||||||
[:h3 (:name file)])
|
[:h3 (:name file)])
|
||||||
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
|
[:> grid-item-metadata* {:file file}]]
|
||||||
|
|
||||||
[:div {:class (stl/css-case :project-th-actions true :force-display menu-open?)}
|
[:div {:class (stl/css-case :project-th-actions true :force-display menu-open?)}
|
||||||
[:div
|
[:div
|
||||||
@@ -451,11 +458,12 @@
|
|||||||
:on-edit on-edit
|
:on-edit on-edit
|
||||||
:on-close on-menu-close
|
:on-close on-menu-close
|
||||||
:origin origin
|
:origin origin
|
||||||
:parent-id (dm/str file-id "-action-menu")}]])]]]]]))
|
:parent-id (dm/str file-id "-action-menu")
|
||||||
|
:can-restore can-restore}]])]]]]]))
|
||||||
|
|
||||||
(mf/defc grid*
|
(mf/defc grid*
|
||||||
{::mf/props :obj}
|
{::mf/props :obj}
|
||||||
[{:keys [files project origin limit create-fn can-edit selected-files]}]
|
[{:keys [files project origin limit create-fn can-edit selected-files can-restore]}]
|
||||||
(let [dragging? (mf/use-state false)
|
(let [dragging? (mf/use-state false)
|
||||||
project-id (get project :id)
|
project-id (get project :id)
|
||||||
team-id (get project :team-id)
|
team-id (get project :team-id)
|
||||||
@@ -535,7 +543,8 @@
|
|||||||
:key (dm/str (:id item))
|
:key (dm/str (:id item))
|
||||||
:origin origin
|
:origin origin
|
||||||
:selected-files selected-files
|
:selected-files selected-files
|
||||||
:can-edit can-edit}])])
|
:can-edit can-edit
|
||||||
|
:can-restore can-restore}])])
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[:> empty-grid-placeholder*
|
[:> empty-grid-placeholder*
|
||||||
@@ -548,7 +557,7 @@
|
|||||||
:on-finish-import on-finish-import}])]))
|
:on-finish-import on-finish-import}])]))
|
||||||
|
|
||||||
(mf/defc line-grid-row
|
(mf/defc line-grid-row
|
||||||
[{:keys [files selected-files dragging? limit can-edit] :as props}]
|
[{:keys [files selected-files dragging? limit can-edit can-restore] :as props}]
|
||||||
(let [elements limit
|
(let [elements limit
|
||||||
limit (if dragging? (dec limit) limit)]
|
limit (if dragging? (dec limit) limit)]
|
||||||
[:ul {:class (stl/css :grid-row :no-wrap)
|
[:ul {:class (stl/css :grid-row :no-wrap)
|
||||||
@@ -563,10 +572,11 @@
|
|||||||
:file item
|
:file item
|
||||||
:selected-files selected-files
|
:selected-files selected-files
|
||||||
:can-edit can-edit
|
:can-edit can-edit
|
||||||
:key (dm/str (:id item))}])]))
|
:key (dm/str (:id item))
|
||||||
|
:can-restore can-restore}])]))
|
||||||
|
|
||||||
(mf/defc line-grid
|
(mf/defc line-grid
|
||||||
[{:keys [project team files limit create-fn can-edit] :as props}]
|
[{:keys [project team files limit create-fn can-edit can-restore] :as props}]
|
||||||
(let [dragging? (mf/use-state false)
|
(let [dragging? (mf/use-state false)
|
||||||
project-id (:id project)
|
project-id (:id project)
|
||||||
team-id (:id team)
|
team-id (:id team)
|
||||||
@@ -664,7 +674,8 @@
|
|||||||
:selected-files selected-files
|
:selected-files selected-files
|
||||||
:dragging? @dragging?
|
:dragging? @dragging?
|
||||||
:can-edit can-edit
|
:can-edit can-edit
|
||||||
:limit limit}]
|
:limit limit
|
||||||
|
:can-restore can-restore}]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[:> empty-grid-placeholder*
|
[:> empty-grid-placeholder*
|
||||||
|
|||||||
@@ -375,3 +375,7 @@ $thumbnail-default-height: deprecated.$s-168; // Default width
|
|||||||
.grid-loader {
|
.grid-loader {
|
||||||
--icon-width: calc(var(--th-width, #{$thumbnail-default-width}) * 0.25);
|
--icon-width: calc(var(--th-width, #{$thumbnail-default-width}) * 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deleted-item {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
[app.main.data.project :as dpj]
|
[app.main.data.project :as dpj]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.dashboard.deleted :as deleted]
|
||||||
[app.main.ui.dashboard.grid :refer [line-grid]]
|
[app.main.ui.dashboard.grid :refer [line-grid]]
|
||||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||||
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
|
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
|
||||||
@@ -315,28 +316,30 @@
|
|||||||
{::mf/props :obj}
|
{::mf/props :obj}
|
||||||
[{:keys [team projects profile]}]
|
[{:keys [team projects profile]}]
|
||||||
|
|
||||||
(let [projects
|
(let [team-id (get team :id)
|
||||||
|
|
||||||
|
recent-map (mf/deref ref:recent-files)
|
||||||
|
permisions (:permissions team)
|
||||||
|
|
||||||
|
can-edit (:can-edit permisions)
|
||||||
|
can-invite (or (:is-owner permisions)
|
||||||
|
(:is-admin permisions))
|
||||||
|
|
||||||
|
show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true))
|
||||||
|
show-team-hero? (deref show-team-hero*)
|
||||||
|
|
||||||
|
my-penpot? (= (:default-team-id profile) team-id)
|
||||||
|
default-team? (:is-default team)
|
||||||
|
|
||||||
|
show-deleted? (:can-edit permisions)
|
||||||
|
|
||||||
|
projects
|
||||||
(mf/with-memo [projects]
|
(mf/with-memo [projects]
|
||||||
(->> projects
|
(->> projects
|
||||||
(remove :deleted-at)
|
(remove :deleted-at)
|
||||||
(sort-by :modified-at)
|
(sort-by :modified-at)
|
||||||
(reverse)))
|
(reverse)))
|
||||||
|
|
||||||
team-id (get team :id)
|
|
||||||
|
|
||||||
recent-map (mf/deref ref:recent-files)
|
|
||||||
permisions (:permissions team)
|
|
||||||
|
|
||||||
can-edit (:can-edit permisions)
|
|
||||||
can-invite (or (:is-owner permisions)
|
|
||||||
(:is-admin permisions))
|
|
||||||
|
|
||||||
show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true))
|
|
||||||
show-team-hero? (deref show-team-hero*)
|
|
||||||
|
|
||||||
is-my-penpot (= (:default-team-id profile) team-id)
|
|
||||||
is-defalt-team? (:is-default team)
|
|
||||||
|
|
||||||
on-close
|
on-close
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn []
|
(fn []
|
||||||
@@ -366,16 +369,20 @@
|
|||||||
[:*
|
[:*
|
||||||
(when (and show-team-hero?
|
(when (and show-team-hero?
|
||||||
can-invite
|
can-invite
|
||||||
(not is-defalt-team?))
|
(not default-team?))
|
||||||
[:> team-hero* {:team team :on-close on-close}])
|
[:> team-hero* {:team team :on-close on-close}])
|
||||||
|
|
||||||
[:div {:class (stl/css-case :dashboard-container true
|
[:div {:class (stl/css-case :dashboard-container true
|
||||||
:no-bg true
|
:no-bg true
|
||||||
:dashboard-projects true
|
:dashboard-projects true
|
||||||
:with-team-hero (and (not is-my-penpot)
|
:with-team-hero (and (not my-penpot?)
|
||||||
(not is-defalt-team?)
|
(not default-team?)
|
||||||
show-team-hero?
|
show-team-hero?
|
||||||
can-invite))}
|
can-invite))}
|
||||||
|
|
||||||
|
(when show-deleted?
|
||||||
|
[:> deleted/menu* {:team-id team-id :section :dashboard-recent}])
|
||||||
|
|
||||||
(for [{:keys [id] :as project} projects]
|
(for [{:keys [id] :as project} projects]
|
||||||
;; FIXME: refactor this, looks inneficient
|
;; FIXME: refactor this, looks inneficient
|
||||||
(let [files (when recent-map
|
(let [files (when recent-map
|
||||||
|
|||||||
@@ -4,16 +4,21 @@
|
|||||||
//
|
//
|
||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "common/refactor/common-refactor.scss" as deprecated;
|
||||||
@use "common/refactor/common-dashboard";
|
@use "common/refactor/common-dashboard";
|
||||||
|
@use "../ds/typography.scss" as t;
|
||||||
|
@use "../ds/_borders.scss" as *;
|
||||||
|
@use "../ds/spacing.scss" as *;
|
||||||
|
@use "../ds/_sizes.scss" as *;
|
||||||
|
@use "../ds/z-index.scss" as *;
|
||||||
|
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-right: deprecated.$s-16;
|
margin-inline-end: var(--sp-l);
|
||||||
border-top: deprecated.$s-1 solid var(--panel-border-color);
|
border-top: $b-1 solid var(--panel-border-color);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-bottom: deprecated.$s-32;
|
padding-bottom: var(--sp-xxxl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-projects {
|
.dashboard-projects {
|
||||||
@@ -27,16 +32,16 @@
|
|||||||
|
|
||||||
.dashboard-shared {
|
.dashboard-shared {
|
||||||
width: calc(100vw - deprecated.$s-320);
|
width: calc(100vw - deprecated.$s-320);
|
||||||
margin-right: deprecated.$s-52;
|
margin-inline-end: deprecated.$s-52;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
margin-top: deprecated.$s-12;
|
margin-block-start: var(--sp-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-project-row {
|
.dashboard-project-row {
|
||||||
--actions-opacity: 0;
|
--actions-opacity: 0;
|
||||||
margin-bottom: deprecated.$s-24;
|
margin-block-end: var(--sp-xxl);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
@@ -60,12 +65,12 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: deprecated.$s-8;
|
gap: var(--sp-s);
|
||||||
width: 99%;
|
width: 99%;
|
||||||
max-height: deprecated.$s-40;
|
max-height: $sz-40;
|
||||||
padding: deprecated.$s-8 deprecated.$s-8 deprecated.$s-8 deprecated.$s-16;
|
padding: var(--sp-s) var(--sp-s) var(--sp-s) var(--sp-l);
|
||||||
margin-top: deprecated.$s-16;
|
margin-block-start: var(--sp-l);
|
||||||
border-radius: deprecated.$br-4;
|
border-radius: $br-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-name-wrapper {
|
.project-name-wrapper {
|
||||||
@@ -73,30 +78,29 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: deprecated.$s-32;
|
min-height: var(--sp-xxxl);
|
||||||
margin-left: deprecated.$s-8;
|
margin-inline-start: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-name {
|
.project-name {
|
||||||
@include deprecated.bodyLargeTypography;
|
@include t.use-typography("body-large");
|
||||||
@include deprecated.textEllipsis;
|
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin-right: deprecated.$s-12;
|
margin-inline-end: var(--sp-m);
|
||||||
line-height: 0.8;
|
line-height: 0.8;
|
||||||
color: var(--title-foreground-color-hover);
|
color: var(--title-foreground-color-hover);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: deprecated.$s-16;
|
height: var(--sp-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-wrapper {
|
.info-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: deprecated.$s-8;
|
gap: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.info,
|
.info,
|
||||||
.recent-files-row-title-info {
|
.recent-files-row-title-info {
|
||||||
@include deprecated.bodyMediumTypography;
|
@include t.use-typography("body-medium");
|
||||||
color: var(--title-foreground-color);
|
color: var(--title-foreground-color);
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -106,16 +110,16 @@
|
|||||||
.project-actions {
|
.project-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
opacity: var(--actions-opacity);
|
opacity: var(--actions-opacity);
|
||||||
margin-left: deprecated.$s-32;
|
margin-inline-start: var(--sp-xxxl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-file-btn,
|
.add-file-btn,
|
||||||
.options-btn {
|
.options-btn {
|
||||||
@extend .button-tertiary;
|
@extend .button-tertiary;
|
||||||
height: deprecated.$s-32;
|
height: var(--sp-xxxl);
|
||||||
width: deprecated.$s-32;
|
width: var(--sp-xxxl);
|
||||||
margin: 0 deprecated.$s-8;
|
margin: 0 var(--sp-s);
|
||||||
padding: deprecated.$s-8;
|
padding: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-icon,
|
.add-icon,
|
||||||
@@ -126,24 +130,24 @@
|
|||||||
|
|
||||||
.grid-container {
|
.grid-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 deprecated.$s-4;
|
padding: 0 var(--sp-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-placement {
|
.placeholder-placement {
|
||||||
margin: deprecated.$s-16 deprecated.$s-32;
|
margin: var(--sp-l) var(--sp-xxxl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-more {
|
.show-more {
|
||||||
--show-more-color: var(--button-secondary-foreground-color-rest);
|
--show-more-color: var(--button-secondary-foreground-color-rest);
|
||||||
@include deprecated.buttonStyle;
|
@include deprecated.buttonStyle;
|
||||||
@include deprecated.bodyMediumTypography;
|
@include t.use-typography("body-medium");
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: deprecated.$s-8;
|
top: var(--sp-s);
|
||||||
right: deprecated.$s-52;
|
right: deprecated.$s-52;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
column-gap: deprecated.$s-12;
|
column-gap: var(--sp-m);
|
||||||
color: var(--show-more-color);
|
color: var(--show-more-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -152,8 +156,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.show-more-icon {
|
.show-more-icon {
|
||||||
height: deprecated.$s-16;
|
height: var(--sp-l);
|
||||||
width: deprecated.$s-16;
|
width: var(--sp-l);
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: var(--show-more-color);
|
stroke: var(--show-more-color);
|
||||||
}
|
}
|
||||||
@@ -164,13 +168,13 @@
|
|||||||
border-radius: deprecated.$br-8;
|
border-radius: deprecated.$br-8;
|
||||||
border: none;
|
border: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: deprecated.$s-16;
|
margin: var(--sp-l);
|
||||||
padding: deprecated.$s-8;
|
padding: var(--sp-s);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border-radius: deprecated.$br-4;
|
border-radius: $br-4;
|
||||||
height: deprecated.$s-200;
|
height: var(--sp-xl) 0;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
@@ -185,18 +189,18 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: deprecated.$s-20 deprecated.$s-20;
|
padding: var(--sp-xl) var(--sp-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: deprecated.$fs-24;
|
font-size: $sz-24;
|
||||||
color: var(--color-foreground-primary);
|
color: var(--color-foreground-primary);
|
||||||
font-weight: deprecated.$fw400;
|
font-weight: deprecated.$fw400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: deprecated.$fs-16;
|
font-size: $sz-16;
|
||||||
span {
|
span {
|
||||||
color: var(--color-foreground-secondary);
|
color: var(--color-foreground-secondary);
|
||||||
display: block;
|
display: block;
|
||||||
@@ -204,15 +208,15 @@
|
|||||||
a {
|
a {
|
||||||
color: var(--color-accent-primary);
|
color: var(--color-accent-primary);
|
||||||
}
|
}
|
||||||
padding: deprecated.$s-8 0;
|
padding: var(--sp-s) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
--close-icon-foreground-color: var(--icon-foreground);
|
--close-icon-foreground-color: var(--icon-foreground);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: deprecated.$s-20;
|
top: var(--sp-xl);
|
||||||
right: deprecated.$s-24;
|
right: var(--sp-xxl);
|
||||||
width: deprecated.$s-24;
|
width: var(--sp-xxl);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -227,7 +231,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.invite {
|
.invite {
|
||||||
height: deprecated.$s-32;
|
height: var(--sp-xxxl);
|
||||||
width: deprecated.$s-180;
|
width: deprecated.$s-180;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,8 +239,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: deprecated.$s-200;
|
width: var(--sp-xl) 0;
|
||||||
height: deprecated.$s-200;
|
height: var(--sp-xl) 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: deprecated.$br-4;
|
border-radius: deprecated.$br-4;
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
|
|||||||
@@ -27,11 +27,11 @@
|
|||||||
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
|
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
|
||||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||||
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
|
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
|
||||||
[app.main.ui.dashboard.subscription :refer [subscription-sidebar*
|
[app.main.ui.dashboard.subscription :refer [dashboard-cta*
|
||||||
|
get-subscription-type
|
||||||
menu-team-icon*
|
menu-team-icon*
|
||||||
dashboard-cta*
|
|
||||||
show-subscription-dashboard-banner?
|
show-subscription-dashboard-banner?
|
||||||
get-subscription-type]]
|
subscription-sidebar*]]
|
||||||
[app.main.ui.dashboard.team-form]
|
[app.main.ui.dashboard.team-form]
|
||||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||||
[app.main.ui.icons :as deprecated-icon]
|
[app.main.ui.icons :as deprecated-icon]
|
||||||
|
|||||||
@@ -205,7 +205,10 @@
|
|||||||
:cmd :export-frames
|
:cmd :export-frames
|
||||||
:origin origin}]))
|
:origin origin}]))
|
||||||
|
|
||||||
(mf/defc export-progress-widget
|
;; FIXME: deprecated, should be refactored in two components and use
|
||||||
|
;; the generic progress reporter
|
||||||
|
|
||||||
|
(mf/defc progress-widget
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[]
|
[]
|
||||||
(let [state (mf/deref refs/export)
|
(let [state (mf/deref refs/export)
|
||||||
@@ -217,11 +220,11 @@
|
|||||||
detail-visible? (:detail-visible state)
|
detail-visible? (:detail-visible state)
|
||||||
widget-visible? (:widget-visible state)
|
widget-visible? (:widget-visible state)
|
||||||
progress (:progress state)
|
progress (:progress state)
|
||||||
exports (:exports state)
|
items (:exports state)
|
||||||
total (count exports)
|
total (or (:total state) (count items))
|
||||||
complete? (= progress total)
|
complete? (= progress total)
|
||||||
circ (* 2 Math/PI 12)
|
circ (* 2 Math/PI 12)
|
||||||
pct (- circ (* circ (/ progress total)))
|
pct (if (zero? total) circ (- circ (* circ (/ progress total))))
|
||||||
|
|
||||||
pwidth
|
pwidth
|
||||||
(if error?
|
(if error?
|
||||||
@@ -243,16 +246,20 @@
|
|||||||
|
|
||||||
title
|
title
|
||||||
(cond
|
(cond
|
||||||
error? (tr "workspace.options.exporting-object-error")
|
error? (tr "workspace.options.exporting-object-error")
|
||||||
complete? (tr "workspace.options.exporting-complete")
|
complete? (tr "workspace.options.exporting-complete")
|
||||||
healthy? (tr "workspace.options.exporting-object")
|
healthy? (tr "workspace.options.exporting-object")
|
||||||
(not healthy?) (tr "workspace.options.exporting-object-slow"))
|
(not healthy?) (tr "workspace.options.exporting-object-slow"))
|
||||||
|
|
||||||
retry-last-export
|
retry-last-operation
|
||||||
(mf/use-fn #(st/emit! (de/retry-last-export)))
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(st/emit! (de/retry-last-export))))
|
||||||
|
|
||||||
toggle-detail-visibility
|
toggle-detail-visibility
|
||||||
(mf/use-fn #(st/emit! (de/toggle-detail-visibililty)))]
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(st/emit! (de/toggle-detail-visibililty))))]
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
(when widget-visible?
|
(when widget-visible?
|
||||||
@@ -283,11 +290,11 @@
|
|||||||
error-icon
|
error-icon
|
||||||
neutral-icon)
|
neutral-icon)
|
||||||
|
|
||||||
[:p {:class (stl/css :export-progress-title)}
|
[:div {:class (stl/css :export-progress-title)}
|
||||||
title
|
[:div {:class (stl/css :title-text)} title]
|
||||||
(if error?
|
(if error?
|
||||||
[:button {:class (stl/css :retry-btn)
|
[:button {:class (stl/css :retry-btn)
|
||||||
:on-click retry-last-export}
|
:on-click retry-last-operation}
|
||||||
(tr "workspace.options.retry")]
|
(tr "workspace.options.retry")]
|
||||||
|
|
||||||
[:span {:class (stl/css :progress)}
|
[:span {:class (stl/css :progress)}
|
||||||
|
|||||||
@@ -12,14 +12,17 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--sp-l);
|
gap: var(--sp-l);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value
|
max-height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value
|
||||||
padding-top: var(--sp-s);
|
padding-top: var(--sp-s);
|
||||||
|
padding-inline: var(--sp-m);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
|
background-color: var(--low-emphasis-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-element-options {
|
.workspace-element-options {
|
||||||
height: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value
|
max-height: calc(100vh - px2rem(180)); // TODO: Fix this hardcoded value
|
||||||
padding-inline: var(--sp-m);
|
padding-inline: var(--sp-m);
|
||||||
|
background-color: var(--low-emphasis-background);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user