mirror of
https://github.com/penpot/penpot.git
synced 2025-07-22 12:57:14 +02:00
🎉 Color picker integration with library
This commit is contained in:
parent
12a2b35b28
commit
ab7fee50ea
9 changed files with 313 additions and 146 deletions
|
@ -126,3 +126,8 @@
|
||||||
[x]
|
[x]
|
||||||
#?(:cljs (js/Math.log10 x)
|
#?(:cljs (js/Math.log10 x)
|
||||||
:clj (Math/log10 x)))
|
:clj (Math/log10 x)))
|
||||||
|
|
||||||
|
(defn clamp [num from to]
|
||||||
|
(if (< num from)
|
||||||
|
from
|
||||||
|
(if (> num to) to num)))
|
||||||
|
|
|
@ -210,6 +210,9 @@
|
||||||
(s/def :internal.file/colors
|
(s/def :internal.file/colors
|
||||||
(s/map-of ::uuid ::color))
|
(s/map-of ::uuid ::color))
|
||||||
|
|
||||||
|
(s/def :internal.file/recent-colors
|
||||||
|
(s/coll-of ::string :kind vector?))
|
||||||
|
|
||||||
(s/def :internal.file/pages
|
(s/def :internal.file/pages
|
||||||
(s/coll-of ::uuid :kind vector?))
|
(s/coll-of ::uuid :kind vector?))
|
||||||
|
|
||||||
|
@ -223,6 +226,7 @@
|
||||||
(s/keys :req-un [:internal.file/pages-index
|
(s/keys :req-un [:internal.file/pages-index
|
||||||
:internal.file/pages]
|
:internal.file/pages]
|
||||||
:opt-un [:internal.file/colors
|
:opt-un [:internal.file/colors
|
||||||
|
:internal.file/recent-colors
|
||||||
:internal.file/media]))
|
:internal.file/media]))
|
||||||
|
|
||||||
(defmulti operation-spec :type)
|
(defmulti operation-spec :type)
|
||||||
|
@ -291,6 +295,9 @@
|
||||||
(defmethod change-spec :del-color [_]
|
(defmethod change-spec :del-color [_]
|
||||||
(s/keys :req-un [::id]))
|
(s/keys :req-un [::id]))
|
||||||
|
|
||||||
|
(defmethod change-spec :add-recent-color [_]
|
||||||
|
(s/keys :req-un [:recent-color/color]))
|
||||||
|
|
||||||
(s/def :internal.changes.media/object ::media-object)
|
(s/def :internal.changes.media/object ::media-object)
|
||||||
|
|
||||||
(defmethod change-spec :add-media [_]
|
(defmethod change-spec :add-media [_]
|
||||||
|
@ -670,6 +677,15 @@
|
||||||
[data {:keys [id]}]
|
[data {:keys [id]}]
|
||||||
(update data :colors dissoc id))
|
(update data :colors dissoc id))
|
||||||
|
|
||||||
|
(defmethod process-change :add-recent-color
|
||||||
|
[data {:keys [color]}]
|
||||||
|
;; Moves the color to the top of the list and then truncates up to 15
|
||||||
|
(update data :recent-colors (fn [rc]
|
||||||
|
(let [rc (conj (filterv (comp not #{color}) (or rc [])) color)]
|
||||||
|
(if (> (count rc) 15)
|
||||||
|
(subvec rc 1)
|
||||||
|
rc)))))
|
||||||
|
|
||||||
(defmethod process-change :add-media
|
(defmethod process-change :add-media
|
||||||
[data {:keys [object]}]
|
[data {:keys [object]}]
|
||||||
(update data :media assoc (:id object) object))
|
(update data :media assoc (:id object) object))
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 6.75rem;
|
height: 6.75rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
.handler {
|
.handler {
|
||||||
box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset;
|
box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset;
|
||||||
|
@ -59,6 +60,7 @@
|
||||||
grid-template-columns: 2.5rem 1fr;
|
grid-template-columns: 2.5rem 1fr;
|
||||||
height: 3.5rem;
|
height: 3.5rem;
|
||||||
grid-row-gap: 0.5rem;
|
grid-row-gap: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-bullet {
|
.color-bullet {
|
||||||
|
@ -80,6 +82,7 @@
|
||||||
#f00 0%, #ff0 17%, #0f0 33%, #0ff 50%,
|
#f00 0%, #ff0 17%, #0f0 33%, #0ff 50%,
|
||||||
#00f 67%, #f0f 83%, #f00 100%);
|
#00f 67%, #f0f 83%, #f00 100%);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hue-selector .handler,
|
.hue-selector .handler,
|
||||||
|
@ -88,7 +91,6 @@
|
||||||
box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px;
|
box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px;
|
||||||
transform: translate(-6px, -2px);
|
transform: translate(-6px, -2px);
|
||||||
left: 50%;
|
left: 50%;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.opacity-selector {
|
.opacity-selector {
|
||||||
|
@ -198,6 +200,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
height: 1.5rem;
|
||||||
|
padding: 0 2.5rem;
|
||||||
|
font-size: $fs12;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker {
|
.color-picker {
|
||||||
|
|
|
@ -38,6 +38,15 @@
|
||||||
(rx/of #(assoc-in % [:workspace-local :color-for-rename] id)
|
(rx/of #(assoc-in % [:workspace-local :color-for-rename] id)
|
||||||
(dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
(dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||||
|
|
||||||
|
(defn add-recent-color
|
||||||
|
[color]
|
||||||
|
(us/assert ::us/string color)
|
||||||
|
(ptk/reify ::add-recent-color
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state s]
|
||||||
|
(let [rchg {:type :add-recent-color
|
||||||
|
:color color}]
|
||||||
|
(rx/of (dwc/commit-changes [rchg] [] {:commit-local? true}))))))
|
||||||
|
|
||||||
(def clear-color-for-rename
|
(def clear-color-for-rename
|
||||||
(ptk/reify ::clear-color-for-rename
|
(ptk/reify ::clear-color-for-rename
|
||||||
|
|
|
@ -99,6 +99,12 @@
|
||||||
(get-in file [:data :colors])))
|
(get-in file [:data :colors])))
|
||||||
st/state))
|
st/state))
|
||||||
|
|
||||||
|
(def workspace-recent-colors
|
||||||
|
(l/derived (fn [state]
|
||||||
|
(when-let [file (:workspace-file state)]
|
||||||
|
(get-in file [:data :recent-colors])))
|
||||||
|
st/state))
|
||||||
|
|
||||||
(def workspace-project
|
(def workspace-project
|
||||||
(l/derived :workspace-project st/state))
|
(l/derived :workspace-project st/state))
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; 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/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2020 UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.main.ui.workspace.colorpicker
|
(ns app.main.ui.workspace.colorpicker
|
||||||
(:require
|
(:require
|
||||||
|
@ -15,187 +17,248 @@
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.color :as uc]
|
[app.util.color :as uc]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.common.math :as math]))
|
[app.common.math :as math]
|
||||||
|
[app.common.uuid :refer [uuid]]
|
||||||
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
|
[app.main.ui.modal :as modal]
|
||||||
|
[okulary.core :as l]
|
||||||
|
[app.main.refs :as refs]))
|
||||||
|
|
||||||
|
(def recent-colors
|
||||||
|
(letfn [(selector [{:keys [objects]}]
|
||||||
|
(as-> {} $
|
||||||
|
(reduce (fn [acc shape]
|
||||||
|
(-> acc
|
||||||
|
(update (:fill-color shape) (fnil inc 0))
|
||||||
|
(update (:stroke-color shape) (fnil inc 0))))
|
||||||
|
$ (vals objects))
|
||||||
|
(reverse (sort-by second $))
|
||||||
|
(map first $)
|
||||||
|
(remove nil? $)))]
|
||||||
|
(l/derived selector st/state)))
|
||||||
|
|
||||||
|
|
||||||
;; --- Color Picker Modal
|
;; --- Color Picker Modal
|
||||||
|
|
||||||
(mf/defc value-selector [{:keys [saturation luminance on-change]}]
|
(mf/defc value-selector [{:keys [hue saturation value on-change]}]
|
||||||
(let [dragging? (mf/use-state false)]
|
(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-selector
|
[:div.value-selector
|
||||||
{:on-mouse-down #(reset! dragging? true)
|
{:on-mouse-down #(reset! dragging? true)
|
||||||
:on-mouse-up #(reset! dragging? false)
|
:on-mouse-up #(reset! dragging? false)
|
||||||
:on-mouse-move
|
:on-pointer-down (partial dom/capture-pointer)
|
||||||
(fn [ev]
|
:on-pointer-up (partial dom/release-pointer)
|
||||||
(when @dragging?
|
:on-click calculate-pos
|
||||||
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
|
:on-mouse-move #(when @dragging? (calculate-pos %))}
|
||||||
{:keys [x y]} (-> ev dom/get-client-position)
|
|
||||||
px (/ (- x left) (- right left))
|
|
||||||
py (/ (- y top) (- bottom top))
|
|
||||||
|
|
||||||
luminance (* (- 1.0 py) (- 1 (* 0.5 px)))]
|
|
||||||
|
|
||||||
(on-change px luminance))))}
|
|
||||||
[:div.handler {:style {:pointer-events "none"
|
[:div.handler {:style {:pointer-events "none"
|
||||||
:left (str (* saturation 100) "%")
|
:left (str (* 100 saturation) "%")
|
||||||
:top (str (* (- 1 (/ luminance (- 1 (* 0.5 saturation))) ) 100) "%")}}]]))
|
:top (str (* 100 (- 1 (/ value 255))) "%")}}]]))
|
||||||
|
|
||||||
(mf/defc hue-selector [{:keys [hue on-change]}]
|
(mf/defc hue-selector [{:keys [hue on-change]}]
|
||||||
(let [dragging? (mf/use-state false)]
|
(let [dragging? (mf/use-state false)
|
||||||
|
calculate-pos
|
||||||
|
(fn [ev]
|
||||||
|
(let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect)
|
||||||
|
{:keys [x]} (-> ev dom/get-client-position)
|
||||||
|
px (math/clamp (/ (- x left) (- right left)) 0 1)]
|
||||||
|
(on-change (* px 360))))]
|
||||||
[:div.hue-selector
|
[:div.hue-selector
|
||||||
{:on-mouse-down #(reset! dragging? true)
|
{:on-mouse-down #(reset! dragging? true)
|
||||||
:on-mouse-up #(reset! dragging? false)
|
:on-mouse-up #(reset! dragging? false)
|
||||||
:on-mouse-move
|
:on-pointer-down (partial dom/capture-pointer)
|
||||||
(fn [ev]
|
:on-pointer-up (partial dom/release-pointer)
|
||||||
(when @dragging?
|
:on-click calculate-pos
|
||||||
(let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect)
|
:on-mouse-move #(when @dragging? (calculate-pos %))}
|
||||||
{:keys [x]} (-> ev dom/get-client-position)
|
|
||||||
px (/ (- x left) (- right left))]
|
|
||||||
(on-change (* px 360)))))}
|
|
||||||
[:div.handler {:style {:pointer-events "none"
|
[:div.handler {:style {:pointer-events "none"
|
||||||
:left (str (* (/ hue 360) 100) "%")}}]]))
|
:left (str (* (/ hue 360) 100) "%")}}]]))
|
||||||
|
|
||||||
(mf/defc opacity-selector [{:keys [opacity on-change]}]
|
(mf/defc opacity-selector [{:keys [opacity on-change]}]
|
||||||
(let [dragging? (mf/use-state false)]
|
(let [dragging? (mf/use-state false)
|
||||||
|
calculate-pos
|
||||||
|
(fn [ev]
|
||||||
|
(let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect)
|
||||||
|
{:keys [x]} (-> ev dom/get-client-position)
|
||||||
|
px (math/clamp (/ (- x left) (- right left)) 0 1)]
|
||||||
|
(on-change px)))]
|
||||||
[:div.opacity-selector
|
[:div.opacity-selector
|
||||||
{:on-mouse-down #(reset! dragging? true)
|
{:on-mouse-down #(reset! dragging? true)
|
||||||
:on-mouse-up #(reset! dragging? false)
|
:on-mouse-up #(reset! dragging? false)
|
||||||
:on-mouse-move
|
:on-pointer-down (partial dom/capture-pointer)
|
||||||
(fn [ev]
|
:on-pointer-up (partial dom/release-pointer)
|
||||||
(when @dragging?
|
:on-click calculate-pos
|
||||||
(let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect)
|
:on-mouse-move #(when @dragging? (calculate-pos %))}
|
||||||
{:keys [x]} (-> ev dom/get-client-position)
|
|
||||||
px (/ (- x left) (- right left))]
|
|
||||||
(on-change px))))}
|
|
||||||
[:div.handler {:style {:pointer-events "none"
|
[:div.handler {:style {:pointer-events "none"
|
||||||
:left (str (* opacity 100) "%")}}]]))
|
:left (str (* opacity 100) "%")}}]]))
|
||||||
|
|
||||||
(defn as-color-state [value opacity]
|
(defn as-color-components [value opacity]
|
||||||
(let [[r g b] (uc/hex->rgb (or value "000000"))
|
(let [[r g b] (uc/hex->rgb (or value "000000"))
|
||||||
[h s l] (uc/hex->hsl (or value "000000"))]
|
[h s v] (uc/hex->hsv (or value "000000"))]
|
||||||
{:hex (or value "000000")
|
{:hex (or value "000000")
|
||||||
:alpha (or opacity 1)
|
:alpha (or opacity 1)
|
||||||
:r r
|
:r r :g g :b b
|
||||||
:g g
|
:h h :s s :v v}))
|
||||||
:b b
|
|
||||||
:h h
|
|
||||||
:s s
|
|
||||||
:l l}))
|
|
||||||
|
|
||||||
(mf/defc colorpicker
|
(mf/defc colorpicker
|
||||||
[{:keys [value opacity]}]
|
[{:keys [value opacity on-change on-accept]}]
|
||||||
(let [state (mf/use-state (as-color-state value opacity))
|
(let [current-color (mf/use-state (as-color-components value opacity))
|
||||||
ref-picker (mf/use-ref)]
|
selected-library (mf/use-state "recent")
|
||||||
|
current-library-colors (mf/use-state [])
|
||||||
|
ref-picker (mf/use-ref)
|
||||||
|
|
||||||
(mf/use-effect (mf/deps value opacity)
|
file-colors (mf/deref refs/workspace-file-colors)
|
||||||
|
shared-libs (mf/deref refs/workspace-libraries)
|
||||||
|
recent-colors (mf/deref refs/workspace-recent-colors)
|
||||||
|
|
||||||
|
value-ref (mf/use-var value)
|
||||||
|
|
||||||
|
on-change (or on-change identity)]
|
||||||
|
|
||||||
|
;; Update state when there is a change in the props upstream
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps value opacity)
|
||||||
(fn []
|
(fn []
|
||||||
(reset! state (as-color-state value opacity))))
|
(reset! current-color (as-color-components value opacity))))
|
||||||
|
|
||||||
(mf/use-effect (mf/deps state)
|
;; Updates the CSS color variable when there is a change in the color
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps @current-color)
|
||||||
(fn [] (let [node (mf/ref-val ref-picker)
|
(fn [] (let [node (mf/ref-val ref-picker)
|
||||||
rgb [(:r @state) (:g @state) (:b @state)]
|
rgb [(:r @current-color) (:g @current-color) (:b @current-color)]
|
||||||
hue-rgb (uc/hsl->rgb (:h @state) 1.0 0.5)]
|
hue-rgb (uc/hsv->rgb [(:h @current-color) 1.0 255])]
|
||||||
(dom/set-css-property node "--color" (str/join ", " rgb))
|
(dom/set-css-property node "--color" (str/join ", " rgb))
|
||||||
(dom/set-css-property node "--hue" (str/join ", " hue-rgb)))))
|
(dom/set-css-property node "--hue" (str/join ", " hue-rgb)))))
|
||||||
|
|
||||||
|
;; Load library colors when the select is changed
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps @selected-library)
|
||||||
|
(fn [] (cond
|
||||||
|
(= @selected-library "recent") (reset! current-library-colors (reverse (or recent-colors [])))
|
||||||
|
(= @selected-library "file") (reset! current-library-colors (into [] (map :value (vals file-colors))))
|
||||||
|
:else ;; Library UUID
|
||||||
|
(reset! current-library-colors (into [] (map :value (vals (get-in shared-libs [(uuid @selected-library) :data :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")
|
||||||
|
(reset! current-library-colors (into [] (map :value (vals file-colors)))))))
|
||||||
|
|
||||||
|
;; When closing the modal we update the recent-color list
|
||||||
|
(mf/use-effect
|
||||||
|
(fn [] #(st/emit! (dwl/add-recent-color @value-ref))))
|
||||||
|
|
||||||
[:div.colorpicker-v2 {:ref ref-picker}
|
[:div.colorpicker-v2 {:ref ref-picker}
|
||||||
[:& value-selector {:luminance (:l @state)
|
[:& value-selector {:hue (:h @current-color)
|
||||||
:saturation (:s @state)
|
:saturation (:s @current-color)
|
||||||
:on-change (fn [s l]
|
:value (:v @current-color)
|
||||||
(let [hex (uc/hsl->hex (:h @state) s l)
|
:on-change (fn [s v]
|
||||||
|
(let [hex (uc/hsv->hex [(:h @current-color) s v])
|
||||||
[r g b] (uc/hex->rgb hex)]
|
[r g b] (uc/hex->rgb hex)]
|
||||||
(swap! state assoc
|
(swap! current-color assoc
|
||||||
:hex hex
|
:hex hex
|
||||||
:r r :g g :b b
|
:r r :g g :b b
|
||||||
:s s :l l)))}]
|
:s s :v v)
|
||||||
|
(reset! value-ref hex)
|
||||||
|
(on-change hex (:alpha @current-color))))}]
|
||||||
[:div.shade-selector
|
[:div.shade-selector
|
||||||
[:div.color-bullet]
|
[:div.color-bullet]
|
||||||
[:& hue-selector {:hue (:h @state)
|
[:& hue-selector {:hue (:h @current-color)
|
||||||
:on-change (fn [h]
|
:on-change (fn [h]
|
||||||
(let [hex (uc/hsl->hex h (:s @state) (:l @state))
|
(let [hex (uc/hsv->hex [h (:s @current-color) (:v @current-color)])
|
||||||
[r g b] (uc/hex->rgb hex)]
|
[r g b] (uc/hex->rgb hex)]
|
||||||
(swap! state assoc
|
(swap! current-color assoc
|
||||||
:hex hex
|
:hex hex
|
||||||
:r r :g g :b b
|
:r r :g g :b b
|
||||||
:h h )))}]
|
:h h )
|
||||||
[:& opacity-selector {:opacity (:alpha @state)
|
(reset! value-ref hex)
|
||||||
|
(on-change hex (:alpha @current-color))))}]
|
||||||
|
[:& opacity-selector {:opacity (:alpha @current-color)
|
||||||
:on-change (fn [alpha]
|
:on-change (fn [alpha]
|
||||||
(swap! state assoc
|
(swap! current-color assoc :alpha alpha)
|
||||||
: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"
|
||||||
:value (:hex @state)
|
:value (:hex @current-color)
|
||||||
:on-change (fn [e]
|
:on-change (fn [e]
|
||||||
(let [val (-> e dom/get-target dom/get-value)
|
(let [val (-> e dom/get-target dom/get-value)
|
||||||
val (if (= (first val) \#) val (str \# val))]
|
val (if (= (first val) \#) val (str \# val))]
|
||||||
(swap! state assoc :hex val)
|
(swap! current-color assoc :hex val)
|
||||||
(when (uc/hex? val)
|
(when (uc/hex? val)
|
||||||
|
(reset! value-ref val)
|
||||||
(let [[r g b] (uc/hex->rgb val)
|
(let [[r g b] (uc/hex->rgb val)
|
||||||
[h s l] (uc/hex->hsl val)]
|
[h s v] (uc/hex->hsv val)]
|
||||||
(swap! state assoc
|
(swap! current-color assoc
|
||||||
:r r :g g :b b
|
:r r :g g :b b
|
||||||
:h h :s s :l l)))))}]
|
:h h :s s :v v)
|
||||||
|
(on-change val (:alpha @current-color))))))}]
|
||||||
[:input.red-value {:id "red-value"
|
[:input.red-value {:id "red-value"
|
||||||
:type "number"
|
:type "number"
|
||||||
:min 0
|
:min 0
|
||||||
:max 255
|
:max 255
|
||||||
:value (:r @state)
|
:value (:r @current-color)
|
||||||
:on-change (fn [e]
|
:on-change (fn [e]
|
||||||
(let [val (-> e dom/get-target dom/get-value)
|
(let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))]
|
||||||
val (if (> val 255) 255 val)
|
(swap! current-color assoc :r val)
|
||||||
val (if (< val 0) 0 val)]
|
|
||||||
(swap! state assoc :r val)
|
|
||||||
(when (not (nil? val))
|
(when (not (nil? val))
|
||||||
(let [{:keys [g b]} @state
|
(let [{:keys [g b]} @current-color
|
||||||
hex (uc/rgb->hex [val g b])
|
hex (uc/rgb->hex [val g b])
|
||||||
[h s l] (uc/hex->hsl hex)]
|
[h s v] (uc/hex->hsv hex)]
|
||||||
(swap! state assoc
|
(reset! value-ref hex)
|
||||||
|
(swap! current-color assoc
|
||||||
:hex hex
|
:hex hex
|
||||||
:h h :s s :l l)))))}]
|
:h h :s s :v v)
|
||||||
|
(on-change hex (:alpha @current-color))))))}]
|
||||||
[:input.green-value {:id "green-value"
|
[:input.green-value {:id "green-value"
|
||||||
:type "number"
|
:type "number"
|
||||||
:min 0
|
:min 0
|
||||||
:max 255
|
:max 255
|
||||||
:value (:g @state)
|
:value (:g @current-color)
|
||||||
:on-change (fn [e]
|
:on-change (fn [e]
|
||||||
(let [val (-> e dom/get-target dom/get-value)
|
(let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))]
|
||||||
val (if (> val 255) 255 val)
|
(swap! current-color assoc :g val)
|
||||||
val (if (< val 0) 0 val)]
|
|
||||||
(swap! state assoc :g val)
|
|
||||||
(when (not (nil? val))
|
(when (not (nil? val))
|
||||||
(let [{:keys [r b]} @state
|
(let [{:keys [r b]} @current-color
|
||||||
hex (uc/rgb->hex [r val b])
|
hex (uc/rgb->hex [r val b])
|
||||||
[h s l] (uc/hex->hsl hex)]
|
[h s v] (uc/hex->hsv hex)]
|
||||||
(swap! state assoc
|
(reset! value-ref hex)
|
||||||
|
(swap! current-color assoc
|
||||||
:hex hex
|
:hex hex
|
||||||
:h h :s s :l l)))))}]
|
:h h :s s :v v)
|
||||||
|
(on-change hex (:alpha @current-color))))))}]
|
||||||
[:input.blue-value {:id "blue-value"
|
[:input.blue-value {:id "blue-value"
|
||||||
:type "number"
|
:type "number"
|
||||||
:min 0
|
:min 0
|
||||||
:max 255
|
:max 255
|
||||||
:value (:b @state)
|
:value (:b @current-color)
|
||||||
:on-change (fn [e]
|
:on-change (fn [e]
|
||||||
(let [val (-> e dom/get-target dom/get-value)
|
(let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))]
|
||||||
val (if (> val 255) 255 val)
|
(swap! current-color assoc :b val)
|
||||||
val (if (< val 0) 0 val)]
|
|
||||||
(swap! state assoc :b val)
|
|
||||||
(when (not (nil? val))
|
(when (not (nil? val))
|
||||||
(let [{:keys [r g]} @state
|
(let [{:keys [r g]} @current-color
|
||||||
hex (uc/rgb->hex [r g val])
|
hex (uc/rgb->hex [r g val])
|
||||||
[h s l] (uc/hex->hsl hex)]
|
[h s v] (uc/hex->hsv hex)]
|
||||||
(swap! state assoc
|
(reset! value-ref hex)
|
||||||
|
(swap! current-color assoc
|
||||||
:hex hex
|
:hex hex
|
||||||
:h h :s s :l l)))))}]
|
:h h :s s :v v)
|
||||||
|
(on-change hex (:alpha @current-color))))))}]
|
||||||
[:input.alpha-value {:id "alpha-value"
|
[:input.alpha-value {:id "alpha-value"
|
||||||
:type "number"
|
:type "number"
|
||||||
:min 0
|
:min 0
|
||||||
:step 0.1
|
:step 0.1
|
||||||
:max 1
|
:max 1
|
||||||
:value (math/precision (:alpha @state) 2)
|
:value (math/precision (:alpha @current-color) 2)
|
||||||
:on-change (fn [e]
|
:on-change (fn [e]
|
||||||
(let [val (-> e dom/get-target dom/get-value)
|
(let [val (-> e dom/get-target dom/get-value (math/clamp 0 1))]
|
||||||
val (if (> val 1) 1 val)
|
(swap! current-color assoc :alpha val)
|
||||||
val (if (< val 0) 0 val)]
|
(on-change (:hex @current-color) val)))}]
|
||||||
(swap! state assoc :alpha val)))}]
|
|
||||||
[:label.hex-label {:for "hex-value"} "HEX"]
|
[:label.hex-label {:for "hex-value"} "HEX"]
|
||||||
[:label.red-label {:for "red-value"} "R"]
|
[:label.red-label {:for "red-value"} "R"]
|
||||||
[:label.green-label {:for "green-value"} "G"]
|
[:label.green-label {:for "green-value"} "G"]
|
||||||
|
@ -203,28 +266,60 @@
|
||||||
[:label.alpha-label {:for "alpha-value"} "A"]]
|
[:label.alpha-label {:for "alpha-value"} "A"]]
|
||||||
|
|
||||||
[:div.libraries
|
[:div.libraries
|
||||||
[:select
|
[:select {:on-change (fn [e]
|
||||||
[:option {:value :recent} "Recent colors"]
|
(let [val (-> e dom/get-target dom/get-value)]
|
||||||
[:option {:value :file} "File library"]
|
(reset! selected-library val)))
|
||||||
[:option {:value #uuid "f5d51910-ab23-11ea-ac38-e1abed64181a" } "TAIGA library"]]
|
:value @selected-library}
|
||||||
|
[:option {:value "recent"} "Recent colors"]
|
||||||
|
[:option {:value "file"} "File library"]
|
||||||
|
(for [[_ {:keys [name id]}] shared-libs]
|
||||||
|
[:option {:key id
|
||||||
|
:value id} name])]
|
||||||
|
|
||||||
[:div.selected-colors
|
[:div.selected-colors
|
||||||
[:div.color-bullet.button.plus-button {:style {:background-color "white"}}
|
(when (= "file" @selected-library)
|
||||||
i/plus]
|
[: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"}}
|
[:div.color-bullet.button {:style {:background-color "white"}}
|
||||||
i/palette]
|
i/palette]
|
||||||
|
|
||||||
#_(for [j (range 0 40)]
|
(for [[idx color] (map-indexed vector @current-library-colors)]
|
||||||
[:div.color-bullet {:style {:background-color "#E8E9EA"}}])]]])
|
[:div.color-bullet {:key (str "color-" idx)
|
||||||
|
:on-click (fn []
|
||||||
|
(swap! current-color assoc :hex color)
|
||||||
|
(reset! value-ref color)
|
||||||
|
(let [[r g b] (uc/hex->rgb color)
|
||||||
|
[h s v] (uc/hex->hsv color)]
|
||||||
|
(swap! current-color assoc
|
||||||
|
:r r :g g :b b
|
||||||
|
:h h :s s :v v)
|
||||||
|
(on-change color (:alpha @current-color))))
|
||||||
|
:style {:background-color color}}])]
|
||||||
|
|
||||||
|
]
|
||||||
|
(when on-accept
|
||||||
|
[:div.actions
|
||||||
|
[:button.btn-primary.btn-large
|
||||||
|
{:on-click (fn []
|
||||||
|
(println "SAVE" @value-ref)
|
||||||
|
(on-accept @value-ref)
|
||||||
|
(modal/hide!))}
|
||||||
|
"Save color"]])])
|
||||||
)
|
)
|
||||||
|
|
||||||
(mf/defc colorpicker-modal
|
(mf/defc colorpicker-modal
|
||||||
[{:keys [x y default value opacity page on-change disable-opacity] :as props}]
|
[{:keys [x y default value opacity page on-change disable-opacity position on-accept] :as props}]
|
||||||
|
(let [position (or position :left)
|
||||||
|
style (case position
|
||||||
|
:left {:left (str (- x 270) "px")
|
||||||
|
:top (str (- y 50) "px")}
|
||||||
|
:right {:left (str (+ x 24) "px")
|
||||||
|
:top (str (- y 50) "px")})]
|
||||||
[:div.modal-overlay.transparent
|
[:div.modal-overlay.transparent
|
||||||
[:div.colorpicker-tooltip
|
[:div.colorpicker-tooltip
|
||||||
{:style {:left (str (- x 270) "px")
|
{:style (clj->js style)}
|
||||||
:top (str (- y 50) "px")}}
|
|
||||||
#_[:& cp/colorpicker {:value (or value default)
|
#_[:& cp/colorpicker {:value (or value default)
|
||||||
:opacity (or opacity 1)
|
:opacity (or opacity 1)
|
||||||
:colors (into-array @cp/most-used-colors)
|
:colors (into-array @cp/most-used-colors)
|
||||||
|
@ -232,8 +327,6 @@
|
||||||
:disable-opacity disable-opacity}]
|
:disable-opacity disable-opacity}]
|
||||||
[:& colorpicker {:value (or value default)
|
[:& colorpicker {:value (or value default)
|
||||||
:opacity (or opacity 1)
|
:opacity (or opacity 1)
|
||||||
:colors (into-array @cp/most-used-colors)
|
|
||||||
:on-change on-change
|
:on-change on-change
|
||||||
:disable-opacity disable-opacity}]
|
:on-accept on-accept
|
||||||
]
|
:disable-opacity disable-opacity}]]]))
|
||||||
])
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
[app.main.ui.modal :as modal]
|
[app.main.ui.modal :as modal]
|
||||||
[app.main.ui.shapes.icon :as icon]
|
[app.main.ui.shapes.icon :as icon]
|
||||||
[app.main.ui.workspace.libraries :refer [libraries-dialog]]
|
[app.main.ui.workspace.libraries :refer [libraries-dialog]]
|
||||||
|
[app.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
|
||||||
[app.util.data :refer [matches-search]]
|
[app.util.data :refer [matches-search]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.dom.dnd :as dnd]
|
[app.util.dom.dnd :as dnd]
|
||||||
|
@ -39,7 +40,7 @@
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(mf/defc modal-edit-color
|
#_(mf/defc modal-edit-color
|
||||||
[{:keys [color-value on-accept on-cancel] :as ctx}]
|
[{:keys [color-value on-accept on-cancel] :as ctx}]
|
||||||
(let [state (mf/use-state {:current-color color-value})]
|
(let [state (mf/use-state {:current-color color-value})]
|
||||||
(letfn [(accept [event]
|
(letfn [(accept [event]
|
||||||
|
@ -167,8 +168,8 @@
|
||||||
(st/emit! (dwl/update-color (assoc color :name name))))
|
(st/emit! (dwl/update-color (assoc color :name name))))
|
||||||
|
|
||||||
edit-color
|
edit-color
|
||||||
(fn [value opacity]
|
(fn [value]
|
||||||
(st/emit! (dwl/update-color (assoc color :value name))))
|
(st/emit! (dwl/update-color (assoc color :value value))))
|
||||||
|
|
||||||
delete-color
|
delete-color
|
||||||
(fn []
|
(fn []
|
||||||
|
@ -197,9 +198,13 @@
|
||||||
|
|
||||||
edit-color-clicked
|
edit-color-clicked
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(modal/show! modal-edit-color
|
(modal/show! colorpicker-modal
|
||||||
{:color-value (:value color)
|
{:x (.-clientX event)
|
||||||
:on-accept edit-color}))
|
:y (.-clientY event)
|
||||||
|
:on-accept edit-color
|
||||||
|
:value (:value color)
|
||||||
|
:disable-opacity true
|
||||||
|
:position :right}))
|
||||||
|
|
||||||
on-context-menu
|
on-context-menu
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
@ -258,10 +263,13 @@
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps file-id)
|
(mf/deps file-id)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(modal/show! modal-edit-color
|
(modal/show! colorpicker-modal
|
||||||
{:color-value "#406280"
|
{:x (.-clientX event)
|
||||||
|
:y (.-clientY event)
|
||||||
:on-accept add-color})))]
|
:on-accept add-color
|
||||||
|
:value "#406280"
|
||||||
|
:disable-opacity true
|
||||||
|
:position :right})))]
|
||||||
[:div.asset-group
|
[:div.asset-group
|
||||||
[:div.group-title
|
[:div.group-title
|
||||||
(t locale "workspace.assets.colors")
|
(t locale "workspace.assets.colors")
|
||||||
|
|
|
@ -43,10 +43,6 @@
|
||||||
[v]
|
[v]
|
||||||
(into [] (gcolor/hexToHsv v)))
|
(into [] (gcolor/hexToHsv v)))
|
||||||
|
|
||||||
(defn hsv->hex
|
|
||||||
[[h s v]]
|
|
||||||
(gcolor/hsvToHex h s v))
|
|
||||||
|
|
||||||
(defn hex->rgba
|
(defn hex->rgba
|
||||||
[^string data ^number opacity]
|
[^string data ^number opacity]
|
||||||
(-> (hex->rgb data)
|
(-> (hex->rgb data)
|
||||||
|
@ -60,13 +56,26 @@
|
||||||
[0 0 0]))))
|
[0 0 0]))))
|
||||||
|
|
||||||
(defn hsl->rgb
|
(defn hsl->rgb
|
||||||
[h s l]
|
[[h s l]]
|
||||||
(gcolor/hslToRgb h s l))
|
(gcolor/hslToRgb h s l))
|
||||||
|
|
||||||
(defn hsl->hex [h s l]
|
(defn hsl->hex
|
||||||
|
[[h s l]]
|
||||||
(gcolor/hslToHex h s l))
|
(gcolor/hslToHex h s l))
|
||||||
|
|
||||||
(defn hex?
|
(defn hex?
|
||||||
[v]
|
[v]
|
||||||
(and (string? v)
|
(and (string? v)
|
||||||
(re-seq #"^#[0-9A-Fa-f]{6}$" v)))
|
(re-seq #"^#[0-9A-Fa-f]{6}$" v)))
|
||||||
|
|
||||||
|
(defn hsl->hsv
|
||||||
|
[[h s l]]
|
||||||
|
(gcolor/hslToHsv h s l))
|
||||||
|
|
||||||
|
(defn hsv->hex
|
||||||
|
[[h s v]]
|
||||||
|
(gcolor/hsvToHex h s v))
|
||||||
|
|
||||||
|
(defn hsv->hsl
|
||||||
|
[hsv]
|
||||||
|
(hex->hsl (hsv->hex hsv)))
|
||||||
|
|
|
@ -206,3 +206,9 @@
|
||||||
|
|
||||||
(defn set-css-property [node property value]
|
(defn set-css-property [node property value]
|
||||||
(.setProperty (.-style node) property value))
|
(.setProperty (.-style node) property value))
|
||||||
|
|
||||||
|
(defn capture-pointer [event]
|
||||||
|
(-> event get-target (.setPointerCapture (.-pointerId event))))
|
||||||
|
|
||||||
|
(defn release-pointer [event]
|
||||||
|
(-> event get-target (.releasePointerCapture (.-pointerId event))))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue