mirror of
https://github.com/penpot/penpot.git
synced 2025-12-24 06:58:34 -05:00
Compare commits
1 Commits
develop
...
niwinz-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08665d2a8c |
@@ -704,7 +704,6 @@
|
||||
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,
|
||||
@@ -814,7 +813,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)
|
||||
@@ -838,7 +837,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}]
|
||||
@@ -853,10 +852,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
|
||||
@@ -873,7 +872,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}]
|
||||
@@ -886,14 +885,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})
|
||||
(select-keys file [:id :name :is-shared]))
|
||||
file)
|
||||
|
||||
(and (false? (:is-shared file))
|
||||
(true? (:is-shared params)))
|
||||
@@ -940,6 +939,11 @@
|
||||
{: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
|
||||
@@ -1090,47 +1094,53 @@
|
||||
|
||||
;; --- MUTATION COMMAND: delete-files-immediatelly
|
||||
|
||||
(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 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 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.12"
|
||||
::sm/params schema:permanently-delete-team-files
|
||||
::db/transaction true}
|
||||
{::doc/added "2.13"
|
||||
::sm/params schema:permanently-delete-team-files}
|
||||
|
||||
[{: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)])))
|
||||
[{: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)))
|
||||
|
||||
;; --- MUTATION COMMAND: restore-files-immediatelly
|
||||
|
||||
@@ -1194,7 +1204,7 @@
|
||||
{:keys [files projects]}
|
||||
(reduce (fn [result {:keys [id project-id]}]
|
||||
(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)
|
||||
|
||||
(-> result
|
||||
@@ -1217,7 +1227,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.12"
|
||||
{::doc/added "2.13"
|
||||
::sse/stream? true
|
||||
::sm/params schema:restore-deleted-team-files}
|
||||
[cfg params]
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
:deleted-at (ct/format-inst deleted-at))
|
||||
|
||||
(db/update! conn :file
|
||||
{:deleted-at deleted-at}
|
||||
{:deleted-at deleted-at
|
||||
:is-shared false}
|
||||
{:id id}
|
||||
{::db/return-keys false})
|
||||
|
||||
@@ -53,7 +54,7 @@
|
||||
(not *team-deletion*))
|
||||
;; NOTE: we don't prevent file deletion on absorb operation failure
|
||||
(try
|
||||
(db/tx-run! cfg files/absorb-library! id)
|
||||
(db/tx-run! cfg files/absorb-library id)
|
||||
(catch Throwable cause
|
||||
(l/warn :hint "error on absorbing library"
|
||||
:file-id id
|
||||
|
||||
@@ -595,8 +595,8 @@
|
||||
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
|
||||
(into []
|
||||
(map (fn [{:keys [event data]}]
|
||||
[(keyword event)
|
||||
(tr/decode-str data)]))
|
||||
(d/vec2 (keyword event)
|
||||
(tr/decode-str data))))
|
||||
(parse-sse (slurp' input)))
|
||||
(finally
|
||||
(.close input)))))
|
||||
|
||||
@@ -1921,7 +1921,11 @@
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error 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])]
|
||||
(t/is (= (:deleted-at row) now)))))))
|
||||
|
||||
29
common/src/app/common/types/project.cljc
Normal file
29
common/src/app/common/types/project.cljc
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.types.project
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as cm]))
|
||||
|
||||
(def schema:project
|
||||
[:map {:title "Profile"}
|
||||
[:id ::sm/uuid]
|
||||
[:created-at {:optional true} ::cm/inst]
|
||||
[:modified-at {:optional true} ::cm/inst]
|
||||
[:name :string]
|
||||
[:is-default {:optional true} ::sm/boolean]
|
||||
[:is-pinned {:optional true} ::sm/boolean]
|
||||
[:count {:optional true} ::sm/int]
|
||||
[:total-count {:optional true} ::sm/int]
|
||||
[:team-id ::sm/uuid]])
|
||||
|
||||
(def valid-project?
|
||||
(sm/lazy-validator schema:project))
|
||||
|
||||
(def check-project
|
||||
(sm/check-fn schema:project))
|
||||
@@ -10,6 +10,7 @@
|
||||
[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]
|
||||
@@ -229,6 +230,91 @@
|
||||
;; Delay so the navigation can finish
|
||||
(rx/delay 250))))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; PROGRESS EVENTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def noop-fn
|
||||
(constantly nil))
|
||||
|
||||
(def ^:private schema:progress-params
|
||||
[:map {:title "Progress"}
|
||||
[:key {:optional true} ::sm/text]
|
||||
[:index {:optional true} ::sm/int]
|
||||
[:total ::sm/int]
|
||||
[:hints
|
||||
[:map-of :keyword fn?]]
|
||||
[:slow-progress-threshold {:optional true} ::sm/int]])
|
||||
|
||||
(def ^:private check-progress-params
|
||||
(sm/check-fn schema:progress-params))
|
||||
|
||||
(defn initialize-progress
|
||||
[& {:keys [key index total hints slow-progress-threshold] :as params}]
|
||||
|
||||
(assert (check-progress-params params))
|
||||
|
||||
(ptk/reify ::initialize-progress
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :progress
|
||||
(fn [_]
|
||||
(let [hint ((:normal hints noop-fn) params)]
|
||||
{:threshold (or slow-progress-threshold 5000)
|
||||
:key key
|
||||
:last-update (ct/now)
|
||||
:healthy true
|
||||
:visible true
|
||||
:hints hints
|
||||
:progress (d/nilv index 0)
|
||||
:total total
|
||||
:hint hint}))))))
|
||||
|
||||
(defn update-progress
|
||||
[{:keys [index total] :as params}]
|
||||
|
||||
(assert (check-progress-params params))
|
||||
|
||||
(ptk/reify ::update-progress
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :progress
|
||||
(fn [state]
|
||||
(let [last-update (get state :last-update)
|
||||
hints (get state :hints)
|
||||
threshold (get state :slow-progress-threshold)
|
||||
|
||||
time-diff (ct/diff-ms last-update (ct/now))
|
||||
healthy? (< time-diff threshold)
|
||||
|
||||
hint (if healthy?
|
||||
((:normal hints noop-fn) params)
|
||||
((:slow hints noop-fn) params))]
|
||||
|
||||
(-> state
|
||||
(assoc :progress index)
|
||||
(assoc :total total)
|
||||
(assoc :last-update (ct/now))
|
||||
(assoc :healthy healthy?)
|
||||
(assoc :hint hint))))))))
|
||||
|
||||
(defn toggle-progress-visibility
|
||||
[]
|
||||
(ptk/reify ::toggle-progress-visibility
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :progress
|
||||
(fn [state]
|
||||
(update state :visible not))))))
|
||||
|
||||
(defn clear-progress
|
||||
[]
|
||||
(ptk/reify ::clear-progress
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(dissoc state :progress))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; NAVEGATION EVENTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
[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]
|
||||
@@ -691,6 +693,56 @@
|
||||
|
||||
;; --- 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))
|
||||
@@ -698,145 +750,189 @@
|
||||
(assert (every? uuid? ids))
|
||||
|
||||
(ptk/reify ::delete-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)]
|
||||
(->> (rp/cmd! :permanently-delete-team-files {:team-id team-id :ids ids})
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
(watch [_ state _]
|
||||
(let [deleted-files
|
||||
(get state :deleted-files)
|
||||
|
||||
on-success
|
||||
(fn []
|
||||
(if (= 1 (count ids))
|
||||
(let [fname (get-in deleted-files [(first ids) :name])]
|
||||
(rx/of (ntf/success (tr "dashboard.delete-success-notification" fname))))
|
||||
(rx/of (ntf/success (tr "dashboard.delete-files-success-notification" (count ids))))))
|
||||
|
||||
on-error
|
||||
#(rx/of (ntf/error (tr "dashboard.errors.error-on-delete-files")))]
|
||||
|
||||
(rx/of (ev/event
|
||||
{::ev/name "delete-files"
|
||||
::ev/origin "dashboard:trash"
|
||||
:team-id team-id
|
||||
:num-files (count ids)})
|
||||
(delete-files
|
||||
{:team-id team-id
|
||||
:ids ids
|
||||
:on-success on-success
|
||||
:on-error on-error}))))))
|
||||
|
||||
|
||||
(defn delete-project-immediately
|
||||
[{:keys [team-id id name] :as project}]
|
||||
(assert (valid-project? project))
|
||||
|
||||
(ptk/reify ::delete-project-immediately
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [ids
|
||||
(reduce-kv (fn [acc file-id file]
|
||||
(if (= (:project-id file) id)
|
||||
(conj acc file-id)
|
||||
acc))
|
||||
#{}
|
||||
(get state :deleted-files))
|
||||
|
||||
on-success
|
||||
#(rx/of (ntf/success (tr "dashboard.delete-success-notification" name)))
|
||||
|
||||
on-error
|
||||
#(rx/of (ntf/error (tr "dashboard.errors.error-on-delete-project" name)))]
|
||||
|
||||
(rx/of (ev/event
|
||||
{::ev/name "delete-files"
|
||||
::ev/origin "dashboard:trash"
|
||||
:team-id team-id
|
||||
:project-id id
|
||||
:num-files (count ids)})
|
||||
(delete-files
|
||||
{:team-id team-id
|
||||
:ids ids
|
||||
:on-success on-success
|
||||
:on-error on-error}))))))
|
||||
|
||||
|
||||
;; --- Restore deleted files immediately
|
||||
|
||||
(defn- 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
|
||||
[{: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?)))))
|
||||
(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- 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/reify ::restore-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
;; Refetch projects to get the updated state without deleted-at
|
||||
(rx/of (fetch-projects team-id)))))
|
||||
|
||||
(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)]
|
||||
(let [progress-hint #(tr "dashboard.progress-notification.restoring-files")
|
||||
slow-hint #(tr "dashboard.progress-notification.slow-restore")]
|
||||
|
||||
(rx/merge
|
||||
(rx/of (initialize-restore-status files))
|
||||
(rx/of (dcm/initialize-progress
|
||||
{:slow-progress-threshold 1000
|
||||
:total (count ids)
|
||||
:hints {:progress progress-hint
|
||||
:slow slow-hint}}))
|
||||
|
||||
(->> (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)))))
|
||||
(let [stream (->> (rp/cmd! ::sse/restore-deleted-team-files {:team-id team-id :ids ids})
|
||||
(rx/share))]
|
||||
|
||||
(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
|
||||
(l/derived (comp :status :persistence) st/state))
|
||||
|
||||
(def restore
|
||||
(l/derived :restore st/state))
|
||||
(def progress
|
||||
(l/derived :progress st/state))
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
{:stream? true
|
||||
:form-data? true}
|
||||
|
||||
::sse/permanently-delete-team-files
|
||||
{:stream? true}
|
||||
|
||||
::sse/restore-deleted-team-files
|
||||
{:stream? true}
|
||||
|
||||
|
||||
103
frontend/src/app/main/ui/components/progress.cljs
Normal file
103
frontend/src/app/main/ui/components/progress.cljs
Normal file
@@ -0,0 +1,103 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.components.progress
|
||||
"Assets exportation common components."
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.color :as clr]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as deprecated-icons]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.theme :as theme]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private neutral-icon
|
||||
(deprecated-icons/icon-xref :msg-neutral (stl/css :icon)))
|
||||
|
||||
(def ^:private error-icon
|
||||
(deprecated-icons/icon-xref :delete-text (stl/css :icon)))
|
||||
|
||||
(def ^:private close-icon
|
||||
(deprecated-icons/icon-xref :close (stl/css :close-icon)))
|
||||
|
||||
(mf/defc progress-notification-widget*
|
||||
[]
|
||||
(let [state (mf/deref refs/progress)
|
||||
profile (mf/deref refs/profile)
|
||||
theme (get profile :theme theme/default)
|
||||
default-theme? (= theme/default theme)
|
||||
error? (:error state)
|
||||
healthy? (:healthy state)
|
||||
visible? (:visible state)
|
||||
progress (:progress state)
|
||||
hint (:hint state)
|
||||
total (:total state)
|
||||
|
||||
pwidth
|
||||
(if error?
|
||||
280
|
||||
(/ (* progress 280) total))
|
||||
|
||||
color
|
||||
(cond
|
||||
error? clr/new-danger
|
||||
healthy? (if default-theme?
|
||||
clr/new-primary
|
||||
clr/new-primary-light)
|
||||
(not healthy?) clr/new-warning)
|
||||
|
||||
background-clr
|
||||
(if default-theme?
|
||||
clr/background-quaternary
|
||||
clr/background-quaternary-light)
|
||||
|
||||
toggle-detail-visibility
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(st/emit! (dcm/toggle-progress-visibility))))]
|
||||
|
||||
[:*
|
||||
(when visible?
|
||||
[:div {:class (stl/css-case :progress-modal true
|
||||
:has-error error?)}
|
||||
(if error?
|
||||
error-icon
|
||||
neutral-icon)
|
||||
|
||||
[:div {:class (stl/css :title)}
|
||||
[:div {:class (stl/css :title-text)} hint]
|
||||
(if error?
|
||||
[:button {:class (stl/css :retry-btn)
|
||||
;; :on-click retry-last-operation
|
||||
}
|
||||
(tr "labels.retry")]
|
||||
|
||||
[:span {:class (stl/css :progress)}
|
||||
(dm/str progress " / " total)])]
|
||||
|
||||
[:button {:class (stl/css :progress-close-button)
|
||||
:on-click toggle-detail-visibility}
|
||||
close-icon]
|
||||
|
||||
(when-not error?
|
||||
[:svg {:class (stl/css :progress-bar)
|
||||
:height 4
|
||||
:width 280}
|
||||
[:g
|
||||
[:path {:d "M0 0 L280 0"
|
||||
:stroke background-clr
|
||||
:stroke-width 30}]
|
||||
[:path {:d (dm/str "M0 0 L280 0")
|
||||
:stroke color
|
||||
:stroke-width 30
|
||||
:fill "transparent"
|
||||
:stroke-dasharray 280
|
||||
:stroke-dashoffset (- 280 pwidth)
|
||||
:style {:transition "stroke-dashoffset 1s ease-in-out"}}]]])])]))
|
||||
101
frontend/src/app/main/ui/components/progress.scss
Normal file
101
frontend/src/app/main/ui/components/progress.scss
Normal file
@@ -0,0 +1,101 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
// PROGRESS WIDGET
|
||||
.progress-widget {
|
||||
@include deprecated.flexCenter;
|
||||
width: deprecated.$s-28;
|
||||
height: deprecated.$s-28;
|
||||
}
|
||||
|
||||
// PROGRESS MODAL
|
||||
.progress-modal {
|
||||
--export-modal-bg-color: var(--alert-background-color-default);
|
||||
--export-modal-fg-color: var(--alert-text-foreground-color-default);
|
||||
--export-modal-icon-color: var(--alert-icon-foreground-color-default);
|
||||
--export-modal-border-color: var(--alert-border-color-default);
|
||||
position: absolute;
|
||||
right: deprecated.$s-16;
|
||||
top: deprecated.$s-48;
|
||||
display: grid;
|
||||
grid-template-columns: deprecated.$s-24 1fr deprecated.$s-24;
|
||||
grid-template-areas:
|
||||
"icon text close"
|
||||
"bar bar bar";
|
||||
gap: deprecated.$s-4 deprecated.$s-8;
|
||||
padding-block-start: deprecated.$s-8;
|
||||
background-color: var(--export-modal-bg-color);
|
||||
border: deprecated.$s-1 solid var(--export-modal-border-color);
|
||||
border-radius: deprecated.$br-8;
|
||||
z-index: deprecated.$z-index-modal;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.has-error {
|
||||
--export-modal-bg-color: var(--alert-background-color-error);
|
||||
--export-modal-fg-color: var(--alert-text-foreground-color-error);
|
||||
--export-modal-icon-color: var(--alert-icon-foreground-color-error);
|
||||
--export-modal-border-color: var(--alert-border-color-error);
|
||||
grid-template-areas: "icon text close";
|
||||
gap: deprecated.$s-8;
|
||||
padding-block: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@extend .button-icon;
|
||||
grid-area: icon;
|
||||
align-self: center;
|
||||
margin-inline-start: deprecated.$s-8;
|
||||
stroke: var(--export-modal-icon-color);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include deprecated.bodyMediumTypography;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: deprecated.$s-8;
|
||||
grid-area: text;
|
||||
align-self: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: var(--export-modal-fg-color);
|
||||
}
|
||||
|
||||
.progress {
|
||||
@include deprecated.bodyMediumTypography;
|
||||
padding-left: deprecated.$s-8;
|
||||
margin: 0;
|
||||
align-self: center;
|
||||
color: var(--modal-text-foreground-color);
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
@include deprecated.buttonStyle;
|
||||
@include deprecated.bodySmallTypography;
|
||||
display: inline;
|
||||
text-align: left;
|
||||
color: var(--modal-link-foreground-color);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.progress-close-button {
|
||||
@include deprecated.buttonStyle;
|
||||
padding: 0;
|
||||
margin-inline-end: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
@extend .button-icon;
|
||||
stroke: var(--export-modal-icon-color);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
margin-top: 0;
|
||||
grid-area: bar;
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
[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*]]
|
||||
@@ -30,7 +31,6 @@
|
||||
[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-widget {:operation :restore}]
|
||||
[:> progress-notification-widget*]
|
||||
|
||||
(case section
|
||||
:dashboard-recent
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[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*]]
|
||||
@@ -27,6 +26,8 @@
|
||||
[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)))
|
||||
@@ -40,57 +41,40 @@
|
||||
[:h1 (tr "dashboard.projects-title")]]])
|
||||
|
||||
(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)
|
||||
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
|
||||
(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)))
|
||||
(mf/use-fn
|
||||
(mf/deps project)
|
||||
(fn []
|
||||
(let [on-accept #(st/emit! (dd/restore-project-immediately project))]
|
||||
(st/emit! (modal/show {:type :confirm
|
||||
:title (tr "dashboard.restore-project-confirmation.title")
|
||||
:message (tr "dashboard.restore-project-confirmation.description" (:name project))
|
||||
:accept-style :primary
|
||||
:accept-label (tr "labels.continue")
|
||||
:on-accept on-accept})))))
|
||||
|
||||
on-delete-project
|
||||
(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})))
|
||||
(mf/use-fn
|
||||
(mf/deps project)
|
||||
(fn []
|
||||
(let [accept-fn #(st/emit! (dd/delete-project-immediately project))]
|
||||
(st/emit! (modal/show {:type :confirm
|
||||
:title (tr "dashboard.delete-forever-confirmation.title")
|
||||
:message (tr "dashboard.delete-project-forever-confirmation.description" (:name project))
|
||||
:accept-label (tr "dashboard.delete-forever-confirmation.title")
|
||||
:on-accept accept-fn})))))
|
||||
options
|
||||
[{: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}]]
|
||||
(mf/with-memo [on-restore-project on-delete-project]
|
||||
[{:name (tr "dashboard.restore-project-button")
|
||||
:id "project-restore"
|
||||
:handler on-restore-project}
|
||||
{:name (tr "dashboard.delete-project-button")
|
||||
:id "project-delete"
|
||||
:handler on-delete-project}])]
|
||||
|
||||
[:> context-menu*
|
||||
{:on-close on-close
|
||||
@@ -102,9 +86,8 @@
|
||||
:options options}]))
|
||||
|
||||
(mf/defc deleted-project-item*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [project team files]}]
|
||||
{::mf/private true}
|
||||
[{:keys [project files]}]
|
||||
(let [project-files (filterv #(= (:project-id %) (:id project)) files)
|
||||
|
||||
empty? (empty? project-files)
|
||||
@@ -170,8 +153,6 @@
|
||||
(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))
|
||||
@@ -193,8 +174,34 @@
|
||||
:limit limit
|
||||
: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*
|
||||
[{:keys [team projects]}]
|
||||
@@ -230,53 +237,33 @@
|
||||
(and (= "enterprise" sub-type) (not canceled?)) 90
|
||||
:else 7))
|
||||
|
||||
on-clear
|
||||
on-delete-all
|
||||
(mf/use-fn
|
||||
(mf/deps team-id deleted-map)
|
||||
(fn []
|
||||
(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")))}))))
|
||||
(when-let [ids (not-empty (into #{} (map key) deleted-map))]
|
||||
(let [on-accept #(st/emit! (dd/delete-files-immediately
|
||||
{:team-id team-id
|
||||
:ids ids}))]
|
||||
(st/emit! (modal/show {:type :confirm
|
||||
:title (tr "dashboard.delete-forever-confirmation.title")
|
||||
:message (tr "dashboard.delete-all-forever-confirmation.description" (count ids))
|
||||
:accept-label (tr "dashboard.delete-forever-confirmation.title")
|
||||
:on-accept on-accept}))))))
|
||||
|
||||
on-restore-all
|
||||
(mf/use-fn
|
||||
(mf/deps team-id deleted-map)
|
||||
(fn []
|
||||
(when 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)})))))))
|
||||
(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}))))))]
|
||||
|
||||
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)
|
||||
@@ -289,35 +276,26 @@
|
||||
[:*
|
||||
[:div {:class (stl/css :no-bg)}
|
||||
|
||||
[: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")]]
|
||||
[:> menu* {:team-id team-id :section :dashboard-deleted}]
|
||||
|
||||
[: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-info-content)}
|
||||
[:p {:class (stl/css :deleted-info)}
|
||||
(tr "dashboard.trash-info-text-part1")
|
||||
[:span {:class (stl/css :info-text-highlight)}
|
||||
(tr "dashboard.trash-info-text-part2" deletion-days)]
|
||||
(tr "dashboard.trash-info-text-part3")
|
||||
[:br]
|
||||
(tr "dashboard.trash-info-text-part4")]
|
||||
[:div {:class (stl/css :deleted-options)}
|
||||
[:> button* {:variant "ghost"
|
||||
:type "button"
|
||||
:on-click on-restore-all}
|
||||
(tr "dashboard.deleted.restore-all")]
|
||||
(tr "dashboard.restore-all-deleted-button")]
|
||||
[:> button* {:variant "destructive"
|
||||
:type "button"
|
||||
:icon "delete"
|
||||
:on-click on-clear}
|
||||
(tr "dashboard.deleted.clear")]]]
|
||||
:on-click on-delete-all}
|
||||
(tr "dashboard.clear-trash-button")]]]
|
||||
|
||||
(when (seq projects)
|
||||
(for [{:keys [id] :as project} projects]
|
||||
@@ -326,6 +304,5 @@
|
||||
(filterv #(= id (:project-id %)))
|
||||
(sort-by :modified-at #(compare %2 %1))))]
|
||||
[:> deleted-project-item* {:project project
|
||||
:team team
|
||||
:files files
|
||||
:key id}])))]]]]))
|
||||
|
||||
@@ -20,17 +20,19 @@
|
||||
padding-block-end: var(--sp-xxxl);
|
||||
}
|
||||
|
||||
.deleted-content {
|
||||
.deleted-info-content {
|
||||
display: flex;
|
||||
gap: var(--sp-l);
|
||||
justify-content: space-between;
|
||||
margin-inline-start: var(--sp-l);
|
||||
margin-block-start: var(--sp-xxl);
|
||||
padding: var(--sp-s) var(--sp-xxl) var(--sp-s) var(--sp-xxl);
|
||||
}
|
||||
|
||||
.deleted-info {
|
||||
@include t.use-typography("body-medium");
|
||||
display: block;
|
||||
height: fit-content;
|
||||
color: var(--color-foreground-secondary);
|
||||
@include t.use-typography("body-large");
|
||||
line-height: 0.8;
|
||||
height: var(--sp-xl);
|
||||
}
|
||||
|
||||
.info-text-highlight {
|
||||
@@ -43,27 +45,37 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-options {
|
||||
.nav {
|
||||
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);
|
||||
}
|
||||
|
||||
.selected {
|
||||
@include t.use-typography("headline-small");
|
||||
.nav-option {
|
||||
color: var(--color-foreground-secondary);
|
||||
padding: 0.5rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-foreground-primary);
|
||||
border: $b-1 solid transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: var(--color-foreground-primary);
|
||||
border-bottom: $b-1 solid var(--color-foreground-primary);
|
||||
padding: 0 var(--sp-m);
|
||||
}
|
||||
|
||||
.project {
|
||||
|
||||
@@ -194,39 +194,32 @@
|
||||
(st/emit! (dd/restore-files-immediately
|
||||
(with-meta {:team-id (:id current-team)
|
||||
: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-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
|
||||
(fn []
|
||||
(st/emit!
|
||||
(modal/show {:type :confirm
|
||||
:title (tr "restore-modal.restore-file.title")
|
||||
:message (tr "restore-modal.restore-file.description" (:name file))
|
||||
:title (tr "dashboard-restore-file-confirmation.title")
|
||||
:message (tr "dashboard-restore-file-confirmation.description" (:name file))
|
||||
:accept-label (tr "labels.continue")
|
||||
:accept-style :primary
|
||||
:on-accept restore-fn})))
|
||||
|
||||
|
||||
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 []
|
||||
(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})))]
|
||||
(let [accept-fn #(st/emit! (dd/delete-files-immediately
|
||||
{:team-id (:id current-team)
|
||||
:ids #{(:id file)}}))]
|
||||
(st/emit!
|
||||
(modal/show {:type :confirm
|
||||
:title (tr "dashboard.delete-forever-confirmation.title")
|
||||
:message (tr "dashboard.delete-file-forever-confirmation.description" (:name file))
|
||||
:accept-label (tr "dashboard.delete-forever-confirmation.title")
|
||||
:on-accept accept-fn}))))]
|
||||
|
||||
(mf/with-effect []
|
||||
(->> (rp/cmd! :get-all-projects)
|
||||
@@ -268,11 +261,11 @@
|
||||
options
|
||||
(if can-restore
|
||||
[(when can-restore
|
||||
{:name (tr "dashboard.restore-file")
|
||||
{:name (tr "dashboard.restore-file-button")
|
||||
:id "restore-file"
|
||||
:handler on-restore-immediately})
|
||||
(when can-restore
|
||||
{:name (tr "dashboard.delete-file")
|
||||
{:name (tr "dashboard.delete-file-button")
|
||||
:id "delete-file"
|
||||
:handler on-delete-immediately})]
|
||||
(if multi?
|
||||
|
||||
@@ -240,10 +240,13 @@
|
||||
|
||||
;; --- Grid Item
|
||||
|
||||
(mf/defc grid-item-metadata
|
||||
[{:keys [modified-at]}]
|
||||
(let [time (ct/timeago modified-at)]
|
||||
[:span {:class (stl/css :date)} time]))
|
||||
(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]))
|
||||
|
||||
(defn create-counter-element
|
||||
[_element file-count]
|
||||
@@ -429,7 +432,7 @@
|
||||
:on-end edit
|
||||
:max-length 250}]
|
||||
[: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
|
||||
|
||||
@@ -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,40 +316,34 @@
|
||||
{::mf/props :obj}
|
||||
[{: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*)
|
||||
|
||||
is-my-penpot (= (:default-team-id profile) team-id)
|
||||
is-defalt-team? (:is-default team)
|
||||
|
||||
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"}))))
|
||||
|
||||
on-deleted-click
|
||||
(mf/use-fn
|
||||
(mf/deps team-id)
|
||||
(fn []
|
||||
(st/emit! (dcm/go-to-dashboard-deleted :team-id team-id))))]
|
||||
::ev/origin "dashboard"}))))]
|
||||
|
||||
(mf/with-effect [show-team-hero?]
|
||||
(swap! storage/global assoc ::show-team-hero show-team-hero?))
|
||||
@@ -383,15 +377,9 @@
|
||||
(not is-defalt-team?)
|
||||
show-team-hero?
|
||||
can-invite))}
|
||||
[: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")]]
|
||||
|
||||
[:> deleted/menu* {:team-id team-id :section :dashboard-recent}]
|
||||
|
||||
(for [{:keys [id] :as project} projects]
|
||||
;; FIXME: refactor this, looks inneficient
|
||||
(let [files (when recent-map
|
||||
|
||||
@@ -248,26 +248,3 @@
|
||||
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.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]
|
||||
@@ -206,13 +205,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]}
|
||||
[{:keys [operation] :or {operation :export}}]
|
||||
(let [state (mf/deref (case operation
|
||||
:export refs/export
|
||||
:restore refs/restore
|
||||
refs/export))
|
||||
[]
|
||||
(let [state (mf/deref refs/export)
|
||||
profile (mf/deref refs/profile)
|
||||
theme (or (:theme profile) theme/default)
|
||||
is-default-theme? (= theme/default theme)
|
||||
@@ -221,10 +220,7 @@
|
||||
detail-visible? (:detail-visible state)
|
||||
widget-visible? (:widget-visible state)
|
||||
progress (:progress state)
|
||||
items (case operation
|
||||
:export (:exports state)
|
||||
:restore (:files state)
|
||||
[])
|
||||
items (:exports state)
|
||||
total (or (:total state) (count items))
|
||||
complete? (= progress total)
|
||||
circ (* 2 Math/PI 12)
|
||||
@@ -250,43 +246,23 @@
|
||||
|
||||
title
|
||||
(cond
|
||||
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")))
|
||||
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"))
|
||||
|
||||
retry-last-operation
|
||||
(mf/use-fn
|
||||
(mf/deps operation)
|
||||
(fn []
|
||||
(case operation
|
||||
:export (st/emit! (de/retry-last-export))
|
||||
:restore (st/emit! (dd/retry-last-restore))
|
||||
nil)))
|
||||
(st/emit! (de/retry-last-export))))
|
||||
|
||||
toggle-detail-visibility
|
||||
(mf/use-fn
|
||||
(mf/deps operation)
|
||||
(fn []
|
||||
(case operation
|
||||
:export (st/emit! (de/toggle-detail-visibililty))
|
||||
:restore (st/emit! (dd/toggle-restore-detail-visibility))
|
||||
nil)))]
|
||||
(st/emit! (de/toggle-detail-visibililty))))]
|
||||
|
||||
[:*
|
||||
(when (and widget-visible? (= operation :export))
|
||||
(when widget-visible?
|
||||
[:div {:class (stl/css :export-progress-widget)
|
||||
:on-click toggle-detail-visibility}
|
||||
[:svg {:width "24" :height "24"}
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
(open-share-dialog)))
|
||||
|
||||
[:div {:class (stl/css :options-zone)}
|
||||
[:& progress-widget {:operation :export}]
|
||||
[:& progress-widget]
|
||||
|
||||
(case section
|
||||
:interactions [:*
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
[:div {:class (stl/css :users-section)}
|
||||
[:& active-sessions]]
|
||||
|
||||
[:& progress-widget {:operation :export}]
|
||||
[:& progress-widget]
|
||||
|
||||
[:div {:class (stl/css :separator)}]
|
||||
|
||||
|
||||
@@ -46,6 +46,10 @@
|
||||
[event]
|
||||
(= "end" (get-type event)))
|
||||
|
||||
(defn progress?
|
||||
[event]
|
||||
(= "progress" (get-type event)))
|
||||
|
||||
(defn event?
|
||||
[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"
|
||||
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"
|
||||
|
||||
msgid "dashboard.labels.deleted"
|
||||
msgid "labels.deleted"
|
||||
msgstr "Deleted"
|
||||
|
||||
msgid "dashboard.deleted.restore-all"
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Restore All"
|
||||
|
||||
msgid "dashboard.deleted.clear"
|
||||
msgid "dashboard.clear-trash-button"
|
||||
msgstr "Clear trash"
|
||||
|
||||
msgid "dashboard.restore-file"
|
||||
msgid "dashboard.restore-file-button"
|
||||
msgstr "Restore file"
|
||||
|
||||
msgid "dashboard.delete-file"
|
||||
msgid "dashboard.delete-file-button"
|
||||
msgstr "Delete file"
|
||||
|
||||
msgid "dashboard.deleted.restore-project"
|
||||
msgid "dashboard.restore-project-button"
|
||||
msgstr "Restore project"
|
||||
|
||||
msgid "dashboard.deleted.delete-project"
|
||||
msgid "dashboard.delete-project-button"
|
||||
msgstr "Delete project"
|
||||
|
||||
msgid "dashboard.deleted.info-text"
|
||||
msgid "dashboard.trash-info-text-part1"
|
||||
msgstr "Deleted files will remain in the trash for"
|
||||
|
||||
msgid "dashboard.deleted.info-days"
|
||||
msgid "dashboard.trash-info-text-part2"
|
||||
msgstr " %s days. "
|
||||
|
||||
msgid "dashboard.deleted.info-text2"
|
||||
msgid "dashboard.trash-info-text-part3"
|
||||
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."
|
||||
|
||||
msgid "dashboard.deleted.delete-forever"
|
||||
msgstr "Delete forever"
|
||||
|
||||
msgid "restore-modal.restore-all.title"
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
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."
|
||||
|
||||
msgid "restore-modal.restore-file.title"
|
||||
msgid "dashboard-restore-file-confirmation.title"
|
||||
msgstr "Restore file"
|
||||
|
||||
msgid "restore-modal.restore-file.description"
|
||||
msgid "dashboard-restore-file-confirmation.description"
|
||||
msgstr "You're going to restore %s."
|
||||
|
||||
msgid "restore-modal.restore-project.title"
|
||||
msgid "dashboard.restore-project-confirmation.title"
|
||||
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."
|
||||
|
||||
msgid "delete-forever-modal.title"
|
||||
msgid "dashboard.delete-forever-confirmation.title"
|
||||
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."
|
||||
|
||||
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."
|
||||
|
||||
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.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 "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."
|
||||
|
||||
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."
|
||||
|
||||
msgid "restore-modal.error-restore-files"
|
||||
msgid "dashboard.errors.error-on-restore-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."
|
||||
|
||||
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."
|
||||
|
||||
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…"
|
||||
|
||||
msgid "restore-modal.failed-progress-label"
|
||||
msgstr "Restore failed"
|
||||
msgid "dashboard.progress-notification.deleting-files"
|
||||
msgstr "Deleting files…"
|
||||
|
||||
msgid "restore-modal.slow-progress-label"
|
||||
msgid "dashboard.progress-notification.slow-restore"
|
||||
msgstr "Restore 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"
|
||||
msgid "dashboard.progress-notification.slow-delete"
|
||||
msgstr "Deletion unexpectedly slow"
|
||||
|
||||
@@ -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 "dashboard.labels.recent"
|
||||
msgid "labels.recent"
|
||||
msgstr "Recientes"
|
||||
|
||||
msgid "dashboard.labels.deleted"
|
||||
msgid "labels.deleted"
|
||||
msgstr "Eliminados"
|
||||
|
||||
msgid "dashboard.deleted.restore-all"
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Restaurar todo"
|
||||
|
||||
msgid "dashboard.deleted.clear"
|
||||
msgid "dashboard.clear-trash-button"
|
||||
msgstr "Vaciar papelera"
|
||||
|
||||
msgid "dashboard.restore-file"
|
||||
msgid "dashboard.restore-file-button"
|
||||
msgstr "Restaurar archivo"
|
||||
|
||||
msgid "dashboard.delete-file"
|
||||
msgid "dashboard.delete-file-button"
|
||||
msgstr "Eliminar archivo"
|
||||
|
||||
msgid "dashboard.deleted.restore-project"
|
||||
msgid "dashboard.restore-project-button"
|
||||
msgstr "Restaurar proyecto"
|
||||
|
||||
msgid "dashboard.deleted.delete-project"
|
||||
msgid "dashboard.delete-project-button"
|
||||
msgstr "Eliminar proyecto"
|
||||
|
||||
msgid "dashboard.deleted.info-text"
|
||||
msgid "dashboard.trash-info-text-part1"
|
||||
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. "
|
||||
|
||||
msgid "dashboard.deleted.info-text2"
|
||||
msgid "dashboard.trash-info-text-part3"
|
||||
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."
|
||||
|
||||
msgid "dashboard.deleted.delete-forever"
|
||||
msgstr "Eliminar para siempre"
|
||||
|
||||
msgid "restore-modal.restore-all.title"
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
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."
|
||||
|
||||
msgid "restore-modal.restore-file.title"
|
||||
msgid "dashboard-restore-file-confirmation.title"
|
||||
msgstr "Restaurar archivo"
|
||||
|
||||
msgid "restore-modal.restore-file.description"
|
||||
msgid "dashboard-restore-file-confirmation.description"
|
||||
msgstr "Vas a restaurar %s."
|
||||
|
||||
msgid "restore-modal.restore-project.title"
|
||||
msgid "dashboard.restore-project-confirmation.title"
|
||||
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."
|
||||
|
||||
msgid "delete-forever-modal.title"
|
||||
msgid "dashboard.delete-forever-confirmation.title"
|
||||
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."
|
||||
|
||||
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."
|
||||
|
||||
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."
|
||||
|
||||
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."
|
||||
|
||||
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."
|
||||
|
||||
msgid "restore-modal.error-restore-files"
|
||||
msgid "dashboard.errors.error-on-restore-files"
|
||||
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."
|
||||
|
||||
msgid "restore-modal.error-restore-project"
|
||||
msgstr "Hubo un error al restaurar el proyecto %s y sus archivos."
|
||||
msgid "dashboard.errors.error-on-restoring-files"
|
||||
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…"
|
||||
|
||||
msgid "restore-modal.failed-progress-label"
|
||||
msgstr "Falló la restauración"
|
||||
msgid "dashboard.progress-notification.deleting-files"
|
||||
msgstr "Eliminando archivos…"
|
||||
|
||||
msgid "restore-modal.slow-progress-label"
|
||||
msgstr "Restauración lenta"
|
||||
msgid "dashboard.progress-notification.slow-restore"
|
||||
msgstr "Restauració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"
|
||||
msgid "dashboard.progress-notification.slow-delete"
|
||||
msgstr "Eliminación inesperadamente lenta"
|
||||
|
||||
Reference in New Issue
Block a user