mirror of
https://github.com/penpot/penpot.git
synced 2025-03-16 01:31:22 -05:00
Merge pull request #2152 from penpot/niwinz-colorpicker-state-management-refactor
♻️ Refactor state management on colorpicker and gradients
This commit is contained in:
commit
5c5ec8ef56
19 changed files with 752 additions and 657 deletions
|
@ -49,6 +49,7 @@
|
|||
- Fix unexpected removal of guides on copy&paste frames [Taiga #3887](https://tree.taiga.io/project/penpot/issue/3887) by @andrewzhurov
|
||||
- Fix props preserving on copy&paste texts [Taiga #3629](https://tree.taiga.io/project/penpot/issue/3629) by @andrewzhurov
|
||||
- Fix unexpected layers ungrouping on moving it [Taiga #3932](https://tree.taiga.io/project/penpot/issue/3932) by @andrewzhurov
|
||||
- Fix unexpected exception and behavior on colorpicker with gradients [Taiga #3448](https://tree.taiga.io/project/penpot/issue/3448)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@
|
|||
|
||||
.color-palette-actions-button {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
& svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
|
52
frontend/src/app/main/broadcast.cljs
Normal file
52
frontend/src/app/main/broadcast.cljs
Normal file
|
@ -0,0 +1,52 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.broadcast
|
||||
"BroadcastChannel API."
|
||||
(:require
|
||||
[app.common.transit :as t]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(defrecord BroadcastMessage [id type data]
|
||||
cljs.core/IDeref
|
||||
(-deref [_] data))
|
||||
|
||||
(def ^:const default-topic "penpot")
|
||||
|
||||
;; The main broadcast channel instance, used for emit data
|
||||
(defonce default-channel
|
||||
(js/BroadcastChannel. default-topic))
|
||||
|
||||
(defonce stream
|
||||
(->> (rx/create (fn [subs]
|
||||
(let [chan (js/BroadcastChannel. default-topic)]
|
||||
(unchecked-set chan "onmessage" #(rx/push! subs (unchecked-get % "data")))
|
||||
(fn [] (.close ^js chan)))))
|
||||
(rx/map t/decode-str)
|
||||
(rx/map map->BroadcastMessage)
|
||||
(rx/share)))
|
||||
|
||||
(defn emit!
|
||||
([type data]
|
||||
(.postMessage ^js default-channel (t/encode-str {:id nil :type type :data data}))
|
||||
nil)
|
||||
([id type data]
|
||||
(.postMessage ^js default-channel (t/encode-str {:id id :type type :data data}))
|
||||
nil))
|
||||
|
||||
(defn type?
|
||||
([type]
|
||||
(fn [obj] (= (:type obj) type)))
|
||||
([obj type]
|
||||
(= (:type obj) type)))
|
||||
|
||||
(defn event
|
||||
[type data]
|
||||
(ptk/reify ::event
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(emit! type data))))
|
|
@ -6,55 +6,32 @@
|
|||
|
||||
(ns app.main.data.workspace.colors
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.colors :as colors]
|
||||
[app.common.data :as d]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.broadcast :as mbc]
|
||||
[app.main.data.modal :as md]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.layout :as layout]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.util.color :as uc]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(defn change-palette-selected
|
||||
"Change the library used by the general palette tool"
|
||||
[selected]
|
||||
(ptk/reify ::change-palette-selected
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :selected-palette] selected))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [wglobal (:workspace-global state)]
|
||||
(layout/persist-layout-state! wglobal)))))
|
||||
|
||||
(defn change-palette-selected-colorpicker
|
||||
"Change the library used by the color picker"
|
||||
[selected]
|
||||
(ptk/reify ::change-palette-selected-colorpicker
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :selected-palette-colorpicker] selected))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [wglobal (:workspace-global state)]
|
||||
(layout/persist-layout-state! wglobal)))))
|
||||
;; A set of keys that are used for shared state identifiers
|
||||
(def ^:const colorpicker-selected-broadcast-key ::colorpicker-selected)
|
||||
(def ^:const colorpalette-selected-broadcast-key ::colorpalette-selected)
|
||||
|
||||
(defn show-palette
|
||||
"Show the palette tool and change the library it uses"
|
||||
[selected]
|
||||
(ptk/reify ::show-palette
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :selected-palette] selected))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (layout/toggle-layout-flag :colorpalette :force? true)))
|
||||
(rx/of (layout/toggle-layout-flag :colorpalette :force? true)
|
||||
(mbc/event colorpalette-selected-broadcast-key selected)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
|
@ -158,10 +135,10 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [change-fn (fn [shape attrs]
|
||||
(-> shape
|
||||
(cond-> (not (contains? shape :fills))
|
||||
(assoc :fills []))
|
||||
(assoc-in [:fills position] (into {} attrs))))]
|
||||
(-> shape
|
||||
(cond-> (not (contains? shape :fills))
|
||||
(assoc :fills []))
|
||||
(assoc-in [:fills position] (into {} attrs))))]
|
||||
(transform-fill state ids color change-fn)))))
|
||||
|
||||
(defn change-fill-and-clear
|
||||
|
@ -342,45 +319,11 @@
|
|||
(-> state
|
||||
(assoc-in [:workspace-global :picking-color?] true)
|
||||
(assoc ::md/modal {:id (random-uuid)
|
||||
:data {:color clr/black :opacity 1}
|
||||
:data {:color colors/black :opacity 1}
|
||||
:type :colorpicker
|
||||
:props {:on-change handle-change-color}
|
||||
:allow-click-outside true})))))))
|
||||
|
||||
(defn start-gradient
|
||||
[gradient]
|
||||
(ptk/reify ::start-gradient
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (-> state wsh/lookup-selected first)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-global :current-gradient] gradient)
|
||||
(assoc-in [:workspace-global :current-gradient :shape-id] id))))))
|
||||
|
||||
(defn stop-gradient
|
||||
[]
|
||||
(ptk/reify ::stop-gradient
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :workspace-global dissoc :current-gradient)))))
|
||||
|
||||
(defn update-gradient
|
||||
[changes]
|
||||
(ptk/reify ::update-gradient
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update-in [:workspace-global :current-gradient] merge changes)))))
|
||||
|
||||
(defn select-gradient-stop
|
||||
[spot]
|
||||
(ptk/reify ::select-gradient-stop
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-global :editing-stop] spot)))))
|
||||
|
||||
(defn color-att->text
|
||||
[color]
|
||||
{:fill-color (:color color)
|
||||
|
@ -409,7 +352,9 @@
|
|||
:fill (change-fill [(:shape-id shape)] new-color (:index shape))
|
||||
:stroke (change-stroke [(:shape-id shape)] new-color (:index shape))
|
||||
:shadow (change-shadow [(:shape-id shape)] new-color (:index shape))
|
||||
:content (dwt/update-text-with-function (:shape-id shape) (partial change-text-color old-color new-color (:index shape))))))))))
|
||||
:content (dwt/update-text-with-function
|
||||
(:shape-id shape)
|
||||
(partial change-text-color old-color new-color (:index shape))))))))))
|
||||
|
||||
(defn apply-color-from-palette
|
||||
[color is-alt?]
|
||||
|
@ -431,3 +376,177 @@
|
|||
(if is-alt?
|
||||
(rx/of (change-stroke ids (merge uc/empty-color color) 0))
|
||||
(rx/of (change-fill ids (merge uc/empty-color color) 0)))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; COLORPICKER STATE MANAGEMENT
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn split-color-components
|
||||
[{:keys [color opacity] :as data}]
|
||||
(let [value (if (uc/hex? color) color colors/black)
|
||||
[r g b] (uc/hex->rgb value)
|
||||
[h s v] (uc/hex->hsv value)]
|
||||
(merge data
|
||||
{:hex (or value "000000")
|
||||
:alpha (or opacity 1)
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v})))
|
||||
|
||||
(defn materialize-color-components
|
||||
[{:keys [hex alpha] :as data}]
|
||||
(-> data
|
||||
(assoc :color hex)
|
||||
(assoc :opacity alpha)))
|
||||
|
||||
(defn clear-color-components
|
||||
[data]
|
||||
(dissoc data :hex :alpha :r :g :b :h :s :v))
|
||||
|
||||
(defn- create-gradient
|
||||
[type]
|
||||
{:start-x 0.5
|
||||
:start-y (if (= type :linear-gradient) 0.0 0.5)
|
||||
:end-x 0.5
|
||||
:end-y 1
|
||||
:width 1.0})
|
||||
|
||||
(defn get-color-from-colorpicker-state
|
||||
[{:keys [type current-color stops gradient] :as state}]
|
||||
(if (= type :color)
|
||||
(clear-color-components current-color)
|
||||
{:gradient (-> gradient
|
||||
(assoc :type (case type
|
||||
:linear-gradient :linear
|
||||
:radial-gradient :radial))
|
||||
(assoc :stops (mapv clear-color-components stops))
|
||||
(dissoc :shape-id))}))
|
||||
|
||||
(defn- colorpicker-onchange-runner
|
||||
"Effect event that runs the on-change callback with the latest
|
||||
colorpicker state converted to color object."
|
||||
[on-change]
|
||||
(ptk/reify ::colorpicker-onchange-runner
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-let [color (some-> state :colorpicker get-color-from-colorpicker-state)]
|
||||
(on-change color)
|
||||
(rx/of (dwl/add-recent-color color))))))
|
||||
|
||||
(defn initialize-colorpicker
|
||||
[on-change]
|
||||
(ptk/reify ::initialize-colorpicker
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [stoper (rx/merge
|
||||
(rx/filter (ptk/type? ::finalize-colorpicker) stream)
|
||||
(rx/filter (ptk/type? ::initialize-colorpicker) stream))]
|
||||
|
||||
(->> (rx/merge
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::update-colorpicker-gradient))
|
||||
(rx/debounce 200))
|
||||
(rx/filter (ptk/type? ::update-colorpicker-color) stream)
|
||||
(rx/filter (ptk/type? ::activate-colorpicker-gradient) stream))
|
||||
(rx/map (constantly (colorpicker-onchange-runner on-change)))
|
||||
(rx/take-until stoper))))))
|
||||
|
||||
(defn finalize-colorpicker
|
||||
[]
|
||||
(ptk/reify ::finalize-colorpicker
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(dissoc state :colorpicker))))
|
||||
|
||||
(defn update-colorpicker
|
||||
[{:keys [gradient] :as data}]
|
||||
(ptk/reify ::update-colorpicker
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [shape-id (-> state wsh/lookup-selected first)]
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(if (some? gradient)
|
||||
(let [stop (or (:editing-stop state) 0)
|
||||
stops (mapv split-color-components (:stops gradient))
|
||||
type (case (:type gradient)
|
||||
:linear :linear-gradient
|
||||
:radial :radial-gradient)]
|
||||
(-> state
|
||||
(assoc :type type)
|
||||
(assoc :current-color (nth stops stop))
|
||||
(assoc :stops stops)
|
||||
(assoc :gradient (-> gradient
|
||||
(dissoc :stops)
|
||||
(assoc :shape-id shape-id)))
|
||||
(assoc :editing-stop stop)))
|
||||
|
||||
(-> state
|
||||
(assoc :type :color)
|
||||
(assoc :current-color (split-color-components (dissoc data :gradient)))
|
||||
(dissoc :editing-stop)
|
||||
(dissoc :gradient)
|
||||
(dissoc :stops)))))))))
|
||||
|
||||
(defn update-colorpicker-color
|
||||
[changes]
|
||||
(ptk/reify ::update-colorpicker-color
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(let [state (-> state
|
||||
(update :current-color merge changes)
|
||||
(update :current-color materialize-color-components))]
|
||||
(if-let [stop (:editing-stop state)]
|
||||
(update-in state [:stops stop] (fn [data] (->> changes
|
||||
(merge data)
|
||||
(materialize-color-components))))
|
||||
(-> state
|
||||
(assoc :type :color)
|
||||
(dissoc :gradient :stops :editing-stop)))))))))
|
||||
|
||||
(defn update-colorpicker-gradient
|
||||
[changes]
|
||||
(ptk/reify ::update-colorpicker-gradient
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:colorpicker :gradient] merge changes))))
|
||||
|
||||
(defn select-colorpicker-gradient-stop
|
||||
[stop]
|
||||
(ptk/reify ::select-colorpicket-gradient-stop
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(if-let [color (get-in state [:stops stop])]
|
||||
(assoc state
|
||||
:current-color color
|
||||
:editing-stop stop)
|
||||
state))))))
|
||||
|
||||
(defn activate-colorpicker-gradient
|
||||
[type]
|
||||
(ptk/reify ::activate-colorpicker-gradient
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(if (= type (:type state))
|
||||
(do
|
||||
(-> state
|
||||
(assoc :type :color)
|
||||
(dissoc :editing-stop :stops :gradient)))
|
||||
(let [gradient (create-gradient type)
|
||||
color (:current-color state)]
|
||||
(-> state
|
||||
(assoc :type type)
|
||||
(assoc :gradient gradient)
|
||||
(cond-> (not (:stops state))
|
||||
(assoc :editing-stop 0
|
||||
:stops [(assoc color :offset 0)
|
||||
(-> color
|
||||
(assoc :alpha 0)
|
||||
(assoc :offset 1)
|
||||
(materialize-color-components))]))))))))))
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
|
||||
(defn add-recent-color
|
||||
[color]
|
||||
(us/assert ::ctc/recent-color color)
|
||||
(us/assert! ::ctc/recent-color color)
|
||||
(ptk/reify ::add-recent-color
|
||||
ptk/WatchEvent
|
||||
(watch [it _ _]
|
||||
|
|
|
@ -410,3 +410,7 @@
|
|||
|
||||
(defn workspace-text-modifier-by-id [id]
|
||||
(l/derived #(get % id) workspace-text-modifier =))
|
||||
|
||||
(def colorpicker
|
||||
(l/derived :colorpicker st/state))
|
||||
|
||||
|
|
|
@ -17,23 +17,31 @@
|
|||
:radial (tr "workspace.gradients.radial")
|
||||
nil))
|
||||
|
||||
(mf/defc color-bullet [{:keys [color on-click]}]
|
||||
(if (uc/multiple? color)
|
||||
[:div.color-bullet.multiple {:on-click #(when on-click (on-click %))}]
|
||||
(mf/defc color-bullet
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [color on-click]}]
|
||||
(let [on-click (mf/use-fn
|
||||
(mf/deps color on-click)
|
||||
(fn [event]
|
||||
(when (fn? on-click)
|
||||
(^function on-click color event))))]
|
||||
|
||||
;; No multiple selection
|
||||
(let [color (if (string? color) {:color color :opacity 1} color)]
|
||||
[:div.color-bullet.tooltip.tooltip-right
|
||||
{:class (dom/classnames :is-library-color (some? (:id color))
|
||||
:is-not-library-color (nil? (:id color))
|
||||
:is-gradient (some? (:gradient color)))
|
||||
:on-click #(when on-click (on-click %))
|
||||
:alt (or (:name color) (:color color) (gradient-type->string (:type (:gradient color))))}
|
||||
(if (:gradient color)
|
||||
[:div.color-bullet-wrapper {:style {:background (uc/color->background color)}}]
|
||||
[:div.color-bullet-wrapper
|
||||
[:div.color-bullet-left {:style {:background (uc/color->background (assoc color :opacity 1))}}]
|
||||
[:div.color-bullet-right {:style {:background (uc/color->background color)}}]])])))
|
||||
(if (uc/multiple? color)
|
||||
[:div.color-bullet.multiple {:on-click on-click}]
|
||||
|
||||
;; No multiple selection
|
||||
(let [color (if (string? color) {:color color :opacity 1} color)]
|
||||
[:div.color-bullet.tooltip.tooltip-right
|
||||
{:class (dom/classnames :is-library-color (some? (:id color))
|
||||
:is-not-library-color (nil? (:id color))
|
||||
:is-gradient (some? (:gradient color)))
|
||||
:on-click on-click
|
||||
:alt (or (:name color) (:color color) (gradient-type->string (:type (:gradient color))))}
|
||||
(if (:gradient color)
|
||||
[:div.color-bullet-wrapper {:style {:background (uc/color->background color)}}]
|
||||
[:div.color-bullet-wrapper
|
||||
[:div.color-bullet-left {:style {:background (uc/color->background (assoc color :opacity 1))}}]
|
||||
[:div.color-bullet-right {:style {:background (uc/color->background color)}}]])]))))
|
||||
|
||||
(mf/defc color-name [{:keys [color size on-click on-double-click]}]
|
||||
(let [color (if (string? color) {:color color :opacity 1} color)
|
||||
|
|
|
@ -7,16 +7,26 @@
|
|||
(ns app.main.ui.hooks
|
||||
"A collection of general purpose react hooks."
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.broadcast :as mbc]
|
||||
[app.main.data.shortcuts :as dsc]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.dom.dnd :as dnd]
|
||||
[app.util.storage :refer [storage]]
|
||||
[app.util.timers :as ts]
|
||||
[beicon.core :as rx]
|
||||
[goog.functions :as f]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn use-id
|
||||
"Get a stable id value across rerenders."
|
||||
[]
|
||||
(mf/use-memo #(dm/str (uuid/next))))
|
||||
|
||||
(defn use-rxsub
|
||||
[ob]
|
||||
(let [[state reset-state!] (mf/useState @ob)]
|
||||
|
@ -191,7 +201,6 @@
|
|||
|
||||
[(deref state) ref]))
|
||||
|
||||
|
||||
(defn use-stream
|
||||
"Wraps the subscription to a stream into a `use-effect` call"
|
||||
([stream on-subscribe]
|
||||
|
@ -205,6 +214,7 @@
|
|||
|
||||
;; https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
|
||||
(defn use-previous
|
||||
"Returns the value from previuous render cycle."
|
||||
[value]
|
||||
(let [ref (mf/use-ref value)]
|
||||
(mf/use-effect
|
||||
|
@ -214,13 +224,27 @@
|
|||
(mf/ref-val ref)))
|
||||
|
||||
(defn use-update-var
|
||||
"Returns a var pointer what automatically updates with latest values."
|
||||
[value]
|
||||
(let [ref (mf/use-var value)]
|
||||
(mf/use-effect
|
||||
(mf/deps value)
|
||||
(fn []
|
||||
(reset! ref value)))
|
||||
ref))
|
||||
(let [ptr (mf/use-var value)]
|
||||
(mf/with-effect [value]
|
||||
(reset! ptr value))
|
||||
ptr))
|
||||
|
||||
(defn use-ref-callback
|
||||
"Returns a stable callback pointer what calls the interned
|
||||
callback. The interned callback will be automatically updated on
|
||||
each reander if the reference changes and works as noop if the
|
||||
pointer references to nil value."
|
||||
[f]
|
||||
(let [ptr (mf/use-ref nil)]
|
||||
(mf/with-effect [f]
|
||||
(mf/set-ref-val! ptr #js {:f f}))
|
||||
(mf/use-fn
|
||||
(fn [& args]
|
||||
(let [obj (mf/ref-val ptr)]
|
||||
(when ^boolean obj
|
||||
(apply (.-f obj) args)))))))
|
||||
|
||||
(defn use-equal-memo
|
||||
[val]
|
||||
|
@ -258,4 +282,34 @@
|
|||
#(cp/focus-objects objects focus))]
|
||||
objects)))
|
||||
|
||||
(defn use-debounce
|
||||
[ms value]
|
||||
(let [[state update-state-fn] (mf/useState value)
|
||||
update-fn (mf/use-memo (mf/deps ms) #(f/debounce update-state-fn ms))]
|
||||
(mf/with-effect [value]
|
||||
(update-fn value))
|
||||
state))
|
||||
|
||||
(defn use-shared-state
|
||||
"A specialized hook that adds persistence and inter-context reactivity
|
||||
to the default mf/use-state hook.
|
||||
|
||||
The state is automatically persisted under the provided key on
|
||||
localStorage. And it will keep watching events with type equals to
|
||||
`key` for new values."
|
||||
[key default]
|
||||
(let [id (use-id)
|
||||
state (mf/use-state (get @storage key default))
|
||||
stream (mf/with-memo []
|
||||
(->> mbc/stream
|
||||
(rx/filter #(= (:type %) key))
|
||||
(rx/filter #(not= (:id %) id))
|
||||
(rx/map deref)))]
|
||||
|
||||
(mf/with-effect [@state key]
|
||||
(mbc/emit! id key @state)
|
||||
(swap! storage assoc key @state))
|
||||
|
||||
(use-stream stream (partial reset! state))
|
||||
|
||||
state))
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logging :as log]
|
||||
[app.common.spec :as us]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -73,43 +74,38 @@
|
|||
|
||||
(defn use-resize-observer
|
||||
[callback]
|
||||
(assert (some? callback))
|
||||
(us/assert! (some? callback) "the `callback` is mandatory")
|
||||
|
||||
(let [prev-val-ref (mf/use-ref nil)
|
||||
current-observer-ref (mf/use-ref nil)
|
||||
|
||||
callback-ref (hooks/use-update-var {:callback callback})
|
||||
observer-ref (mf/use-ref nil)
|
||||
callback (hooks/use-ref-callback callback)
|
||||
|
||||
;; We use the ref as a callback when the dom node is ready (or change)
|
||||
node-ref
|
||||
(mf/use-callback
|
||||
(fn [^js node]
|
||||
(when (some? node)
|
||||
(let [^js current-observer (mf/ref-val current-observer-ref)
|
||||
^js prev-val (mf/ref-val prev-val-ref)]
|
||||
node-ref (mf/use-fn
|
||||
(fn [^js node]
|
||||
(when (some? node)
|
||||
(let [^js observer (mf/ref-val observer-ref)
|
||||
^js prev-val (mf/ref-val prev-val-ref)]
|
||||
|
||||
(when (and (not= prev-val node) (some? current-observer))
|
||||
(log/debug :action "disconnect" :js/prev-val prev-val :js/node node)
|
||||
(.disconnect current-observer)
|
||||
(mf/set-ref-val! current-observer-ref nil))
|
||||
(when (and (not= prev-val node) (some? observer))
|
||||
(log/debug :action "disconnect" :js/prev-val prev-val :js/node node)
|
||||
(.disconnect observer)
|
||||
(mf/set-ref-val! observer-ref nil))
|
||||
|
||||
(when (and (not= prev-val node) (some? node))
|
||||
(let [^js observer
|
||||
(js/ResizeObserver.
|
||||
#(let [callback (get @callback-ref :callback)]
|
||||
(callback last-resize-type (dom/get-client-size node))))]
|
||||
(mf/set-ref-val! current-observer-ref observer)
|
||||
(log/debug :action "observe" :js/node node :js/observer observer)
|
||||
(.observe observer node))))
|
||||
(when (and (not= prev-val node) (some? node))
|
||||
(let [^js observer (js/ResizeObserver.
|
||||
#(callback last-resize-type (dom/get-client-size node)))]
|
||||
(mf/set-ref-val! observer-ref observer)
|
||||
(log/debug :action "observe" :js/node node :js/observer observer)
|
||||
(.observe observer node))))
|
||||
|
||||
(mf/set-ref-val! prev-val-ref node))))]
|
||||
(mf/set-ref-val! prev-val-ref node))))]
|
||||
|
||||
(mf/with-effect []
|
||||
;; On dismount we need to disconnect the current observer
|
||||
(fn []
|
||||
(when-let [observer (mf/ref-val observer-ref)]
|
||||
(log/debug :action "disconnect")
|
||||
(.disconnect ^js observer))))
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
;; On dismount we need to disconnect the current observer
|
||||
(fn []
|
||||
(let [current-observer (mf/ref-val current-observer-ref)]
|
||||
(when (some? current-observer)
|
||||
(log/debug :action "disconnect")
|
||||
(.disconnect current-observer))))))
|
||||
node-ref))
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
|
||||
(ns app.main.ui.workspace.colorpalette
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.workspace.colors :as mdc]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.color-bullet :as cb]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.hooks.resize :refer [use-resize-hook]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -19,36 +21,21 @@
|
|||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]
|
||||
[goog.events :as events]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def palettes-ref
|
||||
(-> (l/in [:library :palettes])
|
||||
(l/derived st/state)))
|
||||
|
||||
(def selected-palette-ref
|
||||
(-> (l/in [:workspace-global :selected-palette])
|
||||
(l/derived st/state)))
|
||||
|
||||
(def selected-palette-size-ref
|
||||
(-> (l/in [:workspace-global :selected-palette-size])
|
||||
(l/derived st/state)))
|
||||
|
||||
;; --- Components
|
||||
(mf/defc palette-item
|
||||
[{:keys [color]}]
|
||||
(let [select-color
|
||||
(fn [event]
|
||||
(st/emit! (mdc/apply-color-from-palette color (kbd/alt? event))))]
|
||||
|
||||
(mf/defc palette-item
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [color]}]
|
||||
(letfn [(select-color [event]
|
||||
(st/emit! (mdc/apply-color-from-palette color (kbd/alt? event))))]
|
||||
[:div.color-cell {:on-click select-color}
|
||||
[:& cb/color-bullet {:color color}]
|
||||
[:& cb/color-name {:color color}]]))
|
||||
|
||||
(mf/defc palette
|
||||
[{:keys [current-colors recent-colors file-colors shared-libs selected]}]
|
||||
[{:keys [current-colors recent-colors file-colors shared-libs selected on-select]}]
|
||||
(let [state (mf/use-state {:show-menu false})
|
||||
|
||||
width (:width @state 0)
|
||||
|
@ -97,54 +84,66 @@
|
|||
(fn [_]
|
||||
(let [dom (mf/ref-val container)
|
||||
width (obj/get dom "clientWidth")]
|
||||
(swap! state assoc :width width))))]
|
||||
(swap! state assoc :width width))))
|
||||
|
||||
on-select-palette
|
||||
(mf/use-fn
|
||||
(mf/deps on-select)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
value (dom/get-attribute node "data-palette")]
|
||||
(on-select (if (or (= "file" value) (= "recent" value))
|
||||
(keyword value)
|
||||
(parse-uuid value))))))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
#(let [dom (mf/ref-val container)
|
||||
width (obj/get dom "clientWidth")]
|
||||
(swap! state assoc :width width)))
|
||||
|
||||
(mf/use-effect
|
||||
#(let [key1 (events/listen js/window "resize" on-resize)]
|
||||
(fn []
|
||||
(events/unlistenByKey key1))))
|
||||
(mf/with-effect []
|
||||
(let [key1 (events/listen js/window "resize" on-resize)]
|
||||
#(events/unlistenByKey key1)))
|
||||
|
||||
[:div.color-palette {:ref parent-ref
|
||||
:class (dom/classnames :no-text (< size 72))
|
||||
:style #js {"--height" (str size "px")
|
||||
"--bullet-size" (str (if (< size 72) (- size 15) (- size 30)) "px")}}
|
||||
:style #js {"--height" (dm/str size "px")
|
||||
"--bullet-size" (dm/str (if (< size 72) (- size 15) (- size 30)) "px")}}
|
||||
[:div.resize-area {:on-pointer-down on-pointer-down
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-mouse-move on-mouse-move}]
|
||||
[:& dropdown {:show (:show-menu @state)
|
||||
:on-close #(swap! state assoc :show-menu false)}
|
||||
[:ul.workspace-context-menu.palette-menu
|
||||
(for [[idx cur-library] (map-indexed vector (vals shared-libs))]
|
||||
(let [colors (-> cur-library (get-in [:data :colors]) vals)]
|
||||
(for [{:keys [data id] :as library} (vals shared-libs)]
|
||||
(let [colors (-> data :colors vals)]
|
||||
[:li.palette-library
|
||||
{:key (str "library-" idx)
|
||||
:on-click #(st/emit! (mdc/change-palette-selected (:id cur-library)))}
|
||||
(when (= selected (:id cur-library)) i/tick)
|
||||
[:div.library-name (str (:name cur-library) " " (str/format "(%s)" (count colors)))]
|
||||
{:key (dm/str "library-" id)
|
||||
:on-click on-select-palette
|
||||
:data-palette (dm/str id)}
|
||||
(when (= selected id) i/tick)
|
||||
[:div.library-name (str (:name library) " " (str/ffmt "(%)" (count colors)))]
|
||||
[:div.color-sample
|
||||
(for [[idx {:keys [color]}] (map-indexed vector (take 7 colors))]
|
||||
[:& cb/color-bullet {:key (str "color-" idx)
|
||||
(for [[i {:keys [color]}] (map-indexed vector (take 7 colors))]
|
||||
[:& cb/color-bullet {:key (dm/str "color-" i)
|
||||
:color color}])]]))
|
||||
|
||||
|
||||
[:li.palette-library
|
||||
{:on-click #(st/emit! (mdc/change-palette-selected :file))}
|
||||
{:on-click on-select-palette
|
||||
:data-palette "file"}
|
||||
(when (= selected :file) i/tick)
|
||||
[:div.library-name (str (tr "workspace.libraries.colors.file-library")
|
||||
(str/format " (%s)" (count file-colors)))]
|
||||
[:div.library-name (dm/str
|
||||
(tr "workspace.libraries.colors.file-library")
|
||||
(str/ffmt " (%)" (count file-colors)))]
|
||||
[:div.color-sample
|
||||
(for [[idx color] (map-indexed vector (take 7 (vals file-colors))) ]
|
||||
[:& cb/color-bullet {:key (str "color-" idx)
|
||||
(for [[i color] (map-indexed vector (take 7 (vals file-colors))) ]
|
||||
[:& cb/color-bullet {:key (dm/str "color-" i)
|
||||
:color color}])]]
|
||||
|
||||
[:li.palette-library
|
||||
{:on-click #(st/emit! (mdc/change-palette-selected :recent))}
|
||||
{:on-click on-select-palette
|
||||
:data-palette "recent"}
|
||||
(when (= selected :recent) i/tick)
|
||||
[:div.library-name (str (tr "workspace.libraries.colors.recent-colors")
|
||||
(str/format " (%s)" (count recent-colors)))]
|
||||
|
@ -178,34 +177,32 @@
|
|||
(let [recent-colors (mf/deref refs/workspace-recent-colors)
|
||||
file-colors (mf/deref refs/workspace-file-colors)
|
||||
shared-libs (mf/deref refs/workspace-libraries)
|
||||
selected (or (mf/deref selected-palette-ref) :recent)
|
||||
current-library-colors (mf/use-state [])]
|
||||
selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent)
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps selected)
|
||||
(fn []
|
||||
(reset! current-library-colors
|
||||
(into []
|
||||
(cond
|
||||
(= selected :recent) (reverse recent-colors)
|
||||
(= selected :file) (->> (vals file-colors) (sort-by :name))
|
||||
:else (->> (library->colors shared-libs selected) (sort-by :name)))))))
|
||||
colors (mf/use-state [])
|
||||
on-select (mf/use-fn #(reset! selected %))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps recent-colors)
|
||||
(fn []
|
||||
(when (= selected :recent)
|
||||
(reset! current-library-colors (reverse recent-colors)))))
|
||||
(mf/with-effect [@selected]
|
||||
(fn []
|
||||
(reset! colors
|
||||
(into []
|
||||
(cond
|
||||
(= @selected :recent) (reverse recent-colors)
|
||||
(= @selected :file) (->> (vals file-colors) (sort-by :name))
|
||||
:else (->> (library->colors shared-libs @selected) (sort-by :name)))))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps file-colors)
|
||||
(fn []
|
||||
(when (= selected :file)
|
||||
(reset! current-library-colors (into [] (->> (vals file-colors)
|
||||
(sort-by :name)))))))
|
||||
(mf/with-effect [recent-colors @selected]
|
||||
(when (= @selected :recent)
|
||||
(reset! colors (reverse recent-colors))))
|
||||
|
||||
[:& palette {:current-colors @current-library-colors
|
||||
(mf/with-effect [file-colors @selected]
|
||||
(when (= @selected :file)
|
||||
(reset! colors (into [] (->> (vals file-colors)
|
||||
(sort-by :name))))))
|
||||
|
||||
[:& palette {:current-colors @colors
|
||||
:recent-colors recent-colors
|
||||
:file-colors file-colors
|
||||
:shared-libs shared-libs
|
||||
:selected selected}]))
|
||||
:selected @selected
|
||||
:on-select on-select}]))
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
(ns app.main.ui.workspace.colorpicker
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
|
@ -41,247 +40,102 @@
|
|||
(def viewport
|
||||
(l/derived :vport refs/workspace-local))
|
||||
|
||||
(def editing-spot-state-ref
|
||||
(l/derived :editing-stop refs/workspace-global))
|
||||
|
||||
(def current-gradient-ref
|
||||
(l/derived :current-gradient refs/workspace-global))
|
||||
|
||||
;; --- Color Picker Modal
|
||||
|
||||
(defn color->components [value opacity]
|
||||
(let [value (if (uc/hex? value) value clr/black)
|
||||
[r g b] (uc/hex->rgb value)
|
||||
[h s v] (uc/hex->hsv value)]
|
||||
|
||||
{:hex (or value "000000")
|
||||
:alpha (or opacity 1)
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v}))
|
||||
|
||||
(defn data->state [{:keys [color opacity gradient]}]
|
||||
(let [type (cond
|
||||
(nil? gradient) :color
|
||||
(= :linear (:type gradient)) :linear-gradient
|
||||
(= :radial (:type gradient)) :radial-gradient)
|
||||
|
||||
parse-stop (fn [{:keys [offset color opacity]}]
|
||||
(vector offset (color->components color opacity)))
|
||||
|
||||
stops (when gradient
|
||||
(map parse-stop (:stops gradient)))
|
||||
|
||||
current-color (if (nil? gradient)
|
||||
(color->components color opacity)
|
||||
(-> stops first second))
|
||||
|
||||
gradient-data (select-keys gradient [:start-x :start-y
|
||||
:end-x :end-y
|
||||
:width])]
|
||||
|
||||
(cond-> {:type type
|
||||
:current-color current-color}
|
||||
gradient (assoc :gradient-data gradient-data)
|
||||
stops (assoc :stops (into {} stops))
|
||||
stops (assoc :editing-stop (-> stops first first)))))
|
||||
|
||||
(defn state->data [{:keys [type current-color stops gradient-data]}]
|
||||
(if (= type :color)
|
||||
{:color (:hex current-color)
|
||||
:opacity (:alpha current-color)}
|
||||
|
||||
(let [gradient-type (case type
|
||||
:linear-gradient :linear
|
||||
:radial-gradient :radial)
|
||||
parse-stop (fn [[offset {:keys [hex alpha]}]]
|
||||
(hash-map :offset offset
|
||||
:color hex
|
||||
:opacity alpha))]
|
||||
{:gradient (-> {:type gradient-type
|
||||
:stops (mapv parse-stop stops)}
|
||||
(merge gradient-data))})))
|
||||
|
||||
(defn create-gradient-data [type]
|
||||
{:start-x 0.5
|
||||
:start-y (if (= type :linear-gradient) 0.0 0.5)
|
||||
:end-x 0.5
|
||||
:end-y 1
|
||||
:width 1.0})
|
||||
|
||||
(mf/defc colorpicker
|
||||
[{:keys [data disable-gradient disable-opacity on-change on-accept]}]
|
||||
(let [state (mf/use-state (data->state data))
|
||||
active-tab (mf/use-state :ramp #_:harmony #_:hsva)
|
||||
(let [state (mf/deref refs/colorpicker)
|
||||
node-ref (mf/use-ref)
|
||||
|
||||
ref-picker (mf/use-ref)
|
||||
|
||||
dirty? (mf/use-var false)
|
||||
last-color (mf/use-var data)
|
||||
|
||||
picking-color? (mf/deref picking-color?)
|
||||
picked-color (mf/deref picked-color)
|
||||
;; TODO: I think we need to put all this picking state under
|
||||
;; the same object for avoid creating adhoc refs for each
|
||||
;; value
|
||||
picking-color? (mf/deref picking-color?)
|
||||
picked-color (mf/deref picked-color)
|
||||
picked-color-select (mf/deref picked-color-select)
|
||||
|
||||
editing-spot-state (mf/deref editing-spot-state-ref)
|
||||
current-gradient (mf/deref current-gradient-ref)
|
||||
current-color (:current-color state)
|
||||
|
||||
current-color (:current-color @state)
|
||||
|
||||
change-tab
|
||||
(fn [tab]
|
||||
#(reset! active-tab tab))
|
||||
active-tab (mf/use-state :ramp #_:harmony #_:hsva)
|
||||
set-ramp-tab! (mf/use-fn #(reset! active-tab :ramp))
|
||||
set-harmony-tab! (mf/use-fn #(reset! active-tab :harmony))
|
||||
set-hsva-tab! (mf/use-fn #(reset! active-tab :hsva))
|
||||
|
||||
handle-change-color
|
||||
(fn [changes]
|
||||
(let [editing-stop (:editing-stop @state)]
|
||||
(swap! state #(cond-> %
|
||||
:always
|
||||
(update :current-color merge changes)
|
||||
|
||||
(not editing-stop)
|
||||
(-> (assoc :type :color)
|
||||
(dissoc :gradient-data :stops :editing-stops))
|
||||
|
||||
editing-stop
|
||||
(update-in [:stops editing-stop] merge changes)))
|
||||
(reset! dirty? true)))
|
||||
(mf/use-fn #(st/emit! (dc/update-colorpicker-color %)))
|
||||
|
||||
handle-click-picker
|
||||
(fn []
|
||||
(if picking-color?
|
||||
(do (modal/disallow-click-outside!)
|
||||
(st/emit! (dc/stop-picker)))
|
||||
(do (modal/allow-click-outside!)
|
||||
(st/emit! (dc/start-picker)))))
|
||||
(mf/use-fn
|
||||
(mf/deps picking-color?)
|
||||
(fn []
|
||||
(if picking-color?
|
||||
(do (modal/disallow-click-outside!)
|
||||
(st/emit! (dc/stop-picker)))
|
||||
(do (modal/allow-click-outside!)
|
||||
(st/emit! (dc/start-picker))))))
|
||||
|
||||
handle-change-stop
|
||||
(fn [offset]
|
||||
(when-let [offset-color (get-in @state [:stops offset])]
|
||||
(swap! state assoc
|
||||
:current-color offset-color
|
||||
:editing-stop offset)
|
||||
|
||||
(st/emit! (dc/select-gradient-stop offset))))
|
||||
(mf/use-fn
|
||||
(fn [offset]
|
||||
(st/emit! (dc/select-colorpicker-gradient-stop offset))))
|
||||
|
||||
on-select-library-color
|
||||
(fn [color]
|
||||
(let [editing-stop (:editing-stop @state)
|
||||
is-gradient? (some? (:gradient color))]
|
||||
|
||||
(if is-gradient?
|
||||
(st/emit! (dc/start-gradient (:gradient color)))
|
||||
(st/emit! (dc/stop-gradient)))
|
||||
|
||||
(if (and (some? editing-stop) (not is-gradient?))
|
||||
(handle-change-color (color->components (:color color) (:opacity color)))
|
||||
(do (reset! dirty? false)
|
||||
(reset! state (-> (data->state color)
|
||||
(assoc :editing-stop nil)))
|
||||
(on-change color)))))
|
||||
|
||||
(mf/use-fn
|
||||
(fn [color]
|
||||
(on-change color)))
|
||||
|
||||
on-add-library-color
|
||||
(fn [_]
|
||||
(st/emit! (dwl/add-color (state->data @state))))
|
||||
(mf/use-fn
|
||||
(mf/deps state)
|
||||
(fn [_]
|
||||
(st/emit! (dwl/add-color (dc/get-color-from-colorpicker-state state)))))
|
||||
|
||||
on-activate-gradient
|
||||
(fn [type]
|
||||
(fn []
|
||||
(reset! dirty? true)
|
||||
(if (= type (:type @state))
|
||||
(do
|
||||
(swap! state assoc :type :color)
|
||||
(swap! state dissoc :editing-stop :stops :gradient-data)
|
||||
(st/emit! (dc/stop-gradient)))
|
||||
(let [gradient-data (create-gradient-data type)]
|
||||
(swap! state assoc :type type :gradient-data gradient-data)
|
||||
(when (not (:stops @state))
|
||||
(swap! state assoc
|
||||
:editing-stop 0
|
||||
:stops {0 (:current-color @state)
|
||||
1 (-> (:current-color @state)
|
||||
(assoc :alpha 0))}))))))]
|
||||
on-activate-linear-gradient
|
||||
(mf/use-fn #(st/emit! (dc/activate-colorpicker-gradient :linear-gradient)))
|
||||
|
||||
on-activate-radial-gradient
|
||||
(mf/use-fn #(st/emit! (dc/activate-colorpicker-gradient :radial-gradient)))]
|
||||
|
||||
;; Initialize colorpicker state
|
||||
(mf/with-effect []
|
||||
(st/emit! (dc/initialize-colorpicker on-change))
|
||||
(partial st/emit! (dc/finalize-colorpicker)))
|
||||
|
||||
;; Update colorpicker with external color changes
|
||||
(mf/with-effect [data]
|
||||
(st/emit! (dc/update-colorpicker data)))
|
||||
|
||||
;; 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)
|
||||
{:keys [r g b h v]} current-color
|
||||
rgb [r g b]
|
||||
hue-rgb (uc/hsv->rgb [h 1.0 255])
|
||||
hsl-from (uc/hsv->hsl [h 0.0 v])
|
||||
hsl-to (uc/hsv->hsl [h 1.0 v])
|
||||
(mf/with-effect [current-color]
|
||||
(let [node (mf/ref-val node-ref)
|
||||
{:keys [r g b h v]} current-color
|
||||
rgb [r g b]
|
||||
hue-rgb (uc/hsv->rgb [h 1.0 255])
|
||||
hsl-from (uc/hsv->hsl [h 0.0 v])
|
||||
hsl-to (uc/hsv->hsl [h 1.0 v])
|
||||
|
||||
format-hsl (fn [[h s l]]
|
||||
(str/fmt "hsl(%s, %s, %s)"
|
||||
h
|
||||
(str (* s 100) "%")
|
||||
(str (* l 100) "%")))]
|
||||
(dom/set-css-property! node "--color" (str/join ", " rgb))
|
||||
(dom/set-css-property! node "--hue-rgb" (str/join ", " hue-rgb))
|
||||
(dom/set-css-property! node "--saturation-grad-from" (format-hsl hsl-from))
|
||||
(dom/set-css-property! node "--saturation-grad-to" (format-hsl hsl-to)))))
|
||||
|
||||
;; When closing the modal we update the recent-color list
|
||||
(mf/use-effect
|
||||
#(fn []
|
||||
(st/emit! (dc/stop-picker))
|
||||
(when @last-color
|
||||
(st/emit! (dwl/add-recent-color @last-color)))))
|
||||
format-hsl (fn [[h s l]]
|
||||
(str/fmt "hsl(%s, %s, %s)"
|
||||
h
|
||||
(str (* s 100) "%")
|
||||
(str (* l 100) "%")))]
|
||||
(dom/set-css-property! node "--color" (str/join ", " rgb))
|
||||
(dom/set-css-property! node "--hue-rgb" (str/join ", " hue-rgb))
|
||||
(dom/set-css-property! node "--saturation-grad-from" (format-hsl hsl-from))
|
||||
(dom/set-css-property! node "--saturation-grad-to" (format-hsl hsl-to))))
|
||||
|
||||
;; Updates color when used el pixel picker
|
||||
(mf/use-effect
|
||||
(mf/deps picking-color? picked-color picked-color-select)
|
||||
(fn []
|
||||
(when (and picking-color? picked-color picked-color-select)
|
||||
(let [[r g b alpha] picked-color
|
||||
hex (uc/rgb->hex [r g b])
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(handle-change-color {:hex hex
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v
|
||||
:alpha (/ alpha 255)})))))
|
||||
(mf/with-effect [picking-color? picked-color picked-color-select]
|
||||
(when (and picking-color? picked-color picked-color-select)
|
||||
(let [[r g b alpha] picked-color
|
||||
hex (uc/rgb->hex [r g b])
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(handle-change-color {:hex hex
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v
|
||||
:alpha (/ alpha 255)}))))
|
||||
|
||||
;; Changes when another gradient handler is selected
|
||||
(mf/use-effect
|
||||
(mf/deps editing-spot-state)
|
||||
#(when (not= editing-spot-state (:editing-stop @state))
|
||||
(handle-change-stop (or editing-spot-state 0))))
|
||||
|
||||
;; Changes on the viewport when moving a gradient handler
|
||||
(mf/use-effect
|
||||
(mf/deps current-gradient)
|
||||
(fn []
|
||||
(when current-gradient
|
||||
(let [gradient-data (select-keys current-gradient [:start-x :start-y
|
||||
:end-x :end-y
|
||||
:width])]
|
||||
(when (not= (:gradient-data @state) gradient-data)
|
||||
(reset! dirty? true)
|
||||
(swap! state assoc :gradient-data gradient-data))))))
|
||||
|
||||
;; Check if we've opened a color with gradient
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(when (:gradient data)
|
||||
(st/emit! (dc/start-gradient (:gradient data))))
|
||||
|
||||
;; on-unmount we stop the handlers
|
||||
#(st/emit! (dc/stop-gradient))))
|
||||
|
||||
;; Send the properties to the store
|
||||
(mf/use-effect
|
||||
(mf/deps @state)
|
||||
(fn []
|
||||
(when @dirty?
|
||||
(let [color (state->data @state)]
|
||||
(reset! dirty? false)
|
||||
(reset! last-color color)
|
||||
(when (:gradient color)
|
||||
(st/emit! (dc/start-gradient (:gradient color))))
|
||||
(on-change color)))))
|
||||
|
||||
[:div.colorpicker {:ref ref-picker}
|
||||
[:div.colorpicker {:ref node-ref}
|
||||
[:div.colorpicker-content
|
||||
[:div.top-actions
|
||||
[:button.picker-btn
|
||||
|
@ -292,70 +146,81 @@
|
|||
(when (not disable-gradient)
|
||||
[:div.gradients-buttons
|
||||
[:button.gradient.linear-gradient
|
||||
{:on-click (on-activate-gradient :linear-gradient)
|
||||
:class (when (= :linear-gradient (:type @state)) "active")}]
|
||||
{:on-click on-activate-linear-gradient
|
||||
:class (when (= :linear-gradient (:type state)) "active")}]
|
||||
|
||||
[:button.gradient.radial-gradient
|
||||
{:on-click (on-activate-gradient :radial-gradient)
|
||||
:class (when (= :radial-gradient (:type @state)) "active")}]])]
|
||||
{:on-click on-activate-radial-gradient
|
||||
:class (when (= :radial-gradient (:type state)) "active")}]])]
|
||||
|
||||
[:& gradients {:type (:type @state)
|
||||
:stops (:stops @state)
|
||||
:editing-stop (:editing-stop @state)
|
||||
:on-select-stop handle-change-stop}]
|
||||
|
||||
(when (or (= (:type state) :linear-gradient)
|
||||
(= (:type state) :radial-gradient))
|
||||
[:& gradients
|
||||
{:stops (:stops state)
|
||||
:editing-stop (:editing-stop state)
|
||||
:on-select-stop handle-change-stop}])
|
||||
|
||||
[:div.colorpicker-tabs
|
||||
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
|
||||
{:class (when (= @active-tab :ramp) "active")
|
||||
:alt (tr "workspace.libraries.colors.rgba")
|
||||
:on-click (change-tab :ramp)} i/picker-ramp]
|
||||
:on-click set-ramp-tab!} i/picker-ramp]
|
||||
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
|
||||
{:class (when (= @active-tab :harmony) "active")
|
||||
:alt (tr "workspace.libraries.colors.rgb-complementary")
|
||||
:on-click (change-tab :harmony)} i/picker-harmony]
|
||||
:on-click set-harmony-tab!} i/picker-harmony]
|
||||
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
|
||||
{:class (when (= @active-tab :hsva) "active")
|
||||
:alt (tr "workspace.libraries.colors.hsv")
|
||||
:on-click (change-tab :hsva)} i/picker-hsv]]
|
||||
:on-click set-hsva-tab!} i/picker-hsv]]
|
||||
|
||||
(if picking-color?
|
||||
[:div.picker-detail-wrapper
|
||||
[:div.center-circle]
|
||||
[:canvas#picker-detail {:width 200 :height 160}]]
|
||||
(case @active-tab
|
||||
:ramp [:& ramp-selector {:color current-color
|
||||
:disable-opacity disable-opacity
|
||||
:on-change handle-change-color
|
||||
:on-start-drag #(st/emit! (dwu/start-undo-transaction))
|
||||
:on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}]
|
||||
:harmony [:& harmony-selector {:color current-color
|
||||
:disable-opacity disable-opacity
|
||||
:on-change handle-change-color
|
||||
:on-start-drag #(st/emit! (dwu/start-undo-transaction))
|
||||
:on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}]
|
||||
:hsva [:& hsva-selector {:color current-color
|
||||
:disable-opacity disable-opacity
|
||||
:on-change handle-change-color
|
||||
:on-start-drag #(st/emit! (dwu/start-undo-transaction))
|
||||
:on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}]
|
||||
:ramp
|
||||
[:& ramp-selector
|
||||
{:color current-color
|
||||
:disable-opacity disable-opacity
|
||||
:on-change handle-change-color
|
||||
:on-start-drag #(st/emit! (dwu/start-undo-transaction))
|
||||
:on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}]
|
||||
:harmony
|
||||
[:& harmony-selector
|
||||
{:color current-color
|
||||
:disable-opacity disable-opacity
|
||||
:on-change handle-change-color
|
||||
:on-start-drag #(st/emit! (dwu/start-undo-transaction))
|
||||
:on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}]
|
||||
:hsva
|
||||
[:& hsva-selector
|
||||
{:color current-color
|
||||
:disable-opacity disable-opacity
|
||||
:on-change handle-change-color
|
||||
:on-start-drag #(st/emit! (dwu/start-undo-transaction))
|
||||
:on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}]
|
||||
nil))
|
||||
|
||||
[:& color-inputs {:type (if (= @active-tab :hsva) :hsv :rgb)
|
||||
:disable-opacity disable-opacity
|
||||
:color current-color
|
||||
:on-change handle-change-color}]
|
||||
[:& color-inputs
|
||||
{:type (if (= @active-tab :hsva) :hsv :rgb)
|
||||
:disable-opacity disable-opacity
|
||||
:color current-color
|
||||
:on-change handle-change-color}]
|
||||
|
||||
[:& libraries {:current-color current-color
|
||||
:disable-gradient disable-gradient
|
||||
:disable-opacity disable-opacity
|
||||
:on-select-color on-select-library-color
|
||||
:on-add-library-color on-add-library-color}]
|
||||
[:& libraries
|
||||
{:current-color current-color
|
||||
:disable-gradient disable-gradient
|
||||
:disable-opacity disable-opacity
|
||||
:on-select-color on-select-library-color
|
||||
:on-add-library-color on-add-library-color}]
|
||||
|
||||
(when on-accept
|
||||
[:div.actions
|
||||
[:button.btn-primary.btn-large
|
||||
{:on-click (fn []
|
||||
(on-accept (state->data @state))
|
||||
(on-accept (dc/get-color-from-colorpicker-state state))
|
||||
(modal/hide!))}
|
||||
(tr "workspace.libraries.colors.save-color")]])]]))
|
||||
|
||||
|
|
|
@ -11,13 +11,17 @@
|
|||
[app.util.dom :as dom]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn parse-hex
|
||||
[val]
|
||||
(if (= (first val) \#)
|
||||
val
|
||||
(str \# val)))
|
||||
|
||||
(mf/defc color-inputs [{:keys [type color disable-opacity 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)
|
||||
|
|
|
@ -6,32 +6,31 @@
|
|||
|
||||
(ns app.main.ui.workspace.colorpicker.gradients
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(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) "%")))
|
||||
(defn- format-rgba
|
||||
[{:keys [r g b alpha offset]}]
|
||||
(str/ffmt "rgba(%1, %2, %3, %4) %5%%" r g b alpha (* offset 100)))
|
||||
|
||||
gradient-css (str/join "," (map format-stop stops))]
|
||||
(str/fmt "linear-gradient(90deg, %s)" gradient-css)))
|
||||
(defn- gradient->string [stops]
|
||||
(let [gradient-css (str/join ", " (map format-rgba stops))]
|
||||
(str/ffmt "linear-gradient(90deg, %1)" 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)}}]]
|
||||
(mf/defc gradients
|
||||
[{:keys [stops editing-stop on-select-stop]}]
|
||||
[: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) "%")}}
|
||||
[:div.gradient-stop-wrapper
|
||||
(for [{:keys [offset hex r g b alpha] :as value} stops]
|
||||
[:div.gradient-stop
|
||||
{:class (when (= editing-stop offset) "active")
|
||||
:on-click (partial on-select-stop offset)
|
||||
:style {:left (dm/str (* offset 100) "%")}
|
||||
:key (dm/str offset)}
|
||||
|
||||
(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)}}]])])]]))
|
||||
[:div.gradient-stop-color {:style {:background-color hex}}]
|
||||
[:div.gradient-stop-alpha {:style {:background-color (str/ffmt "rgba(%1, %2, %3, %4)" r g b alpha)}}]])]])
|
||||
|
|
|
@ -6,90 +6,85 @@
|
|||
|
||||
(ns app.main.ui.workspace.colorpicker.libraries
|
||||
(:require
|
||||
[app.common.uuid :refer [uuid]]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.color-bullet :refer [color-bullet]]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(def selected-palette-ref
|
||||
(-> (l/in [:workspace-global :selected-palette-colorpicker])
|
||||
(l/derived st/state)))
|
||||
|
||||
(mf/defc libraries
|
||||
[{:keys [on-select-color on-add-library-color disable-gradient disable-opacity]}]
|
||||
(let [selected-library (or (mf/deref selected-palette-ref) :recent)
|
||||
current-library-colors (mf/use-state [])
|
||||
(let [selected (h/use-shared-state dc/colorpicker-selected-broadcast-key :recent)
|
||||
current-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)
|
||||
|
||||
parse-selected
|
||||
(fn [selected-str]
|
||||
(if (#{"recent" "file"} selected-str)
|
||||
(keyword selected-str)
|
||||
(uuid selected-str)))
|
||||
on-library-change
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [val (dom/get-target-val event)]
|
||||
(reset! selected
|
||||
(if (or (= val "recent")
|
||||
(= val "file"))
|
||||
(keyword val)
|
||||
(parse-uuid val))))))
|
||||
|
||||
check-valid-color? (fn [color]
|
||||
(and (or (not disable-gradient) (not (:gradient color)))
|
||||
(or (not disable-opacity) (= 1 (:opacity color)))))]
|
||||
check-valid-color?
|
||||
(fn [color]
|
||||
(and (or (not disable-gradient) (not (:gradient color)))
|
||||
(or (not disable-opacity) (= 1 (:opacity color)))))]
|
||||
|
||||
;; Load library colors when the select is changed
|
||||
(mf/use-effect
|
||||
(mf/deps selected-library)
|
||||
(fn []
|
||||
(let [mapped-colors
|
||||
(cond
|
||||
(= selected-library :recent)
|
||||
;; The `map?` check is to keep backwards compatibility. We transform from string to map
|
||||
(map #(if (map? %) % (hash-map :color %)) (reverse (or recent-colors [])))
|
||||
(mf/with-effect [@selected recent-colors file-colors]
|
||||
(let [colors (cond
|
||||
(= @selected :recent)
|
||||
;; The `map?` check is to keep backwards compatibility. We transform from string to map
|
||||
(map #(if (map? %) % {:color %}) (reverse (or recent-colors [])))
|
||||
|
||||
(= selected-library :file)
|
||||
(vals file-colors)
|
||||
(= @selected :file)
|
||||
(vals file-colors)
|
||||
|
||||
:else ;; Library UUID
|
||||
(->> (get-in shared-libs [selected-library :data :colors])
|
||||
(vals)
|
||||
(map #(merge % {:file-id selected-library}))))]
|
||||
:else ;; Library UUID
|
||||
(as-> @selected file-id
|
||||
(->> (get-in shared-libs [file-id :data :colors])
|
||||
(vals)
|
||||
(map #(assoc % :file-id file-id)))))]
|
||||
|
||||
(reset! current-library-colors (into [] (filter check-valid-color?) mapped-colors)))))
|
||||
(reset! current-colors (into [] (filter check-valid-color?) 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 (vals file-colors)]
|
||||
(reset! current-library-colors (into [] (filter check-valid-color?) colors))))))
|
||||
(mf/with-effect [file-colors]
|
||||
(when (= @selected :file)
|
||||
(let [colors (vals file-colors)]
|
||||
(reset! current-colors (into [] (filter check-valid-color?) colors)))))
|
||||
|
||||
[:div.libraries
|
||||
[:select {:on-change (fn [e]
|
||||
(when-let [val (parse-selected (dom/get-target-val e))]
|
||||
(st/emit! (dc/change-palette-selected-colorpicker val))))
|
||||
:value (name selected-library)}
|
||||
[:select {:on-change on-library-change :value (name @selected)}
|
||||
[:option {:value "recent"} (tr "workspace.libraries.colors.recent-colors")]
|
||||
[:option {:value "file"} (tr "workspace.libraries.colors.file-library")]
|
||||
|
||||
(for [[_ {:keys [name id]}] shared-libs]
|
||||
[:option {:key id
|
||||
:value id} name])]
|
||||
[:option {:key id :value id} name])]
|
||||
|
||||
[:div.selected-colors
|
||||
(when (= selected-library :file)
|
||||
(when (= @selected :file)
|
||||
[:div.color-bullet.button.plus-button {:style {:background-color "var(--color-white)"}
|
||||
:on-click on-add-library-color}
|
||||
i/plus])
|
||||
|
||||
[:div.color-bullet.button {:style {:background-color "var(--color-white)"}
|
||||
:on-click #(st/emit! (dc/show-palette selected-library))}
|
||||
:on-click #(st/emit! (dc/show-palette @selected))}
|
||||
i/palette]
|
||||
|
||||
(for [[idx color] (map-indexed vector @current-library-colors)]
|
||||
[:& color-bullet {:key (str "color-" idx)
|
||||
:color color
|
||||
:on-click #(on-select-color color)}])]]))
|
||||
(for [[idx color] (map-indexed vector @current-colors)]
|
||||
[:& color-bullet
|
||||
{:key (dm/str "color-" idx)
|
||||
:color color
|
||||
:on-click on-select-color}])]]))
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
|
||||
shape-bb-ref (hooks/use-update-var shape-bb)
|
||||
|
||||
updates-str (mf/use-memo #(rx/subject))
|
||||
updates-str (mf/use-memo #(rx/subject))
|
||||
|
||||
thumbnail-data-ref (mf/use-memo (mf/deps page-id id) #(refs/thumbnail-frame-data page-id id))
|
||||
thumbnail-data (mf/deref thumbnail-data-ref)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.workspace.sidebar.options.rows.color-row
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pages :as cp]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -22,34 +23,8 @@
|
|||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn color-picker-callback
|
||||
[color disable-gradient disable-opacity handle-change-color handle-open handle-close]
|
||||
(fn [event]
|
||||
(let [color
|
||||
(cond
|
||||
(uc/multiple? color)
|
||||
{:color cp/default-color
|
||||
:opacity 1}
|
||||
|
||||
(= :multiple (:opacity color))
|
||||
(assoc color :opacity 1)
|
||||
|
||||
:else
|
||||
color)
|
||||
|
||||
x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
props {:x x
|
||||
:y y
|
||||
:disable-gradient disable-gradient
|
||||
:disable-opacity disable-opacity
|
||||
:on-change handle-change-color
|
||||
:on-close handle-close
|
||||
:data color}]
|
||||
(handle-open color)
|
||||
(modal/show! :colorpicker props))))
|
||||
|
||||
(defn opacity->string [opacity]
|
||||
(defn opacity->string
|
||||
[opacity]
|
||||
(if (= opacity :multiple)
|
||||
""
|
||||
(str (-> opacity
|
||||
|
@ -57,7 +32,8 @@
|
|||
(* 100)
|
||||
(fmt/format-number)))))
|
||||
|
||||
(defn remove-multiple [v]
|
||||
(defn remove-multiple
|
||||
[v]
|
||||
(if (= v :multiple) nil v))
|
||||
|
||||
(mf/defc color-row
|
||||
|
@ -68,64 +44,88 @@
|
|||
file-colors (mf/deref refs/workspace-file-colors)
|
||||
shared-libs (mf/deref refs/workspace-libraries)
|
||||
hover-detach (mf/use-state false)
|
||||
on-change (h/use-ref-callback on-change)
|
||||
src-colors (if (= (:file-id color) current-file-id)
|
||||
file-colors
|
||||
(dm/get-in shared-libs [(:file-id color) :data :colors]))
|
||||
|
||||
on-change-var (h/use-update-var {:fn on-change})
|
||||
color-name (dm/get-in src-colors [(:id color) :name])
|
||||
|
||||
src-colors (if (= (:file-id color) current-file-id)
|
||||
file-colors
|
||||
(get-in shared-libs [(:file-id color) :data :colors]))
|
||||
parse-color
|
||||
(mf/use-fn
|
||||
(fn [color]
|
||||
(update color :color #(or % (:value color)))))
|
||||
|
||||
color-name (get-in src-colors [(:id color) :name])
|
||||
detach-value
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach color)
|
||||
(fn []
|
||||
(when on-detach
|
||||
(on-detach color))))
|
||||
|
||||
parse-color (fn [color]
|
||||
(-> color
|
||||
(update :color #(or % (:value color)))))
|
||||
handle-select
|
||||
(mf/use-fn
|
||||
(mf/deps select-only color)
|
||||
(fn []
|
||||
(select-only color)))
|
||||
|
||||
detach-value (fn []
|
||||
(when on-detach (on-detach color)))
|
||||
handle-value-change
|
||||
(mf/use-fn
|
||||
(mf/deps color on-change)
|
||||
(fn [new-value]
|
||||
(on-change (-> color
|
||||
(assoc :color new-value)
|
||||
(dissoc :gradient)))))
|
||||
|
||||
change-value (fn [new-value]
|
||||
(when (:fn @on-change-var) ((:fn @on-change-var) (-> color
|
||||
(assoc :color new-value)
|
||||
(dissoc :gradient)))))
|
||||
handle-opacity-change
|
||||
(mf/use-fn
|
||||
(mf/deps color on-change)
|
||||
(fn [value]
|
||||
(on-change (assoc color
|
||||
:opacity (/ value 100)
|
||||
:id nil
|
||||
:file-id nil))))
|
||||
|
||||
change-opacity (fn [new-opacity]
|
||||
(when (:fn @on-change-var) ((:fn @on-change-var) (assoc color
|
||||
:opacity new-opacity
|
||||
:id nil
|
||||
:file-id nil))))
|
||||
handle-click-color
|
||||
(mf/use-fn
|
||||
(mf/deps disable-gradient disable-opacity on-change on-close on-open)
|
||||
(fn [color event]
|
||||
(let [color (cond
|
||||
(uc/multiple? color)
|
||||
{:color cp/default-color
|
||||
:opacity 1}
|
||||
|
||||
handle-pick-color (fn [color]
|
||||
(when (:fn @on-change-var) ((:fn @on-change-var) (merge uc/empty-color color))))
|
||||
(= :multiple (:opacity color))
|
||||
(assoc color :opacity 1)
|
||||
|
||||
handle-select (fn []
|
||||
(select-only color))
|
||||
:else
|
||||
color)
|
||||
|
||||
handle-open (fn [color]
|
||||
(when on-open (on-open (merge uc/empty-color color))))
|
||||
{:keys [x y]} (dom/get-client-position event)
|
||||
|
||||
handle-close (fn [value opacity id file-id]
|
||||
(when on-close (on-close value opacity id file-id)))
|
||||
props {:x x
|
||||
:y y
|
||||
:disable-gradient disable-gradient
|
||||
:disable-opacity disable-opacity
|
||||
:on-change #(on-change (merge uc/empty-color %))
|
||||
:on-close (fn [value opacity id file-id]
|
||||
(when on-close
|
||||
(on-close value opacity id file-id)))
|
||||
:data color}]
|
||||
|
||||
handle-value-change (fn [new-value]
|
||||
(-> new-value
|
||||
change-value))
|
||||
(when on-open
|
||||
(on-open (merge uc/empty-color color)))
|
||||
|
||||
handle-opacity-change (fn [value]
|
||||
(change-opacity (/ value 100)))
|
||||
(modal/show! :colorpicker props))))
|
||||
|
||||
handle-click-color (color-picker-callback color
|
||||
disable-gradient
|
||||
disable-opacity
|
||||
handle-pick-color
|
||||
handle-open
|
||||
handle-close)
|
||||
|
||||
prev-color (h/use-previous color)
|
||||
|
||||
on-drop
|
||||
(fn [_ data]
|
||||
(on-reorder (:index data)))
|
||||
(mf/use-fn
|
||||
(mf/deps on-reorder)
|
||||
(fn [_ data]
|
||||
(on-reorder (:index data))))
|
||||
|
||||
[dprops dref] (if (some? on-reorder)
|
||||
(h/use-sortable
|
||||
|
@ -138,11 +138,9 @@
|
|||
:name (str "Color row" index)})
|
||||
[nil nil])]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps color prev-color)
|
||||
(fn []
|
||||
(when (not= prev-color color)
|
||||
(modal/update-props! :colorpicker {:data (parse-color color)}))))
|
||||
(mf/with-effect [color prev-color]
|
||||
(when (not= prev-color color)
|
||||
(modal/update-props! :colorpicker {:data (parse-color color)})))
|
||||
|
||||
[:div.row-flex.color-data {:title title
|
||||
:class (dom/classnames
|
||||
|
@ -182,7 +180,7 @@
|
|||
(when select-only
|
||||
[:div.element-set-actions-button {:on-click handle-select}
|
||||
i/pointer-inner])]
|
||||
|
||||
|
||||
|
||||
;; Rendering a plain color/opacity
|
||||
:else
|
||||
|
|
|
@ -144,7 +144,7 @@
|
|||
on-pointer-move (actions/on-pointer-move viewport-ref zoom move-stream)
|
||||
on-pointer-up (actions/on-pointer-up)
|
||||
on-move-selected (actions/on-move-selected hover hover-ids selected space?)
|
||||
on-menu-selected (actions/on-menu-selected hover hover-ids selected)
|
||||
on-menu-selected (actions/on-menu-selected hover hover-ids selected)
|
||||
|
||||
on-frame-enter (actions/on-frame-enter frame-hover)
|
||||
on-frame-leave (actions/on-frame-leave frame-hover)
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
[app.util.dom :as dom]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(def gradient-line-stroke-width 2)
|
||||
|
@ -32,12 +31,6 @@
|
|||
(def gradient-square-stroke-color "var(--color-white)")
|
||||
(def gradient-square-stroke-color-selected "var(--color-select)")
|
||||
|
||||
(def editing-spot-ref
|
||||
(l/derived (l/in [:workspace-global :editing-stop]) st/state))
|
||||
|
||||
(def current-gradient-ref
|
||||
(l/derived (l/in [:workspace-global :current-gradient]) st/state =))
|
||||
|
||||
(mf/defc shadow [{:keys [id x y width height offset]}]
|
||||
[:filter {:id id
|
||||
:x x
|
||||
|
@ -130,27 +123,32 @@
|
|||
(let [moving-point (mf/use-var nil)
|
||||
angle (+ 90 (gpt/angle from-p to-p))
|
||||
|
||||
on-click (fn [position event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(when (#{:from-p :to-p} position)
|
||||
(st/emit! (dc/select-gradient-stop (case position
|
||||
:from-p 0
|
||||
:to-p 1)))))
|
||||
on-click
|
||||
(fn [position event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(when (#{:from-p :to-p} position)
|
||||
(st/emit! (dc/select-colorpicker-gradient-stop
|
||||
(case position
|
||||
:from-p 0
|
||||
:to-p 1)))))
|
||||
|
||||
on-mouse-down (fn [position event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(reset! moving-point position)
|
||||
(when (#{:from-p :to-p} position)
|
||||
(st/emit! (dc/select-gradient-stop (case position
|
||||
:from-p 0
|
||||
:to-p 1)))))
|
||||
on-mouse-down
|
||||
(fn [position event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(reset! moving-point position)
|
||||
(when (#{:from-p :to-p} position)
|
||||
(st/emit! (dc/select-colorpicker-gradient-stop
|
||||
(case position
|
||||
:from-p 0
|
||||
:to-p 1)))))
|
||||
|
||||
on-mouse-up (fn [_position event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(reset! moving-point nil))]
|
||||
on-mouse-up
|
||||
(fn [_position event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(reset! moving-point nil))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps @moving-point from-p to-p width-p)
|
||||
|
@ -230,37 +228,24 @@
|
|||
:on-mouse-down (partial on-mouse-down :to-p)
|
||||
:on-mouse-up (partial on-mouse-up :to-p)}]]))
|
||||
|
||||
|
||||
(mf/defc gradient-handlers
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [id zoom]}]
|
||||
(let [current-change (mf/use-state {})
|
||||
shape-ref (mf/use-memo (mf/deps id) #(refs/object-by-id id))
|
||||
shape (mf/deref shape-ref)
|
||||
|
||||
gradient (mf/deref current-gradient-ref)
|
||||
gradient (merge gradient @current-change)
|
||||
|
||||
editing-spot (mf/deref editing-spot-ref)
|
||||
|
||||
transform (gsh/transform-matrix shape)
|
||||
(mf/defc gradient-handlers*
|
||||
[{:keys [zoom stops gradient editing-stop shape]}]
|
||||
(let [transform (gsh/transform-matrix shape)
|
||||
transform-inverse (gsh/inverse-transform-matrix shape)
|
||||
|
||||
{:keys [x y width height] :as sr} (:selrect shape)
|
||||
|
||||
[{start-color :color start-opacity :opacity}
|
||||
{end-color :color end-opacity :opacity}] (:stops gradient)
|
||||
{end-color :color end-opacity :opacity}] stops
|
||||
|
||||
from-p (-> (gpt/point (+ x (* width (:start-x gradient)))
|
||||
(+ y (* height (:start-y gradient))))
|
||||
|
||||
(gpt/transform transform))
|
||||
|
||||
to-p (-> (gpt/point (+ x (* width (:end-x gradient)))
|
||||
(+ y (* height (:end-y gradient))))
|
||||
(gpt/transform transform))
|
||||
|
||||
gradient-vec (gpt/to-vec from-p to-p)
|
||||
gradient-vec (gpt/to-vec from-p to-p)
|
||||
gradient-length (gpt/length gradient-vec)
|
||||
|
||||
width-v (-> gradient-vec
|
||||
|
@ -271,13 +256,12 @@
|
|||
width-p (gpt/add from-p width-v)
|
||||
|
||||
change!
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(fn [changes]
|
||||
(swap! current-change merge changes)
|
||||
(st/emit! (dc/update-gradient changes))))
|
||||
(st/emit! (dc/update-colorpicker-gradient changes))))
|
||||
|
||||
on-change-start
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps transform-inverse width height)
|
||||
(fn [point]
|
||||
(let [point (gpt/transform point transform-inverse)
|
||||
|
@ -286,7 +270,7 @@
|
|||
(change! {:start-x start-x :start-y start-y}))))
|
||||
|
||||
on-change-finish
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps transform-inverse width height)
|
||||
(fn [point]
|
||||
(let [point (gpt/transform point transform-inverse)
|
||||
|
@ -295,7 +279,7 @@
|
|||
(change! {:end-x end-x :end-y end-y}))))
|
||||
|
||||
on-change-width
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps gradient-length width height)
|
||||
(fn [point]
|
||||
(let [scale-factor-y (/ gradient-length (/ height 2))
|
||||
|
@ -304,17 +288,35 @@
|
|||
(when (and norm-dist (d/num? norm-dist))
|
||||
(change! {:width norm-dist})))))]
|
||||
|
||||
(when (and gradient
|
||||
[:& gradient-handler-transformed
|
||||
{:editing editing-stop
|
||||
:from-p from-p
|
||||
:to-p to-p
|
||||
:width-p (when (= :radial (:type gradient)) width-p)
|
||||
:from-color {:value start-color :opacity start-opacity}
|
||||
:to-color {:value end-color :opacity end-opacity}
|
||||
:zoom zoom
|
||||
:on-change-start on-change-start
|
||||
:on-change-finish on-change-finish
|
||||
:on-change-width on-change-width}]))
|
||||
|
||||
(mf/defc gradient-handlers
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [id zoom]}]
|
||||
(let [shape-ref (mf/use-memo (mf/deps id) #(refs/object-by-id id))
|
||||
shape (mf/deref shape-ref)
|
||||
|
||||
state (mf/deref refs/colorpicker)
|
||||
gradient (:gradient state)
|
||||
stops (:stops state)
|
||||
editing-stop (:editing-stop state)]
|
||||
|
||||
(when (and (some? gradient)
|
||||
(= id (:shape-id gradient))
|
||||
(not= (:type shape) :text))
|
||||
[:& gradient-handler-transformed
|
||||
{:editing editing-spot
|
||||
:from-p from-p
|
||||
:to-p to-p
|
||||
:width-p (when (= :radial (:type gradient)) width-p)
|
||||
:from-color {:value start-color :opacity start-opacity}
|
||||
:to-color {:value end-color :opacity end-opacity}
|
||||
:zoom zoom
|
||||
:on-change-start on-change-start
|
||||
:on-change-finish on-change-finish
|
||||
:on-change-width on-change-width}])))
|
||||
[:& gradient-handlers*
|
||||
{:zoom zoom
|
||||
:gradient gradient
|
||||
:stops stops
|
||||
:editing-stop editing-stop
|
||||
:shape shape}])))
|
||||
|
|
|
@ -123,7 +123,7 @@
|
|||
|
||||
(defn get-current-target
|
||||
"Extract the current target from event instance (different from target
|
||||
when event triggered in a child of the subscribing element)."
|
||||
when event triggered in a child of the subscribing element)."
|
||||
[^js event]
|
||||
(when (some? event)
|
||||
(.-currentTarget event)))
|
||||
|
|
Loading…
Add table
Reference in a new issue