♻️ Refactor texts.

This commit is contained in:
Andrey Antukh 2020-04-30 08:17:26 +02:00 committed by Alonso Torres
parent 07981487bf
commit 51c39d169f
4 changed files with 401 additions and 440 deletions

View file

@ -12,8 +12,6 @@
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[clojure.set :as set]
[goog.events :as events]
[goog.object :as gobj]
[potok.core :as ptk]
[uxbox.common.data :as d]
[uxbox.common.exceptions :as ex]

View file

@ -9,23 +9,27 @@
(ns uxbox.main.data.workspace.texts
(:require
[beicon.core :as rx]
[clojure.walk :as walk]
[cljs.spec.alpha :as s]
[clojure.set :as set]
[goog.events :as events]
[goog.object :as gobj]
[potok.core :as ptk]
[uxbox.util.object :as obj]
[uxbox.main.fonts :as fonts]
[uxbox.main.data.workspace.common :as dwc]
["slate-react" :as rslate]
["slate" :as slate :refer [Editor Transforms Text]]))
(defn create-editor
[]
(rslate/withReact (slate/createEditor)))
(defn assign-editor
[editor]
[id editor]
(ptk/reify ::assign-editor
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :editor] editor)
(assoc-in [:workspace-local :editors id] editor)
(update-in [:workspace-local :editor-n] (fnil inc 0))))))
;; --- Helpers
@ -39,222 +43,128 @@
:focus #js {:path #js [0 0 (dec (alength paragraphs))]
:offset 1}}))
(defn set-nodes!
(defn- editor-set!
([editor props]
(set-nodes! editor props #js {}))
(editor-set! editor props #js {}))
([editor props options]
(when (and (nil? (obj/get editor "selection"))
(nil? (obj/get options "at")))
(obj/set! options "at" (calculate-full-selection editor)))
(.setNodes Transforms editor props options)
editor))
(defn is-text?
[v]
(.isText Text v))
(defn- transform-nodes
[pred transform data]
(walk/postwalk
(fn [item]
(if (and (map? item) (pred item))
(transform item)
item))
data))
(defn is-paragraph?
[v]
(= (.-type v) "paragraph"))
;; --- Editor Related Helpers
;; --- Predicates
(defn- ^boolean is-text-node?
[node]
(cond
(object? node) (.isText Text node)
(map? node) (string? (:text node))
:else (throw (ex-info "unexpected type" {:node node}))))
(defn enabled?
[editor universal? pred]
(when editor
(let [result (.nodes Editor editor #js {:match pred :universal universal?})
match (first (es6-iterator-seq result))]
(array? match))))
(defn- ^boolean is-paragraph-node?
[node]
(cond
(object? node) (= (.-type node) "paragraph")
(map? node) (= "paragraph" (:type node))
:else (throw (ex-info "unexpected type" {:node node}))))
(defn text-decoration-enabled?
[editor type]
(enabled? editor true
(fn [v]
(let [val (obj/get v "textDecoration")]
(identical? type val)))))
(defn- ^boolean is-root-node?
[node]
(cond
(object? node) (= (.-type node) "root")
(map? node) (= "root" (:type node))
:else (throw (ex-info "unexpected type" {:node node}))))
(defn text-transform-enabled?
[editor type]
(enabled? editor true
(fn [v]
(let [val (obj/get v "textTransform")]
(identical? type val)))))
(defn- editor-current-values
[editor pred attrs universal?]
(let [options #js {:match pred :universal universal?}
_ (when (nil? (obj/get editor "selection"))
(obj/set! options "at" (calculate-full-selection editor)))
result (.nodes Editor editor options)
match (ffirst (es6-iterator-seq result))]
(when (object? match)
(let [attrs (clj->js attrs)
result (areduce attrs i ret #js {}
(let [val (obj/get match (aget attrs i))]
(if val
(obj/set! ret (aget attrs i) val)
ret)))]
(js->clj result :keywordize-keys true)))))
(defn text-align-enabled?
[editor type]
(enabled? editor false
(fn [v]
(let [val (obj/get v "textAlign")]
(identical? type val)))))
(defn- nodes-seq
[match? node]
(->> (tree-seq map? :children node)
(filter match?)))
(defn vertical-align-enabled?
[editor type]
(enabled? editor false
(fn [v]
(let [val (obj/get v "verticalAlign")]
(identical? type val)))))
(defn- shape-current-values
[shape pred attrs]
(let [root (:content shape)
nodes (nodes-seq pred root)
match (first nodes)]
(when match
(select-keys match attrs))))
;; --- Getters
(defn current-text-values
[{:keys [editor default attrs shape]}]
(if editor
(editor-current-values editor is-text-node? attrs true)
(shape-current-values shape is-text-node? attrs)))
(defn current-value
[editor {:keys [universal?
attr
pred
at]
:as opts}]
(when editor
(let [options #js {:match pred :universal universal?}]
(cond
(object? at)
(obj/set! options "at" at)
(defn current-paragraph-values
[{:keys [editor attrs shape]}]
(if editor
(editor-current-values editor is-paragraph-node? attrs false)
(shape-current-values shape is-paragraph-node? attrs)))
(nil? (obj/get editor "selection"))
(obj/set! options "at" (calculate-full-selection editor)))
(defn current-root-values
[{:keys [editor attrs shape]}]
(if editor
(editor-current-values editor is-root-node? attrs false)
(shape-current-values shape is-root-node? attrs)))
(let [result (.nodes Editor editor options)
match (ffirst (es6-iterator-seq result))]
(when (object? match)
(obj/get match attr))))))
(defn- merge-attrs
[node attrs]
(reduce-kv (fn [node k v]
(if (nil? v)
(dissoc node k)
(assoc node k v)))
node
attrs))
(defn current-line-height
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-paragraph?
:attr "lineHeight"
:universal? false})
default))
(defn- update-attrs
[{:keys [id editor attrs pred split]}]
(if editor
(ptk/reify ::update-attrs
ptk/EffectEvent
(effect [_ state stream]
(editor-set! editor (clj->js attrs) #js {:match pred :split split})))
(defn current-letter-spacing
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-text?
:attr "letterSpacing"
:universal? true})
default))
(ptk/reify ::update-attrs
dwc/IBatchedChange
ptk/UpdateEvent
(update [_ state]
(let [page-id (get-in state [:workspace-page :id])
merge-attrs #(merge-attrs % attrs)]
(update-in state [:workspace-data page-id :objects id]
(fn [{:keys [type content] :as shape}]
(assert (= :text type) "should be shape type")
(update shape :content #(transform-nodes pred merge-attrs %)))))))))
(defn update-text-attrs
[options]
(update-attrs (assoc options :pred is-text-node? :split true)))
(defn current-font-family
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-text?
:attr "fontId"
:universal? true})
default))
(defn update-paragraph-attrs
[options]
(update-attrs (assoc options :pred is-paragraph-node? :split false)))
(defn current-font-size
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-text?
:attr "fontSize"
:universal? true})
default))
(defn current-font-variant
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-text?
:attr "fontVariantId"
:universal? true})
default))
(defn current-fill
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-text?
:attr "fill"
:universal? true})
default))
(defn current-opacity
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-text?
:attr "opacity"
:universal? true})
default))
;; --- Setters
(defn set-text-decoration!
[editor type]
(set-nodes! editor
#js {:textDecoration type}
#js {:match is-text?
:split true}))
(defn set-text-align!
[editor type]
(set-nodes! editor
#js {:textAlign type}
#js {:match is-paragraph?}))
(defn set-text-transform!
[editor type]
(set-nodes! editor
#js {:textTransform type}
#js {:match is-text?
:split true}))
(defn set-vertical-align!
[editor type]
(set-nodes! editor
#js {:verticalAlign type}
#js {:match (fn [item]
(= "text-box" (obj/get item "type")))}))
(defn set-line-height!
[editor val at]
(set-nodes! editor
#js {:lineHeight val}
#js {:at at
:match is-paragraph?}))
(defn set-letter-spacing!
[editor val at]
(set-nodes! editor
#js {:letterSpacing val}
#js {:at at
:match is-text?
:split true}))
(defn set-font!
[editor id family]
(set-nodes! editor
#js {:fontId id
:fontFamily family}
#js {:match is-text?
:split true}))
(defn set-font-size!
[editor val]
(set-nodes! editor
#js {:fontSize val}
#js {:match is-text?
:split true}))
(defn set-font-variant!
[editor id weight style]
(set-nodes! editor
#js {:fontVariantId id
:fontWeight weight
:fontStyle style}
#js {:match is-text?
:split true}))
(defn set-fill!
[editor val]
(set-nodes! editor
#js {:fill val}
#js {:match is-text?
:split true}))
(defn set-opacity!
[editor val]
(set-nodes! editor
#js {:opacity val}
#js {:match is-text?
:split true}))
(defn update-root-attrs
[options]
(update-attrs (assoc options :pred is-root-node? :split false)))

