0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-08 07:50:43 -05:00

🎉 Add new text shape.

This commit is contained in:
Andrey Antukh 2020-04-23 14:45:06 +02:00
parent beed68166e
commit aca1959dd7
11 changed files with 22141 additions and 468 deletions

View file

@ -41,7 +41,7 @@
;; Page Data related
(s/def ::blocked boolean?)
(s/def ::collapsed boolean?)
(s/def ::content string?)
(s/def ::content any?)
(s/def ::fill-color string?)
(s/def ::fill-opacity number?)
(s/def ::font-family string?)

View file

@ -21,7 +21,8 @@
}
:aliases
{:dev
{:extra-deps
{:extra-paths ["test" "dev"]
:extra-deps
{com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
com.bhauman/rebel-readline {:mvn/version "0.1.4"}
org.clojure/tools.namespace {:mvn/version "0.3.1"}
@ -34,8 +35,7 @@
;; i18n parsing
carocad/parcera {:mvn/version "0.11.0"}
org.antlr/antlr4-runtime {:mvn/version "4.7"}}
:extra-paths ["test"]}
org.antlr/antlr4-runtime {:mvn/version "4.7"}}}
:repl
{:main-opts ["-m" "rebel-readline.main"]}

View file

@ -18,6 +18,21 @@
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw=="
},
"@types/debounce": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.0.tgz",
"integrity": "sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw=="
},
"@types/esrever": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@types/esrever/-/esrever-0.2.0.tgz",
"integrity": "sha512-5NI6TeGzVEy/iBcuYtcPzzIC6EqlfQ2+UZ54vT0ulq8bPNGAy8UJD+XcsAyEOcnYFUjOVWuUV+k4/rVkxt9/XQ=="
},
"@types/is-hotkey": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.1.tgz",
"integrity": "sha512-QzVKww91fJv/KzARJBS/Im5GS2A8iE64E1HxOed72EmYOvPLG4PBw77QCIUjFl7VwWB3G/SVrxsHedJD/wtn1A=="
},
"@types/q": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz",
@ -947,6 +962,11 @@
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
},
"compute-scroll-into-view": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.13.tgz",
"integrity": "sha512-o+w9w7A98aAFi/GjK8cxSV+CdASuPa2rR5UWs3+yHkJzWqaKoBEufFNWYaXInCSmUfDCVhesG+v9MTWqOjsxFg=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1191,6 +1211,11 @@
"integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==",
"dev": true
},
"debounce": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg=="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -1335,6 +1360,11 @@
"randombytes": "^2.0.0"
}
},
"direction": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
"integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ=="
},
"dom-serializer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
@ -1559,6 +1589,11 @@
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esrever": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/esrever/-/esrever-0.2.0.tgz",
"integrity": "sha1-lunSj08bGnZ4TNXUkOquAQ50B7g="
},
"events": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
@ -2977,6 +3012,11 @@
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
"dev": true
},
"immer": {
"version": "5.3.6",
"resolved": "https://registry.npmjs.org/immer/-/immer-5.3.6.tgz",
"integrity": "sha512-pqWQ6ozVfNOUDjrLfm4Pt7q4Q12cGw2HUZgry4Q5+Myxu9nmHRkWBpI0J4+MK0AxbdFtdMTwEGVl7Vd+vEiK+A=="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -3143,6 +3183,11 @@
"is-extglob": "^2.1.1"
}
},
"is-hotkey": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.6.tgz",
"integrity": "sha512-1+hMr0GLPM0M49UDRt9RgE8i+SM29UY4AGRP6sGz6fThOVXqSrEvTMakolhHMcVizJnPNAoMpEmE+Oi1k2NrZQ=="
},
"is-negated-glob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
@ -5015,6 +5060,14 @@
"object-assign": "^4.1.1"
}
},
"scroll-into-view-if-needed": {
"version": "2.2.24",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.24.tgz",
"integrity": "sha512-vsC6SzyIZUyJG8o4nbUDCiIwsPdH6W/FVmjT2avR2hp/yzS53JjGmg/bKD20TkoNajbu5dAQN4xR7yes4qhwtQ==",
"requires": {
"compute-scroll-into-view": "^1.0.13"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@ -5204,6 +5257,62 @@
}
}
},
"slate": {
"version": "0.57.1",
"resolved": "https://registry.npmjs.org/slate/-/slate-0.57.1.tgz",
"integrity": "sha512-av95C0zbsi0+khfOuyQlpZy0Q3Pqqh0loCusGGi0Wlb/J8OUBAyK/a5EgTfE0zK4pmjtEBnPksfBuVilg5jrNg==",
"requires": {
"@types/esrever": "^0.2.0",
"esrever": "^0.2.0",
"immer": "^5.0.0",
"is-plain-object": "^3.0.0",
"tiny-warning": "^1.0.3"
},
"dependencies": {
"is-plain-object": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz",
"integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==",
"requires": {
"isobject": "^4.0.0"
}
},
"isobject": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz",
"integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA=="
}
}
},
"slate-react": {
"version": "0.57.1",
"resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.57.1.tgz",
"integrity": "sha512-W+M+YEcqBvuJD2xoU1gKgGbvhv6shwYJ5OOjPF8ZPEokUvGJh81Egxf65ZnnKxJejodsERdlwc2rRjpRphx2LQ==",
"requires": {
"@types/debounce": "^1.2.0",
"@types/is-hotkey": "^0.1.1",
"debounce": "^1.2.0",
"direction": "^1.0.3",
"is-hotkey": "^0.1.6",
"is-plain-object": "^3.0.0",
"scroll-into-view-if-needed": "^2.2.20"
},
"dependencies": {
"is-plain-object": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz",
"integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==",
"requires": {
"isobject": "^4.0.0"
}
},
"isobject": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz",
"integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA=="
}
}
},
"snapdragon": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
@ -5934,6 +6043,11 @@
"setimmediate": "^1.0.4"
}
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tinycolor2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",

