mirror of
https://github.com/penpot/penpot.git
synced 2025-05-18 01:26:10 +02:00
♻️ Refactor texts.
This commit is contained in:
parent
07981487bf
commit
51c39d169f
4 changed files with 401 additions and 440 deletions
|
@ -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]
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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..."}]]]))
|
||||
|
|
|
@ -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}]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue