Merge pull request #263 from tokens-studio/token-sets-themes

Token sets themes
This commit is contained in:
Florian Schrödl 2024-08-27 17:09:47 +02:00 committed by GitHub
commit 734acd27b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1040 additions and 235 deletions

View file

@ -24,7 +24,9 @@
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.token-theme :as ctot]
[app.common.types.tokens-list :as ctol] [app.common.types.tokens-list :as ctol]
[app.common.types.tokens-theme-list :as ctotl]
[app.common.types.typographies-list :as ctyl] [app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as ctt] [app.common.types.typography :as ctt]
[clojure.set :as set])) [clojure.set :as set]))
@ -247,6 +249,53 @@
[:type [:= :del-typography]] [:type [:= :del-typography]]
[:id ::sm/uuid]]] [:id ::sm/uuid]]]
[:add-temporary-token-theme
[:map {:title "AddTemporaryTokenThemeChange"}
[:type [:= :add-temporary-token-theme]]
[:token-theme ::ctot/token-theme]]]
[:update-active-token-themes
[:map {:title "UpdateActiveTokenThemes"}
[:type [:= :update-active-token-themes]]
[:theme-ids [:set ::sm/uuid]]]]
[:delete-temporary-token-theme
[:map {:title "DeleteTemporaryTokenThemeChange"}
[:type [:= :delete-temporary-token-theme]]
[:id ::sm/uuid]]]
[:add-token-theme
[:map {:title "AddTokenThemeChange"}
[:type [:= :add-token-theme]]
[:token-theme ::ctot/token-theme]]]
[:mod-token-theme
[:map {:title "ModTokenThemeChange"}
[:type [:= :mod-token-theme]]
[:id ::sm/uuid]
[:token-theme ::ctot/token-theme]]]
[:del-token-theme
[:map {:title "DelTokenThemeChange"}
[:type [:= :del-token-theme]]
[:id ::sm/uuid]]]
[:add-token-set
[:map {:title "AddTokenSetChange"}
[:type [:= :add-token-set]]
[:token-set ::ctot/token-set]]]
[:mod-token-set
[:map {:title "ModTokenSetChange"}
[:type [:= :mod-token-set]]
[:id ::sm/uuid]
[:token-set ::ctot/token-set]]]
[:del-token-set
[:map {:title "DelTokenSetChange"}
[:type [:= :del-token-set]]
[:id ::sm/uuid]]]
[:add-token [:add-token
[:map {:title "AddTokenChange"} [:map {:title "AddTokenChange"}
[:type [:= :add-token]] [:type [:= :add-token]]
@ -742,6 +791,42 @@
[data {:keys [id]}] [data {:keys [id]}]
(ctol/delete-token data id)) (ctol/delete-token data id))
(defmethod process-change :add-temporary-token-theme
[data {:keys [token-theme]}]
(ctotl/add-temporary-token-theme data token-theme))
(defmethod process-change :update-active-token-themes
[data {:keys [theme-ids]}]
(ctotl/assoc-active-token-themes data theme-ids))
(defmethod process-change :delete-temporary-token-theme
[data {:keys [id]}]
(ctotl/delete-temporary-token-theme data id))
(defmethod process-change :add-token-theme
[data {:keys [token-theme]}]
(ctotl/add-token-theme data token-theme))
(defmethod process-change :mod-token-theme
[data {:keys [id token-theme]}]
(ctotl/update-token-theme data id merge token-theme))
(defmethod process-change :del-token-theme
[data {:keys [id]}]
(ctotl/delete-token-theme data id))
(defmethod process-change :add-token-set
[data {:keys [token-set]}]
(ctotl/add-token-set data token-set))
(defmethod process-change :mod-token-set
[data {:keys [id token-set]}]
(ctotl/update-token-set data id merge token-set))
(defmethod process-change :del-token-set
[data {:keys [id]}]
(ctotl/delete-token-set data id))
;; === Operations ;; === Operations
(defmethod process-operation :set (defmethod process-operation :set
[on-changed shape op] [on-changed shape op]

View file

@ -328,7 +328,7 @@
(update :redo-changes conj add-change) (update :redo-changes conj add-change)
(cond-> (cond->
(and (ctk/in-component-copy? parent) (not ignore-touched)) (and (ctk/in-component-copy? parent) (not ignore-touched))
(update :undo-changes conj restore-touched-change)) (update :undo-changes conj restore-touched-change))
(update :undo-changes conj del-change) (update :undo-changes conj del-change)
(apply-changes-local))))) (apply-changes-local)))))
@ -389,7 +389,7 @@
(update :redo-changes conj set-parent-change) (update :redo-changes conj set-parent-change)
(cond-> (cond->
(ctk/in-component-copy? parent) (ctk/in-component-copy? parent)
(update :undo-changes conj restore-touched-change)) (update :undo-changes conj restore-touched-change))
(update :undo-changes #(reduce mk-undo-change % shapes)) (update :undo-changes #(reduce mk-undo-change % shapes))
(apply-changes-local))))) (apply-changes-local)))))
@ -695,6 +695,68 @@
(update :undo-changes conj {:type :add-typography :typography prev-typography}) (update :undo-changes conj {:type :add-typography :typography prev-typography})
(apply-changes-local)))) (apply-changes-local))))
(defn add-temporary-token-theme
[changes token-theme]
(-> changes
(update :redo-changes conj {:type :add-temporary-token-theme :token-theme token-theme})
(update :undo-changes conj {:type :delete-temporary-token-theme :id (:id token-theme)})
(apply-changes-local)))
(defn update-active-token-themes
[changes token-active-theme-ids prev-token-active-theme-ids]
(-> changes
(update :redo-changes conj {:type :update-active-token-themes :theme-ids token-active-theme-ids})
(update :undo-changes conj {:type :update-active-token-themes :theme-ids prev-token-active-theme-ids})
(apply-changes-local)))
(defn add-token-theme
[changes token-theme]
(-> changes
(update :redo-changes conj {:type :add-token-theme :token-theme token-theme})
(update :undo-changes conj {:type :del-token-theme :id (:id token-theme)})
(apply-changes-local)))
(defn update-token-theme
[changes token-theme prev-token-theme]
(-> changes
(update :redo-changes conj {:type :mod-token-theme :id (:id token-theme) :token-theme token-theme})
(update :undo-changes conj {:type :mod-token-theme :id (:id token-theme) :token-theme (or prev-token-theme token-theme)})
(apply-changes-local)))
(defn delete-token-theme
[changes token-theme-id]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
prev-token-theme (get-in library-data [:token-themes-index token-theme-id])]
(-> changes
(update :redo-changes conj {:type :del-token-theme :id token-theme-id})
(update :undo-changes conj {:type :add-token-theme :token-theme prev-token-theme})
(apply-changes-local))))
(defn add-token-set
[changes token-set]
(-> changes
(update :redo-changes conj {:type :add-token-set :token-set token-set})
(update :undo-changes conj {:type :del-token-set :id (:id token-set)})
(apply-changes-local)))
(defn update-token-set
[changes token-set prev-token-set]
(-> changes
(update :redo-changes conj {:type :mod-token-set :id (:id token-set) :token-set token-set})
(update :undo-changes conj {:type :mod-token-set :id (:id token-set) :token-set (or prev-token-set token-set)})
(apply-changes-local)))
(defn delete-token-set
[changes token-set-id]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
prev-token-set (get-in library-data [:token-sets-index token-set-id])]
(-> changes
(update :redo-changes conj {:type :del-token-set :id token-set-id})
(update :undo-changes conj {:type :add-token-set :token-set prev-token-set})
(apply-changes-local))))
(defn add-token (defn add-token
[changes token] [changes token]
(-> changes (-> changes

View file

@ -27,6 +27,7 @@
[app.common.types.plugins :as ctpg] [app.common.types.plugins :as ctpg]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.token-theme :as ctt]
[app.common.types.typographies-list :as ctyl] [app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as cty] [app.common.types.typography :as cty]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -58,12 +59,26 @@
[:vector {:gen/max 3} ::ctc/recent-color]] [:vector {:gen/max 3} ::ctc/recent-color]]
[:typographies {:optional true} [:typographies {:optional true}
[:map-of {:gen/max 2} ::sm/uuid ::cty/typography]] [:map-of {:gen/max 2} ::sm/uuid ::cty/typography]]
[:tokens {:optional true}
[:map-of {:gen/max 100} ::sm/uuid ::cto/token]]
[:media {:optional true} [:media {:optional true}
[:map-of {:gen/max 5} ::sm/uuid ::media-object]] [:map-of {:gen/max 5} ::sm/uuid ::media-object]]
[:plugin-data {:optional true} [:plugin-data {:optional true}
[:map-of {:gen/max 5} :keyword ::ctpg/plugin-data]]]) [:map-of {:gen/max 5} :keyword ::ctpg/plugin-data]]
[:token-theme-temporary-id {:optional true}
::sm/uuid]
[:token-active-themes {:optional true :default #{}}
[:set ::sm/uuid]]
[:token-themes {:optional true}
[:vector ::sm/uuid]]
[:token-themes-index {:optional true}
[:map-of {:gen/max 5} ::sm/uuid ::ctt/token-theme]]
[:token-set-groups {:optional true}
[:vector ::sm/uuid]]
[:token-set-groups-index {:optional true}
[:map-of {:gen/max 10} ::sm/uuid ::ctt/token-set-group]]
[:token-sets-index {:optional true}
[:map-of {:gen/max 10} ::sm/uuid ::ctt/token-set]]
[:tokens {:optional true}
[:map-of {:gen/max 100} ::sm/uuid ::cto/token]]])
(def check-file-data! (def check-file-data!
(sm/check-fn ::data)) (sm/check-fn ::data))