View file

@ -35,6 +35,8 @@
"react-color": "^2.18.0",
"react-dom": "^16.13.1",
"rxjs": "^7.0.0-beta.0",
"slate": "^0.57.1",
"slate-react": "^0.57.1",
"source-map-support": "^0.5.16",
"tdigest": "^0.1.1",
"xregexp": "^4.3.0"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
foreignObject .rich-text {
color: $color-black;
height: 100%;
}

View file

@ -0,0 +1,218 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.data.workspace.texts
(:require
[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.main.fonts :as fonts]
["slate" :as slate :refer [Editor Transforms Text]]))
(declare set-nodes!)
(declare is-text?)
(defn assign-editor
[editor]
(ptk/reify ::assign-editor
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :editor] editor)
(update-in [:workspace-local :editor-n] (fnil inc 0))))))
;; --- Helpers
(defn set-nodes!
([editor props]
(set-nodes! editor props #js {}))
([editor props options]
(.setNodes Transforms editor props options)
editor))
(defn is-text?
[v]
(.isText Text v))
(defn is-paragraph?
[v]
(= (.-type v) "paragraph"))
;; --- Predicates
(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 text-decoration-enabled?
[editor type]
(enabled? editor true
(fn [v]
(let [val (unchecked-get v "textDecoration")]
(identical? type val)))))
(defn text-transform-enabled?
[editor type]
(enabled? editor true
(fn [v]
(let [val (unchecked-get v "textTransform")]
(identical? type val)))))
(defn text-align-enabled?
[editor type]
(enabled? editor false
(fn [v]
(let [val (unchecked-get v "textAlign")]
(identical? type val)))))
(defn vertical-align-enabled?
[editor type]
(enabled? editor false
(fn [v]
(let [val (unchecked-get v "verticalAlign")]
(identical? type val)))))
;; --- Getters
(defn current-value
[editor {:keys [universal?
attr
pred
at]
:as opts}]
(when editor
(let [options #js {:match pred
:universal universal?}
_ (when at (unchecked-set options "at" at))
result (.nodes Editor editor options)
match (ffirst (es6-iterator-seq result))]
(when (object? match)
(unchecked-get match attr)))))
(defn current-line-height
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-paragraph?
:attr "lineHeight"
:universal? false})
default))
(defn current-letter-spacing
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-text?
:attr "letterSpacing"
:universal? true})
default))
(defn current-font-family
[editor {:keys [at default]}]
(or (current-value editor {:at at
:pred is-text?
:attr "fontId"
:universal? true})
default))
(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))
;; --- 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" (unchecked-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}))

View file

