From e737ec0311bc66835c5368e3ffa585d8ca3b9b06 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 10 Sep 2020 16:24:59 +0200 Subject: [PATCH] :tada: Refactor, performance improvements --- common/app/common/uuid_impl.js | 4 +- frontend/src/app/main/data/fetch.cljs | 29 ++- frontend/src/app/main/ui/context.cljs | 14 ++ frontend/src/app/main/ui/shapes/image.cljs | 16 +- frontend/src/app/main/ui/shapes/text.cljs | 12 +- .../app/main/ui/workspace/colorpicker.cljs | 1 - .../src/app/main/ui/workspace/viewport.cljs | 187 +++++++++++------- 7 files changed, 165 insertions(+), 98 deletions(-) create mode 100644 frontend/src/app/main/ui/context.cljs diff --git a/common/app/common/uuid_impl.js b/common/app/common/uuid_impl.js index 8a26fb5a96..951557932d 100644 --- a/common/app/common/uuid_impl.js +++ b/common/app/common/uuid_impl.js @@ -18,12 +18,12 @@ goog.scope(function() { const self = app.common.uuid_impl; const fill = (() => { - if (typeof window === "object") { + if (typeof window === "object" && typeof window.crypto !== "undefined") { return (buf) => { window.crypto.getRandomValues(buf); return buf; }; - } else if (typeof self === "object") { + } else if (typeof self === "object" && typeof self.crypto !== "undefined") { return (buf) => { self.crypto.getRandomValues(buf); return buf; diff --git a/frontend/src/app/main/data/fetch.cljs b/frontend/src/app/main/data/fetch.cljs index 6680e06cfc..947ad1391e 100644 --- a/frontend/src/app/main/data/fetch.cljs +++ b/frontend/src/app/main/data/fetch.cljs @@ -10,13 +10,26 @@ (ns app.main.data.fetch (:require [promesa.core :as p] - [app.util.object :as obj])) + [potok.core :as ptk] + [okulary.core :as l] + [app.util.object :as obj] + [app.main.store :as st])) + +(defn pending-ref [] + (l/derived ::to-fetch st/state)) + +(defn add [to-fetch id] + (let [to-fetch (or to-fetch (hash-set))] + (conj to-fetch id))) (defn fetch-as-data-uri [url] - (-> (js/fetch url) - (p/then (fn [res] (.blob res))) - (p/then (fn [blob] - (let [reader (js/FileReader.)] - (p/create (fn [resolve reject] - (obj/set! reader "onload" #(resolve [url (.-result reader)])) - (.readAsDataURL reader blob)))))))) + (let [id (random-uuid)] + (st/emit! (fn [state] (update state ::to-fetch add id))) + (-> (js/fetch url) + (p/then (fn [res] (.blob res))) + (p/then (fn [blob] + (let [reader (js/FileReader.)] + (p/create (fn [resolve reject] + (obj/set! reader "onload" #(resolve [url (.-result reader)])) + (.readAsDataURL reader blob)))))) + (p/finally #(st/emit! (fn [state] (update state ::to-fetch disj id))))))) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs new file mode 100644 index 0000000000..9b56bc217e --- /dev/null +++ b/frontend/src/app/main/ui/context.cljs @@ -0,0 +1,14 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.context + (:require + [rumext.alpha :as mf])) + +(def embed-ctx (mf/create-context false)) diff --git a/frontend/src/app/main/ui/shapes/image.cljs b/frontend/src/app/main/ui/shapes/image.cljs index fc7ff7cb93..4fbe7d6ed3 100644 --- a/frontend/src/app/main/ui/shapes/image.cljs +++ b/frontend/src/app/main/ui/shapes/image.cljs @@ -14,6 +14,7 @@ [app.common.geom.shapes :as geom] [app.main.ui.shapes.attrs :as attrs] [app.util.object :as obj] + [app.main.ui.context :as muc] [app.main.data.fetch :as df] [promesa.core :as p])) @@ -24,13 +25,15 @@ (let [shape (unchecked-get props "shape") {:keys [id x y width height rotation metadata]} shape uri (cfg/resolve-media-path (:path metadata)) - data-uri (mf/use-state nil)] + embed-resources? (mf/use-ctx muc/embed-ctx) + data-uri (mf/use-state (when (not embed-resources?) uri))] (mf/use-effect - (mf/deps shape) + (mf/deps uri) (fn [] - (-> (df/fetch-as-data-uri uri) - (p/then #(reset! data-uri (second %)))))) + (if embed-resources? + (-> (df/fetch-as-data-uri uri) + (p/then #(reset! data-uri (second %))))))) (let [transform (geom/transform-matrix shape) props (-> (attrs/extract-style-attrs shape) @@ -49,7 +52,4 @@ :stroke "#000000"})] [:> "image" (obj/merge! props - #js {:xlinkHref @data-uri})])) - - - )) + #js {:href @data-uri})])))) diff --git a/frontend/src/app/main/ui/shapes/text.cljs b/frontend/src/app/main/ui/shapes/text.cljs index ab267a127f..071f207194 100644 --- a/frontend/src/app/main/ui/shapes/text.cljs +++ b/frontend/src/app/main/ui/shapes/text.cljs @@ -6,16 +6,17 @@ (ns app.main.ui.shapes.text (:require + [clojure.set :as set] [promesa.core :as p] [cuerdas.core :as str] [rumext.alpha :as mf] [app.main.data.fetch :as df] + [app.main.fonts :as fonts] + [app.main.ui.context :as muc] [app.common.data :as d] [app.common.geom.shapes :as geom] [app.common.geom.matrix :as gmt] - [app.main.fonts :as fonts] - [app.util.object :as obj] - [clojure.set :as set])) + [app.util.object :as obj])) ;; --- Text Editor Rendering @@ -114,11 +115,12 @@ ([node] (render-text-node 0 node)) ([index {:keys [type text children] :as node}] (mf/html - (let [embeded-fonts (mf/use-state nil)] + (let [embed-resources? (mf/use-ctx muc/embed-ctx) + embeded-fonts (mf/use-state nil)] (mf/use-effect (mf/deps node) (fn [] - (when (= type "root") + (when (and embed-resources? (= type "root")) (let [font-to-embed (get-all-fonts node) embeded (map embed-font font-to-embed)] (-> (p/all embeded) diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index a331731f2a..c0e205921a 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -19,7 +19,6 @@ [app.common.uuid :refer [uuid]] [app.main.data.workspace.libraries :as dwl] [app.main.data.colors :as dwc] - #_[app.main.ui.modal :as modal] [app.main.ui.modal :as modal] [okulary.core :as l] [app.main.refs :as refs] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 16bec9417b..a8a739019c 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -24,6 +24,7 @@ [app.main.data.workspace :as dw] [app.main.data.workspace.drawing :as dd] [app.main.data.colors :as dwc] + [app.main.data.fetch :as mdf] [app.main.refs :as refs] [app.main.store :as st] [app.main.streams :as ms] @@ -42,10 +43,12 @@ [app.util.dom :as dom] [app.util.dom.dnd :as dnd] [app.util.object :as obj] + [app.main.ui.context :as muc] [app.common.geom.shapes :as gsh] [app.common.geom.point :as gpt] [app.util.perf :as perf] - [app.common.uuid :as uuid]) + [app.common.uuid :as uuid] + [app.util.timers :as timers]) (:import goog.events.EventType)) ;; --- Coordinates Widget @@ -161,6 +164,108 @@ :selected selected :hover hover}]])) +(defn format-viewbox [vbox] + (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) + (:y vbox 0) + (:width vbox 0) + (:height vbox 0)])) + +(mf/defc pixel-picker-overlay + {::mf/wrap-props false} + [props] + (let [vport (unchecked-get props "vport") + vbox (unchecked-get props "vbox") + viewport-ref (unchecked-get props "viewport-ref") + options (unchecked-get props "options") + svg-ref (mf/use-ref nil) + canvas-ref (mf/use-ref nil) + fetch-pending (mf/deref (mdf/pending-ref)) + + on-mouse-move-picker + (fn [event] + (when-let [zoom-view-node (.getElementById js/document "picker-detail")] + (let [{brx :left bry :top} (dom/get-bounding-rect (mf/ref-val viewport-ref)) + x (- (.-clientX event) brx) + y (- (.-clientY event) bry) + + zoom-context (.getContext zoom-view-node "2d") + canvas-node (mf/ref-val canvas-ref) + canvas-context (.getContext canvas-node "2d") + pixel-data (.getImageData canvas-context x y 1 1) + rgba (.-data pixel-data) + r (obj/get rgba 0) + g (obj/get rgba 1) + b (obj/get rgba 2) + a (obj/get rgba 3) + + area-data (.getImageData canvas-context (- x 25) (- y 20) 50 40)] + + (-> (js/createImageBitmap area-data) + (p/then (fn [image] + ;; Draw area + (obj/set! zoom-context "imageSmoothingEnabled" false) + (.drawImage zoom-context image 0 0 200 160)))) + (st/emit! (dwc/pick-color [r g b a]))))) + + on-mouse-down-picker + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dwc/pick-color-select true (kbd/shift? event)))) + + on-mouse-up-picker + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dwc/stop-picker)) + (modal/disallow-click-outside!))] + + (mf/use-effect + ;; Everytime we finish retrieving a new URL we redraw the canvas + ;; so even if we're not finished the user can start to pick basic + ;; shapes + (mf/deps fetch-pending) + (fn [] + (try + (timers/raf + #(let [svg-node (mf/ref-val svg-ref) + canvas-node (mf/ref-val canvas-ref) + canvas-context (.getContext canvas-node "2d") + xml (.serializeToString (js/XMLSerializer.) svg-node) + content (str "data:image/svg+xml;base64," (js/btoa xml)) + img (js/Image.)] + (obj/set! img "onload" + (fn [] + (.drawImage canvas-context img 0 0))) + (obj/set! img "src" content))) + (catch :default e (.error js/console e))))) + + [:* + [:div.overlay + {:style {:position "absolute" + :top 0 + :left 0 + :width "100%" + :height "100%" + :cursor cur/picker} + :on-mouse-down on-mouse-down-picker + :on-mouse-up on-mouse-up-picker + :on-mouse-move on-mouse-move-picker}] + [:canvas {:ref canvas-ref + :width (:width vport 0) + :height (:height vport 0) + :style {:display "none"}}] + [:& (mf/provider muc/embed-ctx) {:value true} + [:svg.viewport + {:ref svg-ref + :preserveAspectRatio "xMidYMid meet" + :width (:width vport 0) + :height (:height vport 0) + :view-box (format-viewbox vbox) + :style {:display "none" + :background-color (get options :background "#E8E9EA")}} + [:& frames]]]])) + (mf/defc viewport [{:keys [page-id page local layout] :as props}] (let [{:keys [options-mode @@ -176,7 +281,6 @@ file (mf/deref refs/workspace-file) viewport-ref (mf/use-ref nil) - canvas-ref (mf/use-ref nil) zoom-view-ref (mf/use-ref nil) last-position (mf/use-var nil) drawing (mf/deref refs/workspace-drawing) @@ -418,46 +522,7 @@ prnt (dom/get-parent node)] (st/emit! (dw/update-viewport-size (dom/get-client-size prnt))))) - options (mf/deref refs/workspace-page-options) - - on-mouse-move-picker - (fn [event] - (when-let [zoom-view-node (.getElementById js/document "picker-detail")] - (let [{brx :left bry :top} (dom/get-bounding-rect (mf/ref-val viewport-ref)) - x (- (.-clientX event) brx) - y (- (.-clientY event) bry) - - zoom-context (.getContext zoom-view-node "2d") - canvas-node (mf/ref-val canvas-ref) - canvas-context (.getContext canvas-node "2d") - pixel-data (.getImageData canvas-context x y 1 1) - rgba (.-data pixel-data) - r (obj/get rgba 0) - g (obj/get rgba 1) - b (obj/get rgba 2) - a (obj/get rgba 3) - - area-data (.getImageData canvas-context (- x 25) (- y 20) 50 40)] - - (-> (js/createImageBitmap area-data) - (p/then (fn [image] - ;; Draw area - (obj/set! zoom-context "imageSmoothingEnabled" false) - (.drawImage zoom-context image 0 0 200 160)))) - (st/emit! (dwc/pick-color [r g b a]))))) - - on-mouse-down-picker - (fn [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (st/emit! (dwc/pick-color-select true (kbd/shift? event)))) - - on-mouse-up-picker - (fn [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (st/emit! (dwc/stop-picker)) - (modal/disallow-click-outside!))] + options (mf/deref refs/workspace-page-options)] (mf/use-layout-effect (fn [] @@ -481,44 +546,18 @@ (mf/use-layout-effect (mf/deps layout) on-resize) - (mf/use-effect - (mf/deps props) - (fn [] - (when picking-color? - (try - (let [svg-node (mf/ref-val viewport-ref) - canvas-node (mf/ref-val canvas-ref) - canvas-context (.getContext canvas-node "2d") - xml (.serializeToString (js/XMLSerializer.) svg-node) - content (str "data:image/svg+xml;base64," (js/btoa xml)) - img (js/Image.)] - (obj/set! img "onload" - (fn [] - (.drawImage canvas-context img 0 0))) - (obj/set! img "src" content)) - (catch :default e (.error js/console e)))))) - [:* - (when picking-color? - [:canvas {:ref canvas-ref - :width (:width vport 0) - :height (:height vport 0) - :on-mouse-down on-mouse-down-picker - :on-mouse-up on-mouse-up-picker - :on-mouse-move on-mouse-move-picker - :style {:position "absolute" - :top 0 - :left 0 - :cursor cur/picker}}]) + [:& pixel-picker-overlay {:vport vport + :vbox vbox + :viewport-ref viewport-ref + :options options}]) + [:svg.viewport {:preserveAspectRatio "xMidYMid meet" :width (:width vport 0) :height (:height vport 0) - :view-box (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) - (:y vbox 0) - (:width vbox 0) - (:height vbox 0)]) + :view-box (format-viewbox vbox) :ref viewport-ref :class (when drawing-tool "drawing") :style {:cursor (cond