View file

@ -0,0 +1,44 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.types.token-theme
(:require
[app.common.schema :as sm]))
(sm/register! ::token-theme
[:map {:title "TokenTheme"}
[:id ::sm/uuid]
[:name :string]
[:group {:optional true} :string]
[:source? {:optional true} :boolean]
[:description {:optional true} :string]
[:modified-at {:optional true} ::sm/inst]
[:sets [:set {:gen/max 10 :gen/min 1} ::sm/uuid]]])
(sm/register! ::token-set-group-ref
[:map
[:id ::sm/uuid]
[:type [:= :group]]])
(sm/register! ::token-set-ref
[:map
[:id ::sm/uuid]
[:type [:= :set]]])
(sm/register! ::token-set-group
[:map {:title "TokenSetGroup"}
[:id ::sm/uuid]
[:name :string]
[:items [:vector {:gen/max 10 :gen/min 1}
[:or ::token-set-group-ref ::token-set-ref]]]])
(sm/register! ::token-set
[:map {:title "TokenSet"}
[:id ::sm/uuid]
[:name :string]
[:description {:optional true} :string]
[:modified-at {:optional true} ::sm/inst]
[:tokens [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]])

View file

@ -0,0 +1,78 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.types.tokens-theme-list
(:require
[app.common.data :as d]
[app.common.time :as dt]))
(defn- touch
"Updates the `modified-at` timestamp of a token set."
[token-set]
(assoc token-set :modified-at (dt/now)))
(defn assoc-active-token-themes
[file-data theme-ids]
(assoc file-data :token-active-themes theme-ids))
(defn add-temporary-token-theme
[file-data {:keys [id] :as token-theme}]
(-> file-data
(d/dissoc-in [:token-themes-index (:token-theme-temporary-id file-data)])
(assoc :token-theme-temporary-id id)
(update :token-themes-index assoc id token-theme)))
(defn delete-temporary-token-theme
[file-data token-theme-id]
(cond-> file-data
(= (:token-theme-temporary-id file-data) token-theme-id) (dissoc :token-theme-temporary-id)
:always (d/dissoc-in [:token-themes-index (:token-theme-temporary-id file-data)])))
(defn add-token-theme
[file-data {:keys [index id] :as token-theme}]
(-> file-data
(update :token-themes
(fn [token-themes]
(let [exists? (some (partial = id) token-themes)]
(cond
exists? token-themes
(nil? index) (conj (or token-themes []) id)
:else (d/insert-at-index token-themes index [id])))))
(update :token-themes-index assoc id token-theme)))
(defn update-token-theme
[file-data token-theme-id f & args]
(d/update-in-when file-data [:token-themes-index token-theme-id] #(-> (apply f % args) (touch))))
(defn delete-token-theme
[file-data theme-id]
(-> file-data
(update :token-themes (fn [ids] (d/removev #(= % theme-id) ids)))
(update :token-themes-index dissoc theme-id)
(update :token-active-themes disj theme-id)))
(defn add-token-set
[file-data {:keys [index id] :as token-set}]
(-> file-data
(update :token-set-groups
(fn [token-set-groups]
(let [exists? (some (partial = id) token-set-groups)]
(cond
exists? token-set-groups
(nil? index) (conj (or token-set-groups []) id)
:else (d/insert-at-index token-set-groups index [id])))))
(update :token-sets-index assoc id token-set)))
(defn update-token-set
[file-data token-set-id f & args]
(d/update-in-when file-data [:token-sets-index token-set-id] #(-> (apply f % args) (touch))))
(defn delete-token-set
[file-data token-set-id]
(-> file-data
(update :token-set-groups (fn [xs] (into [] (remove #(= (:id %) token-set-id) xs))))
(update :token-sets-index dissoc token-set-id)
(update :token-themes-index (fn [xs] (update-vals xs #(update % :sets disj token-set-id))))))

View file

@ -15,8 +15,9 @@
[app.main.data.changes :as dch] [app.main.data.changes :as dch]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.tokens.common :refer [workspace-shapes]]
[app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-set :as wtts]
[app.main.ui.workspace.tokens.update :as wtu]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[clojure.data :as data] [clojure.data :as data]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -50,12 +51,6 @@
(let [[shape-leftover token-leftover _matching] (data/diff (:applied-tokens shape) token)] (let [[shape-leftover token-leftover _matching] (data/diff (:applied-tokens shape) token)]
(merge {} shape-leftover token-leftover))) (merge {} shape-leftover token-leftover)))
(defn get-shape-from-state [shape-id state]
(let [current-page-id (get state :current-page-id)
shape (-> (workspace-shapes (:workspace-data state) current-page-id #{shape-id})
(first))]
shape))
(defn token-from-attributes [token attributes] (defn token-from-attributes [token attributes]
(->> (map (fn [attr] [attr (wtt/token-identifier token)]) attributes) (->> (map (fn [attr] [attr (wtt/token-identifier token)]) attributes)
(into {}))) (into {})))
@ -86,19 +81,144 @@
(let [workspace-data (deref refs/workspace-data)] (let [workspace-data (deref refs/workspace-data)]
(get (:tokens workspace-data) id))) (get (:tokens workspace-data) id)))
(defn set-selected-token-set-id
[id]
(ptk/reify ::set-selected-token-set-id
ptk/UpdateEvent
(update [_ state]
(wtts/assoc-selected-token-set-id state id))))
(defn create-token-theme [token-theme]
(let [new-token-theme (merge
{:id (uuid/next)
:sets #{}
:selected :enabled}
token-theme)]
(ptk/reify ::create-token-theme
ptk/WatchEvent
(watch [it _ _]
(let [changes (-> (pcb/empty-changes it)
(pcb/add-token-theme new-token-theme))]
(rx/of
(dch/commit-changes changes)))))))
(defn ensure-token-theme-changes [changes state {:keys [id new-set?]}]
(let [theme-id (wtts/update-theme-id state)
theme (some-> theme-id (wtts/get-workspace-token-theme state))]
(cond
(not theme-id) (-> changes
(pcb/add-temporary-token-theme
{:id (uuid/next)
:name ""
:sets #{id}}))
new-set? (-> changes
(pcb/update-token-theme
(wtts/add-token-set-to-token-theme id theme)
theme))
:else changes)))
(defn toggle-token-theme [token-theme-id]
(ptk/reify ::toggle-token-theme
ptk/WatchEvent
(watch [it state _]
(let [themes (wtts/get-active-theme-ids state)
new-themes (wtts/toggle-active-theme-id token-theme-id state)
changes (-> (pcb/empty-changes it)
(pcb/update-active-token-themes new-themes themes))]
(rx/of
(dch/commit-changes changes)
(wtu/update-workspace-tokens))))))
(defn delete-token-theme [token-theme-id]
(ptk/reify ::delete-token-theme
ptk/WatchEvent
(watch [it state _]
(let [data (get state :workspace-data)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/delete-token-theme token-theme-id))]
(rx/of
(dch/commit-changes changes)
(wtu/update-workspace-tokens))))))
(defn create-token-set [token-set]
(let [new-token-set (merge
{:id (uuid/next)
:name "Token Set"
:tokens []}
token-set)]
(ptk/reify ::create-token-set
ptk/WatchEvent
(watch [it state _]
(let [changes (-> (pcb/empty-changes it)
(pcb/add-token-set new-token-set)
(ensure-token-theme-changes state {:id (:id new-token-set)
:new-set? true}))]
(rx/of
(dch/commit-changes changes)))))))
(defn toggle-token-set [token-set-id]
(ptk/reify ::toggle-token-set
ptk/WatchEvent
(watch [it state _]
(let [theme (some-> (wtts/update-theme-id state)
(wtts/get-workspace-token-theme state))
changes (-> (pcb/empty-changes it)
(pcb/update-token-theme
(wtts/toggle-token-set-to-token-theme token-set-id theme)
theme)
(pcb/update-active-token-themes #{(wtts/update-theme-id state)} (wtts/get-active-theme-ids state)))]
(rx/of
(dch/commit-changes changes)
(wtu/update-workspace-tokens))))))
(defn delete-token-set [token-set-id]
(ptk/reify ::delete-token-set
ptk/WatchEvent
(watch [it state _]
(let [data (get state :workspace-data)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/delete-token-set token-set-id))]
(rx/of
(dch/commit-changes changes)
(wtu/update-workspace-tokens))))))
(defn update-create-token (defn update-create-token
[token] [token]
(let [token (update token :id #(or % (uuid/next)))] (let [token (update token :id #(or % (uuid/next)))]
(ptk/reify ::add-token (ptk/reify ::update-create-token
ptk/WatchEvent ptk/WatchEvent
(watch [it _ _] (watch [it state _]
(let [prev-token (get-token-data-from-token-id (:id token)) (let [prev-token (get-token-data-from-token-id (:id token))
changes (if prev-token create-token? (not prev-token)
(-> (pcb/empty-changes it) token-changes (if create-token?
(pcb/update-token token prev-token)) (-> (pcb/empty-changes it)
(-> (pcb/empty-changes it) (pcb/add-token token))
(pcb/add-token token)))] (-> (pcb/empty-changes it)
(rx/of (dch/commit-changes changes))))))) (pcb/update-token token prev-token)))
token-set (wtts/get-selected-token-set state)
create-set? (not token-set)
new-token-set {:id (uuid/next)
:name "Global"
:tokens [(:id token)]}
selected-token-set-id (if create-set?
(:id new-token-set)
(:id token-set))
set-changes (cond
create-set? (-> token-changes
(pcb/add-token-set new-token-set))
:else (let [updated-token-set (if (contains? token-set (:id token))
token-set
(update token-set :tokens conj (:id token)))]
(-> token-changes
(pcb/update-token-set updated-token-set token-set))))
theme-changes (-> set-changes
(ensure-token-theme-changes state {:new-set? create-set?
:id selected-token-set-id}))]
(rx/of
(set-selected-token-set-id selected-token-set-id)
(dch/commit-changes theme-changes)))))))
(defn delete-token (defn delete-token
[id] [id]
@ -119,6 +239,7 @@
(update :name #(str/concat % "-copy")))] (update :name #(str/concat % "-copy")))]
(update-create-token new-token))) (update-create-token new-token)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TEMP (Move to test) ;; TEMP (Move to test)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -14,6 +14,7 @@
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.workspace.tokens.token-set :as wtts]
[okulary.core :as l])) [okulary.core :as l]))
;; ---- Global refs ;; ---- Global refs
@ -233,11 +234,40 @@
(def workspace-data (def workspace-data
(l/derived :workspace-data st/state)) (l/derived :workspace-data st/state))
(def workspace-tokens (def workspace-selected-token-set-id
(l/derived (fn [data] (l/derived
(get data :tokens {})) wtts/get-selected-token-set-id
workspace-data st/state
=)) =))
(def workspace-active-theme-ids
(l/derived wtts/get-active-theme-ids st/state))
(def workspace-active-set-ids
(l/derived wtts/get-active-set-ids st/state))
(def workspace-token-themes
(l/derived wtts/get-workspace-themes-index st/state))
(def workspace-ordered-token-themes
(l/derived wtts/get-workspace-ordered-themes st/state))
(def workspace-token-sets
(l/derived
(fn [data]
(or (wtts/get-workspace-sets data) {}))
st/state
=))
(def workspace-active-theme-sets-tokens
(l/derived wtts/get-active-theme-sets-tokens-names-map st/state =))
(def workspace-selected-token-set-tokens
(l/derived
(fn [data]
(or (wtts/get-selected-token-set-tokens data) {}))
st/state
=))
(def workspace-file-colors (def workspace-file-colors
(l/derived (fn [data] (l/derived (fn [data]

View file

@ -856,7 +856,7 @@
shape (when-not multiple shape (when-not multiple
(first (deref (refs/objects-by-id ids)))) (first (deref (refs/objects-by-id ids))))
tokens (mf/deref refs/workspace-tokens) tokens (mf/deref refs/workspace-selected-token-set-tokens)
spacing-tokens (mf/use-memo (mf/deps tokens) #(:spacing (wtc/group-tokens-by-type tokens))) spacing-tokens (mf/use-memo (mf/deps tokens) #(:spacing (wtc/group-tokens-by-type tokens)))
spacing-column-options (mf/use-memo spacing-column-options (mf/use-memo

View file

@ -101,7 +101,7 @@
selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
selection-parents (mf/deref selection-parents-ref) selection-parents (mf/deref selection-parents-ref)
tokens (-> (mf/deref refs/workspace-tokens) tokens (-> (mf/deref refs/workspace-active-theme-sets-tokens)
(sd/use-resolved-tokens)) (sd/use-resolved-tokens))
tokens-by-type (mf/use-memo (mf/deps tokens) #(wtc/group-tokens-by-type tokens)) tokens-by-type (mf/use-memo (mf/deps tokens) #(wtc/group-tokens-by-type tokens))

View file

@ -311,7 +311,7 @@
selected (mf/deref refs/selected-shapes) selected (mf/deref refs/selected-shapes)
selected-shapes (into [] (keep (d/getf objects)) selected) selected-shapes (into [] (keep (d/getf objects)) selected)
token-id (:token-id mdata) token-id (:token-id mdata)
token (get (mf/deref refs/workspace-tokens) token-id)] token (get (mf/deref refs/workspace-selected-token-set-tokens) token-id)]
(mf/use-effect (mf/use-effect
(mf/deps mdata) (mf/deps mdata)
(fn [] (fn []

View file

@ -58,6 +58,6 @@
{:global global})) {:global global}))
(defn download-tokens-as-json [] (defn download-tokens-as-json []
(let [all-tokens (deref refs/workspace-tokens) (let [all-tokens (deref refs/workspace-selected-token-set-tokens)
transformed-tokens-json (transform-tokens-into-json-format all-tokens)] transformed-tokens-json (transform-tokens-into-json-format all-tokens)]
(export-tokens-file transformed-tokens-json))) (export-tokens-file transformed-tokens-json)))

View file

@ -99,10 +99,10 @@ Token names should only contain letters and digits separated by . characters.")}
empty-input? (p/rejected nil) empty-input? (p/rejected nil)
direct-self-reference? (p/rejected :error/token-direct-self-reference) direct-self-reference? (p/rejected :error/token-direct-self-reference)
:else (let [token-id (or (:id token) (random-uuid)) :else (let [token-id (or (:id token) (random-uuid))
new-tokens (update tokens token-id merge {:id token-id new-tokens (update tokens token-name merge {:id token-id
:value input :value input
:name token-name})] :name token-name})]
(-> (sd/resolve-tokens+ new-tokens #_ {:debug? true}) (-> (sd/resolve-tokens+ new-tokens {:names-map? true})
(p/then (p/then
(fn [resolved-tokens] (fn [resolved-tokens]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-name)] (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-name)]
@ -139,29 +139,33 @@ Token names should only contain letters and digits separated by . characters.")}
timeout))))] timeout))))]
debounced-resolver-callback)) debounced-resolver-callback))
(defonce form-token-cache-atom (atom nil))
(mf/defc form (mf/defc form
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [token token-type] :as _args}] [{:keys [token token-type] :as _args}]
(let [tokens (mf/deref refs/workspace-tokens) (let [selected-set-tokens (mf/deref refs/workspace-selected-token-set-tokens)
resolved-tokens (sd/use-resolved-tokens tokens) active-theme-tokens (mf/deref refs/workspace-active-theme-sets-tokens)
resolved-tokens (sd/use-resolved-tokens active-theme-tokens {:names-map? true
:cache-atom form-token-cache-atom})
token-path (mf/use-memo token-path (mf/use-memo
(mf/deps (:name token)) (mf/deps (:name token))
#(wtt/token-name->path (:name token))) #(wtt/token-name->path (:name token)))
tokens-tree (mf/use-memo selected-set-tokens-tree (mf/use-memo
(mf/deps token-path resolved-tokens) (mf/deps token-path selected-set-tokens)
(fn [] (fn []
(-> (wtt/token-names-tree resolved-tokens) (-> (wtt/token-names-tree selected-set-tokens)
;; Allow setting editing token to it's own path ;; Allow setting editing token to it's own path
(d/dissoc-in token-path)))) (d/dissoc-in token-path))))
;; Name ;; Name
name-ref (mf/use-var (:name token)) name-ref (mf/use-var (:name token))
name-errors (mf/use-state nil) name-errors (mf/use-state nil)
validate-name (mf/use-callback validate-name (mf/use-callback
(mf/deps tokens-tree) (mf/deps selected-set-tokens-tree)
(fn [value] (fn [value]
(let [schema (token-name-schema {:token token (let [schema (token-name-schema {:token token
:tokens-tree tokens-tree})] :tokens-tree selected-set-tokens-tree})]
(m/explain schema (finalize-name value))))) (m/explain schema (finalize-name value)))))
on-update-name-debounced (mf/use-callback on-update-name-debounced (mf/use-callback
(debounce (fn [e] (debounce (fn [e]
@ -187,7 +191,7 @@ Token names should only contain letters and digits separated by . characters.")}
(= token-or-err :error/token-missing-reference) token-or-err (= token-or-err :error/token-missing-reference) token-or-err
(:resolved-value token-or-err) (:resolved-value token-or-err))] (:resolved-value token-or-err) (:resolved-value token-or-err))]
(reset! token-resolve-result v)))) (reset! token-resolve-result v))))
on-update-value-debounced (use-debonced-resolve-callback name-ref token tokens set-resolve-value) on-update-value-debounced (use-debonced-resolve-callback name-ref token active-theme-tokens set-resolve-value)
on-update-value (mf/use-callback on-update-value (mf/use-callback
(mf/deps on-update-value-debounced) (mf/deps on-update-value-debounced)
(fn [e] (fn [e]

View file

@ -7,95 +7,81 @@
(ns app.main.ui.workspace.tokens.sets (ns app.main.ui.workspace.tokens.sets
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm] [app.main.data.tokens :as wdt]
[app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(def active-sets #{#uuid "2858b330-828e-4131-86ed-e4d1c0f4b3e3"
#uuid "d608877b-842a-473b-83ca-b5f8305caf83"})
(def sets-root-order [#uuid "2858b330-828e-4131-86ed-e4d1c0f4b3e3"
#uuid "9c5108aa-bdb4-409c-a3c8-c3dfce2f8bf8"
#uuid "0381446e-1f1d-423f-912c-ab577d61b79b"])
(def sets {#uuid "9c5108aa-bdb4-409c-a3c8-c3dfce2f8bf8" {:type :group
:name "Group A"
:children [#uuid "d1754e56-3510-493f-8287-5ef3417d4141"
#uuid "d608877b-842a-473b-83ca-b5f8305caf83"]}
#uuid "d608877b-842a-473b-83ca-b5f8305caf83" {:type :set
:name "Set A / 1"}
#uuid "d1754e56-3510-493f-8287-5ef3417d4141" {:type :group
:name "Group A / B"
:children [#uuid "f608877b-842a-473b-83ca-b5f8305caf83"
#uuid "7cc05389-9391-426e-bc0e-ba5cb8f425eb"]}
#uuid "f608877b-842a-473b-83ca-b5f8305caf83" {:type :set
:name "Set A / B / 1"}
#uuid "7cc05389-9391-426e-bc0e-ba5cb8f425eb" {:type :set
:name "Set A / B / 2"}
#uuid "2858b330-828e-4131-86ed-e4d1c0f4b3e3" {:type :set
:name "Set Root 1"}
#uuid "0381446e-1f1d-423f-912c-ab577d61b79b" {:type :set
:name "Set Root 2"}})
(def ^:private chevron-icon (def ^:private chevron-icon
(i/icon-xref :arrow (stl/css :chevron-icon))) (i/icon-xref :arrow (stl/css :chevron-icon)))
(defn set-selected-set (defn on-toggle-token-set-click [id event]
[set-id] (dom/stop-propagation event)
(dm/assert! (uuid? set-id)) (st/emit! (wdt/toggle-token-set id)))
(ptk/reify ::set-selected-set
ptk/UpdateEvent (defn on-select-token-set-click [id event]
(update [_ state] (st/emit! (wdt/set-selected-token-set-id id)))
(assoc state :selected-set-id set-id))))
(defn on-delete-token-set-click [id event]
(dom/stop-propagation event)
(st/emit! (wdt/delete-token-set id)))
(mf/defc sets-tree (mf/defc sets-tree
[{:keys [selected-set-id set-id]}] [{:keys [token-set token-set-active? token-set-selected?] :as _props}]
(let [set (get sets set-id)] (let [{:keys [id name _children]} token-set
(when set selected? (and set? (token-set-selected? id))
(let [{:keys [type name children]} set visible? (token-set-active? id)
visible? (mf/use-state (contains? active-sets set-id)) collapsed? (mf/use-state false)
collapsed? (mf/use-state false) set? true #_(= type :set)
icon (if (= type :set) i/document i/group) group? false #_(= type :group)]
selected? (mf/use-state (= set-id selected-set-id)) [:div {:class (stl/css :set-item-container)
:on-click #(on-select-token-set-click id %)}
on-click [:div {:class (stl/css-case :set-item-group group?
(mf/use-fn :set-item-set set?
(mf/deps type set-id) :selected-set selected?)}
(fn [event] (when group?
(dom/stop-propagation event) [:span {:class (stl/css-case :collapsabled-icon true
(st/emit! (set-selected-set set-id))))] :collapsed @collapsed?)
[:div {:class (stl/css :set-item-container) :on-click #(swap! collapsed? not)}
:on-click on-click} chevron-icon])
[:div {:class (stl/css-case :set-item-group (= type :group) [:span {:class (stl/css :icon)}
:set-item-set (= type :set) (if set? i/document i/group)]
:selected-set (and (= type :set) @selected?))} [:div {:class (stl/css :set-name)} name]
(when (= type :group) [:div {:class (stl/css :delete-set)}
[:span {:class (stl/css-case [:button {:on-click #(on-delete-token-set-click id %)}
:collapsabled-icon true i/delete]]
:collapsed @collapsed?) (when set?
:on-click #(when (= type :group) (swap! collapsed? not))} [:span {:class (stl/css :action-btn)
chevron-icon]) :on-click #(on-toggle-token-set-click id %)}
[:span {:class (stl/css :icon)} icon] (if visible? i/shown i/hide)])]
[:div {:class (stl/css :set-name)} name] #_(when (and children (not @collapsed?))
(when (= type :set) [:div {:class (stl/css :set-children)}
[:span {:class (stl/css :action-btn) (for [child-id children]
:on-click #(swap! visible? not)} [:& sets-tree (assoc props :key child-id
(if @visible? {:key child-id}
i/shown :set-id child-id
i/hide)])] :selected-set-id selected-token-set-id)])])]))
(when (and children (not @collapsed?))
[:div {:class (stl/css :set-children)}
(for [child-id children]
[:& sets-tree {:key child-id :set-id child-id :selected-set-id selected-set-id}])])]))))
(mf/defc sets-list (mf/defc sets-list
[{:keys [selected-set-id]}] [{:keys []}]
[:ul {:class (stl/css :sets-list)} (let [token-sets (mf/deref refs/workspace-token-sets)
(for [set-id sets-root-order] selected-token-set-id (mf/deref refs/workspace-selected-token-set-id)
[:& sets-tree {:key set-id token-set-selected? (mf/use-callback
:set-id set-id (mf/deps selected-token-set-id)
:selected-set-id selected-set-id}])]) (fn [id]
(= id selected-token-set-id)))
active-token-set-ids (mf/deref refs/workspace-active-set-ids)
token-set-active? (mf/use-callback
(mf/deps active-token-set-ids)
(fn [id]
(get active-token-set-ids id)))]
[:ul {:class (stl/css :sets-list)}
(for [[id token-set] token-sets]
[:& sets-tree
{:key id
:token-set token-set
:selected-token-set-id selected-token-set-id
:token-set-selected? token-set-selected?
:token-set-active? token-set-active?}])]))

View file

@ -8,7 +8,7 @@
.sets-list { .sets-list {
width: 100%; width: 100%;
margin-bottom: $s-12; margin-bottom: 0;
overflow-y: auto; overflow-y: auto;
} }
@ -28,6 +28,8 @@
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
color: var(--layer-row-foreground-color); color: var(--layer-row-foreground-color);
padding-right: $s-2;
.set-name { .set-name {
@include textEllipsis; @include textEllipsis;
flex-grow: 1; flex-grow: 1;
@ -56,6 +58,10 @@
background-color: var(--layer-row-background-color-hover); background-color: var(--layer-row-background-color-hover);
color: var(--layer-row-foreground-color-hover); color: var(--layer-row-foreground-color-hover);
box-shadow: -100px 0 0 0 var(--layer-row-background-color-hover); box-shadow: -100px 0 0 0 var(--layer-row-background-color-hover);
.delete-set {
visibility: visible;
}
} }
} }
@ -65,6 +71,27 @@
box-shadow: -100px 0 0 0 var(--layer-row-background-color-selected); box-shadow: -100px 0 0 0 var(--layer-row-background-color-selected);
} }
.delete-set {
@extend .button-tertiary;
height: $s-28;
width: $s-28;
visibility: hidden;
button {
@include buttonStyle;
@include flexCenter;
width: $s-24;
height: 100%;
svg {
@extend .button-icon-small;
height: $s-12;
width: $s-12;
color: transparent;
fill: none;
stroke: var(--icon-foreground);
}
}
}
.action-btn { .action-btn {
@extend .button-tertiary; @extend .button-tertiary;
height: $s-28; height: $s-28;

View file

@ -10,12 +10,14 @@
[app.common.data :as d] [app.common.data :as d]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.tokens :as dt] [app.main.data.tokens :as dt]
[app.main.data.tokens :as wdt]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.title-bar :refer [title-bar]] [app.main.ui.components.title-bar :refer [title-bar]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.assets.common :as cmm] [app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.common :refer [labeled-input]]
[app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]] [app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]]
[app.main.ui.workspace.tokens.core :as wtc] [app.main.ui.workspace.tokens.core :as wtc]
[app.main.ui.workspace.tokens.sets :refer [sets-list]] [app.main.ui.workspace.tokens.sets :refer [sets-list]]
@ -23,6 +25,7 @@
[app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-types :as wtty] [app.main.ui.workspace.tokens.token-types :as wtty]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.storage :refer [storage]]
[cuerdas.core :as str] [cuerdas.core :as str]
[okulary.core :as l] [okulary.core :as l]
[rumext.v2 :as mf] [rumext.v2 :as mf]
@ -37,11 +40,19 @@
(def selected-set-id (def selected-set-id
(l/derived :selected-set-id st/state)) (l/derived :selected-set-id st/state))
;; Event Functions -------------------------------------------------------------
(defn on-set-add-click [_event]
(when-let [set-name (js/window.prompt "Set name")]
(st/emit! (wdt/create-token-set {:name set-name}))))
;; Components ------------------------------------------------------------------
(mf/defc token-pill (mf/defc token-pill
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [on-click token highlighted? on-context-menu]}] [{:keys [on-click token theme-token highlighted? on-context-menu] :as props}]
(let [{:keys [name value resolved-value errors]} token (let [{:keys [name value resolved-value errors]} token
errors? (seq errors)] errors? (and (seq errors) (seq (:errors theme-token)))]
[:button {:class (stl/css-case :token-pill true [:button {:class (stl/css-case :token-pill true
:token-pill-highlighted highlighted? :token-pill-highlighted highlighted?
:token-pill-invalid errors?) :token-pill-invalid errors?)
@ -75,7 +86,7 @@
i/add)) i/add))
(mf/defc token-component (mf/defc token-component
[{:keys [type tokens selected-shapes token-type-props]}] [{:keys [type tokens selected-shapes token-type-props active-theme-tokens]}]
(let [open? (mf/deref (-> (l/key type) (let [open? (mf/deref (-> (l/key type)
(l/derived lens:token-type-open-status))) (l/derived lens:token-type-open-status)))
{:keys [modal attributes all-attributes title]} token-type-props {:keys [modal attributes all-attributes title]} token-type-props
@ -95,6 +106,7 @@
(fn [event] (fn [event]
(let [{:keys [key fields]} modal] (let [{:keys [key fields]} modal]
(dom/stop-propagation event) (dom/stop-propagation event)
(st/emit! (dt/set-token-type-section-open type true))
(modal/show! key {:x (.-clientX ^js event) (modal/show! key {:x (.-clientX ^js event)
:y (.-clientY ^js event) :y (.-clientY ^js event)
:position :right :position :right
@ -115,7 +127,6 @@
[:& cmm/asset-section {:icon (mf/fnc icon-wrapper [_] [:& cmm/asset-section {:icon (mf/fnc icon-wrapper [_]
[:div {:class (stl/css :section-icon)} [:div {:class (stl/css :section-icon)}
[:& token-section-icon {:type type}]]) [:& token-section-icon {:type type}]])
:title title :title title
:assets-count tokens-count :assets-count tokens-count
:open? open?} :open? open?}
@ -127,12 +138,14 @@
[:& cmm/asset-section-block {:role :content} [:& cmm/asset-section-block {:role :content}
[:div {:class (stl/css :token-pills-wrapper)} [:div {:class (stl/css :token-pills-wrapper)}
(for [token (sort-by :modified-at tokens)] (for [token (sort-by :modified-at tokens)]
[:& token-pill (let [theme-token (get active-theme-tokens (wtt/token-identifier token))]
{:key (:id token) [:& token-pill
:token token {:key (:id token)
:highlighted? (wtt/shapes-token-applied? token selected-shapes (or all-attributes attributes)) :token token
:on-click #(on-token-pill-click % token) :theme-token theme-token
:on-context-menu #(on-context-menu % token)}])]])]])) :highlighted? (wtt/shapes-token-applied? token selected-shapes (or all-attributes attributes))
:on-click #(on-token-pill-click % token)
:on-context-menu #(on-context-menu % token)}]))]])]]))
(defn sorted-token-groups (defn sorted-token-groups
"Separate token-types into groups of `:empty` or `:filled` depending if tokens exist for that type. "Separate token-types into groups of `:empty` or `:filled` depending if tokens exist for that type.
@ -149,32 +162,77 @@
{:empty (sort-by :token-key empty) {:empty (sort-by :token-key empty)
:filled (sort-by :token-key filled)})) :filled (sort-by :token-key filled)}))
(mf/defc tokens-explorer (mf/defc tokene-theme-create
[_props] [_props]
(let [objects (mf/deref refs/workspace-page-objects) (let [group (mf/use-state "")
name (mf/use-state "")]
[:div {:style {:display "flex"
:flex-direction "column"
:gap "10px"}}
[:& labeled-input {:label "Group name"
:input-props {:value @group
:on-change #(reset! group (dom/event->value %))}}]
[:& labeled-input {:label "Theme name"
:input-props {:value @name
:on-change #(reset! name (dom/event->value %))}}]
[:button {:on-click #(st/emit! (wdt/create-token-theme {:group @group
:name @name}))}
"Create"]]))
selected (mf/deref refs/selected-shapes) (mf/defc themes-sidebar
selected-shapes (into [] (keep (d/getf objects)) selected) [_props]
(let [open? (mf/use-state true)
tokens (-> (mf/deref refs/workspace-tokens) active-theme-ids (mf/deref refs/workspace-active-theme-ids)
(sd/use-resolved-tokens)) themes (mf/deref refs/workspace-ordered-token-themes)]
token-groups (mf/with-memo [tokens] [:div {:class (stl/css :sets-sidebar)}
(sorted-token-groups tokens))] [:div {:class (stl/css :sidebar-header)}
[:article [:& title-bar {:collapsable true
[:& token-context-menu] :collapsed (not @open?)
[:div.assets-bar :all-clickable true
(for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups) :title "THEMES"
(:empty token-groups))] :on-collapsed #(swap! open? not)}]]
[:& token-component {:key token-key (when @open?
:type token-key [:div
:selected-shapes selected-shapes [:style
:tokens tokens (str "@scope {"
:token-type-props token-type-props}])]])) (str/join "\n"
["ul { list-style-type: circle; margin-left: 20px; }"
".spaced { display: flex; gap: 10px; justify-content: space-between; }"
".spaced-y { display: flex; flex-direction: column; gap: 10px }"
".selected { font-weight: 600; }"
"b { font-weight: 600; }"])
"}")]
[:div.spaced-y
{:style {:padding "10px"}}
[:& tokene-theme-create]
[:div.spaced-y
[:b "Themes"]
[:ul
(for [[group themes] themes]
[:li
{:key (str "token-theme-group" group)}
group
[:ul
(for [{:keys [id name] :as _theme} themes]
[:li {:key (str "tokene-theme-" id)}
[:div.spaced
name
[:div.spaced
[:button
{:on-click (fn [e]
(dom/prevent-default e)
(dom/stop-propagation e)
(st/emit! (wdt/toggle-token-theme id)))}
(if (get active-theme-ids id) "✅" "❎")]
[:button {:on-click (fn [e]
(dom/prevent-default e)
(dom/stop-propagation e)
(st/emit! (wdt/delete-token-theme id)))}
"🗑️"]]]])]])]]]])]))
(mf/defc sets-sidebar (mf/defc sets-sidebar
[] []
(let [selected-set-id (mf/deref selected-set-id) (let [open? (mf/use-state true)]
open? (mf/use-state true)]
[:div {:class (stl/css :sets-sidebar)} [:div {:class (stl/css :sets-sidebar)}
[:div {:class (stl/css :sidebar-header)} [:div {:class (stl/css :sidebar-header)}
[:& title-bar {:collapsable true [:& title-bar {:collapsable true
@ -183,19 +241,76 @@
:title "SETS" :title "SETS"
:on-collapsed #(swap! open? not)}] :on-collapsed #(swap! open? not)}]
[:button {:class (stl/css :add-set) [:button {:class (stl/css :add-set)
:on-click #(println "Add Set")} :on-click #(do
(reset! open? true)
(on-set-add-click %))}
i/add]] i/add]]
(when @open? (when @open?
[:& sets-list {:selected-set-id selected-set-id}])])) [:& sets-list])]))
(mf/defc tokens-explorer
[_props]
(let [open? (mf/use-state true)
objects (mf/deref refs/workspace-page-objects)
selected (mf/deref refs/selected-shapes)
selected-shapes (into [] (keep (d/getf objects)) selected)
active-theme-tokens (sd/use-active-theme-sets-tokens)
tokens (sd/use-resolved-workspace-tokens)
token-groups (mf/with-memo [tokens]
(sorted-token-groups tokens))]
[:article
[:& token-context-menu]
[:& title-bar {:collapsable true
:collapsed (not @open?)
:all-clickable true
:title "TOKENS"
:on-collapsed #(swap! open? not)}]
(when @open?
[:div.assets-bar
(for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups)
(:empty token-groups))]
[:& token-component {:key token-key
:type token-key
:selected-shapes selected-shapes
:active-theme-tokens active-theme-tokens
:tokens tokens
:token-type-props token-type-props}])])]))
(defn dev-or-preview-url? [url]
(let [host (-> url js/URL. .-host)
localhost? (= "localhost" (first (str/split host #":")))
pr? (str/ends-with? host "penpot.alpha.tokens.studio")]
(or localhost? pr?)))
(defn location-url-dev-or-preview-url!? []
(dev-or-preview-url? js/window.location.href))
(defn temp-use-themes-flag []
(let [show? (mf/use-state (or
(location-url-dev-or-preview-url!?)
(get @storage ::show-token-themes-sets?)
false))]
(mf/use-effect
(fn []
(letfn [(toggle! []
(swap! storage update ::show-token-themes-sets? not)
(reset! show? (get @storage ::show-token-themes-sets?)))]
(set! js/window.toggleThemes toggle!))))
show?))
(mf/defc tokens-sidebar-tab (mf/defc tokens-sidebar-tab
{::mf/wrap [mf/memo] {::mf/wrap [mf/memo]
::mf/wrap-props false} ::mf/wrap-props false}
[_props] [_props]
(let [show-sets-section? false] ;; temporarily added this variable to see/hide the sets section till we have it working end to end (let [show-sets-section? (deref (temp-use-themes-flag))]
[:div {:class (stl/css :sidebar-tab-wrapper)} [:div {:class (stl/css :sidebar-tab-wrapper)}
(when show-sets-section? (when show-sets-section?
[:div {:class (stl/css :sets-section-wrapper)} [:div {:class (stl/css :sets-section-wrapper)}
[:& themes-sidebar]
[:& sets-sidebar]]) [:& sets-sidebar]])
[:div {:class (stl/css :tokens-section-wrapper)} [:div {:class (stl/css :tokens-section-wrapper)}
[:& tokens-explorer]] [:& tokens-explorer]]

View file

@ -2,6 +2,7 @@
(:require (:require
["@tokens-studio/sd-transforms" :as sd-transforms] ["@tokens-studio/sd-transforms" :as sd-transforms]
["style-dictionary$default" :as sd] ["style-dictionary$default" :as sd]
[app.common.data :refer [ordered-map]]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token :as wtt]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -73,13 +74,15 @@
(get errors :style-dictionary/missing-reference))) (get errors :style-dictionary/missing-reference)))
(defn resolve-tokens+ (defn resolve-tokens+
[tokens & {:keys [debug?] :as config}] [tokens & {:keys [names-map? debug?] :as config}]
(p/let [sd-tokens (-> (wtt/token-names-tree tokens) (p/let [sd-tokens (-> (wtt/token-names-tree tokens)
(resolve-sd-tokens+ config))] (resolve-sd-tokens+ config))]
(let [resolved-tokens (reduce (let [resolved-tokens (reduce
(fn [acc ^js cur] (fn [acc ^js cur]
(let [id (uuid (.-uuid (.-id cur))) (let [identifier (if names-map?
origin-token (get tokens id) (.. cur -original -name)
(uuid (.-uuid (.-id cur))))
origin-token (get tokens identifier)
parsed-value (wtt/parse-token-value (.-value cur)) parsed-value (wtt/parse-token-value (.-value cur))
resolved-token (if (not parsed-value) resolved-token (if (not parsed-value)
(assoc origin-token :errors [:style-dictionary/missing-reference]) (assoc origin-token :errors [:style-dictionary/missing-reference])
@ -92,15 +95,12 @@
(js/console.log "Resolved tokens" resolved-tokens)) (js/console.log "Resolved tokens" resolved-tokens))
resolved-tokens))) resolved-tokens)))
(defn resolve-workspace-tokens+
[& {:keys [debug?] :as config}]
(when-let [workspace-tokens @refs/workspace-tokens]
(resolve-tokens+ workspace-tokens)))
;; Hooks ----------------------------------------------------------------------- ;; Hooks -----------------------------------------------------------------------
(defonce !tokens-cache (atom nil)) (defonce !tokens-cache (atom nil))
(defonce !theme-tokens-cache (atom nil))
(defn get-cached-tokens [tokens] (defn get-cached-tokens [tokens]
(get @!tokens-cache tokens tokens)) (get @!tokens-cache tokens tokens))
@ -109,11 +109,12 @@
This hook will return the unresolved tokens as state until they are processed, This hook will return the unresolved tokens as state until they are processed,
then the state will be updated with the resolved tokens." then the state will be updated with the resolved tokens."
[tokens & {:keys [cache-atom] [tokens & {:keys [cache-atom _names-map?]
:or {cache-atom !tokens-cache}}] :or {cache-atom !tokens-cache}
(let [tokens-state (mf/use-state (get @cache-atom tokens tokens))] :as config}]
(let [tokens-state (mf/use-state (get @cache-atom tokens))]
(mf/use-effect (mf/use-effect
(mf/deps tokens) (mf/deps tokens config)
(fn [] (fn []
(let [cached (get @cache-atom tokens)] (let [cached (get @cache-atom tokens)]
(cond (cond
@ -122,7 +123,7 @@
;; Get the cached entry ;; Get the cached entry
(some? cached) (reset! tokens-state cached) (some? cached) (reset! tokens-state cached)
;; No cached entry, start processing ;; No cached entry, start processing
:else (let [promise+ (resolve-tokens+ tokens)] :else (let [promise+ (resolve-tokens+ tokens config)]
(swap! cache-atom assoc tokens promise+) (swap! cache-atom assoc tokens promise+)
(p/then promise+ (fn [resolved-tokens] (p/then promise+ (fn [resolved-tokens]
(swap! cache-atom assoc tokens resolved-tokens) (swap! cache-atom assoc tokens resolved-tokens)
@ -130,39 +131,11 @@
@tokens-state)) @tokens-state))
(defn use-resolved-workspace-tokens [& {:as config}] (defn use-resolved-workspace-tokens [& {:as config}]
(-> (mf/deref refs/workspace-tokens) (-> (mf/deref refs/workspace-selected-token-set-tokens)
(use-resolved-tokens config))) (use-resolved-tokens config)))
;; Testing --------------------------------------------------------------------- (defn use-active-theme-sets-tokens [& {:as config}]
(-> (mf/deref refs/workspace-active-theme-sets-tokens)
(comment (use-resolved-tokens (merge {:cache-atom !theme-tokens-cache
(defonce !output (atom nil)) :names-map? true}
config))))
(-> @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+)
(#(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)]
(.then (resolve-sd-tokens+ example {:debug? true})
#(reset! output %)))
nil)

View file

@ -4,6 +4,14 @@
[clojure.set :as set] [clojure.set :as set]
[cuerdas.core :as str])) [cuerdas.core :as str]))
(defn get-workspace-tokens
[state]
(get-in state [:workspace-data :tokens] {}))
(defn get-workspace-token
[token-id state]
(get-in state [:workspace-data :tokens token-id]))
(def parseable-token-value-regexp (def parseable-token-value-regexp
"Regexp that can be used to parse a number value out of resolved token value. "Regexp that can be used to parse a number value out of resolved token value.
This regexp also trims whitespace around the value." This regexp also trims whitespace around the value."

View file

@ -0,0 +1,150 @@
(ns app.main.ui.workspace.tokens.token-set
(:require
[app.common.data :refer [ordered-map]]
[app.main.ui.workspace.tokens.token :as wtt]
[clojure.set :as set]))
;; Themes ----------------------------------------------------------------------
(defn get-theme-group [theme]
(:group theme))
(defn get-workspace-themes [state]
(get-in state [:workspace-data :token-themes] []))
(defn get-workspace-themes-index [state]
(get-in state [:workspace-data :token-themes-index] {}))
(defn get-workspace-token-set-groups [state]
(get-in state [:workspace-data :token-set-groups]))
(defn get-workspace-ordered-themes [state]
(let [themes (get-workspace-themes state)
themes-index (get-workspace-themes-index state)]
(->> (map #(get themes-index %) themes)
(group-by :group))))
(defn get-active-theme-ids [state]
(get-in state [:workspace-data :token-active-themes] #{}))
(defn get-temp-theme-id [state]
(get-in state [:workspace-data :token-theme-temporary-id]))
(defn get-active-theme-ids-or-fallback [state]
(let [active-theme-ids (get-active-theme-ids state)
temp-theme-id (get-temp-theme-id state)]
(cond
(seq active-theme-ids) active-theme-ids
temp-theme-id #{temp-theme-id})))
(defn get-active-set-ids [state]
(let [active-theme-ids (get-active-theme-ids-or-fallback state)
themes-index (get-workspace-themes-index state)
active-set-ids (reduce
(fn [acc cur]
(if-let [sets (get-in themes-index [cur :sets])]
(set/union acc sets)
acc))
#{} active-theme-ids)]
active-set-ids))
(defn get-ordered-active-set-ids [state]
(let [active-set-ids (get-active-set-ids state)
token-set-groups (get-workspace-token-set-groups state)]
(filter active-set-ids token-set-groups)))
(defn theme-ids-with-group
"Returns set of theme-ids that share the same `:group` property as the theme with `theme-id`.
Will also return matching theme-ids without a `:group` property."
[theme-id state]
(let [themes (get-workspace-themes-index state)
theme-group (get-in themes [theme-id :group])
same-group-theme-ids (->> themes
(eduction
(map val)
(filter #(= (:group %) theme-group))
(map :id))
(into #{}))]
same-group-theme-ids))
(defn toggle-active-theme-id
"Toggle a `theme-id` by checking `:token-active-themes`.
De-activate all theme-ids that have the same group as `theme-id` when activating `theme-id`.
Ensures that the temporary theme id is selected when the resulting set is empty."
[theme-id state]
(let [temp-theme-id-set (some->> (get-temp-theme-id state) (conj #{}))
active-theme-ids (get-active-theme-ids state)
add? (not (get active-theme-ids theme-id))
;; Deactivate themes with the same group when activating a theme
same-group-ids (when add? (theme-ids-with-group theme-id state))
theme-ids-without-same-group (set/difference active-theme-ids
same-group-ids
temp-theme-id-set)
new-themes (if add?
(conj theme-ids-without-same-group theme-id)
(disj theme-ids-without-same-group theme-id))]
(if (empty? new-themes)
(or temp-theme-id-set #{})
new-themes)))
(defn update-theme-id
[state]
(let [active-themes (get-active-theme-ids state)
temporary-theme-id (get-temp-theme-id state)]
(cond
(empty? active-themes) temporary-theme-id
(= 1 (count active-themes)) (first active-themes)
:else temporary-theme-id)))
(defn get-workspace-token-theme [id state]
(get-in state [:workspace-data :token-themes-index id]))
(defn add-token-set-to-token-theme [token-set-id token-theme]
(update token-theme :sets conj token-set-id))
(defn toggle-token-set-to-token-theme [token-set-id token-theme]
(update token-theme :sets #(if (get % token-set-id)
(disj % token-set-id)
(conj % token-set-id))))
;; Sets ------------------------------------------------------------------------
(defn get-workspace-sets [state]
(get-in state [:workspace-data :token-sets-index]))
(defn get-token-set [set-id state]
(some-> (get-workspace-sets state)
(get set-id)))
(defn get-workspace-token-set-tokens [set-id state]
(-> (get-token-set set-id state)
:tokens))
(defn get-selected-token-set-id [state]
(or (get-in state [:workspace-local :selected-token-set-id])
(get-in state [:workspace-data :token-set-groups 0])))
(defn get-selected-token-set [state]
(when-let [id (get-selected-token-set-id state)]
(get-token-set id state)))
(defn get-selected-token-set-tokens [state]
(when-let [token-set (get-selected-token-set state)]
(let [tokens (or (wtt/get-workspace-tokens state) {})]
(select-keys tokens (:tokens token-set)))))
(defn assoc-selected-token-set-id [state id]
(assoc-in state [:workspace-local :selected-token-set-id] id))
(defn get-active-theme-sets-tokens-names-map [state]
(let [active-set-ids (get-ordered-active-set-ids state)]
(reduce
(fn [names-map-acc set-id]
(let [token-ids (get-workspace-token-set-tokens set-id state)]
(reduce
(fn [acc token-id]
(if-let [token (wtt/get-workspace-token token-id state)]
(assoc acc (wtt/token-identifier token) token)
acc))
names-map-acc token-ids)))
(ordered-map) active-set-ids)))

View file

@ -6,6 +6,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.style-dictionary :as wtsd] [app.main.ui.workspace.tokens.style-dictionary :as wtsd]
[app.main.ui.workspace.tokens.token-set :as wtts]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[clojure.data :as data] [clojure.data :as data]
[clojure.set :as set] [clojure.set :as set]
@ -120,13 +121,14 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(->> (->>
(rx/from (wtsd/resolve-tokens+ (get-in state [:workspace-data :tokens]))) (rx/from
(->
(wtts/get-active-theme-sets-tokens-names-map state)
(wtsd/resolve-tokens+ {:names-map? true})))
(rx/mapcat (rx/mapcat
(fn [sd-tokens] (fn [sd-tokens]
(let [undo-id (js/Symbol)] (let [undo-id (js/Symbol)]
(rx/concat (rx/concat
(rx/of (dwu/start-undo-transaction undo-id)) (rx/of (dwu/start-undo-transaction undo-id))
(rx/concat (update-tokens sd-tokens)
(->> (update-tokens sd-tokens)
(rx/concat)))
(rx/of (dwu/commit-undo-transaction undo-id)))))))))) (rx/of (dwu/commit-undo-transaction undo-id))))))))))

View file

@ -4,8 +4,10 @@
[app.common.test-helpers.compositions :as ctho] [app.common.test-helpers.compositions :as ctho]
[app.common.test-helpers.files :as cthf] [app.common.test-helpers.files :as cthf]
[app.common.test-helpers.shapes :as cths] [app.common.test-helpers.shapes :as cths]
[app.main.data.tokens :as wdt]
[app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-set :as wtts]
[cljs.test :as t :include-macros true] [cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp] [frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths] [frontend-tests.helpers.state :as ths]
@ -18,24 +20,70 @@
(log/set-level! "app.main.data.changes" :error) (log/set-level! "app.main.data.changes" :error)
(thp/reset-idmap!))}) (thp/reset-idmap!))})
(defn setup-file (defn setup-file []
(cthf/sample-file :file-1 :page-label :page-1))
(def border-radius-token
{:value "12"
:name "borderRadius.sm"
:type :border-radius})
(def ^:private reference-border-radius-token
{:value "{borderRadius.sm} * 2"
:name "borderRadius.md"
:type :border-radius})
(defn setup-file-with-tokens
[& {:keys [rect-1 rect-2 rect-3]}] [& {:keys [rect-1 rect-2 rect-3]}]
(-> (cthf/sample-file :file-1 :page-label :page-1) (-> (setup-file)
(ctho/add-rect :rect-1 rect-1) (ctho/add-rect :rect-1 rect-1)
(ctho/add-rect :rect-2 rect-2) (ctho/add-rect :rect-2 rect-2)
(ctho/add-rect :rect-3 rect-3) (ctho/add-rect :rect-3 rect-3)
(toht/add-token :token-1 {:value "12" (toht/add-token :token-1 border-radius-token)
:name "borderRadius.sm" (toht/add-token :token-2 reference-border-radius-token)))
:type :border-radius})
(toht/add-token :token-2 {:value "{borderRadius.sm} * 2" (t/deftest test-create-token
:name "borderRadius.md" (t/testing "creates token in new token set"
:type :border-radius}))) (t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(wdt/update-create-token border-radius-token)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [set-id (wtts/get-selected-token-set-id new-state)
token-set (wtts/get-token-set set-id new-state)
set-tokens (wtts/get-active-theme-sets-tokens-names-map new-state)]
(t/testing "selects created workspace set and adds token to it"
(t/is (some? token-set))
(t/is (= 1 (count set-tokens)))
(t/is (= (list border-radius-token) (->> (vals set-tokens)
(map #(dissoc % :id :modified-at)))))))))))))
(t/deftest test-create-multiple-tokens
(t/testing "uses selected tokens set when creating multiple tokens"
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(wdt/update-create-token border-radius-token)
(wdt/update-create-token reference-border-radius-token)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [set-tokens (wtts/get-active-theme-sets-tokens-names-map new-state)]
(t/testing "selects created workspace set and adds token to it"
(t/is (= 2 (count set-tokens)))
(t/is (= (list border-radius-token reference-border-radius-token)
(->> (vals set-tokens)
(map #(dissoc % :id :modified-at)))))))))))))
(t/deftest test-apply-token (t/deftest test-apply-token
(t/testing "applies token to shape and updates shape attributes to resolved value" (t/testing "applies token to shape and updates shape attributes to resolved value"
(t/async (t/async
done done
(let [file (setup-file) (let [file (setup-file-with-tokens)
store (ths/setup-store file) store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1) rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)] events [(wtch/apply-token {:shape-ids [(:id rect-1)]
@ -60,7 +108,7 @@
(t/testing "applying a token twice with the same attributes will override the previously applied tokens values" (t/testing "applying a token twice with the same attributes will override the previously applied tokens values"
(t/async (t/async
done done
(let [file (setup-file) (let [file (setup-file-with-tokens)
store (ths/setup-store file) store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1) rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)] events [(wtch/apply-token {:shape-ids [(:id rect-1)]
@ -89,7 +137,7 @@
(t/testing "removes old token attributes and applies only single attribute" (t/testing "removes old token attributes and applies only single attribute"
(t/async (t/async
done done
(let [file (setup-file) (let [file (setup-file-with-tokens)
store (ths/setup-store file) store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1) rect-1 (cths/get-shape file :rect-1)
events [;; Apply `:token-1` to all border radius attributes events [;; Apply `:token-1` to all border radius attributes
@ -123,7 +171,7 @@
(t/testing "applies dimensions token and updates the shapes width and height" (t/testing "applies dimensions token and updates the shapes width and height"
(t/async (t/async
done done
(let [file (-> (setup-file) (let [file (-> (setup-file-with-tokens)
(toht/add-token :token-target {:value "100" (toht/add-token :token-target {:value "100"
:name "dimensions.sm" :name "dimensions.sm"
:type :dimensions})) :type :dimensions}))
@ -151,7 +199,7 @@
(t/testing "applies sizing token and updates the shapes width and height" (t/testing "applies sizing token and updates the shapes width and height"
(t/async (t/async
done done
(let [file (-> (setup-file) (let [file (-> (setup-file-with-tokens)
(toht/add-token :token-target {:value "100" (toht/add-token :token-target {:value "100"
:name "sizing.sm" :name "sizing.sm"
:type :sizing})) :type :sizing}))
@ -179,7 +227,7 @@
(t/testing "applies opacity token and updates the shapes opacity" (t/testing "applies opacity token and updates the shapes opacity"
(t/async (t/async
done done
(let [file (-> (setup-file) (let [file (-> (setup-file-with-tokens)
(toht/add-token :opacity-float {:value "0.3" (toht/add-token :opacity-float {:value "0.3"
:name "opacity.float" :name "opacity.float"
:type :opacity}) :type :opacity})
@ -229,7 +277,7 @@
(t/testing "applies rotation token and updates the shapes rotation" (t/testing "applies rotation token and updates the shapes rotation"
(t/async (t/async
done done
(let [file (-> (setup-file) (let [file (-> (setup-file-with-tokens)
(toht/add-token :token-target {:value "120" (toht/add-token :token-target {:value "120"
:name "rotation.medium" :name "rotation.medium"
:type :rotation})) :type :rotation}))
@ -253,11 +301,11 @@
(t/testing "applies stroke-width token and updates the shapes with stroke" (t/testing "applies stroke-width token and updates the shapes with stroke"
(t/async (t/async
done done
(let [file (-> (setup-file {:rect-1 {:strokes [{:stroke-alignment :inner, (let [file (-> (setup-file-with-tokens {:rect-1 {:strokes [{:stroke-alignment :inner,
:stroke-style :solid, :stroke-style :solid,
:stroke-color "#000000", :stroke-color "#000000",
:stroke-opacity 1, :stroke-opacity 1,
:stroke-width 5}]}}) :stroke-width 5}]}})
(toht/add-token :token-target {:value "10" (toht/add-token :token-target {:value "10"
:name "stroke-width.sm" :name "stroke-width.sm"
:type :stroke-width})) :type :stroke-width}))
@ -286,7 +334,7 @@
(t/testing "should apply token to all selected items, where no item has the token applied" (t/testing "should apply token to all selected items, where no item has the token applied"
(t/async (t/async
done done
(let [file (setup-file) (let [file (setup-file-with-tokens)
store (ths/setup-store file) store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1) rect-1 (cths/get-shape file :rect-1)
rect-2 (cths/get-shape file :rect-2) rect-2 (cths/get-shape file :rect-2)
@ -314,7 +362,7 @@
(t/testing "should unapply given token if one of the selected items has the token applied while keeping other tokens with some attributes" (t/testing "should unapply given token if one of the selected items has the token applied while keeping other tokens with some attributes"
(t/async (t/async
done done
(let [file (-> (setup-file) (let [file (-> (setup-file-with-tokens)
(toht/apply-token-to-shape :rect-1 :token-1 #{:rx :ry}) (toht/apply-token-to-shape :rect-1 :token-1 #{:rx :ry})
(toht/apply-token-to-shape :rect-3 :token-2 #{:rx :ry})) (toht/apply-token-to-shape :rect-3 :token-2 #{:rx :ry}))
store (ths/setup-store file) store (ths/setup-store file)
@ -348,7 +396,7 @@
(t/testing "should apply token to all if none of the shapes has it applied" (t/testing "should apply token to all if none of the shapes has it applied"
(t/async (t/async
done done
(let [file (-> (setup-file) (let [file (-> (setup-file-with-tokens)
(toht/apply-token-to-shape :rect-1 :token-2 #{:rx :ry}) (toht/apply-token-to-shape :rect-1 :token-2 #{:rx :ry})
(toht/apply-token-to-shape :rect-3 :token-2 #{:rx :ry})) (toht/apply-token-to-shape :rect-3 :token-2 #{:rx :ry}))
store (ths/setup-store file) store (ths/setup-store file)

View file

@ -2,7 +2,8 @@
(:require (:require
[app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.style-dictionary :as sd]
[cljs.test :as t :include-macros true] [cljs.test :as t :include-macros true]
[promesa.core :as p])) [promesa.core :as p]
[app.main.ui.workspace.tokens.token :as wtt]))
(def border-radius-token (def border-radius-token
{:id #uuid "8c868278-7c8d-431b-bbc9-7d8f15c8edb9" {:id #uuid "8c868278-7c8d-431b-bbc9-7d8f15c8edb9"
@ -22,7 +23,7 @@
(t/deftest resolve-tokens-test (t/deftest resolve-tokens-test
(t/async (t/async
done done
(t/testing "resolves tokens using style-dictionary" (t/testing "resolves tokens using style-dictionary from a ids map"
(-> (sd/resolve-tokens+ tokens) (-> (sd/resolve-tokens+ tokens)
(p/finally (fn [resolved-tokens] (p/finally (fn [resolved-tokens]
(let [expected-tokens {"borderRadius.sm" (let [expected-tokens {"borderRadius.sm"
@ -35,3 +36,22 @@
:resolved-unit "px")}] :resolved-unit "px")}]
(t/is (= expected-tokens resolved-tokens)) (t/is (= expected-tokens resolved-tokens))
(done)))))))) (done))))))))
(t/deftest resolve-tokens-names-map-test
(t/async
done
(t/testing "resolves tokens using style-dictionary from a names map"
(-> (vals tokens)
(wtt/token-names-map)
(sd/resolve-tokens+ {:names-map? true})
(p/finally (fn [resolved-tokens]
(let [expected-tokens {"borderRadius.sm"
(assoc border-radius-token
:resolved-value 12
:resolved-unit "px")
"borderRadius.md-with-dashes"
(assoc reference-border-radius-token
:resolved-value 24
:resolved-unit "px")}]
(t/is (= expected-tokens resolved-tokens))
(done))))))))

View file

@ -0,0 +1,37 @@
(ns token-tests.token-set-test
(:require
[app.main.ui.workspace.tokens.token-set :as wtts]
[cljs.test :as t]))
(t/deftest toggle-active-theme-id-test
(t/testing "toggles active theme id"
(let [state {:workspace-data {:token-themes-index {1 {:id 1}}}}]
(t/testing "activates theme with id")
(t/is (= (wtts/toggle-active-theme-id 1 state) #{1})))
(let [state {:workspace-data {:token-active-themes #{1}
:token-themes-index {1 {:id 1}}}}]
(t/testing "missing temp theme returns empty set"
(t/is (= #{} (wtts/toggle-active-theme-id 1 state)))))
(let [state {:workspace-data {:token-theme-temporary-id :temp
:token-active-themes #{1}
:token-themes-index {1 {:id 1}}}}]
(t/testing "empty set returns temp theme"
(t/is (= #{:temp} (wtts/toggle-active-theme-id 1 state)))))
(let [state {:workspace-data {:token-active-themes #{2 3 4}
:token-themes-index {1 {:id 1}
2 {:id 2}
3 {:id 3}
4 {:id 4 :group :different}}}}]
(t/testing "removes same group themes and keeps different group themes"
(t/is (= #{1 4} (wtts/toggle-active-theme-id 1 state)))))
(let [state {:workspace-data {:token-active-themes #{1 2 3 4}}
:token-themes-index {1 {:id 1}
2 {:id 2}
3 {:id 3}
4 {:id 4 :group :different}}}]
(t/testing "removes theme when active"
(t/is (= #{4 3 2} (wtts/toggle-active-theme-id 1 state)))))))