🎉 Pixel picker

This commit is contained in:
alonso.torres 2020-09-09 12:17:32 +02:00 committed by Alonso Torres
parent 8aad43883f
commit f8b3baef3f
18 changed files with 494 additions and 199 deletions

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="24px" height="24px">
<path fill="#fff" d="M.607 13.076v2.401l2.224.025 7.86-7.405s-.05-.885-.253-1.087c-.202-.202-2.25-1.744-2.25-1.744z"/>
<path fill="#000" d="M.343 15.974a.514.514 0 01-.317-.321c-.023-.07-.026-.23-.026-1.43 0-1.468-.001-1.445.09-1.586.02-.032 1.703-1.724 3.74-3.759a596.805 596.805 0 003.7-3.716c0-.009-.367-.384-.816-.833a29.9 29.9 0 01-.817-.833c0-.01.474-.49 1.054-1.07l1.053-1.053.948.946.947.947 1.417-1.413C12.366.806 12.765.418 12.856.357c.238-.161.52-.28.792-.334.17-.034.586-.03.76.008.801.173 1.41.794 1.57 1.603.03.15.03.569 0 .718a2.227 2.227 0 01-.334.793c-.061.09-.45.49-1.496 1.54L12.734 6.1l.947.948.947.947-1.053 1.054c-.58.58-1.061 1.054-1.07 1.054-.01 0-.384-.368-.833-.817-.45-.45-.824-.817-.834-.817-.009 0-1.68 1.666-3.716 3.701a493.093 493.093 0 01-3.759 3.74c-.14.091-.117.09-1.59.089-1.187 0-1.366-.004-1.43-.027zm6.024-4.633a592.723 592.723 0 003.663-3.68c0-.02-1.67-1.69-1.69-1.69-.01 0-1.666 1.648-3.68 3.663L.996 13.297v.834c0 .627.005.839.02.854.015.014.227.02.854.02h.833l3.664-3.664z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1743,19 +1743,19 @@
} }
}, },
"workspace.libraries.colors.file-library" : { "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" : { "translations" : {
"en" : "File library" "en" : "File library"
} }
}, },
"workspace.libraries.colors.recent-colors" : { "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" : { "translations" : {
"en" : "Recent colors" "en" : "Recent colors"
} }
}, },
"workspace.libraries.colors.save-color" : { "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" : { "translations" : {
"en" : "Save color" "en" : "Save color"
} }

View file

@ -8,11 +8,54 @@
.colorpicker { .colorpicker {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 13rem;
padding: 0.5rem; padding: 0.5rem;
background-color: $color-white; background-color: $color-white;
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 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 { .handler {
position: absolute; position: absolute;
width: 12px; width: 12px;
@ -25,7 +68,6 @@
background-color: rgba(var(--hue)); background-color: rgba(var(--hue));
position: relative; position: relative;
height: 6.75rem; height: 6.75rem;
width: 100%;
cursor: pointer; cursor: pointer;
.handler { .handler {
@ -137,6 +179,7 @@
border-top: 1px solid $color-gray-10; border-top: 1px solid $color-gray-10;
padding-top: 0.5rem; padding-top: 0.5rem;
margin-top: 0.25rem; margin-top: 0.25rem;
width: 200px;
select { select {
background-image: url(/images/icons/arrow-down.svg); background-image: url(/images/icons/arrow-down.svg);
@ -162,7 +205,8 @@
grid-template-columns: repeat(8, 1fr); grid-template-columns: repeat(8, 1fr);
justify-content: space-between; justify-content: space-between;
margin-right: -8px; margin-right: -8px;
overflow: scroll; overflow-x: hidden;
overflow-y: auto;
max-height: 5.5rem; max-height: 5.5rem;
} }

View file

@ -129,3 +129,31 @@
(-> state (-> state
(update :workspace-layout conj :colorpalette) (update :workspace-layout conj :colorpalette)
(assoc-in [:workspace-local :selected-palette] selected))))) (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)))))

View file

@ -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))))))))

