From b6487015b82d70736111a7696a582c9440a48777 Mon Sep 17 00:00:00 2001 From: moorsecopers99 <46223049+moorsecopers99@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:12:48 +0300 Subject: [PATCH] :sparkles: Add loader feedback while importing and exporting files (#9024) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: Add loader feedback while importing and exporting files Show a loader icon with a status label ("Importing files…" / "Exporting files…") in the import and export dialog footers while the operation is running, so users get clear in-progress feedback and cannot retrigger the action by mistake. Closes #9020 Signed-off-by: moorsecopers99 * :sparkles: Address import/export loader feedback PR review - Show the loader beside file names in the import dialog while files are being imported (previously queued entries kept showing the Penpot logo until each one moved into :import-progress). - Drop the loader from the "Importing files…" / "Exporting files…" footer status, leaving just the text styled with the modal title color, per the design proposal. Signed-off-by: moorsecopers99 * :sparkles: Match design proposal for import/export progress feedback - Move the in-progress label from the modal footer into the modal body, under the file rows, styled italic with the modal title color. - Rename the labels to match the design wording: "Uploading file…" for import and "Downloading file…" for export. - Restore the disabled "Accept" button in the import footer during the import-progress phase, mirroring the disabled "Close" button used by export. Signed-off-by: moorsecopers99 * :bug: Rename deprecated bodySmallTypography mixin to body-small-typography Signed-off-by: moorsecopers99 --------- Signed-off-by: moorsecopers99 Co-authored-by: Andrey Antukh --- CHANGES.md | 1 + .../src/app/main/ui/dashboard/import.cljs | 14 +++++++-- .../src/app/main/ui/dashboard/import.scss | 7 +++++ frontend/src/app/main/ui/exports/files.cljs | 29 ++++++++++++------- frontend/src/app/main/ui/exports/files.scss | 7 +++++ frontend/translations/en.po | 8 +++++ 6 files changed, 52 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f00188f3a1..58b0da4de5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ - Show alpha percentage next to library color values to distinguish colors that differ only in opacity (by @rockchris099) [Github #6328](https://github.com/penpot/penpot/issues/6328) - Add "Clear artboard guides" option to right-click context menu for frames (by @eureka0928) [Github #6987](https://github.com/penpot/penpot/issues/6987) +- Add loader feedback while importing and exporting files [Github #9020](https://github.com/penpot/penpot/issues/9020) - Allow duplicating color and typography styles (by @MkDev11) [Github #2912](https://github.com/penpot/penpot/issues/2912) - Add woff2 support on user uploaded fonts (by @Nivl) [Github #8248](https://github.com/penpot/penpot/pull/8248) - Import Tokens from linked library (by @dfelinto) [Github #8391](https://github.com/penpot/penpot/pull/8391) diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 1510af2455..6b4fe68678 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -195,13 +195,14 @@ {::mf/props :obj ::mf/memo true ::mf/private true} - [{:keys [entries entry edition can-be-deleted on-edit on-change on-delete]}] + [{:keys [entries entry edition can-be-deleted importing? on-edit on-change on-delete]}] (let [status (:status entry) ;; FIXME: rename to format format (:type entry) loading? (or (= :analyze status) - (= :import-progress status)) + (= :import-progress status) + (and importing? (= :import-ready status))) analyze-error? (= :analyze-error status) import-success? (= :import-success status) import-error? (= :import-error status) @@ -498,6 +499,7 @@ :key (dm/str (:uri entry) "/" (:file-id entry)) :entry entry :entries entries + :importing? (= :import-progress status) :on-edit on-edit :on-change on-entry-change :on-delete on-entry-delete @@ -505,7 +507,13 @@ (when (some? template) [:> import-entry* {:entry (assoc template :status status) - :can-be-deleted false}])] + :can-be-deleted false}]) + + (when (= :import-progress status) + [:div {:class (stl/css :status-message) + :role "status" + :aria-live "polite"} + (tr "labels.uploading-file")])] [:div {:class (stl/css :modal-footer)} [:div {:class (stl/css :action-buttons)} diff --git a/frontend/src/app/main/ui/dashboard/import.scss b/frontend/src/app/main/ui/dashboard/import.scss index b428e4b0d7..7d8d0ff428 100644 --- a/frontend/src/app/main/ui/dashboard/import.scss +++ b/frontend/src/app/main/ui/dashboard/import.scss @@ -43,6 +43,13 @@ min-height: 40px; } +.status-message { + @include deprecated.body-small-typography; + + color: var(--modal-title-foreground-color); + font-style: italic; +} + .action-buttons { @extend %modal-action-btns; } diff --git a/frontend/src/app/main/ui/exports/files.cljs b/frontend/src/app/main/ui/exports/files.cljs index 60f19737e5..ec937809ec 100644 --- a/frontend/src/app/main/ui/exports/files.cljs +++ b/frontend/src/app/main/ui/exports/files.cljs @@ -174,15 +174,22 @@ :on-click on-accept}]]]] (= status :exporting) - [:* - [:div {:class (stl/css :modal-content)} - (for [file (:files state)] - [:> export-entry* {:file file :key (dm/str (:id file))}])] + (let [in-progress? (->> state :files (some :loading))] + [:* + [:div {:class (stl/css :modal-content)} + (for [file (:files state)] + [:> export-entry* {:file file :key (dm/str (:id file))}]) - [:div {:class (stl/css :modal-footer)} - [:div {:class (stl/css :action-buttons)} - [:input {:class (stl/css :accept-btn) - :type "button" - :value (tr "labels.close") - :disabled (->> state :files (some :loading)) - :on-click on-cancel}]]]])]])) + (when in-progress? + [:div {:class (stl/css :status-message) + :role "status" + :aria-live "polite"} + (tr "labels.downloading-file")])] + + [:div {:class (stl/css :modal-footer)} + [:div {:class (stl/css :action-buttons)} + [:input {:class (stl/css :accept-btn) + :type "button" + :value (tr "labels.close") + :disabled in-progress? + :on-click on-cancel}]]]]))]])) diff --git a/frontend/src/app/main/ui/exports/files.scss b/frontend/src/app/main/ui/exports/files.scss index c3c09b3fa4..e395c9c509 100644 --- a/frontend/src/app/main/ui/exports/files.scss +++ b/frontend/src/app/main/ui/exports/files.scss @@ -183,6 +183,13 @@ } } +.status-message { + @include deprecated.body-small-typography; + + color: var(--modal-title-foreground-color); + font-style: italic; +} + .action-buttons { @extend %modal-action-btns; } diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 577b480347..54a33820ec 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2804,6 +2804,14 @@ msgstr "Libraries & Templates" msgid "labels.loading" msgstr "Loading…" +#: src/app/main/ui/dashboard/import.cljs +msgid "labels.uploading-file" +msgstr "Uploading file…" + +#: src/app/main/ui/exports/files.cljs +msgid "labels.downloading-file" +msgstr "Downloading file…" + #: src/app/main/ui/workspace/sidebar/versions.cljs:210 msgid "labels.lock" msgstr "Lock"