🎉 Add svg export.

This commit is contained in:
Andrey Antukh 2020-07-30 15:23:11 +02:00 committed by Hirunatan
parent 17bea924b2
commit 17813e5090
18 changed files with 950 additions and 226 deletions

View File

@ -15,10 +15,12 @@
"puppeteer": "^4.0.1",
"puppeteer-cluster": "^0.21.0",
"raw-body": "^2.4.1",
"svgo": "^1.3.2",
"xml-js": "^1.6.11",
"xregexp": "^4.3.0"
},
"devDependencies": {
"shadow-cljs": "^2.10.13",
"shadow-cljs": "^2.10.17",
"source-map-support": "^0.5.19"
}
}

View File

@ -19,10 +19,10 @@
:or {user-agent USER-AGENT
scale 1}}]
(let [[width height] viewport]
(.emulate page #js {:viewport #js {:width width
:height height
:deviceScaleFactor scale}
:userAgent user-agent})))
(.emulate ^js page #js {:viewport #js {:width width
:height height
:deviceScaleFactor scale}
:userAgent user-agent})))
(defn navigate!
([page url] (navigate! page url nil))
@ -36,10 +36,12 @@
(defn screenshot
([frame] (screenshot frame nil))
([frame {:keys [full-page? omit-background?]
([frame {:keys [full-page? omit-background? type]
:or {full-page? false
type "png"
omit-background? false}}]
(.screenshot ^js frame #js {:fullPage full-page?
:type (name type)
:omitBackground omit-background?})))
(defn eval!
@ -50,6 +52,10 @@
[frame selector]
(.$ ^js frame selector))
(defn select-all
[frame selector]
(.$$ ^js frame selector))
(defn set-cookie!
[page {:keys [key value domain]}]
(.setCookie ^js page #js {:name key

View File

@ -1,139 +1,20 @@
(ns app.http
(:require
[promesa.core :as p]
[app.http.export :refer [export-handler]]
[app.http.impl :as impl]
[lambdaisland.glogi :as log]
[app.browser :as bwr]
[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]
["inflation" :as inflate]
["raw-body" :as raw-body])
(:import
goog.Uri))
(defn query-params
"Given goog.Uri, read query parameters into Clojure map."
[^goog.Uri uri]
(let [q (.getQueryData uri)]
(->> q
(.getKeys)
(map (juxt keyword #(.get q %)))
(into {}))))
(defn- match
[router ctx]
(let [uri (.parse Uri (unchecked-get ctx "originalUrl"))]
(when-let [match (r/match-by-path router (.getPath uri))]
(assoc match :query-params (query-params uri)))))
(defn- handle-error
[error request]
(let [{:keys [type message code] :as data} (ex-data error)]
(cond
(= :validation type)
(let [header (get-in request [:headers "accept"])]
(if (and (str/starts-with? header "text/html")
(= :spec-validation (:code data)))
{:status 400
:headers {"content-type" "text/html"}
:body (str "<pre style='font-size:16px'>" (:explain data) "</pre>\n")}
{:status 400
:headers {"x-metadata" (t/encode data)}
:body ""}))
:else
(do
(log/error :msg "Unexpected error"
:error error)
{:status 500
:headers {"x-metadata" (t/encode {:type :unexpected
:message (ex-message error)})}
:body ""}))))
(defn- handle-response
[ctx {:keys [body headers status] :or {headers {} status 200}}]
(run! (fn [[k v]] (.set ^js ctx k v)) headers)
(set! (.-body ^js ctx) body)
(set! (.-status ^js ctx) status)
nil)
(defn- parse-headers
[ctx]
(let [orig (unchecked-get ctx "headers")]
(persistent!
(reduce #(assoc! %1 %2 (unchecked-get orig %2))
(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]
(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)
(handle-response ctx rsp))))
(p/catch (fn [err]
(->> (handle-error err request)
(handle-response ctx))))))))
[promesa.core :as p]
[reitit.core :as r]))
(def routes
[["/export"
["/bitmap" {:handler bitmap-export-handler}]]])
(defn- router-handler
[router]
(fn [{:keys [ctx body] :as request}]
(let [route (match router ctx)
params (merge {}
(:query-params route)
(:path-params route)
(when (map? body) body))
request (assoc request
:route route
:params params)
handler (get-in route [:data :handler])]
(if (and route handler)
(handler request)
{:status 404
:body "Not found"}))))
[["/export" {:handler export-handler}]])
(defn start!
[extra]
(log/info :msg "starting http server" :port 6061)
(let [router (r/router routes)
instance (doto (new koa)
(.use (-> (router-handler router)
(wrap-handler extra))))
server (.createServer http (.callback instance))]
(let [router (r/router routes)
handler (impl/handler router extra)
server (impl/server handler)]
(.listen server 6061)
(p/resolved server)))
@ -143,4 +24,3 @@
(.close server (fn []
(log/info :msg "shutdown http server")
(resolve))))))

