diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index ee335cb94..5c78f7ce5 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -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] diff --git a/frontend/src/uxbox/main/data/workspace/texts.cljs b/frontend/src/uxbox/main/data/workspace/texts.cljs index 7254adf47..8cb488f85 100644 --- a/frontend/src/uxbox/main/data/workspace/texts.cljs +++ b/frontend/src/uxbox/main/data/workspace/texts.cljs @@ -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))) diff --git a/frontend/src/uxbox/main/ui/shapes/text.cljs b/frontend/src/uxbox/main/ui/shapes/text.cljs index 11d24d159..7f1ad9960 100644 --- a/frontend/src/uxbox/main/ui/shapes/text.cljs +++ b/frontend/src/uxbox/main/ui/shapes/text.cljs @@ -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..."}]]])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs index 342a21fe2..41ca167b8 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs @@ -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}]