Compare commits

..

11 Commits

Author SHA1 Message Date
Andrey Antukh
69c880d00e 🐛 Fix importmap usage on firefox 2025-12-23 13:10:58 +01:00
Andrey Antukh
9eebc467ef Preload default translations 2025-12-23 13:10:58 +01:00
Andrey Antukh
b77712ce73 Move frontend/vendor to frontend/packages 2025-12-23 13:10:58 +01:00
Andrey Antukh
3d3e81f314 Replace tubax with more modern tooling 2025-12-23 13:10:58 +01:00
Andrey Antukh
fe6441bb24 Replace hightlight.js internal bundle with direct npm use 2025-12-23 13:10:58 +01:00
Andrey Antukh
e15f0baf30 Replace direct draft-js usage with internal module 2025-12-23 13:10:58 +01:00
Andrey Antukh
c040cbb784 🔥 Remove old gulp related dependencies 2025-12-23 13:10:58 +01:00
Andrey Antukh
7f674b78a9 📎 Move all deps to dev-dependencies on frontend package.json
All they only needed for build process.
2025-12-23 13:10:58 +01:00
Andrey Antukh
099b78affd 📎 Update frontend yarn.lock file 2025-12-23 13:10:58 +01:00
Andrey Antukh
78cc3f0aa4 📎 Add immutable dependency to vendor/draft-js 2025-12-23 13:10:58 +01:00
Andrey Antukh
76f5f12808 ⬆️ Update dependencies on exporter 2025-12-23 13:10:58 +01:00
50 changed files with 1700 additions and 2998 deletions

View File

@@ -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]

View File

@@ -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

View File

@@ -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)))))

View File

@@ -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)))))))

View File

@@ -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"

View File

@@ -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}))))

View File

@@ -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))

View File

@@ -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",

View File

@@ -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

View File

@@ -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"

View File

@@ -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"
}
}

View File

@@ -16,7 +16,9 @@ export const {
RichTextEditorUtil,
SelectionState,
convertFromRaw,
convertToRaw
convertToRaw,
EditorBlock,
Editor
} = pkg;
import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor.js';

View File

@@ -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",

View File

@@ -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"

View File

@@ -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>

View File

@@ -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(

View File

@@ -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)

View File

@@ -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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -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)})))))))

View File

@@ -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

View File

@@ -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))

View File

@@ -87,9 +87,6 @@
{:stream? true
:form-data? true}
::sse/permanently-delete-team-files
{:stream? true}
::sse/restore-deleted-team-files
{:stream? true}

View File

@@ -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"}}]]])])]))

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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}])))]]]]))

View File

@@ -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 {

View File

@@ -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?

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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"}

View File

@@ -167,7 +167,7 @@
(open-share-dialog)))
[:div {:class (stl/css :options-zone)}
[:& progress-widget]
[:& progress-widget {:operation :export}]
(case section
:interactions [:*

View File

@@ -200,7 +200,7 @@
[:div {:class (stl/css :users-section)}
[:& active-sessions]]
[:& progress-widget]
[:& progress-widget {:operation :export}]
[:div {:class (stl/css :separator)}]

View File

@@ -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]

View File

@@ -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))

View File

@@ -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))))

View File

@@ -46,10 +46,6 @@
[event]
(= "end" (get-type event)))
(defn progress?
[event]
(= "progress" (get-type event)))
(defn event?
[event]
(= "event" (get-type event)))

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,5 +0,0 @@
import h from "highlight.js";
export function highlightElement(node) {
return h.highlightElement(node);
}

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -1,4 +0,0 @@
(ns tubax.saxjs
(:require ["sax" :as sax]))
(goog/exportSymbol "sax" sax)

View File

File diff suppressed because it is too large Load Diff