mirror of
https://github.com/penpot/penpot.git
synced 2025-12-23 22:48:40 -05:00
Compare commits
11 Commits
niwinz-dev
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69c880d00e | ||
|
|
9eebc467ef | ||
|
|
b77712ce73 | ||
|
|
3d3e81f314 | ||
|
|
fe6441bb24 | ||
|
|
e15f0baf30 | ||
|
|
c040cbb784 | ||
|
|
7f674b78a9 | ||
|
|
099b78affd | ||
|
|
78cc3f0aa4 | ||
|
|
76f5f12808 |
@@ -704,6 +704,7 @@
|
||||
f.created_at,
|
||||
f.modified_at,
|
||||
f.name,
|
||||
f.is_shared,
|
||||
f.deleted_at AS will_be_deleted_at,
|
||||
ft.media_id AS thumbnail_id,
|
||||
row_number() OVER w AS row_num,
|
||||
@@ -813,7 +814,7 @@
|
||||
AND (f.deleted_at IS NULL OR f.deleted_at > now())
|
||||
ORDER BY f.created_at ASC;")
|
||||
|
||||
(defn- absorb-library-by-file
|
||||
(defn- absorb-library-by-file!
|
||||
[cfg ldata file-id]
|
||||
|
||||
(assert (db/connection-map? cfg)
|
||||
@@ -837,7 +838,7 @@
|
||||
:modified-at (ct/now)
|
||||
:has-media-trimmed false}))))
|
||||
|
||||
(defn- absorb-library*
|
||||
(defn- absorb-library
|
||||
"Find all files using a shared library, and absorb all library assets
|
||||
into the file local libraries"
|
||||
[cfg {:keys [id data] :as library}]
|
||||
@@ -852,10 +853,10 @@
|
||||
:library-id (str id)
|
||||
: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))
|
||||
|
||||
(defn absorb-library
|
||||
(defn absorb-library!
|
||||
[{:keys [::db/conn] :as cfg} id]
|
||||
(let [file (-> (bfc/get-file cfg id
|
||||
:realize? true
|
||||
@@ -872,7 +873,7 @@
|
||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-file-features! (:features file)))
|
||||
|
||||
(absorb-library* cfg file)))
|
||||
(absorb-library cfg file)))
|
||||
|
||||
(defn- set-file-shared
|
||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}]
|
||||
@@ -885,14 +886,14 @@
|
||||
;; file, we need to perform more complex operation,
|
||||
;; so in this case we retrieve the complete file and
|
||||
;; perform all required validations.
|
||||
(let [file (-> (absorb-library cfg id)
|
||||
(let [file (-> (absorb-library! cfg id)
|
||||
(assoc :is-shared false))]
|
||||
(db/delete! conn :file-library-rel {:library-file-id id})
|
||||
(db/update! conn :file
|
||||
{:is-shared false
|
||||
:modified-at (ct/now)}
|
||||
{:id id})
|
||||
file)
|
||||
(select-keys file [:id :name :is-shared]))
|
||||
|
||||
(and (false? (:is-shared file))
|
||||
(true? (:is-shared params)))
|
||||
@@ -939,11 +940,6 @@
|
||||
{:id file-id}
|
||||
{::db/return-keys [:id :name :is-shared :deleted-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/task :delete-object
|
||||
::wrk/params {:object :file
|
||||
@@ -1094,53 +1090,47 @@
|
||||
|
||||
;; --- MUTATION COMMAND: delete-files-immediatelly
|
||||
|
||||
(def ^:private sql:get-delete-team-files-candidates
|
||||
"SELECT f.id
|
||||
FROM file AS f
|
||||
JOIN project AS p ON (p.id = f.project_id)
|
||||
JOIN team AS t ON (t.id = p.team_id)
|
||||
WHERE t.deleted_at IS NULL
|
||||
AND t.id = ?
|
||||
AND f.id = ANY(?::uuid[])")
|
||||
(def ^:private sql:delete-team-files
|
||||
"UPDATE file AS uf SET deleted_at = ?::timestamptz
|
||||
FROM (
|
||||
SELECT f.id
|
||||
FROM file AS f
|
||||
JOIN project AS p ON (p.id = f.project_id)
|
||||
JOIN team AS t ON (t.id = p.team_id)
|
||||
WHERE t.deleted_at IS NULL
|
||||
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
|
||||
[:map {:title "permanently-delete-team-files"}
|
||||
[:team-id ::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
|
||||
"Mark the specified files to be deleted immediatelly on the
|
||||
specified team. The team-id on params will be used to filter and
|
||||
check writable permissons on team."
|
||||
|
||||
{::doc/added "2.13"
|
||||
::sm/params schema:permanently-delete-team-files}
|
||||
{::doc/added "2.12"
|
||||
::sm/params schema:permanently-delete-team-files
|
||||
::db/transaction true}
|
||||
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id] :as params}]
|
||||
(teams/check-edition-permissions! pool profile-id team-id)
|
||||
(sse/response #(db/tx-run! cfg permanently-delete-team-files params)))
|
||||
[{:keys [::db/conn]} {:keys [::rpc/profile-id ::rpc/request-at team-id ids]}]
|
||||
(teams/check-edition-permissions! conn profile-id team-id)
|
||||
|
||||
(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
|
||||
|
||||
@@ -1204,7 +1194,7 @@
|
||||
{:keys [files projects]}
|
||||
(reduce (fn [result {:keys [id project-id]}]
|
||||
(let [index (-> result :files count)]
|
||||
(events/tap :progress {:file-id id :index (inc index) :total total-files})
|
||||
(events/tap :progress {:file-id id :index index :total total-files})
|
||||
(restore-file conn id)
|
||||
|
||||
(-> result
|
||||
@@ -1227,7 +1217,7 @@
|
||||
(sv/defmethod ::restore-deleted-team-files
|
||||
"Removes the deletion mark from the specified files (and respective
|
||||
projects) on the specified team."
|
||||
{::doc/added "2.13"
|
||||
{::doc/added "2.12"
|
||||
::sse/stream? true
|
||||
::sm/params schema:restore-deleted-team-files}
|
||||
[cfg params]
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
:deleted-at (ct/format-inst deleted-at))
|
||||
|
||||
(db/update! conn :file
|
||||
{:deleted-at deleted-at
|
||||
:is-shared false}
|
||||
{:deleted-at deleted-at}
|
||||
{:id id}
|
||||
{::db/return-keys false})
|
||||
|
||||
@@ -54,7 +53,7 @@
|
||||
(not *team-deletion*))
|
||||
;; NOTE: we don't prevent file deletion on absorb operation failure
|
||||
(try
|
||||
(db/tx-run! cfg files/absorb-library id)
|
||||
(db/tx-run! cfg files/absorb-library! id)
|
||||
(catch Throwable cause
|
||||
(l/warn :hint "error on absorbing library"
|
||||
:file-id id
|
||||
|
||||
@@ -595,8 +595,8 @@
|
||||
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
|
||||
(into []
|
||||
(map (fn [{:keys [event data]}]
|
||||
(d/vec2 (keyword event)
|
||||
(tr/decode-str data))))
|
||||
[(keyword event)
|
||||
(tr/decode-str data)]))
|
||||
(parse-sse (slurp' input)))
|
||||
(finally
|
||||
(.close input)))))
|
||||
|
||||
@@ -1921,11 +1921,7 @@
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (fn? result))
|
||||
|
||||
(let [[ev1 ev2 :as events] (th/consume-sse result)]
|
||||
(t/is (= 2 (count events)))
|
||||
(t/is (= (:ids data) (val ev2)))))
|
||||
(t/is (= (:ids data) result)))
|
||||
|
||||
(let [row (th/db-exec-one! ["select * from file where id = ?" file-id])]
|
||||
(t/is (= (:deleted-at row) now)))))))
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||
integrant/integrant {:mvn/version "1.0.0"}
|
||||
|
||||
funcool/tubax {:mvn/version "2021.05.20-0"}
|
||||
funcool/cuerdas {:mvn/version "2026.415"}
|
||||
funcool/promesa
|
||||
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
|
||||
|
||||
@@ -340,7 +340,7 @@
|
||||
(dfn-diff t2 t1)))
|
||||
|
||||
#?(:cljs
|
||||
(defn set-default-locale!
|
||||
(defn set-default-locale
|
||||
[locale]
|
||||
(when-let [locale (unchecked-get locales locale)]
|
||||
(dfn-set-default-options #js {:locale locale}))))
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.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))
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
|
||||
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
@@ -16,9 +16,9 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"generic-pool": "^3.9.0",
|
||||
"inflation": "^2.1.0",
|
||||
"ioredis": "^5.8.1",
|
||||
"playwright": "^1.55.1",
|
||||
"raw-body": "^3.0.1",
|
||||
"ioredis": "^5.8.2",
|
||||
"playwright": "^1.57.0",
|
||||
"raw-body": "^3.0.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"svgo": "penpot/svgo#v3.1",
|
||||
"undici": "^7.16.0",
|
||||
|
||||
@@ -243,7 +243,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bytes@npm:3.1.2":
|
||||
"bytes@npm:~3.1.2":
|
||||
version: 3.1.2
|
||||
resolution: "bytes@npm:3.1.2"
|
||||
checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e
|
||||
@@ -442,7 +442,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"depd@npm:2.0.0, depd@npm:~2.0.0":
|
||||
"depd@npm:~2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "depd@npm:2.0.0"
|
||||
checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c
|
||||
@@ -577,9 +577,9 @@ __metadata:
|
||||
date-fns: "npm:^4.1.0"
|
||||
generic-pool: "npm:^3.9.0"
|
||||
inflation: "npm:^2.1.0"
|
||||
ioredis: "npm:^5.8.1"
|
||||
playwright: "npm:^1.55.1"
|
||||
raw-body: "npm:^3.0.1"
|
||||
ioredis: "npm:^5.8.2"
|
||||
playwright: "npm:^1.57.0"
|
||||
raw-body: "npm:^3.0.2"
|
||||
source-map-support: "npm:^0.5.21"
|
||||
svgo: "penpot/svgo#v3.1"
|
||||
undici: "npm:^7.16.0"
|
||||
@@ -683,16 +683,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-errors@npm:2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "http-errors@npm:2.0.0"
|
||||
"http-errors@npm:~2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "http-errors@npm:2.0.1"
|
||||
dependencies:
|
||||
depd: "npm:2.0.0"
|
||||
inherits: "npm:2.0.4"
|
||||
setprototypeof: "npm:1.2.0"
|
||||
statuses: "npm:2.0.1"
|
||||
toidentifier: "npm:1.0.1"
|
||||
checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19
|
||||
depd: "npm:~2.0.0"
|
||||
inherits: "npm:~2.0.4"
|
||||
setprototypeof: "npm:~1.2.0"
|
||||
statuses: "npm:~2.0.2"
|
||||
toidentifier: "npm:~1.0.1"
|
||||
checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -716,15 +716,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 0.6.3
|
||||
resolution: "iconv-lite@npm:0.6.3"
|
||||
@@ -734,6 +725,15 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 1.2.1
|
||||
resolution: "ieee754@npm:1.2.1"
|
||||
@@ -755,16 +755,16 @@ __metadata:
|
||||
languageName: node
|
||||
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
|
||||
resolution: "inherits@npm:2.0.4"
|
||||
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ioredis@npm:^5.8.1":
|
||||
version: 5.8.1
|
||||
resolution: "ioredis@npm:5.8.1"
|
||||
"ioredis@npm:^5.8.2":
|
||||
version: 5.8.2
|
||||
resolution: "ioredis@npm:5.8.2"
|
||||
dependencies:
|
||||
"@ioredis/commands": "npm:1.4.0"
|
||||
cluster-key-slot: "npm:^1.1.0"
|
||||
@@ -775,7 +775,7 @@ __metadata:
|
||||
redis-errors: "npm:^1.2.0"
|
||||
redis-parser: "npm:^3.0.0"
|
||||
standard-as-callback: "npm:^2.1.0"
|
||||
checksum: 10c0/4ed66444017150da027bce940a24bf726994691e2a7b3aa11d52f8aeb37f258068cc171af4d9c61247acafc28eb086fa8a7c79420b8e8d2907d2f74f39584465
|
||||
checksum: 10c0/305e385f811d49908899e32c2de69616cd059f909afd9e0a53e54f596b1a5835ee3449bfc6a3c49afbc5a2fd27990059e316cc78f449c94024957bd34c826d88
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1106,27 +1106,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright-core@npm:1.55.1":
|
||||
version: 1.55.1
|
||||
resolution: "playwright-core@npm:1.55.1"
|
||||
"playwright-core@npm:1.57.0":
|
||||
version: 1.57.0
|
||||
resolution: "playwright-core@npm:1.57.0"
|
||||
bin:
|
||||
playwright-core: cli.js
|
||||
checksum: 10c0/39837a8c1232ec27486eac8c3fcacc0b090acc64310f7f9004b06715370fc426f944e3610fe8c29f17cd3d68280ed72c75f660c02aa5b5cf0eb34bab0031308f
|
||||
checksum: 10c0/798e35d83bf48419a8c73de20bb94d68be5dde68de23f95d80a0ebe401e3b83e29e3e84aea7894d67fa6c79d2d3d40cc5bcde3e166f657ce50987aaa2421b6a9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright@npm:^1.55.1":
|
||||
version: 1.55.1
|
||||
resolution: "playwright@npm:1.55.1"
|
||||
"playwright@npm:^1.57.0":
|
||||
version: 1.57.0
|
||||
resolution: "playwright@npm:1.57.0"
|
||||
dependencies:
|
||||
fsevents: "npm:2.3.2"
|
||||
playwright-core: "npm:1.55.1"
|
||||
playwright-core: "npm:1.57.0"
|
||||
dependenciesMeta:
|
||||
fsevents:
|
||||
optional: true
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: 10c0/b84a97b0d764403df512f5bbb10c7343974e151a28202cc06f90883a13e8a45f4491a0597f0ae5fb03a026746cbc0d200f0f32195bfaa381aee5ca5770626771
|
||||
checksum: 10c0/ab03c99a67b835bdea9059f516ad3b6e42c21025f9adaa161a4ef6bc7ca716dcba476d287140bb240d06126eb23f889a8933b8f5f1f1a56b80659d92d1358899
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1161,15 +1161,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"raw-body@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "raw-body@npm:3.0.1"
|
||||
"raw-body@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "raw-body@npm:3.0.2"
|
||||
dependencies:
|
||||
bytes: "npm:3.1.2"
|
||||
http-errors: "npm:2.0.0"
|
||||
iconv-lite: "npm:0.7.0"
|
||||
unpipe: "npm:1.0.0"
|
||||
checksum: 10c0/892f4fbd21ecab7e2fed0f045f7af9e16df7e8050879639d4e482784a2f4640aaaa33d916a0e98013f23acb82e09c2e3c57f84ab97104449f728d22f65a7d79a
|
||||
bytes: "npm:~3.1.2"
|
||||
http-errors: "npm:~2.0.1"
|
||||
iconv-lite: "npm:~0.7.0"
|
||||
unpipe: "npm:~1.0.0"
|
||||
checksum: 10c0/d266678d08e1e7abea62c0ce5864344e980fa81c64f6b481e9842c5beaed2cdcf975f658a3ccd67ad35fc919c1f6664ccc106067801850286a6cbe101de89f29
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1270,7 +1270,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"setprototypeof@npm:1.2.0":
|
||||
"setprototypeof@npm:~1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "setprototypeof@npm:1.2.0"
|
||||
checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc
|
||||
@@ -1368,10 +1368,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"statuses@npm:2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "statuses@npm:2.0.1"
|
||||
checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0
|
||||
"statuses@npm:~2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "statuses@npm:2.0.2"
|
||||
checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1500,7 +1500,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"toidentifier@npm:1.0.1":
|
||||
"toidentifier@npm:~1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "toidentifier@npm:1.0.1"
|
||||
checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1
|
||||
@@ -1539,7 +1539,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unpipe@npm:1.0.0":
|
||||
"unpipe@npm:~1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "unpipe@npm:1.0.0"
|
||||
checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
metosin/reitit-core {:mvn/version "0.9.1"}
|
||||
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
|
||||
{:git/tag "v2.2"
|
||||
:git/sha "0f7e15a"
|
||||
|
||||
@@ -53,83 +53,74 @@
|
||||
"watch:storybook:assets": "node ./scripts/watch-storybook.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@penpot/draft-js": "portal:./packages/draft-js",
|
||||
"@penpot/mousetrap": "portal:./packages/mousetrap",
|
||||
"@penpot/plugins-runtime": "1.3.2",
|
||||
"@penpot/svgo": "penpot/svgo#v3.2",
|
||||
"@penpot/text-editor": "portal:./text-editor",
|
||||
"@playwright/test": "1.52.0",
|
||||
"@storybook/addon-docs": "10.0.4",
|
||||
"@storybook/addon-themes": "10.0.4",
|
||||
"@storybook/addon-vitest": "10.0.4",
|
||||
"@storybook/react-vite": "10.0.4",
|
||||
"@tokens-studio/sd-transforms": "1.2.11",
|
||||
"@types/node": "^22.15.21",
|
||||
"@vitest/browser": "3.2.4",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@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",
|
||||
"compression": "^1.8.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"eventsource-parser": "^3.0.6",
|
||||
"express": "^5.1.0",
|
||||
"fancy-log": "^2.0.0",
|
||||
"getopts": "^2.3.0",
|
||||
"gettext-parser": "^8.0.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-gzip": "^1.4.2",
|
||||
"gulp-mustache": "^5.0.0",
|
||||
"gulp-postcss": "^10.0.0",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-svg-sprite": "^2.0.3",
|
||||
"highlight.js": "^11.10.0",
|
||||
"js-beautify": "^1.15.4",
|
||||
"jsdom": "^27.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"map-stream": "0.0.7",
|
||||
"marked": "^15.0.12",
|
||||
"mkdirp": "^3.0.1",
|
||||
"mustache": "^4.2.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"opentype.js": "^1.3.4",
|
||||
"p-limit": "^6.2.0",
|
||||
"playwright": "1.56.1",
|
||||
"postcss": "^8.5.4",
|
||||
"postcss-clean": "^1.2.2",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"prettier": "3.5.3",
|
||||
"pretty-time": "^1.1.0",
|
||||
"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",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-virtualized": "^9.22.6",
|
||||
"rimraf": "^6.0.1",
|
||||
"rxjs": "8.0.0-alpha.14",
|
||||
"sass": "^1.89.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"sax": "^1.4.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"storybook": "10.0.4",
|
||||
"style-dictionary": "5.0.0-rc.1",
|
||||
"svg-sprite": "^2.0.4",
|
||||
"tdigest": "^0.1.2",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"typescript": "^5.9.2",
|
||||
"ua-parser-js": "2.0.5",
|
||||
"vite": "^6.3.5",
|
||||
"vitest": "^3.2.0",
|
||||
"wasm-pack": "^0.13.1",
|
||||
"watcher": "^2.3.1",
|
||||
"workerpool": "^9.3.2",
|
||||
"xregexp": "^5.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ export const {
|
||||
RichTextEditorUtil,
|
||||
SelectionState,
|
||||
convertFromRaw,
|
||||
convertToRaw
|
||||
convertToRaw,
|
||||
EditorBlock,
|
||||
Editor
|
||||
} = pkg;
|
||||
|
||||
import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor.js';
|
||||
@@ -8,7 +8,8 @@
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
|
||||
"draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0",
|
||||
"immutable": "^5.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=0.17.0",
|
||||
@@ -173,12 +173,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@penpot/draft-js-wrapper@workspace:.":
|
||||
"@penpot/draft-js@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@penpot/draft-js-wrapper@workspace:."
|
||||
resolution: "@penpot/draft-js@workspace:."
|
||||
dependencies:
|
||||
draft-js: "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
|
||||
esbuild: "npm:^0.24.0"
|
||||
immutable: "npm:^5.1.4"
|
||||
peerDependencies:
|
||||
react: ">=0.17.0"
|
||||
react-dom: ">=0.17.0"
|
||||
@@ -320,6 +321,13 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 3.7.6
|
||||
resolution: "immutable@npm:3.7.6"
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
<link rel="icon" href="images/favicon.png" />
|
||||
|
||||
<script type="importmap">{{& manifest.importmap }}</script>
|
||||
|
||||
<script type="module">
|
||||
globalThis.penpotVersion = "{{& version}}";
|
||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||
@@ -33,7 +35,6 @@
|
||||
{{# manifest}}
|
||||
<script src="{{& config}}"></script>
|
||||
<script src="{{& polyfills}}"></script>
|
||||
<script type="importmap">{{& importmap }}</script>
|
||||
{{/manifest}}
|
||||
|
||||
<!--cookie-consent-->
|
||||
@@ -49,7 +50,9 @@
|
||||
<script type="module" src="{{& libs}}"></script>
|
||||
<script type="module">
|
||||
import { init } from "{{& app_main}}";
|
||||
init();
|
||||
import defaultTranslations from "{{& default_translations}}";
|
||||
|
||||
init({defaultTranslations});
|
||||
</script>
|
||||
{{/manifest}}
|
||||
</body>
|
||||
|
||||
@@ -187,7 +187,7 @@ async function readManifestFile(resource) {
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
async function readShadowManifest() {
|
||||
async function generateManifest() {
|
||||
const index = {
|
||||
app_main: "./js/main.js",
|
||||
render_main: "./js/render.js",
|
||||
@@ -197,6 +197,7 @@ async function readShadowManifest() {
|
||||
polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION,
|
||||
libs: "./js/libs.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({
|
||||
"imports": {
|
||||
@@ -392,7 +393,7 @@ async function generateTemplates() {
|
||||
const isDebug = process.env.NODE_ENV !== "production";
|
||||
await fs.mkdir("./resources/public/", { recursive: true });
|
||||
|
||||
const manifest = await readShadowManifest();
|
||||
const manifest = await generateManifest();
|
||||
let content;
|
||||
|
||||
const iconsSprite = await fs.readFile(
|
||||
|
||||
@@ -90,7 +90,10 @@
|
||||
(rx/map #(ws/initialize)))))))
|
||||
|
||||
(defn ^:export init
|
||||
[]
|
||||
[options]
|
||||
(some-> (unchecked-get options "defaultTranslations")
|
||||
(i18n/set-default-translations))
|
||||
|
||||
(mw/init!)
|
||||
(i18n/init)
|
||||
(cur/init-styles)
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.team :as ctt]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.modal :as modal]
|
||||
@@ -230,91 +229,6 @@
|
||||
;; Delay so the navigation can finish
|
||||
(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
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -13,14 +13,12 @@
|
||||
[app.common.logging :as log]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.project :refer [valid-project?]]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.websocket :as dws]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
@@ -693,56 +691,6 @@
|
||||
|
||||
;; --- 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 1000
|
||||
: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/mapcat (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))
|
||||
@@ -750,189 +698,145 @@
|
||||
(assert (every? uuid? ids))
|
||||
|
||||
(ptk/reify ::delete-files-immediately
|
||||
ev/Event
|
||||
(-data [_]
|
||||
{:team-id team-id
|
||||
:num-files (count ids)})
|
||||
|
||||
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}))))))
|
||||
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :permanently-delete-team-files {:team-id team-id :ids ids})
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- Restore deleted files immediately
|
||||
|
||||
(defn- initialize-restore-status
|
||||
[files]
|
||||
(ptk/reify ::init-restore-status
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [restore-state {:in-progress true
|
||||
:healthy? true
|
||||
:error false
|
||||
:progress 0
|
||||
:widget-visible true
|
||||
:detail-visible true
|
||||
:files files
|
||||
:last-update (ct/now)
|
||||
:cmd :restore-files}]
|
||||
(assoc state :restore restore-state)))))
|
||||
|
||||
(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))
|
||||
(defn- update-restore-status
|
||||
[{:keys [index total] :as data}]
|
||||
(ptk/reify ::upd-restore-status
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [time-diff (ct/diff-ms (get-in state [:restore :last-update]) (ct/now))
|
||||
healthy? (< time-diff 6000)]
|
||||
(update state :restore assoc
|
||||
:progress index
|
||||
:total total
|
||||
:last-update (ct/now)
|
||||
:healthy? healthy?)))))
|
||||
|
||||
(ptk/reify ::restore-files
|
||||
(defn- complete-restore-status
|
||||
[]
|
||||
(ptk/reify ::comp-restore-status
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [total (get-in state [:restore :total])]
|
||||
(update state :restore assoc
|
||||
:in-progress false
|
||||
:progress total ; Ensure progress equals total on completion
|
||||
:last-update (ct/now))))))
|
||||
|
||||
(defn- error-restore-status
|
||||
[error]
|
||||
(ptk/reify ::err-restore-status
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :restore assoc
|
||||
:in-progress false
|
||||
:error error
|
||||
:last-update (ct/now)
|
||||
:healthy? false))))
|
||||
|
||||
(defn toggle-restore-detail-visibility
|
||||
[]
|
||||
(ptk/reify ::toggle-restore-detail
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:restore :detail-visible] not))))
|
||||
|
||||
(defn retry-last-restore
|
||||
[]
|
||||
(ptk/reify ::retry-restore
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; Reset restore state for retry - actual retry will be handled by UI
|
||||
(if (get state :restore)
|
||||
(update state :restore assoc :error false :in-progress false)
|
||||
state))))
|
||||
|
||||
(defn clear-restore-state
|
||||
[]
|
||||
(ptk/reify ::clear-restore
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(dissoc state :restore))))
|
||||
|
||||
(defn- projects-restored
|
||||
[team-id]
|
||||
(ptk/reify ::projects-restored
|
||||
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 1000
|
||||
: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))))))))))))
|
||||
|
||||
|
||||
;; Refetch projects to get the updated state without deleted-at
|
||||
(rx/of (fetch-projects team-id)))))
|
||||
|
||||
(defn restore-files-immediately
|
||||
[{:keys [team-id ids]}]
|
||||
(assert (uuid? team-id))
|
||||
(assert (set? ids))
|
||||
[{:keys [team-id ids] :as params}]
|
||||
(dm/assert! (uuid? team-id))
|
||||
(dm/assert! (set? ids))
|
||||
(dm/assert! (every? uuid? ids))
|
||||
|
||||
(ptk/reify ::restore-files-immediately
|
||||
ev/Event
|
||||
(-data [_]
|
||||
{:team-id team-id
|
||||
:num-files (count ids)})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [deleted-files
|
||||
(get state :deleted-files)
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
files (mapv #(hash-map :id %) ids)]
|
||||
|
||||
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))))))
|
||||
(rx/merge
|
||||
(rx/of (initialize-restore-status files))
|
||||
|
||||
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")))))]
|
||||
(->> (rp/cmd! ::sse/restore-deleted-team-files {:team-id team-id :ids ids})
|
||||
(rx/tap (fn [event]
|
||||
(let [payload (sse/get-payload event)
|
||||
type (sse/get-type event)]
|
||||
(when (and payload (= type "progress"))
|
||||
(let [{:keys [index total]} payload]
|
||||
(when (and index total)
|
||||
;; Dispatch progress update
|
||||
(st/emit! (update-restore-status {:index index :total total}))))))))
|
||||
(rx/filter sse/end-of-stream?)
|
||||
(rx/map sse/get-payload)
|
||||
(rx/tap on-success)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (complete-restore-status)
|
||||
(projects-restored team-id))))
|
||||
(rx/catch (fn [error]
|
||||
(rx/concat
|
||||
(rx/of (error-restore-status (ex-message error)))
|
||||
(on-error error)))))
|
||||
|
||||
(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}))))))
|
||||
(rx/of (ptk/data-event ::restore-start {:total (count ids)})))))))
|
||||
|
||||
@@ -98,9 +98,7 @@
|
||||
(def context
|
||||
(atom (d/without-nils (collect-context))))
|
||||
|
||||
(add-watch i18n/state "events"
|
||||
(fn [_ _ _ v]
|
||||
(swap! context assoc :locale (get v :locale))))
|
||||
(add-watch i18n/locale "events" #(swap! context assoc :locale %4))
|
||||
|
||||
;; --- EVENT TRANSLATION
|
||||
|
||||
|
||||
@@ -637,5 +637,5 @@
|
||||
(def persistence-state
|
||||
(l/derived (comp :status :persistence) st/state))
|
||||
|
||||
(def progress
|
||||
(l/derived :progress st/state))
|
||||
(def restore
|
||||
(l/derived :restore st/state))
|
||||
|
||||
@@ -87,9 +87,6 @@
|
||||
{:stream? true
|
||||
:form-data? true}
|
||||
|
||||
::sse/permanently-delete-team-files
|
||||
{:stream? true}
|
||||
|
||||
::sse/restore-deleted-team-files
|
||||
{:stream? true}
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.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"}}]]])])]))
|
||||
@@ -1,101 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@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;
|
||||
}
|
||||
@@ -19,7 +19,6 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.progress :refer [progress-notification-widget*]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.dashboard.deleted :refer [deleted-section*]]
|
||||
[app.main.ui.dashboard.files :refer [files-section*]]
|
||||
@@ -31,6 +30,7 @@
|
||||
[app.main.ui.dashboard.sidebar :refer [sidebar*]]
|
||||
[app.main.ui.dashboard.team :refer [team-settings-page* team-members-page* team-invitations-page* webhooks-page*]]
|
||||
[app.main.ui.dashboard.templates :refer [templates-section*]]
|
||||
[app.main.ui.exports.assets :refer [progress-widget]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.modal :refer [modal-container*]]
|
||||
[app.main.ui.workspace.plugins]
|
||||
@@ -87,7 +87,7 @@
|
||||
:on-click clear-selected-fn
|
||||
:ref container}
|
||||
|
||||
[:> progress-notification-widget*]
|
||||
[:& progress-widget {:operation :restore}]
|
||||
|
||||
(case section
|
||||
:dashboard-recent
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
@@ -26,8 +27,6 @@
|
||||
[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)))
|
||||
@@ -41,40 +40,57 @@
|
||||
[:h1 (tr "dashboard.projects-title")]]])
|
||||
|
||||
(mf/defc deleted-project-menu*
|
||||
[{:keys [project show on-close top left]}]
|
||||
[{:keys [project files team-id show on-close top left]}]
|
||||
(let [top (d/nilv top 0)
|
||||
left (d/nilv left 0)
|
||||
|
||||
file-ids
|
||||
(mf/with-memo [files]
|
||||
(into #{} d/xf:map-id files))
|
||||
|
||||
restore-fn
|
||||
(fn [_]
|
||||
(st/emit! (dd/restore-files-immediately
|
||||
(with-meta {:team-id team-id :ids file-ids}
|
||||
{:on-success #(st/emit! (ntf/success (tr "restore-modal.success-restore-immediately" (:name project)))
|
||||
(dd/fetch-projects team-id)
|
||||
(dd/fetch-deleted-files team-id))
|
||||
:on-error #(st/emit! (ntf/error (tr "restore-modal.error-restore-project" (:name project))))}))))
|
||||
|
||||
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})))))
|
||||
(fn []
|
||||
(st/emit!
|
||||
(modal/show {:type :confirm
|
||||
:title (tr "restore-modal.restore-project.title")
|
||||
:message (tr "restore-modal.restore-project.description" (:name project))
|
||||
:accept-style :primary
|
||||
:accept-label (tr "labels.continue")
|
||||
:on-accept restore-fn})))
|
||||
|
||||
delete-fn
|
||||
(fn [_]
|
||||
(st/emit! (ntf/success (tr "delete-forever-modal.success-delete-immediately" (:name project)))
|
||||
(dd/delete-files-immediately
|
||||
{:team-id team-id
|
||||
:ids file-ids})
|
||||
(dd/fetch-projects team-id)
|
||||
(dd/fetch-deleted-files team-id)))
|
||||
|
||||
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})))))
|
||||
(fn []
|
||||
(st/emit!
|
||||
(modal/show {:type :confirm
|
||||
:title (tr "delete-forever-modal.title")
|
||||
:message (tr "delete-forever-modal.delete-project.description" (:name project))
|
||||
:accept-label (tr "dashboard.deleted.delete-forever")
|
||||
:on-accept delete-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}])]
|
||||
[{:name (tr "dashboard.deleted.restore-project")
|
||||
:id "project-restore"
|
||||
:handler on-restore-project}
|
||||
{:name (tr "dashboard.deleted.delete-project")
|
||||
:id "project-delete"
|
||||
:handler on-delete-project}]]
|
||||
|
||||
[:> context-menu*
|
||||
{:on-close on-close
|
||||
@@ -86,8 +102,9 @@
|
||||
:options options}]))
|
||||
|
||||
(mf/defc deleted-project-item*
|
||||
{::mf/private true}
|
||||
[{:keys [project files]}]
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [project team files]}]
|
||||
(let [project-files (filterv #(= (:project-id %) (:id project)) files)
|
||||
|
||||
empty? (empty? project-files)
|
||||
@@ -153,6 +170,8 @@
|
||||
(when (:menu-open @local)
|
||||
[:> deleted-project-menu*
|
||||
{:project project
|
||||
:files project-files
|
||||
:team-id (:id team)
|
||||
:show (:menu-open @local)
|
||||
:left (+ 24 (:x (:menu-pos @local)))
|
||||
:top (:y (:menu-pos @local))
|
||||
@@ -174,34 +193,8 @@
|
||||
: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-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")]]))
|
||||
(def ^:private ref:deleted-files
|
||||
(l/derived :deleted-files st/state))
|
||||
|
||||
(mf/defc deleted-section*
|
||||
[{:keys [team projects]}]
|
||||
@@ -237,33 +230,53 @@
|
||||
(and (= "enterprise" sub-type) (not canceled?)) 90
|
||||
:else 7))
|
||||
|
||||
on-delete-all
|
||||
on-clear
|
||||
(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}))))))
|
||||
(when deleted-map
|
||||
(let [file-ids (into #{} (keys deleted-map))]
|
||||
(when (seq file-ids)
|
||||
(st/emit!
|
||||
(modal/show {:type :confirm
|
||||
:title (tr "delete-forever-modal.title")
|
||||
:message (tr "delete-forever-modal.delete-all.description" (count file-ids))
|
||||
:accept-label (tr "dashboard.deleted.delete-forever")
|
||||
:on-accept #(st/emit!
|
||||
(dd/delete-files-immediately
|
||||
{:team-id team-id
|
||||
:ids file-ids})
|
||||
(dd/fetch-projects team-id)
|
||||
(dd/fetch-deleted-files team-id))})))))))
|
||||
|
||||
restore-fn
|
||||
(fn [file-ids]
|
||||
(st/emit! (dd/restore-files-immediately
|
||||
(with-meta {:team-id team-id :ids file-ids}
|
||||
{:on-success #(st/emit! (dd/fetch-projects team-id)
|
||||
(dd/fetch-deleted-files team-id))
|
||||
:on-error #(st/emit! (ntf/error (tr "restore-modal.error-restore-files")))}))))
|
||||
|
||||
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}))))))]
|
||||
(when deleted-map
|
||||
(let [file-ids (into #{} (keys deleted-map))]
|
||||
(when (seq file-ids)
|
||||
(st/emit!
|
||||
(modal/show {:type :confirm
|
||||
:title (tr "restore-modal.restore-all.title")
|
||||
:message (tr "restore-modal.restore-all.description" (count file-ids))
|
||||
:accept-label (tr "labels.continue")
|
||||
:accept-style :primary
|
||||
:on-accept #(restore-fn file-ids)})))))))
|
||||
|
||||
on-recent-click
|
||||
(mf/use-fn
|
||||
(mf/deps team-id)
|
||||
(fn []
|
||||
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id))))]
|
||||
|
||||
(mf/with-effect [team-id]
|
||||
(st/emit! (dd/fetch-projects team-id)
|
||||
@@ -276,26 +289,35 @@
|
||||
[:*
|
||||
[:div {:class (stl/css :no-bg)}
|
||||
|
||||
[:> menu* {:team-id team-id :section :dashboard-deleted}]
|
||||
[:div {:class (stl/css :nav-options)}
|
||||
[:> button* {:variant "ghost"
|
||||
:data-testid "recent-tab"
|
||||
:type "button"
|
||||
:on-click on-recent-click}
|
||||
(tr "dashboard.labels.recent")]
|
||||
[:div {:class (stl/css :selected)
|
||||
:data-testid "deleted-tab"}
|
||||
(tr "dashboard.labels.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-content)}
|
||||
[:div {:class (stl/css :deleted-info)}
|
||||
[:div
|
||||
(tr "dashboard.deleted.info-text")
|
||||
[:span {:class (stl/css :info-text-highlight)}
|
||||
(tr "dashboard.deleted.info-days" deletion-days)]
|
||||
(tr "dashboard.deleted.info-text2")]
|
||||
[:div
|
||||
(tr "dashboard.deleted.restore-text")]]
|
||||
[:div {:class (stl/css :deleted-options)}
|
||||
[:> button* {:variant "ghost"
|
||||
:type "button"
|
||||
:on-click on-restore-all}
|
||||
(tr "dashboard.restore-all-deleted-button")]
|
||||
(tr "dashboard.deleted.restore-all")]
|
||||
[:> button* {:variant "destructive"
|
||||
:type "button"
|
||||
:icon "delete"
|
||||
:on-click on-delete-all}
|
||||
(tr "dashboard.clear-trash-button")]]]
|
||||
:on-click on-clear}
|
||||
(tr "dashboard.deleted.clear")]]]
|
||||
|
||||
(when (seq projects)
|
||||
(for [{:keys [id] :as project} projects]
|
||||
@@ -304,5 +326,6 @@
|
||||
(filterv #(= id (:project-id %)))
|
||||
(sort-by :modified-at #(compare %2 %1))))]
|
||||
[:> deleted-project-item* {:project project
|
||||
:team team
|
||||
:files files
|
||||
:key id}])))]]]]))
|
||||
|
||||
@@ -20,19 +20,17 @@
|
||||
padding-block-end: var(--sp-xxxl);
|
||||
}
|
||||
|
||||
.deleted-info-content {
|
||||
.deleted-content {
|
||||
display: flex;
|
||||
gap: var(--sp-l);
|
||||
justify-content: space-between;
|
||||
padding: var(--sp-s) var(--sp-xxl) var(--sp-s) var(--sp-xxl);
|
||||
margin-inline-start: var(--sp-l);
|
||||
margin-block-start: var(--sp-xxl);
|
||||
}
|
||||
|
||||
.deleted-info {
|
||||
display: block;
|
||||
height: fit-content;
|
||||
@include t.use-typography("body-medium");
|
||||
color: var(--color-foreground-secondary);
|
||||
@include t.use-typography("body-large");
|
||||
line-height: 0.8;
|
||||
height: var(--sp-xl);
|
||||
}
|
||||
|
||||
.info-text-highlight {
|
||||
@@ -45,37 +43,27 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav {
|
||||
.nav-options {
|
||||
display: flex;
|
||||
gap: var(--sp-l);
|
||||
justify-content: space-between;
|
||||
border-bottom: $b-1 solid var(--panel-border-color);
|
||||
//padding-inline-start: var(--sp-l);
|
||||
padding-inline-start: var(--sp-l);
|
||||
background: var(--color-background-default);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-index-panels);
|
||||
|
||||
/* margin: 0 1.5rem; */
|
||||
/* margin-top: 1rem; */
|
||||
|
||||
margin: var(--sp-xxl) var(--sp-xxl) var(--sp-xxl) var(--sp-xxl);
|
||||
}
|
||||
|
||||
.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 {
|
||||
@include t.use-typography("headline-small");
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-foreground-primary);
|
||||
border: $b-1 solid transparent;
|
||||
border-bottom: $b-1 solid var(--color-foreground-primary);
|
||||
padding: 0 var(--sp-m);
|
||||
}
|
||||
|
||||
.project {
|
||||
|
||||
@@ -194,32 +194,39 @@
|
||||
(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)))
|
||||
{:on-success #(st/emit! (ntf/success (tr "restore-modal.success-restore-immediately" (: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-error #(st/emit! (ntf/error (tr "restore-modal.error-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))
|
||||
:title (tr "restore-modal.restore-file.title")
|
||||
:message (tr "restore-modal.restore-file.description" (:name file))
|
||||
:accept-label (tr "labels.continue")
|
||||
:accept-style :primary
|
||||
:on-accept restore-fn})))
|
||||
|
||||
|
||||
delete-fn
|
||||
(fn [_]
|
||||
(st/emit! (ntf/success (tr "delete-forever-modal.success-delete-immediately" (:name file)))
|
||||
(dd/delete-files-immediately
|
||||
{:team-id (:id current-team)
|
||||
:ids #{(:id file)}})
|
||||
(dd/fetch-projects (:id current-team))
|
||||
(dd/fetch-deleted-files (:id current-team))))
|
||||
|
||||
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}))))]
|
||||
(st/emit!
|
||||
(modal/show {:type :confirm
|
||||
:title (tr "delete-forever-modal.title")
|
||||
:message (tr "delete-forever-modal.delete-file.description" (:name file))
|
||||
:accept-label (tr "delete-forever-modal.title")
|
||||
:on-accept delete-fn})))]
|
||||
|
||||
(mf/with-effect []
|
||||
(->> (rp/cmd! :get-all-projects)
|
||||
@@ -261,11 +268,11 @@
|
||||
options
|
||||
(if can-restore
|
||||
[(when can-restore
|
||||
{:name (tr "dashboard.restore-file-button")
|
||||
{:name (tr "dashboard.restore-file")
|
||||
:id "restore-file"
|
||||
:handler on-restore-immediately})
|
||||
(when can-restore
|
||||
{:name (tr "dashboard.delete-file-button")
|
||||
{:name (tr "dashboard.delete-file")
|
||||
:id "delete-file"
|
||||
:handler on-delete-immediately})]
|
||||
(if multi?
|
||||
|
||||
@@ -240,13 +240,10 @@
|
||||
|
||||
;; --- Grid Item
|
||||
|
||||
(mf/defc grid-item-metadata*
|
||||
[{:keys [file]}]
|
||||
(let [time (ct/timeago (or (:will-be-deleted-at file)
|
||||
(:modified-at file)))]
|
||||
[:span {:class (stl/css :date)
|
||||
:title (tr "dashboard.deleted.will-be-deleted-at" time)}
|
||||
time]))
|
||||
(mf/defc grid-item-metadata
|
||||
[{:keys [modified-at]}]
|
||||
(let [time (ct/timeago modified-at)]
|
||||
[:span {:class (stl/css :date)} time]))
|
||||
|
||||
(defn create-counter-element
|
||||
[_element file-count]
|
||||
@@ -432,7 +429,7 @@
|
||||
:on-end edit
|
||||
:max-length 250}]
|
||||
[:h3 (:name file)])
|
||||
[:> grid-item-metadata* {:file file}]]
|
||||
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
|
||||
|
||||
[:div {:class (stl/css-case :project-th-actions true :force-display menu-open?)}
|
||||
[:div
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
[app.main.data.project :as dpj]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.dashboard.deleted :as deleted]
|
||||
[app.main.ui.dashboard.grid :refer [line-grid]]
|
||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
|
||||
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
|
||||
[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]
|
||||
@@ -316,34 +316,40 @@
|
||||
{::mf/props :obj}
|
||||
[{:keys [team projects profile]}]
|
||||
|
||||
(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*)
|
||||
|
||||
is-my-penpot (= (:default-team-id profile) team-id)
|
||||
is-defalt-team? (:is-default team)
|
||||
|
||||
projects
|
||||
(let [projects
|
||||
(mf/with-memo [projects]
|
||||
(->> projects
|
||||
(remove :deleted-at)
|
||||
(sort-by :modified-at)
|
||||
(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
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! show-team-hero* false)
|
||||
(st/emit! (ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero"
|
||||
::ev/origin "dashboard"}))))]
|
||||
::ev/origin "dashboard"}))))
|
||||
|
||||
on-deleted-click
|
||||
(mf/use-fn
|
||||
(mf/deps team-id)
|
||||
(fn []
|
||||
(st/emit! (dcm/go-to-dashboard-deleted :team-id team-id))))]
|
||||
|
||||
(mf/with-effect [show-team-hero?]
|
||||
(swap! storage/global assoc ::show-team-hero show-team-hero?))
|
||||
@@ -377,9 +383,15 @@
|
||||
(not is-defalt-team?)
|
||||
show-team-hero?
|
||||
can-invite))}
|
||||
|
||||
[:> deleted/menu* {:team-id team-id :section :dashboard-recent}]
|
||||
|
||||
[:div {:class (stl/css :nav-options)}
|
||||
[:div {:class (stl/css :selected)
|
||||
:data-testid "recent-tab"}
|
||||
(tr "dashboard.labels.recent")]
|
||||
[:> button* {:variant "ghost"
|
||||
:type "button"
|
||||
:data-testid "deleted-tab"
|
||||
:on-click on-deleted-click}
|
||||
(tr "dashboard.labels.deleted")]]
|
||||
(for [{:keys [id] :as project} projects]
|
||||
;; FIXME: refactor this, looks inneficient
|
||||
(let [files (when recent-map
|
||||
|
||||
@@ -248,3 +248,26 @@
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-options {
|
||||
display: flex;
|
||||
gap: var(--sp-l);
|
||||
justify-content: space-between;
|
||||
border-bottom: $b-1 solid var(--panel-border-color);
|
||||
padding-inline-start: var(--sp-l);
|
||||
background: var(--color-background-default);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-index-panels);
|
||||
}
|
||||
|
||||
.selected {
|
||||
@include t.use-typography("headline-small");
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-foreground-primary);
|
||||
border: $b-1 solid transparent;
|
||||
border-bottom: $b-1 solid var(--color-foreground-primary);
|
||||
padding: 0 var(--sp-m);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.color :as clr]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.exports.assets :as de]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.refs :as refs]
|
||||
@@ -205,13 +206,13 @@
|
||||
:cmd :export-frames
|
||||
:origin origin}]))
|
||||
|
||||
;; FIXME: deprecated, should be refactored in two components and use
|
||||
;; the generic progress reporter
|
||||
|
||||
(mf/defc progress-widget
|
||||
{::mf/wrap [mf/memo]}
|
||||
[]
|
||||
(let [state (mf/deref refs/export)
|
||||
[{:keys [operation] :or {operation :export}}]
|
||||
(let [state (mf/deref (case operation
|
||||
:export refs/export
|
||||
:restore refs/restore
|
||||
refs/export))
|
||||
profile (mf/deref refs/profile)
|
||||
theme (or (:theme profile) theme/default)
|
||||
is-default-theme? (= theme/default theme)
|
||||
@@ -220,7 +221,10 @@
|
||||
detail-visible? (:detail-visible state)
|
||||
widget-visible? (:widget-visible state)
|
||||
progress (:progress state)
|
||||
items (:exports state)
|
||||
items (case operation
|
||||
:export (:exports state)
|
||||
:restore (:files state)
|
||||
[])
|
||||
total (or (:total state) (count items))
|
||||
complete? (= progress total)
|
||||
circ (* 2 Math/PI 12)
|
||||
@@ -246,23 +250,43 @@
|
||||
|
||||
title
|
||||
(cond
|
||||
error? (tr "workspace.options.exporting-object-error")
|
||||
complete? (tr "workspace.options.exporting-complete")
|
||||
healthy? (tr "workspace.options.exporting-object")
|
||||
(not healthy?) (tr "workspace.options.exporting-object-slow"))
|
||||
error? (case operation
|
||||
:export (tr "workspace.options.exporting-object-error")
|
||||
:restore (tr "workspace.options.restoring-object-error")
|
||||
(tr "workspace.options.processing-object-error"))
|
||||
complete? (case operation
|
||||
:export (tr "workspace.options.exporting-complete")
|
||||
:restore (tr "workspace.options.restoring-complete")
|
||||
(tr "workspace.options.processing-complete"))
|
||||
healthy? (case operation
|
||||
:export (tr "workspace.options.exporting-object")
|
||||
:restore (tr "workspace.options.restoring-object")
|
||||
(tr "workspace.options.processing-object"))
|
||||
(not healthy?) (case operation
|
||||
:export (tr "workspace.options.exporting-object-slow")
|
||||
:restore (tr "workspace.options.restoring-object-slow")
|
||||
(tr "workspace.options.processing-object-slow")))
|
||||
|
||||
retry-last-operation
|
||||
(mf/use-fn
|
||||
(mf/deps operation)
|
||||
(fn []
|
||||
(st/emit! (de/retry-last-export))))
|
||||
(case operation
|
||||
:export (st/emit! (de/retry-last-export))
|
||||
:restore (st/emit! (dd/retry-last-restore))
|
||||
nil)))
|
||||
|
||||
toggle-detail-visibility
|
||||
(mf/use-fn
|
||||
(mf/deps operation)
|
||||
(fn []
|
||||
(st/emit! (de/toggle-detail-visibililty))))]
|
||||
(case operation
|
||||
:export (st/emit! (de/toggle-detail-visibililty))
|
||||
:restore (st/emit! (dd/toggle-restore-detail-visibility))
|
||||
nil)))]
|
||||
|
||||
[:*
|
||||
(when widget-visible?
|
||||
(when (and widget-visible? (= operation :export))
|
||||
[:div {:class (stl/css :export-progress-widget)
|
||||
:on-click toggle-detail-visibility}
|
||||
[:svg {:width "24" :height "24"}
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
(open-share-dialog)))
|
||||
|
||||
[:div {:class (stl/css :options-zone)}
|
||||
[:& progress-widget]
|
||||
[:& progress-widget {:operation :export}]
|
||||
|
||||
(case section
|
||||
:interactions [:*
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
[:div {:class (stl/css :users-section)}
|
||||
[:& active-sessions]]
|
||||
|
||||
[:& progress-widget]
|
||||
[:& progress-widget {:operation :export}]
|
||||
|
||||
[:div {:class (stl/css :separator)}]
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
(ns app.main.ui.workspace.shapes.text.editor
|
||||
(:require
|
||||
["draft-js" :as draft]
|
||||
["@penpot/draft-js" :as draft]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
(ns app.util.code-highlight
|
||||
(:require
|
||||
["@penpot/hljs" :as hljs]
|
||||
["highlight.js" :as hljs]
|
||||
[app.util.dom :as dom]))
|
||||
|
||||
(defn highlight!
|
||||
{:lazy-loadable true}
|
||||
[node]
|
||||
(dom/set-data! node "highlighted" nil)
|
||||
(hljs/highlightElement node))
|
||||
(.highlightElement hljs/default node))
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
(-> (.-language globals/navigator)
|
||||
(parse-locale))))
|
||||
|
||||
|
||||
;; Set initial translation loading state as globaly stored variable;
|
||||
;; this facilitates hot reloading
|
||||
(when-not (exists? (unchecked-get globals/global "penpotTranslations"))
|
||||
@@ -94,14 +93,8 @@
|
||||
(def ^:dynamic *current-locale*
|
||||
(get-current))
|
||||
|
||||
(defonce state
|
||||
(l/atom {:render 0 :locale *current-locale*}))
|
||||
|
||||
(defn- assign-current-locale
|
||||
[state locale]
|
||||
(-> state
|
||||
(update :render inc)
|
||||
(assoc :locale locale)))
|
||||
(defonce locale
|
||||
(l/atom *current-locale*))
|
||||
|
||||
(defn- get-translations
|
||||
"Get globaly stored mutable object with all loaded translations"
|
||||
@@ -115,6 +108,10 @@
|
||||
(unchecked-set translations locale data)
|
||||
nil))
|
||||
|
||||
(defn set-default-translations
|
||||
[data]
|
||||
(set-translations cf/default-language data))
|
||||
|
||||
(defn- load
|
||||
[locale]
|
||||
(let [path (str "./translation." locale ".js?version=" (:full cf/version))]
|
||||
@@ -123,15 +120,14 @@
|
||||
(p/fnly (fn [data cause]
|
||||
(if cause
|
||||
(js/console.error "unexpected error on fetching locale" cause)
|
||||
(do
|
||||
(set! *current-locale* locale)
|
||||
(set-translations locale data)
|
||||
(swap! state assign-current-locale locale))))))))
|
||||
(set-translations locale data)))))))
|
||||
|
||||
(defn init
|
||||
"Initialize the i18n module"
|
||||
[]
|
||||
(load *current-locale*))
|
||||
(load *current-locale*)
|
||||
(when-not (= *current-locale* cf/default-language)
|
||||
(load cf/default-language)))
|
||||
|
||||
(defn set-locale
|
||||
[lname]
|
||||
@@ -146,7 +142,10 @@
|
||||
(recur (rest locales)))
|
||||
cf/default-language))))]
|
||||
|
||||
(load lname)))
|
||||
(->> (load lname)
|
||||
(p/fnly (fn [_r _c]
|
||||
(set! *current-locale* lname)
|
||||
(reset! locale lname))))))
|
||||
|
||||
(deftype C [val]
|
||||
IDeref
|
||||
@@ -207,9 +206,7 @@
|
||||
:className class
|
||||
:on-click on-click}]))
|
||||
|
||||
(add-watch state "common.time"
|
||||
(add-watch locale "common.time"
|
||||
(fn [_ _ pv cv]
|
||||
(let [pv (get pv :locale)
|
||||
cv (get cv :locale)]
|
||||
(when (not= pv cv)
|
||||
(ct/set-default-locale! cv)))))
|
||||
(when (not= pv cv)
|
||||
(ct/set-default-locale cv))))
|
||||
|
||||
@@ -46,10 +46,6 @@
|
||||
[event]
|
||||
(= "end" (get-type event)))
|
||||
|
||||
(defn progress?
|
||||
[event]
|
||||
(= "progress" (get-type event)))
|
||||
|
||||
(defn event?
|
||||
[event]
|
||||
(= "event" (get-type event)))
|
||||
|
||||
@@ -8422,113 +8422,110 @@ msgstr "Autosaved versions will be kept for %s days."
|
||||
msgid "workspace.viewport.click-to-close-path"
|
||||
msgstr "Click to close the path"
|
||||
|
||||
msgid "dashboard.deleted.will-be-deleted-at"
|
||||
msgstr "Will be deleted %s"
|
||||
|
||||
msgid "labels.recent"
|
||||
msgid "dashboard.labels.recent"
|
||||
msgstr "Recent"
|
||||
|
||||
msgid "labels.deleted"
|
||||
msgid "dashboard.labels.deleted"
|
||||
msgstr "Deleted"
|
||||
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgid "dashboard.deleted.restore-all"
|
||||
msgstr "Restore All"
|
||||
|
||||
msgid "dashboard.clear-trash-button"
|
||||
msgid "dashboard.deleted.clear"
|
||||
msgstr "Clear trash"
|
||||
|
||||
msgid "dashboard.restore-file-button"
|
||||
msgid "dashboard.restore-file"
|
||||
msgstr "Restore file"
|
||||
|
||||
msgid "dashboard.delete-file-button"
|
||||
msgid "dashboard.delete-file"
|
||||
msgstr "Delete file"
|
||||
|
||||
msgid "dashboard.restore-project-button"
|
||||
msgid "dashboard.deleted.restore-project"
|
||||
msgstr "Restore project"
|
||||
|
||||
msgid "dashboard.delete-project-button"
|
||||
msgid "dashboard.deleted.delete-project"
|
||||
msgstr "Delete project"
|
||||
|
||||
msgid "dashboard.trash-info-text-part1"
|
||||
msgid "dashboard.deleted.info-text"
|
||||
msgstr "Deleted files will remain in the trash for"
|
||||
|
||||
msgid "dashboard.trash-info-text-part2"
|
||||
msgid "dashboard.deleted.info-days"
|
||||
msgstr " %s days. "
|
||||
|
||||
msgid "dashboard.trash-info-text-part3"
|
||||
msgid "dashboard.deleted.info-text2"
|
||||
msgstr "After that, they will be permanently deleted."
|
||||
|
||||
msgid "dashboard.trash-info-text-part4"
|
||||
msgid "dashboard.deleted.restore-text"
|
||||
msgstr "If you change your mind, you can restore them or delete them permanently from each file's menu."
|
||||
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Restore all projects and files"
|
||||
|
||||
msgid "dashboard.restore-all-confirmation.description"
|
||||
msgstr "You're going to restore all your projects and files. This may take a while."
|
||||
|
||||
msgid "dashboard-restore-file-confirmation.title"
|
||||
msgstr "Restore file"
|
||||
|
||||
msgid "dashboard-restore-file-confirmation.description"
|
||||
msgstr "You're going to restore %s."
|
||||
|
||||
msgid "dashboard.restore-project-confirmation.title"
|
||||
msgstr "Restore Project"
|
||||
|
||||
msgid "dashboard.restore-project-confirmation.description"
|
||||
msgstr "You're going to restore %s project and all the files contained in it."
|
||||
|
||||
msgid "dashboard.delete-forever-confirmation.title"
|
||||
msgid "dashboard.deleted.delete-forever"
|
||||
msgstr "Delete forever"
|
||||
|
||||
msgid "dashboard.delete-all-forever-confirmation.description"
|
||||
msgid "restore-modal.restore-all.title"
|
||||
msgstr "Restore all projects and files"
|
||||
|
||||
msgid "restore-modal.restore-all.description"
|
||||
msgstr "You're going to restore all your projects and files. This may take a while."
|
||||
|
||||
msgid "restore-modal.restore-file.title"
|
||||
msgstr "Restore file"
|
||||
|
||||
msgid "restore-modal.restore-file.description"
|
||||
msgstr "You're going to restore %s."
|
||||
|
||||
msgid "restore-modal.restore-project.title"
|
||||
msgstr "Restore Project"
|
||||
|
||||
msgid "restore-modal.restore-project.description"
|
||||
msgstr "You're going to restore %s project and all the files contained in it."
|
||||
|
||||
msgid "delete-forever-modal.title"
|
||||
msgstr "Delete forever"
|
||||
|
||||
msgid "delete-forever-modal.delete-all.description"
|
||||
msgstr "Are you sure you want to delete forever all your deleted projects and files? This is a non reversible action."
|
||||
|
||||
msgid "dashboard.delete-file-forever-confirmation.description"
|
||||
msgid "delete-forever-modal.delete-file.description"
|
||||
msgstr "Are you sure you want to delete forever %s? This is a non reversible action."
|
||||
|
||||
msgid "dashboard.delete-project-forever-confirmation.description"
|
||||
msgstr "Are you sure you want to delete forever %s project? You're going to delete it forever an all of the files contained in it. This is a non reversible action."
|
||||
msgid "delete-forever-modal.delete-project.description"
|
||||
msgstr "Are you sure you want to delete forever %s project? You're going to delete it forever an all of the files contained in it. This is a non reeversible action."
|
||||
|
||||
msgid "dashboard.restore-files-success-notification"
|
||||
msgstr "%s files have been successfully restored."
|
||||
|
||||
msgid "dashboard.restore-success-notification"
|
||||
msgid "restore-modal.success-restore-immediately"
|
||||
msgstr "%s has been successfully restored."
|
||||
|
||||
msgid "dashboard.delete-files-success-notification"
|
||||
msgstr "%s files have been successfully deleted."
|
||||
|
||||
msgid "dashboard.delete-success-notification"
|
||||
msgid "delete-forever-modal.success-delete-immediately"
|
||||
msgstr "%s has been successfully deleted."
|
||||
|
||||
msgid "dashboard.errors.error-on-restore-files"
|
||||
msgid "restore-modal.error-restore-files"
|
||||
msgstr "There was an error while restoring the files."
|
||||
|
||||
msgid "dashboard.errors.error-on-restore-file"
|
||||
msgid "restore-modal.error-restore-file"
|
||||
msgstr "There was an error while restoring the file %s."
|
||||
|
||||
msgid "dashboard.errors.error-on-restoring-project"
|
||||
msgid "restore-modal.error-restore-project"
|
||||
msgstr "There was an error while restoring the project %s and its files."
|
||||
|
||||
msgid "dashboard.errors.error-on-delete-file"
|
||||
msgstr "There was an error while deleting the file %s."
|
||||
|
||||
msgid "dashboard.errors.error-on-delete-files"
|
||||
msgstr "There was an error while deleting the files."
|
||||
|
||||
msgid "dashboard.errors.error-on-delete-project"
|
||||
msgstr "There was an error while deleting the project %s."
|
||||
|
||||
msgid "dashboard.progress-notification.restoring-files"
|
||||
msgid "restore-modal.normal-progress-label"
|
||||
msgstr "Restoring files…"
|
||||
|
||||
msgid "dashboard.progress-notification.deleting-files"
|
||||
msgstr "Deleting files…"
|
||||
msgid "restore-modal.failed-progress-label"
|
||||
msgstr "Restore failed"
|
||||
|
||||
msgid "dashboard.progress-notification.slow-restore"
|
||||
msgid "restore-modal.slow-progress-label"
|
||||
msgstr "Restore unexpectedly slow"
|
||||
|
||||
msgid "dashboard.progress-notification.slow-delete"
|
||||
msgstr "Deletion unexpectedly slow"
|
||||
msgid "restore-modal.complete-process-label"
|
||||
msgstr "Restore completed"
|
||||
|
||||
msgid "progress-widget.default-normal-progress-label"
|
||||
msgstr "Processing…"
|
||||
|
||||
msgid "progress-widget.default-failed-progress-label"
|
||||
msgstr "Process failed"
|
||||
|
||||
msgid "progress-widget.default-slow-progress-label"
|
||||
msgstr "Process unexpectedly slow"
|
||||
|
||||
msgid "progress-widget.default-complete-progress-label"
|
||||
msgstr "Process completed"
|
||||
@@ -8278,110 +8278,110 @@ msgstr "Los autoguardados duran %s días."
|
||||
msgid "workspace.viewport.click-to-close-path"
|
||||
msgstr "Pulsar para cerrar la ruta"
|
||||
|
||||
msgid "labels.recent"
|
||||
msgid "dashboard.labels.recent"
|
||||
msgstr "Recientes"
|
||||
|
||||
msgid "labels.deleted"
|
||||
msgid "dashboard.labels.deleted"
|
||||
msgstr "Eliminados"
|
||||
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgid "dashboard.deleted.restore-all"
|
||||
msgstr "Restaurar todo"
|
||||
|
||||
msgid "dashboard.clear-trash-button"
|
||||
msgid "dashboard.deleted.clear"
|
||||
msgstr "Vaciar papelera"
|
||||
|
||||
msgid "dashboard.restore-file-button"
|
||||
msgid "dashboard.restore-file"
|
||||
msgstr "Restaurar archivo"
|
||||
|
||||
msgid "dashboard.delete-file-button"
|
||||
msgid "dashboard.delete-file"
|
||||
msgstr "Eliminar archivo"
|
||||
|
||||
msgid "dashboard.restore-project-button"
|
||||
msgid "dashboard.deleted.restore-project"
|
||||
msgstr "Restaurar proyecto"
|
||||
|
||||
msgid "dashboard.delete-project-button"
|
||||
msgid "dashboard.deleted.delete-project"
|
||||
msgstr "Eliminar proyecto"
|
||||
|
||||
msgid "dashboard.trash-info-text-part1"
|
||||
msgid "dashboard.deleted.info-text"
|
||||
msgstr "Los archivos eliminados permanecerán en la papelera durante"
|
||||
|
||||
msgid "dashboard.trash-info-text-part2"
|
||||
msgid "dashboard.deleted.info-days"
|
||||
msgstr " %s días. "
|
||||
|
||||
msgid "dashboard.trash-info-text-part3"
|
||||
msgid "dashboard.deleted.info-text2"
|
||||
msgstr "Después de eso, serán eliminados permanentemente."
|
||||
|
||||
msgid "dashboard.trash-info-text-part4"
|
||||
msgid "dashboard.deleted.restore-text"
|
||||
msgstr "Si cambias de opinión, puedes restaurarlos o eliminarlos permanentemente desde el menú de cada archivo."
|
||||
|
||||
msgid "dashboard.deleted.delete-forever"
|
||||
msgstr "Eliminar para siempre"
|
||||
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgid "restore-modal.restore-all.title"
|
||||
msgstr "Restaurar todos los proyectos y archivos"
|
||||
|
||||
msgid "dashboard.restore-all-confirmation.description"
|
||||
msgid "restore-modal.restore-all.description"
|
||||
msgstr "Vas a restaurar todos tus proyectos y archivos. Esto puede tardar un poco."
|
||||
|
||||
msgid "dashboard-restore-file-confirmation.title"
|
||||
msgid "restore-modal.restore-file.title"
|
||||
msgstr "Restaurar archivo"
|
||||
|
||||
msgid "dashboard-restore-file-confirmation.description"
|
||||
msgid "restore-modal.restore-file.description"
|
||||
msgstr "Vas a restaurar %s."
|
||||
|
||||
msgid "dashboard.restore-project-confirmation.title"
|
||||
msgid "restore-modal.restore-project.title"
|
||||
msgstr "Restaurar proyecto"
|
||||
|
||||
msgid "dashboard.restore-project-confirmation.description"
|
||||
msgid "restore-modal.restore-project.description"
|
||||
msgstr "Vas a restaurar el proyecto %s y todos los archivos que contiene."
|
||||
|
||||
msgid "dashboard.delete-forever-confirmation.title"
|
||||
msgid "delete-forever-modal.title"
|
||||
msgstr "Eliminar para siempre"
|
||||
|
||||
msgid "dashboard.delete-all-forever-confirmation.description"
|
||||
msgid "delete-forever-modal.delete-all.description"
|
||||
msgstr "¿Estás seguro de que quieres eliminar para siempre todos tus proyectos y archivos eliminados? Esta es una acción irreversible."
|
||||
|
||||
msgid "dashboard.delete-file-forever-confirmation.description"
|
||||
msgid "delete-forever-modal.delete-file.description"
|
||||
msgstr "¿Estás seguro de que quieres eliminar para siempre %s? Esta es una acción irreversible."
|
||||
|
||||
msgid "dashboard.delete-project-forever-confirmation.description"
|
||||
msgid "delete-forever-modal.delete-project.description"
|
||||
msgstr "¿Estás seguro de que quieres eliminar para siempre el proyecto %s? Vas a eliminarlo para siempre junto con todos los archivos que contiene. Esta es una acción irreversible."
|
||||
|
||||
msgid "dashboard.restore-files-success-notification"
|
||||
msgstr "%s ficheros han sido restaurado correctamente."
|
||||
|
||||
msgid "dashboard.restore-success-notification"
|
||||
msgid "restore-modal.success-restore-immediately"
|
||||
msgstr "%s ha sido restaurado correctamente."
|
||||
|
||||
msgid "dashboard.delete-files-success-notification"
|
||||
msgstr "%s ficheros han sido eliminados correctamente."
|
||||
|
||||
msgid "dashboard.delete-success-notification"
|
||||
msgid "delete-forever-modal.success-delete-immediately"
|
||||
msgstr "%s ha sido eliminado correctamente."
|
||||
|
||||
msgid "dashboard.errors.error-on-restore-files"
|
||||
msgid "restore-modal.error-restore-files"
|
||||
msgstr "Hubo un error al restaurar los archivos."
|
||||
|
||||
msgid "dashboard.errors.error-on-restore-file"
|
||||
msgid "restore-modal.error-restore-file"
|
||||
msgstr "Hubo un error al restaurar el archivo %s."
|
||||
|
||||
msgid "dashboard.errors.error-on-restoring-files"
|
||||
msgstr "Hubo un error al restaurar archivos."
|
||||
msgid "restore-modal.error-restore-project"
|
||||
msgstr "Hubo un error al restaurar el proyecto %s y sus archivos."
|
||||
|
||||
msgid "dashboard.errors.error-on-delete-files"
|
||||
msgstr "Hubo un error al eliminar archivos."
|
||||
|
||||
msgid "dashboard.errors.error-on-delete-project"
|
||||
msgstr "Hubo un error al eliminar el proyecto %s"
|
||||
|
||||
msgid "dashboard.progress-notification.restoring-files"
|
||||
msgid "restore-modal.normal-progress-label"
|
||||
msgstr "Restaurando archivos…"
|
||||
|
||||
msgid "dashboard.progress-notification.deleting-files"
|
||||
msgstr "Eliminando archivos…"
|
||||
msgid "restore-modal.failed-progress-label"
|
||||
msgstr "Falló la restauración"
|
||||
|
||||
msgid "dashboard.progress-notification.slow-restore"
|
||||
msgstr "Restauración inesperadamente lenta"
|
||||
msgid "restore-modal.slow-progress-label"
|
||||
msgstr "Restauración lenta"
|
||||
|
||||
msgid "dashboard.progress-notification.slow-delete"
|
||||
msgstr "Eliminación inesperadamente lenta"
|
||||
msgid "restore-modal.complete-process-label"
|
||||
msgstr "Restauración completada"
|
||||
|
||||
msgid "progress-widget.default-normal-progress-label"
|
||||
msgstr "Procesando…"
|
||||
|
||||
msgid "progress-widget.default-failed-progress-label"
|
||||
msgstr "Falló el procesamiento"
|
||||
|
||||
msgid "progress-widget.default-slow-progress-label"
|
||||
msgstr "Procesamiento lento"
|
||||
|
||||
msgid "progress-widget.default-complete-progress-label"
|
||||
msgstr "Procesamiento completado"
|
||||
5
frontend/vendor/hljs/index.js
vendored
5
frontend/vendor/hljs/index.js
vendored
@@ -1,5 +0,0 @@
|
||||
import h from "highlight.js";
|
||||
|
||||
export function highlightElement(node) {
|
||||
return h.highlightElement(node);
|
||||
}
|
||||
13
frontend/vendor/hljs/package.json
vendored
13
frontend/vendor/hljs/package.json
vendored
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "@penpot/hljs",
|
||||
"version": "1.0.0",
|
||||
"description": "Penpot Hightlight.js ESM wrapper",
|
||||
"main": "index.js",
|
||||
"packageManager": "yarn@4.3.1",
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"highlight.js": "^11.10.0"
|
||||
}
|
||||
}
|
||||
21
frontend/vendor/hljs/yarn.lock
vendored
21
frontend/vendor/hljs/yarn.lock
vendored
@@ -1,21 +0,0 @@
|
||||
# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 8
|
||||
cacheKey: 10c0
|
||||
|
||||
"@penpot/hljs@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@penpot/hljs@workspace:."
|
||||
dependencies:
|
||||
highlight.js: "npm:^11.10.0"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"highlight.js@npm:^11.10.0":
|
||||
version: 11.10.0
|
||||
resolution: "highlight.js@npm:11.10.0"
|
||||
checksum: 10c0/cd8bf7ef06cbd72ddb83580ecabe769f08f062be8bb82d2eb492d31c17f7480d1f8d14a66fc81deee0601645435f19f04c470510563f847242a41ccff0ab873e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
4
frontend/vendor/tubax/saxjs.cljs
vendored
4
frontend/vendor/tubax/saxjs.cljs
vendored
@@ -1,4 +0,0 @@
|
||||
(ns tubax.saxjs
|
||||
(:require ["sax" :as sax]))
|
||||
|
||||
(goog/exportSymbol "sax" sax)
|
||||
2965
frontend/yarn.lock
2965
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user