Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2022-08-23 08:11:58 +02:00
commit b3d6b4b402
46 changed files with 909 additions and 788 deletions

View file

@ -64,6 +64,7 @@
### :bug: Bugs fixed ### :bug: Bugs fixed
- Fix font rendering on grid thumbnails [Taiga #3473](https://tree.taiga.io/project/penpot/issue/3473) - Fix font rendering on grid thumbnails [Taiga #3473](https://tree.taiga.io/project/penpot/issue/3473)
- Fix inconsistent representation of rectangles [Taiga #3977](https://tree.taiga.io/project/penpot/issue/3977)
- Fix recent fonts info [Taiga #3953](https://tree.taiga.io/project/penpot/issue/3953) - Fix recent fonts info [Taiga #3953](https://tree.taiga.io/project/penpot/issue/3953)
- Fix clipped elements affect boards and centering [Taiga #3666](https://tree.taiga.io/project/penpot/issue/3666) - Fix clipped elements affect boards and centering [Taiga #3666](https://tree.taiga.io/project/penpot/issue/3666)
- Fix intro action in multi input [Taiga #3541](https://tree.taiga.io/project/penpot/issue/3541) - Fix intro action in multi input [Taiga #3541](https://tree.taiga.io/project/penpot/issue/3541)
@ -78,6 +79,7 @@
- Fix unexpected removal of guides on copy&paste frames [Taiga #3887](https://tree.taiga.io/project/penpot/issue/3887) by @andrewzhurov - 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 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 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)
### :arrow_up: Deps updates ### :arrow_up: Deps updates
### :heart: Community contributions by (Thank you!) ### :heart: Community contributions by (Thank you!)

View file

@ -224,7 +224,11 @@
#_:else (rect->path shape)) #_:else (rect->path shape))
;; Apply the transforms that had the shape ;; Apply the transforms that had the shape
transform (:transform shape) transform
(cond-> (:transform shape (gmt/matrix))
(:flip-x shape) (gmt/scale (gpt/point -1 1))
(:flip-y shape) (gmt/scale (gpt/point 1 -1)))
new-content (cond-> new-content new-content (cond-> new-content
(some? transform) (some? transform)
(gsp/transform-content (gmt/transform-in (gsc/center-shape shape) transform)))] (gsp/transform-content (gmt/transform-in (gsc/center-shape shape) transform)))]

View file

@ -87,11 +87,17 @@
(defn set-radius-4 (defn set-radius-4
[shape attr value] [shape attr value]
(cond-> shape (let [attr (cond->> attr
(:rx shape) (:flip-x shape)
(-> (dissoc :rx :rx) (get {:r1 :r2 :r2 :r1 :r3 :r4 :r4 :r3})
(assoc :r1 0 :r2 0 :r3 0 :r4 0))
:always (:flip-y shape)
(assoc attr value))) (get {:r1 :r4 :r2 :r3 :r3 :r2 :r4 :r1}))]
(cond-> shape
(:rx shape)
(-> (dissoc :rx :rx)
(assoc :r1 0 :r2 0 :r3 0 :r4 0))
:always
(assoc attr value))))

View file

@ -82,6 +82,7 @@
.color-palette-actions-button { .color-palette-actions-button {
cursor: pointer; cursor: pointer;
display: flex;
& svg { & svg {
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;

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

View file

@ -6,55 +6,32 @@
(ns app.main.data.workspace.colors (ns app.main.data.workspace.colors
(:require (:require
[app.common.colors :as clr] [app.common.colors :as colors]
[app.common.data :as d] [app.common.data :as d]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.main.broadcast :as mbc]
[app.main.data.modal :as md] [app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.layout :as layout] [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.state-helpers :as wsh]
[app.main.data.workspace.texts :as dwt] [app.main.data.workspace.texts :as dwt]
[app.util.color :as uc] [app.util.color :as uc]
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk])) [potok.core :as ptk]))
(defn change-palette-selected ;; A set of keys that are used for shared state identifiers
"Change the library used by the general palette tool" (def ^:const colorpicker-selected-broadcast-key ::colorpicker-selected)
[selected] (def ^:const colorpalette-selected-broadcast-key ::colorpalette-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)))))
(defn show-palette (defn show-palette
"Show the palette tool and change the library it uses" "Show the palette tool and change the library it uses"
[selected] [selected]
(ptk/reify ::show-palette (ptk/reify ::show-palette
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-global :selected-palette] selected))
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (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 ptk/EffectEvent
(effect [_ state _] (effect [_ state _]
@ -158,10 +135,10 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [change-fn (fn [shape attrs] (let [change-fn (fn [shape attrs]
(-> shape (-> shape
(cond-> (not (contains? shape :fills)) (cond-> (not (contains? shape :fills))
(assoc :fills [])) (assoc :fills []))
(assoc-in [:fills position] (into {} attrs))))] (assoc-in [:fills position] (into {} attrs))))]
(transform-fill state ids color change-fn))))) (transform-fill state ids color change-fn)))))
(defn change-fill-and-clear (defn change-fill-and-clear
@ -342,45 +319,11 @@
(-> state (-> state
(assoc-in [:workspace-global :picking-color?] true) (assoc-in [:workspace-global :picking-color?] true)
(assoc ::md/modal {:id (random-uuid) (assoc ::md/modal {:id (random-uuid)
:data {:color clr/black :opacity 1} :data {:color colors/black :opacity 1}
:type :colorpicker :type :colorpicker
:props {:on-change handle-change-color} :props {:on-change handle-change-color}
:allow-click-outside true}))))))) :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 (defn color-att->text
[color] [color]
{:fill-color (:color color) {:fill-color (:color color)
@ -409,7 +352,9 @@
:fill (change-fill [(:shape-id shape)] new-color (:index shape)) :fill (change-fill [(:shape-id shape)] new-color (:index shape))
:stroke (change-stroke [(: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)) :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 (defn apply-color-from-palette
[color is-alt?] [color is-alt?]
@ -431,3 +376,177 @@
(if is-alt? (if is-alt?
(rx/of (change-stroke ids (merge uc/empty-color color) 0)) (rx/of (change-stroke ids (merge uc/empty-color color) 0))
(rx/of (change-fill 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))]))))))))))

View file

