mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
169 lines
6.0 KiB
Clojure
169 lines
6.0 KiB
Clojure
(ns app.main.ui.workspace.tokens.token
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.data.macros :as dm]
|
|
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
|
|
[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 attributes-map
|
|
"Creats an attributes map using collection of `attributes` for `id`."
|
|
[attributes token]
|
|
(->> (map (fn [attr] [attr (token-identifier token)]) attributes)
|
|
(into {})))
|
|
|
|
(defn remove-attributes-for-token
|
|
"Removes applied tokens with `token-id` for the given `attributes` set from `applied-tokens`."
|
|
[attributes token applied-tokens]
|
|
(let [attr? (set attributes)]
|
|
(->> (remove (fn [[k v]]
|
|
(and (attr? k)
|
|
(= v (token-identifier token))))
|
|
applied-tokens)
|
|
(into {}))))
|
|
|
|
(defn token-attribute-applied?
|
|
"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])]
|
|
(= (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`."
|
|
[token shape token-attributes]
|
|
(some #(token-attribute-applied? token shape %) token-attributes))
|
|
|
|
(defn shapes-token-applied?
|
|
"Test if `token` is applied to to any of `shapes` with at least one of the one of the given `token-attributes`."
|
|
[token shapes token-attributes]
|
|
(some #(token-applied? token % token-attributes) shapes))
|
|
|
|
(defn shapes-ids-by-applied-attributes [token shapes token-attributes]
|
|
(reduce (fn [acc shape]
|
|
(let [applied-ids-by-attribute (->> (map #(when (token-attribute-applied? token shape %)
|
|
[% #{(:id shape)}])
|
|
token-attributes)
|
|
(filter some?)
|
|
(into {}))]
|
|
(merge-with into acc applied-ids-by-attribute)))
|
|
{} shapes))
|
|
|
|
(defn shapes-applied-all? [ids-by-attributes shape-ids attributes]
|
|
(every? #(set/superset? (get ids-by-attributes %) shape-ids) attributes))
|
|
|
|
(defn token-name->path
|
|
"Splits token-name into a path vector split by `.` characters.
|
|
|
|
Will concatenate multiple `.` characters into one."
|
|
[token-name]
|
|
(str/split token-name #"\.+"))
|
|
|
|
(defn token-name->path-selector
|
|
"Splits token-name into map with `:path` and `:selector` using `token-name->path`.
|
|
|
|
`:selector` is the last item of the names path
|
|
`:path` is everything leading up the the `:selector`."
|
|
[token-name]
|
|
(let [path-segments (token-name->path token-name)
|
|
last-idx (dec (count path-segments))
|
|
[path [selector]] (split-at last-idx path-segments)]
|
|
{:path (seq path)
|
|
:selector selector}))
|
|
|
|
(defn token-names-map
|
|
"Convert tokens into a map with their `:name` as the key.
|
|
|
|
E.g.: {\"sm\" {:token-type :border-radius :id #uuid \"000\" ...}}"
|
|
[tokens]
|
|
(->> (map (fn [{:keys [name] :as token}] [name token]) tokens)
|
|
(into {})))
|
|
|
|
(defn token-names-tree-id-map [tokens]
|
|
(reduce
|
|
(fn [acc [_ {:keys [name] :as token}]]
|
|
(when (string? name)
|
|
(let [temp-id (random-uuid)
|
|
token (assoc token :temp/id temp-id)]
|
|
(-> acc
|
|
(assoc-in (concat [:tree] (token-name->path name)) token)
|
|
(assoc-in [:ids-map temp-id] token)))))
|
|
{:tree {}
|
|
:ids-map {}}
|
|
tokens))
|
|
|
|
(defn token-names-tree
|
|
"Convert tokens into a nested tree with their `:name` as the path."
|
|
[tokens]
|
|
(reduce
|
|
(fn [acc [_ {:keys [name] :as token}]]
|
|
(when (string? name)
|
|
(let [path (token-name->path name)]
|
|
(assoc-in acc path token))))
|
|
{} tokens))
|
|
|
|
(defn token-name-path-exists?
|
|
"Traverses the path from `token-name` down a `token-tree` and checks if a token at that path exists.
|
|
|
|
It's not allowed to create a token inside a token. E.g.:
|
|
Creating a token with
|
|
|
|
{:name \"foo.bar\"}
|
|
|
|
in the tokens tree:
|
|
|
|
{\"foo\" {:name \"other\"}}"
|
|
[token-name token-names-tree]
|
|
(let [{:keys [path selector]} (token-name->path-selector token-name)
|
|
path-target (reduce
|
|
(fn [acc cur]
|
|
(let [target (get acc cur)]
|
|
(cond
|
|
;; Path segment doesn't exist yet
|
|
(nil? target) (reduced false)
|
|
;; A token exists at this path
|
|
(:name target) (reduced true)
|
|
;; Continue traversing the true
|
|
:else target)))
|
|
token-names-tree path)]
|
|
(cond
|
|
(boolean? path-target) path-target
|
|
(get path-target :name) true
|
|
:else (-> (get path-target selector)
|
|
(seq)
|
|
(boolean)))))
|
|
|
|
(defn color-token? [token]
|
|
(= (:type token) :color))
|
|
|
|
(defn resolved-value-hex [{:keys [resolved-value] :as token}]
|
|
(when (and resolved-value (color-token? token))
|
|
(some->> (tinycolor/valid-color resolved-value)
|
|
(tinycolor/->hex)
|
|
(str "#"))))
|