mirror of
https://github.com/penpot/penpot.git
synced 2025-03-25 06:01:46 -05:00
🎉 Add stacked exports.
This commit is contained in:
parent
a8d5cdc29f
commit
2fb4e72240
14 changed files with 549 additions and 208 deletions
|
@ -80,7 +80,7 @@
|
|||
(s/def ::stroke-width number?)
|
||||
(s/def ::stroke-alignment #{:center :inner :outer})
|
||||
(s/def ::text-align #{"left" "right" "center" "justify"})
|
||||
(s/def ::type #{:rect :path :circle :image :text :canvas :curve :icon :frame :group})
|
||||
(s/def ::type keyword?)
|
||||
(s/def ::x number?)
|
||||
(s/def ::y number?)
|
||||
(s/def ::cx number?)
|
||||
|
@ -93,6 +93,14 @@
|
|||
(s/def ::x2 number?)
|
||||
(s/def ::y2 number?)
|
||||
|
||||
(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 ::selrect (s/keys :req-un [::x
|
||||
::y
|
||||
::x1
|
||||
|
@ -124,6 +132,7 @@
|
|||
::rx ::ry
|
||||
::cx ::cy
|
||||
::x ::y
|
||||
::exports
|
||||
::stroke-color
|
||||
::stroke-opacity
|
||||
::stroke-style
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
|
||||
;; --- Default Specs
|
||||
|
||||
(s/def ::keyword keyword?)
|
||||
(s/def ::inst inst?)
|
||||
(s/def ::string string?)
|
||||
(s/def ::email (s/conformer email-conformer str))
|
||||
|
|
104
exporter/package-lock.json
generated
104
exporter/package-lock.json
generated
|
@ -261,6 +261,11 @@
|
|||
"integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
|
||||
"dev": true
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"cache-content-type": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz",
|
||||
|
@ -344,8 +349,7 @@
|
|||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||
"dev": true
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"create-ecdh": {
|
||||
"version": "4.0.3",
|
||||
|
@ -697,11 +701,29 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||
},
|
||||
"immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
|
||||
},
|
||||
"inflation": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz",
|
||||
"integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
|
@ -724,8 +746,7 @@
|
|||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"dev": true
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
|
@ -733,6 +754,41 @@
|
|||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"jszip": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
|
||||
"integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==",
|
||||
"requires": {
|
||||
"lie": "~3.3.0",
|
||||
"pako": "~1.0.2",
|
||||
"readable-stream": "~2.3.6",
|
||||
"set-immediate-shim": "~1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"keygrip": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
|
||||
|
@ -795,6 +851,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"requires": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
@ -993,8 +1057,7 @@
|
|||
"pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
},
|
||||
"parse-asn1": {
|
||||
"version": "5.1.5",
|
||||
|
@ -1053,8 +1116,7 @@
|
|||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"dev": true
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.3",
|
||||
|
@ -1190,6 +1252,17 @@
|
|||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz",
|
||||
"integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.3",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
|
@ -1234,6 +1307,16 @@
|
|||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"set-immediate-shim": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
|
||||
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
|
||||
},
|
||||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
|
@ -1497,6 +1580,11 @@
|
|||
"through": "^2.3.8"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"url": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
||||
|
|
|
@ -9,9 +9,12 @@
|
|||
"author": "UXBOX LABS SL",
|
||||
"license": "SEE LICENSE IN <LICENSE>",
|
||||
"dependencies": {
|
||||
"inflation": "^2.0.0",
|
||||
"jszip": "^3.5.0",
|
||||
"koa": "^2.13.0",
|
||||
"puppeteer": "^4.0.1",
|
||||
"puppeteer-cluster": "^0.21.0",
|
||||
"raw-body": "^2.4.1",
|
||||
"xregexp": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
[funcool/cuerdas "2020.03.26-3"]
|
||||
[lambdaisland/glogi "1.0.63"]
|
||||
[metosin/reitit-core "0.5.2"]
|
||||
[com.cognitect/transit-cljs "0.8.264"]]
|
||||
[com.cognitect/transit-cljs "0.8.264"]
|
||||
[frankiesardo/linked "1.3.0"]]
|
||||
|
||||
:source-paths ["src" "../common"]
|
||||
:nrepl {:port 3497}
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -416,7 +416,7 @@
|
|||
}
|
||||
},
|
||||
"dashboard.sidebar.drafts" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:129" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:128" ],
|
||||
"translations" : {
|
||||
"en" : "Drafts",
|
||||
"fr" : "Brouillons",
|
||||
|
@ -424,7 +424,7 @@
|
|||
}
|
||||
},
|
||||
"dashboard.sidebar.libraries" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:135" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:134" ],
|
||||
"translations" : {
|
||||
"en" : "Libraries",
|
||||
"fr" : "Librairies",
|
||||
|
@ -432,7 +432,7 @@
|
|||
}
|
||||
},
|
||||
"dashboard.sidebar.recent" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:122" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:121" ],
|
||||
"translations" : {
|
||||
"en" : "Recent",
|
||||
"fr" : "Récent",
|
||||
|
@ -528,7 +528,7 @@
|
|||
}
|
||||
},
|
||||
"ds.search.placeholder" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:188" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:187" ],
|
||||
"translations" : {
|
||||
"en" : "Search...",
|
||||
"fr" : "Rechercher...",
|
||||
|
@ -584,7 +584,7 @@
|
|||
}
|
||||
},
|
||||
"errors.image-format-unsupported" : {
|
||||
"used-in" : [ "src/uxbox/main/data/images.cljs:376", "src/uxbox/main/data/workspace/persistence.cljs:365", "src/uxbox/main/data/users.cljs:177" ],
|
||||
"used-in" : [ "src/uxbox/main/data/users.cljs:177", "src/uxbox/main/data/workspace/persistence.cljs:365", "src/uxbox/main/data/images.cljs:376" ],
|
||||
"translations" : {
|
||||
"en" : "The image format is not supported (must be svg, jpg or png).",
|
||||
"fr" : "Le format d'image n'est pas supporté (doit être svg, jpg ou png).",
|
||||
|
@ -592,7 +592,7 @@
|
|||
}
|
||||
},
|
||||
"errors.image-too-large" : {
|
||||
"used-in" : [ "src/uxbox/main/data/images.cljs:374", "src/uxbox/main/data/workspace/persistence.cljs:363", "src/uxbox/main/data/users.cljs:175" ],
|
||||
"used-in" : [ "src/uxbox/main/data/users.cljs:175", "src/uxbox/main/data/workspace/persistence.cljs:363", "src/uxbox/main/data/images.cljs:374" ],
|
||||
"translations" : {
|
||||
"en" : "The image is too large to be inserted (must be under 5mb).",
|
||||
"fr" : "L'image est trop grande (doit être inférieure à 5 Mo).",
|
||||
|
@ -632,7 +632,7 @@
|
|||
}
|
||||
},
|
||||
"errors.unexpected-error" : {
|
||||
"used-in" : [ "src/uxbox/main/data/images.cljs:385", "src/uxbox/main/data/workspace/persistence.cljs:334", "src/uxbox/main/data/workspace/persistence.cljs:374", "src/uxbox/main/data/users.cljs:185", "src/uxbox/main/ui/auth/register.cljs:54", "src/uxbox/main/ui/settings/change_email.cljs:51" ],
|
||||
"used-in" : [ "src/uxbox/main/data/users.cljs:185", "src/uxbox/main/data/workspace/persistence.cljs:334", "src/uxbox/main/data/workspace/persistence.cljs:374", "src/uxbox/main/data/images.cljs:385", "src/uxbox/main/ui/settings/change_email.cljs:51", "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:65", "src/uxbox/main/ui/auth/register.cljs:54" ],
|
||||
"translations" : {
|
||||
"en" : "An unexpected error occurred.",
|
||||
"fr" : "Une erreur inattendue c'est produite",
|
||||
|
@ -672,7 +672,7 @@
|
|||
}
|
||||
},
|
||||
"image.loading" : {
|
||||
"used-in" : [ "src/uxbox/main/data/images.cljs:393", "src/uxbox/main/data/workspace/persistence.cljs:341", "src/uxbox/main/data/workspace/persistence.cljs:382", "src/uxbox/main/data/users.cljs:191" ],
|
||||
"used-in" : [ "src/uxbox/main/data/users.cljs:191", "src/uxbox/main/data/workspace/persistence.cljs:341", "src/uxbox/main/data/workspace/persistence.cljs:382", "src/uxbox/main/data/images.cljs:393" ],
|
||||
"translations" : {
|
||||
"en" : "Loading image...",
|
||||
"fr" : "Chargement de l'image...",
|
||||
|
@ -840,11 +840,12 @@
|
|||
}
|
||||
},
|
||||
"settings.multiple" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:132", "src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs:117", "src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs:126" ],
|
||||
"translations" : {
|
||||
"en" : "Multiple",
|
||||
"es" : "Múltiple"
|
||||
}
|
||||
"en" : null,
|
||||
"fr" : null,
|
||||
"es" : null
|
||||
},
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs:117", "src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs:126", "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:132" ]
|
||||
},
|
||||
"settings.new-email-label" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/settings/change_email.cljs:75" ],
|
||||
|
@ -1367,27 +1368,33 @@
|
|||
}
|
||||
},
|
||||
"workspace.options.design" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:70" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:65" ],
|
||||
"translations" : {
|
||||
"en" : "Design",
|
||||
"fr" : "Conception",
|
||||
"es" : "Diseño"
|
||||
}
|
||||
},
|
||||
"workspace.options.export-object" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:78" ],
|
||||
"workspace.options.export" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:115", "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:148" ],
|
||||
"translations" : {
|
||||
"en" : "Export"
|
||||
}
|
||||
},
|
||||
"workspace.options.export-object" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:144" ],
|
||||
"translations" : {
|
||||
"en" : "Export shape"
|
||||
}
|
||||
},
|
||||
"workspace.options.exporting-object" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:77" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:143" ],
|
||||
"translations" : {
|
||||
"en" : "Exporting..."
|
||||
}
|
||||
},
|
||||
"workspace.options.fill" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:40", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:382" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:382", "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:40" ],
|
||||
"translations" : {
|
||||
"en" : "Fill",
|
||||
"fr" : "Remplissage",
|
||||
|
@ -1395,7 +1402,7 @@
|
|||
}
|
||||
},
|
||||
"workspace.options.font-options" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:388" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:387" ],
|
||||
"translations" : {
|
||||
"en" : "Text",
|
||||
"fr" : "Texte",
|
||||
|
@ -1715,18 +1722,20 @@
|
|||
}
|
||||
},
|
||||
"workspace.options.group-fill" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:39" ],
|
||||
"translations" : {
|
||||
"en" : "Group fill",
|
||||
"es" : "Relleno de grupo"
|
||||
}
|
||||
"en" : null,
|
||||
"fr" : null,
|
||||
"es" : null
|
||||
},
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:39" ]
|
||||
},
|
||||
"workspace.options.group-stroke" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:65" ],
|
||||
"translations" : {
|
||||
"en" : "Group stroke",
|
||||
"es" : "Borde de grupo"
|
||||
}
|
||||
"en" : null,
|
||||
"fr" : null,
|
||||
"es" : null
|
||||
},
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:65" ]
|
||||
},
|
||||
"workspace.options.navigate-to" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:59" ],
|
||||
|
@ -1745,7 +1754,7 @@
|
|||
}
|
||||
},
|
||||
"workspace.options.position" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:112", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:125" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:144", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:125" ],
|
||||
"translations" : {
|
||||
"en" : "Position",
|
||||
"fr" : "Position",
|
||||
|
@ -1753,7 +1762,7 @@
|
|||
}
|
||||
},
|
||||
"workspace.options.prototype" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:80" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:74" ],
|
||||
"translations" : {
|
||||
"en" : "Prototype",
|
||||
"fr" : "Prototype",
|
||||
|
@ -1761,7 +1770,7 @@
|
|||
}
|
||||
},
|
||||
"workspace.options.radius" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:158" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:186" ],
|
||||
"translations" : {
|
||||
"en" : "Radius",
|
||||
"fr" : "Rayon",
|
||||
|
@ -1769,7 +1778,7 @@
|
|||
}
|
||||
},
|
||||
"workspace.options.rotation" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:131" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:163" ],
|
||||
"translations" : {
|
||||
"en" : "Rotation",
|
||||
"fr" : "Rotation",
|
||||
|
@ -1793,21 +1802,23 @@
|
|||
}
|
||||
},
|
||||
"workspace.options.selection-fill" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:38" ],
|
||||
"translations" : {
|
||||
"en" : "Selection fill",
|
||||
"es" : "Relleno de selección"
|
||||
}
|
||||
"en" : null,
|
||||
"fr" : null,
|
||||
"es" : null
|
||||
},
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:38" ]
|
||||
},
|
||||
"workspace.options.selection-stroke" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:64" ],
|
||||
"translations" : {
|
||||
"en" : "Selection stroke",
|
||||
"es" : "Borde de selección"
|
||||
}
|
||||
"en" : null,
|
||||
"fr" : null,
|
||||
"es" : null
|
||||
},
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:64" ]
|
||||
},
|
||||
"workspace.options.size" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:82", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:98" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:114", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:98" ],
|
||||
"translations" : {
|
||||
"en" : "Size",
|
||||
"fr" : "Taille",
|
||||
|
|
|
@ -760,3 +760,42 @@
|
|||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.element-set-options-group {
|
||||
display: flex;
|
||||
padding: 3px;
|
||||
border: 1px solid $color-black;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #1F1F1F;
|
||||
}
|
||||
}
|
||||
|
||||
.exports-options {
|
||||
.element-set-options-group {
|
||||
justify-content: space-between;
|
||||
.delete-icon {
|
||||
display: flex;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: $color-gray-20;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.download-button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,8 @@
|
|||
(ns uxbox.main.ui.workspace.sidebar.options
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
|
@ -22,6 +19,7 @@
|
|||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.workspace.sidebar.align :refer [align-options]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.circle :as circle]
|
||||
[uxbox.main.ui.workspace.sidebar.options.exports :refer [exports-menu]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.frame :as frame]
|
||||
[uxbox.main.ui.workspace.sidebar.options.group :as group]
|
||||
[uxbox.main.ui.workspace.sidebar.options.icon :as icon]
|
||||
|
@ -32,61 +30,11 @@
|
|||
[uxbox.main.ui.workspace.sidebar.options.interactions :refer [interactions-menu]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.path :as path]
|
||||
[uxbox.main.ui.workspace.sidebar.options.rect :as rect]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.http :as http]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]
|
||||
[uxbox.util.object :as obj]))
|
||||
[uxbox.main.ui.workspace.sidebar.options.text :as text]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]))
|
||||
|
||||
;; --- Options
|
||||
|
||||
(defn- request-screenshot
|
||||
[page-id shape-id]
|
||||
(http/send! {:method :get
|
||||
:uri "/export/bitmap"
|
||||
:query {:page-id page-id
|
||||
:object-id shape-id}}
|
||||
{:credentials? true
|
||||
:response-type :blob}))
|
||||
|
||||
(defn- trigger-download
|
||||
[name blob]
|
||||
(let [link (dom/create-element "a")
|
||||
uri (dom/create-uri blob)]
|
||||
(obj/set! link "href" uri)
|
||||
(obj/set! link "download" (str/slug name))
|
||||
(obj/set! (.-style ^js link) "display" "none")
|
||||
(.appendChild (.-body ^js js/document) link)
|
||||
(.click link)
|
||||
(.remove link)))
|
||||
|
||||
(mf/defc shape-export
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [shape page] :as props}]
|
||||
(let [loading? (mf/use-state false)
|
||||
locale (mf/deref i18n/locale)
|
||||
on-click (fn [event]
|
||||
(dom/prevent-default event)
|
||||
(swap! loading? not)
|
||||
(->> (request-screenshot (:id page) (:id shape))
|
||||
(rx/subs
|
||||
(fn [{:keys [status body] :as response}]
|
||||
(if (= status 200)
|
||||
(trigger-download (:name shape) body)
|
||||
(st/emit! (dm/error (tr "errors.unexpected-error")))))
|
||||
(constantly nil)
|
||||
(fn []
|
||||
(swap! loading? not)))))]
|
||||
|
||||
[:div.element-set
|
||||
[:div.btn-large.btn-icon-dark
|
||||
{:on-click (when-not @loading? on-click)
|
||||
:class (dom/classnames
|
||||
:btn-disabled @loading?)
|
||||
:disabled @loading?}
|
||||
(if @loading?
|
||||
(t locale "workspace.options.exporting-object")
|
||||
(t locale "workspace.options.export-object"))]]))
|
||||
|
||||
(mf/defc shape-options
|
||||
{::mf/wrap [#(mf/throttle % 60)]}
|
||||
[{:keys [shape page] :as props}]
|
||||
|
@ -102,7 +50,7 @@
|
|||
:curve [:& path/options {:shape shape}]
|
||||
:image [:& image/options {:shape shape}]
|
||||
nil)
|
||||
[:& shape-export {:shape shape :page page}]])
|
||||
[:& exports-menu {:shape shape :page page}]])
|
||||
|
||||
|
||||
(mf/defc options-content
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.exports
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[beicon.core :as rx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.object :as obj]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.http-api :as http]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]))
|
||||
|
||||
(defn- request-export
|
||||
[shape exports]
|
||||
(http/send! {:method :post
|
||||
:uri "/export/bitmap"
|
||||
:response-type :blob
|
||||
:auth true
|
||||
:body {:page-id (:page-id shape)
|
||||
:object-id (:id shape)
|
||||
:name (:name shape)
|
||||
:exports exports}}))
|
||||
|
||||
(defn- trigger-download
|
||||
[name blob]
|
||||
(let [link (dom/create-element "a")
|
||||
uri (dom/create-uri blob)]
|
||||
(obj/set! link "href" uri)
|
||||
(obj/set! link "download" (str/slug name))
|
||||
(obj/set! (.-style ^js link) "display" "none")
|
||||
(.appendChild (.-body ^js js/document) link)
|
||||
(.click link)
|
||||
(.remove link)))
|
||||
|
||||
(mf/defc exports-menu
|
||||
[{:keys [shape page] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
exports (:exports shape [])
|
||||
loading? (mf/use-state false)
|
||||
|
||||
on-download
|
||||
(mf/use-callback
|
||||
(mf/deps shape)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(swap! loading? not)
|
||||
(->> (request-export (assoc shape :page-id (:id page)) exports)
|
||||
(rx/subs
|
||||
(fn [{:keys [status body] :as response}]
|
||||
(js/console.log status body)
|
||||
(if (= status 200)
|
||||
(trigger-download (:name shape) body)
|
||||
(st/emit! (dm/error (tr "errors.unexpected-error")))))
|
||||
(constantly nil)
|
||||
(fn []
|
||||
(swap! loading? not))))))
|
||||
|
||||
_ (prn "exports-menu" exports)
|
||||
|
||||
add-export
|
||||
(mf/use-callback
|
||||
(mf/deps shape)
|
||||
(fn []
|
||||
(let [xspec {:type :png
|
||||
:suffix ""
|
||||
:scale 1}]
|
||||
(st/emit! (udw/update-shape (:id shape)
|
||||
{:exports (conj exports xspec)})))))
|
||||
delete-export
|
||||
(mf/use-callback
|
||||
(mf/deps shape)
|
||||
(fn [index]
|
||||
(let [[before after] (split-at index exports)
|
||||
exports (d/concat [] before (rest after))]
|
||||
(st/emit! (udw/update-shape (:id shape)
|
||||
{:exports exports})))))
|
||||
|
||||
on-scale-change
|
||||
(mf/use-callback
|
||||
(mf/deps shape)
|
||||
(fn [index event]
|
||||
(let [target (dom/get-target event)
|
||||
value (dom/get-value target)
|
||||
value (d/parse-double value)
|
||||
exports (assoc-in exports [index :scale] value)]
|
||||
(st/emit! (udw/update-shape (:id shape)
|
||||
{:exports exports})))))
|
||||
|
||||
on-suffix-change
|
||||
(mf/use-callback
|
||||
(mf/deps shape)
|
||||
(fn [index event]
|
||||
(let [target (dom/get-target event)
|
||||
value (dom/get-value target)
|
||||
exports (assoc-in exports [index :suffix] value)]
|
||||
(st/emit! (udw/update-shape (:id shape)
|
||||
{:exports exports})))))]
|
||||
|
||||
|
||||
(if (seq exports)
|
||||
[:div.element-set.exports-options
|
||||
[:div.element-set-title
|
||||
[:span (t locale "workspace.options.export")]
|
||||
[:div.add-page {:on-click add-export} i/close]]
|
||||
[:div.element-set-content
|
||||
(for [[index export] (d/enumerate exports)]
|
||||
[:div.element-set-options-group
|
||||
{:key index}
|
||||
[:select.input-select {:on-change (partial on-scale-change index)
|
||||
:value (:scale export)}
|
||||
[:option {:value "0.5"} "0.5x"]
|
||||
[:option {:value "0.75"} "0.75x"]
|
||||
[:option {:value "1"} "1x"]
|
||||
[:option {:value "1.5"} "1.5x"]
|
||||
[:option {:value "2"} "2x"]
|
||||
[:option {:value "4"} "4x"]
|
||||
[:option {:value "6"} "6x"]]
|
||||
[:input.input-text {:value (:suffix export)
|
||||
:on-change (partial on-suffix-change index)}]
|
||||
[:select.input-select
|
||||
[:option {:value "png"} "PNG"]]
|
||||
[:div.delete-icon {:on-click (partial delete-export index)}
|
||||
i/trash]])
|
||||
|
||||
[:div.btn-large.btn-icon-dark.download-button
|
||||
{:on-click (when-not @loading? on-download)
|
||||
:class (dom/classnames
|
||||
:btn-disabled @loading?)
|
||||
:disabled @loading?}
|
||||
(if @loading?
|
||||
(t locale "workspace.options.exporting-object")
|
||||
(t locale "workspace.options.export-object"))]]]
|
||||
|
||||
[:div.element-set
|
||||
[:div.element-set-title
|
||||
[:span (t locale "workspace.options.export")]
|
||||
[:div.add-page {:on-click add-export} i/close]]])))
|
||||
|
||||
|
Loading…
Add table
Reference in a new issue