diff --git a/exporter/package.json b/exporter/package.json index f0f99731a..444edee0c 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -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" } } diff --git a/exporter/src/app/browser.cljs b/exporter/src/app/browser.cljs index a0b572613..0ba877fa7 100644 --- a/exporter/src/app/browser.cljs +++ b/exporter/src/app/browser.cljs @@ -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 diff --git a/exporter/src/app/http.cljs b/exporter/src/app/http.cljs index 1b78e21e1..eb9ecca37 100644 --- a/exporter/src/app/http.cljs +++ b/exporter/src/app/http.cljs @@ -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 "
" (:explain data) "
\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)))))) - diff --git a/exporter/src/app/http/bitmap_export.cljs b/exporter/src/app/http/export.cljs similarity index 50% rename from exporter/src/app/http/bitmap_export.cljs rename to exporter/src/app/http/export.cljs index fe2b9065c..6e1f89430 100644 --- a/exporter/src/app/http/bitmap_export.cljs +++ b/exporter/src/app/http/export.cljs @@ -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")})) - diff --git a/exporter/src/app/http/export_bitmap.cljs b/exporter/src/app/http/export_bitmap.cljs new file mode 100644 index 000000000..87c4e5500 --- /dev/null +++ b/exporter/src/app/http/export_bitmap.cljs @@ -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")})) + diff --git a/exporter/src/app/http/export_svg.cljs b/exporter/src/app/http/export_svg.cljs new file mode 100644 index 000000000..c9ba05873 --- /dev/null +++ b/exporter/src/app/http/export_svg.cljs @@ -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"})) diff --git a/exporter/src/app/http/impl.cljs b/exporter/src/app/http/impl.cljs new file mode 100644 index 000000000..c04c461a7 --- /dev/null +++ b/exporter/src/app/http/impl.cljs @@ -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 "
" (:explain data) "
\n")} + {:status 400 + :headers {"content-type" "text/html"} + :body (str "
" (:explain data) "
\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))))) + diff --git a/exporter/test.svg b/exporter/test.svg new file mode 100644 index 000000000..36a04aa36 --- /dev/null +++ b/exporter/test.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/exporter/yarn.lock b/exporter/yarn.lock index 3c912013a..05b49ff8c 100644 --- a/exporter/yarn.lock +++ b/exporter/yarn.lock @@ -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" diff --git a/frontend/src/uxbox/main/data/workspace/texts.cljs b/frontend/src/uxbox/main/data/workspace/texts.cljs index 9be809d4f..2c00e331f 100644 --- a/frontend/src/uxbox/main/data/workspace/texts.cljs +++ b/frontend/src/uxbox/main/data/workspace/texts.cljs @@ -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?))) diff --git a/frontend/src/uxbox/main/exports.cljs b/frontend/src/uxbox/main/exports.cljs index a015113de..7a0bcdec1 100644 --- a/frontend/src/uxbox/main/exports.cljs +++ b/frontend/src/uxbox/main/exports.cljs @@ -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 diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index 1d9248317..23a9394f2 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -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))) diff --git a/frontend/src/uxbox/main/ui/shapes/frame.cljs b/frontend/src/uxbox/main/ui/shapes/frame.cljs index c73ac4eff..3225bcf70 100644 --- a/frontend/src/uxbox/main/ui/shapes/frame.cljs +++ b/frontend/src/uxbox/main/ui/shapes/frame.cljs @@ -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 diff --git a/frontend/src/uxbox/main/ui/shapes/group.cljs b/frontend/src/uxbox/main/ui/shapes/group.cljs index 5a2da89f5..4526b0b56 100644 --- a/frontend/src/uxbox/main/ui/shapes/group.cljs +++ b/frontend/src/uxbox/main/ui/shapes/group.cljs @@ -30,7 +30,6 @@ [:& shape-wrapper {:frame frame :shape item :key (:id item)}]) - (when (not is-child-selected?) [:rect {:transform transform :x x diff --git a/frontend/src/uxbox/main/ui/shapes/text.cljs b/frontend/src/uxbox/main/ui/shapes/text.cljs index 8996eb6f3..1ffac8086 100644 --- a/frontend/src/uxbox/main/ui/shapes/text.cljs +++ b/frontend/src/uxbox/main/ui/shapes/text.cljs @@ -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 diff --git a/frontend/src/uxbox/main/ui/workspace/shapes/group.cljs b/frontend/src/uxbox/main/ui/workspace/shapes/group.cljs index b53cd912b..2a777833c 100644 --- a/frontend/src/uxbox/main/ui/workspace/shapes/group.cljs +++ b/frontend/src/uxbox/main/ui/workspace/shapes/group.cljs @@ -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 diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/exports.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/exports.cljs index 30fa969e1..f5ca72a1c 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/exports.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/exports.cljs @@ -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]]) diff --git a/tracebitmap.txt b/tracebitmap.txt index eec9ad5f2..a14ad9242 100644 --- a/tracebitmap.txt +++ b/tracebitmap.txt @@ -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