View file

@ -63,6 +63,7 @@
(dom/prevent-default event)
(when selected?
(st/emit! (dw/start-edition-mode (:id shape)))))]
[:g.shape {:on-double-click on-double-click
:on-mouse-down on-mouse-down
:on-context-menu on-context-menu}
@ -71,11 +72,11 @@
[:& text-shape {:shape shape
:selected? selected?}])]))
;; --- Text Rendering
;; --- Text Editor Rendering
(defn- generate-text-box-styles
(defn- generate-root-styles
[data]
(let [valign (obj/get data "verticalAlign")
(let [valign (obj/get data "vertical-align")
base #js {:height "100%"
:width "100%"
:display "flex"}]
@ -84,75 +85,31 @@
(= valign "center") (obj/set! "alignItems" "center")
(= valign "bottom") (obj/set! "alignItems" "flex-end"))))
(mf/defc rt-text-box
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
type (obj/get data "type")
style (generate-text-box-styles data)
attrs (obj/set! attrs "style" style)
attrs (obj/set! attrs "className" type)]
[:> :div attrs childs]))
(defn- generate-text-styles
[data]
(let [valign (obj/get data "verticalAlign")
base #js {:display "inline-block"
:width "100%"}]
base))
(mf/defc rt-text
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
type (obj/get data "type")
style (generate-text-styles data)
attrs (obj/set! attrs "style" style)
attrs (obj/set! attrs "className" type)]
[:> :div attrs childs]))
(defn- generate-paragraph-styles
[data]
(let [base #js {:fontSize "14px"
:margin "inherit"
:lineHeight "1.2"}
lh (obj/get data "lineHeight")
ta (obj/get data "textAlign")]
lh (obj/get data "line-height")
ta (obj/get data "text-align")]
(cond-> base
ta (obj/set! "textAlign" ta)
lh (obj/set! "lineHeight" lh))))
(mf/defc rt-pharagraph
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
style (generate-paragraph-styles data)
attrs (obj/set! attrs "style" style)]
[:> :p attrs childs]))
(defn- generate-leaf-styles
(defn- generate-text-styles
[data]
(let [letter-spacing (obj/get data "letterSpacing")
text-decoration (obj/get data "textDecoration")
text-transform (obj/get data "textTransform")
(let [letter-spacing (obj/get data "letter-spacing")
text-decoration (obj/get data "text-decoration")
text-transform (obj/get data "text-transform")
font-id (obj/get data "fontId")
font-variant-id (obj/get data "fontVariantId")
font-id (obj/get data "font-id")
font-variant-id (obj/get data "font-variant-id")
font-family (obj/get data "fontFamily")
font-size (obj/get data "fontSize")
font-family (obj/get data "font-family")
font-size (obj/get data "font-size")
fill (obj/get data "fill")
opacity (obj/get data "opacity")
fontsdb (mf/deref fonts/fontsdb)
fontsdb (deref fonts/fontsdb)
base #js {:textDecoration text-decoration
:color fill
@ -185,14 +142,50 @@
base))
(mf/defc rt-leaf
(mf/defc editor-root-node
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
type (obj/get data "type")
style (generate-root-styles data)
attrs (obj/set! attrs "style" style)
attrs (obj/set! attrs "className" type)]
[:> :div attrs childs]))
(mf/defc editor-paragraph-set-node
{::mf/wrap-props false}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
type (obj/get data "type")
style #js {:display "inline-block"
:width "100%"}
attrs (obj/set! attrs "style" style)
attrs (obj/set! attrs "className" type)]
[:> :div attrs childs]))
(mf/defc editor-paragraph-node
{::mf/wrap-props false}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
style (generate-paragraph-styles data)
attrs (obj/set! attrs "style" style)]
[:> :p attrs childs]))
(mf/defc editor-text-node
{::mf/wrap-props false}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "leaf")
style (generate-leaf-styles data)
style (generate-text-styles data)
attrs (obj/set! attrs "style" style)]
[:> :span attrs childs]))
@ -201,48 +194,44 @@
(mf/html
(let [element (obj/get props "element")]
(case (obj/get element "type")
"text-box" [:> rt-text-box props]
"text" [:> rt-text props]
"paragraph" [:> rt-pharagraph props]
"root" [:> editor-root-node props]
"paragraph-set" [:> editor-paragraph-set-node props]
"paragraph" [:> editor-paragraph-node props]
nil))))
(defn- render-leaf
(defn- render-text
[props]
(mf/html
[:> rt-leaf props]))
[:> editor-text-node props]))
;; --- Text Shape Edit
(defn- initial-text
([] (initial-text ""))
([text]
#js [#js {:type "text-box"
:children #js [#js {:type "text"
:children #js [#js {:type "paragraph"
:children #js [#js {:text text}]}]}]}]))
[text]
(clj->js
[{:type "root"
:children [{:type "paragraph-set"
:children [{:type "paragraph"
:children [{:text (or text "")}]}]}]}]))
(defn- parse-content
[content]
(cond
(string? content) (initial-text content)
(vector? content) (clj->js content)
(object? content) content
:else (initial-text)))
(map? content) (clj->js [content])
:else (initial-text "")))
(mf/defc text-shape-edit
{::mf/wrap [mf/memo]}
[{:keys [shape] :as props}]
(let [{:keys [id x y width height content]} shape
state (mf/use-state #(parse-content content))
value (mf/use-var @state)
editor (mf/use-memo #(rslate/withReact (slate/createEditor)))
state (mf/use-state #(parse-content content))
editor (mf/use-memo #(dwt/create-editor))
self-ref (mf/use-ref)
on-close
(fn []
(st/emit! dw/clear-edition-mode
dw/deselect-all))
(st/emit! dw/clear-edition-mode))
on-click
(fn [event]
@ -264,22 +253,21 @@
on-mount
(fn []
(let [
lkey1 (events/listen js/document EventType.CLICK on-click)
(let [lkey1 (events/listen js/document EventType.CLICK on-click)
lkey2 (events/listen js/document EventType.KEYUP on-keyup)]
(st/emit! (dwt/assign-editor editor))
#(let [content (js->clj @value)]
(st/emit! (dwt/assign-editor nil)
(dw/update-shape id {:content content}))
(st/emit! (dwt/assign-editor id editor))
#(do
(st/emit! (dwt/assign-editor id nil))
(events/unlistenByKey lkey1)
(events/unlistenByKey lkey2))))
on-change
(mf/use-callback
(fn [val]
(st/emit! (dwt/assign-editor editor))
(reset! state val)
(reset! value val)))]
(let [content (js->clj val :keywordize-keys true)
content (first content)]
(st/emit! (dw/update-shape id {:content content}))
(reset! state val))))]
(mf/use-effect on-mount)
@ -293,7 +281,7 @@
:spell-check "false"
:class "rich-text"
:render-element render-element
:render-leaf render-leaf
:render-leaf render-text
:on-blur (fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
@ -303,43 +291,45 @@
;; --- Text Shape Wrapper
(defn- render-text-node
([node] (render-text-node 0 node))
([index {:keys [type text children] :as node}]
(mf/html
(if (string? text)
(let [style (generate-text-styles (clj->js node))]
[:span {:style style :key index} text])
(let [children (map-indexed render-text-node children)]
(case type
"root"
(let [style (generate-root-styles (clj->js node))]
[:div.root.rich-text {:key index :style style} children])
"paragraph-set"
(let [style #js {:display "inline-block"
:width "100%"}]
[:div.paragraphs {:key index :style style} children])
"paragraph"
(let [style (generate-paragraph-styles (clj->js node))]
[:p {:key index :style style} children])
nil))))))
(mf/defc text-content
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [root (obj/get props "content")]
(render-text-node root)))
(mf/defc text-shape
[{:keys [shape selected?] :as props}]
(let [{:keys [id x y width height content]} shape
content (parse-content content)
editor (mf/use-memo #(rslate/withReact (slate/createEditor)))
on-mount
(fn []
(when selected?
(st/emit! (dwt/assign-editor editor))
#(st/emit! (dwt/assign-editor nil))))
on-change
(mf/use-callback
(fn [val]
(let [content (js->clj val)]
(st/emit! (dw/update-shape id {:content content})))))
render-element (mf/use-callback render-element)
render-leaf (mf/use-callback render-leaf)]
(mf/use-effect (mf/deps id selected?) on-mount)
(let [{:keys [id x y width height rotation content]} shape]
[:foreignObject {:x x
:y y
:transform (geom/transform-matrix shape)
:id (str id)
:width width
:height height}
[:& text-content {:content (:content shape)}]]))
[:> rslate/Slate {:editor editor
:value content
:on-change on-change}
[:> rslate/Editable {:auto-focus "false"
:read-only "true"
:class "rich-text"
:render-element render-element
:render-leaf render-leaf
:placeholder "Type some text here..."}]]]))

View file

@ -48,37 +48,64 @@
(mf/defc font-options
[{:keys [editor shape] :as props}]
(let [selection (mf/use-ref)
font-id (dwt/current-font-family editor {:default "sourcesanspro"})
font-size (dwt/current-font-size editor {:default "14"})
font-var (dwt/current-font-variant editor {:default "regular"})
{:keys [font-id
font-size
font-variant-id]
:or {font-id "sourcesanspro"
font-size "14"
font-variant-id "regular"}}
(dwt/current-text-values
{:editor editor
:shape shape
:attrs [:font-id
:font-size
:font-variant-id]})
fonts (mf/deref fonts/fontsdb)
font (get fonts font-id)
change-font
(fn [id]
(st/emit! (dwt/update-text-attrs
{:id (:id shape)
:editor editor
:attrs {:font-id id
:font-family (:family (get fonts id))
:font-variant-id nil
:font-weight nil
:font-style nil}})))
on-font-family-change
(fn [event]
(let [id (-> (dom/get-target event)
(dom/get-value))
font (get fonts id)]
(fonts/ensure-loaded! id
#(do
(dwt/set-font! editor id (:family font))
(when (not= id font-id)
(dwt/set-font-variant! editor nil nil nil))))))
(fonts/ensure-loaded! id (partial change-font id))))
on-font-size-change
(fn [event]
(let [val (-> (dom/get-target event)
(dom/get-value))]
(dwt/set-font-size! editor val)))
(st/emit! (dwt/update-text-attrs
{:id (:id shape)
:editor editor
:attrs {:font-size val}}))))
on-font-variant-change
(fn [event]
(let [id (-> (dom/get-target event)
(dom/get-value))
variant (d/seek #(= id (:id %)) (:variants font))]
(dwt/set-font! editor (:id font) (:family font))
(dwt/set-font-variant! editor id (:weight variant) (:style variant))))
(st/emit! (dwt/update-text-attrs
{:id (:id shape)
:editor editor
:attrs {:font-id (:id font)
:font-family (:family font)
:font-variant-id id
:font-weight (:weight variant)
:font-style (:style variant)}}))))
]
[:*
@ -109,7 +136,7 @@
:on-change on-font-size-change
}]]
[:select.input-select {:value font-var
[:select.input-select {:value font-variant-id
:on-change on-font-variant-change}
(for [variant (:variants font)]
[:option {:value (:id variant)
@ -118,48 +145,63 @@
(mf/defc text-align-options
[{:keys [editor locale] :as props}]
(let [on-text-align-change
(fn [event type]
(dwt/set-text-align! editor type))]
;; --- Align
[{:keys [editor shape locale] :as props}]
(let [{:keys [text-align]
:or {text-align "left"}}
(dwt/current-paragraph-values
{:editor editor
:shape shape
:attrs [:text-align]})
on-change
(fn [event type]
(st/emit! (dwt/update-paragraph-attrs
{:id (:id shape)
:editor editor
:attrs {:text-align type}})))]
;; --- Align
[:div.row-flex.align-icons
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.align-left")
:class (dom/classnames
:current (dwt/text-align-enabled? editor "left"))
:on-click #(on-text-align-change % "left")}
:class (dom/classnames :current (= "left" text-align))
:on-click #(on-change % "left")}
i/text-align-left]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.align-center")
:class (dom/classnames
:current (dwt/text-align-enabled? editor "center"))
:on-click #(on-text-align-change % "center")}
:class (dom/classnames :current (= "center" text-align))
:on-click #(on-change % "center")}
i/text-align-center]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.align-right")
:class (dom/classnames
:current (dwt/text-align-enabled? editor "right"))
:on-click #(on-text-align-change % "right")}
:class (dom/classnames :current (= "right" text-align))
:on-click #(on-change % "right")}
i/text-align-right]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.align-justify")
:class (dom/classnames
:current (dwt/text-align-enabled? editor "justify"))
:on-click #(on-text-align-change % "justify")}
:class (dom/classnames :current (= "justify" text-align))
:on-click #(on-change % "justify")}
i/text-align-justify]]))
(mf/defc text-fill-options
[{:keys [editor] :as props}]
(let [color (dwt/current-fill editor {:default "#000000"})
opacity (dwt/current-opacity editor {:default 1})
[{:keys [editor shape] :as props}]
(let [{:keys [fill opacity]
:or {fill "#000000"
opacity 1}}
(dwt/current-text-values
{:editor editor
:shape shape
:attrs [:fill :opacity]})
opacity (math/round (* opacity 100))
on-color-change
(fn [color]
(dwt/set-fill! editor color))
(st/emit! (dwt/update-text-attrs
{:id (:id shape)
:editor editor
:attrs {:fill color}})))
on-color-input-change
(fn [event]
@ -175,7 +217,10 @@
(when (str/numeric? value)
(let [value (-> (d/parse-integer value 1)
(/ 100))]
(dwt/set-opacity! editor value)))))
(st/emit! (dwt/update-text-attrs
{:id (:id shape)
:editor editor
:attrs {:opacity value}}))))))
show-color-picker
(fn [event]
@ -184,22 +229,22 @@
props {:x x :y y
:on-change on-color-change
:default "#ffffff"
:value color
:value fill
:transparent? true}]
(modal/show! colorpicker-modal props)))]
[:div.row-flex.color-data
[:span.color-th
{:style {:background-color color}
{:style {:background-color fill}
:on-click show-color-picker
}]
[:div.color-info
[:input {:default-value color
[:input {:default-value fill
:pattern "^#(?:[0-9a-fA-F]{3}){1,2}$"
:ref (fn [el]
(when el
(set! (.-value el) color)))
(set! (.-value el) fill)))
:on-change on-color-input-change
}]]
@ -222,12 +267,25 @@
}]]))
(mf/defc spacing-options
[{:keys [editor locale] :as props}]
(let [selection (mf/use-ref)
lh (dwt/current-line-height editor {:default "1.2"
:at (mf/ref-val selection)})
ls (dwt/current-letter-spacing editor {:default "0"
:at (mf/ref-val selection)})]
[{:keys [editor shape locale] :as props}]
(let [{:keys [letter-spacing
line-height]
:or {line-height "1.2"
letter-spacing "0"}}
(dwt/current-text-values
{:editor editor
:shape shape
:attrs [:line-height
:letter-spacing]})
on-change
(fn [event attr]
(let [val (-> (dom/get-target event)
(dom/get-value))]
(st/emit! (dwt/update-text-attrs
{:id (:id shape)
:editor editor
:attrs {attr val}}))))]
[:div.row-flex
[:div.input-icon
[:span.icon-before.tooltip.tooltip-bottom
@ -238,12 +296,9 @@
:step "0.1"
:min "0"
:max "200"
:value lh
:on-change (fn [event]
(let [val (-> (dom/get-target event)
(dom/get-value))
sel (mf/ref-val selection)]
(dwt/set-line-height! editor val sel)))}]]
:value line-height
:on-change #(on-change % :line-height)}]]
[:div.input-icon
[:span.icon-before.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.letter-spacing")}
@ -253,12 +308,8 @@
:step "0.1"
:min "0"
:max "200"
:value ls
:on-change (fn [event]
(let [val (-> (dom/get-target event)
(dom/get-value))
sel (mf/ref-val selection)]
(dwt/set-letter-spacing! editor val sel)))}]]]))
:value letter-spacing
:on-change #(on-change % :letter-spacing)}]]]))
;; (mf/defc box-sizing-options
;; [{:keys [editor] :as props}]
@ -274,126 +325,138 @@
;; i/auto-fix]])
(mf/defc vertical-align-options
[{:keys [editor locale] :as props}]
(let [on-vertical-align-change
[{:keys [editor locale shape] :as props}]
(let [{:keys [vertical-align]
:or {vertical-align "top"}}
(dwt/current-root-values
{:editor editor
:shape shape
:attrs [:vertical-align]})
on-change
(fn [event type]
(dwt/set-vertical-align! editor type))]
(st/emit! (dwt/update-root-attrs
{:id (:id shape)
:editor editor
:attrs {:vertical-align type}})))]
[:div.row-flex
[:span.element-set-subtitle (t locale "workspace.options.font-options.vertical-align")]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.align-top")
:class (dom/classnames
:current (dwt/vertical-align-enabled? editor "top"))
:on-click #(on-vertical-align-change % "top")}
:class (dom/classnames :current (= "top" vertical-align))
:on-click #(on-change % "top")}
i/align-top]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.align-middle")
:class (dom/classnames
:current (dwt/vertical-align-enabled? editor "center"))
:on-click #(on-vertical-align-change % "center")}
:class (dom/classnames :current (= "center" vertical-align))
:on-click #(on-change % "center")}
i/align-middle]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.align-bottom")
:class (dom/classnames
:current (dwt/vertical-align-enabled? editor "bottom"))
:on-click #(on-vertical-align-change % "bottom")}
:class (dom/classnames :current (= "bottom" vertical-align))
:on-click #(on-change % "bottom")}
i/align-bottom]]]))
(mf/defc text-decoration-options
[{:keys [editor locale] :as props}]
(let [on-decoration-change
[{:keys [editor locale shape] :as props}]
(let [{:keys [text-decoration]
:or {text-decoration "none"}}
(dwt/current-text-values
{:editor editor
:shape shape
:attrs [:text-decoration]})
on-change
(fn [event type]
(dom/prevent-default event)
(dom/stop-propagation event)
(if (dwt/text-decoration-enabled? editor type)
(dwt/set-text-decoration! editor "none")
(dwt/set-text-decoration! editor type)))]
(st/emit! (dwt/update-text-attrs
{:id (:id shape)
:editor editor
:attrs {:text-decoration type}})))]
[:div.row-flex
[:span.element-set-subtitle (t locale "workspace.options.font-options.decoration")]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.none")
:on-click #(on-decoration-change % "none")}
:class (dom/classnames :current (= "none" text-decoration))
:on-click #(on-change % "none")}
i/minus]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.underline")
:class (dom/classnames
:current (dwt/text-decoration-enabled? editor "underline"))
:on-click #(on-decoration-change % "underline")}
:class (dom/classnames :current (= "underline" text-decoration))
:on-click #(on-change % "underline")}
i/underline]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.strikethrough")
:class (dom/classnames
:current (dwt/text-decoration-enabled? editor "line-through"))
:on-click #(on-decoration-change % "line-through")}
:class (dom/classnames :current (= "line-through" text-decoration))
:on-click #(on-change % "line-through")}
i/strikethrough]]]))
(mf/defc text-transform-options
[{:keys [editor locale] :as props}]
(let [on-text-transform-change
[{:keys [editor locale shape] :as props}]
(let [{:keys [text-transform]
:or {text-transform "none"}}
(dwt/current-text-values
{:editor editor
:shape shape
:attrs [:text-transform]})
on-change
(fn [event type]
(dom/prevent-default event)
(dom/stop-propagation event)
(if (dwt/text-transform-enabled? editor type)
(dwt/set-text-transform! editor "none")
(dwt/set-text-transform! editor type)))]
(st/emit! (dwt/update-text-attrs
{:id (:id shape)
:editor editor
:attrs {:text-transform type}})))]
[:div.row-flex
[:span.element-set-subtitle (t locale "workspace.options.font-options.text-case")]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.none")
:class (dom/classnames
:current (dwt/text-transform-enabled? editor "none"))
:on-click #(on-text-transform-change % "none")}
:class (dom/classnames :current (= "none" text-transform))
:on-click #(on-change % "none")}
i/minus]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.uppercase")
:class (dom/classnames
:current (dwt/text-transform-enabled? editor "uppercase"))
:on-click #(on-text-transform-change % "uppercase")}
:class (dom/classnames :current (= "uppercase" text-transform))
:on-click #(on-change % "uppercase")}
i/uppercase]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.lowercase")
:class (dom/classnames
:current (dwt/text-transform-enabled? editor "lowercase"))
:on-click #(on-text-transform-change % "lowercase")}
:class (dom/classnames :current (= "lowercase" text-transform))
:on-click #(on-change % "lowercase")}
i/lowercase]
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.font-options.titlecase")
:class (dom/classnames
:current (dwt/text-transform-enabled? editor "capitalize"))
:on-click #(on-text-transform-change % "capitalize")}
:class (dom/classnames :current (= "capitalize" text-transform))
:on-click #(on-change % "capitalize")}
i/titlecase]]]))
(mf/defc text-menu
{::mf/wrap [mf/memo]}
[{:keys [shape] :as props}]
(let [id (:id shape)
editor (:editor (mf/deref refs/workspace-local))
locale (i18n/use-locale)]
local (mf/deref refs/workspace-local)
editor (get-in local [:editors (:id shape)])
locale (mf/deref i18n/locale)]
[:*
[:div.element-set
[:div.element-set-title (t locale "workspace.options.fill")]
[:div.element-set-content
[:& text-fill-options {:editor editor}]]]
[:& text-fill-options {:editor editor :shape shape}]]]
[:div.element-set
[:div.element-set-title (t locale "workspace.options.font-options")]
[:div.element-set-content
[:& font-options {:editor editor :locale locale}]
[:& text-align-options {:editor editor :locale locale}]
[:& spacing-options {:editor editor :locale locale}]
[:& vertical-align-options {:editor editor :locale locale}]
[:& text-decoration-options {:editor editor :locale locale}]
[:& text-transform-options {:editor editor :locale locale}]]]]))
[:& font-options {:editor editor :locale locale :shape shape}]
[:& text-align-options {:editor editor :locale locale :shape shape}]
[:& spacing-options {:editor editor :locale locale :shape shape}]
[:& vertical-align-options {:editor editor :locale locale :shape shape}]
[:& text-decoration-options {:editor editor :locale locale :shape shape}]
[:& text-transform-options {:editor editor :locale locale :shape shape}]]]]))
(mf/defc options
[{:keys [shape] :as props}]