Merge branch 'token-studio-develop' into token-sets-ui

This commit is contained in:
Akshay Gupta 2024-08-20 17:02:00 +05:30
commit 1a3184d327
No known key found for this signature in database
14 changed files with 335 additions and 258 deletions

View file

@ -16,6 +16,7 @@
[app.main.data.workspace.shapes :as dwsh]
[app.main.refs :as refs]
[app.main.ui.workspace.tokens.common :refer [workspace-shapes]]
[app.main.ui.workspace.tokens.token :as wtt]
[beicon.v2.core :as rx]
[clojure.data :as data]
[cuerdas.core :as str]
@ -55,22 +56,22 @@
(first))]
shape))
(defn token-from-attributes [token-id attributes]
(->> (map (fn [attr] [attr token-id]) attributes)
(defn token-from-attributes [token attributes]
(->> (map (fn [attr] [attr (wtt/token-identifier token)]) attributes)
(into {})))
(defn unapply-token-id [shape attributes]
(update shape :applied-tokens d/without-keys attributes))
(defn apply-token-id-to-attributes [{:keys [shape token-id attributes]}]
(let [token (token-from-attributes token-id attributes)]
(defn apply-token-to-attributes [{:keys [shape token attributes]}]
(let [token (token-from-attributes token attributes)]
(toggle-or-apply-token shape token)))
(defn apply-token-to-shape
[{:keys [shape token attributes] :as _props}]
(let [applied-tokens (apply-token-id-to-attributes {:shape shape
:token-id (:id token)
:attributes attributes})]
(let [applied-tokens (apply-token-to-attributes {:shape shape
:token token
:attributes attributes})]
(update shape :applied-tokens #(merge % applied-tokens))))
(defn maybe-apply-token-to-shape
@ -80,17 +81,6 @@
(apply-token-to-shape props)
shape))
(defn update-token-from-attributes
[{:keys [token-id shape-id attributes]}]
(ptk/reify ::update-token-from-attributes
ptk/WatchEvent
(watch [_ state _]
(let [shape (get-shape-from-state shape-id state)
applied-tokens (apply-token-id-to-attributes {:shape shape
:token-id token-id
:attributes attributes})]
(rx/of (update-shape shape-id {:applied-tokens applied-tokens}))))))
(defn get-token-data-from-token-id
[id]
(let [workspace-data (deref refs/workspace-data)]

View file

@ -29,6 +29,8 @@
[app.main.ui.icons :as i]
[app.main.ui.workspace.tokens.core :as wtc]
[app.main.ui.workspace.tokens.editable-select :refer [editable-select]]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.token-types :as wtty]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@ -985,18 +987,25 @@
(mf/use-fn
(mf/deps ids)
(fn [type prop value]
(let [token-value (wtc/maybe-resolve-token-value value)
val (or token-value (mth/finite value 0))]
(let [token-identifier (wtt/token-identifier value)
val (or token-identifier (mth/finite value 0))
on-update-shape wtch/update-layout-padding]
(cond
(and (= type :simple) (= prop :p1))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}
:applied-tokens {:padding-p1 (if token-value (:id value) nil)
:padding-p3 (if token-value (:id value) nil)}}))
(if token-identifier
(st/emit! (wtch/apply-token {:shape-ids ids
:attributes #{:p1 :p3}
:token value
:on-update-shape on-update-shape}))
(st/emit! (on-update-shape value ids #{:p1 :p3})))
(and (= type :simple) (= prop :p2))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}
:applied-tokens {:padding-p2 (if token-value (:id value) nil)
:padding-p4 (if token-value (:id value) nil)}}))
(if token-identifier
(st/emit! (wtch/apply-token {:shape-ids ids
:attributes #{:p2 :p4}
:token value
:on-update-shape on-update-shape}))
(st/emit! (on-update-shape value ids #{:p2 :p4})))
(some? prop)
(st/emit! (dwsl/update-layout ids {:layout-padding {prop val}}))))))

View file

@ -300,7 +300,7 @@
(update-fn shape)
shape))
{:reg-objects? true
:attrs [:rx :ry :r1 :r2 :r3 :r4]})))
:attrs [:rx :ry :r1 :r2 :r3 :r4 :applied-tokens]})))
on-switch-to-radius-1
(mf/use-fn

View file

@ -34,11 +34,10 @@
(watch [_ state _]
(->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens])))
(rx/mapcat
(fn [sd-tokens]
(fn [resolved-tokens]
(let [undo-id (js/Symbol)
resolved-value (-> (get sd-tokens (:id token))
(wtt/resolve-token-value))
tokenized-attributes (wtt/attributes-map attributes (:id token))]
resolved-value (get-in resolved-tokens [(wtt/token-identifier token) :resolved-value])
tokenized-attributes (wtt/attributes-map attributes token)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dwsh/update-shapes shape-ids (fn [shape]
@ -58,7 +57,7 @@
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(let [remove-token #(when % (wtt/remove-attributes-for-token-id attributes (:id token) %))]
(let [remove-token #(when % (wtt/remove-attributes-for-token attributes token %))]
(dwsh/update-shapes
shape-ids
(fn [shape]
@ -137,6 +136,9 @@
(zipmap (repeat value)))]
{:layout-gap layout-gap}))
(defn update-layout-padding [value shape-ids attrs]
(dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat value))}))
(defn update-layout-spacing [value shape-ids attributes]
(ptk/reify ::update-layout-spacing
ptk/WatchEvent
@ -149,15 +151,6 @@
(rx/of
(dwsl/update-layout layout-shape-ids layout-attributes))))))
(defn update-layout-spacing-column [value shape-ids]
(ptk/reify ::update-layout-spacing-column
ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(for [shape-id shape-ids]
(let [layout-update {:layout-gap {:column-gap value :row-gap value}}]
(dwsl/update-layout [shape-id] layout-update)))))))
(defn update-shape-position [value shape-ids attributes]
(ptk/reify ::update-shape-position
ptk/WatchEvent

View file

@ -81,8 +81,7 @@
(concat [all-action] single-actions)))
(defn spacing-attribute-actions [{:keys [token selected-shapes] :as context-data}]
(let [on-update-shape (fn [resolved-value shape-ids attrs]
(dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat resolved-value))}))
(let [on-update-shape-padding wtch/update-layout-padding
padding-attrs {:p1 "Top"
:p2 "Right"
:p3 "Bottom"
@ -105,7 +104,7 @@
:shape-ids shape-ids}]
(if all-selected?
(st/emit! (wtch/unapply-token props))
(st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape))))))}
(st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape-padding))))))}
{:title "Horizontal"
:selected? horizontal-padding-selected?
:action (fn []
@ -116,7 +115,7 @@
horizontal-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove horizontal-attributes))
:else (wtch/apply-token (assoc props
:attributes horizontal-attributes
:on-update-shape on-update-shape)))]
:on-update-shape on-update-shape-padding)))]
(st/emit! event)))}
{:title "Vertical"
:selected? vertical-padding-selected?
@ -128,7 +127,7 @@
vertical-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes))
:else (wtch/apply-token (assoc props
:attributes vertical-attributes
:on-update-shape on-update-shape)))]
:on-update-shape on-update-shape-padding)))]
(st/emit! event)))}]
single-padding-items (->> padding-attrs
(map (fn [[attr title]]
@ -149,7 +148,7 @@
all-selected? (-> (assoc props :attributes-to-remove all-padding-attrs)
(wtch/apply-token))
selected? (wtch/unapply-token props)
:else (-> (assoc props :on-update-shape on-update-shape)
:else (-> (assoc props :on-update-shape on-update-shape-padding)
(wtch/apply-token)))]
(st/emit! event))}))))
gap-items (all-or-sepearate-actions {:attribute-labels {:column-gap "Column Gap"

View file

@ -14,6 +14,7 @@
width: 100%;
padding: $s-8;
border-radius: $br-8;
position: relative;
cursor: pointer;
background: transparent;

View file

@ -8,14 +8,15 @@
(:require-macros [app.main.style :as stl])
(:require
["lodash.debounce" :as debounce]
[app.main.ui.workspace.tokens.update :as wtu]
[app.common.data :as d]
[app.main.data.modal :as modal]
[app.main.data.tokens :as dt]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.workspace.tokens.common :as tokens.common]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.update :as wtu]
[app.util.dom :as dom]
[cuerdas.core :as str]
[malli.core :as m]
@ -92,7 +93,7 @@ Token names should only contain letters and digits separated by . characters.")}
;; When creating a new token we dont have a token name yet,
;; so we use a temporary token name that hopefully doesn't clash with any of the users token names.
token-name (if (str/empty? name-value) "__TOKEN_STUDIO_SYSTEM.TEMP" name-value)
token-references (sd/find-token-references input)
token-references (wtt/find-token-references input)
direct-self-reference? (get token-references token-name)]
(cond
empty-input? (p/rejected nil)
@ -104,7 +105,7 @@ Token names should only contain letters and digits separated by . characters.")}
(-> (sd/resolve-tokens+ new-tokens #_ {:debug? true})
(p/then
(fn [resolved-tokens]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-id)]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-name)]
(cond
resolved-value (p/resolved resolved-token)
(sd/missing-reference-error? errors) (p/rejected :error/token-missing-reference)
@ -141,14 +142,15 @@ Token names should only contain letters and digits separated by . characters.")}
(mf/defc form
{::mf/wrap-props false}
[{:keys [token token-type] :as _args}]
(let [tokens (sd/use-resolved-workspace-tokens)
(let [tokens (mf/deref refs/workspace-tokens)
resolved-tokens (sd/use-resolved-tokens tokens)
token-path (mf/use-memo
(mf/deps (:name token))
#(wtt/token-name->path (:name token)))
tokens-tree (mf/use-memo
(mf/deps token-path tokens)
(mf/deps token-path resolved-tokens)
(fn []
(-> (wtt/token-names-tree tokens)
(-> (wtt/token-names-tree resolved-tokens)
;; Allow setting editing token to it's own path
(d/dissoc-in token-path))))
@ -177,7 +179,7 @@ Token names should only contain letters and digits separated by . characters.")}
;; Value
value-ref (mf/use-var (:value token))
token-resolve-result (mf/use-state (get-in tokens [(:id token) :resolved-value]))
token-resolve-result (mf/use-state (get-in resolved-tokens [(wtt/token-identifier token) :resolved-value]))
set-resolve-value (mf/use-callback
(fn [token-or-err]
(let [v (cond
@ -219,7 +221,7 @@ Token names should only contain letters and digits separated by . characters.")}
(not valid-description-field?))
on-submit (mf/use-callback
(mf/deps validate-name validate-descripion token tokens)
(mf/deps validate-name validate-descripion token resolved-tokens)
(fn [e]
(dom/prevent-default e)
;; We have to re-validate the current form values before submitting
@ -236,7 +238,7 @@ Token names should only contain letters and digits separated by . characters.")}
(validate-token-value+ {:input final-value
:name-value final-name
:token token
:tokens tokens})])
:tokens resolved-tokens})])
(p/finally (fn [result err]
;; The result should be a vector of all resolved validations
;; We do not handle the error case as it will be handled by the components validations

View file

@ -2,18 +2,15 @@
(:require
["@tokens-studio/sd-transforms" :as sd-transforms]
["style-dictionary$default" :as sd]
[app.common.data :as d]
[app.main.refs :as refs]
[app.main.ui.workspace.tokens.token :as wtt]
[cuerdas.core :as str]
[promesa.core :as p]
[rumext.v2 :as mf]
[shadow.resource]))
[rumext.v2 :as mf]))
(def StyleDictionary
"The global StyleDictionary instance used as an external library for now,
as the package would need webpack to be bundled,
because shadow-cljs doesn't support some of the more modern bundler features."
"Initiates the global StyleDictionary instance with transforms
from tokens-studio used to parse and resolved token values."
(do
(sd-transforms/registerTransforms sd)
(.registerFormat sd #js {:name "custom/json"
@ -23,13 +20,6 @@
;; Functions -------------------------------------------------------------------
(defn find-token-references
"Finds token reference values in `value-string` and returns a set with all contained namespaces."
[value-string]
(some->> (re-seq #"\{([^}]*)\}" value-string)
(map second)
(into #{})))
(defn tokens->style-dictionary+
"Resolves references and math expressions using StyleDictionary.
Returns a promise with the resolved dictionary."
@ -88,16 +78,16 @@
(resolve-sd-tokens+ config))]
(let [resolved-tokens (reduce
(fn [acc ^js cur]
(let [value (.-value cur)
resolved-value (d/parse-double (.-value cur))
original-value (-> cur .-original .-value)
id (uuid (.-uuid (.-id cur)))
missing-reference? (and (not resolved-value)
(re-find #"\{" value)
(= value original-value))]
(cond-> (assoc-in acc [id :resolved-value] resolved-value)
missing-reference? (update-in [id :errors] (fnil conj #{}) :style-dictionary/missing-reference))))
tokens sd-tokens)]
(let [id (uuid (.-uuid (.-id cur)))
origin-token (get tokens id)
parsed-value (wtt/parse-token-value (.-value cur))
resolved-token (if (not parsed-value)
(assoc origin-token :errors [:style-dictionary/missing-reference])
(assoc origin-token
:resolved-value (:value parsed-value)
:resolved-unit (:unit parsed-value)))]
(assoc acc (wtt/token-identifier resolved-token) resolved-token)))
{} sd-tokens)]
(when debug?
(js/console.log "Resolved tokens" resolved-tokens))
resolved-tokens)))
@ -148,20 +138,31 @@
(comment
(defonce !output (atom nil))
(-> @refs/workspace-tokens
(resolve-tokens+ {:debug? false})
(.then js/console.log))
(-> (resolve-workspace-tokens+ {:debug? true})
(p/then #(reset! !output %)))
@!output
(->> @refs/workspace-tokens
(resolve-tokens+))
(resolve-tokens+)
(#(doto % js/console.log)))
(->
(clj->js {"a" {:name "a" :value "5"}
"b" {:name "b" :value "{a} * 2"}})
(#(resolve-sd-tokens+ % {:debug? true})))
(defonce output (atom nil))
(require '[shadow.resource])
(let [example (-> (shadow.resource/inline "./data/example-tokens-set.json")
(js/JSON.parse)
.-core)]
(resolve-sd-tokens+ example {:debug? true}))
(.then (resolve-sd-tokens+ example {:debug? true})
#(reset! output %)))
nil)

View file

@ -4,6 +4,32 @@
[clojure.set :as set]
[cuerdas.core :as str]))
(def parseable-token-value-regexp
"Regexp that can be used to parse a number value out of resolved token value.
This regexp also trims whitespace around the value."
#"^\s*(-?[0-9]+\.?[0-9]*)(px|%)?\s*$")
(defn parse-token-value
"Parses a resolved value and separates the unit from the value.
Returns a map of {:value `number` :unit `string`}."
[value]
(cond
(number? value) {:value value}
(string? value) (when-let [[_ value unit] (re-find parseable-token-value-regexp value)]
(when-let [parsed-value (d/parse-double value)]
{:value parsed-value
:unit unit}))))
(defn find-token-references
"Finds token reference values in `value-string` and returns a set with all contained namespaces."
[value-string]
(some->> (re-seq #"\{([^}]*)\}" value-string)
(map second)
(into #{})))
(defn token-identifier [{:keys [name] :as _token}]
name)
(defn resolve-token-value [{:keys [value resolved-value] :as _token}]
(or
resolved-value
@ -11,17 +37,17 @@
(defn attributes-map
"Creats an attributes map using collection of `attributes` for `id`."
[attributes id]
(->> (map (fn [attr] {attr id}) attributes)
[attributes token]
(->> (map (fn [attr] [attr (token-identifier token)]) attributes)
(into {})))
(defn remove-attributes-for-token-id
(defn remove-attributes-for-token
"Removes applied tokens with `token-id` for the given `attributes` set from `applied-tokens`."
[attributes token-id applied-tokens]
[attributes token applied-tokens]
(let [attr? (set attributes)]
(->> (remove (fn [[k v]]
(and (attr? k)
(= v token-id)))
(= v (token-identifier token))))
applied-tokens)
(into {}))))
@ -29,7 +55,7 @@
"Test if `token` is applied to a `shape` on single `token-attribute`."
[token shape token-attribute]
(when-let [id (get-in shape [:applied-tokens token-attribute])]
(= (:id token) id)))
(= (token-identifier token) id)))
(defn token-applied?
"Test if `token` is applied to a `shape` with at least one of the one of the given `token-attributes`."