From 203473c9658e55be9252d947d3d096114b50101a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 30 Aug 2021 16:54:27 +0200 Subject: [PATCH] :tada: Export to PDF all artboards of one page --- CHANGES.md | 1 + docker/devenv/Dockerfile | 1 + docker/gitpod/Dockerfile | 1 + docker/images/Dockerfile.exporter | 1 + exporter/src/app/browser.cljs | 8 ++- exporter/src/app/http.cljs | 4 +- exporter/src/app/http/export_frames.cljs | 66 +++++++++++++++++++ exporter/src/app/renderer/pdf.cljs | 15 +++-- frontend/src/app/main/repo.cljs | 8 +++ .../src/app/main/ui/workspace/header.cljs | 34 ++++++++-- frontend/translations/en.po | 3 + frontend/translations/es.po | 9 +++ 12 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 exporter/src/app/http/export_frames.cljs diff --git a/CHANGES.md b/CHANGES.md index f5ba977c7..25e8a18d5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Allow to zoom with ctrl + middle button [Taiga #1428](https://tree.taiga.io/project/penpot/us/1428). - Auto placement of duplicated objects [Taiga #1386](https://tree.taiga.io/project/penpot/us/1386). - Enable penpot SVG metadata only when exporting complete files [Taiga #1914](https://tree.taiga.io/project/penpot/us/1914?milestone=295883). +- Export to PDF all artboards of one page [Taiga #1895](https://tree.taiga.io/project/penpot/us/1895). - Go to a undo step clicking on a history element of the list [Taiga #1374](https://tree.taiga.io/project/penpot/us/1374). - Increment font size by 10 with shift+arrows [1047](https://github.com/penpot/penpot/issues/1047). - New shortcut to detach components Ctrl+Shift+K [Taiga #1799](https://tree.taiga.io/project/penpot/us/1799). diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index 4db03f4f3..bdfbd3341 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -44,6 +44,7 @@ RUN set -ex; \ python \ build-essential \ imagemagick \ + ghostscript \ netpbm \ potrace \ webp \ diff --git a/docker/gitpod/Dockerfile b/docker/gitpod/Dockerfile index 7db973a46..00d906976 100644 --- a/docker/gitpod/Dockerfile +++ b/docker/gitpod/Dockerfile @@ -9,6 +9,7 @@ FROM gitpod/workspace-postgres RUN set -ex; \ brew install redis; \ brew install imagemagick; \ + brew install ghostscript; \ brew install mailhog; \ brew install openldap; \ sudo mkdir -p /var/log/nginx; \ diff --git a/docker/images/Dockerfile.exporter b/docker/images/Dockerfile.exporter index f8636c295..6e5955aee 100644 --- a/docker/images/Dockerfile.exporter +++ b/docker/images/Dockerfile.exporter @@ -20,6 +20,7 @@ RUN set -ex; \ apt-get -qq update; \ apt-get -qqy install \ imagemagick \ + ghostscript \ netpbm \ potrace \ gconf-service \ diff --git a/exporter/src/app/browser.cljs b/exporter/src/app/browser.cljs index d161d1629..35f1b9a0b 100644 --- a/exporter/src/app/browser.cljs +++ b/exporter/src/app/browser.cljs @@ -69,12 +69,14 @@ (defn pdf ([page] (pdf page nil)) - ([page {:keys [viewport omit-background? prefer-css-page-size?] + ([page {:keys [viewport omit-background? prefer-css-page-size? save-path] :or {viewport {} omit-background? true - prefer-css-page-size? true}}] + prefer-css-page-size? true + save-path nil}}] (let [viewport (d/merge default-viewport viewport)] - (.pdf ^js page #js {:width (:width viewport) + (.pdf ^js page #js {:path save-path + :width (:width viewport) :height (:height viewport) :scale (:scale viewport) :omitBackground omit-background? diff --git a/exporter/src/app/http.cljs b/exporter/src/app/http.cljs index 4dca3cac4..c8d3d4168 100644 --- a/exporter/src/app/http.cljs +++ b/exporter/src/app/http.cljs @@ -8,13 +8,15 @@ (:require [app.config :as cf] [app.http.export :refer [export-handler]] + [app.http.export-frames :refer [export-frames-handler]] [app.http.impl :as impl] [lambdaisland.glogi :as log] [promesa.core :as p] [reitit.core :as r])) (def routes - [["/export" {:handler export-handler}]]) + [["/export-frames" {:handler export-frames-handler}] + ["/export" {:handler export-handler}]]) (def instance (atom nil)) diff --git a/exporter/src/app/http/export_frames.cljs b/exporter/src/app/http/export_frames.cljs new file mode 100644 index 000000000..1710eaa5d --- /dev/null +++ b/exporter/src/app/http/export_frames.cljs @@ -0,0 +1,66 @@ +;; 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) UXBOX Labs SL + +(ns app.http.export-frames + (:require + ["path" :as path] + [app.common.exceptions :as exc :include-macros true] + [app.common.spec :as us] + [app.renderer.pdf :as rp] + [app.util.shell :as sh] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [promesa.core :as p])) + +(s/def ::name ::us/string) +(s/def ::file-id ::us/uuid) +(s/def ::page-id ::us/uuid) +(s/def ::frame-id ::us/uuid) +(s/def ::frame-ids (s/coll-of ::frame-id :kind vector?)) + +(s/def ::handler-params + (s/keys :req-un [::file-id ::page-id ::frame-ids])) + +(defn- export-frame + [tdpath file-id page-id token frame-id] + (p/let [spath (path/join tdpath (str frame-id ".pdf")) + result (rp/render {:name (str frame-id) + :suffix "" + :token token + :file-id file-id + :page-id page-id + :object-id frame-id + :scale 1 + :save-path spath})] + spath)) + +(defn- join-files + [tdpath file-id paths] + (let [output-path (path/join tdpath (str file-id ".pdf")) + paths-str (str/join " " paths)] + (-> (sh/run-cmd! (str "gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile='" output-path "' " paths-str)) + (p/then (constantly output-path))))) + +(defn- clean-tmp-data + [tdpath data] + (p/do! + (sh/rmdir! tdpath) + data)) + +(defn export-frames-handler + [{:keys [params cookies] :as request}] + (let [{:keys [name file-id page-id frame-ids]} (us/conform ::handler-params params) + token (.get ^js cookies "auth-token")] + (p/let [tdpath (sh/create-tmpdir! "pdfexport-") + data (-> (p/all (map (partial export-frame tdpath file-id page-id token) frame-ids)) + (p/then (partial join-files tdpath file-id)) + (p/then sh/read-file) + (p/then (partial clean-tmp-data tdpath)))] + {:status 200 + :body data + :headers {"content-type" "application/pdf" + "content-length" (.-length data)}}))) + diff --git a/exporter/src/app/renderer/pdf.cljs b/exporter/src/app/renderer/pdf.cljs index d57729b86..7f88bc238 100644 --- a/exporter/src/app/renderer/pdf.cljs +++ b/exporter/src/app/renderer/pdf.cljs @@ -26,7 +26,7 @@ :value token})) (defn pdf-from-object - [{:keys [file-id page-id object-id token scale type]}] + [{:keys [file-id page-id object-id token scale type save-path]}] (letfn [(handle [page] (let [path (str "/render-object/" file-id "/" page-id "/" object-id) uri (-> (u/uri (cf/get :public-uri)) @@ -39,10 +39,12 @@ (log/info :uri uri) (let [options {:cookie cookie}] (p/do! - (bw/configure-page! page options) - (bw/navigate! page uri) - (bw/wait-for page "#screenshot") - (bw/pdf page))))] + (bw/configure-page! page options) + (bw/navigate! page uri) + (bw/wait-for page "#screenshot") + (if save-path + (bw/pdf page {:save-path save-path}) + (bw/pdf page)))))] (bw/exec! handle))) @@ -54,10 +56,11 @@ (s/def ::scale ::us/number) (s/def ::token ::us/string) (s/def ::filename ::us/string) +(s/def ::save-path ::us/string) (s/def ::render-params (s/keys :req-un [::name ::suffix ::object-id ::page-id ::scale ::token ::file-id] - :opt-un [::filename])) + :opt-un [::filename ::save-path])) (defn render [params] diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index e9df95ee6..7ec498c40 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -107,6 +107,14 @@ :response-type :blob}) (rx/mapcat handle-response))) +(defmethod query :export-frames + [_ params] + (->> (http/send! {:method :post + :uri (u/join base-uri "export-frames") + :body (http/transit-data params) + :response-type :blob}) + (rx/mapcat handle-response))) + (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/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 333424485..b650f94af 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.math :as mth] [app.config :as cf] + [app.main.data.messages :as dm] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.shortcuts :as sc] @@ -92,10 +93,12 @@ ;; --- Header Users (mf/defc menu - [{:keys [layout project file team-id] :as props}] + [{:keys [layout project file team-id page-id] :as props}] (let [show-menu? (mf/use-state false) editing? (mf/use-state false) + frames (mf/deref refs/workspace-frames) + edit-input-ref (mf/use-ref nil) add-shared-fn @@ -142,7 +145,7 @@ (dom/prevent-default event) (reset! editing? true)) - on-export-files + on-export-file (mf/use-callback (mf/deps file team-id) (fn [_] @@ -159,7 +162,24 @@ {:type :export :team-id team-id :has-libraries? (->> files (some :has-libraries?)) - :files files})))))))] + :files files}))))))) + + on-export-frames + (mf/use-callback + (mf/deps file) + (fn [_] + (let [filename (str (:name file) ".pdf") + frame-ids (mapv :id frames)] + (->> (rp/query! :export-frames + {:name (:name file) + :file-id (:id file) + :page-id page-id + :frame-ids frame-ids}) + (rx/subs + (fn [body] + (dom/trigger-download filename body)) + (fn [_error] + (st/emit! (dm/error (tr "errors.unexpected-error")))))))))] (mf/use-effect (mf/deps @editing?) @@ -256,9 +276,12 @@ [:li {:on-click on-add-shared} [:span (tr "dashboard.add-shared")]]) - [:li.export-file {:on-click on-export-files} + [:li.export-file {:on-click on-export-file} [:span (tr "dashboard.export-single")]] + [:li.export-file {:on-click on-export-frames} + [:span (tr "dashboard.export-frames")]] + (when (contains? @cf/flags :user-feedback) [:li.feedback {:on-click (st/emitf (rt/nav :settings-feedback))} [:span (tr "labels.give-feedback")] @@ -290,7 +313,8 @@ [:& menu {:layout layout :project project :file file - :team-id team-id}] + :team-id team-id + :page-id page-id}] [:div.users-section [:& active-sessions]] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 1bb119fff..f319af2ee 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -212,6 +212,9 @@ msgstr "Export %s files" msgid "dashboard.export-single" msgstr "Export file" +msgid "dashboard.export-frames" +msgstr "Export artboards to PDF..." + msgid "dashboard.export.detail" msgstr "* Might include components, graphics, colors and/or typographies." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index e853dc615..bf1880d63 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -210,6 +210,15 @@ msgstr "Duplicar %s archivos" msgid "dashboard.empty-files" msgstr "Todavía no hay ningún archivo aquí" +msgid "dashboard.export-multi" +msgstr "Exportar %s archivos" + +msgid "dashboard.export-single" +msgstr "Exportar archivo" + +msgid "dashboard.export-frames" +msgstr "Exportar tableros a PDF..." + msgid "dashboard.export.detail" msgstr "* Pueden incluir components, gráficos, colores y/o tipografias."