From 3ae7c42afa13dba1b388cac377d47cef2a327164 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 18 Mar 2022 14:00:10 +0100 Subject: [PATCH] :sparkles: Exporting big files flow --- CHANGES.md | 3 +- exporter/src/app/handlers/export_frames.cljs | 3 + .../resources/styles/main/partials/modal.scss | 8 +- frontend/src/app/main/data/exports.cljs | 39 +++++++-- frontend/src/app/main/repo.cljs | 18 +--- frontend/src/app/main/ui/export.cljs | 82 ++++++++++++------- .../src/app/main/ui/workspace/header.cljs | 20 +---- frontend/translations/en.po | 16 ++-- frontend/translations/es.po | 16 ++-- 9 files changed, 118 insertions(+), 87 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c451a5b0c..c18046681 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,8 @@ ### :boom: Breaking changes ### :sparkles: New features -- Multiexport from main menu [Taiga #520](https://tree.taiga.io/project/penpot/us/2854) +- Exporting big files flow [Taiga #2218](https://tree.taiga.io/project/penpot/us/2218) +- Multiexport from main menu [Taiga #520](https://tree.taiga.io/project/penpot/us/28541) - Multipexport assets (aka bulk export) [Taiga #520](https://tree.taiga.io/project/penpot/us/520) - Set the artboard layer fixed at the top side of the layers [Taiga #2636](https://tree.taiga.io/project/penpot/us/2636) - Set an artboard as the file thumbnail [Taiga #1526](https://tree.taiga.io/project/penpot/us/1526) diff --git a/exporter/src/app/handlers/export_frames.cljs b/exporter/src/app/handlers/export_frames.cljs index 63440c3ac..9b1b6d0b2 100644 --- a/exporter/src/app/handlers/export_frames.cljs +++ b/exporter/src/app/handlers/export_frames.cljs @@ -55,6 +55,7 @@ on-progress (fn [progress] (let [data {:type :export-update :resource-id (:id resource) + :name (:name resource) :status "running" :progress progress}] (redis/pub! topic data))) @@ -62,6 +63,7 @@ on-complete (fn [resource] (let [data {:type :export-update :resource-id (:id resource) + :name (:name resource) :size (:size resource) :status "ended"}] (redis/pub! topic data))) @@ -69,6 +71,7 @@ on-error (fn [cause] (let [data {:type :export-update :resource-id (:id resource) + :name (:name resource) :status "error" :cause (ex-message cause)}] (redis/pub! topic data))) diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index 222dea85c..5510537fd 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -235,13 +235,13 @@ .import-dialog, .export-dialog, -.export-shapes-dialog { +.export-multiple-dialog { background-color: $color-white; border: 1px solid $color-gray-20; width: 30rem; min-height: 14rem; - &.no-shapes { + &.empty { width: 39rem; } @@ -1338,7 +1338,7 @@ } } -// Export shapes +// Export multiple .export-progress-modal-overlay { display: flex; @@ -1413,7 +1413,7 @@ } } -.export-shapes-dialog { +.export-multiple-dialog { .modal-content { padding: 0; } diff --git a/frontend/src/app/main/data/exports.cljs b/frontend/src/app/main/data/exports.cljs index 380f098f2..4451cfd4d 100644 --- a/frontend/src/app/main/data/exports.cljs +++ b/frontend/src/app/main/data/exports.cljs @@ -6,6 +6,7 @@ (ns app.main.data.exports (:require + [app.common.data.macros :as dm] [app.main.data.modal :as modal] [app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.state-helpers :as wsh] @@ -92,8 +93,31 @@ (rx/of (modal/show :export-shapes {:exports (vec exports) :filename filename})))))) +(defn show-workspace-export-frames-dialog + ([frames] + (ptk/reify ::show-workspace-export-frames-dialog + ptk/WatchEvent + (watch [_ state _] + (let [file-id (:current-file-id state) + page-id (:current-page-id state) + filename (-> (wsh/lookup-page state page-id) + :name + (dm/str ".pdf")) + + exports (for [frame frames] + {:enabled true + :page-id page-id + :file-id file-id + :frame-id (:id frame) + :shape frame + :name (:name frame)})] + + (rx/of (modal/show :export-frames + {:exports (vec exports) + :filename filename}))))))) + (defn- initialize-export-status - [exports filename resource-id] + [exports filename resource-id query] (ptk/reify ::initialize-export-status ptk/UpdateEvent (update [_ state] @@ -106,7 +130,8 @@ :detail-visible true :exports exports :filename filename - :last-update (dt/now)})))) + :last-update (dt/now) + :query query})))) (defn- update-export-status [{:keys [progress status resource-id name] :as data}] @@ -154,7 +179,7 @@ (dissoc state :export)))))))))) (defn request-multiple-export - [{:keys [filename exports] :as params}] + [{:keys [filename exports query] :as params}] (ptk/reify ::request-multiple-export ptk/WatchEvent (watch [_ state _] @@ -187,11 +212,11 @@ ;; Launch the exportation process and stores the resource id ;; locally. - (->> (rp/query! :export-shapes-multiple params) + (->> (rp/query! query params) (rx/tap (fn [{:keys [id]}] (vreset! resource-id id))) (rx/map (fn [{:keys [id]}] - (initialize-export-status exports filename id)))) + (initialize-export-status exports filename id query)))) ;; We proceed to update the export state with incoming ;; progress updates. We delay the stoper for give some time @@ -220,6 +245,6 @@ (ptk/reify ::retry-last-export ptk/WatchEvent (watch [_ state _] - (let [{:keys [exports filename]} (:export state)] - (rx/of (request-multiple-export {:exports exports :filename filename})))))) + (let [{:keys [exports filename query]} (:export state)] + (rx/of (request-multiple-export {:exports exports :filename filename :query query})))))) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index fab5ef9b7..afbdde5f8 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -126,24 +126,14 @@ [_ params] (send-export-command :cmd :export-shapes :params params :blob? false)) +(defmethod query :export-frames + [_ params] + (send-export-command :cmd :export-frames :params (assoc params :uri (str base-uri)) :blob? false)) + (defmethod query :download-export-resource [_ id] (send-export-command :cmd :get-resource :params {:id id} :blob? true)) -(defmethod query :export-frames - [_ exports] - (let [params {:uri (str base-uri) - :cmd :export-frames - :wait false - :exports exports}] - (->> (http/send! {:method :post - :uri (u/join base-uri "api/export") - :body (http/transit-data params) - :credentials "include" - :response-type :blob}) - (rx/mapcat handle-response) - (rx/ignore)))) - (derive :upload-file-media-object ::multipart-upload) (derive :update-profile-photo ::multipart-upload) (derive :update-team-photo ::multipart-upload) diff --git a/frontend/src/app/main/ui/export.cljs b/frontend/src/app/main/ui/export.cljs index 14f284554..e872d28d1 100644 --- a/frontend/src/app/main/ui/export.cljs +++ b/frontend/src/app/main/ui/export.cljs @@ -21,10 +21,8 @@ [cuerdas.core :as str] [rumext.alpha :as mf])) -(mf/defc export-shapes-dialog - {::mf/register modal/components - ::mf/register-as :export-shapes} - [{:keys [exports filename]}] +(mf/defc export-multiple-dialog + [{:keys [exports filename title query no-selection]}] (let [lstate (mf/deref refs/export) in-progress? (:in-progress lstate) @@ -45,7 +43,8 @@ (fn [event] (dom/prevent-default event) (st/emit! (modal/hide) - (de/request-multiple-export {:filename filename :exports enabled-exports}))) + (de/request-multiple-export {:filename filename :exports enabled-exports :query query}))) + on-toggle-enabled (fn [index] (swap! exports update-in [index :enabled] not)) @@ -53,15 +52,14 @@ change-all (fn [_] (swap! exports (fn [exports] - (mapv #(assoc % :enabled (not all-checked?)) exports)))) - ] + (mapv #(assoc % :enabled (not all-checked?)) exports))))] [:div.modal-overlay - [:div.modal-container.export-shapes-dialog - {:class (when (empty? all-exports) "no-shapes")} + [:div.modal-container.export-multiple-dialog + {:class (when (empty? all-exports) "empty")} [:div.modal-header [:div.modal-header-title - [:h2 (tr "dashboard.export-shapes.title")]] + [:h2 title]] [:div.modal-close-button {:on-click cancel-fn} i/close]] @@ -76,7 +74,7 @@ all-checked? [:span i/checkbox-checked] all-unchecked? [:span i/checkbox-unchecked] :else [:span i/checkbox-intermediate])] - [:div.field.title (tr "dashboard.export-shapes.selected" + [:div.field.title (tr "dashboard.export-multiple.selected" (c (count enabled-exports)) (c (count all-exports)))]] @@ -90,22 +88,27 @@ [:span.unchecked i/checkbox-unchecked])] [:div.field.image - [:svg {:view-box (dm/str x " " y " " width " " height) - :width 24 - :height 20 - :version "1.1" - :xmlns "http://www.w3.org/2000/svg" - :xmlnsXlink "http://www.w3.org/1999/xlink" - ;; Fix Chromium bug about color of html texts - ;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5 - :style {:-webkit-print-color-adjust :exact}} + (if (some? (:thumbnail shape)) + [:img {:src (:thumbnail shape)}] + [:svg {:view-box (dm/str x " " y " " width " " height) + :width 24 + :height 20 + :version "1.1" + :xmlns "http://www.w3.org/2000/svg" + :xmlnsXlink "http://www.w3.org/1999/xlink" + ;; Fix Chromium bug about color of html texts + ;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5 + :style {:-webkit-print-color-adjust :exact}} - [:& shape-wrapper {:shape shape}]]] + [:& shape-wrapper {:shape shape}]])] [:div.field.name (cond-> (:name shape) suffix (str suffix))] - [:div.field.scale (dm/str (* width (:scale export)) "x" - (* height (:scale export)) "px ")] - [:div.field.extension (-> export :type d/name str/upper)]]))] + (when (:scale export) + [:div.field.scale (dm/str (* width (:scale export)) "x" + (* height (:scale export)) "px ")]) + + (when (:type export) + [:div.field.extension (-> export :type d/name str/upper)])]))] [:div.modal-footer [:div.action-buttons @@ -124,13 +127,30 @@ (tr "labels.export")) :on-click (when-not in-progress? accept-fn)}]]]] - [:div.no-selection - [:img {:src "images/export-no-shapes.png" :border "0"}] - [:p (tr "dashboard.export-shapes.no-elements")] - [:p (tr "dashboard.export-shapes.how-to")] - [:p [:a {:target "_blank" - :href "https://help.penpot.app/user-guide/exporting/ "} - (tr "dashboard.export-shapes.how-to-link")]]])]]]])) + [:& no-selection])]]]])) + +(mf/defc shapes-no-selection [] + [:div.no-selection + [:img {:src "images/export-no-shapes.png" :border "0"}] + [:p (tr "dashboard.export-shapes.no-elements")] + [:p (tr "dashboard.export-shapes.how-to")] + [:p [:a {:target "_blank" + :href "https://help.penpot.app/user-guide/exporting/ "} + (tr "dashboard.export-shapes.how-to-link")]]]) + +(mf/defc export-shapes-dialog + {::mf/register modal/components + ::mf/register-as :export-shapes} + [{:keys [exports filename]}] + (let [title (tr "dashboard.export-shapes.title")] + (export-multiple-dialog {:exports exports :filename filename :title title :query :export-shapes-multiple :no-selection shapes-no-selection}))) + +(mf/defc export-frames + {::mf/register modal/components + ::mf/register-as :export-frames} + [{:keys [exports filename]}] + (let [title (tr "dashboard.export-frames.title")] + (export-multiple-dialog {:exports exports :filename filename :title title :query :export-frames}))) (mf/defc export-progress-widget {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 221f1f2c0..b3c60a0a5 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -7,11 +7,9 @@ (ns app.main.ui.workspace.header (:require [app.common.data :as d] - [app.common.data.macros :as dm] [app.config :as cf] [app.main.data.events :as ev] [app.main.data.exports :as de] - [app.main.data.messages :as msg] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.shortcuts :as sc] @@ -105,7 +103,7 @@ ;; --- Header Users (mf/defc menu - [{:keys [layout project file team-id page-id] :as props}] + [{:keys [layout project file team-id] :as props}] (let [show-menu? (mf/use-state false) show-sub-menu? (mf/use-state false) editing? (mf/use-state false) @@ -188,21 +186,7 @@ (mf/use-callback (mf/deps file frames) (fn [_] - (when (seq frames) - (let [filename (dm/str (:name file) ".pdf") - xform (comp (map :id) - (map (fn [id] - {:file-id (:id file) - :page-id page-id - :frame-id id})))] - (st/emit! (msg/info (tr "workspace.options.exporting-object") {:timeout nil})) - (->> (rp/query! :export-frames (into [] xform frames)) - (rx/subs - (fn [body] - (dom/trigger-download filename body)) - (fn [_error] - (st/emit! (msg/error (tr "errors.unexpected-error")))) - (st/emitf msg/hide))))))) + (st/emit! (de/show-workspace-export-frames-dialog frames)))) on-item-hover (mf/use-callback diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a06a30223..bc1ff89f2 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -304,23 +304,27 @@ msgstr "Include shared library assets in file libraries" msgid "dashboard.export.title" msgstr "Export files" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +#:src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Export to PDF" + +#:src/app/main/ui/export.cljs msgid "dashboard.export-shapes.title" msgstr "Export selection" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs -msgid "dashboard.export-shapes.selected" +#:src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" msgstr "%s de %s elements selected" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +#:src/app/main/ui/export.cljs msgid "dashboard.export-shapes.no-elements" msgstr "There are no elements with export settings." -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +#:src/app/main/ui/export.cljs msgid "dashboard.export-shapes.how-to" msgstr "You can add export settings to elements from the design properties (at the bottom of the right sidebar)." -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +#:src/app/main/ui/export.cljs msgid "dashboard.export-shapes.how-to-link" msgstr "Info how to set exports at Penpot." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 05437697a..b37d3ee22 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -308,23 +308,27 @@ msgstr "Incluir librerias compartidas dentro de las librerias del fichero" msgid "dashboard.export.title" msgstr "Exportar ficheros" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +#:src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Exportar a PDF" + +#: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.title" msgstr "Exportar selección" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs -msgid "dashboard.export-shapes.selected" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" msgstr "%s de %s elementos seleccionados" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +#: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.no-elements" msgstr "No hay elementos con configuraciones de exportación." -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +#: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.how-to" msgstr " Puedes añadir configuraciones de exportación a elementos desde las propiedades de diseño (al final del lateral derecho)." -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +#: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.how-to-link" msgstr "Información sobre cómo configurar exportaciones en Penpot."