mirror of
https://github.com/penpot/penpot.git
synced 2025-04-16 00:41:25 -05:00
✨ Color picker refactor
This commit is contained in:
parent
c266f78d1e
commit
a412fc113d
14 changed files with 831 additions and 528 deletions
|
@ -163,6 +163,27 @@
|
|||
(map #(dwt/update-text-attrs {:id % :editor (get editors %) :attrs attrs}) text-ids)
|
||||
(dwc/update-shapes shape-ids update-fn))))))))
|
||||
|
||||
(defn change-stroke2 [ids color]
|
||||
(ptk/reify ::change-stroke
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [objects (get-in state [:workspace-data :pages-index (:current-page-id state) :objects])
|
||||
children (mapcat #(cph/get-children % objects) ids)
|
||||
ids (into ids children)
|
||||
|
||||
update-fn (fn [s]
|
||||
(cond-> s
|
||||
true
|
||||
(assoc :stroke-color (:color color)
|
||||
:stroke-color-gradient (:gradient color)
|
||||
:stroke-color-ref-id (:id color)
|
||||
:stroke-color-ref-file (:file-id color))
|
||||
|
||||
(= (:stroke-style s) :none)
|
||||
(assoc :stroke-style :solid
|
||||
:stroke-width 1
|
||||
:stroke-opacity 1)))]
|
||||
(rx/of (dwc/update-shapes ids update-fn))))))
|
||||
(defn change-stroke [ids color id file-id]
|
||||
(ptk/reify ::change-stroke
|
||||
ptk/WatchEvent
|
||||
|
@ -186,13 +207,14 @@
|
|||
|
||||
(defn picker-for-selected-shape []
|
||||
;; TODO: replace st/emit! by a subject push and set that in the WatchEvent
|
||||
(let [handle-change-color (fn [color opacity id file-id shift?]
|
||||
(let [ids (get-in @st/state [:workspace-local :selected])]
|
||||
(st/emit!
|
||||
(if shift?
|
||||
(change-stroke ids color nil nil)
|
||||
(change-fill ids color nil nil))
|
||||
(md/hide))))]
|
||||
(let [handle-change-color
|
||||
(fn [color shift?]
|
||||
(let [ids (get-in @st/state [:workspace-local :selected])]
|
||||
(st/emit!
|
||||
(if shift?
|
||||
(change-stroke2 ids color)
|
||||
(change-fill2 ids color))
|
||||
(md/hide))))]
|
||||
(ptk/reify ::start-picker
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
|
|
@ -1444,22 +1444,23 @@
|
|||
|
||||
(defn change-canvas-color
|
||||
[color]
|
||||
(s/assert string? color)
|
||||
;; TODO: Create a color spec
|
||||
#_(s/assert string? color)
|
||||
(ptk/reify ::change-canvas-color
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (get state :current-page-id)
|
||||
options (dwc/lookup-page-options state page-id)
|
||||
ccolor (:background options)]
|
||||
previus-color (:background options)]
|
||||
(rx/of (dwc/commit-changes
|
||||
[{:type :set-option
|
||||
:page-id page-id
|
||||
:option :background
|
||||
:value color}]
|
||||
:value (:color color)}]
|
||||
[{:type :set-option
|
||||
:page-id page-id
|
||||
:option :background
|
||||
:value ccolor}]
|
||||
:value previus-color}]
|
||||
{:commit-local? true}))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -24,7 +24,13 @@
|
|||
[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]]))
|
||||
[app.util.i18n :as i18n :refer [t]]
|
||||
[app.main.ui.workspace.colorpicker.gradients :refer [gradients]]
|
||||
[app.main.ui.workspace.colorpicker.harmony :refer [harmony-selector]]
|
||||
[app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]]
|
||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]]
|
||||
[app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]]
|
||||
[app.main.ui.workspace.colorpicker.libraries :refer [libraries]]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
|
@ -48,409 +54,6 @@
|
|||
|
||||
;; --- Color Picker Modal
|
||||
|
||||
(mf/defc value-saturation-selector [{:keys [hue saturation value on-change]}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
calculate-pos
|
||||
(fn [ev]
|
||||
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
|
||||
{:keys [x y]} (-> ev dom/get-client-position)
|
||||
px (math/clamp (/ (- x left) (- right left)) 0 1)
|
||||
py (* 255 (- 1 (math/clamp (/ (- y top) (- bottom top)) 0 1)))]
|
||||
(on-change px py)))]
|
||||
[:div.value-saturation-selector
|
||||
{:on-mouse-down #(reset! dragging? true)
|
||||
:on-mouse-up #(reset! dragging? false)
|
||||
:on-pointer-down (partial dom/capture-pointer)
|
||||
:on-pointer-up (partial dom/release-pointer)
|
||||
:on-click calculate-pos
|
||||
:on-mouse-move #(when @dragging? (calculate-pos %))}
|
||||
[:div.handler {:style {:pointer-events "none"
|
||||
:left (str (* 100 saturation) "%")
|
||||
:top (str (* 100 (- 1 (/ value 255))) "%")}}]]))
|
||||
|
||||
|
||||
(mf/defc slider-selector [{:keys [value class min-value max-value vertical? reverse? on-change]}]
|
||||
(let [min-value (or min-value 0)
|
||||
max-value (or max-value 1)
|
||||
dragging? (mf/use-state false)
|
||||
calculate-pos
|
||||
(fn [ev]
|
||||
(when on-change
|
||||
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
|
||||
{:keys [x y]} (-> ev dom/get-client-position)
|
||||
unit-value (if vertical?
|
||||
(math/clamp (/ (- bottom y) (- bottom top)) 0 1)
|
||||
(math/clamp (/ (- x left) (- right left)) 0 1))
|
||||
unit-value (if reverse?
|
||||
(math/abs (- unit-value 1.0))
|
||||
unit-value)
|
||||
value (+ min-value (* unit-value (- max-value min-value)))]
|
||||
(on-change (math/precision value 2)))))]
|
||||
|
||||
[:div.slider-selector
|
||||
{:class (str (if vertical? "vertical " "") class)
|
||||
:on-mouse-down #(reset! dragging? true)
|
||||
:on-mouse-up #(reset! dragging? false)
|
||||
:on-pointer-down (partial dom/capture-pointer)
|
||||
:on-pointer-up (partial dom/release-pointer)
|
||||
:on-click calculate-pos
|
||||
:on-mouse-move #(when @dragging? (calculate-pos %))}
|
||||
|
||||
(let [value-percent (* (/ (- value min-value)
|
||||
(- max-value min-value)) 100)
|
||||
|
||||
value-percent (if reverse?
|
||||
(math/abs (- value-percent 100))
|
||||
value-percent)
|
||||
value-percent-str (str value-percent "%")
|
||||
|
||||
style-common #js {:pointerEvents "none"}
|
||||
style-horizontal (obj/merge! #js {:left value-percent-str} style-common)
|
||||
style-vertical (obj/merge! #js {:bottom value-percent-str} style-common)]
|
||||
[:div.handler {:style (if vertical? style-vertical style-horizontal)}])]))
|
||||
|
||||
|
||||
(defn create-color-wheel
|
||||
[canvas-node]
|
||||
(let [ctx (.getContext canvas-node "2d")
|
||||
width (obj/get canvas-node "width")
|
||||
height (obj/get canvas-node "height")
|
||||
radius (/ width 2)
|
||||
cx (/ width 2)
|
||||
cy (/ width 2)
|
||||
step 0.2]
|
||||
|
||||
(.clearRect ctx 0 0 width height)
|
||||
|
||||
(doseq [degrees (range 0 360 step)]
|
||||
(let [degrees-rad (math/radians degrees)
|
||||
x (* radius (math/cos (- degrees-rad)))
|
||||
y (* radius (math/sin (- degrees-rad)))]
|
||||
(obj/set! ctx "strokeStyle" (str/format "hsl(%s, 100%, 50%)" degrees))
|
||||
(.beginPath ctx)
|
||||
(.moveTo ctx cx cy)
|
||||
(.lineTo ctx (+ cx x) (+ cy y))
|
||||
(.stroke ctx)))
|
||||
|
||||
(let [grd (.createRadialGradient ctx cx cy 0 cx cx radius)]
|
||||
(.addColorStop grd 0 "white")
|
||||
(.addColorStop grd 1 "rgba(255, 255, 255, 0")
|
||||
(obj/set! ctx "fillStyle" grd)
|
||||
|
||||
(.beginPath ctx)
|
||||
(.arc ctx cx cy radius 0 (* 2 math/PI) true)
|
||||
(.closePath ctx)
|
||||
(.fill ctx))))
|
||||
|
||||
(mf/defc ramp-selector [{:keys [color on-change]}]
|
||||
(let [{hue :h saturation :s value :v alpha :alpha} color
|
||||
|
||||
on-change-value-saturation
|
||||
(fn [new-saturation new-value]
|
||||
(let [hex (uc/hsv->hex [hue new-saturation new-value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:s new-saturation
|
||||
:v new-value})))
|
||||
|
||||
on-change-hue
|
||||
(fn [new-hue]
|
||||
(let [hex (uc/hsv->hex [new-hue saturation value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:h new-hue} )))
|
||||
|
||||
on-change-opacity
|
||||
(fn [new-opacity]
|
||||
(on-change {:alpha new-opacity} ))]
|
||||
[:*
|
||||
[:& value-saturation-selector
|
||||
{:hue hue
|
||||
:saturation saturation
|
||||
:value value
|
||||
:on-change on-change-value-saturation}]
|
||||
|
||||
[:div.shade-selector
|
||||
[:div.color-bullet]
|
||||
[:& slider-selector {:class "hue"
|
||||
:max-value 360
|
||||
:value hue
|
||||
:on-change on-change-hue}]
|
||||
|
||||
[:& slider-selector {:class "opacity"
|
||||
:max-value 1
|
||||
:value alpha
|
||||
:on-change on-change-opacity}]]]))
|
||||
|
||||
(defn color->point
|
||||
[canvas-side hue saturation]
|
||||
(let [hue-rad (math/radians (- hue))
|
||||
comp-x (* saturation (math/cos hue-rad))
|
||||
comp-y (* saturation (math/sin hue-rad))
|
||||
x (+ (/ canvas-side 2) (* comp-x (/ canvas-side 2)))
|
||||
y (+ (/ canvas-side 2) (* comp-y (/ canvas-side 2)))]
|
||||
(gpt/point x y)))
|
||||
|
||||
(mf/defc harmony-selector [{:keys [color on-change]}]
|
||||
(let [canvas-ref (mf/use-ref nil)
|
||||
{hue :h saturation :s value :v alpha :alpha} color
|
||||
|
||||
canvas-side 152
|
||||
pos-current (color->point canvas-side hue saturation)
|
||||
pos-complement (color->point canvas-side (mod (+ hue 180) 360) saturation)
|
||||
dragging? (mf/use-state false)
|
||||
|
||||
calculate-pos (fn [ev]
|
||||
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
|
||||
{:keys [x y]} (-> ev dom/get-client-position)
|
||||
px (math/clamp (/ (- x left) (- right left)) 0 1)
|
||||
py (math/clamp (/ (- y top) (- bottom top)) 0 1)
|
||||
|
||||
px (- (* 2 px) 1)
|
||||
py (- (* 2 py) 1)
|
||||
|
||||
angle (math/degrees (math/atan2 px py))
|
||||
new-hue (math/precision (mod (- angle 90 ) 360) 2)
|
||||
new-saturation (math/clamp (math/distance [px py] [0 0]) 0 1)
|
||||
hex (uc/hsv->hex [new-hue new-saturation value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:h new-hue
|
||||
:s new-saturation})))
|
||||
|
||||
on-change-value (fn [new-value]
|
||||
(let [hex (uc/hsv->hex [hue saturation new-value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:v new-value})))
|
||||
on-complement-click (fn [ev]
|
||||
(let [new-hue (mod (+ hue 180) 360)
|
||||
hex (uc/hsv->hex [new-hue saturation value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:h new-hue
|
||||
:s saturation})))
|
||||
|
||||
on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps canvas-ref)
|
||||
(fn [] (when canvas-ref
|
||||
(create-color-wheel (mf/ref-val canvas-ref)))))
|
||||
|
||||
[:div.harmony-selector
|
||||
[:div.hue-wheel-wrapper
|
||||
[:canvas.hue-wheel
|
||||
{:ref canvas-ref
|
||||
:width canvas-side
|
||||
:height canvas-side
|
||||
:on-mouse-down #(reset! dragging? true)
|
||||
:on-mouse-up #(reset! dragging? false)
|
||||
:on-pointer-down (partial dom/capture-pointer)
|
||||
:on-pointer-up (partial dom/release-pointer)
|
||||
:on-click calculate-pos
|
||||
:on-mouse-move #(when @dragging? (calculate-pos %))}]
|
||||
[:div.handler {:style {:pointer-events "none"
|
||||
:left (:x pos-current)
|
||||
:top (:y pos-current)}}]
|
||||
[:div.handler.complement {:style {:left (:x pos-complement)
|
||||
:top (:y pos-complement)
|
||||
:cursor "pointer"}
|
||||
:on-click on-complement-click}]]
|
||||
[:div.handlers-wrapper
|
||||
[:& slider-selector {:class "value"
|
||||
:vertical? true
|
||||
:reverse? true
|
||||
:value value
|
||||
:max-value 255
|
||||
:vertical true
|
||||
:on-change on-change-value}]
|
||||
[:& slider-selector {:class "opacity"
|
||||
:vertical? true
|
||||
:value alpha
|
||||
:max-value 1
|
||||
:vertical true
|
||||
:on-change on-change-opacity}]]]))
|
||||
|
||||
(mf/defc hsva-selector [{:keys [color on-change]}]
|
||||
(let [{hue :h saturation :s value :v alpha :alpha} color
|
||||
handle-change-slider (fn [key]
|
||||
(fn [new-value]
|
||||
(let [change (hash-map key new-value)
|
||||
{:keys [h s v]} (merge color change)
|
||||
hex (uc/hsv->hex [h s v])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change (merge change
|
||||
{:hex hex
|
||||
:r r :g g :b b})))))
|
||||
on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))]
|
||||
[:div.hsva-selector
|
||||
[:span.hsva-selector-label "H"]
|
||||
[:& slider-selector
|
||||
{:class "hue" :max-value 360 :value hue :on-change (handle-change-slider :h)}]
|
||||
|
||||
[:span.hsva-selector-label "S"]
|
||||
[:& slider-selector
|
||||
{:class "saturation" :max-value 1 :value saturation :on-change (handle-change-slider :s)}]
|
||||
|
||||
[:span.hsva-selector-label "V"]
|
||||
[:& slider-selector
|
||||
{:class "value" :reverse? true :max-value 255 :value value :on-change (handle-change-slider :v)}]
|
||||
|
||||
[:span.hsva-selector-label "A"]
|
||||
[:& slider-selector
|
||||
{:class "opacity" :max-value 1 :value alpha :on-change on-change-opacity}]]))
|
||||
|
||||
(mf/defc color-inputs [{:keys [type color on-change]}]
|
||||
(let [{red :r green :g blue :b
|
||||
hue :h saturation :s value :v
|
||||
hex :hex alpha :alpha} color
|
||||
|
||||
parse-hex (fn [val] (if (= (first val) \#) val (str \# val)))
|
||||
|
||||
refs {:hex (mf/use-ref nil)
|
||||
:r (mf/use-ref nil)
|
||||
:g (mf/use-ref nil)
|
||||
:b (mf/use-ref nil)
|
||||
:h (mf/use-ref nil)
|
||||
:s (mf/use-ref nil)
|
||||
:v (mf/use-ref nil)
|
||||
:alpha (mf/use-ref nil)}
|
||||
|
||||
on-change-hex
|
||||
(fn [e]
|
||||
(let [val (-> e dom/get-target-val parse-hex)]
|
||||
(when (uc/hex? val)
|
||||
(let [[r g b] (uc/hex->rgb val)
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(on-change {:hex val
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b})))))
|
||||
|
||||
on-change-property
|
||||
(fn [property max-value]
|
||||
(fn [e]
|
||||
(let [val (-> e dom/get-target-val (math/clamp 0 max-value))
|
||||
val (if (#{:s} property) (/ val 100) val)]
|
||||
(when (not (nil? val))
|
||||
(if (#{:r :g :b} property)
|
||||
(let [{:keys [r g b]} (merge color (hash-map property val))
|
||||
hex (uc/rgb->hex [r g b])
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(on-change {:hex hex
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b}))
|
||||
|
||||
(let [{:keys [h s v]} (merge color (hash-map property val))
|
||||
hex (uc/hsv->hex [h s v])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b})))))))
|
||||
|
||||
on-change-opacity
|
||||
(fn [e]
|
||||
(when-let [new-alpha (-> e dom/get-target-val (math/clamp 0 100) (/ 100))]
|
||||
(on-change {:alpha new-alpha})))]
|
||||
|
||||
|
||||
;; Updates the inputs values when a property is changed in the parent
|
||||
(mf/use-effect
|
||||
(mf/deps color type)
|
||||
(fn []
|
||||
(doseq [ref-key (keys refs)]
|
||||
(let [property-val (get color ref-key)
|
||||
property-ref (get refs ref-key)]
|
||||
(when (and property-val property-ref)
|
||||
(when-let [node (mf/ref-val property-ref)]
|
||||
(case ref-key
|
||||
(:s :alpha) (dom/set-value! node (math/round (* property-val 100)))
|
||||
:hex (dom/set-value! node property-val)
|
||||
(dom/set-value! node (math/round property-val)))))))))
|
||||
|
||||
[:div.color-values
|
||||
[:input {:id "hex-value"
|
||||
:ref (:hex refs)
|
||||
:default-value hex
|
||||
:on-change on-change-hex}]
|
||||
|
||||
(if (= type :rgb)
|
||||
[:*
|
||||
[:input {:id "red-value"
|
||||
:ref (:r refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 255
|
||||
:default-value red
|
||||
:on-change (on-change-property :r 255)}]
|
||||
|
||||
[:input {:id "green-value"
|
||||
:ref (:g refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 255
|
||||
:default-value green
|
||||
:on-change (on-change-property :g 255)}]
|
||||
|
||||
[:input {:id "blue-value"
|
||||
:ref (:b refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 255
|
||||
:default-value blue
|
||||
:on-change (on-change-property :b 255)}]]
|
||||
[:*
|
||||
[:input {:id "hue-value"
|
||||
:ref (:h refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 360
|
||||
:default-value hue
|
||||
:on-change (on-change-property :h 360)}]
|
||||
|
||||
[:input {:id "saturation-value"
|
||||
:ref (:s refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 100
|
||||
:step 1
|
||||
:default-value saturation
|
||||
:on-change (on-change-property :s 100)}]
|
||||
|
||||
[:input {:id "value-value"
|
||||
:ref (:v refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 255
|
||||
:default-value value
|
||||
:on-change (on-change-property :v 255)}]])
|
||||
|
||||
[:input.alpha-value {:id "alpha-value"
|
||||
:ref (:alpha refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:step 1
|
||||
:max 100
|
||||
:default-value (if (= alpha :multiple) "" (math/precision alpha 2))
|
||||
:on-change on-change-opacity}]
|
||||
|
||||
[:label.hex-label {:for "hex-value"} "HEX"]
|
||||
(if (= type :rgb)
|
||||
[:*
|
||||
[:label.red-label {:for "red-value"} "R"]
|
||||
[:label.green-label {:for "green-value"} "G"]
|
||||
[:label.blue-label {:for "blue-value"} "B"]]
|
||||
[:*
|
||||
[:label.red-label {:for "hue-value"} "H"]
|
||||
[:label.green-label {:for "saturation-value"} "S"]
|
||||
[:label.blue-label {:for "value-value"} "V"]])
|
||||
[:label.alpha-label {:for "alpha-value"} "A"]]))
|
||||
|
||||
(defn color->components [value opacity]
|
||||
(let [value (if (uc/hex? value) value "#000000")
|
||||
[r g b] (uc/hex->rgb value)
|
||||
|
@ -513,15 +116,10 @@
|
|||
[{:keys [data on-change on-accept]}]
|
||||
(let [state (mf/use-state (data->state data))
|
||||
active-tab (mf/use-state :ramp #_:harmony #_:hsva)
|
||||
selected-library (mf/use-state "recent")
|
||||
current-library-colors (mf/use-state [])
|
||||
locale (mf/deref i18n/locale)
|
||||
|
||||
ref-picker (mf/use-ref)
|
||||
|
||||
file-colors (mf/deref refs/workspace-file-colors)
|
||||
shared-libs (mf/deref refs/workspace-libraries)
|
||||
recent-colors (mf/deref refs/workspace-recent-colors)
|
||||
|
||||
picking-color? (mf/deref picking-color?)
|
||||
picked-color (mf/deref picked-color)
|
||||
picked-color-select (mf/deref picked-color-select)
|
||||
|
@ -529,18 +127,10 @@
|
|||
|
||||
editing-spot-state (mf/deref editing-spot-state-ref)
|
||||
|
||||
locale (mf/deref i18n/locale)
|
||||
|
||||
;; data-ref (mf/use-var data)
|
||||
|
||||
current-color (:current-color @state)
|
||||
|
||||
parse-selected
|
||||
(fn [selected]
|
||||
(if (#{"recent" "file"} selected)
|
||||
(keyword selected)
|
||||
(uuid selected)) )
|
||||
|
||||
change-tab
|
||||
(fn [tab]
|
||||
#(reset! active-tab tab))
|
||||
|
@ -565,6 +155,9 @@
|
|||
(swap! state assoc :editing-stop offset)
|
||||
(st/emit! (dc/select-gradient-stop offset))))
|
||||
|
||||
on-select-library-color
|
||||
(fn [color] (prn "color" color))
|
||||
|
||||
on-activate-gradient
|
||||
(fn [type]
|
||||
(fn []
|
||||
|
@ -609,30 +202,6 @@
|
|||
(dom/set-css-property node "--saturation-grad-from" (format-hsl hsl-from))
|
||||
(dom/set-css-property node "--saturation-grad-to" (format-hsl hsl-to)))))
|
||||
|
||||
;; Load library colors when the select is changed
|
||||
(mf/use-effect
|
||||
(mf/deps @selected-library)
|
||||
(fn []
|
||||
(let [mapped-colors
|
||||
(cond
|
||||
(= @selected-library "recent")
|
||||
(map #(hash-map :value %) (reverse (or recent-colors [])))
|
||||
|
||||
(= @selected-library "file")
|
||||
(map #(select-keys % [:id :value]) (vals file-colors))
|
||||
|
||||
:else ;; Library UUID
|
||||
(map #(merge {:file-id (uuid @selected-library)} (select-keys % [:id :value]))
|
||||
(vals (get-in shared-libs [(uuid @selected-library) :data :colors]))))]
|
||||
(reset! current-library-colors (into [] mapped-colors)))))
|
||||
|
||||
;; If the file colors change and the file option is selected updates the state
|
||||
(mf/use-effect
|
||||
(mf/deps file-colors)
|
||||
(fn [] (when (= @selected-library "file")
|
||||
(let [colors (map #(select-keys % [:id :value]) (vals file-colors))]
|
||||
(reset! current-library-colors (into [] colors))))))
|
||||
|
||||
;; When closing the modal we update the recent-color list
|
||||
#_(mf/use-effect
|
||||
(fn [] (fn []
|
||||
|
@ -646,7 +215,7 @@
|
|||
hex (uc/rgb->hex [r g b])
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
|
||||
(swap! update :current-color assoc
|
||||
(swap! state update :current-color assoc
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v
|
||||
:hex hex)
|
||||
|
@ -669,8 +238,8 @@
|
|||
|
||||
(mf/use-effect
|
||||
(mf/deps data)
|
||||
#(let [gradient-data (-> data data->state :gradient-data)]
|
||||
(swap! state assoc :gradient-data gradient-data)))
|
||||
#(if-let [gradient-data (-> data data->state :gradient-data)]
|
||||
(swap! state assoc :gradient-data gradient-data)))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps @state)
|
||||
|
@ -696,29 +265,10 @@
|
|||
{:on-click (on-activate-gradient :radial-gradient)
|
||||
:class (when (= :radial-gradient (:type @state)) "active")}]]]
|
||||
|
||||
(when (#{:linear-gradient :radial-gradient} (:type @state))
|
||||
[:div.gradient-stops
|
||||
(let [format-stop (fn [[offset {:keys [r g b alpha]}]]
|
||||
(str/fmt "rgba(%s, %s, %s, %s) %s"
|
||||
r g b alpha
|
||||
(str (* offset 100) "%")))
|
||||
gradient-data (str/join "," (map format-stop (:stops @state)))
|
||||
|
||||
]
|
||||
[:div.gradient-background-wrapper
|
||||
[:div.gradient-background {:style {:background (str/fmt "linear-gradient(90deg, %s)" gradient-data) }}]])
|
||||
[:div.gradient-stop-wrapper
|
||||
(for [[offset value] (:stops @state)]
|
||||
[:div.gradient-stop {:class (when (= (:editing-stop @state) offset) "active")
|
||||
:on-click (partial handle-change-stop offset)
|
||||
:style {:left (str (* offset 100) "%")}}
|
||||
|
||||
(let [{:keys [hex r g b alpha]} value]
|
||||
[:*
|
||||
[:div.gradient-stop-color {:style {:background-color hex}}]
|
||||
[:div.gradient-stop-alpha {:style {:background-color (str/format "rgba(%s, %s, %s, %s)" r g b alpha)}}]])
|
||||
|
||||
])]])
|
||||
[:& gradients {:type (:type @state)
|
||||
:stops (:stops @state)
|
||||
:editing-stop (:editing-stop @state)
|
||||
:on-select-stop handle-change-stop}]
|
||||
|
||||
(if picking-color?
|
||||
[:div.picker-detail-wrapper
|
||||
|
@ -730,42 +280,12 @@
|
|||
:hsva [:& hsva-selector {:color current-color :on-change handle-change-color}]
|
||||
nil))
|
||||
|
||||
[:& color-inputs {:type (if (= @active-tab :hsva) :hsv :rgb) :color current-color :on-change handle-change-color}]
|
||||
[:& color-inputs {:type (if (= @active-tab :hsva) :hsv :rgb)
|
||||
:color current-color
|
||||
:on-change handle-change-color}]
|
||||
|
||||
[:div.libraries
|
||||
[:select {:on-change (fn [e]
|
||||
(let [val (-> e dom/get-target dom/get-value)]
|
||||
(reset! selected-library val)))
|
||||
:value @selected-library}
|
||||
[:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")]
|
||||
[:option {:value "file"} (t locale "workspace.libraries.colors.file-library")]
|
||||
(for [[_ {:keys [name id]}] shared-libs]
|
||||
[:option {:key id
|
||||
:value id} name])]
|
||||
|
||||
[:div.selected-colors
|
||||
(when (= "file" @selected-library)
|
||||
[:div.color-bullet.button.plus-button {:style {:background-color "white"}
|
||||
:on-click #(st/emit! (dwl/add-color (:hex current-color)))}
|
||||
i/plus])
|
||||
|
||||
[:div.color-bullet.button {:style {:background-color "white"}
|
||||
:on-click #(st/emit! (dc/show-palette (parse-selected @selected-library)))}
|
||||
i/palette]
|
||||
|
||||
(for [[idx {:keys [id file-id value]}] (map-indexed vector @current-library-colors)]
|
||||
[:div.color-bullet {:key (str "color-" idx)
|
||||
:on-click (fn []
|
||||
(swap! update :current-color assoc :hex value)
|
||||
#_(reset! value-ref value)
|
||||
(let [[r g b] (uc/hex->rgb value)
|
||||
[h s v] (uc/hex->hsv value)]
|
||||
(swap! update current-color assoc
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v)
|
||||
;; TODO: CHANGE TO SUPPORT GRADIENTS
|
||||
#_(on-change value (:alpha current-color) id file-id)))
|
||||
:style {:background-color value}}])]]]
|
||||
[:& libraries {:current-color current-color
|
||||
:on-select-color on-select-library-color}]]
|
||||
[:div.colorpicker-tabs
|
||||
[:div.colorpicker-tab {:class (when (= @active-tab :ramp) "active")
|
||||
:on-click (change-tab :ramp)} i/picker-ramp]
|
||||
|
|
172
frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs
Normal file
172
frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs
Normal file
|
@ -0,0 +1,172 @@
|
|||
;; 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.color-inputs
|
||||
(: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]]))
|
||||
|
||||
(mf/defc color-inputs [{:keys [type color on-change]}]
|
||||
(let [{red :r green :g blue :b
|
||||
hue :h saturation :s value :v
|
||||
hex :hex alpha :alpha} color
|
||||
|
||||
parse-hex (fn [val] (if (= (first val) \#) val (str \# val)))
|
||||
|
||||
refs {:hex (mf/use-ref nil)
|
||||
:r (mf/use-ref nil)
|
||||
:g (mf/use-ref nil)
|
||||
:b (mf/use-ref nil)
|
||||
:h (mf/use-ref nil)
|
||||
:s (mf/use-ref nil)
|
||||
:v (mf/use-ref nil)
|
||||
:alpha (mf/use-ref nil)}
|
||||
|
||||
on-change-hex
|
||||
(fn [e]
|
||||
(let [val (-> e dom/get-target-val parse-hex)]
|
||||
(when (uc/hex? val)
|
||||
(let [[r g b] (uc/hex->rgb val)
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(on-change {:hex val
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b})))))
|
||||
|
||||
on-change-property
|
||||
(fn [property max-value]
|
||||
(fn [e]
|
||||
(let [val (-> e dom/get-target-val (math/clamp 0 max-value))
|
||||
val (if (#{:s} property) (/ val 100) val)]
|
||||
(when (not (nil? val))
|
||||
(if (#{:r :g :b} property)
|
||||
(let [{:keys [r g b]} (merge color (hash-map property val))
|
||||
hex (uc/rgb->hex [r g b])
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(on-change {:hex hex
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b}))
|
||||
|
||||
(let [{:keys [h s v]} (merge color (hash-map property val))
|
||||
hex (uc/hsv->hex [h s v])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b})))))))
|
||||
|
||||
on-change-opacity
|
||||
(fn [e]
|
||||
(when-let [new-alpha (-> e dom/get-target-val (math/clamp 0 100) (/ 100))]
|
||||
(on-change {:alpha new-alpha})))]
|
||||
|
||||
|
||||
;; Updates the inputs values when a property is changed in the parent
|
||||
(mf/use-effect
|
||||
(mf/deps color type)
|
||||
(fn []
|
||||
(doseq [ref-key (keys refs)]
|
||||
(let [property-val (get color ref-key)
|
||||
property-ref (get refs ref-key)]
|
||||
(when (and property-val property-ref)
|
||||
(when-let [node (mf/ref-val property-ref)]
|
||||
(case ref-key
|
||||
(:s :alpha) (dom/set-value! node (math/round (* property-val 100)))
|
||||
:hex (dom/set-value! node property-val)
|
||||
(dom/set-value! node (math/round property-val)))))))))
|
||||
|
||||
[:div.color-values
|
||||
[:input {:id "hex-value"
|
||||
:ref (:hex refs)
|
||||
:default-value hex
|
||||
:on-change on-change-hex}]
|
||||
|
||||
(if (= type :rgb)
|
||||
[:*
|
||||
[:input {:id "red-value"
|
||||
:ref (:r refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 255
|
||||
:default-value red
|
||||
:on-change (on-change-property :r 255)}]
|
||||
|
||||
[:input {:id "green-value"
|
||||
:ref (:g refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 255
|
||||
:default-value green
|
||||
:on-change (on-change-property :g 255)}]
|
||||
|
||||
[:input {:id "blue-value"
|
||||
:ref (:b refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 255
|
||||
:default-value blue
|
||||
:on-change (on-change-property :b 255)}]]
|
||||
[:*
|
||||
[:input {:id "hue-value"
|
||||
:ref (:h refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 360
|
||||
:default-value hue
|
||||
:on-change (on-change-property :h 360)}]
|
||||
|
||||
[:input {:id "saturation-value"
|
||||
:ref (:s refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 100
|
||||
:step 1
|
||||
:default-value saturation
|
||||
:on-change (on-change-property :s 100)}]
|
||||
|
||||
[:input {:id "value-value"
|
||||
:ref (:v refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 255
|
||||
:default-value value
|
||||
:on-change (on-change-property :v 255)}]])
|
||||
|
||||
[:input.alpha-value {:id "alpha-value"
|
||||
:ref (:alpha refs)
|
||||
:type "number"
|
||||
:min 0
|
||||
:step 1
|
||||
:max 100
|
||||
:default-value (if (= alpha :multiple) "" (math/precision alpha 2))
|
||||
:on-change on-change-opacity}]
|
||||
|
||||
[:label.hex-label {:for "hex-value"} "HEX"]
|
||||
(if (= type :rgb)
|
||||
[:*
|
||||
[:label.red-label {:for "red-value"} "R"]
|
||||
[:label.green-label {:for "green-value"} "G"]
|
||||
[:label.blue-label {:for "blue-value"} "B"]]
|
||||
[:*
|
||||
[:label.red-label {:for "hue-value"} "H"]
|
||||
[:label.green-label {:for "saturation-value"} "S"]
|
||||
[:label.blue-label {:for "value-value"} "V"]])
|
||||
[:label.alpha-label {:for "alpha-value"} "A"]]))
|
|
@ -0,0 +1,54 @@
|
|||
;; 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.gradients
|
||||
(: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]]))
|
||||
|
||||
(defn gradient->string [stops]
|
||||
(let [format-stop
|
||||
(fn [[offset {:keys [r g b alpha]}]]
|
||||
(str/fmt "rgba(%s, %s, %s, %s) %s"
|
||||
r g b alpha (str (* offset 100) "%")))
|
||||
|
||||
gradient-css (str/join "," (map format-stop stops))]
|
||||
(str/fmt "linear-gradient(90deg, %s)" gradient-css)))
|
||||
|
||||
(mf/defc gradients [{:keys [type stops editing-stop on-select-stop]}]
|
||||
(when (#{:linear-gradient :radial-gradient} type)
|
||||
[:div.gradient-stops
|
||||
[:div.gradient-background-wrapper
|
||||
[:div.gradient-background {:style {:background (gradient->string stops)}}]]
|
||||
|
||||
[:div.gradient-stop-wrapper
|
||||
(for [[offset value] stops]
|
||||
[:div.gradient-stop
|
||||
{:class (when (= editing-stop offset) "active")
|
||||
:on-click (partial on-select-stop offset)
|
||||
:style {:left (str (* offset 100) "%")}}
|
||||
|
||||
(let [{:keys [hex r g b alpha]} value]
|
||||
[:*
|
||||
[:div.gradient-stop-color {:style {:background-color hex}}]
|
||||
[:div.gradient-stop-alpha {:style {:background-color (str/format "rgba(%s, %s, %s, %s)" r g b alpha)}}]])])]]))
|
154
frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs
Normal file
154
frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs
Normal file
|
@ -0,0 +1,154 @@
|
|||
;; 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.harmony
|
||||
(: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]]
|
||||
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]))
|
||||
|
||||
|
||||
(defn create-color-wheel
|
||||
[canvas-node]
|
||||
(let [ctx (.getContext canvas-node "2d")
|
||||
width (obj/get canvas-node "width")
|
||||
height (obj/get canvas-node "height")
|
||||
radius (/ width 2)
|
||||
cx (/ width 2)
|
||||
cy (/ width 2)
|
||||
step 0.2]
|
||||
|
||||
(.clearRect ctx 0 0 width height)
|
||||
|
||||
(doseq [degrees (range 0 360 step)]
|
||||
(let [degrees-rad (math/radians degrees)
|
||||
x (* radius (math/cos (- degrees-rad)))
|
||||
y (* radius (math/sin (- degrees-rad)))]
|
||||
(obj/set! ctx "strokeStyle" (str/format "hsl(%s, 100%, 50%)" degrees))
|
||||
(.beginPath ctx)
|
||||
(.moveTo ctx cx cy)
|
||||
(.lineTo ctx (+ cx x) (+ cy y))
|
||||
(.stroke ctx)))
|
||||
|
||||
(let [grd (.createRadialGradient ctx cx cy 0 cx cx radius)]
|
||||
(.addColorStop grd 0 "white")
|
||||
(.addColorStop grd 1 "rgba(255, 255, 255, 0")
|
||||
(obj/set! ctx "fillStyle" grd)
|
||||
|
||||
(.beginPath ctx)
|
||||
(.arc ctx cx cy radius 0 (* 2 math/PI) true)
|
||||
(.closePath ctx)
|
||||
(.fill ctx))))
|
||||
|
||||
(defn color->point
|
||||
[canvas-side hue saturation]
|
||||
(let [hue-rad (math/radians (- hue))
|
||||
comp-x (* saturation (math/cos hue-rad))
|
||||
comp-y (* saturation (math/sin hue-rad))
|
||||
x (+ (/ canvas-side 2) (* comp-x (/ canvas-side 2)))
|
||||
y (+ (/ canvas-side 2) (* comp-y (/ canvas-side 2)))]
|
||||
(gpt/point x y)))
|
||||
|
||||
(mf/defc harmony-selector [{:keys [color on-change]}]
|
||||
(let [canvas-ref (mf/use-ref nil)
|
||||
{hue :h saturation :s value :v alpha :alpha} color
|
||||
|
||||
canvas-side 152
|
||||
pos-current (color->point canvas-side hue saturation)
|
||||
pos-complement (color->point canvas-side (mod (+ hue 180) 360) saturation)
|
||||
dragging? (mf/use-state false)
|
||||
|
||||
calculate-pos (fn [ev]
|
||||
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
|
||||
{:keys [x y]} (-> ev dom/get-client-position)
|
||||
px (math/clamp (/ (- x left) (- right left)) 0 1)
|
||||
py (math/clamp (/ (- y top) (- bottom top)) 0 1)
|
||||
|
||||
px (- (* 2 px) 1)
|
||||
py (- (* 2 py) 1)
|
||||
|
||||
angle (math/degrees (math/atan2 px py))
|
||||
new-hue (math/precision (mod (- angle 90 ) 360) 2)
|
||||
new-saturation (math/clamp (math/distance [px py] [0 0]) 0 1)
|
||||
hex (uc/hsv->hex [new-hue new-saturation value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:h new-hue
|
||||
:s new-saturation})))
|
||||
|
||||
on-change-value (fn [new-value]
|
||||
(let [hex (uc/hsv->hex [hue saturation new-value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:v new-value})))
|
||||
on-complement-click (fn [ev]
|
||||
(let [new-hue (mod (+ hue 180) 360)
|
||||
hex (uc/hsv->hex [new-hue saturation value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:h new-hue
|
||||
:s saturation})))
|
||||
|
||||
on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps canvas-ref)
|
||||
(fn [] (when canvas-ref
|
||||
(create-color-wheel (mf/ref-val canvas-ref)))))
|
||||
|
||||
[:div.harmony-selector
|
||||
[:div.hue-wheel-wrapper
|
||||
[:canvas.hue-wheel
|
||||
{:ref canvas-ref
|
||||
:width canvas-side
|
||||
:height canvas-side
|
||||
:on-mouse-down #(reset! dragging? true)
|
||||
:on-mouse-up #(reset! dragging? false)
|
||||
:on-pointer-down (partial dom/capture-pointer)
|
||||
:on-pointer-up (partial dom/release-pointer)
|
||||
:on-click calculate-pos
|
||||
:on-mouse-move #(when @dragging? (calculate-pos %))}]
|
||||
[:div.handler {:style {:pointer-events "none"
|
||||
:left (:x pos-current)
|
||||
:top (:y pos-current)}}]
|
||||
[:div.handler.complement {:style {:left (:x pos-complement)
|
||||
:top (:y pos-complement)
|
||||
:cursor "pointer"}
|
||||
:on-click on-complement-click}]]
|
||||
[:div.handlers-wrapper
|
||||
[:& slider-selector {:class "value"
|
||||
:vertical? true
|
||||
:reverse? true
|
||||
:value value
|
||||
:max-value 255
|
||||
:vertical true
|
||||
:on-change on-change-value}]
|
||||
[:& slider-selector {:class "opacity"
|
||||
:vertical? true
|
||||
:value alpha
|
||||
:max-value 1
|
||||
:vertical true
|
||||
:on-change on-change-opacity}]]]))
|
57
frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs
Normal file
57
frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs
Normal file
|
@ -0,0 +1,57 @@
|
|||
;; 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.hsva
|
||||
(: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]]
|
||||
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]))
|
||||
|
||||
(mf/defc hsva-selector [{:keys [color on-change]}]
|
||||
(let [{hue :h saturation :s value :v alpha :alpha} color
|
||||
handle-change-slider (fn [key]
|
||||
(fn [new-value]
|
||||
(let [change (hash-map key new-value)
|
||||
{:keys [h s v]} (merge color change)
|
||||
hex (uc/hsv->hex [h s v])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change (merge change
|
||||
{:hex hex
|
||||
:r r :g g :b b})))))
|
||||
on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))]
|
||||
[:div.hsva-selector
|
||||
[:span.hsva-selector-label "H"]
|
||||
[:& slider-selector
|
||||
{:class "hue" :max-value 360 :value hue :on-change (handle-change-slider :h)}]
|
||||
|
||||
[:span.hsva-selector-label "S"]
|
||||
[:& slider-selector
|
||||
{:class "saturation" :max-value 1 :value saturation :on-change (handle-change-slider :s)}]
|
||||
|
||||
[:span.hsva-selector-label "V"]
|
||||
[:& slider-selector
|
||||
{:class "value" :reverse? true :max-value 255 :value value :on-change (handle-change-slider :v)}]
|
||||
|
||||
[:span.hsva-selector-label "A"]
|
||||
[:& slider-selector
|
||||
{:class "opacity" :max-value 1 :value alpha :on-change on-change-opacity}]]))
|
100
frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs
Normal file
100
frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs
Normal file
|
@ -0,0 +1,100 @@
|
|||
;; 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.libraries
|
||||
(: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]]
|
||||
[app.main.ui.workspace.colorpicker.gradients :refer [gradients]]
|
||||
[app.main.ui.workspace.colorpicker.harmony :refer [harmony-selector]]
|
||||
[app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]]
|
||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]]
|
||||
[app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]]))
|
||||
|
||||
(mf/defc libraries [{:keys [current-color on-select-color]}]
|
||||
(let [selected-library (mf/use-state "recent")
|
||||
current-library-colors (mf/use-state [])
|
||||
|
||||
shared-libs (mf/deref refs/workspace-libraries)
|
||||
file-colors (mf/deref refs/workspace-file-colors)
|
||||
recent-colors (mf/deref refs/workspace-recent-colors)
|
||||
locale (mf/deref i18n/locale)
|
||||
|
||||
parse-selected
|
||||
(fn [selected]
|
||||
(if (#{"recent" "file"} selected)
|
||||
(keyword selected)
|
||||
(uuid selected)) )]
|
||||
|
||||
;; Load library colors when the select is changed
|
||||
(mf/use-effect
|
||||
(mf/deps @selected-library)
|
||||
(fn []
|
||||
(let [mapped-colors
|
||||
(cond
|
||||
(= @selected-library "recent")
|
||||
(map #(hash-map :value %) (reverse (or recent-colors [])))
|
||||
|
||||
(= @selected-library "file")
|
||||
(map #(select-keys % [:id :value]) (vals file-colors))
|
||||
|
||||
:else ;; Library UUID
|
||||
(map #(merge {:file-id (uuid @selected-library)} (select-keys % [:id :value]))
|
||||
(vals (get-in shared-libs [(uuid @selected-library) :data :colors]))))]
|
||||
(reset! current-library-colors (into [] mapped-colors)))))
|
||||
|
||||
;; If the file colors change and the file option is selected updates the state
|
||||
(mf/use-effect
|
||||
(mf/deps file-colors)
|
||||
(fn [] (when (= @selected-library "file")
|
||||
(let [colors (map #(select-keys % [:id :value]) (vals file-colors))]
|
||||
(reset! current-library-colors (into [] colors))))))
|
||||
|
||||
|
||||
[:div.libraries
|
||||
[:select {:on-change (fn [e]
|
||||
(when-let [val (dom/get-target-val e)]
|
||||
(reset! selected-library val)))
|
||||
:value @selected-library}
|
||||
[:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")]
|
||||
[:option {:value "file"} (t locale "workspace.libraries.colors.file-library")]
|
||||
|
||||
(for [[_ {:keys [name id]}] shared-libs]
|
||||
[:option {:key id
|
||||
:value id} name])]
|
||||
|
||||
[:div.selected-colors
|
||||
(when (= "file" @selected-library)
|
||||
[:div.color-bullet.button.plus-button {:style {:background-color "white"}
|
||||
:on-click #(st/emit! (dwl/add-color current-color))}
|
||||
i/plus])
|
||||
|
||||
[:div.color-bullet.button {:style {:background-color "white"}
|
||||
:on-click #(st/emit! (dc/show-palette (parse-selected @selected-library)))}
|
||||
i/palette]
|
||||
|
||||
(for [[idx color] (map-indexed vector @current-library-colors)]
|
||||
[:div.color-bullet
|
||||
{:key (str "color-" idx)
|
||||
:on-click #(on-select-color color)
|
||||
:style {:background (uc/color->background color)}}])]]))
|
|
@ -0,0 +1,29 @@
|
|||
;; 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-overlay
|
||||
(: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]]))
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
;; 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]]))
|
||||
|
||||
|
92
frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs
Normal file
92
frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs
Normal file
|
@ -0,0 +1,92 @@
|
|||
;; 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.ramp
|
||||
(: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]]
|
||||
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]))
|
||||
|
||||
|
||||
(mf/defc value-saturation-selector [{:keys [hue saturation value on-change]}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
calculate-pos
|
||||
(fn [ev]
|
||||
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
|
||||
{:keys [x y]} (-> ev dom/get-client-position)
|
||||
px (math/clamp (/ (- x left) (- right left)) 0 1)
|
||||
py (* 255 (- 1 (math/clamp (/ (- y top) (- bottom top)) 0 1)))]
|
||||
(on-change px py)))]
|
||||
[:div.value-saturation-selector
|
||||
{:on-mouse-down #(reset! dragging? true)
|
||||
:on-mouse-up #(reset! dragging? false)
|
||||
:on-pointer-down (partial dom/capture-pointer)
|
||||
:on-pointer-up (partial dom/release-pointer)
|
||||
:on-click calculate-pos
|
||||
:on-mouse-move #(when @dragging? (calculate-pos %))}
|
||||
[:div.handler {:style {:pointer-events "none"
|
||||
:left (str (* 100 saturation) "%")
|
||||
:top (str (* 100 (- 1 (/ value 255))) "%")}}]]))
|
||||
|
||||
|
||||
(mf/defc ramp-selector [{:keys [color on-change]}]
|
||||
(let [{hue :h saturation :s value :v alpha :alpha} color
|
||||
|
||||
on-change-value-saturation
|
||||
(fn [new-saturation new-value]
|
||||
(let [hex (uc/hsv->hex [hue new-saturation new-value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:s new-saturation
|
||||
:v new-value})))
|
||||
|
||||
on-change-hue
|
||||
(fn [new-hue]
|
||||
(let [hex (uc/hsv->hex [new-hue saturation value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:h new-hue} )))
|
||||
|
||||
on-change-opacity
|
||||
(fn [new-opacity]
|
||||
(on-change {:alpha new-opacity} ))]
|
||||
[:*
|
||||
[:& value-saturation-selector
|
||||
{:hue hue
|
||||
:saturation saturation
|
||||
:value value
|
||||
:on-change on-change-value-saturation}]
|
||||
|
||||
[:div.shade-selector
|
||||
[:div.color-bullet]
|
||||
[:& slider-selector {:class "hue"
|
||||
:max-value 360
|
||||
:value hue
|
||||
:on-change on-change-hue}]
|
||||
|
||||
[:& slider-selector {:class "opacity"
|
||||
:max-value 1
|
||||
:value alpha
|
||||
:on-change on-change-opacity}]]]))
|
|
@ -0,0 +1,68 @@
|
|||
;; 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.slider-selector
|
||||
(: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]]))
|
||||
|
||||
(mf/defc slider-selector
|
||||
[{:keys [value class min-value max-value vertical? reverse? on-change]}]
|
||||
(let [min-value (or min-value 0)
|
||||
max-value (or max-value 1)
|
||||
dragging? (mf/use-state false)
|
||||
calculate-pos
|
||||
(fn [ev]
|
||||
(when on-change
|
||||
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
|
||||
{:keys [x y]} (-> ev dom/get-client-position)
|
||||
unit-value (if vertical?
|
||||
(math/clamp (/ (- bottom y) (- bottom top)) 0 1)
|
||||
(math/clamp (/ (- x left) (- right left)) 0 1))
|
||||
unit-value (if reverse?
|
||||
(math/abs (- unit-value 1.0))
|
||||
unit-value)
|
||||
value (+ min-value (* unit-value (- max-value min-value)))]
|
||||
(on-change (math/precision value 2)))))]
|
||||
|
||||
[:div.slider-selector
|
||||
{:class (str (if vertical? "vertical " "") class)
|
||||
:on-mouse-down #(reset! dragging? true)
|
||||
:on-mouse-up #(reset! dragging? false)
|
||||
:on-pointer-down (partial dom/capture-pointer)
|
||||
:on-pointer-up (partial dom/release-pointer)
|
||||
:on-click calculate-pos
|
||||
:on-mouse-move #(when @dragging? (calculate-pos %))}
|
||||
|
||||
(let [value-percent (* (/ (- value min-value)
|
||||
(- max-value min-value)) 100)
|
||||
|
||||
value-percent (if reverse?
|
||||
(math/abs (- value-percent 100))
|
||||
value-percent)
|
||||
value-percent-str (str value-percent "%")
|
||||
|
||||
style-common #js {:pointerEvents "none"}
|
||||
style-horizontal (obj/merge! #js {:left value-percent-str} style-common)
|
||||
style-vertical (obj/merge! #js {:bottom value-percent-str} style-common)]
|
||||
[:div.handler {:style (if vertical? style-vertical style-horizontal)}])]))
|
|
@ -34,18 +34,6 @@
|
|||
(modal/show! :colorpicker props))))
|
||||
|
||||
|
||||
;; TODO: REMOVE `VALUE` WHEN COLOR IS INTEGRATED
|
||||
(defn as-background [{:keys [color opacity gradient value] :as tt}]
|
||||
(cond
|
||||
(and gradient (not= :multiple gradient))
|
||||
(uc/gradient->css gradient)
|
||||
|
||||
(not= color :multiple)
|
||||
(let [[r g b] (uc/hex->rgb (or color value))]
|
||||
(str/fmt "rgba(%s, %s, %s, %s)" r g b opacity))
|
||||
|
||||
:else "transparent"))
|
||||
|
||||
(defn remove-hash [value]
|
||||
(if (or (nil? value) (= value :multiple)) "" (subs value 1)))
|
||||
|
||||
|
@ -99,6 +87,7 @@
|
|||
;; (when on-change (on-change new-value new-opacity id file-id)))
|
||||
|
||||
handle-pick-color (fn [color]
|
||||
(prn "handle-pick-color" color)
|
||||
(reset! state color)
|
||||
(when on-change
|
||||
(on-change color)))
|
||||
|
@ -136,7 +125,7 @@
|
|||
[:div.row-flex.color-data
|
||||
[:span.color-th
|
||||
{:class (when (and (:id color) (not= (:id color) :multiple)) "color-name")
|
||||
:style {:background (as-background color)}
|
||||
:style {:background (uc/color->background color)}
|
||||
:on-click (color-picker-callback @state handle-pick-color handle-open handle-close)}
|
||||
(when (= value :multiple) "?")]
|
||||
|
||||
|
@ -149,7 +138,10 @@
|
|||
;; Rendering a gradient
|
||||
(:gradient color)
|
||||
[:div.color-info
|
||||
[:div.color-name (str (get-in color [:gradient :type]))]]
|
||||
[:div.color-name
|
||||
(case (get-in color [:gradient :type])
|
||||
:linear "Linear gradient"
|
||||
:radial "Radial gradient")]]
|
||||
|
||||
;; Rendering a plain color/opacity
|
||||
:else
|
||||
|
|
|
@ -90,3 +90,16 @@
|
|||
(str/fmt "linear-gradient(to bottom, %s)" stops-css)
|
||||
(str/fmt "radial-gradient(circle, %s)" stops-css))))
|
||||
|
||||
;; TODO: REMOVE `VALUE` WHEN COLOR IS INTEGRATED
|
||||
(defn color->background [{:keys [color opacity gradient value]}]
|
||||
(let [color (or color value)
|
||||
opacity (or opacity 1)]
|
||||
(cond
|
||||
(and gradient (not= :multiple gradient))
|
||||
(gradient->css gradient)
|
||||
|
||||
(not= color :multiple)
|
||||
(let [[r g b] (hex->rgb (or color value))]
|
||||
(str/fmt "rgba(%s, %s, %s, %s)" r g b opacity))
|
||||
|
||||
:else "transparent")))
|
||||
|
|
Loading…
Add table
Reference in a new issue