@ -117,7 +117,7 @@
(defn add-recent-color (defn add-recent-color
[color] [color]
(us/assert ::ctc/recent-color color) (us/assert! ::ctc/recent-color color)
(ptk/reify ::add-recent-color (ptk/reify ::add-recent-color
ptk/WatchEvent ptk/WatchEvent
(watch [it _ _] (watch [it _ _]

View file

@ -426,3 +426,6 @@
(->> ids (->> ids
(some #(-> (cph/get-parent objects %) :layout)))) (some #(-> (cph/get-parent objects %) :layout))))
workspace-page-objects)) workspace-page-objects))
(def colorpicker
(l/derived :colorpicker st/state))

View file

@ -17,23 +17,31 @@
:radial (tr "workspace.gradients.radial") :radial (tr "workspace.gradients.radial")
nil)) nil))
(mf/defc color-bullet [{:keys [color on-click]}] (mf/defc color-bullet
(if (uc/multiple? color) {::mf/wrap [mf/memo]}
[:div.color-bullet.multiple {:on-click #(when on-click (on-click %))}] [{: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 (if (uc/multiple? color)
(let [color (if (string? color) {:color color :opacity 1} color)] [:div.color-bullet.multiple {:on-click on-click}]
[:div.color-bullet.tooltip.tooltip-right
{:class (dom/classnames :is-library-color (some? (:id color)) ;; No multiple selection
:is-not-library-color (nil? (:id color)) (let [color (if (string? color) {:color color :opacity 1} color)]
:is-gradient (some? (:gradient color))) [:div.color-bullet.tooltip.tooltip-right
:on-click #(when on-click (on-click %)) {:class (dom/classnames :is-library-color (some? (:id color))
:alt (or (:name color) (:color color) (gradient-type->string (:type (:gradient color))))} :is-not-library-color (nil? (:id color))
(if (:gradient color) :is-gradient (some? (:gradient color)))
[:div.color-bullet-wrapper {:style {:background (uc/color->background color)}}] :on-click on-click
[:div.color-bullet-wrapper :alt (or (:name color) (:color color) (gradient-type->string (:type (:gradient color))))}
[:div.color-bullet-left {:style {:background (uc/color->background (assoc color :opacity 1))}}] (if (:gradient color)
[:div.color-bullet-right {:style {:background (uc/color->background 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]}] (mf/defc color-name [{:keys [color size on-click on-double-click]}]
(let [color (if (string? color) {:color color :opacity 1} color) (let [color (if (string? color) {:color color :opacity 1} color)

View file

@ -7,16 +7,26 @@
(ns app.main.ui.hooks (ns app.main.ui.hooks
"A collection of general purpose react hooks." "A collection of general purpose react hooks."
(:require (:require
[app.common.data.macros :as dm]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.uuid :as uuid]
[app.main.broadcast :as mbc]
[app.main.data.shortcuts :as dsc] [app.main.data.shortcuts :as dsc]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.dom.dnd :as dnd] [app.util.dom.dnd :as dnd]
[app.util.storage :refer [storage]]
[app.util.timers :as ts] [app.util.timers :as ts]
[beicon.core :as rx] [beicon.core :as rx]
[goog.functions :as f]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(defn use-id
"Get a stable id value across rerenders."
[]
(mf/use-memo #(dm/str (uuid/next))))
(defn use-rxsub (defn use-rxsub
[ob] [ob]
(let [[state reset-state!] (mf/useState @ob)] (let [[state reset-state!] (mf/useState @ob)]
@ -191,7 +201,6 @@
[(deref state) ref])) [(deref state) ref]))
(defn use-stream (defn use-stream
"Wraps the subscription to a stream into a `use-effect` call" "Wraps the subscription to a stream into a `use-effect` call"
([stream on-subscribe] ([stream on-subscribe]
@ -205,6 +214,7 @@
;; https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state ;; https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
(defn use-previous (defn use-previous
"Returns the value from previuous render cycle."
[value] [value]
(let [ref (mf/use-ref value)] (let [ref (mf/use-ref value)]
(mf/use-effect (mf/use-effect
@ -214,13 +224,27 @@
(mf/ref-val ref))) (mf/ref-val ref)))
(defn use-update-var (defn use-update-var
"Returns a var pointer what automatically updates with latest values."
[value] [value]
(let [ref (mf/use-var value)] (let [ptr (mf/use-var value)]
(mf/use-effect (mf/with-effect [value]
(mf/deps value) (reset! ptr value))
(fn [] ptr))
(reset! ref value)))
ref)) (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 (defn use-equal-memo
[val] [val]
@ -258,4 +282,34 @@
#(cp/focus-objects objects focus))] #(cp/focus-objects objects focus))]
objects))) 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))

View file

@ -8,6 +8,7 @@
(:require (:require
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.spec :as us]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.util.dom :as dom] [app.util.dom :as dom]
@ -73,43 +74,38 @@
(defn use-resize-observer (defn use-resize-observer
[callback] [callback]
(assert (some? callback)) (us/assert! (some? callback) "the `callback` is mandatory")
(let [prev-val-ref (mf/use-ref nil) (let [prev-val-ref (mf/use-ref nil)
current-observer-ref (mf/use-ref nil) observer-ref (mf/use-ref nil)
callback (hooks/use-ref-callback callback)
callback-ref (hooks/use-update-var {:callback callback})
;; We use the ref as a callback when the dom node is ready (or change) ;; We use the ref as a callback when the dom node is ready (or change)
node-ref node-ref (mf/use-fn
(mf/use-callback (fn [^js node]
(fn [^js node] (when (some? node)
(when (some? node) (let [^js observer (mf/ref-val observer-ref)
(let [^js current-observer (mf/ref-val current-observer-ref) ^js prev-val (mf/ref-val prev-val-ref)]
^js prev-val (mf/ref-val prev-val-ref)]
(when (and (not= prev-val node) (some? current-observer)) (when (and (not= prev-val node) (some? observer))
(log/debug :action "disconnect" :js/prev-val prev-val :js/node node) (log/debug :action "disconnect" :js/prev-val prev-val :js/node node)
(.disconnect current-observer) (.disconnect observer)
(mf/set-ref-val! current-observer-ref nil)) (mf/set-ref-val! observer-ref nil))
(when (and (not= prev-val node) (some? node)) (when (and (not= prev-val node) (some? node))
(let [^js observer (let [^js observer (js/ResizeObserver.
(js/ResizeObserver. #(callback last-resize-type (dom/get-client-size node)))]
#(let [callback (get @callback-ref :callback)] (mf/set-ref-val! observer-ref observer)
(callback last-resize-type (dom/get-client-size node))))] (log/debug :action "observe" :js/node node :js/observer observer)
(mf/set-ref-val! current-observer-ref observer) (.observe observer node))))
(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)) node-ref))

View file

@ -6,11 +6,13 @@
(ns app.main.ui.workspace.colorpalette (ns app.main.ui.workspace.colorpalette
(:require (:require
[app.common.data.macros :as dm]
[app.main.data.workspace.colors :as mdc] [app.main.data.workspace.colors :as mdc]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.color-bullet :as cb] [app.main.ui.components.color-bullet :as cb]
[app.main.ui.components.dropdown :refer [dropdown]] [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.hooks.resize :refer [use-resize-hook]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
@ -19,36 +21,21 @@
[app.util.object :as obj] [app.util.object :as obj]
[cuerdas.core :as str] [cuerdas.core :as str]
[goog.events :as events] [goog.events :as events]
[okulary.core :as l]
[rumext.alpha :as mf])) [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 ;; --- 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} [:div.color-cell {:on-click select-color}
[:& cb/color-bullet {:color color}] [:& cb/color-bullet {:color color}]
[:& cb/color-name {:color color}]])) [:& cb/color-name {:color color}]]))
(mf/defc palette (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}) (let [state (mf/use-state {:show-menu false})
width (:width @state 0) width (:width @state 0)
@ -97,54 +84,66 @@
(fn [_] (fn [_]
(let [dom (mf/ref-val container) (let [dom (mf/ref-val container)
width (obj/get dom "clientWidth")] 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 (mf/use-layout-effect
#(let [dom (mf/ref-val container) #(let [dom (mf/ref-val container)
width (obj/get dom "clientWidth")] width (obj/get dom "clientWidth")]
(swap! state assoc :width width))) (swap! state assoc :width width)))
(mf/use-effect (mf/with-effect []
#(let [key1 (events/listen js/window "resize" on-resize)] (let [key1 (events/listen js/window "resize" on-resize)]
(fn [] #(events/unlistenByKey key1)))
(events/unlistenByKey key1))))
[:div.color-palette {:ref parent-ref [:div.color-palette {:ref parent-ref
:class (dom/classnames :no-text (< size 72)) :class (dom/classnames :no-text (< size 72))
:style #js {"--height" (str size "px") :style #js {"--height" (dm/str size "px")
"--bullet-size" (str (if (< size 72) (- size 15) (- size 30)) "px")}} "--bullet-size" (dm/str (if (< size 72) (- size 15) (- size 30)) "px")}}
[:div.resize-area {:on-pointer-down on-pointer-down [:div.resize-area {:on-pointer-down on-pointer-down
:on-lost-pointer-capture on-lost-pointer-capture :on-lost-pointer-capture on-lost-pointer-capture
:on-mouse-move on-mouse-move}] :on-mouse-move on-mouse-move}]
[:& dropdown {:show (:show-menu @state) [:& dropdown {:show (:show-menu @state)
:on-close #(swap! state assoc :show-menu false)} :on-close #(swap! state assoc :show-menu false)}
[:ul.workspace-context-menu.palette-menu [:ul.workspace-context-menu.palette-menu
(for [[idx cur-library] (map-indexed vector (vals shared-libs))] (for [{:keys [data id] :as library} (vals shared-libs)]
(let [colors (-> cur-library (get-in [:data :colors]) vals)] (let [colors (-> data :colors vals)]
[:li.palette-library [:li.palette-library
{:key (str "library-" idx) {:key (dm/str "library-" id)
:on-click #(st/emit! (mdc/change-palette-selected (:id cur-library)))} :on-click on-select-palette
(when (= selected (:id cur-library)) i/tick) :data-palette (dm/str id)}
[:div.library-name (str (:name cur-library) " " (str/format "(%s)" (count colors)))] (when (= selected id) i/tick)
[:div.library-name (str (:name library) " " (str/ffmt "(%)" (count colors)))]
[:div.color-sample [:div.color-sample
(for [[idx {:keys [color]}] (map-indexed vector (take 7 colors))] (for [[i {:keys [color]}] (map-indexed vector (take 7 colors))]
[:& cb/color-bullet {:key (str "color-" idx) [:& cb/color-bullet {:key (dm/str "color-" i)
:color color}])]])) :color color}])]]))
[:li.palette-library [: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) (when (= selected :file) i/tick)
[:div.library-name (str (tr "workspace.libraries.colors.file-library") [:div.library-name (dm/str
(str/format " (%s)" (count file-colors)))] (tr "workspace.libraries.colors.file-library")
(str/ffmt " (%)" (count file-colors)))]
[:div.color-sample [:div.color-sample
(for [[idx color] (map-indexed vector (take 7 (vals file-colors))) ] (for [[i color] (map-indexed vector (take 7 (vals file-colors))) ]
[:& cb/color-bullet {:key (str "color-" idx) [:& cb/color-bullet {:key (dm/str "color-" i)
:color color}])]] :color color}])]]
[:li.palette-library [: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) (when (= selected :recent) i/tick)
[:div.library-name (str (tr "workspace.libraries.colors.recent-colors") [:div.library-name (str (tr "workspace.libraries.colors.recent-colors")
(str/format " (%s)" (count recent-colors)))] (str/format " (%s)" (count recent-colors)))]
@ -178,34 +177,32 @@
(let [recent-colors (mf/deref refs/workspace-recent-colors) (let [recent-colors (mf/deref refs/workspace-recent-colors)
file-colors (mf/deref refs/workspace-file-colors) file-colors (mf/deref refs/workspace-file-colors)
shared-libs (mf/deref refs/workspace-libraries) shared-libs (mf/deref refs/workspace-libraries)
selected (or (mf/deref selected-palette-ref) :recent) selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent)
current-library-colors (mf/use-state [])]
(mf/use-effect colors (mf/use-state [])
(mf/deps selected) on-select (mf/use-fn #(reset! 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)))))))
(mf/use-effect (mf/with-effect [@selected]
(mf/deps recent-colors) (fn []
(fn [] (reset! colors
(when (= selected :recent) (into []
(reset! current-library-colors (reverse recent-colors))))) (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/with-effect [recent-colors @selected]
(mf/deps file-colors) (when (= @selected :recent)
(fn [] (reset! colors (reverse recent-colors))))
(when (= selected :file)
(reset! current-library-colors (into [] (->> (vals file-colors)
(sort-by :name)))))))
[:& 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 :recent-colors recent-colors
:file-colors file-colors :file-colors file-colors
:shared-libs shared-libs :shared-libs shared-libs
:selected selected}])) :selected @selected
:on-select on-select}]))

View file

@ -6,7 +6,6 @@
(ns app.main.ui.workspace.colorpicker (ns app.main.ui.workspace.colorpicker
(:require (:require
[app.common.colors :as clr]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace.colors :as dc] [app.main.data.workspace.colors :as dc]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
@ -41,247 +40,102 @@
(def viewport (def viewport
(l/derived :vport refs/workspace-local)) (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 ;; --- 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 (mf/defc colorpicker
[{:keys [data disable-gradient disable-opacity on-change on-accept]}] [{:keys [data disable-gradient disable-opacity on-change on-accept]}]
(let [state (mf/use-state (data->state data)) (let [state (mf/deref refs/colorpicker)
active-tab (mf/use-state :ramp #_:harmony #_:hsva) node-ref (mf/use-ref)
ref-picker (mf/use-ref) ;; TODO: I think we need to put all this picking state under
;; the same object for avoid creating adhoc refs for each
dirty? (mf/use-var false) ;; value
last-color (mf/use-var data) picking-color? (mf/deref picking-color?)
picked-color (mf/deref picked-color)
picking-color? (mf/deref picking-color?)
picked-color (mf/deref picked-color)
picked-color-select (mf/deref picked-color-select) picked-color-select (mf/deref picked-color-select)
editing-spot-state (mf/deref editing-spot-state-ref) current-color (:current-color state)
current-gradient (mf/deref current-gradient-ref)
current-color (:current-color @state) active-tab (mf/use-state :ramp #_:harmony #_:hsva)
set-ramp-tab! (mf/use-fn #(reset! active-tab :ramp))
change-tab set-harmony-tab! (mf/use-fn #(reset! active-tab :harmony))
(fn [tab] set-hsva-tab! (mf/use-fn #(reset! active-tab :hsva))
#(reset! active-tab tab))
handle-change-color handle-change-color
(fn [changes] (mf/use-fn #(st/emit! (dc/update-colorpicker-color %)))
(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)))
handle-click-picker handle-click-picker
(fn [] (mf/use-fn
(if picking-color? (mf/deps picking-color?)
(do (modal/disallow-click-outside!) (fn []
(st/emit! (dc/stop-picker))) (if picking-color?
(do (modal/allow-click-outside!) (do (modal/disallow-click-outside!)
(st/emit! (dc/start-picker))))) (st/emit! (dc/stop-picker)))
(do (modal/allow-click-outside!)
(st/emit! (dc/start-picker))))))
handle-change-stop handle-change-stop
(fn [offset] (mf/use-fn
(when-let [offset-color (get-in @state [:stops offset])] (fn [offset]
(swap! state assoc (st/emit! (dc/select-colorpicker-gradient-stop offset))))
:current-color offset-color
:editing-stop offset)
(st/emit! (dc/select-gradient-stop offset))))
on-select-library-color on-select-library-color
(fn [color] (mf/use-fn
(let [editing-stop (:editing-stop @state) (fn [color]
is-gradient? (some? (:gradient color))] (on-change 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)))))
on-add-library-color on-add-library-color
(fn [_] (mf/use-fn
(st/emit! (dwl/add-color (state->data @state)))) (mf/deps state)
(fn [_]
(st/emit! (dwl/add-color (dc/get-color-from-colorpicker-state state)))))
on-activate-gradient on-activate-linear-gradient
(fn [type] (mf/use-fn #(st/emit! (dc/activate-colorpicker-gradient :linear-gradient)))
(fn []
(reset! dirty? true) on-activate-radial-gradient
(if (= type (:type @state)) (mf/use-fn #(st/emit! (dc/activate-colorpicker-gradient :radial-gradient)))]
(do
(swap! state assoc :type :color) ;; Initialize colorpicker state
(swap! state dissoc :editing-stop :stops :gradient-data) (mf/with-effect []
(st/emit! (dc/stop-gradient))) (st/emit! (dc/initialize-colorpicker on-change))
(let [gradient-data (create-gradient-data type)] (partial st/emit! (dc/finalize-colorpicker)))
(swap! state assoc :type type :gradient-data gradient-data)
(when (not (:stops @state)) ;; Update colorpicker with external color changes
(swap! state assoc (mf/with-effect [data]
:editing-stop 0 (st/emit! (dc/update-colorpicker data)))
:stops {0 (:current-color @state)
1 (-> (:current-color @state)
(assoc :alpha 0))}))))))]
;; Updates the CSS color variable when there is a change in the color ;; Updates the CSS color variable when there is a change in the color
(mf/use-effect (mf/with-effect [current-color]
(mf/deps current-color) (let [node (mf/ref-val node-ref)
(fn [] (let [node (mf/ref-val ref-picker) {:keys [r g b h v]} current-color
{:keys [r g b h v]} current-color rgb [r g b]
rgb [r g b] hue-rgb (uc/hsv->rgb [h 1.0 255])
hue-rgb (uc/hsv->rgb [h 1.0 255]) hsl-from (uc/hsv->hsl [h 0.0 v])
hsl-from (uc/hsv->hsl [h 0.0 v]) hsl-to (uc/hsv->hsl [h 1.0 v])
hsl-to (uc/hsv->hsl [h 1.0 v])
format-hsl (fn [[h s l]] format-hsl (fn [[h s l]]
(str/fmt "hsl(%s, %s, %s)" (str/fmt "hsl(%s, %s, %s)"
h h
(str (* s 100) "%") (str (* s 100) "%")
(str (* l 100) "%")))] (str (* l 100) "%")))]
(dom/set-css-property! node "--color" (str/join ", " rgb)) (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 "--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-from" (format-hsl hsl-from))
(dom/set-css-property! node "--saturation-grad-to" (format-hsl hsl-to))))) (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)))))
;; Updates color when used el pixel picker ;; Updates color when used el pixel picker
(mf/use-effect (mf/with-effect [picking-color? picked-color picked-color-select]
(mf/deps picking-color? picked-color picked-color-select) (when (and picking-color? picked-color picked-color-select)
(fn [] (let [[r g b alpha] picked-color
(when (and picking-color? picked-color picked-color-select) hex (uc/rgb->hex [r g b])
(let [[r g b alpha] picked-color [h s v] (uc/hex->hsv hex)]
hex (uc/rgb->hex [r g b]) (handle-change-color {:hex hex
[h s v] (uc/hex->hsv hex)] :r r :g g :b b
(handle-change-color {:hex hex :h h :s s :v v
:r r :g g :b b :alpha (/ alpha 255)}))))
:h h :s s :v v
:alpha (/ alpha 255)})))))
;; Changes when another gradient handler is selected [:div.colorpicker {:ref node-ref}
(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-content [:div.colorpicker-content
[:div.top-actions [:div.top-actions
[:button.picker-btn [:button.picker-btn
@ -292,70 +146,81 @@
(when (not disable-gradient) (when (not disable-gradient)
[:div.gradients-buttons [:div.gradients-buttons
[:button.gradient.linear-gradient [:button.gradient.linear-gradient
{:on-click (on-activate-gradient :linear-gradient) {:on-click on-activate-linear-gradient
:class (when (= :linear-gradient (:type @state)) "active")}] :class (when (= :linear-gradient (:type state)) "active")}]
[:button.gradient.radial-gradient [:button.gradient.radial-gradient
{:on-click (on-activate-gradient :radial-gradient) {:on-click on-activate-radial-gradient
:class (when (= :radial-gradient (:type @state)) "active")}]])] :class (when (= :radial-gradient (:type state)) "active")}]])]
[:& gradients {:type (:type @state)
:stops (:stops @state) (when (or (= (:type state) :linear-gradient)
:editing-stop (:editing-stop @state) (= (:type state) :radial-gradient))
:on-select-stop handle-change-stop}] [:& gradients
{:stops (:stops state)
:editing-stop (:editing-stop state)
:on-select-stop handle-change-stop}])
[:div.colorpicker-tabs [:div.colorpicker-tabs
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand [:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
{:class (when (= @active-tab :ramp) "active") {:class (when (= @active-tab :ramp) "active")
:alt (tr "workspace.libraries.colors.rgba") :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 [:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
{:class (when (= @active-tab :harmony) "active") {:class (when (= @active-tab :harmony) "active")
:alt (tr "workspace.libraries.colors.rgb-complementary") :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 [:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
{:class (when (= @active-tab :hsva) "active") {:class (when (= @active-tab :hsva) "active")
:alt (tr "workspace.libraries.colors.hsv") :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? (if picking-color?
[:div.picker-detail-wrapper [:div.picker-detail-wrapper
[:div.center-circle] [:div.center-circle]
[:canvas#picker-detail {:width 200 :height 160}]] [:canvas#picker-detail {:width 200 :height 160}]]
(case @active-tab (case @active-tab
:ramp [:& ramp-selector {:color current-color :ramp
:disable-opacity disable-opacity [:& ramp-selector
:on-change handle-change-color {:color current-color
:on-start-drag #(st/emit! (dwu/start-undo-transaction)) :disable-opacity disable-opacity
:on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}] :on-change handle-change-color
:harmony [:& harmony-selector {:color current-color :on-start-drag #(st/emit! (dwu/start-undo-transaction))
:disable-opacity disable-opacity :on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}]
:on-change handle-change-color :harmony
:on-start-drag #(st/emit! (dwu/start-undo-transaction)) [:& harmony-selector
:on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}] {:color current-color
:hsva [:& hsva-selector {:color current-color :disable-opacity disable-opacity
:disable-opacity disable-opacity :on-change handle-change-color
:on-change handle-change-color :on-start-drag #(st/emit! (dwu/start-undo-transaction))
:on-start-drag #(st/emit! (dwu/start-undo-transaction)) :on-finish-drag #(st/emit! (dwu/commit-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)) nil))
[:& color-inputs {:type (if (= @active-tab :hsva) :hsv :rgb) [:& color-inputs
:disable-opacity disable-opacity {:type (if (= @active-tab :hsva) :hsv :rgb)
:color current-color :disable-opacity disable-opacity
:on-change handle-change-color}] :color current-color
:on-change handle-change-color}]
[:& libraries {:current-color current-color [:& libraries
:disable-gradient disable-gradient {:current-color current-color
:disable-opacity disable-opacity :disable-gradient disable-gradient
:on-select-color on-select-library-color :disable-opacity disable-opacity
:on-add-library-color on-add-library-color}] :on-select-color on-select-library-color
:on-add-library-color on-add-library-color}]
(when on-accept (when on-accept
[:div.actions [:div.actions
[:button.btn-primary.btn-large [:button.btn-primary.btn-large
{:on-click (fn [] {:on-click (fn []
(on-accept (state->data @state)) (on-accept (dc/get-color-from-colorpicker-state state))
(modal/hide!))} (modal/hide!))}
(tr "workspace.libraries.colors.save-color")]])]])) (tr "workspace.libraries.colors.save-color")]])]]))

View file

@ -11,13 +11,17 @@
[app.util.dom :as dom] [app.util.dom :as dom]
[rumext.alpha :as mf])) [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]}] (mf/defc color-inputs [{:keys [type color disable-opacity on-change]}]
(let [{red :r green :g blue :b (let [{red :r green :g blue :b
hue :h saturation :s value :v hue :h saturation :s value :v
hex :hex alpha :alpha} color hex :hex alpha :alpha} color
parse-hex (fn [val] (if (= (first val) \#) val (str \# val)))
refs {:hex (mf/use-ref nil) refs {:hex (mf/use-ref nil)
:r (mf/use-ref nil) :r (mf/use-ref nil)
:g (mf/use-ref nil) :g (mf/use-ref nil)

View file

@ -6,32 +6,31 @@
(ns app.main.ui.workspace.colorpicker.gradients (ns app.main.ui.workspace.colorpicker.gradients
(:require (:require
[app.common.data.macros :as dm]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(defn gradient->string [stops] (defn- format-rgba
(let [format-stop [{:keys [r g b alpha offset]}]
(fn [[offset {:keys [r g b alpha]}]] (str/ffmt "rgba(%1, %2, %3, %4) %5%%" r g b alpha (* offset 100)))
(str/fmt "rgba(%s, %s, %s, %s) %s"
r g b alpha (str (* offset 100) "%")))
gradient-css (str/join "," (map format-stop stops))] (defn- gradient->string [stops]
(str/fmt "linear-gradient(90deg, %s)" gradient-css))) (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]}] (mf/defc gradients
(when (#{:linear-gradient :radial-gradient} type) [{:keys [stops editing-stop on-select-stop]}]
[:div.gradient-stops [:div.gradient-stops
[:div.gradient-background-wrapper [:div.gradient-background-wrapper
[:div.gradient-background {:style {:background (gradient->string stops)}}]] [:div.gradient-background {:style {:background (gradient->string stops)}}]]
[:div.gradient-stop-wrapper [:div.gradient-stop-wrapper
(for [[offset value] stops] (for [{:keys [offset hex r g b alpha] :as value} stops]
[:div.gradient-stop [:div.gradient-stop
{:class (when (= editing-stop offset) "active") {:class (when (= editing-stop offset) "active")
:on-click (partial on-select-stop offset) :on-click (partial on-select-stop offset)
:style {:left (str (* offset 100) "%")}} :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/ffmt "rgba(%1, %2, %3, %4)" r g b alpha)}}]])]])
[: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)}}]])])]]))

View file

@ -6,90 +6,85 @@
(ns app.main.ui.workspace.colorpicker.libraries (ns app.main.ui.workspace.colorpicker.libraries
(:require (:require
[app.common.uuid :refer [uuid]] [app.common.data.macros :as dm]
[app.main.data.workspace.colors :as dc] [app.main.data.workspace.colors :as dc]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.color-bullet :refer [color-bullet]] [app.main.ui.components.color-bullet :refer [color-bullet]]
[app.main.ui.hooks :as h]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[okulary.core :as l]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(def selected-palette-ref
(-> (l/in [:workspace-global :selected-palette-colorpicker])
(l/derived st/state)))
(mf/defc libraries (mf/defc libraries
[{:keys [on-select-color on-add-library-color disable-gradient disable-opacity]}] [{:keys [on-select-color on-add-library-color disable-gradient disable-opacity]}]
(let [selected-library (or (mf/deref selected-palette-ref) :recent) (let [selected (h/use-shared-state dc/colorpicker-selected-broadcast-key :recent)
current-library-colors (mf/use-state []) current-colors (mf/use-state [])
shared-libs (mf/deref refs/workspace-libraries) shared-libs (mf/deref refs/workspace-libraries)
file-colors (mf/deref refs/workspace-file-colors) file-colors (mf/deref refs/workspace-file-colors)
recent-colors (mf/deref refs/workspace-recent-colors) recent-colors (mf/deref refs/workspace-recent-colors)
parse-selected on-library-change
(fn [selected-str] (mf/use-fn
(if (#{"recent" "file"} selected-str) (fn [event]
(keyword selected-str) (let [val (dom/get-target-val event)]
(uuid selected-str))) (reset! selected
(if (or (= val "recent")
(= val "file"))
(keyword val)
(parse-uuid val))))))
check-valid-color? (fn [color] check-valid-color?
(and (or (not disable-gradient) (not (:gradient color))) (fn [color]
(or (not disable-opacity) (= 1 (:opacity color)))))] (and (or (not disable-gradient) (not (:gradient color)))
(or (not disable-opacity) (= 1 (:opacity color)))))]
;; Load library colors when the select is changed ;; Load library colors when the select is changed
(mf/use-effect (mf/with-effect [@selected recent-colors file-colors]
(mf/deps selected-library) (let [colors (cond
(fn [] (= @selected :recent)
(let [mapped-colors ;; The `map?` check is to keep backwards compatibility. We transform from string to map
(cond (map #(if (map? %) % {:color %}) (reverse (or recent-colors [])))
(= 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 [])))
(= selected-library :file) (= @selected :file)
(vals file-colors) (vals file-colors)
:else ;; Library UUID :else ;; Library UUID
(->> (get-in shared-libs [selected-library :data :colors]) (as-> @selected file-id
(vals) (->> (get-in shared-libs [file-id :data :colors])
(map #(merge % {:file-id selected-library}))))] (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 ;; If the file colors change and the file option is selected updates the state
(mf/use-effect (mf/with-effect [file-colors]
(mf/deps file-colors) (when (= @selected :file)
(fn [] (when (= selected-library :file) (let [colors (vals file-colors)]
(let [colors (vals file-colors)] (reset! current-colors (into [] (filter check-valid-color?) colors)))))
(reset! current-library-colors (into [] (filter check-valid-color?) colors))))))
[:div.libraries [:div.libraries
[:select {:on-change (fn [e] [:select {:on-change on-library-change :value (name @selected)}
(when-let [val (parse-selected (dom/get-target-val e))]
(st/emit! (dc/change-palette-selected-colorpicker val))))
:value (name selected-library)}
[:option {:value "recent"} (tr "workspace.libraries.colors.recent-colors")] [:option {:value "recent"} (tr "workspace.libraries.colors.recent-colors")]
[:option {:value "file"} (tr "workspace.libraries.colors.file-library")] [:option {:value "file"} (tr "workspace.libraries.colors.file-library")]
(for [[_ {:keys [name id]}] shared-libs] (for [[_ {:keys [name id]}] shared-libs]
[:option {:key id [:option {:key id :value id} name])]
:value id} name])]
[:div.selected-colors [:div.selected-colors
(when (= selected-library :file) (when (= @selected :file)
[:div.color-bullet.button.plus-button {:style {:background-color "var(--color-white)"} [:div.color-bullet.button.plus-button {:style {:background-color "var(--color-white)"}
:on-click on-add-library-color} :on-click on-add-library-color}
i/plus]) i/plus])
[:div.color-bullet.button {:style {:background-color "var(--color-white)"} [: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] i/palette]
(for [[idx color] (map-indexed vector @current-library-colors)] (for [[idx color] (map-indexed vector @current-colors)]
[:& color-bullet {:key (str "color-" idx) [:& color-bullet
:color color {:key (dm/str "color-" idx)
:on-click #(on-select-color color)}])]])) :color color
:on-click on-select-color}])]]))

View file

@ -79,7 +79,7 @@
shape-bb-ref (hooks/use-update-var shape-bb) 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-ref (mf/use-memo (mf/deps page-id id) #(refs/thumbnail-frame-data page-id id))
thumbnail-data (mf/deref thumbnail-data-ref) thumbnail-data (mf/deref thumbnail-data-ref)

View file

@ -152,12 +152,9 @@
(fn [state] (fn [state]
(let [old-state (mf/ref-val prev-value)] (let [old-state (mf/ref-val prev-value)]
(if (and (some? state) (some? old-state)) (if (and (some? state) (some? old-state))
(let [block-changes (ted/get-content-changes old-state state) (let [block-changes (ted/get-content-changes old-state state)
prev-data (ted/get-editor-current-inline-styles old-state)
prev-data (-> (ted/get-editor-current-inline-styles old-state) block-to-setup (get-blocks-to-setup block-changes)
(dissoc :text-align :text-direction))
block-to-setup (get-blocks-to-setup block-changes)
block-to-add-styles (get-blocks-to-add-styles block-changes)] block-to-add-styles (get-blocks-to-add-styles block-changes)]
(-> state (-> state
(ted/setup-block-styles block-to-setup prev-data) (ted/setup-block-styles block-to-setup prev-data)
@ -211,11 +208,12 @@
handle-pasted-text handle-pasted-text
(fn [text _ _] (fn [text _ _]
(let [style (ted/get-editor-current-inline-styles state) (let [current-block-styles (ted/get-editor-current-block-data state)
state (-> (ted/insert-text state text style) inline-styles (ted/get-editor-current-inline-styles state)
(handle-change))] style (merge current-block-styles inline-styles)
state (-> (ted/insert-text state text style)
(handle-change))]
(st/emit! (dwt/update-editor-state shape state))) (st/emit! (dwt/update-editor-state shape state)))
"handled")] "handled")]
(mf/use-layout-effect on-mount) (mf/use-layout-effect on-mount)

View file

@ -19,7 +19,7 @@
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[clojure.set :refer [union]] [clojure.set :refer [rename-keys union]]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(def measure-attrs (def measure-attrs
@ -46,10 +46,26 @@
:svg-raw #{:size :position :rotation} :svg-raw #{:size :position :rotation}
:text #{:size :position :rotation}}) :text #{:size :position :rotation}})
(defn select-measure-keys
"Consider some shapes can be drawn from bottom to top or from left to right"
[shape]
(let [shape (cond
(and (:flip-x shape) (:flip-y shape))
(rename-keys shape {:r1 :r3 :r2 :r4 :r3 :r1 :r4 :r2})
(:flip-x shape)
(rename-keys shape {:r1 :r2 :r2 :r1 :r3 :r4 :r4 :r3})
(:flip-y shape)
(rename-keys shape {:r1 :r4 :r2 :r3 :r3 :r2 :r4 :r1})
:else
shape)]
(select-keys shape measure-attrs)))
;; -- User/drawing coords ;; -- User/drawing coords
(mf/defc measures-menu (mf/defc measures-menu
[{:keys [ids ids-with-children values type all-types shape] :as props}] [{:keys [ids ids-with-children values type all-types shape] :as props}]
(let [options (if (= type :multiple) (let [options (if (= type :multiple)
(reduce #(union %1 %2) (map #(get type->options %) all-types)) (reduce #(union %1 %2) (map #(get type->options %) all-types))
(get type->options type)) (get type->options type))
@ -221,7 +237,7 @@
(st/emit! (dch/update-shapes ids (fn [shape] (assoc shape :hide-in-viewer (not value)))))))) (st/emit! (dch/update-shapes ids (fn [shape] (assoc shape :hide-in-viewer (not value))))))))
select-all #(-> % (dom/get-target) (.select))] select-all #(-> % (dom/get-target) (.select))]
(mf/use-layout-effect (mf/use-layout-effect
(mf/deps radius-mode @radius-multi?) (mf/deps radius-mode @radius-multi?)
(fn [] (fn []

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.rows.color-row (ns app.main.ui.workspace.sidebar.options.rows.color-row
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -22,34 +23,8 @@
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(defn color-picker-callback (defn opacity->string
[color disable-gradient disable-opacity handle-change-color handle-open handle-close] [opacity]
(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]
(if (= opacity :multiple) (if (= opacity :multiple)
"" ""
(str (-> opacity (str (-> opacity
@ -57,7 +32,8 @@
(* 100) (* 100)
(fmt/format-number))))) (fmt/format-number)))))
(defn remove-multiple [v] (defn remove-multiple
[v]
(if (= v :multiple) nil v)) (if (= v :multiple) nil v))
(mf/defc color-row (mf/defc color-row
@ -68,64 +44,88 @@
file-colors (mf/deref refs/workspace-file-colors) file-colors (mf/deref refs/workspace-file-colors)
shared-libs (mf/deref refs/workspace-libraries) shared-libs (mf/deref refs/workspace-libraries)
hover-detach (mf/use-state false) 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) parse-color
file-colors (mf/use-fn
(get-in shared-libs [(:file-id color) :data :colors])) (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] handle-select
(-> color (mf/use-fn
(update :color #(or % (:value color))))) (mf/deps select-only color)
(fn []
(select-only color)))
detach-value (fn [] handle-value-change
(when on-detach (on-detach color))) (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] handle-opacity-change
(when (:fn @on-change-var) ((:fn @on-change-var) (-> color (mf/use-fn
(assoc :color new-value) (mf/deps color on-change)
(dissoc :gradient))))) (fn [value]
(on-change (assoc color
:opacity (/ value 100)
:id nil
:file-id nil))))
change-opacity (fn [new-opacity] handle-click-color
(when (:fn @on-change-var) ((:fn @on-change-var) (assoc color (mf/use-fn
:opacity new-opacity (mf/deps disable-gradient disable-opacity on-change on-close on-open)
:id nil (fn [color event]
:file-id nil)))) (let [color (cond
(uc/multiple? color)
{:color cp/default-color
:opacity 1}
handle-pick-color (fn [color] (= :multiple (:opacity color))
(when (:fn @on-change-var) ((:fn @on-change-var) (merge uc/empty-color color)))) (assoc color :opacity 1)
handle-select (fn [] :else
(select-only color)) color)
handle-open (fn [color] {:keys [x y]} (dom/get-client-position event)
(when on-open (on-open (merge uc/empty-color color))))
handle-close (fn [value opacity id file-id] props {:x x
(when on-close (on-close value opacity id file-id))) :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] (when on-open
(-> new-value (on-open (merge uc/empty-color color)))
change-value))
handle-opacity-change (fn [value] (modal/show! :colorpicker props))))
(change-opacity (/ value 100)))
handle-click-color (color-picker-callback color
disable-gradient
disable-opacity
handle-pick-color
handle-open
handle-close)
prev-color (h/use-previous color) prev-color (h/use-previous color)
on-drop on-drop
(fn [_ data] (mf/use-fn
(on-reorder (:index data))) (mf/deps on-reorder)
(fn [_ data]
(on-reorder (:index data))))
[dprops dref] (if (some? on-reorder) [dprops dref] (if (some? on-reorder)
(h/use-sortable (h/use-sortable
@ -138,11 +138,9 @@
:name (str "Color row" index)}) :name (str "Color row" index)})
[nil nil])] [nil nil])]
(mf/use-effect (mf/with-effect [color prev-color]
(mf/deps color prev-color) (when (not= prev-color color)
(fn [] (modal/update-props! :colorpicker {:data (parse-color color)})))
(when (not= prev-color color)
(modal/update-props! :colorpicker {:data (parse-color color)}))))
[:div.row-flex.color-data {:title title [:div.row-flex.color-data {:title title
:class (dom/classnames :class (dom/classnames
@ -182,7 +180,7 @@
(when select-only (when select-only
[:div.element-set-actions-button {:on-click handle-select} [:div.element-set-actions-button {:on-click handle-select}
i/pointer-inner])] i/pointer-inner])]
;; Rendering a plain color/opacity ;; Rendering a plain color/opacity
:else :else

View file

@ -21,11 +21,11 @@
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [select-measure-keys measure-attrs measures-menu]]
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-attrs shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-attrs shadow-menu]]
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]]
[app.main.ui.workspace.sidebar.options.menus.text :as ot] [app.main.ui.workspace.sidebar.options.menus.text :as ot]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
;; Define how to read each kind of attribute depending on the shape type: ;; Define how to read each kind of attribute depending on the shape type:
;; - shape: read the attribute directly from the shape. ;; - shape: read the attribute directly from the shape.
@ -197,8 +197,10 @@
:shape (let [;; Get the editable attrs from the shape, ensuring that all attributes :shape (let [;; Get the editable attrs from the shape, ensuring that all attributes
;; are present, with value nil if they are not present in the shape. ;; are present, with value nil if they are not present in the shape.
shape-values (merge shape-values (merge
(into {} (map #(vector % nil)) editable-attrs) (into {} (map #(vector % nil)) editable-attrs)
(select-keys shape editable-attrs))] (cond
(= attr-group :measure) (select-measure-keys shape)
:else (select-keys shape editable-attrs)))]
[(conj ids id) [(conj ids id)
(merge-attrs values shape-values)]) (merge-attrs values shape-values)])

View file

@ -12,7 +12,7 @@
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [select-measure-keys measures-menu]]
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]]
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]]
[app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]] [app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]]
@ -23,8 +23,7 @@
[{:keys [shape] :as props}] [{:keys [shape] :as props}]
(let [ids [(:id shape)] (let [ids [(:id shape)]
type (:type shape) type (:type shape)
measure-values (select-measure-keys shape)
measure-values (select-keys shape measure-attrs)
layer-values (select-keys shape layer-attrs) layer-values (select-keys shape layer-attrs)
constraint-values (select-keys shape constraint-attrs) constraint-values (select-keys shape constraint-attrs)
fill-values (select-keys shape fill-attrs) fill-values (select-keys shape fill-attrs)

View file

@ -19,7 +19,6 @@
[app.util.dom :as dom] [app.util.dom :as dom]
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[okulary.core :as l]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(def gradient-line-stroke-width 2) (def gradient-line-stroke-width 2)
@ -32,12 +31,6 @@
(def gradient-square-stroke-color "var(--color-white)") (def gradient-square-stroke-color "var(--color-white)")
(def gradient-square-stroke-color-selected "var(--color-select)") (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]}] (mf/defc shadow [{:keys [id x y width height offset]}]
[:filter {:id id [:filter {:id id
:x x :x x
@ -130,27 +123,32 @@
(let [moving-point (mf/use-var nil) (let [moving-point (mf/use-var nil)
angle (+ 90 (gpt/angle from-p to-p)) angle (+ 90 (gpt/angle from-p to-p))
on-click (fn [position event] on-click
(dom/stop-propagation event) (fn [position event]
(dom/prevent-default event) (dom/stop-propagation event)
(when (#{:from-p :to-p} position) (dom/prevent-default event)
(st/emit! (dc/select-gradient-stop (case position (when (#{:from-p :to-p} position)
:from-p 0 (st/emit! (dc/select-colorpicker-gradient-stop
:to-p 1))))) (case position
:from-p 0
:to-p 1)))))
on-mouse-down (fn [position event] on-mouse-down
(dom/stop-propagation event) (fn [position event]
(dom/prevent-default event) (dom/stop-propagation event)
(reset! moving-point position) (dom/prevent-default event)
(when (#{:from-p :to-p} position) (reset! moving-point position)
(st/emit! (dc/select-gradient-stop (case position (when (#{:from-p :to-p} position)
:from-p 0 (st/emit! (dc/select-colorpicker-gradient-stop
:to-p 1))))) (case position
:from-p 0
:to-p 1)))))
on-mouse-up (fn [_position event] on-mouse-up
(dom/stop-propagation event) (fn [_position event]
(dom/prevent-default event) (dom/stop-propagation event)
(reset! moving-point nil))] (dom/prevent-default event)
(reset! moving-point nil))]
(mf/use-effect (mf/use-effect
(mf/deps @moving-point from-p to-p width-p) (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-down (partial on-mouse-down :to-p)
:on-mouse-up (partial on-mouse-up :to-p)}]])) :on-mouse-up (partial on-mouse-up :to-p)}]]))
(mf/defc gradient-handlers*
(mf/defc gradient-handlers [{:keys [zoom stops gradient editing-stop shape]}]
{::mf/wrap [mf/memo]} (let [transform (gsh/transform-matrix shape)
[{: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)
transform-inverse (gsh/inverse-transform-matrix shape) transform-inverse (gsh/inverse-transform-matrix shape)
{:keys [x y width height] :as sr} (:selrect shape) {:keys [x y width height] :as sr} (:selrect shape)
[{start-color :color start-opacity :opacity} [{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))) from-p (-> (gpt/point (+ x (* width (:start-x gradient)))
(+ y (* height (:start-y gradient)))) (+ y (* height (:start-y gradient))))
(gpt/transform transform)) (gpt/transform transform))
to-p (-> (gpt/point (+ x (* width (:end-x gradient))) to-p (-> (gpt/point (+ x (* width (:end-x gradient)))
(+ y (* height (:end-y gradient)))) (+ y (* height (:end-y gradient))))
(gpt/transform transform)) (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) gradient-length (gpt/length gradient-vec)
width-v (-> gradient-vec width-v (-> gradient-vec
@ -271,13 +256,12 @@
width-p (gpt/add from-p width-v) width-p (gpt/add from-p width-v)
change! change!
(mf/use-callback (mf/use-fn
(fn [changes] (fn [changes]
(swap! current-change merge changes) (st/emit! (dc/update-colorpicker-gradient changes))))
(st/emit! (dc/update-gradient changes))))
on-change-start on-change-start
(mf/use-callback (mf/use-fn
(mf/deps transform-inverse width height) (mf/deps transform-inverse width height)
(fn [point] (fn [point]
(let [point (gpt/transform point transform-inverse) (let [point (gpt/transform point transform-inverse)
@ -286,7 +270,7 @@
(change! {:start-x start-x :start-y start-y})))) (change! {:start-x start-x :start-y start-y}))))
on-change-finish on-change-finish
(mf/use-callback (mf/use-fn
(mf/deps transform-inverse width height) (mf/deps transform-inverse width height)
(fn [point] (fn [point]
(let [point (gpt/transform point transform-inverse) (let [point (gpt/transform point transform-inverse)
@ -295,7 +279,7 @@
(change! {:end-x end-x :end-y end-y})))) (change! {:end-x end-x :end-y end-y}))))
on-change-width on-change-width
(mf/use-callback (mf/use-fn
(mf/deps gradient-length width height) (mf/deps gradient-length width height)
(fn [point] (fn [point]
(let [scale-factor-y (/ gradient-length (/ height 2)) (let [scale-factor-y (/ gradient-length (/ height 2))
@ -304,17 +288,35 @@
(when (and norm-dist (d/num? norm-dist)) (when (and norm-dist (d/num? norm-dist))
(change! {:width 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)) (= id (:shape-id gradient))
(not= (:type shape) :text)) (not= (:type shape) :text))
[:& gradient-handler-transformed [:& gradient-handlers*
{:editing editing-spot {:zoom zoom
:from-p from-p :gradient gradient
:to-p to-p :stops stops
:width-p (when (= :radial (:type gradient)) width-p) :editing-stop editing-stop
:from-color {:value start-color :opacity start-opacity} :shape shape}])))
: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}])))

View file

@ -121,7 +121,7 @@
(defn get-current-target (defn get-current-target
"Extract the current target from event instance (different from 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] [^js event]
(when (some? event) (when (some? event)
(.-currentTarget event))) (.-currentTarget event)))

View file

@ -72,10 +72,10 @@
(defn get-editor-current-inline-styles (defn get-editor-current-inline-styles
[state] [state]
(if (impl/isCurrentEmpty state) (if (impl/isCurrentEmpty state)
(let [block (impl/getCurrentBlock state)] (get-editor-current-block-data state)
(get-editor-block-data block))
(-> (.getCurrentInlineStyle ^js state) (-> (.getCurrentInlineStyle ^js state)
(txt/styles-to-attrs)))) (txt/styles-to-attrs)
(dissoc :text-align :text-direction))))
(defn update-editor-current-block-data (defn update-editor-current-block-data
[state attrs] [state attrs]
@ -89,7 +89,8 @@
(impl/updateBlockData state block-key (clj->js attrs)) (impl/updateBlockData state block-key (clj->js attrs))
(let [attrs (-> (impl/getInlineStyle state block-key 0) (let [attrs (-> (impl/getInlineStyle state block-key 0)
(txt/styles-to-attrs))] (txt/styles-to-attrs)
(dissoc :text-align :text-direction))]
(impl/updateBlockData state block-key (clj->js attrs))))) (impl/updateBlockData state block-key (clj->js attrs)))))
state (impl/applyInlineStyle state (txt/attrs-to-styles attrs)) state (impl/applyInlineStyle state (txt/attrs-to-styles attrs))

View file

@ -62,23 +62,23 @@ msgstr "سعيد برؤيتك مجددا!"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "تسجيل الدخول عبر Github" msgstr "Github"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "تسجيل الدخول عبر Gitlab" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "تسجيل الدخول عبر جوجل" msgstr "جوجل"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "تسجيل الدخول باستخدام LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "تسجيل الدخول باستخدام OpenID (SSO)" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -2055,4 +2055,4 @@ msgid "workspace.options.layer-options.blend-mode.difference"
msgstr "اختلاف" msgstr "اختلاف"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "انقر لإغلاق المسار" msgstr "انقر لإغلاق المسار"

View file

@ -65,23 +65,23 @@ msgstr "Ens agrada tornar a veure-vos!"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "Entra amb GitHub" msgstr "GitHub"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "Entra amb Gitlab" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "Entra amb Google" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Entra amb LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "Entra amb OpenID (SSO)" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -4162,4 +4162,4 @@ msgid "workspace.updates.update"
msgstr "Actualitza" msgstr "Actualitza"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Feu clic per a tancar el camí" msgstr "Feu clic per a tancar el camí"

View file

@ -65,23 +65,23 @@ msgstr "Fedt at se dig igen!"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "Log på med Github" msgstr "Github"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "Log på med Gitlab" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "Log på med Google" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Log på med LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "Log på med OpenID (SSO)" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -476,4 +476,4 @@ msgstr "Skrifttype Udbydere - %s - Penpot"
#: src/app/main/ui/dashboard/fonts.cljs #: src/app/main/ui/dashboard/fonts.cljs
msgid "title.dashboard.fonts" msgid "title.dashboard.fonts"
msgstr "Skrifttyper - %s - Penpot" msgstr "Skrifttyper - %s - Penpot"

View file

@ -66,23 +66,23 @@ msgstr "Schön, Sie wiederzusehen!"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "Anmelden mit GitHub" msgstr "GitHub"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "Einloggen mit Gitlab" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "Anmelden mit Google" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Anmelden mit LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "Anmelden mit OpenID (SSO)" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -3764,4 +3764,4 @@ msgid "workspace.updates.update"
msgstr "Aktualisieren" msgstr "Aktualisieren"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Klicken Sie, um den Pfad zu schließen" msgstr "Klicken Sie, um den Pfad zu schließen"

View file

@ -60,15 +60,15 @@ msgstr "Χαίρομαι που σας ξαναδώ"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "Συνδεθείτε με το Github" msgstr "Github"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "Συνδεθείτε με το Gitlab" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Συνδεθείτε με το LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -2303,4 +2303,4 @@ msgid "workspace.updates.update"
msgstr "Ενημέρωση" msgstr "Ενημέρωση"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Κάντε κλικ για να κλείσετε τη διαδρομή" msgstr "Κάντε κλικ για να κλείσετε τη διαδρομή"

View file

@ -75,11 +75,11 @@ msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Login with LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "OpenID Connect" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"

View file

@ -78,11 +78,11 @@ msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Entrar con LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "OpenID Connect" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"

View file

@ -69,23 +69,23 @@ msgstr "خوشحالم که دوباره شما را می‌بینم!"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "ورود با گیتهاب" msgstr "گیتهاب"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "ورود با گیتلب" msgstr "گیتلب"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "ورود با گوگل" msgstr "گوگل"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "ورود با LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "ورود با OpenID" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -2413,4 +2413,4 @@ msgid "workspace.updates.update"
msgstr "به‌روزرسانی" msgstr "به‌روزرسانی"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "برای بستن مسیر کلیک کنید" msgstr "برای بستن مسیر کلیک کنید"

View file

@ -66,23 +66,23 @@ msgstr "Ravi de vous revoir!"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "Se connecter via GitHub" msgstr "GitHub"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "Se connecter via GitLab" msgstr "GitLab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "Se connecter via Google" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Se connecter via LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "Se connecter via OpenID (SSO)" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -3207,4 +3207,4 @@ msgid "workspace.updates.update"
msgstr "Actualiser" msgstr "Actualiser"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Cliquez pour fermer le chemin" msgstr "Cliquez pour fermer le chemin"

View file

@ -73,11 +73,11 @@ msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Entrar con LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "OpenID Connect" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -565,4 +565,4 @@ msgstr "S"
#, permanent #, permanent
msgid "handoff.attributes.stroke.alignment.center" msgid "handoff.attributes.stroke.alignment.center"
msgstr "Centro" msgstr "Centro"

View file

@ -75,11 +75,11 @@ msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "כניסה עם LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "OpenID Connect" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -4213,4 +4213,4 @@ msgid "workspace.updates.update"
msgstr "עדכון" msgstr "עדכון"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "לחיצה תסגור את הנתיב" msgstr "לחיצה תסגור את הנתיב"

View file

@ -69,23 +69,23 @@ msgstr "Senang bertemu denganmu lagi!"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "Masuk dengan Github" msgstr "Github"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "Masuk dengan Gitlab" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "Masuk dengan Google" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Masuk dengan LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "Masuk dengan OpenID (SSO)" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -254,4 +254,4 @@ msgstr "* Dapat mencakup komponen, grafik, warna dan/atau tipografi."
msgid "dashboard.export.explain" msgid "dashboard.export.explain"
msgstr "" msgstr ""
"Satu atau lebih file yang ingin anda ekspor, menggunakan pustaka bersama. " "Satu atau lebih file yang ingin anda ekspor, menggunakan pustaka bersama. "
"Apa yang akan anda lakukan dengan asetnya*?" "Apa yang akan anda lakukan dengan asetnya*?"

View file

@ -79,11 +79,11 @@ msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Prisijungti su LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "OpenID prisijungimas" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -392,4 +392,4 @@ msgid "dashboard.import.progress.upload-data"
msgstr "Įkeliami duomenys į serverį (%s/%s)" msgstr "Įkeliami duomenys į serverį (%s/%s)"
msgid "dashboard.import.progress.upload-media" msgid "dashboard.import.progress.upload-media"
msgstr "Įkeliamas failas: %s" msgstr "Įkeliamas failas: %s"

View file

@ -69,23 +69,23 @@ msgstr "നിങ്ങളെ വീണ്ടും കാണാൻ കഴിഞ
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "ഗിറ്റ്ഹബ്ബ് ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" msgstr "ഗിറ്റ്ഹബ്ബ്"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "ഗിറ്റ്ലാബ് ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" msgstr "ഗിറ്റ്ലാബ്"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "ഗൂഗിൾ ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" msgstr "ഗൂഗിൾ"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "LDAP ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "ഓപ്പൺഐഡി (SSO) ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" msgstr "ഓപ്പൺഐഡി"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -263,4 +263,4 @@ msgid "dashboard.export-single"
msgstr "പെൻപോട്ട് ഫയൽ എക്സ്പോർട്ട് ചെയ്യുക" msgstr "പെൻപോട്ട് ഫയൽ എക്സ്പോർട്ട് ചെയ്യുക"
msgid "dashboard.export.detail" msgid "dashboard.export.detail"
msgstr "* ഘടകങ്ങൾ, ഗ്രാഫിക്സ്, നിറങ്ങൾ അല്ലെങ്കിൽ മുദ്രണകലകൾ എന്നിവ ഉൾപ്പെടാം." msgstr "* ഘടകങ്ങൾ, ഗ്രാഫിക്സ്, നിറങ്ങൾ അല്ലെങ്കിൽ മുദ്രണകലകൾ എന്നിവ ഉൾപ്പെടാം."

View file

@ -78,11 +78,11 @@ msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Zaloguj się przez LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "OpenID Connect" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -4175,4 +4175,4 @@ msgid "workspace.updates.update"
msgstr "Aktualizuj" msgstr "Aktualizuj"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Kliknij, aby zamknąć ścieżkę" msgstr "Kliknij, aby zamknąć ścieżkę"

View file

@ -65,23 +65,23 @@ msgstr "Bom te ver de novo!"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "Entrar com o Github" msgstr "Github"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "Entrar com o Gitlab" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "Entrar com o Google" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Entrar com LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "Entrar com OpenID (SSO)" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -2089,4 +2089,4 @@ msgid "workspace.updates.update"
msgstr "Atualizar" msgstr "Atualizar"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Clique para fechar o caminho" msgstr "Clique para fechar o caminho"

View file

@ -66,23 +66,23 @@ msgstr "Mă bucur să te văd din nou!"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "Conectează-te cu Github" msgstr "Github"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "Conectează-te cu Gitlab" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "Conectează-te cu Google" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Conectează-te cu LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "Conectează-te cu OpenID (SSO)" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -2553,4 +2553,4 @@ msgid "workspace.updates.update"
msgstr "Actualizează" msgstr "Actualizează"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Click pentru a închide calea" msgstr "Click pentru a închide calea"

View file

@ -63,23 +63,23 @@ msgstr "Рады видеть Вас снова!"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "Вход через Gitnub" msgstr "Gitnub"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "Вход через Gitlab" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "Войти с Google" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "Вход через LDAP" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "Войти с OpenID (SSO)" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -2329,4 +2329,4 @@ msgid "workspace.updates.update"
msgstr "" msgstr ""
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Нажмите для замыкания контура" msgstr "Нажмите для замыкания контура"

View file

@ -78,11 +78,11 @@ msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "LDAP ile oturum aç" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "OpenID ile Bağlan" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -4264,4 +4264,4 @@ msgid "workspace.updates.update"
msgstr "Güncelle" msgstr "Güncelle"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Yolu kapatmak için tıklayın" msgstr "Yolu kapatmak için tıklayın"

View file

@ -61,23 +61,23 @@ msgstr "很高兴又见到你!"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "使用GitHub登录" msgstr "GitHub"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "使用Gitlab登录" msgstr "Gitlab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "使用Google登录" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "使用LDAP登录" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "使用OpenID登录" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -3527,4 +3527,4 @@ msgid "workspace.updates.update"
msgstr "更新" msgstr "更新"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "单击以闭合路径" msgstr "单击以闭合路径"

View file

@ -61,23 +61,23 @@ msgstr "很高興再次見到你!"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit" msgid "auth.login-with-github-submit"
msgstr "透過 GitHub 登入" msgstr "GitHub"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-gitlab-submit" msgid "auth.login-with-gitlab-submit"
msgstr "透過 GitLab 登入" msgstr "GitLab"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-google-submit" msgid "auth.login-with-google-submit"
msgstr "透過 Google 登入" msgstr "Google"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-ldap-submit" msgid "auth.login-with-ldap-submit"
msgstr "透過 LDAP 登入" msgstr "LDAP"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-oidc-submit" msgid "auth.login-with-oidc-submit"
msgstr "使用 OpenID (SSO) 登入" msgstr "OpenID"
#: src/app/main/ui/auth/recovery.cljs #: src/app/main/ui/auth/recovery.cljs
msgid "auth.new-password" msgid "auth.new-password"
@ -1087,4 +1087,4 @@ msgstr "歷史"
#: src/app/main/data/workspace/libraries.cljs #: src/app/main/data/workspace/libraries.cljs
msgid "workspace.updates.update" msgid "workspace.updates.update"
msgstr "更新" msgstr "更新"