mirror of
https://github.com/penpot/penpot.git
synced 2026-06-01 13:10:21 +00:00
171 lines
5.8 KiB
Clojure
171 lines
5.8 KiB
Clojure
(ns app.main.data.tokenscript
|
|
(:require
|
|
["@penpot/tokenscript" :refer [BaseSymbolType
|
|
ColorSymbol
|
|
ListSymbol NumberSymbol
|
|
NumberWithUnitSymbol
|
|
ProcessorError
|
|
processTokens
|
|
TokenSymbol
|
|
makeConfig]]
|
|
[app.common.logging :as l]
|
|
[app.common.time :as ct]
|
|
[app.main.data.workspace.tokens.errors :as wte]))
|
|
|
|
(l/set-level! :debug)
|
|
|
|
;; Config ----------------------------------------------------------------------
|
|
|
|
(def config (makeConfig))
|
|
|
|
;; Class predicates ------------------------------------------------------------
|
|
;; Predicates to get information about the tokenscript interpreter symbol type
|
|
;; Or to determine the error
|
|
|
|
(defn tokenscript-symbol? [v]
|
|
(instance? BaseSymbolType v))
|
|
|
|
(defn structured-token? [v]
|
|
(instance? TokenSymbol v))
|
|
|
|
(defn structured-record-token? [^js v]
|
|
(and (structured-token? v) (instance? js/Map (.-value v))))
|
|
|
|
(defn structured-array-token? [^js v]
|
|
(and (structured-token? v) (instance? js/Array (.-value v))))
|
|
|
|
(defn number-with-unit-symbol? [v]
|
|
(instance? NumberWithUnitSymbol v))
|
|
|
|
(defn number-symbol? [v]
|
|
(instance? NumberSymbol v))
|
|
|
|
(defn list-symbol? [v]
|
|
(instance? ListSymbol v))
|
|
|
|
(defn color-symbol? [v]
|
|
(instance? ColorSymbol v))
|
|
|
|
(defn processor-error? [err]
|
|
(instance? ProcessorError err))
|
|
|
|
;; Conversion Tools ------------------------------------------------------------
|
|
;; Helpers to convert tokenscript symbols to penpot accepted formats
|
|
|
|
(defn color-symbol->hex-string [^js v]
|
|
(when (color-symbol? v)
|
|
(.toString (.to v "hex"))))
|
|
|
|
(defn color-alpha [^js v]
|
|
(if (.isHex v)
|
|
1
|
|
(or (.getAttribute v "alpha") 1)))
|
|
|
|
(defn color-symbol->penpot-color [^js v]
|
|
{:color (color-symbol->hex-string v)
|
|
:opacity (color-alpha v)})
|
|
|
|
(defn rem-number-with-unit? [v]
|
|
(and (number-with-unit-symbol? v)
|
|
(= (.-unit v) "rem")))
|
|
|
|
(defn percent-number-with-unit? [v]
|
|
(and (number-with-unit-symbol? v)
|
|
(= (.-unit v) "%")))
|
|
|
|
(defn rem->px [^js v]
|
|
(* (.-value v) 16))
|
|
|
|
(declare tokenscript-symbols->penpot-unit)
|
|
|
|
(defn structured-token->penpot-map
|
|
"Converts structured token (record or array) to penpot map format.
|
|
Structured tokens are non-primitive token types like `typography` or `box-shadow`."
|
|
[^js token-symbol]
|
|
(if (instance? js/Array (.-value token-symbol))
|
|
(mapv tokenscript-symbols->penpot-unit (.-value token-symbol))
|
|
(let [entries (es6-iterator-seq (.entries (.-value token-symbol)))]
|
|
(into {} (map (fn [[k v :as V]]
|
|
[(keyword k) (tokenscript-symbols->penpot-unit v)])
|
|
entries)))))
|
|
|
|
(defn tokenscript-symbols->penpot-unit [^js v]
|
|
(cond
|
|
(nil? v) nil
|
|
(structured-token? v) (structured-token->penpot-map v)
|
|
(list-symbol? v) (structured-token->penpot-map v)
|
|
(color-symbol? v) (.-value (.to v "hex"))
|
|
(rem-number-with-unit? v) (rem->px v)
|
|
(percent-number-with-unit? v) (/ (.-value v) 100)
|
|
:else (.-value v)))
|
|
|
|
;; Processors ------------------------------------------------------------------
|
|
;; The processor resolves tokens
|
|
;; resolved/error tokens get put back into a clojure structure directly during build time
|
|
;; For updating tokens we use the `TokenResolver` crud methods from the processing result
|
|
;; The `TokenResolver` has detailed information for each tokens dependency graph
|
|
|
|
(defn create-token-builder
|
|
"Collects resolved tokens during build time into a clojure structure.
|
|
Returns Tokenscript Symbols in `:resolved-value` key."
|
|
[tokens]
|
|
(let [output (volatile! tokens)
|
|
|
|
;; When a token is resolved (No parsing / reference errors) we assing `:resolved-value` for the original token
|
|
on-resolve
|
|
(fn [^js/String token-name ^js/Symbol resolved-symbol]
|
|
(vswap! output assoc-in [token-name :resolved-value] resolved-symbol))
|
|
|
|
;; When a token contains any errors we assing `:errors` for the original token
|
|
on-error
|
|
(fn [^js/String token-name ^js/Error _error ^js/String _original-value]
|
|
(let [value (get tokens token-name)
|
|
default-error [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]]
|
|
(vswap! output assoc-in [token-name :errors] default-error)))
|
|
|
|
;; Extract the atom value
|
|
get-result
|
|
(fn [] @output)]
|
|
#js {:onResolve on-resolve
|
|
:onError on-error
|
|
:getResult get-result}))
|
|
|
|
(defn clj->token->tokenscript-token
|
|
"Convert penpot token into a format that tokenscript can handle."
|
|
[{:keys [type value]}]
|
|
#js {"$type" (name type)
|
|
"$value" (clj->js value)})
|
|
|
|
(defn clj-tokens->tokenscript-tokens
|
|
"Convert penpot map of tokens into tokenscript map structure.
|
|
tokenscript accepts a map of [token-name {\"$type\": string, \"$value\": any}]"
|
|
[tokens]
|
|
(let [token-map (js/Map.)]
|
|
(doseq [[k token] tokens]
|
|
(.set token-map k (clj->token->tokenscript-token token)))
|
|
token-map))
|
|
|
|
(defn process-tokens
|
|
"Builds tokens using `tokenscript`."
|
|
[tokens]
|
|
(let [input (clj-tokens->tokenscript-tokens tokens)
|
|
result (processTokens input #js {:config config
|
|
:builder (create-token-builder tokens)})]
|
|
result))
|
|
|
|
(defn update-token
|
|
[tokens token]
|
|
(let [result (process-tokens tokens)
|
|
resolver (.-resolver result)]
|
|
(.updateToken resolver #js {:tokenPath (:name token)
|
|
:tokenData (clj->token->tokenscript-token token)})))
|
|
|
|
;; Main ------------------------------------------------------------------------
|
|
|
|
(defn resolve-tokens [tokens]
|
|
(let [tpoint (ct/tpoint-ms)
|
|
result (process-tokens tokens)
|
|
elapsed (tpoint)]
|
|
(l/dbg :hint "tokenscript/resolve-tokens" :elapsed elapsed)
|
|
(.-output result)))
|