diff --git a/frontend/resources/images/cursors/picker.svg b/frontend/resources/images/cursors/picker.svg new file mode 100644 index 000000000..13daf5423 --- /dev/null +++ b/frontend/resources/images/cursors/picker.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index 8a3bd2443..635cc19c9 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -1743,19 +1743,19 @@ } }, "workspace.libraries.colors.file-library" : { - "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:277", "src/app/main/ui/workspace/colorpalette.cljs:149" ], + "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:314", "src/app/main/ui/workspace/colorpalette.cljs:149" ], "translations" : { "en" : "File library" } }, "workspace.libraries.colors.recent-colors" : { - "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:276", "src/app/main/ui/workspace/colorpalette.cljs:159" ], + "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:313", "src/app/main/ui/workspace/colorpalette.cljs:159" ], "translations" : { "en" : "Recent colors" } }, "workspace.libraries.colors.save-color" : { - "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:312" ], + "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:349" ], "translations" : { "en" : "Save color" } diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss index 19047e241..2ff6badc4 100644 --- a/frontend/resources/styles/main/partials/colorpicker.scss +++ b/frontend/resources/styles/main/partials/colorpicker.scss @@ -8,11 +8,54 @@ .colorpicker { display: flex; flex-direction: column; - width: 13rem; padding: 0.5rem; background-color: $color-white; box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + & > * { + width: 200px; + } + + .top-actions { + display: flex; + margin-bottom: 0.25rem; + + .picker-btn { + background: none; + border: none; + cursor: pointer; + + &.active, + &:hover svg { + fill: $color-primary; + } + + svg { + width: 14px; + height: 14px; + } + } + } + + .picker-detail-wrapper { + position: relative; + + .center-circle { + width: 14px; + height: 14px; + border: 2px solid $color-white; + border-radius: 8px; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-7px, -7px); + filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); + } + } + #picker-detail { + border: 1px solid $color-gray-10; + } + .handler { position: absolute; width: 12px; @@ -25,7 +68,6 @@ background-color: rgba(var(--hue)); position: relative; height: 6.75rem; - width: 100%; cursor: pointer; .handler { @@ -137,6 +179,7 @@ border-top: 1px solid $color-gray-10; padding-top: 0.5rem; margin-top: 0.25rem; + width: 200px; select { background-image: url(/images/icons/arrow-down.svg); @@ -162,7 +205,8 @@ grid-template-columns: repeat(8, 1fr); justify-content: space-between; margin-right: -8px; - overflow: scroll; + overflow-x: hidden; + overflow-y: auto; max-height: 5.5rem; } diff --git a/frontend/src/app/main/data/colors.cljs b/frontend/src/app/main/data/colors.cljs index 1a9522ab7..a94efbe54 100644 --- a/frontend/src/app/main/data/colors.cljs +++ b/frontend/src/app/main/data/colors.cljs @@ -129,3 +129,31 @@ (-> state (update :workspace-layout conj :colorpalette) (assoc-in [:workspace-local :selected-palette] selected))))) + +(defn start-picker [] + (ptk/reify ::start-picker + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :picking-color?] true))))) + +(defn stop-picker [] + (ptk/reify ::stop-picker + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :picking-color?] false))))) + +(defn pick-color [rgba] + (ptk/reify ::pick-color + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :picked-color] rgba))))) + +(defn pick-color-select [value] + (ptk/reify ::pick-color + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :picked-color-select] value))))) diff --git a/frontend/src/app/main/data/fetch.cljs b/frontend/src/app/main/data/fetch.cljs new file mode 100644 index 000000000..6680e06cf --- /dev/null +++ b/frontend/src/app/main/data/fetch.cljs @@ -0,0 +1,22 @@ +;; 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.data.fetch + (:require + [promesa.core :as p] + [app.util.object :as obj])) + +(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)))))))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 9812f2524..fb1acc624 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -102,7 +102,10 @@ :right-sidebar? true :color-for-rename nil :selected-palette :recent - :selected-palette-size :big}) + :selected-palette-size :big + :picking-color? false + :picked-color nil + :picked-color-select false}) (def initialize-layout (ptk/reify ::initialize-layout diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index 30f9a9b40..11d555b40 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -114,6 +114,11 @@ (unchecked-set node "type" "text/css") node)) +(defn gfont-url [family variants] + (let [base (str "https://fonts.googleapis.com/css?family=" family) + variants (str/join "," (map :id variants))] + (str base ":" variants "&display=block"))) + (defmulti ^:private load-font :backend) (defmethod load-font :builtin @@ -126,10 +131,7 @@ [{:keys [id family variants ::on-loaded] :as font}] (when (exists? js/window) (js/console.log "[debug:fonts]: loading google font" id) - (let [base (str "https://fonts.googleapis.com/css?family=" family) - variants (str/join "," (map :id variants)) - uri (str base ":" variants "&display=block") - node (create-link-node uri)] + (let [node (create-link-node (gfont-url family variants))] (.addEventListener node "load" (fn [event] (when (fn? on-loaded) (on-loaded id)))) (.append (.-head js/document) node) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 83290369e..18d594f20 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -80,6 +80,14 @@ (def current-hover (l/derived :hover workspace-local)) +(def picking-color? + (l/derived :picking-color? workspace-local)) + +(def picked-color + (l/derived :picked-color workspace-local)) + +(def picked-color-select + (l/derived :picked-color-select workspace-local)) (def workspace-layout (l/derived :workspace-layout st/state)) diff --git a/frontend/src/app/main/ui/cursors.cljs b/frontend/src/app/main/ui/cursors.cljs index d827bdf6c..f7dd21fed 100644 --- a/frontend/src/app/main/ui/cursors.cljs +++ b/frontend/src/app/main/ui/cursors.cljs @@ -32,6 +32,7 @@ (def resize-ns (cursor-fn :resize-h 90)) (def rotate (cursor-fn :rotate 90)) (def text (cursor-ref :text)) +(def picker (cursor-ref :picker 0 0 24)) (mf/defc debug-preview {::mf/wrap-props false} diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index 10615ea2b..a382896af 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -10,6 +10,7 @@ (:import goog.events.EventType)) (defonce state (atom nil)) +(defonce can-click-outside (atom false)) (defn show! [component props] @@ -25,13 +26,12 @@ (reset! state nil) (dom/stop-propagation event))) -(defn- on-parent-clicked - [event parent-ref] - (let [parent (mf/ref-val parent-ref) +(defn- on-click + [event wrapper-ref] + (let [wrapper (mf/ref-val wrapper-ref) current (dom/get-target event)] - ;; (js/console.log current (.-className ^js current)) - (when (and (dom/equals? (.-firstElementChild ^js parent) current) - (str/includes? (.-className ^js current) "modal-overlay")) + + (when (and (not @can-click-outside) (not (.contains wrapper current))) (dom/stop-propagation event) (dom/prevent-default event) (reset! state nil)))) @@ -39,15 +39,19 @@ (mf/defc modal-wrapper [{:keys [component props]}] - (mf/use-effect - (fn [] - (let [key (events/listen js/document EventType.KEYDOWN on-esc-clicked)] - #(events/unlistenByKey %)))) + (let [wrapper-ref (mf/use-ref nil)] + (mf/use-effect + (fn [] + (let [key (events/listen js/document EventType.KEYDOWN on-esc-clicked)] + #(events/unlistenByKey key)))) + + (mf/use-effect + (fn [] + (let [key (events/listen js/document EventType.CLICK #(on-click % wrapper-ref))] + #(events/unlistenByKey key)))) - (let [ref (mf/use-ref nil)] [:div.modal-wrapper - {:ref ref - :on-click #(on-parent-clicked % ref)} + {:ref wrapper-ref} [:& component props]])) (mf/defc modal @@ -58,4 +62,8 @@ :key (random-uuid)}])) +(defn allow-click-outside! [] + (reset! can-click-outside true)) +(defn disallow-click-outside! [] + (reset! can-click-outside false)) diff --git a/frontend/src/app/main/ui/shapes/image.cljs b/frontend/src/app/main/ui/shapes/image.cljs index 9cb077b46..fc7ff7cb9 100644 --- a/frontend/src/app/main/ui/shapes/image.cljs +++ b/frontend/src/app/main/ui/shapes/image.cljs @@ -13,23 +13,43 @@ [app.config :as cfg] [app.common.geom.shapes :as geom] [app.main.ui.shapes.attrs :as attrs] - [app.util.object :as obj])) + [app.util.object :as obj] + [app.main.data.fetch :as df] + [promesa.core :as p])) (mf/defc image-shape {::mf/wrap-props false} [props] + (let [shape (unchecked-get props "shape") {:keys [id x y width height rotation metadata]} shape - transform (geom/transform-matrix shape) - uri (cfg/resolve-media-path (:path metadata)) - props (-> (attrs/extract-style-attrs shape) - (obj/merge! - #js {:x x - :y y - :transform transform - :id (str "shape-" id) - :preserveAspectRatio "none" - :xlinkHref uri - :width width - :height height}))] - [:> "image" props])) + uri (cfg/resolve-media-path (:path metadata)) + data-uri (mf/use-state nil)] + + (mf/use-effect + (mf/deps shape) + (fn [] + (-> (df/fetch-as-data-uri uri) + (p/then #(reset! data-uri (second %)))))) + + (let [transform (geom/transform-matrix shape) + props (-> (attrs/extract-style-attrs shape) + (obj/merge! + #js {:x x + :y y + :transform transform + :id (str "shape-" id) + :width width + :height height + :preserveAspectRatio "none"}))] + (if (nil? @data-uri) + [:> "rect" (obj/merge! + props + #js {:fill "#E8E9EA" + :stroke "#000000"})] + [:> "image" (obj/merge! + props + #js {:xlinkHref @data-uri})])) + + + )) diff --git a/frontend/src/app/main/ui/shapes/text.cljs b/frontend/src/app/main/ui/shapes/text.cljs index 75a7c2bd9..ab267a127 100644 --- a/frontend/src/app/main/ui/shapes/text.cljs +++ b/frontend/src/app/main/ui/shapes/text.cljs @@ -6,12 +6,16 @@ (ns app.main.ui.shapes.text (:require + [promesa.core :as p] + [cuerdas.core :as str] [rumext.alpha :as mf] + [app.main.data.fetch :as df] [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])) + [app.util.object :as obj] + [clojure.set :as set])) ;; --- Text Editor Rendering @@ -55,7 +59,8 @@ base #js {:textDecoration text-decoration :color fill :opacity opacity - :textTransform text-transform}] + :textTransform text-transform + :lineHeight "inherit"}] (when (and (string? letter-spacing) (pos? (alength letter-spacing))) @@ -83,33 +88,69 @@ base)) +(defn get-all-fonts [node] + (let [current-font (if (not (nil? (:font-id node))) + #{(:font-id node)} + #{}) + children-font (map get-all-fonts (:children node))] + (reduce set/union (conj children-font current-font)))) + + +(defn fetch-font [font-id] + (let [{:keys [family variants]} (get @fonts/fontsdb font-id)] + (-> (js/fetch (fonts/gfont-url family variants)) + (p/then (fn [res] (.text res)))))) + +(defn embed-font [font-id] + (p/let [font-text (fetch-font font-id) + url-to-data (->> font-text + (re-seq #"url\(([^)]+)\)") + (map second) + (map df/fetch-as-data-uri) + (p/all))] + (reduce (fn [text [url data]] (str/replace text url data)) font-text url-to-data))) + (defn- render-text-node ([node] (render-text-node 0 node)) ([index {:keys [type text children] :as node}] (mf/html - (if (string? text) - (let [style (generate-text-styles (clj->js node))] - [:span {:style style :key index} text]) - (let [children (map-indexed render-text-node children)] - (case type - "root" - (let [style (generate-root-styles (clj->js node))] - [:div.root.rich-text - {:key index - :style style - :xmlns "http://www.w3.org/1999/xhtml"} - children]) + (let [embeded-fonts (mf/use-state nil)] + (mf/use-effect + (mf/deps node) + (fn [] + (when (= type "root") + (let [font-to-embed (get-all-fonts node) + embeded (map embed-font font-to-embed)] + (-> (p/all embeded) + (p/then (fn [result] (reset! embeded-fonts (str/join "\n" result))))))))) - "paragraph-set" - (let [style #js {:display "inline-block" - :width "100%"}] - [:div.paragraphs {:key index :style style} children]) + (if (string? text) + (let [style (generate-text-styles (clj->js node))] + [:span {:style style :key index} text]) + (let [children (map-indexed render-text-node children)] + (case type + "root" + (let [style (generate-root-styles (clj->js node))] + + [:div.root.rich-text + {:key index + :style style + :xmlns "http://www.w3.org/1999/xhtml"} + [:* + (when (not (nil? @embeded-fonts)) + [:style @embeded-fonts]) + children]]) - "paragraph" - (let [style (generate-paragraph-styles (clj->js node))] - [:p {:key index :style style} children]) + "paragraph-set" + (let [style #js {:display "inline-block" + :width "100%"}] + [:div.paragraphs {:key index :style style} children]) - nil)))))) + "paragraph" + (let [style (generate-paragraph-styles (clj->js node))] + [:p {:key index :style style} children]) + + nil))))))) (mf/defc text-content {::mf/wrap-props false diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 13a5be855..306ad4144 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -102,6 +102,10 @@ shared-libs (mf/deref refs/workspace-libraries) recent-colors (mf/deref refs/workspace-recent-colors) + picking-color? (mf/deref refs/picking-color?) + picked-color (mf/deref refs/picked-color) + picked-color-select (mf/deref refs/picked-color-select) + locale (mf/deref i18n/locale) value-ref (mf/use-var value) @@ -154,37 +158,70 @@ ;; When closing the modal we update the recent-color list (mf/use-effect - (fn [] #(st/emit! (dwl/add-recent-color @value-ref)))) + (fn [] #(st/emit! (dwc/stop-picker) + (dwl/add-recent-color @value-ref)))) + + (mf/use-effect + (mf/deps picking-color? picked-color) + (fn [] (when picking-color? + (let [[r g b] picked-color + hex (uc/rgb->hex [r g b]) + [h s v] (uc/hex->hsv hex)] + (swap! current-color assoc + :r r :g g :b b + :h h :s s :v v + :hex hex) + (when picked-color-select + (on-change hex (:alpha @current-color))))))) + + (mf/use-effect + (mf/deps picking-color? picked-color-select) + (fn [] (when picking-color? + (on-change (:hex @current-color) (:alpha @current-color))))) [:div.colorpicker {:ref ref-picker} - [:& value-selector {:hue (:h @current-color) - :saturation (:s @current-color) - :value (:v @current-color) - :on-change (fn [s v] - (let [hex (uc/hsv->hex [(:h @current-color) s v]) - [r g b] (uc/hex->rgb hex)] - (swap! current-color assoc - :hex hex - :r r :g g :b b - :s s :v v) - (reset! value-ref hex) - (on-change hex (:alpha @current-color))))}] - [:div.shade-selector - [:div.color-bullet] - [:& hue-selector {:hue (:h @current-color) - :on-change (fn [h] - (let [hex (uc/hsv->hex [h (:s @current-color) (:v @current-color)]) - [r g b] (uc/hex->rgb hex)] - (swap! current-color assoc - :hex hex - :r r :g g :b b - :h h ) - (reset! value-ref hex) - (on-change hex (:alpha @current-color))))}] - [:& opacity-selector {:opacity (:alpha @current-color) - :on-change (fn [alpha] - (swap! current-color assoc :alpha alpha) - (on-change (:hex @current-color) alpha))}]] + [:div.top-actions + [:button.picker-btn + {:class (when picking-color? "active") + :on-click (fn [] + (modal/allow-click-outside!) + (st/emit! (dwc/start-picker)))} + i/picker]] + + (if picking-color? + [:div.picker-detail-wrapper + [:div.center-circle] + [:canvas#picker-detail {:width 200 + :height 160}]] + [:& value-selector {:hue (:h @current-color) + :saturation (:s @current-color) + :value (:v @current-color) + :on-change (fn [s v] + (let [hex (uc/hsv->hex [(:h @current-color) s v]) + [r g b] (uc/hex->rgb hex)] + (swap! current-color assoc + :hex hex + :r r :g g :b b + :s s :v v) + (reset! value-ref hex) + (on-change hex (:alpha @current-color))))}]) + (when (not picking-color?) + [:div.shade-selector + [:div.color-bullet] + [:& hue-selector {:hue (:h @current-color) + :on-change (fn [h] + (let [hex (uc/hsv->hex [h (:s @current-color) (:v @current-color)]) + [r g b] (uc/hex->rgb hex)] + (swap! current-color assoc + :hex hex + :r r :g g :b b + :h h ) + (reset! value-ref hex) + (on-change hex (:alpha @current-color))))}] + [:& opacity-selector {:opacity (:alpha @current-color) + :on-change (fn [alpha] + (swap! current-color assoc :alpha alpha) + (on-change (:hex @current-color) alpha))}]]) [:div.color-values [:input.hex-value {:id "hex-value" @@ -320,12 +357,11 @@ :top (str (- y 50) "px")} :right {:left (str (+ x 24) "px") :top (str (- y 50) "px")})] - [:div.modal-overlay.transparent - [:div.colorpicker-tooltip + [:div.colorpicker-tooltip {:style (clj->js style)} [:& colorpicker {:value (or value default) :opacity (or opacity 1) :on-change on-change :on-accept on-accept - :disable-opacity disable-opacity}]]])) + :disable-opacity disable-opacity}]])) diff --git a/frontend/src/app/main/ui/workspace/rules.cljs b/frontend/src/app/main/ui/workspace/rules.cljs index bdd98a1b9..cf5644fe8 100644 --- a/frontend/src/app/main/ui/workspace/rules.cljs +++ b/frontend/src/app/main/ui/workspace/rules.cljs @@ -33,49 +33,50 @@ (defn draw-rule! [dctx {:keys [zoom size start count type] :or {count 200}}] - (let [txfm (- (* (- 0 start) zoom) 20) - minv (mth/round start) - maxv (mth/round (+ start (/ size zoom))) - step (calculate-step-size zoom)] + (when start + (let [txfm (- (* (- 0 start) zoom) 20) + minv (max (mth/round start) -10000) + maxv (min (mth/round (+ start (/ size zoom))) 10000) + step (calculate-step-size zoom)] - (if (= type :horizontal) - (.translate dctx txfm 0) - (.translate dctx 0 txfm)) + (if (= type :horizontal) + (.translate dctx txfm 0) + (.translate dctx 0 txfm)) - (obj/set! dctx "font" "12px sourcesanspro") - (obj/set! dctx "fillStyle" "#7B7D85") - (obj/set! dctx "strokeStyle" "#7B7D85") - (obj/set! dctx "textAlign" "center") + (obj/set! dctx "font" "12px sourcesanspro") + (obj/set! dctx "fillStyle" "#7B7D85") + (obj/set! dctx "strokeStyle" "#7B7D85") + (obj/set! dctx "textAlign" "center") - (loop [i minv] - (when (< i maxv) - (let [pos (+ (* i zoom) 0)] - (when (= (mod i step) 0) - (.save dctx) - (if (= type :horizontal) - (do - (.fillText dctx (str i) pos 13)) - (do - (.translate dctx 12 pos) - (.rotate dctx (/ (* 270 js/Math.PI) 180)) - (.fillText dctx (str i) 0 0))) - (.restore dctx)) - (recur (inc i))))) + (loop [i minv] + (when (< i maxv) + (let [pos (+ (* i zoom) 0)] + (when (= (mod i step) 0) + (.save dctx) + (if (= type :horizontal) + (do + (.fillText dctx (str i) pos 13)) + (do + (.translate dctx 12 pos) + (.rotate dctx (/ (* 270 js/Math.PI) 180)) + (.fillText dctx (str i) 0 0))) + (.restore dctx)) + (recur (inc i))))) - (let [path (js/Path2D.)] - (loop [i minv] - (if (> i maxv) - (.stroke dctx path) - (let [pos (+ (* i zoom) 0)] - (when (= (mod i step) 0) - (if (= type :horizontal) - (do - (.moveTo path pos 17) - (.lineTo path pos 20)) - (do - (.moveTo path 17 pos) - (.lineTo path 20 pos)))) - (recur (inc i)))))))) + (let [path (js/Path2D.)] + (loop [i minv] + (if (> i maxv) + (.stroke dctx path) + (let [pos (+ (* i zoom) 0)] + (when (= (mod i step) 0) + (if (= type :horizontal) + (do + (.moveTo path pos 17) + (.lineTo path pos 20)) + (do + (.moveTo path 17 pos) + (.lineTo path 20 pos)))) + (recur (inc i))))))))) (mf/defc horizontal-rule diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index 290a60bcf..b571173b3 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -119,7 +119,8 @@ base #js {:textDecoration text-decoration :color fill :opacity opacity - :textTransform text-transform}] + :textTransform text-transform + :lineHeight "inherit"}] (when (and (string? letter-spacing) (pos? (alength letter-spacing))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index c8e387b6e..8f74455d2 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -36,7 +36,7 @@ (if (= value :multiple) "transparent" value)) (defn remove-hash [value] - (if (= value :multiple) "" (subs value 1))) + (if (or (nil? value) (= value :multiple)) "" (subs value 1))) (defn append-hash [value] (str "#" value)) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 618cfd290..1e104eae3 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -15,12 +15,15 @@ [goog.events :as events] [potok.core :as ptk] [rumext.alpha :as mf] + [promesa.core :as p] [app.main.ui.icons :as i] [app.main.ui.cursors :as cur] + [app.main.ui.modal :as modal] [app.common.data :as d] [app.main.constants :as c] [app.main.data.workspace :as dw] [app.main.data.workspace.drawing :as dd] + [app.main.data.colors :as dwc] [app.main.refs :as refs] [app.main.store :as st] [app.main.streams :as ms] @@ -168,15 +171,20 @@ edition tooltip selected - panning]} local + panning + picking-color?]} local 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) drawing-tool (:tool drawing) drawing-obj (:object drawing) + pick-color (mf/use-state [255 255 255 255]) + zoom (or zoom 1) on-mouse-down @@ -410,7 +418,47 @@ prnt (dom/get-parent node)] (st/emit! (dw/update-viewport-size (dom/get-client-size prnt))))) - options (mf/deref refs/workspace-page-options)] + 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))) + + on-mouse-up-picker + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dwc/pick-color-select false) + (dwc/stop-picker)) + (modal/disallow-click-outside!))] (mf/use-layout-effect (fn [] @@ -434,73 +482,103 @@ (mf/use-layout-effect (mf/deps layout) on-resize) - [: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)]) - :ref viewport-ref - :class (when drawing-tool "drawing") - :style {:cursor (cond - panning cur/hand - (= drawing-tool :frame) cur/create-artboard - (= drawing-tool :rect) cur/create-rectangle - (= drawing-tool :circle) cur/create-ellipse - (= drawing-tool :path) cur/pen - (= drawing-tool :curve)cur/pencil - drawing-tool cur/create-shape - :else cur/pointer-inner) - :background-color (get options :background "#E8E9EA")} - :on-context-menu on-context-menu - :on-click on-click - :on-double-click on-double-click - :on-mouse-down on-mouse-down - :on-mouse-up on-mouse-up - :on-pointer-down on-pointer-down - :on-pointer-up on-pointer-up - :on-drag-enter on-drag-enter - :on-drag-over on-drag-over - :on-drop on-drop} + (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)))))) - [:g - [:& frames {:key page-id - :hover (:hover local) - :selected (:selected selected)}] + [:* - (when (seq selected) - [:& selection-handlers {:selected selected - :zoom zoom - :edition edition}]) + (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}}]) + [: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)]) + :ref viewport-ref + :class (when drawing-tool "drawing") + :style {:cursor (cond + panning cur/hand + (= drawing-tool :frame) cur/create-artboard + (= drawing-tool :rect) cur/create-rectangle + (= drawing-tool :circle) cur/create-ellipse + (= drawing-tool :path) cur/pen + (= drawing-tool :curve)cur/pencil + drawing-tool cur/create-shape + :else cur/pointer-inner) + :background-color (get options :background "#E8E9EA")} + :on-context-menu on-context-menu + :on-click on-click + :on-double-click on-double-click + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up + :on-pointer-down on-pointer-down + :on-pointer-up on-pointer-up + :on-drag-enter on-drag-enter + :on-drag-over on-drag-over + :on-drop on-drop} - (when drawing-obj - [:& draw-area {:shape drawing-obj - :zoom zoom - :modifiers (:modifiers local)}]) + [:g + [:& frames {:key page-id + :hover (:hover local) + :selected (:selected selected)}] - (when (contains? layout :display-grid) - [:& frame-grid {:zoom zoom}]) + (when (seq selected) + [:& selection-handlers {:selected selected + :zoom zoom + :edition edition}]) - [:& snap-points {:layout layout - :transform (:transform local) - :drawing drawing-obj - :zoom zoom - :page-id page-id - :selected selected}] + (when drawing-obj + [:& draw-area {:shape drawing-obj + :zoom zoom + :modifiers (:modifiers local)}]) - [:& snap-distances {:layout layout - :zoom zoom - :transform (:transform local) - :selected selected - :page-id page-id}] + (when (contains? layout :display-grid) + [:& frame-grid {:zoom zoom}]) - (when tooltip - [:& cursor-tooltip {:zoom zoom :tooltip tooltip}])] + [:& snap-points {:layout layout + :transform (:transform local) + :drawing drawing-obj + :zoom zoom + :page-id page-id + :selected selected}] - [:& presence/active-cursors {:page-id page-id}] - [:& selection-rect {:data (:selrect local)}] - (when (= options-mode :prototype) - [:& interactions {:selected selected}])])) + [:& snap-distances {:layout layout + :zoom zoom + :transform (:transform local) + :selected selected + :page-id page-id}] + + (when tooltip + [:& cursor-tooltip {:zoom zoom :tooltip tooltip}])] + + [:& presence/active-cursors {:page-id page-id}] + [:& selection-rect {:data (:selrect local)}] + (when (= options-mode :prototype) + [:& interactions {:selected selected}])]])) diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index 88c229701..d80f4436c 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -51,9 +51,7 @@ (defn hex->hsl [hex] (try (into [] (gcolor/hexToHsl hex)) - (catch :default e (do - (.log js/console e) - [0 0 0])))) + (catch :default e [0 0 0]))) (defn hsl->rgb [[h s l]]