mirror of https://github.com/penpot/penpot.git
♻️ Make several adjustments to the dashboard deleted page (#7999)
* ♻️ Make several sustantial adjustments to the dashboard deleted page * 📎 Add PR feedback changes
This commit is contained in:
parent
e3405eacca
commit
7b5817f407
|
|
@ -704,7 +704,6 @@
|
||||||
f.created_at,
|
f.created_at,
|
||||||
f.modified_at,
|
f.modified_at,
|
||||||
f.name,
|
f.name,
|
||||||
f.is_shared,
|
|
||||||
f.deleted_at AS will_be_deleted_at,
|
f.deleted_at AS will_be_deleted_at,
|
||||||
ft.media_id AS thumbnail_id,
|
ft.media_id AS thumbnail_id,
|
||||||
row_number() OVER w AS row_num,
|
row_number() OVER w AS row_num,
|
||||||
|
|
@ -814,7 +813,7 @@
|
||||||
AND (f.deleted_at IS NULL OR f.deleted_at > now())
|
AND (f.deleted_at IS NULL OR f.deleted_at > now())
|
||||||
ORDER BY f.created_at ASC;")
|
ORDER BY f.created_at ASC;")
|
||||||
|
|
||||||
(defn- absorb-library-by-file!
|
(defn- absorb-library-by-file
|
||||||
[cfg ldata file-id]
|
[cfg ldata file-id]
|
||||||
|
|
||||||
(assert (db/connection-map? cfg)
|
(assert (db/connection-map? cfg)
|
||||||
|
|
@ -838,7 +837,7 @@
|
||||||
:modified-at (ct/now)
|
:modified-at (ct/now)
|
||||||
:has-media-trimmed false}))))
|
:has-media-trimmed false}))))
|
||||||
|
|
||||||
(defn- absorb-library
|
(defn- absorb-library*
|
||||||
"Find all files using a shared library, and absorb all library assets
|
"Find all files using a shared library, and absorb all library assets
|
||||||
into the file local libraries"
|
into the file local libraries"
|
||||||
[cfg {:keys [id data] :as library}]
|
[cfg {:keys [id data] :as library}]
|
||||||
|
|
@ -853,10 +852,10 @@
|
||||||
:library-id (str id)
|
:library-id (str id)
|
||||||
:files (str/join "," (map str ids)))
|
:files (str/join "," (map str ids)))
|
||||||
|
|
||||||
(run! (partial absorb-library-by-file! cfg data) ids)
|
(run! (partial absorb-library-by-file cfg data) ids)
|
||||||
library))
|
library))
|
||||||
|
|
||||||
(defn absorb-library!
|
(defn absorb-library
|
||||||
[{:keys [::db/conn] :as cfg} id]
|
[{:keys [::db/conn] :as cfg} id]
|
||||||
(let [file (-> (bfc/get-file cfg id
|
(let [file (-> (bfc/get-file cfg id
|
||||||
:realize? true
|
:realize? true
|
||||||
|
|
@ -873,7 +872,7 @@
|
||||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||||
(cfeat/check-file-features! (:features file)))
|
(cfeat/check-file-features! (:features file)))
|
||||||
|
|
||||||
(absorb-library cfg file)))
|
(absorb-library* cfg file)))
|
||||||
|
|
||||||
(defn- set-file-shared
|
(defn- set-file-shared
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}]
|
[{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}]
|
||||||
|
|
@ -886,14 +885,14 @@
|
||||||
;; file, we need to perform more complex operation,
|
;; file, we need to perform more complex operation,
|
||||||
;; so in this case we retrieve the complete file and
|
;; so in this case we retrieve the complete file and
|
||||||
;; perform all required validations.
|
;; perform all required validations.
|
||||||
(let [file (-> (absorb-library! cfg id)
|
(let [file (-> (absorb-library cfg id)
|
||||||
(assoc :is-shared false))]
|
(assoc :is-shared false))]
|
||||||
(db/delete! conn :file-library-rel {:library-file-id id})
|
(db/delete! conn :file-library-rel {:library-file-id id})
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:is-shared false
|
{:is-shared false
|
||||||
:modified-at (ct/now)}
|
:modified-at (ct/now)}
|
||||||
{:id id})
|
{:id id})
|
||||||
(select-keys file [:id :name :is-shared]))
|
file)
|
||||||
|
|
||||||
(and (false? (:is-shared file))
|
(and (false? (:is-shared file))
|
||||||
(true? (:is-shared params)))
|
(true? (:is-shared params)))
|
||||||
|
|
@ -940,6 +939,11 @@
|
||||||
{:id file-id}
|
{:id file-id}
|
||||||
{::db/return-keys [:id :name :is-shared :deleted-at
|
{::db/return-keys [:id :name :is-shared :deleted-at
|
||||||
:project-id :created-at :modified-at]})]
|
:project-id :created-at :modified-at]})]
|
||||||
|
|
||||||
|
;; Remove all possible relations for that file
|
||||||
|
(db/delete! conn :file-library-rel
|
||||||
|
{:library-file-id file-id})
|
||||||
|
|
||||||
(wrk/submit! {::db/conn conn
|
(wrk/submit! {::db/conn conn
|
||||||
::wrk/task :delete-object
|
::wrk/task :delete-object
|
||||||
::wrk/params {:object :file
|
::wrk/params {:object :file
|
||||||
|
|
@ -1090,47 +1094,53 @@
|
||||||
|
|
||||||
;; --- MUTATION COMMAND: delete-files-immediatelly
|
;; --- MUTATION COMMAND: delete-files-immediatelly
|
||||||
|
|
||||||
(def ^:private sql:delete-team-files
|
(def ^:private sql:get-delete-team-files-candidates
|
||||||
"UPDATE file AS uf SET deleted_at = ?::timestamptz
|
"SELECT f.id
|
||||||
FROM (
|
FROM file AS f
|
||||||
SELECT f.id
|
JOIN project AS p ON (p.id = f.project_id)
|
||||||
FROM file AS f
|
JOIN team AS t ON (t.id = p.team_id)
|
||||||
JOIN project AS p ON (p.id = f.project_id)
|
WHERE t.deleted_at IS NULL
|
||||||
JOIN team AS t ON (t.id = p.team_id)
|
AND t.id = ?
|
||||||
WHERE t.deleted_at IS NULL
|
AND f.id = ANY(?::uuid[])")
|
||||||
AND t.id = ?
|
|
||||||
AND f.id = ANY(?::uuid[])
|
|
||||||
) AS subquery
|
|
||||||
WHERE uf.id = subquery.id
|
|
||||||
RETURNING uf.id, uf.deleted_at;")
|
|
||||||
|
|
||||||
(def ^:private schema:permanently-delete-team-files
|
(def ^:private schema:permanently-delete-team-files
|
||||||
[:map {:title "permanently-delete-team-files"}
|
[:map {:title "permanently-delete-team-files"}
|
||||||
[:team-id ::sm/uuid]
|
[:team-id ::sm/uuid]
|
||||||
[:ids [::sm/set ::sm/uuid]]])
|
[:ids [::sm/set ::sm/uuid]]])
|
||||||
|
|
||||||
|
(defn- permanently-delete-team-files
|
||||||
|
[{:keys [::db/conn]} {:keys [::rpc/request-at team-id ids]}]
|
||||||
|
(let [ids (into #{}
|
||||||
|
d/xf:map-id
|
||||||
|
(db/exec! conn [sql:get-delete-team-files-candidates team-id
|
||||||
|
(db/create-array conn "uuid" ids)]))]
|
||||||
|
|
||||||
|
(reduce (fn [acc id]
|
||||||
|
(events/tap :progress {:file-id id :index (inc (count acc)) :total (count ids)})
|
||||||
|
(db/update! conn :file
|
||||||
|
{:deleted-at request-at}
|
||||||
|
{:id id}
|
||||||
|
{::db/return-keys false})
|
||||||
|
(wrk/submit! {::db/conn conn
|
||||||
|
::wrk/task :delete-object
|
||||||
|
::wrk/params {:object :file
|
||||||
|
:deleted-at request-at
|
||||||
|
:id id}})
|
||||||
|
(conj acc id))
|
||||||
|
#{}
|
||||||
|
ids)))
|
||||||
|
|
||||||
(sv/defmethod ::permanently-delete-team-files
|
(sv/defmethod ::permanently-delete-team-files
|
||||||
"Mark the specified files to be deleted immediatelly on the
|
"Mark the specified files to be deleted immediatelly on the
|
||||||
specified team. The team-id on params will be used to filter and
|
specified team. The team-id on params will be used to filter and
|
||||||
check writable permissons on team."
|
check writable permissons on team."
|
||||||
|
|
||||||
{::doc/added "2.12"
|
{::doc/added "2.13"
|
||||||
::sm/params schema:permanently-delete-team-files
|
::sm/params schema:permanently-delete-team-files}
|
||||||
::db/transaction true}
|
|
||||||
|
|
||||||
[{:keys [::db/conn]} {:keys [::rpc/profile-id ::rpc/request-at team-id ids]}]
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id] :as params}]
|
||||||
(teams/check-edition-permissions! conn profile-id team-id)
|
(teams/check-edition-permissions! pool profile-id team-id)
|
||||||
|
(sse/response #(db/tx-run! cfg permanently-delete-team-files params)))
|
||||||
(reduce (fn [acc {:keys [id deleted-at]}]
|
|
||||||
(wrk/submit! {::db/conn conn
|
|
||||||
::wrk/task :delete-object
|
|
||||||
::wrk/params {:object :file
|
|
||||||
:deleted-at deleted-at
|
|
||||||
:id id}})
|
|
||||||
(conj acc id))
|
|
||||||
#{}
|
|
||||||
(db/plan conn [sql:delete-team-files request-at team-id
|
|
||||||
(db/create-array conn "uuid" ids)])))
|
|
||||||
|
|
||||||
;; --- MUTATION COMMAND: restore-files-immediatelly
|
;; --- MUTATION COMMAND: restore-files-immediatelly
|
||||||
|
|
||||||
|
|
@ -1194,7 +1204,7 @@
|
||||||
{:keys [files projects]}
|
{:keys [files projects]}
|
||||||
(reduce (fn [result {:keys [id project-id]}]
|
(reduce (fn [result {:keys [id project-id]}]
|
||||||
(let [index (-> result :files count)]
|
(let [index (-> result :files count)]
|
||||||
(events/tap :progress {:file-id id :index index :total total-files})
|
(events/tap :progress {:file-id id :index (inc index) :total total-files})
|
||||||
(restore-file conn id)
|
(restore-file conn id)
|
||||||
|
|
||||||
(-> result
|
(-> result
|
||||||
|
|
@ -1217,7 +1227,7 @@
|
||||||
(sv/defmethod ::restore-deleted-team-files
|
(sv/defmethod ::restore-deleted-team-files
|
||||||
"Removes the deletion mark from the specified files (and respective
|
"Removes the deletion mark from the specified files (and respective
|
||||||
projects) on the specified team."
|
projects) on the specified team."
|
||||||
{::doc/added "2.12"
|
{::doc/added "2.13"
|
||||||
::sse/stream? true
|
::sse/stream? true
|
||||||
::sm/params schema:restore-deleted-team-files}
|
::sm/params schema:restore-deleted-team-files}
|
||||||
[cfg params]
|
[cfg params]
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@
|
||||||
:deleted-at (ct/format-inst deleted-at))
|
:deleted-at (ct/format-inst deleted-at))
|
||||||
|
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:deleted-at deleted-at}
|
{:deleted-at deleted-at
|
||||||
|
:is-shared false}
|
||||||
{:id id}
|
{:id id}
|
||||||
{::db/return-keys false})
|
{::db/return-keys false})
|
||||||
|
|
||||||
|
|
@ -53,7 +54,7 @@
|
||||||
(not *team-deletion*))
|
(not *team-deletion*))
|
||||||
;; NOTE: we don't prevent file deletion on absorb operation failure
|
;; NOTE: we don't prevent file deletion on absorb operation failure
|
||||||
(try
|
(try
|
||||||
(db/tx-run! cfg files/absorb-library! id)
|
(db/tx-run! cfg files/absorb-library id)
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(l/warn :hint "error on absorbing library"
|
(l/warn :hint "error on absorbing library"
|
||||||
:file-id id
|
:file-id id
|
||||||
|
|
|
||||||
|
|
@ -595,8 +595,8 @@
|
||||||
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
|
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
|
||||||
(into []
|
(into []
|
||||||
(map (fn [{:keys [event data]}]
|
(map (fn [{:keys [event data]}]
|
||||||
[(keyword event)
|
(d/vec2 (keyword event)
|
||||||
(tr/decode-str data)]))
|
(tr/decode-str data))))
|
||||||
(parse-sse (slurp' input)))
|
(parse-sse (slurp' input)))
|
||||||
(finally
|
(finally
|
||||||
(.close input)))))
|
(.close input)))))
|
||||||
|
|
|
||||||
|
|
@ -1921,7 +1921,11 @@
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(let [result (:result out)]
|
(let [result (:result out)]
|
||||||
(t/is (= (:ids data) result)))
|
(t/is (fn? result))
|
||||||
|
|
||||||
|
(let [[ev1 ev2 :as events] (th/consume-sse result)]
|
||||||
|
(t/is (= 2 (count events)))
|
||||||
|
(t/is (= (:ids data) (val ev2)))))
|
||||||
|
|
||||||
(let [row (th/db-exec-one! ["select * from file where id = ?" file-id])]
|
(let [row (th/db-exec-one! ["select * from file where id = ?" file-id])]
|
||||||
(t/is (= (:deleted-at row) now)))))))
|
(t/is (= (:deleted-at row) now)))))))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.common.types.project
|
||||||
|
(:require
|
||||||
|
[app.common.schema :as sm]
|
||||||
|
[app.common.time :as cm]))
|
||||||
|
|
||||||
|
(def schema:project
|
||||||
|
[:map {:title "Profile"}
|
||||||
|
[:id ::sm/uuid]
|
||||||
|
[:created-at {:optional true} ::cm/inst]
|
||||||
|
[:modified-at {:optional true} ::cm/inst]
|
||||||
|
[:name :string]
|
||||||
|
[:is-default {:optional true} ::sm/boolean]
|
||||||
|
[:is-pinned {:optional true} ::sm/boolean]
|
||||||
|
[:count {:optional true} ::sm/int]
|
||||||
|
[:total-count {:optional true} ::sm/int]
|
||||||
|
[:team-id ::sm/uuid]])
|
||||||
|
|
||||||
|
(def valid-project?
|
||||||
|
(sm/lazy-validator schema:project))
|
||||||
|
|
||||||
|
(def check-project
|
||||||
|
(sm/check-fn schema:project))
|
||||||
|
|
@ -302,3 +302,9 @@
|
||||||
:height 720}])
|
:height 720}])
|
||||||
|
|
||||||
(def max-input-length 255)
|
(def max-input-length 255)
|
||||||
|
|
||||||
|
(def ^:const default-slow-progress-threshold
|
||||||
|
"A constant value that represents a threshold in milliseconds when a
|
||||||
|
normal progress becomes tagged as slow if no event received in the
|
||||||
|
specified amount of time"
|
||||||
|
1000)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
|
[app.common.time :as ct]
|
||||||
[app.common.types.team :as ctt]
|
[app.common.types.team :as ctt]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
|
|
@ -229,6 +230,91 @@
|
||||||
;; Delay so the navigation can finish
|
;; Delay so the navigation can finish
|
||||||
(rx/delay 250))))))))
|
(rx/delay 250))))))))
|
||||||
|
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; PROGRESS EVENTS
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(def noop-fn
|
||||||
|
(constantly nil))
|
||||||
|
|
||||||
|
(def ^:private schema:progress-params
|
||||||
|
[:map {:title "Progress"}
|
||||||
|
[:key {:optional true} ::sm/text]
|
||||||
|
[:index {:optional true} ::sm/int]
|
||||||
|
[:total ::sm/int]
|
||||||
|
[:hints
|
||||||
|
[:map-of :keyword fn?]]
|
||||||
|
[:slow-progress-threshold {:optional true} ::sm/int]])
|
||||||
|
|
||||||
|
(def ^:private check-progress-params
|
||||||
|
(sm/check-fn schema:progress-params))
|
||||||
|
|
||||||
|
(defn initialize-progress
|
||||||
|
[& {:keys [key index total hints slow-progress-threshold] :as params}]
|
||||||
|
|
||||||
|
(assert (check-progress-params params))
|
||||||
|
|
||||||
|
(ptk/reify ::initialize-progress
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update state :progress
|
||||||
|
(fn [_]
|
||||||
|
(let [hint ((:normal hints noop-fn) params)]
|
||||||
|
{:threshold (or slow-progress-threshold 5000)
|
||||||
|
:key key
|
||||||
|
:last-update (ct/now)
|
||||||
|
:healthy true
|
||||||
|
:visible true
|
||||||
|
:hints hints
|
||||||
|
:progress (d/nilv index 0)
|
||||||
|
:total total
|
||||||
|
:hint hint}))))))
|
||||||
|
|
||||||
|
(defn update-progress
|
||||||
|
[{:keys [index total] :as params}]
|
||||||
|
|
||||||
|
(assert (check-progress-params params))
|
||||||
|
|
||||||
|
(ptk/reify ::update-progress
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update state :progress
|
||||||
|
(fn [state]
|
||||||
|
(let [last-update (get state :last-update)
|
||||||
|
hints (get state :hints)
|
||||||
|
threshold (get state :slow-progress-threshold)
|
||||||
|
|
||||||
|
time-diff (ct/diff-ms last-update (ct/now))
|
||||||
|
healthy? (< time-diff threshold)
|
||||||
|
|
||||||
|
hint (if healthy?
|
||||||
|
((:normal hints noop-fn) params)
|
||||||
|
((:slow hints noop-fn) params))]
|
||||||
|
|
||||||
|
(-> state
|
||||||
|
(assoc :progress index)
|
||||||
|
(assoc :total total)
|
||||||
|
(assoc :last-update (ct/now))
|
||||||
|
(assoc :healthy healthy?)
|
||||||
|
(assoc :hint hint))))))))
|
||||||
|
|
||||||
|
(defn toggle-progress-visibility
|
||||||
|
[]
|
||||||
|
(ptk/reify ::toggle-progress-visibility
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update state :progress
|
||||||
|
(fn [state]
|
||||||
|
(update state :visible not))))))
|
||||||
|
|
||||||
|
(defn clear-progress
|
||||||
|
[]
|
||||||
|
(ptk/reify ::clear-progress
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(dissoc state :progress))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; NAVEGATION EVENTS
|
;; NAVEGATION EVENTS
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,15 @@
|
||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
|
[app.common.types.project :refer [valid-project?]]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
[app.main.constants :as mconst]
|
||||||
[app.main.data.common :as dcm]
|
[app.main.data.common :as dcm]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.fonts :as df]
|
[app.main.data.fonts :as df]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
|
[app.main.data.notifications :as ntf]
|
||||||
[app.main.data.websocket :as dws]
|
[app.main.data.websocket :as dws]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
|
@ -691,6 +694,56 @@
|
||||||
|
|
||||||
;; --- Delete files immediately
|
;; --- Delete files immediately
|
||||||
|
|
||||||
|
(defn- delete-files
|
||||||
|
[{:keys [team-id ids on-success on-error]}]
|
||||||
|
(assert (uuid? team-id))
|
||||||
|
(assert (set? ids))
|
||||||
|
(assert (every? uuid? ids))
|
||||||
|
(assert (fn? on-success))
|
||||||
|
(assert (fn? on-error))
|
||||||
|
|
||||||
|
(ptk/reify ::delete-files
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ _ _]
|
||||||
|
(let [progress-hint #(tr "dashboard.progress-notification.deleting-files")
|
||||||
|
slow-hint #(tr "dashboard.progress-notification.slow-delete")
|
||||||
|
stream (->> (rp/cmd! ::sse/permanently-delete-team-files {:team-id team-id :ids ids})
|
||||||
|
(rx/share))]
|
||||||
|
(rx/merge
|
||||||
|
(rx/of (dcm/initialize-progress
|
||||||
|
{:slow-progress-threshold
|
||||||
|
mconst/default-slow-progress-threshold
|
||||||
|
:total (count ids)
|
||||||
|
:hints {:progress progress-hint
|
||||||
|
:slow slow-hint}}))
|
||||||
|
|
||||||
|
(->> stream
|
||||||
|
(rx/filter sse/progress?)
|
||||||
|
(rx/mapcat (fn [event]
|
||||||
|
(if-let [payload (sse/get-payload event)]
|
||||||
|
(let [{:keys [index total]} payload]
|
||||||
|
(if (and index total)
|
||||||
|
(rx/of (dcm/update-progress {:index index :total total}))
|
||||||
|
(rx/empty)))
|
||||||
|
(rx/empty))))
|
||||||
|
(rx/catch rx/empty))
|
||||||
|
|
||||||
|
(->> stream
|
||||||
|
(rx/filter sse/end-of-stream?)
|
||||||
|
(rx/map sse/get-payload)
|
||||||
|
(rx/merge-map (fn [_]
|
||||||
|
(rx/concat
|
||||||
|
(rx/of (dcm/clear-progress)
|
||||||
|
(fetch-projects team-id)
|
||||||
|
(fetch-deleted-files team-id)
|
||||||
|
(fetch-projects team-id))
|
||||||
|
(on-success))))
|
||||||
|
|
||||||
|
(rx/catch (fn [error]
|
||||||
|
(rx/concat
|
||||||
|
(rx/of (dcm/clear-progress))
|
||||||
|
(on-error error))))))))))
|
||||||
|
|
||||||
(defn delete-files-immediately
|
(defn delete-files-immediately
|
||||||
[{:keys [team-id ids] :as params}]
|
[{:keys [team-id ids] :as params}]
|
||||||
(assert (uuid? team-id))
|
(assert (uuid? team-id))
|
||||||
|
|
@ -698,145 +751,190 @@
|
||||||
(assert (every? uuid? ids))
|
(assert (every? uuid? ids))
|
||||||
|
|
||||||
(ptk/reify ::delete-files-immediately
|
(ptk/reify ::delete-files-immediately
|
||||||
ev/Event
|
|
||||||
(-data [_]
|
|
||||||
{:team-id team-id
|
|
||||||
:num-files (count ids)})
|
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ state _]
|
||||||
(let [{:keys [on-success on-error]
|
(let [deleted-files
|
||||||
:or {on-success identity
|
(get state :deleted-files)
|
||||||
on-error rx/throw}} (meta params)]
|
|
||||||
(->> (rp/cmd! :permanently-delete-team-files {:team-id team-id :ids ids})
|
on-success
|
||||||
(rx/tap on-success)
|
(fn []
|
||||||
(rx/catch on-error))))))
|
(if (= 1 (count ids))
|
||||||
|
(let [fname (get-in deleted-files [(first ids) :name])]
|
||||||
|
(rx/of (ntf/success (tr "dashboard.delete-success-notification" fname))))
|
||||||
|
(rx/of (ntf/success (tr "dashboard.delete-files-success-notification" (count ids))))))
|
||||||
|
|
||||||
|
on-error
|
||||||
|
#(rx/of (ntf/error (tr "dashboard.errors.error-on-delete-files")))]
|
||||||
|
|
||||||
|
(rx/of (ev/event
|
||||||
|
{::ev/name "delete-files"
|
||||||
|
::ev/origin "dashboard:trash"
|
||||||
|
:team-id team-id
|
||||||
|
:num-files (count ids)})
|
||||||
|
(delete-files
|
||||||
|
{:team-id team-id
|
||||||
|
:ids ids
|
||||||
|
:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn delete-project-immediately
|
||||||
|
[{:keys [team-id id name] :as project}]
|
||||||
|
(assert (valid-project? project))
|
||||||
|
|
||||||
|
(ptk/reify ::delete-project-immediately
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [ids
|
||||||
|
(reduce-kv (fn [acc file-id file]
|
||||||
|
(if (= (:project-id file) id)
|
||||||
|
(conj acc file-id)
|
||||||
|
acc))
|
||||||
|
#{}
|
||||||
|
(get state :deleted-files))
|
||||||
|
|
||||||
|
on-success
|
||||||
|
#(rx/of (ntf/success (tr "dashboard.delete-success-notification" name)))
|
||||||
|
|
||||||
|
on-error
|
||||||
|
#(rx/of (ntf/error (tr "dashboard.errors.error-on-delete-project" name)))]
|
||||||
|
|
||||||
|
(rx/of (ev/event
|
||||||
|
{::ev/name "delete-files"
|
||||||
|
::ev/origin "dashboard:trash"
|
||||||
|
:team-id team-id
|
||||||
|
:project-id id
|
||||||
|
:num-files (count ids)})
|
||||||
|
(delete-files
|
||||||
|
{:team-id team-id
|
||||||
|
:ids ids
|
||||||
|
:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Restore deleted files immediately
|
;; --- 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- update-restore-status
|
(defn- restore-files
|
||||||
[{:keys [index total] :as data}]
|
[{:keys [team-id ids on-success on-error]}]
|
||||||
(ptk/reify ::upd-restore-status
|
(assert (uuid? team-id))
|
||||||
ptk/UpdateEvent
|
(assert (set? ids))
|
||||||
(update [_ state]
|
(assert (every? uuid? ids))
|
||||||
(let [time-diff (ct/diff-ms (get-in state [:restore :last-update]) (ct/now))
|
(assert (fn? on-success))
|
||||||
healthy? (< time-diff 6000)]
|
(assert (fn? on-error))
|
||||||
(update state :restore assoc
|
|
||||||
:progress index
|
|
||||||
:total total
|
|
||||||
:last-update (ct/now)
|
|
||||||
:healthy? healthy?)))))
|
|
||||||
|
|
||||||
(defn- complete-restore-status
|
(ptk/reify ::restore-files
|
||||||
[]
|
|
||||||
(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
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
;; Refetch projects to get the updated state without deleted-at
|
(let [progress-hint #(tr "dashboard.progress-notification.restoring-files")
|
||||||
(rx/of (fetch-projects team-id)))))
|
slow-hint #(tr "dashboard.progress-notification.slow-restore")]
|
||||||
|
|
||||||
(defn restore-files-immediately
|
|
||||||
[{: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 [_ _ _]
|
|
||||||
(let [{:keys [on-success on-error]
|
|
||||||
:or {on-success identity
|
|
||||||
on-error rx/throw}} (meta params)
|
|
||||||
files (mapv #(hash-map :id %) ids)]
|
|
||||||
|
|
||||||
(rx/merge
|
(rx/merge
|
||||||
(rx/of (initialize-restore-status files))
|
(rx/of (dcm/initialize-progress
|
||||||
|
{:slow-progress-threshold
|
||||||
|
mconst/default-slow-progress-threshold
|
||||||
|
:total (count ids)
|
||||||
|
:hints {:progress progress-hint
|
||||||
|
:slow slow-hint}}))
|
||||||
|
|
||||||
(->> (rp/cmd! ::sse/restore-deleted-team-files {:team-id team-id :ids ids})
|
(let [stream (->> (rp/cmd! ::sse/restore-deleted-team-files {:team-id team-id :ids ids})
|
||||||
(rx/tap (fn [event]
|
(rx/share))]
|
||||||
(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 (ptk/data-event ::restore-start {:total (count ids)})))))))
|
(rx/merge
|
||||||
|
(->> stream
|
||||||
|
(rx/filter sse/progress?)
|
||||||
|
(rx/mapcat (fn [event]
|
||||||
|
(if-let [payload (sse/get-payload event)]
|
||||||
|
(let [{:keys [index total]} payload]
|
||||||
|
(if (and index total)
|
||||||
|
(rx/of (dcm/update-progress {:index index :total total}))
|
||||||
|
(rx/empty)))
|
||||||
|
(rx/empty))))
|
||||||
|
(rx/catch rx/empty))
|
||||||
|
|
||||||
|
(->> stream
|
||||||
|
(rx/filter sse/end-of-stream?)
|
||||||
|
(rx/map sse/get-payload)
|
||||||
|
(rx/mapcat (fn [_]
|
||||||
|
(rx/concat
|
||||||
|
(rx/of (dcm/clear-progress)
|
||||||
|
;; (ntf/success (tr "dashboard.restore-success-notification"))
|
||||||
|
(fetch-projects team-id)
|
||||||
|
(fetch-deleted-files team-id)
|
||||||
|
(fetch-projects team-id))
|
||||||
|
(on-success))))
|
||||||
|
(rx/catch (fn [error]
|
||||||
|
(rx/concat
|
||||||
|
(rx/of (dcm/clear-progress))
|
||||||
|
(on-error error))))))))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn restore-files-immediately
|
||||||
|
[{:keys [team-id ids]}]
|
||||||
|
(assert (uuid? team-id))
|
||||||
|
(assert (set? ids))
|
||||||
|
|
||||||
|
(ptk/reify ::restore-files-immediately
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [deleted-files
|
||||||
|
(get state :deleted-files)
|
||||||
|
|
||||||
|
on-success
|
||||||
|
(fn []
|
||||||
|
(if (= 1 (count ids))
|
||||||
|
(let [fname (get-in deleted-files [(first ids) :name])]
|
||||||
|
(rx/of (ntf/success (tr "dashboard.restore-success-notification" fname))))
|
||||||
|
(rx/of (ntf/success (tr "dashboard.restore-files-success-notification" (count ids))))))
|
||||||
|
|
||||||
|
on-error
|
||||||
|
(fn [_cause]
|
||||||
|
(if (= 1 (count ids))
|
||||||
|
(let [fname (get-in deleted-files [(first ids) :name])]
|
||||||
|
(rx/of (ntf/error (tr "dashboard.errors.error-on-restore-file" fname))))
|
||||||
|
(rx/of (ntf/error (tr "dashboard.errors.error-on-restore-files")))))]
|
||||||
|
|
||||||
|
(rx/of (ev/event
|
||||||
|
{::ev/name "restore-files"
|
||||||
|
::ev/origin "dashboard:trash"
|
||||||
|
:team-id team-id
|
||||||
|
:num-files (count ids)})
|
||||||
|
(restore-files
|
||||||
|
{:team-id team-id
|
||||||
|
:ids ids
|
||||||
|
:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|
||||||
|
(defn restore-project-immediately
|
||||||
|
[{:keys [team-id id name] :as project}]
|
||||||
|
(assert (valid-project? project))
|
||||||
|
|
||||||
|
(ptk/reify ::restore-project-immediately
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [ids
|
||||||
|
(reduce-kv (fn [acc file-id file]
|
||||||
|
(if (= (:project-id file) id)
|
||||||
|
(conj acc file-id)
|
||||||
|
acc))
|
||||||
|
#{}
|
||||||
|
(get state :deleted-files))
|
||||||
|
|
||||||
|
on-success
|
||||||
|
#(st/emit! (ntf/success (tr "dashboard.restore-success-notification" name)))
|
||||||
|
|
||||||
|
on-error
|
||||||
|
#(st/emit! (ntf/error (tr "dashboard.errors.error-on-restoring-project" name)))]
|
||||||
|
|
||||||
|
(rx/of (ev/event
|
||||||
|
{::ev/name "restore-files"
|
||||||
|
::ev/origin "dashboard:trash"
|
||||||
|
:team-id team-id
|
||||||
|
:project-id id
|
||||||
|
:num-files (count ids)})
|
||||||
|
(restore-files
|
||||||
|
{:team-id team-id
|
||||||
|
:ids ids
|
||||||
|
:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|
|
||||||
|
|
@ -637,5 +637,5 @@
|
||||||
(def persistence-state
|
(def persistence-state
|
||||||
(l/derived (comp :status :persistence) st/state))
|
(l/derived (comp :status :persistence) st/state))
|
||||||
|
|
||||||
(def restore
|
(def progress
|
||||||
(l/derived :restore st/state))
|
(l/derived :progress st/state))
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,9 @@
|
||||||
{:stream? true
|
{:stream? true
|
||||||
:form-data? true}
|
:form-data? true}
|
||||||
|
|
||||||
|
::sse/permanently-delete-team-files
|
||||||
|
{:stream? true}
|
||||||
|
|
||||||
::sse/restore-deleted-team-files
|
::sse/restore-deleted-team-files
|
||||||
{:stream? true}
|
{:stream? true}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.main.ui.components.progress
|
||||||
|
"Assets exportation common components."
|
||||||
|
(:require-macros [app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.types.color :as clr]
|
||||||
|
[app.main.data.common :as dcm]
|
||||||
|
[app.main.refs :as refs]
|
||||||
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.icons :as deprecated-icons]
|
||||||
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
|
[app.util.theme :as theme]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(def ^:private neutral-icon
|
||||||
|
(deprecated-icons/icon-xref :msg-neutral (stl/css :icon)))
|
||||||
|
|
||||||
|
(def ^:private error-icon
|
||||||
|
(deprecated-icons/icon-xref :delete-text (stl/css :icon)))
|
||||||
|
|
||||||
|
(def ^:private close-icon
|
||||||
|
(deprecated-icons/icon-xref :close (stl/css :close-icon)))
|
||||||
|
|
||||||
|
(mf/defc progress-notification-widget*
|
||||||
|
[]
|
||||||
|
(let [state (mf/deref refs/progress)
|
||||||
|
profile (mf/deref refs/profile)
|
||||||
|
theme (get profile :theme theme/default)
|
||||||
|
default-theme? (= theme/default theme)
|
||||||
|
error? (:error state)
|
||||||
|
healthy? (:healthy state)
|
||||||
|
visible? (:visible state)
|
||||||
|
progress (:progress state)
|
||||||
|
hint (:hint state)
|
||||||
|
total (:total state)
|
||||||
|
|
||||||
|
pwidth
|
||||||
|
(if error?
|
||||||
|
280
|
||||||
|
(/ (* progress 280) total))
|
||||||
|
|
||||||
|
color
|
||||||
|
(cond
|
||||||
|
error? clr/new-danger
|
||||||
|
healthy? (if default-theme?
|
||||||
|
clr/new-primary
|
||||||
|
clr/new-primary-light)
|
||||||
|
(not healthy?) clr/new-warning)
|
||||||
|
|
||||||
|
background-clr
|
||||||
|
(if default-theme?
|
||||||
|
clr/background-quaternary
|
||||||
|
clr/background-quaternary-light)
|
||||||
|
|
||||||
|
toggle-detail-visibility
|
||||||
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(st/emit! (dcm/toggle-progress-visibility))))]
|
||||||
|
|
||||||
|
[:*
|
||||||
|
(when visible?
|
||||||
|
[:div {:class (stl/css-case :progress-modal true
|
||||||
|
:has-error error?)}
|
||||||
|
(if error?
|
||||||
|
error-icon
|
||||||
|
neutral-icon)
|
||||||
|
|
||||||
|
[:div {:class (stl/css :title)}
|
||||||
|
[:div {:class (stl/css :title-text)} hint]
|
||||||
|
(if error?
|
||||||
|
[:button {:class (stl/css :retry-btn)
|
||||||
|
;; :on-click retry-last-operation
|
||||||
|
}
|
||||||
|
(tr "labels.retry")]
|
||||||
|
|
||||||
|
[:span {:class (stl/css :progress)}
|
||||||
|
(dm/str progress " / " total)])]
|
||||||
|
|
||||||
|
[:button {:class (stl/css :progress-close-button)
|
||||||
|
:on-click toggle-detail-visibility}
|
||||||
|
close-icon]
|
||||||
|
|
||||||
|
(when-not error?
|
||||||
|
[:svg {:class (stl/css :progress-bar)
|
||||||
|
:height 4
|
||||||
|
:width 280}
|
||||||
|
[:g
|
||||||
|
[:path {:d "M0 0 L280 0"
|
||||||
|
:stroke background-clr
|
||||||
|
:stroke-width 30}]
|
||||||
|
[:path {:d (dm/str "M0 0 L280 0")
|
||||||
|
:stroke color
|
||||||
|
:stroke-width 30
|
||||||
|
:fill "transparent"
|
||||||
|
:stroke-dasharray 280
|
||||||
|
:stroke-dashoffset (- 280 pwidth)
|
||||||
|
:style {:transition "stroke-dashoffset 1s ease-in-out"}}]]])])]))
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
|
||||||
|
// PROGRESS WIDGET
|
||||||
|
.progress-widget {
|
||||||
|
@include deprecated.flexCenter;
|
||||||
|
width: deprecated.$s-28;
|
||||||
|
height: deprecated.$s-28;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PROGRESS MODAL
|
||||||
|
.progress-modal {
|
||||||
|
--export-modal-bg-color: var(--alert-background-color-default);
|
||||||
|
--export-modal-fg-color: var(--alert-text-foreground-color-default);
|
||||||
|
--export-modal-icon-color: var(--alert-icon-foreground-color-default);
|
||||||
|
--export-modal-border-color: var(--alert-border-color-default);
|
||||||
|
position: absolute;
|
||||||
|
right: deprecated.$s-16;
|
||||||
|
top: deprecated.$s-48;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: deprecated.$s-24 1fr deprecated.$s-24;
|
||||||
|
grid-template-areas:
|
||||||
|
"icon text close"
|
||||||
|
"bar bar bar";
|
||||||
|
gap: deprecated.$s-4 deprecated.$s-8;
|
||||||
|
padding-block-start: deprecated.$s-8;
|
||||||
|
background-color: var(--export-modal-bg-color);
|
||||||
|
border: deprecated.$s-1 solid var(--export-modal-border-color);
|
||||||
|
border-radius: deprecated.$br-8;
|
||||||
|
z-index: deprecated.$z-index-modal;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-error {
|
||||||
|
--export-modal-bg-color: var(--alert-background-color-error);
|
||||||
|
--export-modal-fg-color: var(--alert-text-foreground-color-error);
|
||||||
|
--export-modal-icon-color: var(--alert-icon-foreground-color-error);
|
||||||
|
--export-modal-border-color: var(--alert-border-color-error);
|
||||||
|
grid-template-areas: "icon text close";
|
||||||
|
gap: deprecated.$s-8;
|
||||||
|
padding-block: deprecated.$s-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@extend .button-icon;
|
||||||
|
grid-area: icon;
|
||||||
|
align-self: center;
|
||||||
|
margin-inline-start: deprecated.$s-8;
|
||||||
|
stroke: var(--export-modal-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include deprecated.bodyMediumTypography;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: deprecated.$s-8;
|
||||||
|
grid-area: text;
|
||||||
|
align-self: center;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--export-modal-fg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
@include deprecated.bodyMediumTypography;
|
||||||
|
padding-left: deprecated.$s-8;
|
||||||
|
margin: 0;
|
||||||
|
align-self: center;
|
||||||
|
color: var(--modal-text-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-btn {
|
||||||
|
@include deprecated.buttonStyle;
|
||||||
|
@include deprecated.bodySmallTypography;
|
||||||
|
display: inline;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--modal-link-foreground-color);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-close-button {
|
||||||
|
@include deprecated.buttonStyle;
|
||||||
|
padding: 0;
|
||||||
|
margin-inline-end: deprecated.$s-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
@extend .button-icon;
|
||||||
|
stroke: var(--export-modal-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
margin-top: 0;
|
||||||
|
grid-area: bar;
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.router :as rt]
|
[app.main.router :as rt]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.components.progress :refer [progress-notification-widget*]]
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.dashboard.deleted :refer [deleted-section*]]
|
[app.main.ui.dashboard.deleted :refer [deleted-section*]]
|
||||||
[app.main.ui.dashboard.files :refer [files-section*]]
|
[app.main.ui.dashboard.files :refer [files-section*]]
|
||||||
|
|
@ -30,7 +31,6 @@
|
||||||
[app.main.ui.dashboard.sidebar :refer [sidebar*]]
|
[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.team :refer [team-settings-page* team-members-page* team-invitations-page* webhooks-page*]]
|
||||||
[app.main.ui.dashboard.templates :refer [templates-section*]]
|
[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.hooks :as hooks]
|
||||||
[app.main.ui.modal :refer [modal-container*]]
|
[app.main.ui.modal :refer [modal-container*]]
|
||||||
[app.main.ui.workspace.plugins]
|
[app.main.ui.workspace.plugins]
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
:on-click clear-selected-fn
|
:on-click clear-selected-fn
|
||||||
:ref container}
|
:ref container}
|
||||||
|
|
||||||
[:& progress-widget {:operation :restore}]
|
[:> progress-notification-widget*]
|
||||||
|
|
||||||
(case section
|
(case section
|
||||||
:dashboard-recent
|
:dashboard-recent
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
[app.main.data.common :as dcm]
|
[app.main.data.common :as dcm]
|
||||||
[app.main.data.dashboard :as dd]
|
[app.main.data.dashboard :as dd]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.notifications :as ntf]
|
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||||
|
|
@ -27,6 +26,8 @@
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(def ^:private ref:deleted-files
|
||||||
|
(l/derived :deleted-files st/state))
|
||||||
|
|
||||||
(def ^:private menu-icon
|
(def ^:private menu-icon
|
||||||
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
|
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
|
||||||
|
|
@ -40,57 +41,40 @@
|
||||||
[:h1 (tr "dashboard.projects-title")]]])
|
[:h1 (tr "dashboard.projects-title")]]])
|
||||||
|
|
||||||
(mf/defc deleted-project-menu*
|
(mf/defc deleted-project-menu*
|
||||||
[{:keys [project files team-id show on-close top left]}]
|
[{:keys [project show on-close top left]}]
|
||||||
(let [top (d/nilv top 0)
|
(let [top (d/nilv top 0)
|
||||||
left (d/nilv left 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
|
on-restore-project
|
||||||
(fn []
|
(mf/use-fn
|
||||||
(st/emit!
|
(mf/deps project)
|
||||||
(modal/show {:type :confirm
|
(fn []
|
||||||
:title (tr "restore-modal.restore-project.title")
|
(let [on-accept #(st/emit! (dd/restore-project-immediately project))]
|
||||||
:message (tr "restore-modal.restore-project.description" (:name project))
|
(st/emit! (modal/show {:type :confirm
|
||||||
:accept-style :primary
|
:title (tr "dashboard.restore-project-confirmation.title")
|
||||||
:accept-label (tr "labels.continue")
|
:message (tr "dashboard.restore-project-confirmation.description" (:name project))
|
||||||
:on-accept restore-fn})))
|
:accept-style :primary
|
||||||
|
:accept-label (tr "labels.continue")
|
||||||
delete-fn
|
:on-accept on-accept})))))
|
||||||
(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
|
on-delete-project
|
||||||
(fn []
|
(mf/use-fn
|
||||||
(st/emit!
|
(mf/deps project)
|
||||||
(modal/show {:type :confirm
|
(fn []
|
||||||
:title (tr "delete-forever-modal.title")
|
(let [accept-fn #(st/emit! (dd/delete-project-immediately project))]
|
||||||
:message (tr "delete-forever-modal.delete-project.description" (:name project))
|
(st/emit! (modal/show {:type :confirm
|
||||||
:accept-label (tr "dashboard.deleted.delete-forever")
|
:title (tr "dashboard.delete-forever-confirmation.title")
|
||||||
:on-accept delete-fn})))
|
:message (tr "dashboard.delete-project-forever-confirmation.description" (:name project))
|
||||||
|
:accept-label (tr "dashboard.delete-forever-confirmation.title")
|
||||||
|
:on-accept accept-fn})))))
|
||||||
options
|
options
|
||||||
[{:name (tr "dashboard.deleted.restore-project")
|
(mf/with-memo [on-restore-project on-delete-project]
|
||||||
:id "project-restore"
|
[{:name (tr "dashboard.restore-project-button")
|
||||||
:handler on-restore-project}
|
:id "project-restore"
|
||||||
{:name (tr "dashboard.deleted.delete-project")
|
:handler on-restore-project}
|
||||||
:id "project-delete"
|
{:name (tr "dashboard.delete-project-button")
|
||||||
:handler on-delete-project}]]
|
:id "project-delete"
|
||||||
|
:handler on-delete-project}])]
|
||||||
|
|
||||||
[:> context-menu*
|
[:> context-menu*
|
||||||
{:on-close on-close
|
{:on-close on-close
|
||||||
|
|
@ -102,9 +86,8 @@
|
||||||
:options options}]))
|
:options options}]))
|
||||||
|
|
||||||
(mf/defc deleted-project-item*
|
(mf/defc deleted-project-item*
|
||||||
{::mf/props :obj
|
{::mf/private true}
|
||||||
::mf/private true}
|
[{:keys [project files]}]
|
||||||
[{:keys [project team files]}]
|
|
||||||
(let [project-files (filterv #(= (:project-id %) (:id project)) files)
|
(let [project-files (filterv #(= (:project-id %) (:id project)) files)
|
||||||
|
|
||||||
empty? (empty? project-files)
|
empty? (empty? project-files)
|
||||||
|
|
@ -170,8 +153,6 @@
|
||||||
(when (:menu-open @local)
|
(when (:menu-open @local)
|
||||||
[:> deleted-project-menu*
|
[:> deleted-project-menu*
|
||||||
{:project project
|
{:project project
|
||||||
:files project-files
|
|
||||||
:team-id (:id team)
|
|
||||||
:show (:menu-open @local)
|
:show (:menu-open @local)
|
||||||
:left (+ 24 (:x (:menu-pos @local)))
|
:left (+ 24 (:x (:menu-pos @local)))
|
||||||
:top (:y (:menu-pos @local))
|
:top (:y (:menu-pos @local))
|
||||||
|
|
@ -193,8 +174,34 @@
|
||||||
:limit limit
|
:limit limit
|
||||||
:selected-files selected-files}])]]))
|
:selected-files selected-files}])]]))
|
||||||
|
|
||||||
(def ^:private ref:deleted-files
|
|
||||||
(l/derived :deleted-files st/state))
|
(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")]]))
|
||||||
|
|
||||||
(mf/defc deleted-section*
|
(mf/defc deleted-section*
|
||||||
[{:keys [team projects]}]
|
[{:keys [team projects]}]
|
||||||
|
|
@ -230,53 +237,33 @@
|
||||||
(and (= "enterprise" sub-type) (not canceled?)) 90
|
(and (= "enterprise" sub-type) (not canceled?)) 90
|
||||||
:else 7))
|
:else 7))
|
||||||
|
|
||||||
on-clear
|
on-delete-all
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps team-id deleted-map)
|
(mf/deps team-id deleted-map)
|
||||||
(fn []
|
(fn []
|
||||||
(when deleted-map
|
(when-let [ids (not-empty (into #{} (map key) deleted-map))]
|
||||||
(let [file-ids (into #{} (keys deleted-map))]
|
(let [on-accept #(st/emit! (dd/delete-files-immediately
|
||||||
(when (seq file-ids)
|
{:team-id team-id
|
||||||
(st/emit!
|
:ids ids}))]
|
||||||
(modal/show {:type :confirm
|
(st/emit! (modal/show {:type :confirm
|
||||||
:title (tr "delete-forever-modal.title")
|
:title (tr "dashboard.delete-forever-confirmation.title")
|
||||||
:message (tr "delete-forever-modal.delete-all.description" (count file-ids))
|
:message (tr "dashboard.delete-all-forever-confirmation.description" (count ids))
|
||||||
:accept-label (tr "dashboard.deleted.delete-forever")
|
:accept-label (tr "dashboard.delete-forever-confirmation.title")
|
||||||
:on-accept #(st/emit!
|
:on-accept on-accept}))))))
|
||||||
(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
|
on-restore-all
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps team-id deleted-map)
|
(mf/deps team-id deleted-map)
|
||||||
(fn []
|
(fn []
|
||||||
(when deleted-map
|
(when-let [ids (not-empty (into #{} (map key) deleted-map))]
|
||||||
(let [file-ids (into #{} (keys deleted-map))]
|
(let [on-accept #(st/emit! (dd/restore-files-immediately {:team-id team-id :ids ids}))]
|
||||||
(when (seq file-ids)
|
(st/emit! (modal/show {:type :confirm
|
||||||
(st/emit!
|
:title (tr "dashboard.restore-all-confirmation.title")
|
||||||
(modal/show {:type :confirm
|
:message (tr "dashboard.restore-all-confirmation.description" (count ids))
|
||||||
:title (tr "restore-modal.restore-all.title")
|
:accept-label (tr "labels.continue")
|
||||||
:message (tr "restore-modal.restore-all.description" (count file-ids))
|
:accept-style :primary
|
||||||
:accept-label (tr "labels.continue")
|
:on-accept on-accept}))))))]
|
||||||
: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]
|
(mf/with-effect [team-id]
|
||||||
(st/emit! (dd/fetch-projects team-id)
|
(st/emit! (dd/fetch-projects team-id)
|
||||||
|
|
@ -289,35 +276,26 @@
|
||||||
[:*
|
[:*
|
||||||
[:div {:class (stl/css :no-bg)}
|
[:div {:class (stl/css :no-bg)}
|
||||||
|
|
||||||
[:div {:class (stl/css :nav-options)}
|
[:> menu* {:team-id team-id :section :dashboard-deleted}]
|
||||||
[:> 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-content)}
|
[:div {:class (stl/css :deleted-info-content)}
|
||||||
[:div {:class (stl/css :deleted-info)}
|
[:p {:class (stl/css :deleted-info)}
|
||||||
[:div
|
(tr "dashboard.trash-info-text-part1")
|
||||||
(tr "dashboard.deleted.info-text")
|
[:span {:class (stl/css :info-text-highlight)}
|
||||||
[:span {:class (stl/css :info-text-highlight)}
|
(tr "dashboard.trash-info-text-part2" deletion-days)]
|
||||||
(tr "dashboard.deleted.info-days" deletion-days)]
|
(tr "dashboard.trash-info-text-part3")
|
||||||
(tr "dashboard.deleted.info-text2")]
|
[:br]
|
||||||
[:div
|
(tr "dashboard.trash-info-text-part4")]
|
||||||
(tr "dashboard.deleted.restore-text")]]
|
|
||||||
[:div {:class (stl/css :deleted-options)}
|
[:div {:class (stl/css :deleted-options)}
|
||||||
[:> button* {:variant "ghost"
|
[:> button* {:variant "ghost"
|
||||||
:type "button"
|
:type "button"
|
||||||
:on-click on-restore-all}
|
:on-click on-restore-all}
|
||||||
(tr "dashboard.deleted.restore-all")]
|
(tr "dashboard.restore-all-deleted-button")]
|
||||||
[:> button* {:variant "destructive"
|
[:> button* {:variant "destructive"
|
||||||
:type "button"
|
:type "button"
|
||||||
:icon "delete"
|
:icon "delete"
|
||||||
:on-click on-clear}
|
:on-click on-delete-all}
|
||||||
(tr "dashboard.deleted.clear")]]]
|
(tr "dashboard.clear-trash-button")]]]
|
||||||
|
|
||||||
(when (seq projects)
|
(when (seq projects)
|
||||||
(for [{:keys [id] :as project} projects]
|
(for [{:keys [id] :as project} projects]
|
||||||
|
|
@ -326,6 +304,5 @@
|
||||||
(filterv #(= id (:project-id %)))
|
(filterv #(= id (:project-id %)))
|
||||||
(sort-by :modified-at #(compare %2 %1))))]
|
(sort-by :modified-at #(compare %2 %1))))]
|
||||||
[:> deleted-project-item* {:project project
|
[:> deleted-project-item* {:project project
|
||||||
:team team
|
|
||||||
:files files
|
:files files
|
||||||
:key id}])))]]]]))
|
:key id}])))]]]]))
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,19 @@
|
||||||
padding-block-end: var(--sp-xxxl);
|
padding-block-end: var(--sp-xxxl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleted-content {
|
.deleted-info-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--sp-l);
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-inline-start: var(--sp-l);
|
padding: var(--sp-s) var(--sp-xxl) var(--sp-s) var(--sp-xxl);
|
||||||
margin-block-start: var(--sp-xxl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleted-info {
|
.deleted-info {
|
||||||
@include t.use-typography("body-medium");
|
display: block;
|
||||||
|
height: fit-content;
|
||||||
color: var(--color-foreground-secondary);
|
color: var(--color-foreground-secondary);
|
||||||
|
@include t.use-typography("body-large");
|
||||||
|
line-height: 0.8;
|
||||||
|
height: var(--sp-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-text-highlight {
|
.info-text-highlight {
|
||||||
|
|
@ -43,27 +45,37 @@
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-options {
|
.nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--sp-l);
|
gap: var(--sp-l);
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-bottom: $b-1 solid var(--panel-border-color);
|
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);
|
background: var(--color-background-default);
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: var(--z-index-panels);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.nav-option {
|
||||||
@include t.use-typography("headline-small");
|
color: var(--color-foreground-secondary);
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: var(--color-foreground-primary);
|
|
||||||
border: $b-1 solid transparent;
|
border: $b-1 solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
color: var(--color-foreground-primary);
|
||||||
border-bottom: $b-1 solid var(--color-foreground-primary);
|
border-bottom: $b-1 solid var(--color-foreground-primary);
|
||||||
padding: 0 var(--sp-m);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.project {
|
.project {
|
||||||
|
|
|
||||||
|
|
@ -194,39 +194,32 @@
|
||||||
(st/emit! (dd/restore-files-immediately
|
(st/emit! (dd/restore-files-immediately
|
||||||
(with-meta {:team-id (:id current-team)
|
(with-meta {:team-id (:id current-team)
|
||||||
:ids #{(:id file)}}
|
:ids #{(:id file)}}
|
||||||
{:on-success #(st/emit! (ntf/success (tr "restore-modal.success-restore-immediately" (:name file)))
|
{:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file)))
|
||||||
(dd/fetch-projects (:id current-team))
|
(dd/fetch-projects (:id current-team))
|
||||||
(dd/fetch-deleted-files (:id current-team)))
|
(dd/fetch-deleted-files (:id current-team)))
|
||||||
:on-error #(st/emit! (ntf/error (tr "restore-modal.error-restore-file" (:name file))))}))))
|
:on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))}))))
|
||||||
|
|
||||||
on-restore-immediately
|
on-restore-immediately
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit!
|
(st/emit!
|
||||||
(modal/show {:type :confirm
|
(modal/show {:type :confirm
|
||||||
:title (tr "restore-modal.restore-file.title")
|
:title (tr "dashboard-restore-file-confirmation.title")
|
||||||
:message (tr "restore-modal.restore-file.description" (:name file))
|
:message (tr "dashboard-restore-file-confirmation.description" (:name file))
|
||||||
:accept-label (tr "labels.continue")
|
:accept-label (tr "labels.continue")
|
||||||
:accept-style :primary
|
:accept-style :primary
|
||||||
:on-accept restore-fn})))
|
: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
|
on-delete-immediately
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit!
|
(let [accept-fn #(st/emit! (dd/delete-files-immediately
|
||||||
(modal/show {:type :confirm
|
{:team-id (:id current-team)
|
||||||
:title (tr "delete-forever-modal.title")
|
:ids #{(:id file)}}))]
|
||||||
:message (tr "delete-forever-modal.delete-file.description" (:name file))
|
(st/emit!
|
||||||
:accept-label (tr "delete-forever-modal.title")
|
(modal/show {:type :confirm
|
||||||
:on-accept delete-fn})))]
|
:title (tr "dashboard.delete-forever-confirmation.title")
|
||||||
|
:message (tr "dashboard.delete-file-forever-confirmation.description" (:name file))
|
||||||
|
:accept-label (tr "dashboard.delete-forever-confirmation.title")
|
||||||
|
:on-accept accept-fn}))))]
|
||||||
|
|
||||||
(mf/with-effect []
|
(mf/with-effect []
|
||||||
(->> (rp/cmd! :get-all-projects)
|
(->> (rp/cmd! :get-all-projects)
|
||||||
|
|
@ -268,11 +261,11 @@
|
||||||
options
|
options
|
||||||
(if can-restore
|
(if can-restore
|
||||||
[(when can-restore
|
[(when can-restore
|
||||||
{:name (tr "dashboard.restore-file")
|
{:name (tr "dashboard.restore-file-button")
|
||||||
:id "restore-file"
|
:id "restore-file"
|
||||||
:handler on-restore-immediately})
|
:handler on-restore-immediately})
|
||||||
(when can-restore
|
(when can-restore
|
||||||
{:name (tr "dashboard.delete-file")
|
{:name (tr "dashboard.delete-file-button")
|
||||||
:id "delete-file"
|
:id "delete-file"
|
||||||
:handler on-delete-immediately})]
|
:handler on-delete-immediately})]
|
||||||
(if multi?
|
(if multi?
|
||||||
|
|
|
||||||
|
|
@ -240,10 +240,13 @@
|
||||||
|
|
||||||
;; --- Grid Item
|
;; --- Grid Item
|
||||||
|
|
||||||
(mf/defc grid-item-metadata
|
(mf/defc grid-item-metadata*
|
||||||
[{:keys [modified-at]}]
|
[{:keys [file]}]
|
||||||
(let [time (ct/timeago modified-at)]
|
(let [time (ct/timeago (or (:will-be-deleted-at file)
|
||||||
[:span {:class (stl/css :date)} time]))
|
(:modified-at file)))]
|
||||||
|
[:span {:class (stl/css :date)
|
||||||
|
:title (tr "dashboard.deleted.will-be-deleted-at" time)}
|
||||||
|
time]))
|
||||||
|
|
||||||
(defn create-counter-element
|
(defn create-counter-element
|
||||||
[_element file-count]
|
[_element file-count]
|
||||||
|
|
@ -429,7 +432,7 @@
|
||||||
:on-end edit
|
:on-end edit
|
||||||
:max-length 250}]
|
:max-length 250}]
|
||||||
[:h3 (:name file)])
|
[:h3 (:name file)])
|
||||||
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
|
[:> grid-item-metadata* {:file file}]]
|
||||||
|
|
||||||
[:div {:class (stl/css-case :project-th-actions true :force-display menu-open?)}
|
[:div {:class (stl/css-case :project-th-actions true :force-display menu-open?)}
|
||||||
[:div
|
[:div
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@
|
||||||
[app.main.data.project :as dpj]
|
[app.main.data.project :as dpj]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.dashboard.deleted :as deleted]
|
||||||
[app.main.ui.dashboard.grid :refer [line-grid]]
|
[app.main.ui.dashboard.grid :refer [line-grid]]
|
||||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||||
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
|
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
|
||||||
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
|
[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.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||||
[app.main.ui.hooks :as hooks]
|
[app.main.ui.hooks :as hooks]
|
||||||
[app.main.ui.icons :as deprecated-icon]
|
[app.main.ui.icons :as deprecated-icon]
|
||||||
|
|
@ -316,40 +316,34 @@
|
||||||
{::mf/props :obj}
|
{::mf/props :obj}
|
||||||
[{:keys [team projects profile]}]
|
[{:keys [team projects profile]}]
|
||||||
|
|
||||||
(let [projects
|
(let [team-id (get team :id)
|
||||||
|
|
||||||
|
recent-map (mf/deref ref:recent-files)
|
||||||
|
permisions (:permissions team)
|
||||||
|
|
||||||
|
can-edit (:can-edit permisions)
|
||||||
|
can-invite (or (:is-owner permisions)
|
||||||
|
(:is-admin permisions))
|
||||||
|
|
||||||
|
show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true))
|
||||||
|
show-team-hero? (deref show-team-hero*)
|
||||||
|
|
||||||
|
my-penpot? (= (:default-team-id profile) team-id)
|
||||||
|
default-team? (:is-default team)
|
||||||
|
|
||||||
|
projects
|
||||||
(mf/with-memo [projects]
|
(mf/with-memo [projects]
|
||||||
(->> projects
|
(->> projects
|
||||||
(remove :deleted-at)
|
(remove :deleted-at)
|
||||||
(sort-by :modified-at)
|
(sort-by :modified-at)
|
||||||
(reverse)))
|
(reverse)))
|
||||||
|
|
||||||
team-id (get team :id)
|
|
||||||
|
|
||||||
recent-map (mf/deref ref:recent-files)
|
|
||||||
permisions (:permissions team)
|
|
||||||
|
|
||||||
can-edit (:can-edit permisions)
|
|
||||||
can-invite (or (:is-owner permisions)
|
|
||||||
(:is-admin permisions))
|
|
||||||
|
|
||||||
show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true))
|
|
||||||
show-team-hero? (deref show-team-hero*)
|
|
||||||
|
|
||||||
is-my-penpot (= (:default-team-id profile) team-id)
|
|
||||||
is-defalt-team? (:is-default team)
|
|
||||||
|
|
||||||
on-close
|
on-close
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn []
|
(fn []
|
||||||
(reset! show-team-hero* false)
|
(reset! show-team-hero* false)
|
||||||
(st/emit! (ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero"
|
(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?]
|
(mf/with-effect [show-team-hero?]
|
||||||
(swap! storage/global assoc ::show-team-hero show-team-hero?))
|
(swap! storage/global assoc ::show-team-hero show-team-hero?))
|
||||||
|
|
@ -373,25 +367,19 @@
|
||||||
[:*
|
[:*
|
||||||
(when (and show-team-hero?
|
(when (and show-team-hero?
|
||||||
can-invite
|
can-invite
|
||||||
(not is-defalt-team?))
|
(not default-team?))
|
||||||
[:> team-hero* {:team team :on-close on-close}])
|
[:> team-hero* {:team team :on-close on-close}])
|
||||||
|
|
||||||
[:div {:class (stl/css-case :dashboard-container true
|
[:div {:class (stl/css-case :dashboard-container true
|
||||||
:no-bg true
|
:no-bg true
|
||||||
:dashboard-projects true
|
:dashboard-projects true
|
||||||
:with-team-hero (and (not is-my-penpot)
|
:with-team-hero (and (not my-penpot?)
|
||||||
(not is-defalt-team?)
|
(not default-team?)
|
||||||
show-team-hero?
|
show-team-hero?
|
||||||
can-invite))}
|
can-invite))}
|
||||||
[:div {:class (stl/css :nav-options)}
|
|
||||||
[:div {:class (stl/css :selected)
|
[:> deleted/menu* {:team-id team-id :section :dashboard-recent}]
|
||||||
: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]
|
(for [{:keys [id] :as project} projects]
|
||||||
;; FIXME: refactor this, looks inneficient
|
;; FIXME: refactor this, looks inneficient
|
||||||
(let [files (when recent-map
|
(let [files (when recent-map
|
||||||
|
|
|
||||||
|
|
@ -248,26 +248,3 @@
|
||||||
width: 0;
|
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,7 +12,6 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.types.color :as clr]
|
[app.common.types.color :as clr]
|
||||||
[app.main.data.dashboard :as dd]
|
|
||||||
[app.main.data.exports.assets :as de]
|
[app.main.data.exports.assets :as de]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
|
|
@ -206,13 +205,13 @@
|
||||||
:cmd :export-frames
|
:cmd :export-frames
|
||||||
:origin origin}]))
|
:origin origin}]))
|
||||||
|
|
||||||
|
;; FIXME: deprecated, should be refactored in two components and use
|
||||||
|
;; the generic progress reporter
|
||||||
|
|
||||||
(mf/defc progress-widget
|
(mf/defc progress-widget
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [operation] :or {operation :export}}]
|
[]
|
||||||
(let [state (mf/deref (case operation
|
(let [state (mf/deref refs/export)
|
||||||
:export refs/export
|
|
||||||
:restore refs/restore
|
|
||||||
refs/export))
|
|
||||||
profile (mf/deref refs/profile)
|
profile (mf/deref refs/profile)
|
||||||
theme (or (:theme profile) theme/default)
|
theme (or (:theme profile) theme/default)
|
||||||
is-default-theme? (= theme/default theme)
|
is-default-theme? (= theme/default theme)
|
||||||
|
|
@ -221,10 +220,7 @@
|
||||||
detail-visible? (:detail-visible state)
|
detail-visible? (:detail-visible state)
|
||||||
widget-visible? (:widget-visible state)
|
widget-visible? (:widget-visible state)
|
||||||
progress (:progress state)
|
progress (:progress state)
|
||||||
items (case operation
|
items (:exports state)
|
||||||
:export (:exports state)
|
|
||||||
:restore (:files state)
|
|
||||||
[])
|
|
||||||
total (or (:total state) (count items))
|
total (or (:total state) (count items))
|
||||||
complete? (= progress total)
|
complete? (= progress total)
|
||||||
circ (* 2 Math/PI 12)
|
circ (* 2 Math/PI 12)
|
||||||
|
|
@ -250,43 +246,23 @@
|
||||||
|
|
||||||
title
|
title
|
||||||
(cond
|
(cond
|
||||||
error? (case operation
|
error? (tr "workspace.options.exporting-object-error")
|
||||||
:export (tr "workspace.options.exporting-object-error")
|
complete? (tr "workspace.options.exporting-complete")
|
||||||
:restore (tr "workspace.options.restoring-object-error")
|
healthy? (tr "workspace.options.exporting-object")
|
||||||
(tr "workspace.options.processing-object-error"))
|
(not healthy?) (tr "workspace.options.exporting-object-slow"))
|
||||||
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
|
retry-last-operation
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps operation)
|
|
||||||
(fn []
|
(fn []
|
||||||
(case operation
|
(st/emit! (de/retry-last-export))))
|
||||||
:export (st/emit! (de/retry-last-export))
|
|
||||||
:restore (st/emit! (dd/retry-last-restore))
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
toggle-detail-visibility
|
toggle-detail-visibility
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps operation)
|
|
||||||
(fn []
|
(fn []
|
||||||
(case operation
|
(st/emit! (de/toggle-detail-visibililty))))]
|
||||||
:export (st/emit! (de/toggle-detail-visibililty))
|
|
||||||
:restore (st/emit! (dd/toggle-restore-detail-visibility))
|
|
||||||
nil)))]
|
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
(when (and widget-visible? (= operation :export))
|
(when widget-visible?
|
||||||
[:div {:class (stl/css :export-progress-widget)
|
[:div {:class (stl/css :export-progress-widget)
|
||||||
:on-click toggle-detail-visibility}
|
:on-click toggle-detail-visibility}
|
||||||
[:svg {:width "24" :height "24"}
|
[:svg {:width "24" :height "24"}
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@
|
||||||
(open-share-dialog)))
|
(open-share-dialog)))
|
||||||
|
|
||||||
[:div {:class (stl/css :options-zone)}
|
[:div {:class (stl/css :options-zone)}
|
||||||
[:& progress-widget {:operation :export}]
|
[:& progress-widget]
|
||||||
|
|
||||||
(case section
|
(case section
|
||||||
:interactions [:*
|
:interactions [:*
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@
|
||||||
[:div {:class (stl/css :users-section)}
|
[:div {:class (stl/css :users-section)}
|
||||||
[:& active-sessions]]
|
[:& active-sessions]]
|
||||||
|
|
||||||
[:& progress-widget {:operation :export}]
|
[:& progress-widget]
|
||||||
|
|
||||||
[:div {:class (stl/css :separator)}]
|
[:div {:class (stl/css :separator)}]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@
|
||||||
[event]
|
[event]
|
||||||
(= "end" (get-type event)))
|
(= "end" (get-type event)))
|
||||||
|
|
||||||
|
(defn progress?
|
||||||
|
[event]
|
||||||
|
(= "progress" (get-type event)))
|
||||||
|
|
||||||
(defn event?
|
(defn event?
|
||||||
[event]
|
[event]
|
||||||
(= "event" (get-type event)))
|
(= "event" (get-type event)))
|
||||||
|
|
|
||||||
|
|
@ -8422,110 +8422,113 @@ msgstr "Autosaved versions will be kept for %s days."
|
||||||
msgid "workspace.viewport.click-to-close-path"
|
msgid "workspace.viewport.click-to-close-path"
|
||||||
msgstr "Click to close the path"
|
msgstr "Click to close the path"
|
||||||
|
|
||||||
msgid "dashboard.labels.recent"
|
msgid "dashboard.deleted.will-be-deleted-at"
|
||||||
|
msgstr "Will be deleted %s"
|
||||||
|
|
||||||
|
msgid "labels.recent"
|
||||||
msgstr "Recent"
|
msgstr "Recent"
|
||||||
|
|
||||||
msgid "dashboard.labels.deleted"
|
msgid "labels.deleted"
|
||||||
msgstr "Deleted"
|
msgstr "Deleted"
|
||||||
|
|
||||||
msgid "dashboard.deleted.restore-all"
|
msgid "dashboard.restore-all-deleted-button"
|
||||||
msgstr "Restore All"
|
msgstr "Restore All"
|
||||||
|
|
||||||
msgid "dashboard.deleted.clear"
|
msgid "dashboard.clear-trash-button"
|
||||||
msgstr "Clear trash"
|
msgstr "Clear trash"
|
||||||
|
|
||||||
msgid "dashboard.restore-file"
|
msgid "dashboard.restore-file-button"
|
||||||
msgstr "Restore file"
|
msgstr "Restore file"
|
||||||
|
|
||||||
msgid "dashboard.delete-file"
|
msgid "dashboard.delete-file-button"
|
||||||
msgstr "Delete file"
|
msgstr "Delete file"
|
||||||
|
|
||||||
msgid "dashboard.deleted.restore-project"
|
msgid "dashboard.restore-project-button"
|
||||||
msgstr "Restore project"
|
msgstr "Restore project"
|
||||||
|
|
||||||
msgid "dashboard.deleted.delete-project"
|
msgid "dashboard.delete-project-button"
|
||||||
msgstr "Delete project"
|
msgstr "Delete project"
|
||||||
|
|
||||||
msgid "dashboard.deleted.info-text"
|
msgid "dashboard.trash-info-text-part1"
|
||||||
msgstr "Deleted files will remain in the trash for"
|
msgstr "Deleted files will remain in the trash for"
|
||||||
|
|
||||||
msgid "dashboard.deleted.info-days"
|
msgid "dashboard.trash-info-text-part2"
|
||||||
msgstr " %s days. "
|
msgstr " %s days. "
|
||||||
|
|
||||||
msgid "dashboard.deleted.info-text2"
|
msgid "dashboard.trash-info-text-part3"
|
||||||
msgstr "After that, they will be permanently deleted."
|
msgstr "After that, they will be permanently deleted."
|
||||||
|
|
||||||
msgid "dashboard.deleted.restore-text"
|
msgid "dashboard.trash-info-text-part4"
|
||||||
msgstr "If you change your mind, you can restore them or delete them permanently from each file's menu."
|
msgstr "If you change your mind, you can restore them or delete them permanently from each file's menu."
|
||||||
|
|
||||||
msgid "dashboard.deleted.delete-forever"
|
msgid "dashboard.restore-all-confirmation.title"
|
||||||
msgstr "Delete forever"
|
|
||||||
|
|
||||||
msgid "restore-modal.restore-all.title"
|
|
||||||
msgstr "Restore all projects and files"
|
msgstr "Restore all projects and files"
|
||||||
|
|
||||||
msgid "restore-modal.restore-all.description"
|
msgid "dashboard.restore-all-confirmation.description"
|
||||||
msgstr "You're going to restore all your projects and files. This may take a while."
|
msgstr "You're going to restore all your projects and files. This may take a while."
|
||||||
|
|
||||||
msgid "restore-modal.restore-file.title"
|
msgid "dashboard-restore-file-confirmation.title"
|
||||||
msgstr "Restore file"
|
msgstr "Restore file"
|
||||||
|
|
||||||
msgid "restore-modal.restore-file.description"
|
msgid "dashboard-restore-file-confirmation.description"
|
||||||
msgstr "You're going to restore %s."
|
msgstr "You're going to restore %s."
|
||||||
|
|
||||||
msgid "restore-modal.restore-project.title"
|
msgid "dashboard.restore-project-confirmation.title"
|
||||||
msgstr "Restore Project"
|
msgstr "Restore Project"
|
||||||
|
|
||||||
msgid "restore-modal.restore-project.description"
|
msgid "dashboard.restore-project-confirmation.description"
|
||||||
msgstr "You're going to restore %s project and all the files contained in it."
|
msgstr "You're going to restore %s project and all the files contained in it."
|
||||||
|
|
||||||
msgid "delete-forever-modal.title"
|
msgid "dashboard.delete-forever-confirmation.title"
|
||||||
msgstr "Delete forever"
|
msgstr "Delete forever"
|
||||||
|
|
||||||
msgid "delete-forever-modal.delete-all.description"
|
msgid "dashboard.delete-all-forever-confirmation.description"
|
||||||
msgstr "Are you sure you want to delete forever all your deleted projects and files? This is a non reversible action."
|
msgstr "Are you sure you want to delete forever all your deleted projects and files? This is a non reversible action."
|
||||||
|
|
||||||
msgid "delete-forever-modal.delete-file.description"
|
msgid "dashboard.delete-file-forever-confirmation.description"
|
||||||
msgstr "Are you sure you want to delete forever %s? This is a non reversible action."
|
msgstr "Are you sure you want to delete forever %s? This is a non reversible action."
|
||||||
|
|
||||||
msgid "delete-forever-modal.delete-project.description"
|
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 reeversible action."
|
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 "restore-modal.success-restore-immediately"
|
msgid "dashboard.restore-files-success-notification"
|
||||||
|
msgstr "%s files have been successfully restored."
|
||||||
|
|
||||||
|
msgid "dashboard.restore-success-notification"
|
||||||
msgstr "%s has been successfully restored."
|
msgstr "%s has been successfully restored."
|
||||||
|
|
||||||
msgid "delete-forever-modal.success-delete-immediately"
|
msgid "dashboard.delete-files-success-notification"
|
||||||
|
msgstr "%s files have been successfully deleted."
|
||||||
|
|
||||||
|
msgid "dashboard.delete-success-notification"
|
||||||
msgstr "%s has been successfully deleted."
|
msgstr "%s has been successfully deleted."
|
||||||
|
|
||||||
msgid "restore-modal.error-restore-files"
|
msgid "dashboard.errors.error-on-restore-files"
|
||||||
msgstr "There was an error while restoring the files."
|
msgstr "There was an error while restoring the files."
|
||||||
|
|
||||||
msgid "restore-modal.error-restore-file"
|
msgid "dashboard.errors.error-on-restore-file"
|
||||||
msgstr "There was an error while restoring the file %s."
|
msgstr "There was an error while restoring the file %s."
|
||||||
|
|
||||||
msgid "restore-modal.error-restore-project"
|
msgid "dashboard.errors.error-on-restoring-project"
|
||||||
msgstr "There was an error while restoring the project %s and its files."
|
msgstr "There was an error while restoring the project %s and its files."
|
||||||
|
|
||||||
msgid "restore-modal.normal-progress-label"
|
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"
|
||||||
msgstr "Restoring files…"
|
msgstr "Restoring files…"
|
||||||
|
|
||||||
msgid "restore-modal.failed-progress-label"
|
msgid "dashboard.progress-notification.deleting-files"
|
||||||
msgstr "Restore failed"
|
msgstr "Deleting files…"
|
||||||
|
|
||||||
msgid "restore-modal.slow-progress-label"
|
msgid "dashboard.progress-notification.slow-restore"
|
||||||
msgstr "Restore unexpectedly slow"
|
msgstr "Restore unexpectedly slow"
|
||||||
|
|
||||||
msgid "restore-modal.complete-process-label"
|
msgid "dashboard.progress-notification.slow-delete"
|
||||||
msgstr "Restore completed"
|
msgstr "Deletion unexpectedly slow"
|
||||||
|
|
||||||
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"
|
msgid "workspace.viewport.click-to-close-path"
|
||||||
msgstr "Pulsar para cerrar la ruta"
|
msgstr "Pulsar para cerrar la ruta"
|
||||||
|
|
||||||
msgid "dashboard.labels.recent"
|
msgid "labels.recent"
|
||||||
msgstr "Recientes"
|
msgstr "Recientes"
|
||||||
|
|
||||||
msgid "dashboard.labels.deleted"
|
msgid "labels.deleted"
|
||||||
msgstr "Eliminados"
|
msgstr "Eliminados"
|
||||||
|
|
||||||
msgid "dashboard.deleted.restore-all"
|
msgid "dashboard.restore-all-deleted-button"
|
||||||
msgstr "Restaurar todo"
|
msgstr "Restaurar todo"
|
||||||
|
|
||||||
msgid "dashboard.deleted.clear"
|
msgid "dashboard.clear-trash-button"
|
||||||
msgstr "Vaciar papelera"
|
msgstr "Vaciar papelera"
|
||||||
|
|
||||||
msgid "dashboard.restore-file"
|
msgid "dashboard.restore-file-button"
|
||||||
msgstr "Restaurar archivo"
|
msgstr "Restaurar archivo"
|
||||||
|
|
||||||
msgid "dashboard.delete-file"
|
msgid "dashboard.delete-file-button"
|
||||||
msgstr "Eliminar archivo"
|
msgstr "Eliminar archivo"
|
||||||
|
|
||||||
msgid "dashboard.deleted.restore-project"
|
msgid "dashboard.restore-project-button"
|
||||||
msgstr "Restaurar proyecto"
|
msgstr "Restaurar proyecto"
|
||||||
|
|
||||||
msgid "dashboard.deleted.delete-project"
|
msgid "dashboard.delete-project-button"
|
||||||
msgstr "Eliminar proyecto"
|
msgstr "Eliminar proyecto"
|
||||||
|
|
||||||
msgid "dashboard.deleted.info-text"
|
msgid "dashboard.trash-info-text-part1"
|
||||||
msgstr "Los archivos eliminados permanecerán en la papelera durante"
|
msgstr "Los archivos eliminados permanecerán en la papelera durante"
|
||||||
|
|
||||||
msgid "dashboard.deleted.info-days"
|
msgid "dashboard.trash-info-text-part2"
|
||||||
msgstr " %s días. "
|
msgstr " %s días. "
|
||||||
|
|
||||||
msgid "dashboard.deleted.info-text2"
|
msgid "dashboard.trash-info-text-part3"
|
||||||
msgstr "Después de eso, serán eliminados permanentemente."
|
msgstr "Después de eso, serán eliminados permanentemente."
|
||||||
|
|
||||||
msgid "dashboard.deleted.restore-text"
|
msgid "dashboard.trash-info-text-part4"
|
||||||
msgstr "Si cambias de opinión, puedes restaurarlos o eliminarlos permanentemente desde el menú de cada archivo."
|
msgstr "Si cambias de opinión, puedes restaurarlos o eliminarlos permanentemente desde el menú de cada archivo."
|
||||||
|
|
||||||
msgid "dashboard.deleted.delete-forever"
|
msgid "dashboard.deleted.delete-forever"
|
||||||
msgstr "Eliminar para siempre"
|
msgstr "Eliminar para siempre"
|
||||||
|
|
||||||
msgid "restore-modal.restore-all.title"
|
msgid "dashboard.restore-all-confirmation.title"
|
||||||
msgstr "Restaurar todos los proyectos y archivos"
|
msgstr "Restaurar todos los proyectos y archivos"
|
||||||
|
|
||||||
msgid "restore-modal.restore-all.description"
|
msgid "dashboard.restore-all-confirmation.description"
|
||||||
msgstr "Vas a restaurar todos tus proyectos y archivos. Esto puede tardar un poco."
|
msgstr "Vas a restaurar todos tus proyectos y archivos. Esto puede tardar un poco."
|
||||||
|
|
||||||
msgid "restore-modal.restore-file.title"
|
msgid "dashboard-restore-file-confirmation.title"
|
||||||
msgstr "Restaurar archivo"
|
msgstr "Restaurar archivo"
|
||||||
|
|
||||||
msgid "restore-modal.restore-file.description"
|
msgid "dashboard-restore-file-confirmation.description"
|
||||||
msgstr "Vas a restaurar %s."
|
msgstr "Vas a restaurar %s."
|
||||||
|
|
||||||
msgid "restore-modal.restore-project.title"
|
msgid "dashboard.restore-project-confirmation.title"
|
||||||
msgstr "Restaurar proyecto"
|
msgstr "Restaurar proyecto"
|
||||||
|
|
||||||
msgid "restore-modal.restore-project.description"
|
msgid "dashboard.restore-project-confirmation.description"
|
||||||
msgstr "Vas a restaurar el proyecto %s y todos los archivos que contiene."
|
msgstr "Vas a restaurar el proyecto %s y todos los archivos que contiene."
|
||||||
|
|
||||||
msgid "delete-forever-modal.title"
|
msgid "dashboard.delete-forever-confirmation.title"
|
||||||
msgstr "Eliminar para siempre"
|
msgstr "Eliminar para siempre"
|
||||||
|
|
||||||
msgid "delete-forever-modal.delete-all.description"
|
msgid "dashboard.delete-all-forever-confirmation.description"
|
||||||
msgstr "¿Estás seguro de que quieres eliminar para siempre todos tus proyectos y archivos eliminados? Esta es una acción irreversible."
|
msgstr "¿Estás seguro de que quieres eliminar para siempre todos tus proyectos y archivos eliminados? Esta es una acción irreversible."
|
||||||
|
|
||||||
msgid "delete-forever-modal.delete-file.description"
|
msgid "dashboard.delete-file-forever-confirmation.description"
|
||||||
msgstr "¿Estás seguro de que quieres eliminar para siempre %s? Esta es una acción irreversible."
|
msgstr "¿Estás seguro de que quieres eliminar para siempre %s? Esta es una acción irreversible."
|
||||||
|
|
||||||
msgid "delete-forever-modal.delete-project.description"
|
msgid "dashboard.delete-project-forever-confirmation.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."
|
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 "restore-modal.success-restore-immediately"
|
msgid "dashboard.restore-files-success-notification"
|
||||||
|
msgstr "%s ficheros han sido restaurado correctamente."
|
||||||
|
|
||||||
|
msgid "dashboard.restore-success-notification"
|
||||||
msgstr "%s ha sido restaurado correctamente."
|
msgstr "%s ha sido restaurado correctamente."
|
||||||
|
|
||||||
msgid "delete-forever-modal.success-delete-immediately"
|
msgid "dashboard.delete-files-success-notification"
|
||||||
|
msgstr "%s ficheros han sido eliminados correctamente."
|
||||||
|
|
||||||
|
msgid "dashboard.delete-success-notification"
|
||||||
msgstr "%s ha sido eliminado correctamente."
|
msgstr "%s ha sido eliminado correctamente."
|
||||||
|
|
||||||
msgid "restore-modal.error-restore-files"
|
msgid "dashboard.errors.error-on-restore-files"
|
||||||
msgstr "Hubo un error al restaurar los archivos."
|
msgstr "Hubo un error al restaurar los archivos."
|
||||||
|
|
||||||
msgid "restore-modal.error-restore-file"
|
msgid "dashboard.errors.error-on-restore-file"
|
||||||
msgstr "Hubo un error al restaurar el archivo %s."
|
msgstr "Hubo un error al restaurar el archivo %s."
|
||||||
|
|
||||||
msgid "restore-modal.error-restore-project"
|
msgid "dashboard.errors.error-on-restoring-files"
|
||||||
msgstr "Hubo un error al restaurar el proyecto %s y sus archivos."
|
msgstr "Hubo un error al restaurar archivos."
|
||||||
|
|
||||||
msgid "restore-modal.normal-progress-label"
|
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"
|
||||||
msgstr "Restaurando archivos…"
|
msgstr "Restaurando archivos…"
|
||||||
|
|
||||||
msgid "restore-modal.failed-progress-label"
|
msgid "dashboard.progress-notification.deleting-files"
|
||||||
msgstr "Falló la restauración"
|
msgstr "Eliminando archivos…"
|
||||||
|
|
||||||
msgid "restore-modal.slow-progress-label"
|
msgid "dashboard.progress-notification.slow-restore"
|
||||||
msgstr "Restauración lenta"
|
msgstr "Restauración inesperadamente lenta"
|
||||||
|
|
||||||
msgid "restore-modal.complete-process-label"
|
msgid "dashboard.progress-notification.slow-delete"
|
||||||
msgstr "Restauración completada"
|
msgstr "Eliminación inesperadamente lenta"
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue