From 264811c5ee29210231e3f900d86ba5a1b4af21fd Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 2 Dec 2020 15:37:11 +0100 Subject: [PATCH] :bug: Fixes problems with the picker for Safari and Firefox --- .../resources/polyfills/createImageBitmap.js | 33 +++ frontend/src/app/main/data/workspace.cljs | 3 +- frontend/src/app/main/ui/confirm.cljs | 2 +- frontend/src/app/main/ui/modal.cljs | 2 +- .../workspace/colorpicker/pixel_overlay.cljs | 212 ++++++++++-------- .../workspace/colorpicker/pixel_picker.cljs | 29 --- .../src/app/main/ui/workspace/viewport.cljs | 4 +- 7 files changed, 158 insertions(+), 127 deletions(-) create mode 100644 frontend/resources/polyfills/createImageBitmap.js delete mode 100644 frontend/src/app/main/ui/workspace/colorpicker/pixel_picker.cljs diff --git a/frontend/resources/polyfills/createImageBitmap.js b/frontend/resources/polyfills/createImageBitmap.js new file mode 100644 index 0000000000..0ddf17c321 --- /dev/null +++ b/frontend/resources/polyfills/createImageBitmap.js @@ -0,0 +1,33 @@ +/* + * Safari and Edge polyfill for createImageBitmap + * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap + * + * Support source image types Blob and ImageData. + * + * From: https://dev.to/nektro/createimagebitmap-polyfill-for-safari-and-edge-228 + * Updated by Yoan Tournade + */ +if (!('createImageBitmap' in window)) { + window.createImageBitmap = async function (data) { + return new Promise((resolve,reject) => { + let dataURL; + if (data instanceof Blob) { + dataURL = URL.createObjectURL(data); + } else if (data instanceof ImageData) { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + canvas.width = data.width; + canvas.height = data.height; + ctx.putImageData(data,0,0); + dataURL = canvas.toDataURL(); + } else { + throw new Error('createImageBitmap does not handle the provided image source type'); + } + const img = document.createElement('img'); + img.addEventListener('load',function () { + resolve(this); + }); + img.src = dataURL; + }); + }; +} diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index bd4251ffe7..31ec93f3f7 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1621,8 +1621,7 @@ (let [edition-id (get-in state [:workspace-local :edition]) path-edit-mode (get-in state [:workspace-local :edit-path edition-id :edit-mode])] (if-not (= :draw path-edit-mode) - (rx/of :interrupt - (deselect-all true)) + (rx/of :interrupt (deselect-all true)) (rx/empty)))))) (defn c-mod diff --git a/frontend/src/app/main/ui/confirm.cljs b/frontend/src/app/main/ui/confirm.cljs index d791de0f4f..73bf41bcc0 100644 --- a/frontend/src/app/main/ui/confirm.cljs +++ b/frontend/src/app/main/ui/confirm.cljs @@ -64,7 +64,7 @@ (dom/stop-propagation event) (st/emit! (modal/hide)) (on-accept props)))) - key (events/listen (dom/get-root) EventType.KEYDOWN on-keydown)] + key (events/listen js/document EventType.KEYDOWN on-keydown)] #(events/unlistenByKey key)))) [:div.modal-overlay diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index 908ebced87..07d3849a6f 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -76,7 +76,7 @@ (mf/deps allow-click-outside) (fn [] (let [keys [(events/listen js/window EventType.POPSTATE on-pop-state) - (events/listen (dom/get-root) EventType.KEYDOWN handle-keydown) + (events/listen js/document EventType.KEYDOWN handle-keydown) (events/listen (dom/get-root) EventType.CLICK handle-click-outside)]] #(doseq [key keys] (events/unlistenByKey key))))) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs index 3599660a05..84a9a5dbf4 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs @@ -54,106 +54,124 @@ [:& shape-wrapper {:shape item :key (:id item)}]))]])) -(defn draw-picker-canvas [svg-node canvas-node] - (let [canvas-context (.getContext canvas-node "2d") - xml (.serializeToString (js/XMLSerializer.) svg-node) - img-src (str "data:image/svg+xml;base64," - (-> xml js/encodeURIComponent js/unescape js/btoa)) - img (js/Image.) - - on-error (fn [err] (.error js/console "ERROR" err)) - on-load (fn [] (.drawImage canvas-context img 0 0))] - (.addEventListener img "error" on-error) - (.addEventListener img "load" on-load) - (obj/set! img "src" img-src))) - (mf/defc pixel-overlay {::mf/wrap-props false} [props] - (let [vport (unchecked-get props "vport") - vbox (unchecked-get props "vbox") + (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)) + options (unchecked-get props "options") + svg-ref (mf/use-ref nil) + canvas-ref (mf/use-ref nil) + img-ref (mf/use-ref nil) - update-canvas-stream (rx/subject) + update-str (rx/subject) handle-keydown - (fn [event] - (when (and (kbd/esc? event)) - (do (dom/stop-propagation event) - (dom/prevent-default event) - (st/emit! (dwc/stop-picker)) - (modal/disallow-click-outside!)))) + (mf/use-callback + (fn [event] + (when (and (kbd/esc? event)) + (do (dom/stop-propagation event) + (dom/prevent-default event) + (st/emit! (dwc/stop-picker)) + (modal/disallow-click-outside!))))) - 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) + handle-mouse-move-picker + (mf/use-callback + (mf/deps viewport-ref) + (fn [event] + (when-let [zoom-view-node (.getElementById js/document "picker-detail")] + (let [viewport-node (mf/ref-val viewport-ref) + canvas-node (mf/ref-val canvas-ref) - 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) + {brx :left bry :top} (dom/get-bounding-rect viewport-node) + x (- (.-clientX event) brx) + y (- (.-clientY event) bry) - area-data (.getImageData canvas-context (- x 25) (- y 20) 50 40)] + zoom-context (.getContext zoom-view-node "2d") + 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])))))) - (-> (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]))))) + handle-mouse-down-picker + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dwc/pick-color-select true (kbd/shift? event))))) - on-mouse-down-picker - (fn [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (st/emit! (dwc/pick-color-select true (kbd/shift? event)))) + handle-mouse-up-picker + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dwc/stop-picker)) + (modal/disallow-click-outside!))) - on-mouse-up-picker - (fn [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (st/emit! (dwc/stop-picker)) - (modal/disallow-click-outside!))] + handle-image-load + (mf/use-callback + (mf/deps img-ref) + (fn [] + (let [canvas-node (mf/ref-val canvas-ref) + img-node (mf/ref-val img-ref) + canvas-context (.getContext canvas-node "2d")] + (.drawImage canvas-context img-node 0 0)))) + + handle-draw-picker-canvas + (mf/use-callback + (mf/deps img-ref) + (fn [] + (let [img-node (mf/ref-val img-ref) + svg-node (mf/ref-val svg-ref) + xml (-> (js/XMLSerializer.) + (.serializeToString svg-node) + js/encodeURIComponent + js/unescape + js/btoa) + img-src (str "data:image/svg+xml;base64," xml)] + (obj/set! img-node "src" img-src)))) + + handle-svg-change + (mf/use-callback + (fn [] + (rx/push! update-str :update)))] (mf/use-effect (fn [] - (let [listener (events/listen (dom/get-root) EventType.KEYDOWN handle-keydown)] + (let [listener (events/listen js/document EventType.KEYDOWN handle-keydown)] #(events/unlistenByKey listener)))) (mf/use-effect (fn [] - (let [sub (->> update-canvas-stream + (let [sub (->> update-str (rx/debounce 10) - (rx/subs #(draw-picker-canvas (mf/ref-val svg-ref) - (mf/ref-val canvas-ref))))] - + (rx/subs handle-draw-picker-canvas))] #(rx/dispose! sub)))) (mf/use-effect - (mf/deps svg-ref canvas-ref) + (mf/deps svg-ref) (fn [] - (when (and svg-ref canvas-ref) - - (let [config (clj->js {:attributes true - :childList true - :subtree true - :characterData true}) - on-svg-change (fn [mutation-list] (rx/push! update-canvas-stream :update)) - observer (js/MutationObserver. on-svg-change)] - - (.observe observer (mf/ref-val svg-ref) config) + (when svg-ref + (let [config #js {:attributes true + :childList true + :subtree true + :characterData true} + svg-node (mf/ref-val svg-ref) + observer (js/MutationObserver. handle-svg-change)] + (.observe observer svg-node config) + (handle-svg-change) ;; Disconnect on unmount #(.disconnect observer))))) @@ -167,21 +185,31 @@ :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"}}] + :on-mouse-down handle-mouse-down-picker + :on-mouse-up handle-mouse-up-picker + :on-mouse-move handle-mouse-move-picker} + [:div {:style {:display "none"}} + [:img {:ref img-ref + :on-load handle-image-load + :style {:position "absolute" + :width "100%" + :height "100%"}}] + [:canvas {:ref canvas-ref + :width (:width vport 0) + :height (:height vport 0) + :style {:position "absolute" + :width "100%" + :height "100%"}}] - [:& (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")}} - [:& overlay-frames]]]])) + [:& (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 {:position "absolute" + :width "100%" + :height "100%" + :background-color (get options :background "#E8E9EA")}} + [:& overlay-frames]]]]]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/pixel_picker.cljs b/frontend/src/app/main/ui/workspace/colorpicker/pixel_picker.cljs deleted file mode 100644 index 21a35a0fc5..0000000000 --- a/frontend/src/app/main/ui/workspace/colorpicker/pixel_picker.cljs +++ /dev/null @@ -1,29 +0,0 @@ -;; 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.workspace.colorpicker.pixel-picker - (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [cuerdas.core :as str] - [app.common.geom.point :as gpt] - [app.common.math :as math] - [app.common.uuid :refer [uuid]] - [app.util.dom :as dom] - [app.util.color :as uc] - [app.util.object :as obj] - [app.main.store :as st] - [app.main.refs :as refs] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] - [app.main.data.modal :as modal] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]])) - - diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a6886f9144..21173e1c94 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -490,8 +490,8 @@ (let [node (mf/ref-val viewport-ref) prnt (dom/get-parent node) - keys [(events/listen (dom/get-root) EventType.KEYDOWN on-key-down) - (events/listen (dom/get-root) EventType.KEYUP on-key-up) + keys [(events/listen js/document EventType.KEYDOWN on-key-down) + (events/listen js/document EventType.KEYUP on-key-up) (events/listen node EventType.MOUSEMOVE on-mouse-move) ;; bind with passive=false to allow the event to be cancelled ;; https://stackoverflow.com/a/57582286/3219895