View File

@ -1,47 +1,14 @@
(ns app.http.bitmap-export
(ns app.http.export
(:require
[cuerdas.core :as str]
[app.browser :as bwr]
[app.config :as cfg]
[app.http.export-bitmap :as bitmap]
[app.http.export-svg :as svg]
[app.zipfile :as zip]
[lambdaisland.glogi :as log]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[lambdaisland.glogi :as log]
[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 type]}]
(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")]
(case type
:png (bwr/screenshot dom {:omit-background? true :type type})
:jpeg (bwr/screenshot dom {:omit-background? false :type type})))))]
(bwr/exec! browser handle)))
[uxbox.common.spec :as us]))
(s/def ::name ::us/string)
(s/def ::page-id ::us/uuid)
@ -49,25 +16,23 @@
(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 ::export (s/keys :req-un [::type ::suffix ::scale]))
(s/def ::exports (s/coll-of ::export :kind vector?))
(s/def ::bitmap-handler-params
(s/def ::handler-params
(s/keys :req-un [::page-id ::object-id ::name ::exports]))
(declare handle-single-export)
(declare handle-multiple-export)
(declare perform-bitmap-export)
(declare perform-export)
(declare attach-filename)
(defn bitmap-export-handler
(defn export-handler
[{:keys [params browser cookies] :as request}]
(let [{:keys [exports page-id object-id name]} (us/conform ::bitmap-handler-params params)
(let [{:keys [exports page-id object-id name]} (us/conform ::handler-params params)
token (.get ^js cookies "auth-token")]
(case (count exports)
0 (exc/raise :type :validation :code :missing-exports)
@ -87,20 +52,19 @@
:page-id page-id
:object-id object-id)) exports)))))
(defn handle-single-export
(defn- handle-single-export
[{:keys [browser]} params]
(p/let [result (perform-bitmap-export browser params)]
(p/let [result (perform-export browser params)]
{:status 200
:body (:content result)
:headers {"content-type" (:mime-type result)
"content-length" (:length result)}}))
(defn handle-multiple-export
(defn- handle-multiple-export
[{:keys [browser]} exports]
(let [proms (->> exports
(attach-filename)
(map (partial perform-bitmap-export browser)))]
(map (partial perform-export browser)))]
(-> (p/all proms)
(p/then (fn [results]
(reduce #(zip/add! %1 (:filename %2) (:content %2)) (zip/create) results)))
@ -109,18 +73,24 @@
:headers {"content-type" "application/zip"}
:body (.generateNodeStream ^js fzip)})))))
(defn- perform-export
[browser params]
(case (:type params)
:png (bitmap/export browser params)
:jpeg (bitmap/export browser params)
:svg (svg/export browser params)))
(defn- find-filename-candidate
[params used]
(loop [index 0]
(let [candidate (str (str/slug (:name params))
(if (not (str/blank? (:suffix params "")))
(:suffix params "")
(when (pos? index)
(str "-" (inc index))))
(str/trim (str/blank? (:suffix params "")))
(when (pos? index)
(str "-" (inc index)))
(case (:type params)
:png ".png"
:png ".png"
:jpeg ".jpg"
:svg ".svg"))]
:svg ".svg"))]
(if (contains? used candidate)
(recur (inc index))
candidate))))
@ -138,15 +108,3 @@
(recur (next exports)
(conj used candidate)
(conj result export))))))
(defn- perform-bitmap-export
[browser params]
(p/let [content (screenshot-object browser params)]
{:content content
:filename (or (:filename params)
(find-filename-candidate params #{}))
:length (alength content)
:mime-type (case (:type params)
:png "image/png"
:jpeg "image/jpeg")}))

View File

@ -0,0 +1,73 @@
(ns app.http.export-bitmap
(:require
[cuerdas.core :as str]
[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.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 type]}]
(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")]
(case type
:png (bwr/screenshot dom {:omit-background? true :type type})
:jpeg (bwr/screenshot dom {:omit-background? false :type type})))))]
(bwr/exec! browser handle)))
(s/def ::name ::us/string)
(s/def ::suffix ::us/string)
(s/def ::type #{:jpeg :png})
(s/def ::page-id ::us/uuid)
(s/def ::object-id ::us/uuid)
(s/def ::scale ::us/number)
(s/def ::token ::us/string)
(s/def ::filename ::us/string)
(s/def ::export-params
(s/keys :req-un [::name ::suffix ::type ::object-id ::page-id ::scale ::token]
:opt-un [::filename]))
(defn export
[browser params]
(us/assert ::export-params params)
(p/let [content (screenshot-object browser params)]
{:content content
:filename (or (:filename params)
(str (str/slug (:name params))
(str/trim (:suffix params ""))
(case (:type params)
:png ".png"
:jpeg ".jpg")))
:length (alength content)
:mime-type (case (:type params)
:png "image/png"
:jpeg "image/jpeg")}))

View File

@ -0,0 +1,271 @@
(ns app.http.export-svg
(:require
[cuerdas.core :as str]
[clojure.walk :as walk]
[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.data :as d]
[uxbox.common.pages :as cp]
[uxbox.common.spec :as us]
["xml-js" :as xml]
["child_process" :as chp]
["os" :as os]
["path" :as path]
["fs" :as fs])
(:import
goog.Uri))
(def default-svgo-plugins
#js [#js {:convertStyleToAttrs false}])
(defn- create-tmpdir!
[prefix]
(p/create
(fn [resolve reject]
(fs/mkdtemp (path/join (os/tmpdir) prefix)
(fn [err dir]
(if err
(reject err)
(resolve dir)))))))
(defn- write-file!
[fpath content]
(p/create
(fn [resolve reject]
(fs/writeFile fpath content (fn [err]
(if err
(reject err)
(resolve nil)))))))
(defn- read-file
[fpath]
(p/create
(fn [resolve reject]
(fs/readFile fpath (fn [err content]
(if err
(reject err)
(resolve content)))))))
(defn- run-cmd!
[cmd]
(p/create
(fn [resolve reject]
(log/info :fn :run-cmd :cmd cmd)
(chp/exec cmd #js {:encoding "buffer"}
(fn [error stdout stderr]
;; (log/info :fn :run-cmd :stdout stdout)
(if error
(reject error)
(resolve stdout)))))))
(defn- rmdir!
[path]
(p/create
(fn [resolve reject]
(fs/rmdir path #js {:recursive true}
(fn [err]
(if err
(reject err)
(resolve nil)))))))
(defn- parse-xml
[data]
(js->clj (xml/xml2js data)))
(defn- encode-xml
[data]
(xml/js2xml (clj->js data)))
(defn- render-object
[browser {:keys [page-id object-id token scale suffix type]}]
(letfn [(render-in-page [page {:keys [uri cookie] :as rctx}]
(p/do!
(bwr/emulate! page {:viewport [1920 1080]
:scale 2})
(bwr/set-cookie! page cookie)
(bwr/navigate! page uri)
(bwr/eval! page (js* "() => document.body.style.background = 'transparent'"))
page))
(convert-to-ppm [pngpath]
(log/info :fn :convert-to-ppm)
(let [basepath (path/dirname pngpath)
ppmpath (path/join basepath "origin.ppm")]
(-> (run-cmd! (str "convert " pngpath " " ppmpath))
(p/then (constantly ppmpath)))))
(trace-color-mask [pbmpath]
(log/info :fn :trace-color-mask :pbmpath pbmpath)
(let [basepath (path/dirname pbmpath)
basename (path/basename pbmpath ".pbm")
svgpath (path/join basepath (str basename ".svg"))]
(-> (run-cmd! (str "potrace --flat -b svg " pbmpath " -o " svgpath))
(p/then (constantly svgpath)))))
(generate-color-mask [ppmpath color]
(log/info :fn :generate-color-mask :ppmpath ppmpath :color color)
(let [basepath (path/dirname ppmpath)
pbmpath (path/join basepath (str "mask-" (subs color 1) ".pbm"))]
(-> (run-cmd! (str/format "ppmcolormask \"%s\" %s" color ppmpath))
(p/then (fn [stdout]
(-> (write-file! pbmpath stdout)
(p/then (constantly pbmpath)))))
(p/then trace-color-mask)
(p/then clean-svg)
(p/then (fn [svgpath]
(p/let [data (read-file svgpath)
data (parse-xml data)
data (get-in data ["elements" 0])]
{:svgpath svgpath
:color color
:svgdata data}))))))
(join-color-layers [layers]
(log/info :fn :join-color-layers :layers (map :svgpath layers))
(loop [main (-> (:svgdata (first layers))
(assoc "elements" []))
layers (seq layers)]
(if (nil? layers)
main
(let [layer (first layers)
elements (map (fn [element]
(update element "attributes" assoc "fill" (:color layer)))
(get-in layer [:svgdata "elements"] []))]
(recur (update main "elements" d/concat elements)
(next layers))))))
(convert-to-svg [colors ppmpath]
(log/info :fn :convert-to-svg :ppmpath ppmpath :colors colors)
(-> (p/all (map (partial generate-color-mask ppmpath) colors))
(p/then join-color-layers)))
(clean-svg [svgpath]
(log/info :fn :clean-svg :svgpath svgpath)
(let [basepath (path/dirname svgpath)
basename (path/basename svgpath ".svg")
svgpath' (path/join basepath (str basename "-optimized.svg"))]
(-> (run-cmd! (str "svgcleaner " svgpath " " svgpath'))
(p/then (constantly svgpath')))))
(trace-single-node [{:keys [data] :as node}]
(log/info :fn :trace-single-node)
(p/let [tdpath (create-tmpdir! "svgexport-")
pngpath (path/join tdpath "origin.png")
_ (write-file! pngpath data)
ppmpath (convert-to-ppm pngpath)
svgdata (convert-to-svg (:colors node) ppmpath)
svgdata (update svgdata "attributes" assoc
"width" (:width node)
"height" (:height node)
"x" (:x node)
"y" (:y node))]
(-> node
(dissoc :data)
(assoc :tempdir tdpath
:svgdata svgdata))))
(extract-element-attrs [^js element]
(let [^js attrs (.. element -attributes)
^js colors (.. element -dataset -colors)]
#js {:id (.. attrs -id -value)
:x (.. attrs -x -value)
:y (.. attrs -y -value)
:width (.. attrs -width -value)
:height (.. attrs -height -value)
:colors (.split colors ",")}))
(extract-single-node [node]
(log/info :fn :extract-single-node)
(p/let [attrs (bwr/eval! node extract-element-attrs)
shot (bwr/screenshot node {:omit-background? true :type "png"})]
{:id (unchecked-get attrs "id")
:x (unchecked-get attrs "x")
:y (unchecked-get attrs "y")
:width (unchecked-get attrs "width")
:height (unchecked-get attrs "height")
:colors (vec (unchecked-get attrs "colors"))
:data shot}))
(clean-temp-data [{:keys [tempdir] :as node}]
(p/do!
(rmdir! tempdir)
(dissoc node :tempdir)))
(process-single-text-node [item]
(-> (p/resolved item)
(p/then extract-single-node)
(p/then trace-single-node)
(p/then clean-temp-data)))
(process-text-nodes [page]
(log/info :fn :process-text-nodes)
(-> (bwr/select-all page "#screenshot foreignObject")
(p/then #(p/all (map process-single-text-node %)))))
(replace-nodes-on-main [main nodes]
(let [main (parse-xml main)
index (d/index-by :id nodes)
main (walk/prewalk (fn [form]
(cond
(and (map? form)
(= "element" (get form "type"))
(= "foreignObject" (get form "name")))
(let [id (get-in form ["attributes" "id"])
node (get index id)]
(if node
(:svgdata node)
form))
:else
form))
main)]
(encode-xml main)))
(render-svg [page]
(p/let [dom (bwr/select page "#screenshot")
main (bwr/eval! dom (fn [elem] (.-innerHTML ^js elem)))
nodes (process-text-nodes page)]
(replace-nodes-on-main main nodes)))
(handle [rctx page]
(p/let [page (render-in-page page rctx)]
(render-svg page)))]
(let [path (str "/render-object/" page-id "/" object-id)
uri (doto (Uri. (:public-uri cfg/config))
(.setPath "/")
(.setFragment path))
rctx {:cookie {:domain (str (.getDomain uri) ":" (.getPort uri))
:key "auth-token"
:value token}
:uri (.toString uri)}]
(bwr/exec! browser (partial handle rctx)))))
(s/def ::name ::us/string)
(s/def ::suffix ::us/string)
(s/def ::type #{:svg})
(s/def ::page-id ::us/uuid)
(s/def ::object-id ::us/uuid)
(s/def ::scale ::us/number)
(s/def ::token ::us/string)
(s/def ::filename ::us/string)
(s/def ::export-params
(s/keys :req-un [::name ::suffix ::type ::object-id ::page-id ::scale ::token]
:opt-un [::filename]))
(defn export
[browser params]
(us/assert ::export-params params)
(p/let [content (render-object browser params)]
{:content content
:filename (or (:filename params)
(str (str/slug (:name params))
(str/trim (:suffix params ""))
".svg"))
:length (alength content)
:mime-type "image/svg+xml"}))

View File

@ -0,0 +1,136 @@
(ns app.http.impl
(:require
["http" :as http]
["inflation" :as inflate]
["koa" :as koa]
["raw-body" :as raw-body]
[app.util.transit :as t]
[cuerdas.core :as str]
[lambdaisland.glogi :as log]
[promesa.core :as p]
[reitit.core :as r])
(:import
goog.Uri))
(defn- query-params
"Given goog.Uri, read query parameters into Clojure map."
[^Uri uri]
(let [^js q (.getQueryData uri)]
(->> q
(.getKeys)
(map (juxt keyword #(.get q %)))
(into {}))))
(defn- match
[router ctx]
(let [uri (.parse Uri (unchecked-get ctx "originalUrl"))]
(when-let [match (r/match-by-path router (.getPath ^js uri))]
(assoc match :query-params (query-params uri)))))
(defn- handle-error
[error request]
(let [{:keys [type message code] :as data} (ex-data error)]
(cond
(= :validation type)
(let [header (get-in request [:headers "accept"])]
(if (and (str/starts-with? header "text/html")
(= :spec-validation (:code data)))
{:status 400
:headers {"content-type" "text/html"}
:body (str "<pre style='font-size:16px'>" (:explain data) "</pre>\n")}
{:status 400
:headers {"content-type" "text/html"}
:body (str "<pre style='font-size:16px'>" (:explain data) "</pre>\n")}))
:else
(do
(log/error :msg "Unexpected error"
:error error)
(js/console.error error)
{:status 500
:headers {"x-metadata" (t/encode {:type :unexpected
:message (ex-message error)})}
:body ""}))))
(defn- handle-response
[ctx {:keys [body headers status] :or {headers {} status 200}}]
(run! (fn [[k v]] (.set ^js ctx k v)) headers)
(set! (.-body ^js ctx) body)
(set! (.-status ^js ctx) status)
nil)
(defn- parse-headers
[ctx]
(let [orig (unchecked-get ctx "headers")]
(persistent!
(reduce #(assoc! %1 %2 (unchecked-get orig %2))
(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]
(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)
(handle-response ctx rsp))))
(p/catch (fn [err]
(->> (handle-error err request)
(handle-response ctx))))))))
(defn- router-handler
[router]
(fn [{:keys [ctx body] :as request}]
(let [route (match router ctx)
params (merge {}
(:query-params route)
(:path-params route)
(when (map? body) body))
request (assoc request
:route route
:params params)
handler (get-in route [:data :handler])]
(if (and route handler)
(handler request)
{:status 404
:body "Not found"}))))
(defn server
[handler]
(.createServer http @handler))
(defn handler
[router extra]
(let [instance (doto (new koa)
(.use (-> (router-handler router)
(wrap-handler extra))))]
(specify! instance
cljs.core/IDeref
(-deref [_]
(.callback instance)))))

1
exporter/test.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -15,6 +15,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce"
integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==
"@types/q@^1.5.1":
version "1.5.4"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
"@types/yauzl@^2.9.1":
version "2.9.1"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"
@ -35,11 +40,25 @@ agent-base@5:
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
any-promise@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
asn1.js@^4.0.0:
version "4.10.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
@ -91,6 +110,11 @@ bn.js@^5.1.1:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
boolbase@^1.0.0, boolbase@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@ -215,6 +239,15 @@ cache-content-type@^1.0.0:
mime-types "^2.1.18"
ylru "^1.2.0"
chalk@^2.4.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
@ -233,6 +266,27 @@ co@^4.6.0:
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
coa@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==
dependencies:
"@types/q" "^1.5.1"
chalk "^2.4.1"
q "^1.1.2"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -326,6 +380,49 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"
css-select-base-adapter@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
css-select@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef"
integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==
dependencies:
boolbase "^1.0.0"
css-what "^3.2.1"
domutils "^1.7.0"
nth-check "^1.0.2"
css-tree@1.0.0-alpha.37:
version "1.0.0-alpha.37"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==
dependencies:
mdn-data "2.0.4"
source-map "^0.6.1"
css-tree@1.0.0-alpha.39:
version "1.0.0-alpha.39"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb"
integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==
dependencies:
mdn-data "2.0.6"
source-map "^0.6.1"
css-what@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39"
integrity sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==
csso@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903"
integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==
dependencies:
css-tree "1.0.0-alpha.39"
debug@4, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
@ -345,6 +442,13 @@ deep-equal@~1.0.1:
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
dependencies:
object-keys "^1.0.12"
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@ -382,11 +486,37 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
dependencies:
domelementtype "^2.0.1"
entities "^2.0.0"
domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
domelementtype@1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
domelementtype@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d"
integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==
domutils@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
dependencies:
dom-serializer "0"
domelementtype "1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -417,11 +547,52 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
dependencies:
once "^1.4.0"
entities@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==
es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
version "1.17.6"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
is-callable "^1.2.0"
is-regex "^1.1.0"
object-inspect "^1.7.0"
object-keys "^1.1.1"
object.assign "^4.1.0"
string.prototype.trimend "^1.0.1"
string.prototype.trimstart "^1.0.1"
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
dependencies:
is-callable "^1.1.4"
is-date-object "^1.0.1"
is-symbol "^1.0.2"
escape-html@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
events@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59"
@ -468,6 +639,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
get-stream@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
@ -487,6 +663,23 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-symbols@^1.0.0, has-symbols@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
hash-base@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
@ -601,11 +794,35 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
is-callable@^1.1.4, is-callable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
is-date-object@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
is-generator-function@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522"
integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==
is-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff"
integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==
dependencies:
has-symbols "^1.0.1"
is-symbol@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
dependencies:
has-symbols "^1.0.1"
isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@ -616,6 +833,14 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
js-yaml@^3.13.1:
version "3.14.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
jszip@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6"
@ -698,6 +923,16 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
mdn-data@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
mdn-data@2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978"
integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@ -745,6 +980,11 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mitt@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/mitt/-/mitt-2.0.1.tgz#9e8a075b4daae82dd91aac155a0ece40ca7cb393"
@ -755,6 +995,13 @@ mkdirp-classic@^0.5.2:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@~0.5.1:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
minimist "^1.2.5"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -799,11 +1046,56 @@ node-libs-browser@^2.0.0:
util "^0.11.0"
vm-browserify "^1.0.1"
nth-check@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==
dependencies:
boolbase "~1.0.0"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
object-inspect@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object.assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
dependencies:
define-properties "^1.1.2"
function-bind "^1.1.1"
has-symbols "^1.0.0"
object-keys "^1.0.11"
object.getownpropertydescriptors@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
object.values@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e"
integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
function-bind "^1.1.1"
has "^1.0.3"
on-finished@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@ -950,6 +1242,11 @@ puppeteer@^4.0.1:
unbzip2-stream "^1.3.3"
ws "^7.2.3"
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
querystring-es3@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@ -1047,6 +1344,11 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2,
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
set-immediate-shim@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
@ -1080,10 +1382,10 @@ shadow-cljs-jar@1.3.2:
resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b"
integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==
shadow-cljs@^2.10.13:
version "2.10.13"
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.10.13.tgz#8e9f74901b51cd22169fc476eae048dba5a40c93"
integrity sha512-I3TJeh/hwJFwKruALnKrWVPnqbdou+tXQ++SWwOfN0CiYHcj4MMhyfBJfMsKPiOsOouRCCYTQVqsiJHIitK2/Q==
shadow-cljs@^2.10.17:
version "2.10.17"
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.10.17.tgz#490aea50f95481bb40936d79bade3aa62033dca3"
integrity sha512-yUz/hpaLz72IhI/lENYIGcwAytsUlie6/HX10ilVTjZDG9wn3Kc+DtNdgVSDpods79n7HHbDq7kImz7PUmBKnA==
dependencies:
node-libs-browser "^2.0.0"
readline-sync "^1.4.7"
@ -1112,11 +1414,21 @@ source-map@^0.5.6:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
source-map@^0.6.0:
source-map@^0.6.0, source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
stable@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
"statuses@>= 1.5.0 < 2", statuses@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
@ -1141,6 +1453,22 @@ stream-http@^2.7.2:
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
string.prototype.trimend@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"
string.prototype.trimstart@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"
string_decoder@^1.0.0, string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@ -1155,6 +1483,32 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
svgo@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"
integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==
dependencies:
chalk "^2.4.1"
coa "^2.0.2"
css-select "^2.0.0"
css-select-base-adapter "^0.1.1"
css-tree "1.0.0-alpha.37"
csso "^4.0.2"
js-yaml "^3.13.1"
mkdirp "~0.5.1"
object.values "^1.1.0"
sax "~1.2.4"
stable "^0.1.8"
unquote "~1.1.1"
util.promisify "~1.0.0"
tar-fs@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5"
@ -1234,6 +1588,11 @@ unpipe@1.0.0:
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
unquote@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@ -1247,6 +1606,16 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
util.promisify@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee"
integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.2"
has-symbols "^1.0.1"
object.getownpropertydescriptors "^2.1.0"
util@0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
@ -1297,6 +1666,13 @@ ws@^7.2.3:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd"
integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==
xml-js@^1.6.11:
version "1.6.11"
resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
dependencies:
sax "^1.2.4"
xregexp@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"

View File

@ -112,7 +112,7 @@
ret)))]
(js->clj result :keywordize-keys true)))))
(defn- nodes-seq
(defn nodes-seq
[match? node]
(->> (tree-seq map? :children node)
(filter match?)))

View File

@ -66,6 +66,7 @@
(let [childs (mapv #(get objects %) (:shapes shape))]
[:& group-shape {:frame frame
:shape shape
:is-child-selected? true
:childs childs}]))))
(defn shape-wrapper-factory

View File

@ -108,10 +108,10 @@
(defn is-child-selected?
[id]
(letfn [(selector [state]
(let [page-id (get-in state [:workspace-page :id])
objects (get-in state [:workspace-data page-id :objects])
(let [page-id (get-in state [:workspace-page :id])
objects (get-in state [:workspace-data page-id :objects])
selected (get-in state [:workspace-local :selected])
shape (get objects id)
shape (get objects id)
children (cph/get-children id objects)]
(some selected children)))]
(l/derived selector st/state)))

View File

@ -34,7 +34,9 @@
:id (str "shape-" id)
:width width
:height height}))]
[:svg {:x x :y y :width width :height height}
[:svg {:x x :y y :width width :height height
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
[:> "rect" props]
(for [[i item] (d/enumerate childs)]
[:& shape-wrapper {:frame shape

View File

@ -30,7 +30,6 @@
[:& shape-wrapper {:frame frame
:shape item
:key (:id item)}])
(when (not is-child-selected?)
[:rect {:transform transform
:x x

View File

@ -94,7 +94,11 @@
(case type
"root"
(let [style (generate-root-styles (clj->js node))]
[:div.root.rich-text {:key index :style style} children])
[:div.root.rich-text
{:key index
:style style
:xmlns "http://www.w3.org/1999/xhtml"}
children])
"paragraph-set"
(let [style #js {:display "inline-block"
@ -114,6 +118,13 @@
(let [root (obj/get props "content")]
(render-text-node root)))
(defn- retrieve-colors
[shape]
(let [colors (into #{} (comp (map :fill)
(filter string?))
(tree-seq map? :children (:content shape)))]
(apply str (interpose "," colors))))
(mf/defc text-shape
{::mf/wrap-props false}
[props]
@ -122,6 +133,7 @@
{:keys [id x y width height rotation content]} shape]
[:foreignObject {:x x
:y y
:data-colors (retrieve-colors shape)
:transform (geom/transform-matrix shape)
:id (str id)
:width width

View File

@ -39,18 +39,21 @@
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
on-mouse-down (mf/use-callback (mf/deps shape)
#(common/on-mouse-down % shape))
on-context-menu (mf/use-callback (mf/deps shape)
#(common/on-context-menu % shape))
childs-ref (mf/use-memo (mf/deps shape)
#(refs/objects-by-id (:shapes shape)))
on-mouse-down
(mf/use-callback (mf/deps shape) #(common/on-mouse-down % shape))
on-context-menu
(mf/use-callback (mf/deps shape) #(common/on-context-menu % shape))
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
childs (mf/deref childs-ref)
is-child-selected-ref (mf/use-memo (mf/deps (:id shape))
#(refs/is-child-selected? (:id shape)))
is-child-selected? (mf/deref is-child-selected-ref)
is-child-selected-ref
(mf/use-memo (mf/deps (:id shape)) #(refs/is-child-selected? (:id shape)))
is-child-selected?
(mf/deref is-child-selected-ref)
on-double-click
(mf/use-callback

View File

@ -25,7 +25,7 @@
(defn- request-export
[shape exports]
(http/send! {:method :post
:uri "/export/bitmap"
:uri "/export"
:response-type :blob
:auth true
:body {:page-id (:page-id shape)
@ -140,7 +140,8 @@
[:select.input-select {:value (name (:type export))
:on-change (partial on-type-change index)}
[:option {:value "png"} "PNG"]
[:option {:value "jpeg"} "JPEG"]]
[:option {:value "jpeg"} "JPEG"]
[:option {:value "svg"} "SVG"]]
[:div.delete-icon {:on-click (partial delete-export index)}
i/minus]])

View File

@ -1,5 +1,8 @@
How to trace a bitmap to svg in multiple colors:
Dependencies: imagemagick, netpbm, potrace
Generate the PPM format from PNG:
convert download.png download.ppm