@ -0,0 +1,178 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.fonts
"Fonts management and loading logic."
(:require
[beicon.core :as rx]
[okulary.core :as l]
[cuerdas.core :as str]
[uxbox.util.dom :as dom]
[uxbox.util.http :as http]
[uxbox.util.transit :as t]
[uxbox.util.timers :as ts]
[uxbox.common.data :as d]
[clojure.set :as set]))
(def local-fonts
[{:id "sourcesanspro"
:name "Source Sans Pro"
:family "sourcesanspro"
:variants [{:name "100" :weight "100" :style "normal"}
{:name "100italic" :weight "100" :style "italic"}
{:name "200" :weight "200" :style "normal"}
{:name "200italic" :weight "200" :style "italic"}
{:name "300" :weight "300" :style "normal"}
{:name "300italic" :weight "300" :style "italic"}
{:name "regular" :weight "400" :style "normal"}
{:name "italic" :weight "400" :style "italic"}
{:name "500" :weight "500" :style "normal"}
{:name "500italic" :weight "500" :style "italic"}
{:name "bold" :weight "bold" :style "normal"}
{:name "bolditalic" :weight "bold" :style "italic"}
{:name "black" :weight "900" :style "normal"}
{:name "blackitalic" :weight "900" :style "italic"}]}
{:id "roboto"
:family "roboto"
:name "Roboto"
:variants [{:name "100" :weight "100" :style "normal"}
{:name "100italic" :weight "100" :style "italic"}
{:name "200" :weight "200" :style "normal"}
{:name "200italic" :weight "200" :style "italic"}
{:name "regular" :weight "400" :style "normal"}
{:name "italic" :weight "400" :style "italic"}
{:name "500" :weight "500" :style "normal"}
{:name "500italic" :weight "500" :style "italic"}
{:name "bold" :weight "bold" :style "normal"}
{:name "bolditalic" :weight "bold" :style "italic"}
{:name "black" :weight "900" :style "normal"}
{:name "blackitalic" :weight "900" :style "italic"}]}
{:id "robotocondensed"
:family "robotocondensed"
:name "Roboto Condensed"
:variants [{:name "100" :weight "100" :style "normal"}
{:name "100italic" :weight "100" :style "italic"}
{:name "200" :weight "200" :style "normal"}
{:name "200italic" :weight "200" :style "italic"}
{:name "regular" :weight "400" :style "normal"}
{:name "italic" :weight "400" :style "italic"}
{:name "500" :weight "500" :style "normal"}
{:name "500italic" :weight "500" :style "italic"}
{:name "bold" :weight "bold" :style "normal"}
{:name "bolditalic" :weight "bold" :style "italic"}
{:name "black" :weight "900" :style "normal"}
{:name "blackitalic" :weight "900" :style "italic"}]}])
(defonce fontsdb (l/atom {}))
(defonce fontsview (l/atom {}))
(defn- materialize-fontsview
[db]
(reset! fontsview (reduce-kv (fn [acc k v]
(assoc acc k (sort-by :name v)))
{}
(group-by :backend (vals db)))))
(add-watch fontsdb "main"
(fn [_ _ _ db]
(ts/schedule-on-idle #(materialize-fontsview db))))
(defn register!
[backend fonts]
(let [fonts (map #(assoc % :backend backend) fonts)]
(swap! fontsdb #(merge % (d/index-by :id fonts)))))
(register! :builtin local-fonts)
(defn resolve-variants
[id]
(get-in @fontsdb [id :variants]))
(defn resolve-fonts
[backend]
(get @fontsview backend))
;; --- Google Fonts Fetcher
(def ^:private gfonts-uri "/fonts/gfonts.2020.04.23.json")
(defn- parse-gfont-variant
[variant]
(cond
(= "regular" variant)
{:name "regular" :width "400" :style "normal"}
(= "italic" variant)
{:name "italic" :width "400" :style "italic"}
:else
(when-let [[a b c] (re-find #"^(\d+)(.*)$" variant)]
(if (str/empty? c)
{:name a :width b :style "normal"}
{:name a :width b :style c}))))
(defn- parse-gfont
[font]
(let [family (get font "family")
variants (get font "variants")]
{:id (str "gfont-" (str/slug family))
:family family
:name family
:variants (into [] (comp (map parse-gfont-variant)
(filter identity))
variants)}))
(let [url (unchecked-get js/location "origin")
url (str url gfonts-uri)
stm (->> (http/send! {:method :get :url url})
(rx/map :body)
(rx/map t/decode)
(rx/mapcat #(get % "items"))
(rx/map parse-gfont)
(rx/reduce conj []))]
(rx/subscribe stm #(register! :google %)))
;; --- Fonts Loader
(defonce loaded (l/atom #{}))
(defn- create-link-node
[uri]
(let [node (.createElement js/document "link")]
(unchecked-set node "href" uri)
(unchecked-set node "rel" "stylesheet")
(unchecked-set node "type" "text/css")
node))
(defmulti ^:private load-font :backend)
(defmethod load-font :builtin
[{:keys [id] :as font}]
(js/console.log "[debug:fonts]: loading builtin font" id))
(defmethod load-font :google
[{:keys [id family variants] :as font}]
(js/console.log "[debug:fonts]: loading google font" id)
(let [base (str "https://fonts.googleapis.com/css?family=" family)
variants (str/join "," (map :name variants))
uri (str base ":" variants)
node (create-link-node uri)]
(.append (.-head js/document) node)
nil))
(defmethod load-font :default
[{:keys [backend] :as font}]
(js/console.warn "no implementation found for" backend))
(defn ensure-loaded!
[id]
(when-not (contains? @loaded id)
(when-let [font (get @fontsdb id)]
(load-font font)
(swap! loaded conj id))))

View file

@ -11,14 +11,22 @@
[goog.object :as gobj]
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.data.workspace :as dw]
[uxbox.util.geom.shapes :as geom]
[uxbox.main.data.workspace.texts :as dwt]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.shapes.common :as common]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.fonts :as fonts]
[uxbox.util.color :as color]
[uxbox.util.dom :as dom]
[uxbox.util.geom.matrix :as gmt]))
[uxbox.util.geom.shapes :as geom]
[uxbox.util.geom.matrix :as gmt]
["slate" :as slate]
["slate-react" :as rslate])
(:import goog.events.EventType
goog.events.KeyCodes))
;; --- Events
@ -54,111 +62,267 @@
(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}
(if edition?
[:& text-shape-edit {:shape (geom/transform-shape frame shape)}]
[:& text-shape {:shape (geom/transform-shape frame shape)}])]))
[:& text-shape {:shape (geom/transform-shape frame shape)
:selected? selected?}])]))
;; --- Text Styles Helpers
;; --- Text Rendering
(defn- make-style
[{:keys [fill-color
fill-opacity
font-family
font-weight
font-style
font-size
text-align
line-height
letter-spacing
user-select
width
height]
:or {fill-color "#000000"
fill-opacity 1
font-family "sourcesanspro"
font-weight "normal"
font-style "normal"
font-size 18
line-height 1.4
letter-spacing 1
text-align "left"
user-select false}
:as shape}]
(let [color (-> fill-color
(color/hex->rgba fill-opacity)
(color/rgb->str))]
(rumext.util/map->obj
(merge
{:fontSize (str font-size "px")
:color color
:width width
:height height
:whiteSpace "pre-wrap"
:textAlign text-align
:fontFamily font-family
:fontWeight font-weight
:fontStyle font-style
:margin "0px"
:padding "0px"
:border "0px"
:resize "none"
:background "transparent"}
(when user-select {:userSelect "auto"})
(when line-height {:lineHeight line-height})
(when letter-spacing {:letterSpacing letter-spacing})))))
(defn obj-assoc!
[obj attr value]
(unchecked-set obj attr value)
obj)
(defn- generate-text-box-styles
[data]
(let [valign (unchecked-get data "verticalAlign")
base #js {:height "100%"
:width "100%"
:display "flex"}]
(cond-> base
(= valign "top") (obj-assoc! "alignItems" "flex-start")
(= valign "center") (obj-assoc! "alignItems" "center")
(= valign "bottom") (obj-assoc! "alignItems" "flex-end"))))
(mf/defc rt-text-box
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [attrs (unchecked-get props "attributes")
childs (unchecked-get props "children")
data (unchecked-get props "element")
type (unchecked-get data "type")
style (generate-text-box-styles data)
attrs (obj-assoc! attrs "style" style)
attrs (obj-assoc! attrs "className" type)]
[:> :div attrs childs]))
(defn- generate-text-styles
[data]
(let [valign (unchecked-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 (unchecked-get props "attributes")
childs (unchecked-get props "children")
data (unchecked-get props "element")
type (unchecked-get data "type")
style (generate-text-styles data)
attrs (obj-assoc! attrs "style" style)
attrs (obj-assoc! attrs "className" type)]
[:> :div attrs childs]))
(defn- generate-paragraph-styles
[data]
(let [base #js {:fontSize "14px"
:margin "inherit"
:lineHeight "1.2"}
lh (unchecked-get data "lineHeight")
ta (unchecked-get data "textAlign")]
(cond-> base
ta (obj-assoc! "textAlign" ta)
lh (obj-assoc! "lineHeight" lh))))
(mf/defc rt-pharagraph
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [attrs (unchecked-get props "attributes")
childs (unchecked-get props "children")
data (unchecked-get props "element")
style (generate-paragraph-styles data)
attrs (obj-assoc! attrs "style" style)]
[:> :p attrs childs]))
(defn- generate-leaf-styles
[data]
(let [letter-spacing (unchecked-get data "letterSpacing")
text-decoration (unchecked-get data "textDecoration")
text-transform (unchecked-get data "textTransform")
font-id (unchecked-get data "fontId")
font-variant-id (unchecked-get data "fontVariantId")
font-family (unchecked-get data "fontFamily")
font-size (unchecked-get data "fontSize")
fontsdb (mf/deref fonts/fontsdb)
base #js {:textDecoration text-decoration
:textTransform text-transform}]
(when (and (string? letter-spacing)
(pos? (alength letter-spacing)))
(obj-assoc! base "letterSpacing" (str letter-spacing "px")))
(when (and (string? font-size)
(pos? (alength font-size)))
(obj-assoc! base "fontSize" (str font-size "px")))
(when (and (string? font-id)
(pos? (alength font-id)))
(let [font (get fontsdb font-id)]
(fonts/ensure-loaded! font-id)
(let [font-family (or (:family font)
(unchecked-get data "fontFamily"))
font-variant (d/seek #(= font-variant-id (:name %))
(:variants font))
font-style (or (:style font-variant)
(unchecked-get data "fontStyle"))
font-weight (or (:weight font-variant)
(unchecked-get data "fontWeight"))]
(obj-assoc! base "fontFamily" font-family)
(obj-assoc! base "fontStyle" font-style)
(obj-assoc! base "fontWeight" font-weight))))
base))
(mf/defc rt-leaf
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [attrs (unchecked-get props "attributes")
childs (unchecked-get props "children")
data (unchecked-get props "leaf")
style (generate-leaf-styles data)
attrs (obj-assoc! attrs "style" style)]
[:> :span attrs childs]))
(defn- render-element
[props]
(mf/html
(let [element (unchecked-get props "element")]
(case (unchecked-get element "type")
"text-box" [:> rt-text-box props]
"text" [:> rt-text props]
"paragraph" [:> rt-pharagraph props]
nil))))
(defn- render-leaf
[props]
(mf/html
[:> rt-leaf 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}]}]}]}]))
(defn- parse-content
[content]
(cond
(string? content) (initial-text content)
(vector? content) (clj->js content)
(object? content) content
:else (initial-text)))
(mf/defc text-shape-edit
{::mf/wrap [mf/memo]}
[{:keys [shape] :as props}]
(let [ref (mf/use-ref)
{:keys [id x y width height content]} shape
(let [{:keys [id x y width height content]} shape
on-unmount
state (mf/use-state #(parse-content content))
value (mf/use-var @state)
editor (mf/use-memo #(rslate/withReact (slate/createEditor)))
self-ref (mf/use-ref)
on-close
(fn []
(let [content (-> (mf/ref-val ref)
(dom/get-value))]
(st/emit! (dw/update-shape id {:content content}))))
on-blur
(fn [event]
(st/emit! dw/clear-edition-mode
dw/deselect-all))]
(mf/use-effect
#(let [dom (mf/ref-val ref)
val (dom/get-value dom)]
(.focus dom)
(gobj/set dom "selectionStart" (count val))
(gobj/set dom "selectionEnd" (count val))
dw/deselect-all))
on-click
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [sidebar (dom/get-element "settings-bar")
self (mf/ref-val self-ref)
target (dom/get-target event)]
(when-not (or (.contains sidebar target)
(.contains self target))
(on-close))))
on-keyup
(fn [event]
(when (= (.-keyCode event) 27) ; ESC
(on-close)))
on-mount
(fn []
(let [content (-> (mf/ref-val ref)
(dom/get-value))]
(st/emit! (dw/update-shape id {:content content}))))))
(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}))
(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)))]
(mf/use-effect on-mount)
[:foreignObject {:x x :y y :width width :height height}
[:textarea {:style (make-style shape)
:on-blur on-blur
:default-value content
:ref ref}]]))
[:foreignObject {:x x :y y :width width :height height :ref self-ref}
[:> rslate/Slate {:editor editor
:value @state
:on-change on-change}
[:> rslate/Editable
{:auto-focus "true"
:spell-check "false"
:class "rich-text"
:render-element render-element
:render-leaf render-leaf
:on-blur (fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
;; WARN: monky patch
(unchecked-set slate/Transforms "deselect" (constantly nil)))
:placeholder "Type some text here..."}]]]))
;; --- Text Shape Wrapper
(mf/defc text-shape
[{:keys [shape] :as props}]
[{:keys [shape selected?] :as props}]
(let [{:keys [id x y width height rotation content]} shape
transform (when (and rotation (pos? rotation))
(str/format "rotate(%s %s %s)"
rotation
(+ x (/ width 2))
(+ y (/ height 2))))]
(+ y (/ height 2))))
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))))
render-element (mf/use-callback render-element)
render-leaf (mf/use-callback render-leaf)]
(mf/use-effect (mf/deps id selected?) on-mount)
[:foreignObject {:x x
:y y
@ -166,4 +330,12 @@
:id (str id)
:width width
:height height}
[:div {:style (make-style shape)} content]]))
[:> rslate/Slate {:editor editor
:value content}
[:> 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

@ -64,7 +64,7 @@
:segments []}
{:type :text
:name "Text"
:content "Type your text here"}])
:content nil}])
(defn start-drawing
[type]

View file

@ -10,415 +10,334 @@
(ns uxbox.main.ui.workspace.sidebar.options.text
(:require
[rumext.alpha :as mf]
[okulary.core :as l]
[uxbox.builtins.icons :as i]
[uxbox.common.data :as d]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.workspace.texts :as dwt]
[uxbox.main.store :as st]
[uxbox.main.refs :as refs]
[uxbox.main.ui.workspace.sidebar.options.measures :refer [measures-menu]]
[uxbox.util.dom :as dom]
[uxbox.main.fonts :as fonts]
[uxbox.util.math :as math]
[uxbox.util.i18n :refer [tr]]))
[uxbox.util.i18n :as i18n :refer [tr t]]
["slate" :refer [Transforms]]))
(declare +fonts+)
(def ^:private editor-ref
(l/derived :editor refs/workspace-local))
(mf/defc fonts-menu
[{:keys [shape] :as props}]
(let [id (:id shape)
font-family (:font-family shape "sourcesanspro")
font-style (:font-style shape "normal")
font-weight (:font-weight shape "normal")
font-size (:font-size shape 16)
text-align (:text-align shape "left")
line-height (:line-height shape 1.4)
letter-spacing (:letter-spacing shape 1)
(mf/defc font-select-optgroups
{::mf/wrap [mf/memo]}
[]
[:*
[:optgroup {:label "Local"}
(for [font fonts/local-fonts]
[:option {:value (:id font)
:key (:id font)}
(:name font)])]
[:optgroup {:label "Google"}
(for [font (fonts/resolve-fonts :google)]
[:option {:value (:id font)
:key (:id font)}
(:name font)])]])
styles (:styles (d/seek #(= (:id %) font-family) +fonts+))
(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"})
fonts (mf/deref fonts/fontsdb)
font (get fonts font-id)
on-font-family-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value))
attrs {:font-family value
:font-weight "normal"
:font-style "normal"}]
(st/emit! (dw/update-shape id attrs))))
(let [id (-> (dom/get-target event)
(dom/get-value))
font (get fonts id)]
(dwt/set-font! editor id (:family font))
(when (not= id font-id)
(dwt/set-font-variant! editor nil nil nil))))
on-font-size-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-integer 0))
attrs {:font-size value}]
(st/emit! (dw/update-shape id attrs))))
(let [val (-> (dom/get-target event)
(dom/get-value))]
(dwt/set-font-size! editor val)))
on-font-letter-spacing-change
on-font-variant-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-double 0))
attrs {:letter-spacing value}]
(st/emit! (dw/update-shape id attrs))))
on-font-line-height-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-double 0))
attrs {:line-height value}]
(st/emit! (dw/update-shape id attrs))))
on-font-align-change
(fn [event value]
(let [attrs {:text-align value}]
(st/emit! (dw/update-shape id attrs))))
on-font-style-change
(fn [event]
(let [[weight style] (-> (dom/get-target event)
(dom/get-value)
(d/read-string))
attrs {:font-style style
:font-weight weight}]
(st/emit! (dw/update-shape id attrs))))
(let [id (-> (dom/get-target event)
(dom/get-value))
variant (d/seek #(= id (:name %)) (:variants font))]
(dwt/set-font! editor (:id font) (:family font))
(dwt/set-font-variant! editor id (:weight variant) (:style variant))))
]
[:*
[:div.row-flex
[:select.input-select {:value font-id
:on-change on-font-family-change}
[:& font-select-optgroups]]]
[:div.row-flex
[:div.editable-select
[:select.input-select {:value font-size
:on-change on-font-size-change}
[:option {:value "8"} "8"]
[:option {:value "9"} "9"]
[:option {:value "10"} "10"]
[:option {:value "11"} "11"]
[:option {:value "12"} "12"]
[:option {:value "14"} "14"]
[:option {:value "18"} "18"]
[:option {:value "24"} "24"]
[:option {:value "36"} "36"]
[:option {:value "48"} "48"]
[:option {:value "72"} "72"]]
[:input.input-text {:type "number"
:min "0"
:max "200"
:value font-size
:on-change on-font-size-change
}]]
[:select.input-select {:value font-var
:on-change on-font-variant-change}
(for [variant (:variants font)]
[:option {:value (:name variant)
:key (pr-str variant)}
(:name variant)])]]]))
(mf/defc text-align-options
[{:keys [editor] :as props}]
(let [on-text-align-change
(fn [event type]
(js/console.log (dwt/set-text-align! editor type)))]
;; --- Align
[:div.row-flex.align-icons
[:span.tooltip.tooltip-bottom
{:alt "Align left"
:class (dom/classnames
:current (dwt/text-align-enabled? editor "left"))
:on-click #(on-text-align-change % "left")}
i/text-align-left]
[:span.tooltip.tooltip-bottom
{:alt "Align center"
:class (dom/classnames
:current (dwt/text-align-enabled? editor "center"))
:on-click #(on-text-align-change % "center")}
i/text-align-center]
[:span.tooltip.tooltip-bottom
{:alt "Align right"
:class (dom/classnames
:current (dwt/text-align-enabled? editor "right"))
:on-click #(on-text-align-change % "right")}
i/text-align-right]
[:span.tooltip.tooltip-bottom
{:alt "Justify"
:class (dom/classnames
:current (dwt/text-align-enabled? editor "justify"))
:on-click #(on-text-align-change % "justify")}
i/text-align-justify]]))
(mf/defc spacing-options
[{:keys [editor] :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)})]
[:div.row-flex
[:div.input-icon
[:span.icon-before.tooltip.tooltip-bottom
{:alt "Line height"}
i/line-height]
[:input.input-text
{:type "number"
:step "0.1"
:min "0"
:max "200"
:value lh
:on-focus (fn [event]
(js/console.log "line-height on-focus")
;; (mf/set-ref-val! selection (.-selection editor))
(dom/prevent-default event)
(dom/stop-propagation event))
:on-blur (fn [event]
;; (js/console.log "line-height on-blur")
(mf/set-ref-val! selection nil)
(dom/prevent-default event)
(dom/stop-propagation event))
:on-change (fn [event]
(let [val (-> (dom/get-target event)
(dom/get-value))
sel (mf/ref-val selection)]
;; (js/console.log "line-height on-change" sel val)
(dwt/set-line-height! editor val sel)))}]]
[:div.input-icon
[:span.icon-before.tooltip.tooltip-bottom
{:alt "Letter spacing"}
i/letter-spacing]
[:input.input-text
{:type "number"
:step "0.1"
:min "0"
:max "200"
:value ls
:on-focus (fn [event]
;; (js/console.log "letter-spacing on-focus")
(mf/set-ref-val! selection (.-selection editor))
(dom/prevent-default event)
(dom/stop-propagation event))
:on-blur (fn [event]
;; (js/console.log "letter-spacing on-blur")
(mf/set-ref-val! selection nil)
(dom/prevent-default event)
(dom/stop-propagation event))
:on-change (fn [event]
(let [val (-> (dom/get-target event)
(dom/get-value))
sel (mf/ref-val selection)]
;; (js/console.log "letter-spacing on-change" sel val)
(dwt/set-letter-spacing! editor val sel)))}]]]))
(mf/defc box-sizing-options
[{:keys [editor] :as props}]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt "Auto height"}
i/auto-height]
[:span.tooltip.tooltip-bottom
{:alt "Auto width"}
i/auto-width]
[:span.tooltip.tooltip-bottom
{:alt "Fixed size"}
i/auto-fix]])
(mf/defc vertical-align-options
[{:keys [editor] :as props}]
(let [on-vertical-align-change
(fn [event type]
(dwt/set-vertical-align! editor type))]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt "Align top"
:class (dom/classnames
:current (dwt/vertical-align-enabled? editor "top"))
:on-click #(on-vertical-align-change % "top")}
i/align-top]
[:span.tooltip.tooltip-bottom
{:alt "Align middle"
:class (dom/classnames
:current (dwt/vertical-align-enabled? editor "center"))
:on-click #(on-vertical-align-change % "center")}
i/align-middle]
[:span.tooltip.tooltip-bottom
{:alt "Align bottom"
:class (dom/classnames
:current (dwt/vertical-align-enabled? editor "bottom"))
:on-click #(on-vertical-align-change % "bottom")}
i/align-bottom]]))
(mf/defc text-decoration-options
[{:keys [editor] :as props}]
(let [on-decoration-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)))]
[:div.row-flex
[:span.element-set-subtitle "Decoration"]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt "None"
:on-click #(on-decoration-change % "none")}
i/minus]
[:span.tooltip.tooltip-bottom
{:alt "Underline"
:class (dom/classnames
:current (dwt/text-decoration-enabled? editor "underline"))
:on-click #(on-decoration-change % "underline")}
i/underline]
[:span.tooltip.tooltip-bottom
{:alt "Strikethrough"
:class (dom/classnames
:current (dwt/text-decoration-enabled? editor "line-through"))
:on-click #(on-decoration-change % "line-through")}
i/strikethrough]]]))
(mf/defc text-transform-options
[{:keys [editor] :as props}]
(let [on-text-transform-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)))]
[:div.row-flex
[:span.element-set-subtitle "Case"]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt "None"
:class (dom/classnames
:current (dwt/text-transform-enabled? editor "none"))
:on-click #(on-text-transform-change % "none")}
i/minus]
[:span.tooltip.tooltip-bottom
{:alt "Uppercase"
:class (dom/classnames
:current (dwt/text-transform-enabled? editor "uppercase"))
:on-click #(on-text-transform-change % "uppercase")}
i/uppercase]
[:span.tooltip.tooltip-bottom
{:alt "Lowercase"
:class (dom/classnames
:current (dwt/text-transform-enabled? editor "lowercase"))
:on-click #(on-text-transform-change % "lowercase")}
i/lowercase]
[:span.tooltip.tooltip-bottom
{:alt "Titlecase"
:class (dom/classnames
:current (dwt/text-transform-enabled? editor "capitalize"))
:on-click #(on-text-transform-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)]
[:div.element-set
[:div.element-set-title (tr "workspace.options.font-options")]
[:div.element-set-title (t locale "workspace.options.font-options")]
[:div.element-set-content
[:div.row-flex
[:select.input-select {:value font-family
:on-change on-font-family-change}
(for [font +fonts+]
[:option {:value (:id font)
:key (:id font)}
(:name font)])]]
[:& font-options {:editor editor}]
[:& text-align-options {:editor editor}]
[:& spacing-options {:editor editor}]
[:div.row-flex
[:div.editable-select
[:select.input-select {:value font-size
:on-change on-font-size-change}
[:option {:value "8"} "8"]
[:option {:value "9"} "9"]
[:option {:value "10"} "10"]
[:option {:value "11"} "11"]
[:option {:value "12"} "12"]
[:option {:value "14"} "14"]
[:option {:value "18"} "18"]
[:option {:value "24"} "24"]
[:option {:value "36"} "36"]
[:option {:value "48"} "48"]
[:option {:value "72"} "72"]]
[:input.input-text {:type "number"
:min "0"
:max "200"
:value (-> font-size
(math/precision 2)
(d/coalesce-str "0"))
:on-change on-font-size-change}]]
[:& vertical-align-options {:editor editor}]
[:& box-sizing-options {:editor editor}]]
[:select.input-select {:value (pr-str [font-weight font-style])
:on-change on-font-style-change}
(for [style styles
:let [data (mapv #(get style %) [:weight :style])]]
[:option {:value (pr-str data)
:key (:name style)}
(:name style)])]]
[:div.row-flex.align-icons
[:span.tooltip.tooltip-bottom
{:alt "Align left"
:class (when (= text-align "left") "current")
:on-click #(on-font-align-change % "left")}
i/text-align-left]
[:span.tooltip.tooltip-bottom
{:alt "Align center"
:class (when (= text-align "center") "current")
:on-click #(on-font-align-change % "center")}
i/text-align-center]
[:span.tooltip.tooltip-bottom
{:alt "Align right"
:class (when (= text-align "right") "current")
:on-click #(on-font-align-change % "right")}
i/text-align-right]
[:span.tooltip.tooltip-bottom
{:alt "Justify"
:class (when (= text-align "justify") "current")
:on-click #(on-font-align-change % "justify")}
i/text-align-justify]]
[:div.row-flex
[:div.input-icon
[:span.icon-before.tooltip.tooltip-bottom
{:alt "Line height"}
i/line-height]
[:input.input-text {:type "number"
:step "0.1"
:min "0"
:max "200"
:value (-> line-height
(math/precision 2)
(d/coalesce-str "0"))
:on-change on-font-line-height-change}]]
[:div.input-icon
[:span.icon-before.tooltip.tooltip-bottom
{:alt "Letter spacing"}
i/letter-spacing]
[:input.input-text {:type "number"
:step "0.1"
:min "0"
:max "200"
:value (-> letter-spacing
(math/precision 2)
(d/coalesce-str "0"))
:on-change on-font-letter-spacing-change}]]]
[:div.row-flex
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt "Align top"}
i/align-top]
[:span.tooltip.tooltip-bottom
{:alt "Align middle"}
i/align-middle]
[:span.tooltip.tooltip-bottom
{:alt "Align bottom"}
i/align-bottom]]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt "Auto height"}
i/auto-height]
[:span.tooltip.tooltip-bottom
{:alt "Auto width"}
i/auto-width]
[:span.tooltip.tooltip-bottom
{:alt "Fixed size"}
i/auto-fix]]]
[:div.row-flex
[:span.element-set-subtitle "Decoration"]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt "None"}
i/minus]
[:span.tooltip.tooltip-bottom
{:alt "Underline"}
i/underline]
[:span.tooltip.tooltip-bottom
{:alt "Strikethrough"}
i/strikethrough]]]
[:div.row-flex
[:span.element-set-subtitle "Case"]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt "None"}
i/minus]
[:span.tooltip.tooltip-bottom
{:alt "Uppercase"}
i/uppercase]
[:span.tooltip.tooltip-bottom
{:alt "Lowercase"}
i/lowercase]
[:span.tooltip.tooltip-bottom
{:alt "Titlecase"}
i/titlecase]]]
]]))
(def +fonts+
[{:id "sourcesanspro"
:name "Source Sans Pro"
:styles [{:name "Extra-Light"
:weight "100"
:style "normal"}
{:name "Extra-Light Italic"
:weight "100"
:style "italic"}
{:name "Light"
:weight "200"
:style "normal"}
{:name "Light Italic"
:weight "200"
:style "italic"}
{:name "Regular"
:weight "normal"
:style "normal"}
{:name "Italic"
:weight "normal"
:style "italic"}
{:name "Semi-Bold"
:weight "500"
:style "normal"}
{:name "Semi-Bold Italic"
:weight "500"
:style "italic"}
{:name "Bold"
:weight "bold"
:style "normal"}
{:name "Bold Italic"
:weight "bold"
:style "italic"}
{:name "Black"
:weight "900"
:style "normal"}
{:name "Black Italic"
:weight "900"
:style "italic"}]}
{:id "opensans"
:name "Open Sans"
:styles [{:name "Extra-Light"
:weight "100"
:style "normal"}
{:name "Extra-Light Italic"
:weight "100"
:style "italic"}
{:name "Light"
:weight "200"
:style "normal"}
{:name "Light Italic"
:weight "200"
:style "italic"}
{:name "Regular"
:weight "normal"
:style "normal"}
{:name "Italic"
:weight "normal"
:style "italic"}
{:name "Semi-Bold"
:weight "500"
:style "normal"}
{:name "Semi-Bold Italic"
:weight "500"
:style "italic"}
{:name "Bold"
:weight "bold"
:style "normal"}
{:name "Bold Italic"
:weight "bold"
:style "italic"}
{:name "Black"
:weight "900"
:style "normal"}
{:name "Black Italic"
:weight "900"
:style "italic"}]}
{:id "bebas"
:name "Bebas"
:styles [{:name "Normal"
:weight "normal"
:style "normal"}]}
{:id "gooddog"
:name "Good Dog"
:styles [{:name "Normal"
:weight "normal"
:style "normal"}]}
{:id "caviardreams"
:name "Caviar Dreams"
:styles [{:name "Normal"
:weight "normal"
:style "normal"}
{:name "Normal Italic"
:weight "normal"
:style "italic"}
{:name "Bold"
:weight "bold"
:style "normal"}
{:name "Bold Italic"
:weight "bold"
:style "italic"}]}
{:id "ptsans"
:name "PT Sans"
:styles [{:name "Normal"
:weight "normal"
:style "normal"}
{:name "Normal Italic"
:weight "normal"
:style "italic"}
{:name "Bold"
:weight "bold"
:style "normal"}
{:name "Bold Italic"
:weight "bold"
:style "italic"}]}
{:id "roboto"
:name "Roboto"
:styles [{:name "Extra-Light"
:weight "100"
:style "normal"}
{:name "Extra-Light Italic"
:weight "100"
:style "italic"}
{:name "Light"
:weight "200"
:style "normal"}
{:name "Light Italic"
:weight "200"
:style "italic"}
{:name "Regular"
:weight "normal"
:style "normal"}
{:name "Italic"
:weight "normal"
:style "italic"}
{:name "Semi-Bold"
:weight "500"
:style "normal"}
{:name "Semi-Bold Italic"
:weight "500"
:style "italic"}
{:name "Bold"
:weight "bold"
:style "normal"}
{:name "Bold Italic"
:weight "bold"
:style "italic"}
{:name "Black"
:weight "900"
:style "normal"}
{:name "Black Italic"
:weight "900"
:style "italic"}]}
{:id "robotocondensed"
:name "Roboto Condensed"
:styles [{:name "Extra-Light"
:weight "100"
:style "normal"}
{:name "Extra-Light Italic"
:weight "100"
:style "italic"}
{:name "Light"
:weight "200"
:style "normal"}
{:name "Light Italic"
:weight "200"
:style "italic"}
{:name "Regular"
:weight "normal"
:style "normal"}
{:name "Italic"
:weight "normal"
:style "italic"}
{:name "Semi-Bold"
:weight "500"
:style "normal"}
{:name "Semi-Bold Italic"
:weight "500"
:style "italic"}
{:name "Bold"
:weight "bold"
:style "normal"}
{:name "Bold Italic"
:weight "bold"
:style "italic"}
{:name "Black"
:weight "900"
:style "normal"}
{:name "Black Italic"
:weight "900"
:style "italic"}]}
])
[:& text-decoration-options {:editor editor}]
[:& text-transform-options {:editor editor}]]]))
(mf/defc options
[{:keys [shape] :as props}]
[:div
[:& measures-menu {:shape shape}]
[:& fonts-menu {:shape shape}]])
[:& text-menu {:shape shape}]])