View file

@ -102,7 +102,10 @@
:right-sidebar? true :right-sidebar? true
:color-for-rename nil :color-for-rename nil
:selected-palette :recent :selected-palette :recent
:selected-palette-size :big}) :selected-palette-size :big
:picking-color? false
:picked-color nil
:picked-color-select false})
(def initialize-layout (def initialize-layout
(ptk/reify ::initialize-layout (ptk/reify ::initialize-layout

View file

@ -114,6 +114,11 @@
(unchecked-set node "type" "text/css") (unchecked-set node "type" "text/css")
node)) 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) (defmulti ^:private load-font :backend)
(defmethod load-font :builtin (defmethod load-font :builtin
@ -126,10 +131,7 @@
[{:keys [id family variants ::on-loaded] :as font}] [{:keys [id family variants ::on-loaded] :as font}]
(when (exists? js/window) (when (exists? js/window)
(js/console.log "[debug:fonts]: loading google font" id) (js/console.log "[debug:fonts]: loading google font" id)
(let [base (str "https://fonts.googleapis.com/css?family=" family) (let [node (create-link-node (gfont-url family variants))]
variants (str/join "," (map :id variants))
uri (str base ":" variants "&display=block")
node (create-link-node uri)]
(.addEventListener node "load" (fn [event] (when (fn? on-loaded) (.addEventListener node "load" (fn [event] (when (fn? on-loaded)
(on-loaded id)))) (on-loaded id))))
(.append (.-head js/document) node) (.append (.-head js/document) node)

View file

@ -80,6 +80,14 @@
(def current-hover (def current-hover
(l/derived :hover workspace-local)) (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 (def workspace-layout
(l/derived :workspace-layout st/state)) (l/derived :workspace-layout st/state))

View file

@ -32,6 +32,7 @@
(def resize-ns (cursor-fn :resize-h 90)) (def resize-ns (cursor-fn :resize-h 90))
(def rotate (cursor-fn :rotate 90)) (def rotate (cursor-fn :rotate 90))
(def text (cursor-ref :text)) (def text (cursor-ref :text))
(def picker (cursor-ref :picker 0 0 24))
(mf/defc debug-preview (mf/defc debug-preview
{::mf/wrap-props false} {::mf/wrap-props false}

View file

@ -10,6 +10,7 @@
(:import goog.events.EventType)) (:import goog.events.EventType))
(defonce state (atom nil)) (defonce state (atom nil))
(defonce can-click-outside (atom false))
(defn show! (defn show!
[component props] [component props]
@ -25,13 +26,12 @@
(reset! state nil) (reset! state nil)
(dom/stop-propagation event))) (dom/stop-propagation event)))
(defn- on-parent-clicked (defn- on-click
[event parent-ref] [event wrapper-ref]
(let [parent (mf/ref-val parent-ref) (let [wrapper (mf/ref-val wrapper-ref)
current (dom/get-target event)] current (dom/get-target event)]
;; (js/console.log current (.-className ^js current))
(when (and (dom/equals? (.-firstElementChild ^js parent) current) (when (and (not @can-click-outside) (not (.contains wrapper current)))
(str/includes? (.-className ^js current) "modal-overlay"))
(dom/stop-propagation event) (dom/stop-propagation event)
(dom/prevent-default event) (dom/prevent-default event)
(reset! state nil)))) (reset! state nil))))
@ -39,15 +39,19 @@
(mf/defc modal-wrapper (mf/defc modal-wrapper
[{:keys [component props]}] [{:keys [component props]}]
(mf/use-effect (let [wrapper-ref (mf/use-ref nil)]
(fn [] (mf/use-effect
(let [key (events/listen js/document EventType.KEYDOWN on-esc-clicked)] (fn []
#(events/unlistenByKey %)))) (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 [:div.modal-wrapper
{:ref ref {:ref wrapper-ref}
:on-click #(on-parent-clicked % ref)}
[:& component props]])) [:& component props]]))
(mf/defc modal (mf/defc modal
@ -58,4 +62,8 @@
:key (random-uuid)}])) :key (random-uuid)}]))
(defn allow-click-outside! []
(reset! can-click-outside true))
(defn disallow-click-outside! []
(reset! can-click-outside false))

View file

@ -13,23 +13,43 @@
[app.config :as cfg] [app.config :as cfg]
[app.common.geom.shapes :as geom] [app.common.geom.shapes :as geom]
[app.main.ui.shapes.attrs :as attrs] [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/defc image-shape
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")
{:keys [id x y width height rotation metadata]} shape {:keys [id x y width height rotation metadata]} shape
transform (geom/transform-matrix shape) uri (cfg/resolve-media-path (:path metadata))
uri (cfg/resolve-media-path (:path metadata)) data-uri (mf/use-state nil)]
props (-> (attrs/extract-style-attrs shape)
(obj/merge! (mf/use-effect
#js {:x x (mf/deps shape)
:y y (fn []
:transform transform (-> (df/fetch-as-data-uri uri)
:id (str "shape-" id) (p/then #(reset! data-uri (second %))))))
:preserveAspectRatio "none"
:xlinkHref uri (let [transform (geom/transform-matrix shape)
:width width props (-> (attrs/extract-style-attrs shape)
:height height}))] (obj/merge!
[:> "image" props])) #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})]))
))

View file

@ -6,12 +6,16 @@
(ns app.main.ui.shapes.text (ns app.main.ui.shapes.text
(:require (:require
[promesa.core :as p]
[cuerdas.core :as str]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[app.main.data.fetch :as df]
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as geom] [app.common.geom.shapes :as geom]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.util.object :as obj])) [app.util.object :as obj]
[clojure.set :as set]))
;; --- Text Editor Rendering ;; --- Text Editor Rendering
@ -55,7 +59,8 @@
base #js {:textDecoration text-decoration base #js {:textDecoration text-decoration
:color fill :color fill
:opacity opacity :opacity opacity
:textTransform text-transform}] :textTransform text-transform
:lineHeight "inherit"}]
(when (and (string? letter-spacing) (when (and (string? letter-spacing)
(pos? (alength letter-spacing))) (pos? (alength letter-spacing)))
@ -83,33 +88,69 @@
base)) 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 (defn- render-text-node
([node] (render-text-node 0 node)) ([node] (render-text-node 0 node))
([index {:keys [type text children] :as node}] ([index {:keys [type text children] :as node}]
(mf/html (mf/html
(if (string? text) (let [embeded-fonts (mf/use-state nil)]
(let [style (generate-text-styles (clj->js node))] (mf/use-effect
[:span {:style style :key index} text]) (mf/deps node)
(let [children (map-indexed render-text-node children)] (fn []
(case type (when (= type "root")
"root" (let [font-to-embed (get-all-fonts node)
(let [style (generate-root-styles (clj->js node))] embeded (map embed-font font-to-embed)]
[:div.root.rich-text (-> (p/all embeded)
{:key index (p/then (fn [result] (reset! embeded-fonts (str/join "\n" result)))))))))
:style style
:xmlns "http://www.w3.org/1999/xhtml"}
children])
"paragraph-set" (if (string? text)
(let [style #js {:display "inline-block" (let [style (generate-text-styles (clj->js node))]
:width "100%"}] [:span {:style style :key index} text])
[:div.paragraphs {:key index :style style} children]) (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" "paragraph-set"
(let [style (generate-paragraph-styles (clj->js node))] (let [style #js {:display "inline-block"
[:p {:key index :style style} children]) :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/defc text-content
{::mf/wrap-props false {::mf/wrap-props false

View file

@ -102,6 +102,10 @@
shared-libs (mf/deref refs/workspace-libraries) shared-libs (mf/deref refs/workspace-libraries)
recent-colors (mf/deref refs/workspace-recent-colors) 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) locale (mf/deref i18n/locale)
value-ref (mf/use-var value) value-ref (mf/use-var value)
@ -154,37 +158,70 @@
;; When closing the modal we update the recent-color list ;; When closing the modal we update the recent-color list
(mf/use-effect (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} [:div.colorpicker {:ref ref-picker}
[:& value-selector {:hue (:h @current-color) [:div.top-actions
:saturation (:s @current-color) [:button.picker-btn
:value (:v @current-color) {:class (when picking-color? "active")
:on-change (fn [s v] :on-click (fn []
(let [hex (uc/hsv->hex [(:h @current-color) s v]) (modal/allow-click-outside!)
[r g b] (uc/hex->rgb hex)] (st/emit! (dwc/start-picker)))}
(swap! current-color assoc i/picker]]
:hex hex
:r r :g g :b b (if picking-color?
:s s :v v) [:div.picker-detail-wrapper
(reset! value-ref hex) [:div.center-circle]
(on-change hex (:alpha @current-color))))}] [:canvas#picker-detail {:width 200
[:div.shade-selector :height 160}]]
[:div.color-bullet] [:& value-selector {:hue (:h @current-color)
[:& hue-selector {:hue (:h @current-color) :saturation (:s @current-color)
:on-change (fn [h] :value (:v @current-color)
(let [hex (uc/hsv->hex [h (:s @current-color) (:v @current-color)]) :on-change (fn [s v]
[r g b] (uc/hex->rgb hex)] (let [hex (uc/hsv->hex [(:h @current-color) s v])
(swap! current-color assoc [r g b] (uc/hex->rgb hex)]
:hex hex (swap! current-color assoc
:r r :g g :b b :hex hex
:h h ) :r r :g g :b b
(reset! value-ref hex) :s s :v v)
(on-change hex (:alpha @current-color))))}] (reset! value-ref hex)
[:& opacity-selector {:opacity (:alpha @current-color) (on-change hex (:alpha @current-color))))}])
:on-change (fn [alpha] (when (not picking-color?)
(swap! current-color assoc :alpha alpha) [:div.shade-selector
(on-change (:hex @current-color) alpha))}]] [: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 [:div.color-values
[:input.hex-value {:id "hex-value" [:input.hex-value {:id "hex-value"
@ -320,12 +357,11 @@
:top (str (- y 50) "px")} :top (str (- y 50) "px")}
:right {:left (str (+ x 24) "px") :right {:left (str (+ x 24) "px")
:top (str (- y 50) "px")})] :top (str (- y 50) "px")})]
[:div.modal-overlay.transparent [:div.colorpicker-tooltip
[:div.colorpicker-tooltip
{:style (clj->js style)} {:style (clj->js style)}
[:& colorpicker {:value (or value default) [:& colorpicker {:value (or value default)
:opacity (or opacity 1) :opacity (or opacity 1)
:on-change on-change :on-change on-change
:on-accept on-accept :on-accept on-accept
:disable-opacity disable-opacity}]]])) :disable-opacity disable-opacity}]]))

View file

@ -33,49 +33,50 @@
(defn draw-rule! (defn draw-rule!
[dctx {:keys [zoom size start count type] :or {count 200}}] [dctx {:keys [zoom size start count type] :or {count 200}}]
(let [txfm (- (* (- 0 start) zoom) 20) (when start
minv (mth/round start) (let [txfm (- (* (- 0 start) zoom) 20)
maxv (mth/round (+ start (/ size zoom))) minv (max (mth/round start) -10000)
step (calculate-step-size zoom)] maxv (min (mth/round (+ start (/ size zoom))) 10000)
step (calculate-step-size zoom)]
(if (= type :horizontal) (if (= type :horizontal)
(.translate dctx txfm 0) (.translate dctx txfm 0)
(.translate dctx 0 txfm)) (.translate dctx 0 txfm))
(obj/set! dctx "font" "12px sourcesanspro") (obj/set! dctx "font" "12px sourcesanspro")
(obj/set! dctx "fillStyle" "#7B7D85") (obj/set! dctx "fillStyle" "#7B7D85")
(obj/set! dctx "strokeStyle" "#7B7D85") (obj/set! dctx "strokeStyle" "#7B7D85")
(obj/set! dctx "textAlign" "center") (obj/set! dctx "textAlign" "center")
(loop [i minv] (loop [i minv]
(when (< i maxv) (when (< i maxv)
(let [pos (+ (* i zoom) 0)] (let [pos (+ (* i zoom) 0)]
(when (= (mod i step) 0) (when (= (mod i step) 0)
(.save dctx) (.save dctx)
(if (= type :horizontal) (if (= type :horizontal)
(do (do
(.fillText dctx (str i) pos 13)) (.fillText dctx (str i) pos 13))
(do (do
(.translate dctx 12 pos) (.translate dctx 12 pos)
(.rotate dctx (/ (* 270 js/Math.PI) 180)) (.rotate dctx (/ (* 270 js/Math.PI) 180))
(.fillText dctx (str i) 0 0))) (.fillText dctx (str i) 0 0)))
(.restore dctx)) (.restore dctx))
(recur (inc i))))) (recur (inc i)))))
(let [path (js/Path2D.)] (let [path (js/Path2D.)]
(loop [i minv] (loop [i minv]
(if (> i maxv) (if (> i maxv)
(.stroke dctx path) (.stroke dctx path)
(let [pos (+ (* i zoom) 0)] (let [pos (+ (* i zoom) 0)]
(when (= (mod i step) 0) (when (= (mod i step) 0)
(if (= type :horizontal) (if (= type :horizontal)
(do (do
(.moveTo path pos 17) (.moveTo path pos 17)
(.lineTo path pos 20)) (.lineTo path pos 20))
(do (do
(.moveTo path 17 pos) (.moveTo path 17 pos)
(.lineTo path 20 pos)))) (.lineTo path 20 pos))))
(recur (inc i)))))))) (recur (inc i)))))))))
(mf/defc horizontal-rule (mf/defc horizontal-rule

View file

@ -119,7 +119,8 @@
base #js {:textDecoration text-decoration base #js {:textDecoration text-decoration
:color fill :color fill
:opacity opacity :opacity opacity
:textTransform text-transform}] :textTransform text-transform
:lineHeight "inherit"}]
(when (and (string? letter-spacing) (when (and (string? letter-spacing)
(pos? (alength letter-spacing))) (pos? (alength letter-spacing)))

View file

@ -36,7 +36,7 @@
(if (= value :multiple) "transparent" value)) (if (= value :multiple) "transparent" value))
(defn remove-hash [value] (defn remove-hash [value]
(if (= value :multiple) "" (subs value 1))) (if (or (nil? value) (= value :multiple)) "" (subs value 1)))
(defn append-hash [value] (defn append-hash [value]
(str "#" value)) (str "#" value))

View file

@ -15,12 +15,15 @@
[goog.events :as events] [goog.events :as events]
[potok.core :as ptk] [potok.core :as ptk]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[promesa.core :as p]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.cursors :as cur] [app.main.ui.cursors :as cur]
[app.main.ui.modal :as modal]
[app.common.data :as d] [app.common.data :as d]
[app.main.constants :as c] [app.main.constants :as c]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.drawing :as dd] [app.main.data.workspace.drawing :as dd]
[app.main.data.colors :as dwc]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.streams :as ms] [app.main.streams :as ms]
@ -168,15 +171,20 @@
edition edition
tooltip tooltip
selected selected
panning]} local panning
picking-color?]} local
file (mf/deref refs/workspace-file) file (mf/deref refs/workspace-file)
viewport-ref (mf/use-ref nil) 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) last-position (mf/use-var nil)
drawing (mf/deref refs/workspace-drawing) drawing (mf/deref refs/workspace-drawing)
drawing-tool (:tool drawing) drawing-tool (:tool drawing)
drawing-obj (:object drawing) drawing-obj (:object drawing)
pick-color (mf/use-state [255 255 255 255])
zoom (or zoom 1) zoom (or zoom 1)
on-mouse-down on-mouse-down
@ -410,7 +418,47 @@
prnt (dom/get-parent node)] prnt (dom/get-parent node)]
(st/emit! (dw/update-viewport-size (dom/get-client-size prnt))))) (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 (mf/use-layout-effect
(fn [] (fn []
@ -434,73 +482,103 @@
(mf/use-layout-effect (mf/deps layout) on-resize) (mf/use-layout-effect (mf/deps layout) on-resize)
[:svg.viewport (mf/use-effect
{:preserveAspectRatio "xMidYMid meet" (mf/deps props)
:width (:width vport 0) (fn []
:height (:height vport 0) (when picking-color?
:view-box (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) (try
(:y vbox 0) (let [svg-node (mf/ref-val viewport-ref)
(:width vbox 0) canvas-node (mf/ref-val canvas-ref)
(:height vbox 0)]) canvas-context (.getContext canvas-node "2d")
:ref viewport-ref xml (.serializeToString (js/XMLSerializer.) svg-node)
:class (when drawing-tool "drawing") content (str "data:image/svg+xml;base64," (js/btoa xml))
:style {:cursor (cond img (js/Image.)]
panning cur/hand (obj/set! img "onload"
(= drawing-tool :frame) cur/create-artboard (fn []
(= drawing-tool :rect) cur/create-rectangle (.drawImage canvas-context img 0 0)))
(= drawing-tool :circle) cur/create-ellipse (obj/set! img "src" content))
(= drawing-tool :path) cur/pen (catch :default e (.error js/console e))))))
(= 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}
[:g [:*
[:& frames {:key page-id
:hover (:hover local)
:selected (:selected selected)}]
(when (seq selected) (when picking-color?
[:& selection-handlers {:selected selected [:canvas {:ref canvas-ref
:zoom zoom :width (:width vport 0)
:edition edition}]) :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 [:g
[:& draw-area {:shape drawing-obj [:& frames {:key page-id
:zoom zoom :hover (:hover local)
:modifiers (:modifiers local)}]) :selected (:selected selected)}]
(when (contains? layout :display-grid) (when (seq selected)
[:& frame-grid {:zoom zoom}]) [:& selection-handlers {:selected selected
:zoom zoom
:edition edition}])
[:& snap-points {:layout layout (when drawing-obj
:transform (:transform local) [:& draw-area {:shape drawing-obj
:drawing drawing-obj :zoom zoom
:zoom zoom :modifiers (:modifiers local)}])
:page-id page-id
:selected selected}]
[:& snap-distances {:layout layout (when (contains? layout :display-grid)
:zoom zoom [:& frame-grid {:zoom zoom}])
:transform (:transform local)
:selected selected
:page-id page-id}]
(when tooltip [:& snap-points {:layout layout
[:& cursor-tooltip {:zoom zoom :tooltip tooltip}])] :transform (:transform local)
:drawing drawing-obj
:zoom zoom
:page-id page-id
:selected selected}]
[:& presence/active-cursors {:page-id page-id}] [:& snap-distances {:layout layout
[:& selection-rect {:data (:selrect local)}] :zoom zoom
(when (= options-mode :prototype) :transform (:transform local)
[:& interactions {:selected selected}])])) :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}])]]))

View file

@ -51,9 +51,7 @@
(defn hex->hsl [hex] (defn hex->hsl [hex]
(try (try
(into [] (gcolor/hexToHsl hex)) (into [] (gcolor/hexToHsl hex))
(catch :default e (do (catch :default e [0 0 0])))
(.log js/console e)
[0 0 0]))))
(defn hsl->rgb (defn hsl->rgb
[[h s l]] [[h s l]]