mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ Add numeric token type (#6575)
* ✨ Add numeric type token * 🐛 Fix comments
This commit is contained in:
@@ -29,6 +29,16 @@
|
||||
:name "Rect1"}
|
||||
params)))
|
||||
|
||||
(defn add-text
|
||||
[file text-label content & {:keys [text-params] :as text}]
|
||||
(let [shape (-> (cts/setup-shape {:type :text :x 0 :y 0})
|
||||
(txt/change-text content))]
|
||||
(ths/add-sample-shape file text-label
|
||||
(merge shape
|
||||
text-params))))
|
||||
|
||||
|
||||
|
||||
(defn add-frame
|
||||
[file frame-label & {:keys [] :as params}]
|
||||
;; Generated shape tree:
|
||||
|
||||
@@ -169,6 +169,13 @@
|
||||
item)))
|
||||
root)))
|
||||
|
||||
(defn update-text-content
|
||||
[shape pred-fn update-fn attrs]
|
||||
(let [update-attrs-fn #(update-fn % attrs)
|
||||
transform #(transform-nodes pred-fn update-attrs-fn %)]
|
||||
(-> shape
|
||||
(update :content transform))))
|
||||
|
||||
(defn generate-shape-name
|
||||
[text]
|
||||
(subs text 0 (min 280 (count text))))
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
[:blur {:optional true} ::ctsb/blur]
|
||||
[:grow-type {:optional true}
|
||||
[::sm/one-of grow-types]]
|
||||
[:applied-tokens {:optional true} ::cto/applied-tokens]
|
||||
[:applied-tokens {:optional true} cto/schema:applied-tokens]
|
||||
[:plugin-data {:optional true} ::ctpg/plugin-data]])
|
||||
|
||||
(def schema:group-attrs
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.registry :as sr]
|
||||
[clojure.data :as data]
|
||||
[clojure.set :as set]
|
||||
[malli.util :as mu]))
|
||||
@@ -17,16 +16,10 @@
|
||||
;; HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn merge-schemas
|
||||
"Merge registered schemas."
|
||||
[& schema-keys]
|
||||
(let [schemas (map #(get @sr/registry %) schema-keys)]
|
||||
(reduce sm/merge schemas)))
|
||||
|
||||
(defn schema-keys
|
||||
(defn- schema-keys
|
||||
"Converts registed map schema into set of keys."
|
||||
[registered-schema]
|
||||
(->> (get @sr/registry registered-schema)
|
||||
[schema]
|
||||
(->> schema
|
||||
(sm/schema)
|
||||
(mu/keys)
|
||||
(into #{})))
|
||||
@@ -40,7 +33,7 @@
|
||||
:border-radius "borderRadius"
|
||||
:color "color"
|
||||
:dimensions "dimension"
|
||||
:numeric "numeric"
|
||||
:number "number"
|
||||
:opacity "opacity"
|
||||
:other "other"
|
||||
:rotation "rotation"
|
||||
@@ -55,95 +48,86 @@
|
||||
(def token-types
|
||||
(into #{} (keys token-type->dtcg-token-type)))
|
||||
|
||||
(defn valid-token-type?
|
||||
[t]
|
||||
(token-types t))
|
||||
|
||||
(def token-name-ref
|
||||
[:and :string [:re #"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?<!\.)$"]])
|
||||
|
||||
(defn valid-token-name-ref?
|
||||
[n]
|
||||
(string? n))
|
||||
(def ^:private schema:color
|
||||
[:map
|
||||
[:fill {:optional true} token-name-ref]
|
||||
[:stroke-color {:optional true} token-name-ref]])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::color}
|
||||
[:map
|
||||
[:fill {:optional true} token-name-ref]
|
||||
[:stroke-color {:optional true} token-name-ref]])
|
||||
(def color-keys (schema-keys schema:color))
|
||||
|
||||
(def color-keys (schema-keys ::color))
|
||||
(def ^:private schema:border-radius
|
||||
[:map
|
||||
[:r1 {:optional true} token-name-ref]
|
||||
[:r2 {:optional true} token-name-ref]
|
||||
[:r3 {:optional true} token-name-ref]
|
||||
[:r4 {:optional true} token-name-ref]])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::border-radius}
|
||||
[:map
|
||||
[:r1 {:optional true} token-name-ref]
|
||||
[:r2 {:optional true} token-name-ref]
|
||||
[:r3 {:optional true} token-name-ref]
|
||||
[:r4 {:optional true} token-name-ref]])
|
||||
(def border-radius-keys (schema-keys schema:border-radius))
|
||||
|
||||
(def border-radius-keys (schema-keys ::border-radius))
|
||||
(def ^:private schema:stroke-width
|
||||
[:map
|
||||
[:stroke-width {:optional true} token-name-ref]])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::stroke-width}
|
||||
[:map
|
||||
[:stroke-width {:optional true} token-name-ref]])
|
||||
(def stroke-width-keys (schema-keys schema:stroke-width))
|
||||
|
||||
(def stroke-width-keys (schema-keys ::stroke-width))
|
||||
(def ^:private schema:sizing
|
||||
[:map
|
||||
[:width {:optional true} token-name-ref]
|
||||
[:height {:optional true} token-name-ref]
|
||||
[:layout-item-min-w {:optional true} token-name-ref]
|
||||
[:layout-item-max-w {:optional true} token-name-ref]
|
||||
[:layout-item-min-h {:optional true} token-name-ref]
|
||||
[:layout-item-max-h {:optional true} token-name-ref]])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::sizing}
|
||||
[:map
|
||||
[:width {:optional true} token-name-ref]
|
||||
[:height {:optional true} token-name-ref]
|
||||
[:layout-item-min-w {:optional true} token-name-ref]
|
||||
[:layout-item-max-w {:optional true} token-name-ref]
|
||||
[:layout-item-min-h {:optional true} token-name-ref]
|
||||
[:layout-item-max-h {:optional true} token-name-ref]])
|
||||
(def sizing-keys (schema-keys schema:sizing))
|
||||
|
||||
(def sizing-keys (schema-keys ::sizing))
|
||||
(def ^:private schema:opacity
|
||||
[:map
|
||||
[:opacity {:optional true} token-name-ref]])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::opacity}
|
||||
[:map
|
||||
[:opacity {:optional true} token-name-ref]])
|
||||
(def opacity-keys (schema-keys schema:opacity))
|
||||
|
||||
(def opacity-keys (schema-keys ::opacity))
|
||||
(def ^:private schema:spacing
|
||||
[:map
|
||||
[:row-gap {:optional true} token-name-ref]
|
||||
[:column-gap {:optional true} token-name-ref]
|
||||
[:p1 {:optional true} token-name-ref]
|
||||
[:p2 {:optional true} token-name-ref]
|
||||
[:p3 {:optional true} token-name-ref]
|
||||
[:p4 {:optional true} token-name-ref]
|
||||
[:m1 {:optional true} token-name-ref]
|
||||
[:m2 {:optional true} token-name-ref]
|
||||
[:m3 {:optional true} token-name-ref]
|
||||
[:m4 {:optional true} token-name-ref]
|
||||
[:x {:optional true} token-name-ref]
|
||||
[:y {:optional true} token-name-ref]])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::spacing}
|
||||
[:map
|
||||
[:row-gap {:optional true} token-name-ref]
|
||||
[:column-gap {:optional true} token-name-ref]
|
||||
[:p1 {:optional true} token-name-ref]
|
||||
[:p2 {:optional true} token-name-ref]
|
||||
[:p3 {:optional true} token-name-ref]
|
||||
[:p4 {:optional true} token-name-ref]
|
||||
[:m1 {:optional true} token-name-ref]
|
||||
[:m2 {:optional true} token-name-ref]
|
||||
[:m3 {:optional true} token-name-ref]
|
||||
[:m4 {:optional true} token-name-ref]
|
||||
[:x {:optional true} token-name-ref]
|
||||
[:y {:optional true} token-name-ref]])
|
||||
(def spacing-keys (schema-keys schema:spacing))
|
||||
|
||||
(def spacing-keys (schema-keys ::spacing))
|
||||
(def ^:private schema:dimensions
|
||||
[:merge
|
||||
schema:sizing
|
||||
schema:spacing
|
||||
schema:stroke-width
|
||||
schema:border-radius])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::dimensions}
|
||||
[:merge
|
||||
::sizing
|
||||
::spacing
|
||||
::stroke-width
|
||||
::border-radius])
|
||||
(def dimensions-keys (schema-keys schema:dimensions))
|
||||
|
||||
(def dimensions-keys (schema-keys ::dimensions))
|
||||
(def ^:private schema:rotation
|
||||
[:map
|
||||
[:rotation {:optional true} token-name-ref]])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::rotation}
|
||||
[:map
|
||||
[:rotation {:optional true} token-name-ref]])
|
||||
(def rotation-keys (schema-keys schema:rotation))
|
||||
|
||||
(def rotation-keys (schema-keys ::rotation))
|
||||
(def ^:private schema:number
|
||||
[:map
|
||||
[:rotation {:optional true} token-name-ref]
|
||||
[:line-height {:optional true} token-name-ref]])
|
||||
|
||||
(def number-keys (schema-keys schema:number))
|
||||
|
||||
(def all-keys (set/union color-keys
|
||||
border-radius-keys
|
||||
@@ -152,21 +136,21 @@
|
||||
opacity-keys
|
||||
spacing-keys
|
||||
dimensions-keys
|
||||
rotation-keys))
|
||||
rotation-keys
|
||||
number-keys))
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::tokens}
|
||||
[:map {:title "Applied Tokens"}])
|
||||
(def ^:private schema:tokens
|
||||
[:map {:title "Applied Tokens"}])
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::applied-tokens}
|
||||
[:merge
|
||||
::tokens
|
||||
::border-radius
|
||||
::sizing
|
||||
::spacing
|
||||
::rotation
|
||||
::dimensions])
|
||||
(def schema:applied-tokens
|
||||
[:merge
|
||||
schema:tokens
|
||||
schema:border-radius
|
||||
schema:sizing
|
||||
schema:spacing
|
||||
schema:rotation
|
||||
schema:number
|
||||
schema:dimensions])
|
||||
|
||||
(defn shape-attr->token-attrs
|
||||
([shape-attr] (shape-attr->token-attrs shape-attr nil))
|
||||
@@ -197,7 +181,8 @@
|
||||
(sizing-keys shape-attr) #{shape-attr}
|
||||
(opacity-keys shape-attr) #{shape-attr}
|
||||
(spacing-keys shape-attr) #{shape-attr}
|
||||
(rotation-keys shape-attr) #{shape-attr})))
|
||||
(rotation-keys shape-attr) #{shape-attr}
|
||||
(number-keys shape-attr) #{shape-attr})))
|
||||
|
||||
(defn token-attr->shape-attr
|
||||
[token-attr]
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
:type :boolean
|
||||
:value true)
|
||||
token2 (ctob/make-token :name "test-token-2"
|
||||
:type :numeric
|
||||
:type :number
|
||||
:value 66
|
||||
:description "test description"
|
||||
:modified-at now)]
|
||||
@@ -42,7 +42,7 @@
|
||||
(t/is (ctob/check-token token1))
|
||||
|
||||
(t/is (= (:name token2) "test-token-2"))
|
||||
(t/is (= (:type token2) :numeric))
|
||||
(t/is (= (:type token2) :number))
|
||||
(t/is (= (:value token2) 66))
|
||||
(t/is (= (:description token2) "test description"))
|
||||
(t/is (= (:modified-at token2) now))
|
||||
|
||||
3
frontend/resources/images/icons/number.svg
Normal file
3
frontend/resources/images/icons/number.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 16 16">
|
||||
<path d="M2 4.5h1v7m0 0H2m1 0h1m1-7h4V8H5.5v3.5h4m1-7H14V8m0 0h-3m3 0v3.5h-3.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 199 B |
@@ -55,8 +55,45 @@
|
||||
{:value value :unit (tinycolor/color-format tc)}
|
||||
{:errors [(wte/error-with-value :error.token/invalid-color value)]}))
|
||||
|
||||
(defn- parse-sd-token-numeric-value
|
||||
"Parses `value` of a numeric `sd-token` into a map like `{:value 1 :unit \"px\"}`.
|
||||
(defn- numeric-string? [s]
|
||||
(and (string? s)
|
||||
(re-matches #"^-?\d+(\.\d+)?$" s)))
|
||||
|
||||
(defn- with-units [s]
|
||||
(and (string? s)
|
||||
(re-matches #"^-?\d+(\.\d+)?(px|rem)$" s)))
|
||||
|
||||
;; TODO: After mergin "dimension-tokens" revisit this function to check if it's still
|
||||
(defn- parse-sd-token-number-value
|
||||
"Parses `value` of a number `sd-token` into a map like `{:value 1 :unit \"px\"}`.
|
||||
If the `value` is not parseable and/or has missing references returns a map with `:errors`."
|
||||
[value]
|
||||
(let [number? (or (number? value)
|
||||
(numeric-string? value))
|
||||
parsed-value (cft/parse-token-value value)
|
||||
out-of-bounds (or (>= (:value parsed-value) sm/max-safe-int)
|
||||
(<= (:value parsed-value) sm/min-safe-int))]
|
||||
|
||||
(cond
|
||||
(and parsed-value (not out-of-bounds) number?)
|
||||
parsed-value
|
||||
|
||||
out-of-bounds
|
||||
{:errors [(wte/error-with-value :error.token/number-too-large value)]}
|
||||
|
||||
(seq (ctob/find-token-value-references value))
|
||||
(let [references (seq (ctob/find-token-value-references value))]
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)]
|
||||
:references references})
|
||||
|
||||
(with-units value)
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/value-with-units value)]}
|
||||
|
||||
:else
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]})))
|
||||
|
||||
(defn- parse-sd-token-general-value
|
||||
"Parses `value` of a number `sd-token` into a map like `{:value 1 :unit \"px\"}`.
|
||||
If the `value` is not parseable and/or has missing references returns a map with `:errors`."
|
||||
[value]
|
||||
(let [parsed-value (cft/parse-token-value value)
|
||||
@@ -162,7 +199,8 @@
|
||||
:color (parse-sd-token-color-value value)
|
||||
:opacity (parse-sd-token-opacity-value value has-references?)
|
||||
:stroke-width (parse-sd-token-stroke-width-value value has-references?)
|
||||
(parse-sd-token-numeric-value value))
|
||||
:number (parse-sd-token-number-value value)
|
||||
(parse-sd-token-general-value value))
|
||||
output-token (cond (:errors parsed-token-value)
|
||||
(merge origin-token parsed-token-value)
|
||||
|
||||
|
||||
@@ -398,13 +398,6 @@
|
||||
|
||||
(rx/of (dwsh/update-shapes shape-ids update-fn))))))
|
||||
|
||||
(defn- update-text-content
|
||||
[shape pred-fn update-fn attrs]
|
||||
(let [update-attrs-fn #(update-fn % attrs)
|
||||
transform #(txt/transform-nodes pred-fn update-attrs-fn %)]
|
||||
(-> shape
|
||||
(update :content transform))))
|
||||
|
||||
(defn update-root-attrs
|
||||
[{:keys [id attrs]}]
|
||||
(ptk/reify ::update-root-attrs
|
||||
@@ -416,7 +409,7 @@
|
||||
update-fn
|
||||
(fn [shape]
|
||||
(if (some? (:content shape))
|
||||
(update-text-content shape txt/is-root-node? d/txt-merge attrs)
|
||||
(txt/update-text-content shape txt/is-root-node? d/txt-merge attrs)
|
||||
(assoc shape :content (d/txt-merge {:type "root"} attrs))))
|
||||
|
||||
shape-ids (cond (cfh/text-shape? shape) [id]
|
||||
@@ -444,7 +437,7 @@
|
||||
node
|
||||
attrs))
|
||||
|
||||
update-fn #(update-text-content % txt/is-paragraph-node? merge-fn attrs)
|
||||
update-fn #(txt/update-text-content % txt/is-paragraph-node? merge-fn attrs)
|
||||
shape-ids (cond
|
||||
(cfh/text-shape? shape) [id]
|
||||
(cfh/group-shape? shape) (cfh/get-children-ids objects id))]
|
||||
@@ -469,7 +462,7 @@
|
||||
shape-ids (cond
|
||||
(cfh/text-shape? shape) [id]
|
||||
(cfh/group-shape? shape) (cfh/get-children-ids objects id))]
|
||||
(rx/of (dwsh/update-shapes shape-ids #(update-text-content % update-node? d/txt-merge attrs))))))))
|
||||
(rx/of (dwsh/update-shapes shape-ids #(txt/update-text-content % update-node? d/txt-merge attrs))))))))
|
||||
|
||||
(defn migrate-node
|
||||
[node]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.token :as ctt]
|
||||
@@ -42,34 +43,36 @@
|
||||
(ptk/reify ::apply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-let [tokens (some-> (dsh/lookup-file-data state)
|
||||
(get :tokens-lib)
|
||||
(ctob/get-active-themes-set-tokens))]
|
||||
(->> (sd/resolve-tokens tokens)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [undo-id (js/Symbol)
|
||||
objects (dsh/lookup-page-objects state)
|
||||
;; We do not allow to apply tokens while text editor is open.
|
||||
(when (empty? (get state :workspace-editor-state))
|
||||
(when-let [tokens (some-> (dsh/lookup-file-data state)
|
||||
(get :tokens-lib)
|
||||
(ctob/get-active-themes-set-tokens))]
|
||||
(->> (sd/resolve-tokens tokens)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [undo-id (js/Symbol)
|
||||
objects (dsh/lookup-page-objects state)
|
||||
|
||||
shape-ids (or (->> (select-keys objects shape-ids)
|
||||
(filter (fn [[_ shape]] (not= (:type shape) :group)))
|
||||
(keys))
|
||||
[])
|
||||
shape-ids (or (->> (select-keys objects shape-ids)
|
||||
(filter (fn [[_ shape]] (not= (:type shape) :group)))
|
||||
(keys))
|
||||
[])
|
||||
|
||||
resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value])
|
||||
tokenized-attributes (cft/attributes-map attributes token)]
|
||||
(rx/of
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "apply-tokens"}))
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dwsh/update-shapes shape-ids (fn [shape]
|
||||
(cond-> shape
|
||||
attributes-to-remove
|
||||
(update :applied-tokens #(apply (partial dissoc %) attributes-to-remove))
|
||||
:always
|
||||
(update :applied-tokens merge tokenized-attributes))))
|
||||
(when on-update-shape
|
||||
(on-update-shape resolved-value shape-ids attributes))
|
||||
(dwu/commit-undo-transaction undo-id))))))))))
|
||||
resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value])
|
||||
tokenized-attributes (cft/attributes-map attributes token)]
|
||||
(rx/of
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "apply-tokens"}))
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dwsh/update-shapes shape-ids (fn [shape]
|
||||
(cond-> shape
|
||||
attributes-to-remove
|
||||
(update :applied-tokens #(apply (partial dissoc %) attributes-to-remove))
|
||||
:always
|
||||
(update :applied-tokens merge tokenized-attributes))))
|
||||
(when on-update-shape
|
||||
(on-update-shape resolved-value shape-ids attributes))
|
||||
(dwu/commit-undo-transaction undo-id)))))))))))
|
||||
|
||||
(defn unapply-token
|
||||
"Removes `attributes` that match `token` for `shape-ids`.
|
||||
@@ -328,7 +331,22 @@
|
||||
(dwsl/update-layout-child shape-ids props {:ignore-touched true
|
||||
:page-id page-id}))))))))
|
||||
|
||||
;; Map token types to different properties used along the cokde ---------------------------------------------------------
|
||||
(defn update-line-height
|
||||
([value shape-ids attributes] (update-line-height value shape-ids attributes nil))
|
||||
([value shape-ids _attributes page-id] ; The attributes param is
|
||||
; needed to have the same
|
||||
; arity that other update
|
||||
; functions
|
||||
(let [update-node? (fn [node]
|
||||
(or (txt/is-text-node? node)
|
||||
(txt/is-paragraph-node? node)))]
|
||||
(when (number? value)
|
||||
(dwsh/update-shapes shape-ids
|
||||
#(txt/update-text-content % update-node? d/txt-merge {:line-height value})
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))))
|
||||
|
||||
;; Map token types to different properties used along the cokde ---------------------------------------------
|
||||
|
||||
;; FIXME: the values should be lazy evaluated, probably a function,
|
||||
;; becasue on future we will need to translate that labels and that
|
||||
@@ -390,6 +408,15 @@
|
||||
:fields [{:label "Opacity"
|
||||
:key :opacity}]}}
|
||||
|
||||
:number
|
||||
{:title "Number"
|
||||
:attributes ctt/rotation-keys
|
||||
:all-attributes ctt/number-keys
|
||||
:on-update-shape update-rotation
|
||||
:modal {:key :tokens/number
|
||||
:fields [{:label "Number"
|
||||
:key :number}]}}
|
||||
|
||||
:rotation
|
||||
{:title "Rotation"
|
||||
:attributes ctt/rotation-keys
|
||||
|
||||
@@ -56,6 +56,10 @@
|
||||
{:error/code :error.style-dictionary/invalid-token-value
|
||||
:error/fn #(str (tr "workspace.tokens.invalid-value" %))}
|
||||
|
||||
:error.style-dictionary/value-with-units
|
||||
{:error/code :error.style-dictionary/value-with-units
|
||||
:error/fn #(str (tr "workspace.tokens.value-with-units"))}
|
||||
|
||||
:error.style-dictionary/invalid-token-value-opacity
|
||||
{:error/code :error.style-dictionary/invalid-token-value-opacity
|
||||
:error/fn #(str/join "\n" [(str (tr "workspace.tokens.invalid-value" %) ".") (tr "workspace.tokens.opacity-range")])}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
ctt/stroke-width-keys dwta/update-stroke-width
|
||||
ctt/sizing-keys dwta/update-shape-dimensions
|
||||
ctt/opacity-keys dwta/update-opacity
|
||||
#{:line-height} dwta/update-line-height
|
||||
#{:x :y} dwta/update-shape-position
|
||||
#{:p1 :p2 :p3 :p4} dwta/update-layout-padding
|
||||
#{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin
|
||||
@@ -125,6 +126,7 @@
|
||||
(defn- actionize-shapes-update-info [page-id shapes-update-info]
|
||||
(mapcat (fn [[attrs update-infos]]
|
||||
(let [action (some attribute-actions-map attrs)]
|
||||
(assert (fn? action) "missing action function on attributes->shape-update")
|
||||
(map
|
||||
(fn [[v shape-ids]]
|
||||
(action v shape-ids attrs page-id))
|
||||
|
||||
@@ -12,3 +12,4 @@ $br-4: px2rem(4);
|
||||
$br-circle: 50%;
|
||||
|
||||
$b-1: px2rem(1);
|
||||
$b-2: px2rem(2);
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
(def ^:icon-id msg-neutral "msg-neutral")
|
||||
(def ^:icon-id msg-success "msg-success")
|
||||
(def ^:icon-id msg-warning "msg-warning")
|
||||
(def ^:icon-id number "number")
|
||||
(def ^:icon-id open-link "open-link")
|
||||
(def ^:icon-id padding-bottom "padding-bottom")
|
||||
(def ^:icon-id padding-extended "padding-extended")
|
||||
|
||||
@@ -182,6 +182,7 @@
|
||||
(def ^:icon msg-neutral (icon-xref :msg-neutral))
|
||||
(def ^:icon msg-success (icon-xref :msg-success))
|
||||
(def ^:icon msg-warning (icon-xref :msg-warning))
|
||||
(def ^:icon number (icon-xref :number))
|
||||
(def ^:icon open-link (icon-xref :open-link))
|
||||
(def ^:icon oauth-1 (icon-xref :oauth-1))
|
||||
(def ^:icon oauth-2 (icon-xref :oauth-2))
|
||||
|
||||
@@ -37,10 +37,14 @@
|
||||
:selected-pred #(seq (% ids-by-attributes))}))
|
||||
|
||||
(defn generic-attribute-actions [attributes title {:keys [token selected-shapes on-update-shape hint]}]
|
||||
(let [on-update-shape-fn (or on-update-shape
|
||||
(-> (dwta/get-token-properties token)
|
||||
(:on-update-shape)))
|
||||
{:keys [selected-pred shape-ids]} (attribute-actions token selected-shapes attributes)]
|
||||
(let [on-update-shape-fn
|
||||
(or on-update-shape
|
||||
(-> (dwta/get-token-properties token)
|
||||
(:on-update-shape)))
|
||||
|
||||
{:keys [selected-pred shape-ids]}
|
||||
(attribute-actions token selected-shapes attributes)]
|
||||
|
||||
(map (fn [attribute]
|
||||
(let [selected? (selected-pred attribute)
|
||||
props {:attributes #{attribute}
|
||||
@@ -244,6 +248,9 @@
|
||||
:sizing sizing-attribute-actions
|
||||
:rotation (partial generic-attribute-actions #{:rotation} "Rotation")
|
||||
:opacity (partial generic-attribute-actions #{:opacity} "Opacity")
|
||||
:number (fn [context-data]
|
||||
[(generic-attribute-actions #{:rotation} "Rotation" (assoc context-data :on-update-shape dwta/update-rotation))
|
||||
(generic-attribute-actions #{:line-height} "Line Height" (assoc context-data :on-update-shape dwta/update-line-height))])
|
||||
:stroke-width stroke-width
|
||||
:dimensions (fn [context-data]
|
||||
(concat
|
||||
@@ -371,9 +378,12 @@
|
||||
|
||||
(mf/defc menu-tree
|
||||
[{:keys [selected-shapes submenu-offset type errors] :as context-data}]
|
||||
(let [shape-types (into #{} (map :type selected-shapes))
|
||||
(let [shape-types (into #{} (map :type selected-shapes))
|
||||
editing-ref (mf/deref refs/workspace-editor-state)
|
||||
not-editing? (empty? editing-ref)
|
||||
entries (if (and (not (some? errors))
|
||||
(seq selected-shapes)
|
||||
not-editing?
|
||||
(not= shape-types #{:group}))
|
||||
(if (some? type)
|
||||
(submenu-actions-selection-actions context-data)
|
||||
|
||||
@@ -138,9 +138,9 @@
|
||||
[properties]
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc numeric-modal
|
||||
(mf/defc number-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/numeric}
|
||||
::mf/register-as :tokens/number}
|
||||
[properties]
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
:color "drop"
|
||||
:boolean "boolean-difference"
|
||||
:opacity "percentage"
|
||||
:number "number"
|
||||
:rotation "rotation"
|
||||
:spacing "padding-extended"
|
||||
:string "text-mixed"
|
||||
@@ -70,6 +71,8 @@
|
||||
[{:keys [type tokens selected-shapes active-theme-tokens is-open]}]
|
||||
(let [{:keys [modal title]}
|
||||
(get dwta/token-properties type)
|
||||
editing-ref (mf/deref refs/workspace-editor-state)
|
||||
not-editing? (empty? editing-ref)
|
||||
|
||||
can-edit?
|
||||
(mf/use-ctx ctx/can-edit?)
|
||||
@@ -111,10 +114,10 @@
|
||||
|
||||
on-token-pill-click
|
||||
(mf/use-fn
|
||||
(mf/deps selected-shapes)
|
||||
(mf/deps selected-shapes not-editing?)
|
||||
(fn [event token]
|
||||
(dom/stop-propagation event)
|
||||
(when (seq selected-shapes)
|
||||
(when (and not-editing? (seq selected-shapes))
|
||||
(st/emit! (dwta/toggle-token {:token token
|
||||
:shapes selected-shapes})))))]
|
||||
|
||||
@@ -129,8 +132,7 @@
|
||||
[:> icon-button* {:on-click on-popover-open-click
|
||||
:variant "ghost"
|
||||
:icon "add"
|
||||
;; TODO: This needs translation
|
||||
:aria-label (str "Add token: " title)}])]
|
||||
:aria-label (tr "workspace.tokens.add-token" title)}])]
|
||||
(when is-open
|
||||
[:& cmm/asset-section-block {:role :content}
|
||||
[:div {:class (stl/css :token-pills-wrapper)}
|
||||
@@ -145,22 +147,27 @@
|
||||
|
||||
(defn- get-sorted-token-groups
|
||||
"Separate token-types into groups of `empty` or `filled` depending if
|
||||
tokens exist for that type. Sort each group alphabetically (by
|
||||
their type)."
|
||||
tokens exist for that type. Sort each group alphabetically (by their type).
|
||||
If `:token-units` is not in cf/flags, number tokens are excluded."
|
||||
[tokens-by-type]
|
||||
(loop [empty #js []
|
||||
filled #js []
|
||||
types (-> dwta/token-properties keys seq)]
|
||||
(if-let [type (first types)]
|
||||
(if (not-empty (get tokens-by-type type))
|
||||
(recur empty
|
||||
(array/conj! filled type)
|
||||
(rest types))
|
||||
(recur (array/conj! empty type)
|
||||
filled
|
||||
(rest types)))
|
||||
[(seq (array/sort! empty))
|
||||
(seq (array/sort! filled))])))
|
||||
(let [all-types (-> dwta/token-properties keys seq)
|
||||
token-units? (contains? cf/flags :token-units)
|
||||
filtered-types (if token-units?
|
||||
all-types
|
||||
(remove #(= % :number) all-types))]
|
||||
(loop [empty #js []
|
||||
filled #js []
|
||||
types filtered-types]
|
||||
(if-let [type (first types)]
|
||||
(if (not-empty (get tokens-by-type type))
|
||||
(recur empty
|
||||
(array/conj! filled type)
|
||||
(rest types))
|
||||
(recur (array/conj! empty type)
|
||||
filled
|
||||
(rest types)))
|
||||
[(seq (array/sort! empty))
|
||||
(seq (array/sort! filled))]))))
|
||||
|
||||
(mf/defc themes-header*
|
||||
{::mf/private true}
|
||||
|
||||
@@ -165,14 +165,14 @@
|
||||
(mf/defc token-pill*
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [on-click token on-context-menu selected-shapes active-theme-tokens]}]
|
||||
(let [{:keys [name value errors]} token
|
||||
(let [{:keys [name value errors type]} token
|
||||
|
||||
has-selected? (pos? (count selected-shapes))
|
||||
is-reference? (cft/is-reference? token)
|
||||
contains-path? (str/includes? name ".")
|
||||
|
||||
{:keys [attributes all-attributes]}
|
||||
(get dwta/token-properties (:type token))
|
||||
(get dwta/token-properties type)
|
||||
|
||||
full-applied?
|
||||
(if has-selected?
|
||||
@@ -204,10 +204,12 @@
|
||||
|
||||
color
|
||||
(when (cft/color-token? token)
|
||||
(let [theme-token (get active-theme-tokens (:name token))]
|
||||
(let [theme-token (get active-theme-tokens name)]
|
||||
(or (dwtc/resolved-token-bullet-color theme-token)
|
||||
(dwtc/resolved-token-bullet-color token))))
|
||||
|
||||
number-token (= type :number)
|
||||
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(mf/deps errors? on-click token)
|
||||
@@ -240,18 +242,20 @@
|
||||
(dom/stop-propagation event)
|
||||
(when (and can-edit? (not (seq errors)) on-click)
|
||||
(on-click event))))
|
||||
|
||||
on-hover
|
||||
(mf/use-fn
|
||||
(mf/deps selected-shapes is-viewer? active-theme-tokens token half-applied? no-valid-value ref-not-in-active-set)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
theme-token (get active-theme-tokens (:name token))
|
||||
theme-token (get active-theme-tokens name)
|
||||
title (generate-tooltip is-viewer? (first selected-shapes) theme-token token
|
||||
half-applied? no-valid-value ref-not-in-active-set)]
|
||||
(dom/set-attribute! node "title" title))))]
|
||||
|
||||
[:button {:class (stl/css-case
|
||||
:token-pill true
|
||||
:token-pill-no-icon (and number-token (not errors?))
|
||||
:token-pill-default can-edit?
|
||||
:token-pill-applied (and can-edit? has-selected? (or half-applied? full-applied?))
|
||||
:token-pill-invalid (and can-edit? errors?)
|
||||
@@ -276,13 +280,12 @@
|
||||
{:icon-id "broken-link"
|
||||
:class (stl/css :token-pill-icon)}]
|
||||
|
||||
color
|
||||
[:& color-bullet {:color color
|
||||
:mini true}]
|
||||
:else
|
||||
[:> token-status-icon*
|
||||
{:icon-id token-status-id
|
||||
:class (stl/css :token-pill-icon)}])
|
||||
(not number-token)
|
||||
(if color
|
||||
[:& color-bullet {:color color :mini true}]
|
||||
[:> token-status-icon*
|
||||
{:icon-id token-status-id
|
||||
:class (stl/css :token-pill-icon)}]))
|
||||
|
||||
(if contains-path?
|
||||
(let [[first-part last-part] (cfh/split-by-last-period name)]
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "../../ds/typography.scss" as *;
|
||||
@use "../../ds/borders.scss" as *;
|
||||
@import "refactor/common-refactor.scss";
|
||||
|
||||
.token-pill {
|
||||
@@ -16,15 +17,20 @@
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: center;
|
||||
gap: $s-6;
|
||||
border: $s-1 solid var(--token-pill-border);
|
||||
outline: $s-2 solid var(--token-pill-outline);
|
||||
height: $s-24;
|
||||
border: $b-1 solid var(--token-pill-border);
|
||||
outline: $b-2 solid var(--token-pill-outline);
|
||||
height: var(--sp-xxl);
|
||||
border-radius: $br-8;
|
||||
padding: $s-2 $s-8 $s-2 $s-4;
|
||||
padding: var(--sp-xxs) var(--sp-s) var(--sp-xxs) var(--sp-xs);
|
||||
color: var(--token-pill-foreground);
|
||||
background: var(--token-pill-background);
|
||||
}
|
||||
|
||||
.token-pill-no-icon {
|
||||
grid-template-columns: 1fr;
|
||||
padding: var(--sp-xxs) var(--sp-s);
|
||||
}
|
||||
|
||||
.name-wrapper {
|
||||
@include use-typography("code-font");
|
||||
display: block;
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[cuerdas.core :as str]
|
||||
[frontend-tests.helpers.pages :as thp]
|
||||
[frontend-tests.helpers.state :as ths]
|
||||
[frontend-tests.tokens.helpers.state :as tohs]
|
||||
@@ -39,6 +41,7 @@
|
||||
(ctho/add-rect :rect-1 rect-1)
|
||||
(ctho/add-rect :rect-2 rect-2)
|
||||
(ctho/add-rect :rect-3 rect-3)
|
||||
(ctho/add-text :text-1 "Hello World!")
|
||||
(assoc-in [:data :tokens-lib]
|
||||
(-> (ctob/make-tokens-lib)
|
||||
(ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"}))
|
||||
@@ -443,6 +446,40 @@
|
||||
(t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target')))
|
||||
(t/is (empty? (:strokes rect-without-stroke')))))))))))
|
||||
|
||||
(t/deftest test-apply-line-height
|
||||
(t/testing "applies line-height token and updates the text line-height"
|
||||
(t/async
|
||||
done
|
||||
(let [line-height-token {:name "big-height"
|
||||
:value "1.5"
|
||||
:type :number}
|
||||
file (-> (setup-file-with-tokens)
|
||||
(update-in [:data :tokens-lib]
|
||||
#(ctob/add-token-in-set % "Set A" (ctob/make-token line-height-token))))
|
||||
store (ths/setup-store file)
|
||||
text-1 (cths/get-shape file :text-1)
|
||||
events [(dwta/apply-token {:shape-ids [(:id text-1)]
|
||||
:attributes #{:line-height}
|
||||
:token (toht/get-token file "big-height")
|
||||
:on-update-shape dwta/update-line-height})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-state new-state)
|
||||
token-target' (toht/get-token file' "big-height")
|
||||
text-1' (cths/get-shape file' :text-1)
|
||||
style-text-blocks (->> (:content text-1')
|
||||
(txt/content->text+styles)
|
||||
(remove (fn [[_ text]] (str/empty? (str/trim text))))
|
||||
(mapv (fn [[style text]]
|
||||
{:styles (merge txt/default-text-attrs style)
|
||||
:text-content text}))
|
||||
(first)
|
||||
(:styles))]
|
||||
(t/is (some? (:applied-tokens text-1')))
|
||||
(t/is (= (:line-height (:applied-tokens text-1')) (:name token-target')))
|
||||
(t/is (= (:line-height style-text-blocks) 1.5)))))))))
|
||||
|
||||
(t/deftest test-toggle-token-none
|
||||
(t/testing "should apply token to all selected items, where no item has the token applied"
|
||||
(t/async
|
||||
|
||||
@@ -7282,6 +7282,10 @@ msgstr ""
|
||||
msgid "workspace.tokens.invalid-value"
|
||||
msgstr "Invalid token value: %s"
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:57
|
||||
msgid "workspace.tokens.value-with-units"
|
||||
msgstr "Invalid value: Units are not allowed."
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/modals/themes.cljs:190
|
||||
msgid "workspace.tokens.label.group"
|
||||
msgstr "Group"
|
||||
@@ -7497,6 +7501,10 @@ msgstr "The value is not valid"
|
||||
msgid "workspace.tokens.warning-name-change"
|
||||
msgstr "Renaming this token will break any reference to its old name."
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/sidebar.cljs
|
||||
msgid "workspace.tokens.add-token"
|
||||
msgstr "Add token: %s"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar.cljs:137, src/app/main/ui/workspace/sidebar.cljs:146
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Assets"
|
||||
|
||||
@@ -7298,6 +7298,10 @@ msgstr ""
|
||||
msgid "workspace.tokens.invalid-value"
|
||||
msgstr "Valor de token no válido: %s"
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:57
|
||||
msgid "workspace.tokens.value-with-units"
|
||||
msgstr "Valor no válido: No se permiten unidades."
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/modals/themes.cljs:190
|
||||
msgid "workspace.tokens.label.group"
|
||||
msgstr "Grupo"
|
||||
@@ -7476,6 +7480,10 @@ msgstr "La importación se ha realizado correctamente pero se omitieron algunos
|
||||
msgid "workspace.tokens.unknown-token-type-section"
|
||||
msgstr "El tipo '%s' no está soportado (%s)\n"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/sidebar.cljs
|
||||
msgid "workspace.tokens.add-token"
|
||||
msgstr "Añadir token: %s"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar.cljs:137, src/app/main/ui/workspace/sidebar.cljs:146
|
||||
msgid "workspace.toolbar.assets"
|
||||
msgstr "Recursos"
|
||||
|
||||
Reference in New Issue
Block a user