mirror of
https://github.com/penpot/penpot.git
synced 2025-07-17 11:17:26 +02:00
🎉 Add stacked exports.
This commit is contained in:
parent
a8d5cdc29f
commit
2fb4e72240
14 changed files with 549 additions and 208 deletions
|
@ -15,11 +15,13 @@
|
|||
(f page)))))
|
||||
|
||||
(defn emulate!
|
||||
[page {:keys [viewport user-agent]
|
||||
:or {user-agent USER-AGENT}}]
|
||||
[page {:keys [viewport user-agent scale]
|
||||
:or {user-agent USER-AGENT
|
||||
scale 1}}]
|
||||
(let [[width height] viewport]
|
||||
(.emulate page #js {:viewport #js {:width width
|
||||
:height height}
|
||||
:height height
|
||||
:deviceScaleFactor scale}
|
||||
:userAgent user-agent})))
|
||||
|
||||
(defn navigate!
|
||||
|
@ -33,10 +35,20 @@
|
|||
(.waitFor ^js page ms))
|
||||
|
||||
(defn screenshot
|
||||
([page] (screenshot page nil))
|
||||
([page {:keys [full-page?]
|
||||
:or {full-page? true}}]
|
||||
(.screenshot ^js page #js {:fullPage full-page? :omitBackground true})))
|
||||
([frame] (screenshot frame nil))
|
||||
([frame {:keys [full-page? omit-background?]
|
||||
:or {full-page? false
|
||||
omit-background? false}}]
|
||||
(.screenshot ^js frame #js {:fullPage full-page?
|
||||
:omitBackground omit-background?})))
|
||||
|
||||
(defn eval!
|
||||
[frame f]
|
||||
(.evaluate ^js frame f))
|
||||
|
||||
(defn select
|
||||
[frame selector]
|
||||
(.$ ^js frame selector))
|
||||
|
||||
(defn set-cookie!
|
||||
[page {:keys [key value domain]}]
|
||||
|
@ -47,16 +59,15 @@
|
|||
(defn start!
|
||||
([] (start! nil))
|
||||
([{:keys [concurrency concurrency-strategy]
|
||||
:or {concurrency 2
|
||||
concurrency-strategy :browser}}]
|
||||
:or {concurrency 10
|
||||
concurrency-strategy :incognito}}]
|
||||
(let [ccst (case concurrency-strategy
|
||||
:browser (.-CONCURRENCY_BROWSER ^js ppc/Cluster)
|
||||
:incognito (.-CONCURRENCY_CONTEXT ^js ppc/Cluster)
|
||||
:page (.-CONCURRENCY_PAGE ^js ppc/Cluster))
|
||||
opts #js {:concurrency ccst
|
||||
:maxConcurrency concurrency
|
||||
:puppeteerOptions #js {:args #js ["--no-sandbox"
|
||||
"--explicitly-allowed-ports=6000"]}}]
|
||||
:puppeteerOptions #js {:args #js ["--no-sandbox"]}}]
|
||||
(.launch ^js ppc/Cluster opts))))
|
||||
|
||||
(defn stop!
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
[promesa.core :as p]
|
||||
[lambdaisland.glogi :as log]
|
||||
[app.browser :as bwr]
|
||||
[app.http.screenshot :refer [bitmap-handler
|
||||
page-handler]]
|
||||
[app.http.bitmap-export :refer [bitmap-export-handler]]
|
||||
[app.util.transit :as t]
|
||||
[reitit.core :as r]
|
||||
[cuerdas.core :as str]
|
||||
["koa" :as koa]
|
||||
["http" :as http])
|
||||
["http" :as http]
|
||||
["inflation" :as inflate]
|
||||
["raw-body" :as raw-body])
|
||||
(:import
|
||||
goog.Uri))
|
||||
|
||||
|
@ -26,11 +27,7 @@
|
|||
[router ctx]
|
||||
(let [uri (.parse Uri (unchecked-get ctx "originalUrl"))]
|
||||
(when-let [match (r/match-by-path router (.getPath uri))]
|
||||
(let [qparams (query-params uri)
|
||||
params {:path (:path-params match) :query qparams}]
|
||||
(assoc match
|
||||
:params params
|
||||
:query-params qparams)))))
|
||||
(assoc match :query-params (query-params uri)))))
|
||||
|
||||
(defn- handle-error
|
||||
[error request]
|
||||
|
@ -72,15 +69,33 @@
|
|||
(transient {})
|
||||
(js/Object.keys orig)))))
|
||||
|
||||
(def parse-body?
|
||||
#{"POST" "PUT" "DELETE"})
|
||||
|
||||
(defn- parse-body
|
||||
[ctx]
|
||||
(let [headers (unchecked-get ctx "headers")
|
||||
ctype (unchecked-get headers "content-type")]
|
||||
(when (parse-body? (.-method ^js ctx))
|
||||
(-> (inflate (.-req ^js ctx))
|
||||
(raw-body #js {:limit "5mb" :encoding "utf8"})
|
||||
(p/then (fn [data]
|
||||
(cond-> data
|
||||
(= ctype "application/transit+json")
|
||||
(t/decode))))))))
|
||||
|
||||
(defn- wrap-handler
|
||||
[f extra]
|
||||
(fn [ctx]
|
||||
(let [cookies (unchecked-get ctx "cookies")
|
||||
headers (parse-headers ctx)
|
||||
request (assoc extra
|
||||
:ctx ctx
|
||||
:headers headers
|
||||
:cookies cookies)]
|
||||
(p/let [cookies (unchecked-get ctx "cookies")
|
||||
headers (parse-headers ctx)
|
||||
body (parse-body ctx)
|
||||
request (assoc extra
|
||||
:method (str/lower (unchecked-get ctx "method"))
|
||||
:body body
|
||||
:ctx ctx
|
||||
:headers headers
|
||||
:cookies cookies)]
|
||||
(-> (p/do! (f request))
|
||||
(p/then (fn [rsp]
|
||||
(when (map? rsp)
|
||||
|
@ -91,16 +106,20 @@
|
|||
|
||||
(def routes
|
||||
[["/export"
|
||||
["/bitmap" {:handler bitmap-handler}]
|
||||
["/page" {:handler page-handler}]]])
|
||||
["/bitmap" {:handler bitmap-export-handler}]]])
|
||||
|
||||
(defn- router-handler
|
||||
[router]
|
||||
(fn [{:keys [ctx] :as req}]
|
||||
(fn [{:keys [ctx body] :as request}]
|
||||
(let [route (match router ctx)
|
||||
request (assoc req
|
||||
params (merge {}
|
||||
(:query-params route)
|
||||
(:path-params route)
|
||||
(when (map? body) body))
|
||||
request (assoc request
|
||||
:route route
|
||||
:params (:params route))
|
||||
:params params)
|
||||
|
||||
handler (get-in route [:data :handler])]
|
||||
(if (and route handler)
|
||||
(handler request)
|
||||
|
|
120
exporter/src/app/http/bitmap_export.cljs
Normal file
120
exporter/src/app/http/bitmap_export.cljs
Normal file
|
@ -0,0 +1,120 @@
|
|||
(ns app.http.bitmap-export
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[app.browser :as bwr]
|
||||
[app.config :as cfg]
|
||||
[app.zipfile :as zip]
|
||||
[lambdaisland.glogi :as log]
|
||||
[cljs.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
[uxbox.common.exceptions :as exc :include-macros true]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.common.pages :as cp]
|
||||
[uxbox.common.spec :as us])
|
||||
(:import
|
||||
goog.Uri))
|
||||
|
||||
(defn- screenshot-object
|
||||
[browser {:keys [page-id object-id token scale suffix]}]
|
||||
(letfn [(handle [page]
|
||||
(let [path (str "/render-object/" page-id "/" object-id)
|
||||
uri (doto (Uri. (:public-uri cfg/config))
|
||||
(.setPath "/")
|
||||
(.setFragment path))
|
||||
cookie {:domain (str (.getDomain uri)
|
||||
":"
|
||||
(.getPort uri))
|
||||
:key "auth-token"
|
||||
:value token}]
|
||||
(log/info :uri (.toString uri))
|
||||
(screenshot page (.toString uri) cookie)))
|
||||
|
||||
(screenshot [page uri cookie]
|
||||
(p/do!
|
||||
(bwr/emulate! page {:viewport [1920 1080]
|
||||
:scale scale})
|
||||
(bwr/set-cookie! page cookie)
|
||||
(bwr/navigate! page uri)
|
||||
(bwr/eval! page (js* "() => document.body.style.background = 'transparent'"))
|
||||
(p/let [dom (bwr/select page "#screenshot")]
|
||||
(bwr/screenshot dom {:omit-background? true
|
||||
:type type}))))]
|
||||
|
||||
(bwr/exec! browser handle)))
|
||||
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::object-id ::us/uuid)
|
||||
(s/def ::scale ::us/number)
|
||||
(s/def ::suffix ::us/string)
|
||||
(s/def ::type ::us/keyword)
|
||||
|
||||
(s/def ::suffix string?)
|
||||
(s/def ::scale number?)
|
||||
(s/def ::export
|
||||
(s/keys :req-un [::type ::suffix ::scale]))
|
||||
|
||||
(s/def ::exports (s/coll-of ::export :kind vector?))
|
||||
|
||||
(s/def ::bitmap-handler-params
|
||||
(s/keys :req-un [::page-id ::object-id ::name ::exports]))
|
||||
|
||||
(declare handle-single-export)
|
||||
(declare handle-multiple-export)
|
||||
|
||||
(defn bitmap-export-handler
|
||||
[{:keys [params browser cookies] :as request}]
|
||||
(let [{:keys [exports page-id object-id name]} (us/conform ::bitmap-handler-params params)
|
||||
token (.get ^js cookies "auth-token")]
|
||||
(case (count exports)
|
||||
0 (exc/raise :type :validation :code :missing-exports)
|
||||
1 (handle-single-export
|
||||
request
|
||||
(assoc (first exports)
|
||||
:name name
|
||||
:token token
|
||||
:page-id page-id
|
||||
:object-id object-id))
|
||||
(handle-multiple-export
|
||||
request
|
||||
(->> (d/enumerate exports)
|
||||
(map (fn [[index item]]
|
||||
(assoc item
|
||||
:name name
|
||||
:index index
|
||||
:token token
|
||||
:page-id page-id
|
||||
:object-id object-id))))))))
|
||||
|
||||
(defn perform-bitmap-export
|
||||
[browser params]
|
||||
(p/let [content (screenshot-object browser params)]
|
||||
{:content content
|
||||
:filename (str (str/slug (:name params))
|
||||
(if (not (str/blank? (:suffix params "")))
|
||||
(:suffix params "")
|
||||
(let [index (:index params 0)]
|
||||
(when (pos? index)
|
||||
(str "-" (inc index)))))
|
||||
".png")
|
||||
:length (alength content)
|
||||
:mime-type "image/png"}))
|
||||
|
||||
(defn handle-single-export
|
||||
[{:keys [browser]} params]
|
||||
(p/let [result (perform-bitmap-export browser params)]
|
||||
{:status 200
|
||||
:body (:content result)
|
||||
:headers {"content-type" (:mime-type result)
|
||||
"content-length" (:length result)}}))
|
||||
|
||||
(defn handle-multiple-export
|
||||
[{:keys [browser]} exports]
|
||||
(let [proms (map (partial perform-bitmap-export browser) exports)]
|
||||
(-> (p/all proms)
|
||||
(p/then (fn [results]
|
||||
(reduce #(zip/add! %1 (:filename %2) (:content %2)) (zip/create) results)))
|
||||
(p/then (fn [fzip]
|
||||
{:status 200
|
||||
:headers {"content-type" "application/zip"}
|
||||
:body (.generateNodeStream ^js fzip)})))))
|
|
@ -1,73 +0,0 @@
|
|||
(ns app.http.screenshot
|
||||
(:require
|
||||
[app.browser :as bwr]
|
||||
[app.config :as cfg]
|
||||
[lambdaisland.glogi :as log]
|
||||
[cljs.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
[uxbox.common.exceptions :as exc :include-macros true]
|
||||
[uxbox.common.spec :as us])
|
||||
(:import
|
||||
goog.Uri))
|
||||
|
||||
(defn- load-and-screenshot
|
||||
[page url cookie]
|
||||
(p/do!
|
||||
(bwr/emulate! page {:viewport [1920 1080]})
|
||||
(bwr/set-cookie! page cookie)
|
||||
(bwr/navigate! page url)
|
||||
(bwr/sleep page 500)
|
||||
(.evaluate page (js* "() => document.body.style.background = 'transparent'"))
|
||||
;; (.screenshot ^js page #js {:omitBackground true :fullPage true})
|
||||
(p/let [dom (.$ page "#screenshot")]
|
||||
(.screenshot ^js dom #js {:omitBackground true}))))
|
||||
|
||||
(defn- take-screenshot
|
||||
[browser {:keys [page-id object-id token]}]
|
||||
(letfn [(on-browser [page]
|
||||
(let [path (str "/render-object/" page-id "/" object-id)
|
||||
uri (doto (Uri. (:public-uri cfg/config))
|
||||
(.setPath "/")
|
||||
(.setFragment path))
|
||||
cookie {:domain (str (.getDomain uri)
|
||||
":"
|
||||
(.getPort uri))
|
||||
:key "auth-token"
|
||||
:value token}]
|
||||
(log/info :uri (.toString uri))
|
||||
(load-and-screenshot page (.toString uri) cookie)))]
|
||||
(bwr/exec! browser on-browser)))
|
||||
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::object-id ::us/uuid)
|
||||
(s/def ::bitmap-handler-params
|
||||
(s/keys :req-un [::page-id ::object-id]))
|
||||
|
||||
(defn bitmap-handler
|
||||
[{:keys [params browser cookies] :as request}]
|
||||
(let [params (us/conform ::bitmap-handler-params (:query params))
|
||||
token (.get ^js cookies "auth-token")]
|
||||
(-> (take-screenshot browser {:page-id (:page-id params)
|
||||
:object-id (:object-id params)
|
||||
:token token})
|
||||
(p/then (fn [result]
|
||||
{:status 200
|
||||
:body result
|
||||
:headers {"content-type" "image/png"
|
||||
"content-length" (alength result)}})))))
|
||||
|
||||
(defn page-handler
|
||||
[{:keys [params browser] :as request}]
|
||||
(letfn [(screenshot [page uri]
|
||||
(p/do!
|
||||
(bwr/emulate! page {:viewport [1920 1080]})
|
||||
(bwr/navigate! page uri)
|
||||
(bwr/sleep page 500)
|
||||
;; (.evaluate page (js* "() => document.body.style.background = 'transparent'"))
|
||||
(.screenshot ^js page #js {:omitBackground false})))]
|
||||
(p/let [uri (get-in params [:query :uri])
|
||||
sht (bwr/exec! browser #(screenshot % uri))]
|
||||
{:status 200
|
||||
:body sht
|
||||
:headers {"content-type" "image/png"
|
||||
"content-length" (alength sht)}})))
|
13
exporter/src/app/zipfile.cljs
Normal file
13
exporter/src/app/zipfile.cljs
Normal file
|
@ -0,0 +1,13 @@
|
|||
(ns app.zipfile
|
||||
(:require
|
||||
["jszip" :as jszip]))
|
||||
|
||||
(defn create
|
||||
[]
|
||||
(new jszip))
|
||||
|
||||
(defn add!
|
||||
[zfile name data]
|
||||
(.file ^js zfile name data)
|
||||
zfile)
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue