diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 67353769a0..ec8bb4775a 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -258,7 +258,7 @@ [:update-active-token-themes [:map {:title "UpdateActiveTokenThemes"} [:type [:= :update-active-token-themes]] - [:theme-ids [:set ::sm/uuid]]]] + [:theme-ids [:set :string]]]] [:delete-temporary-token-theme [:map {:title "DeleteTemporaryTokenThemeChange"} @@ -274,14 +274,14 @@ [:mod-token-theme [:map {:title "ModTokenThemeChange"} [:type [:= :mod-token-theme]] - [:id ::sm/uuid] + [:group :string] [:name :string] [:token-theme ::ctot/token-theme]]] [:del-token-theme [:map {:title "DelTokenThemeChange"} [:type [:= :del-token-theme]] - [:id ::sm/uuid] + [:group :string] [:name :string]]] [:add-token-set @@ -292,29 +292,24 @@ [:mod-token-set [:map {:title "ModTokenSetChange"} [:type [:= :mod-token-set]] - [:id ::sm/uuid] [:name :string] [:token-set ::ctot/token-set]]] [:del-token-set [:map {:title "DelTokenSetChange"} [:type [:= :del-token-set]] - [:id ::sm/uuid] [:name :string]]] [:add-token [:map {:title "AddTokenChange"} [:type [:= :add-token]] - [:set-id ::sm/uuid] [:set-name :string] [:token ::cto/token]]] [:mod-token [:map {:title "ModTokenChange"} [:type [:= :mod-token]] - [:set-id ::sm/uuid] [:set-name :string] - [:id ::sm/uuid] [:name :string] [:token ::cto/token]]] @@ -322,7 +317,6 @@ [:map {:title "DelTokenChange"} [:type [:= :del-token]] [:set-name :string] - [:id ::sm/uuid] [:name :string]]]]]) (sm/register! ::changes @@ -793,131 +787,89 @@ ;; -- Tokens (defmethod process-change :add-token - [data {:keys [set-id set-name token]}] - (-> data - (ctol/add-token set-id token) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/add-token-in-set set-name (ctob/make-token token)))))) + [data {:keys [set-name token]}] + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/add-token-in-set set-name (ctob/make-token token))))) (defmethod process-change :mod-token - [data {:keys [set-name id name token]}] - (-> data - (ctol/update-token id merge token) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/update-token-in-set - set-name - name - (fn [old-token] - (ctob/make-token (merge old-token token)))))))) + [data {:keys [set-name name token]}] + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/update-token-in-set + set-name + name + (fn [old-token] + (ctob/make-token (merge old-token token))))))) (defmethod process-change :del-token - [data {:keys [set-name id name]}] - (-> data - (ctol/delete-token id) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/delete-token-from-set - set-name - name))))) - -(defn- set-ids->names - [data sets] - (let [lib-sets (:token-sets-index data) - set-id->name - (fn [set-id] - (dm/get-in lib-sets [set-id :name]))] - (map set-id->name sets))) + [data {:keys [set-name name]}] + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/delete-token-from-set + set-name + name)))) (defmethod process-change :add-temporary-token-theme [data {:keys [token-theme]}] - (-> data - (ctotl/add-temporary-token-theme token-theme) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/add-theme (-> token-theme - (update :sets (partial set-ids->names data)) - (ctob/make-token-theme))))))) + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/add-theme (ctob/make-token-theme token-theme))))) (defmethod process-change :update-active-token-themes [data {:keys [theme-ids]}] - (ctotl/assoc-active-token-themes data theme-ids)) + (update data :tokens-lib #(-> % (ctob/ensure-tokens-lib) + (ctob/set-active-themes theme-ids)))) (defmethod process-change :delete-temporary-token-theme - [data {:keys [id group name]}] - (-> data - (ctotl/delete-temporary-token-theme id) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/delete-theme group name))))) + [data {:keys [group name]}] + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/delete-theme group name)))) (defmethod process-change :add-token-theme [data {:keys [token-theme]}] - (-> data - (ctotl/add-token-theme token-theme) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/add-theme (-> token-theme - (update :sets (partial set-ids->names data)) - (ctob/make-token-theme))))))) + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/add-theme (-> token-theme + (ctob/make-token-theme)))))) (defmethod process-change :mod-token-theme - [data {:keys [id name group token-theme]}] - (-> data - (ctotl/update-token-theme id merge token-theme) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/update-theme name group - (fn [prev-theme] - (merge prev-theme - (-> token-theme - (update :sets (partial set-ids->names data)))))))))) + [data {:keys [name group token-theme]}] + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/update-theme group name + (fn [prev-theme] + (merge prev-theme token-theme)))))) (defmethod process-change :del-token-theme - [data {:keys [id group name]}] - (-> data - (ctotl/delete-token-theme id) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/delete-theme group name))))) + [data {:keys [group name]}] + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/delete-theme group name)))) (defmethod process-change :add-token-set [data {:keys [token-set]}] - (-> data - (ctotl/add-token-set token-set) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/add-set (ctob/make-token-set token-set)))))) + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/add-set (ctob/make-token-set token-set))))) (defmethod process-change :mod-token-set - [data {:keys [id name token-set]}] - (-> data - (ctotl/update-token-set id merge token-set) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/update-set name (fn [prev-set] - (merge prev-set - (dissoc token-set :tokens)))))))) + [data {:keys [name token-set]}] + (update data :tokens-lib (fn [lib] + (let [path-changed? (not= name (:name token-set)) + lib' (-> lib + (ctob/ensure-tokens-lib) + (ctob/update-set name (fn [prev-set] + (merge prev-set (dissoc token-set :tokens)))))] + (cond-> lib' + path-changed? (ctob/update-set-name name (:name token-set))))))) (defmethod process-change :del-token-set - [data {:keys [id name]}] - (-> data - (ctotl/delete-token-set id) - (update :tokens-lib - #(-> % - (ctob/ensure-tokens-lib) - (ctob/delete-set name))))) + [data {:keys [name]}] + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/delete-set name)))) ;; === Operations (defmethod process-operation :set diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 44f57464c3..57978d0d2e 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -20,7 +20,8 @@ [app.common.types.component :as ctk] [app.common.types.file :as ctf] [app.common.types.shape.layout :as ctl] - [app.common.uuid :as uuid])) + [app.common.uuid :as uuid] + [app.common.types.tokens-lib :as ctob])) ;; Auxiliary functions to help create a set of changes (undo + redo) @@ -713,23 +714,28 @@ [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) :name (:name token-theme)}) + (update :undo-changes conj {:type :del-token-theme :group (:group token-theme) :name (:name 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) :name (:name prev-token-theme) :token-theme token-theme}) - (update :undo-changes conj {:type :mod-token-theme :id (:id token-theme) :name (:name token-theme) :token-theme (or prev-token-theme token-theme)}) - (apply-changes-local))) + (let [name (or (:name prev-token-theme) + (:name token-theme)) + group (or (:group prev-token-theme) + (:group token-theme))] + (-> changes + (update :redo-changes conj {:type :mod-token-theme :group group :name name :token-theme token-theme}) + (update :undo-changes conj {:type :mod-token-theme :group group :name name :token-theme (or prev-token-theme token-theme)}) + (apply-changes-local)))) (defn delete-token-theme - [changes token-theme-id] + [changes group name] (assert-library! changes) (let [library-data (::library-data (meta changes)) - prev-token-theme (get-in library-data [:token-themes-index token-theme-id])] + prev-token-theme (some-> (get library-data :tokens-lib) + (ctob/get-theme group name))] (-> changes - (update :redo-changes conj {:type :del-token-theme :id token-theme-id :name (:name prev-token-theme)}) + (update :redo-changes conj {:type :del-token-theme :group group :name name}) (update :undo-changes conj {:type :add-token-theme :token-theme prev-token-theme}) (apply-changes-local)))) @@ -737,48 +743,51 @@ [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) :name (:name token-set)}) + (update :undo-changes conj {:type :del-token-set :name (:name 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) :name (:name prev-token-set) :token-set token-set}) - (update :undo-changes conj {:type :mod-token-set :id (:id token-set) :name (:name prev-token-set) :token-set (or prev-token-set token-set)}) + (update :redo-changes conj {:type :mod-token-set :name (:name prev-token-set) :token-set token-set}) + (update :undo-changes conj {:type :mod-token-set :name (:name token-set) :token-set (or prev-token-set token-set)}) (apply-changes-local))) (defn delete-token-set - [changes token-set-id token-set-name] + [changes token-set-name] (assert-library! changes) (let [library-data (::library-data (meta changes)) - prev-token-set (get-in library-data [:token-sets-index token-set-id])] + prev-token-theme (some-> (get library-data :tokens-lib) + (ctob/get-set token-set-name))] (-> changes - (update :redo-changes conj {:type :del-token-set :id token-set-id :name token-set-name}) - (update :undo-changes conj {:type :add-token-set :token-set prev-token-set}) + (update :redo-changes conj {:type :del-token-set :name token-set-name}) + (update :undo-changes conj {:type :add-token-set :token-set prev-token-theme}) (apply-changes-local)))) (defn add-token - [changes set-id set-name token] + [changes set-name token] (-> changes - (update :redo-changes conj {:type :add-token :set-id set-id :set-name set-name :token token}) - (update :undo-changes conj {:type :del-token :set-name set-name :id (:id token) :name (:name token)}) + (update :redo-changes conj {:type :add-token :set-name set-name :token token}) + (update :undo-changes conj {:type :del-token :set-name set-name :name (:name token)}) (apply-changes-local))) (defn update-token - [changes set-id set-name {:keys [id name] :as token} {prev-name :name :as prev-token}] + [changes set-name token prev-token] (-> changes - (update :redo-changes conj {:type :mod-token :set-id set-id :set-name set-name :id id :name prev-name :token token}) - (update :undo-changes conj {:type :mod-token :set-id set-id :set-name set-name :id id :name name :token (or prev-token token)}) + (update :redo-changes conj {:type :mod-token :set-name set-name :name (:name prev-token) :token token}) + (update :undo-changes conj {:type :mod-token :set-name set-name :name (:name token) :token (or prev-token token)}) (apply-changes-local))) (defn delete-token - [changes set-name token-id token-name] + [changes set-name token-name] (assert-library! changes) (let [library-data (::library-data (meta changes)) - prev-token (get-in library-data [:tokens token-id])] + prev-token (some-> (get library-data :tokens-lib) + (ctob/get-set set-name) + (ctob/get-token token-name))] (-> changes - (update :redo-changes conj {:type :del-token :set-name set-name :id token-id :name token-name}) - (update :undo-changes conj {:type :add-token :set-id uuid/zero :set-name set-name :token prev-token}) + (update :redo-changes conj {:type :del-token :set-name set-name :name token-name}) + (update :undo-changes conj {:type :add-token :set-name set-name :token prev-token}) (apply-changes-local)))) (defn add-component diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index dcf9900f86..69e57a2594 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -64,22 +64,6 @@ [:map-of {:gen/max 5} ::sm/uuid ::media-object]] [:plugin-data {:optional true} [: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]] [:tokens-lib {:optional true} ::ctl/tokens-lib]]) (def check-file-data! diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index ed63908ab2..ea8744d68b 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -58,13 +58,13 @@ [n] (string? n)) +;; TODO Move this to tokens-lib (sm/register! ::token [:map {:title "Token"} - [:id ::sm/uuid] [:name token-name-ref] [:type [::sm/one-of token-types]] [:value :any] - [:description {:optional true} :string] + [:description {:optional true} [:maybe :string]] [:modified-at {:optional true} ::sm/inst]]) (sm/register! ::color diff --git a/common/src/app/common/types/token_theme.cljc b/common/src/app/common/types/token_theme.cljc index d76f1e277d..ed7388995c 100644 --- a/common/src/app/common/types/token_theme.cljc +++ b/common/src/app/common/types/token_theme.cljc @@ -10,35 +10,16 @@ (sm/register! ::token-theme [:map {:title "TokenTheme"} - [:id ::sm/uuid] [:name :string] - [:group {:optional true} :string] - [:source? {:optional true} :boolean] - [:description {:optional true} :string] + [:group :string] + [:description [:maybe :string]] + [:is-source :boolean] [: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]]]]) + [:sets :any]]) (sm/register! ::token-set [:map {:title "TokenSet"} - [:id ::sm/uuid] [:name :string] - [:description {:optional true} :string] + [:description {:optional true} [:maybe :string]] [:modified-at {:optional true} ::sm/inst] - [:tokens [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]]) + [:tokens :any]]) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 476bff5247..d9a5c95a08 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -6,14 +6,16 @@ (ns app.common.types.tokens-lib (:require + #?(:clj [app.common.fressian :as fres]) [app.common.data :as d] [app.common.data.macros :as dm] [app.common.schema :as sm] [app.common.time :as dt] [app.common.transit :as t] [app.common.types.token :as cto] - [cuerdas.core :as str] - #?(:clj [app.common.fressian :as fres]))) + [clojure.set :as set] + [clojure.walk :as walk] + [cuerdas.core :as str])) ;; === Groups handling @@ -102,10 +104,10 @@ (def schema:token [:and [:map {:title "Token"} - [:name cto/token-name-ref] ;; not necessary to have uuid + [:name cto/token-name-ref] [:type [::sm/one-of cto/token-types]] [:value :any] - [:description [:maybe :string]] ;; defrecord always have the attributes, even with nil value + [:description [:maybe :string]] [:modified-at ::sm/inst]] [:fn (partial instance? Token)]]) @@ -130,12 +132,27 @@ token)) +(defn group-by-type [tokens] + (let [tokens' (if (or (map? tokens) + (d/ordered-map? tokens)) + (vals tokens) + tokens)] + (group-by :type tokens'))) + +(defn filter-by-type [token-type tokens] + (let [token-type? #(= token-type (:type %))] + (cond + (d/ordered-map? tokens) (into (d/ordered-map) (filter (comp token-type? val) tokens)) + (map? tokens) (into {} (filter (comp token-type? val) tokens)) + :else (filter token-type? tokens)))) + ;; === Token Set (defprotocol ITokenSet (add-token [_ token] "add a token at the end of the list") (update-token [_ token-name f] "update a token in the list") (delete-token [_ token-name] "delete a token from the list") + (get-token [_ token-name] "return token by token-name") (get-tokens [_] "return an ordered sequence of all tokens in the set")) (defrecord TokenSet [name description modified-at tokens] @@ -168,6 +185,9 @@ (dt/now) (dissoc tokens token-name))) + (get-token [_ token-name] + (get tokens token-name)) + (get-tokens [_] (vals tokens))) @@ -221,6 +241,7 @@ (set-count [_] "get the total number if sets in the library") (get-set-tree [_] "get a nested tree of all sets in the library") (get-sets [_] "get an ordered sequence of all sets in the library") + (get-ordered-set-names [_] "get an ordered sequence of all sets names in the library") (get-set [_ set-name] "get one set looking for name") (get-set-group [_ set-group-path] "get the attributes of a set group")) @@ -249,20 +270,55 @@ ;; === TokenTheme +(def theme-separator "/") + +(defn token-theme-path [group name] + (join-path [group name] theme-separator)) + +(defn split-token-theme-path [path] + (split-path path theme-separator)) + +(def hidden-token-theme-group + "") + +(def hidden-token-theme-name + "__PENPOT__HIDDEN__TOKEN__THEME__") + +(def hidden-token-theme-path + (token-theme-path hidden-token-theme-group hidden-token-theme-name)) + + (defprotocol ITokenTheme - (toggle-set [_ set-name] "togle a set used / not used in the theme")) + (set-sets [_ set-names] "set the active token sets") + (toggle-set [_ set-name] "togle a set used / not used in the theme") + (theme-path [_] "get `token-theme-path` from theme") + (theme-matches-group-name [_ group name] "if a theme matches the given group & name") + (hidden-temporary-theme? [_] "if a theme is the (from the user ui) hidden temporary theme")) (defrecord TokenTheme [name group description is-source modified-at sets] ITokenTheme - (toggle-set [_ set-name] + (set-sets [_ set-names] (TokenTheme. name group description is-source (dt/now) - (if (sets set-name) - (disj sets set-name) - (conj sets set-name))))) + set-names)) + + (toggle-set [this set-name] + (set-sets this (if (sets set-name) + (disj sets set-name) + (conj sets set-name)))) + + (theme-path [_] + (token-theme-path group name)) + + (theme-matches-group-name [this group name] + (and (= (:group this) group) + (= (:name this) name))) + + (hidden-temporary-theme? [this] + (theme-matches-group-name this hidden-token-theme-group hidden-token-theme-name))) (def schema:token-theme [:and [:map {:title "TokenTheme"} @@ -271,8 +327,7 @@ [:description [:maybe :string]] [:is-source :boolean] [:modified-at ::sm/inst] - [:sets [:and [:set {:gen/max 5} :string] - [:fn d/ordered-set?]]]] + [:sets [:set {:gen/max 5} :string]]] [:fn (partial instance? TokenTheme)]]) (sm/register! ::token-theme schema:token-theme) @@ -297,7 +352,7 @@ (update :group #(or % top-level-theme-group-name)) (update :is-source #(or % false)) (update :modified-at #(or % (dt/now))) - (update :sets #(into (d/ordered-set) %))) + (update :sets #(into #{} %))) token-theme (map->TokenTheme params)] (dm/assert! @@ -306,6 +361,12 @@ token-theme)) +(defn make-hidden-token-theme + [& {:keys [] :as params}] + (make-token-theme (assoc params + :group hidden-token-theme-group + :name hidden-token-theme-name))) + ;; === TokenThemes (collection) (defprotocol ITokenThemes @@ -316,7 +377,14 @@ (get-theme-tree [_] "get a nested tree of all themes in the library") (get-themes [_] "get an ordered sequence of all themes in the library") (get-theme [_ group name] "get one theme looking for name") - (get-theme-groups [_] "get a sequence of group names by order")) + (get-theme-groups [_] "get a sequence of group names by order") + (get-active-theme-paths [_] "get the active theme paths") + (get-active-themes [_] "get an ordered sequence of active themes in the library") + (set-active-themes [_ active-themes] "set active themes in library") + (theme-active? [_ group name] "predicate if token theme is active") + (activate-theme [_ group name] "adds theme from the active-themes") + (deactivate-theme [_ group name] "removes theme from the active-themes") + (toggle-theme-active? [_ group name] "toggles theme in the active-themes")) (def schema:token-themes [:and @@ -333,6 +401,12 @@ (def check-token-themes! (sm/check-fn ::token-themes)) +(def schema:active-token-themes + [:set string?]) + +(def valid-active-token-themes? + (sm/validator schema:active-token-themes)) + ;; === Tokens Lib (defprotocol ITokensLib @@ -341,20 +415,30 @@ (update-token-in-set [_ set-name token-name f] "update a token in a set") (delete-token-from-set [_ set-name token-name] "delete a token from a set") (toggle-set-in-theme [_ group-name theme-name set-name] "toggle a set used / not used in a theme") + (get-active-themes-set-names [_] "set of set names that are active in the the active themes") + (get-active-themes-set-tokens [_] "set of set names that are active in the the active themes") + (update-set-name [_ old-set-name new-set-name] "updates set name in themes") (validate [_])) -(deftype TokensLib [sets set-groups themes] +(deftype TokensLib [sets set-groups themes active-themes] ;; NOTE: This is only for debug purposes, pending to properly ;; implement the toString and alternative printing. #?@(:clj [clojure.lang.IDeref - (deref [_] {:sets sets :set-groups set-groups :themes themes})] + (deref [_] {:sets sets + :set-groups set-groups + :themes themes + :active-themes active-themes})] :cljs [cljs.core/IDeref - (-deref [_] {:sets sets :set-groups set-groups :themes themes})]) + (-deref [_] {:sets sets + :set-groups set-groups + :themes themes + :active-themes active-themes})]) #?@(:cljs [cljs.core/IEncodeJS (-clj->js [_] (js-obj "sets" (clj->js sets) "set-groups" (clj->js set-groups) - "themes" (clj->js themes)))]) + "themes" (clj->js themes) + "active-themes" (clj->js active-themes)))]) ITokenSets (add-set [_ token-set] @@ -365,7 +449,8 @@ (cond-> set-groups (not (str/empty? groups-str)) (assoc groups-str (make-token-set-group))) - themes))) + themes + active-themes))) (update-set [this set-name f] (let [path (split-path set-name "/") @@ -381,14 +466,16 @@ (d/oassoc-in-before path path' set') (d/dissoc-in path))) set-groups ;; TODO update set-groups as needed - themes)) + themes + active-themes)) this))) (delete-set [_ set-name] (let [path (split-path set-name "/")] (TokensLib. (d/dissoc-in sets path) set-groups ;; TODO remove set-group if needed - themes))) + themes + active-themes))) (get-set-tree [_] sets) @@ -397,6 +484,9 @@ (->> (tree-seq d/ordered-map? vals sets) (filter (partial instance? TokenSet)))) + (get-ordered-set-names [this] + (map :name (get-sets this))) + (set-count [this] (count (get-sets this))) @@ -412,7 +502,8 @@ (dm/assert! "expected valid token theme" (check-token-theme! token-theme)) (TokensLib. sets set-groups - (update themes (:group token-theme) d/oassoc (:name token-theme) token-theme))) + (update themes (:group token-theme) d/oassoc (:name token-theme) token-theme) + active-themes)) (update-theme [this group name f] (let [theme (dm/get-in themes [group name])] @@ -420,21 +511,28 @@ (let [theme' (-> (make-token-theme (f theme)) (assoc :modified-at (dt/now))) group' (:group theme') - name' (:name theme')] + name' (:name theme') + same-group? (= group group') + same-name? (= name name') + same-path? (and same-group? same-name?)] (check-token-theme! theme') (TokensLib. sets set-groups - (if (and (= group group') (= name name')) + (if same-path? (update themes group' assoc name' theme') (-> themes (d/oassoc-in-before [group name] [group' name'] theme') - (d/dissoc-in [group name]))))) + (d/dissoc-in [group name]))) + (if same-path? + active-themes + (disj active-themes (token-theme-path group name))))) this))) (delete-theme [_ group name] (TokensLib. sets set-groups - (d/dissoc-in themes [group name]))) + (d/dissoc-in themes [group name]) + (disj active-themes (token-theme-path group name)))) (get-theme-tree [_] themes) @@ -455,13 +553,58 @@ (get-theme [_ group name] (dm/get-in themes [group name])) + (set-active-themes [_ active-themes] + (TokensLib. sets + set-groups + themes + active-themes)) + + (activate-theme [this group name] + (if-let [theme (get-theme this group name)] + (let [group-themes (->> (get themes group) + (map (comp theme-path val)) + (into #{})) + active-themes' (-> (set/difference active-themes group-themes) + (conj (theme-path theme)))] + (TokensLib. sets + set-groups + themes + active-themes')) + this)) + + (deactivate-theme [_ group name] + (TokensLib. sets + set-groups + themes + (disj active-themes (token-theme-path group name)))) + + (theme-active? [_ group name] + (contains? active-themes (token-theme-path group name))) + + (toggle-theme-active? [this group name] + (if (theme-active? this group name) + (deactivate-theme this group name) + (activate-theme this group name))) + + (get-active-theme-paths [_] + active-themes) + + (get-active-themes [this] + (into + (list) + (comp + (filter (partial instance? TokenTheme)) + (filter #(theme-active? this (:group %) (:name %)))) + (tree-seq d/ordered-map? vals themes))) + ITokensLib (add-token-in-set [this set-name token] (dm/assert! "expected valid token instance" (check-token! token)) (if (contains? sets set-name) (TokensLib. (update sets set-name add-token token) set-groups - themes) + themes + active-themes) this)) (update-token-in-set [this set-name token-name f] @@ -469,7 +612,8 @@ (TokensLib. (update sets set-name #(update-token % token-name f)) set-groups - themes) + themes + active-themes) this)) (delete-token-from-set [this set-name token-name] @@ -477,7 +621,8 @@ (TokensLib. (update sets set-name #(delete-token % token-name)) set-groups - themes) + themes + active-themes) this)) (toggle-set-in-theme [this theme-group theme-name set-name] @@ -485,12 +630,46 @@ (TokensLib. sets set-groups (d/oupdate-in themes [theme-group theme-name] - #(toggle-set % set-name))) + #(toggle-set % set-name)) + active-themes) this)) + (get-active-themes-set-names [this] + (into #{} + (mapcat :sets) + (get-active-themes this))) + + (get-active-themes-set-tokens [this] + (let [sets-order (get-ordered-set-names this) + active-themes (get-active-themes this) + order-theme-set (fn [theme] + (filter #(contains? (set (:sets theme)) %) sets-order))] + (reduce + (fn [tokens theme] + (reduce + (fn [tokens' cur] + (merge tokens' (:tokens (get-set this cur)))) + tokens (order-theme-set theme))) + (d/ordered-map) active-themes))) + + ;; TODO Move to `update-set` + (update-set-name [_ old-set-name new-set-name] + (TokensLib. sets + set-groups + (walk/postwalk + (fn [form] + (if (instance? TokenTheme form) + (-> form + (update :sets disj old-set-name) + (update :sets conj new-set-name)) + form)) + themes) + active-themes)) + (validate [_] (and (valid-token-sets? sets) ;; TODO: validate set-groups - (valid-token-themes? themes)))) + (valid-token-themes? themes) + (valid-active-token-themes? active-themes)))) (defn valid-tokens-lib? [o] @@ -513,10 +692,11 @@ ;; with pages and pages-index. (make-tokens-lib :sets (d/ordered-map) :set-groups {} - :themes (d/ordered-map))) + :themes (d/ordered-map) + :active-themes #{})) - ([& {:keys [sets set-groups themes]}] - (let [tokens-lib (TokensLib. sets set-groups themes)] + ([& {:keys [sets set-groups themes active-themes]}] + (let [tokens-lib (TokensLib. sets set-groups themes (or active-themes #{}))] (dm/assert! "expected valid tokens lib" @@ -541,16 +721,16 @@ :class TokensLib :wfn deref :rfn #(make-tokens-lib %)} - + {:id "penpot/token-set" :class TokenSet :wfn #(into {} %) - :rfn #(make-token-set %)} - + :rfn #(make-token-set %)} + {:id "penpot/token-theme" :class TokenTheme :wfn #(into {} %) - :rfn #(make-token-theme %)} + :rfn #(make-token-theme %)} {:id "penpot/token" :class Token @@ -592,9 +772,11 @@ (fres/write-tag! w n 3) (fres/write-object! w (.-sets o)) (fres/write-object! w (.-set-groups o)) - (fres/write-object! w (.-themes o))) + (fres/write-object! w (.-themes o)) + (fres/write-object! w (.-active-themes o))) :rfn (fn [r] - (let [sets (fres/read-object! r) - set-groups (fres/read-object! r) - themes (fres/read-object! r)] - (->TokensLib sets set-groups themes)))})) + (let [sets (fres/read-object! r) + set-groups (fres/read-object! r) + themes (fres/read-object! r) + active-themes (fres/read-object! r)] + (->TokensLib sets set-groups themes active-themes)))})) diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index 8efed001bc..072f71382c 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -6,8 +6,8 @@ (ns common-tests.types.tokens-lib-test (:require + #?(:clj [app.common.fressian :as fres]) [app.common.data :as d] - [app.common.fressian :as fres] [app.common.time :as dt] [app.common.transit :as tr] [app.common.types.tokens-lib :as ctob] @@ -182,6 +182,19 @@ (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))) (t/deftest delete-token-set + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set"))) + + tokens-lib' (-> tokens-lib + (ctob/delete-set "test-token-set") + (ctob/delete-set "not-existing-set")) + + token-set' (ctob/get-set tokens-lib' "updated-name")] + + (t/is (= (ctob/set-count tokens-lib') 0)) + (t/is (nil? token-set')))) + + (t/deftest active-themes-set-names (let [tokens-lib (-> (ctob/make-tokens-lib) (ctob/add-set (ctob/make-token-set :name "test-token-set"))) @@ -307,7 +320,35 @@ (t/is (= (ctob/set-count tokens-lib') 1)) (t/is (= (count (:tokens token-set')) 0)) (t/is (nil? token')) - (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))) + + (t/deftest list-active-themes-tokens-in-order + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :name "out-of-order-theme" + ;; Out of order sets in theme + :sets ["unknown-set" "set-b" "set-a"])) + (ctob/set-active-themes #{"/out-of-order-theme"}) + + (ctob/add-set (ctob/make-token-set :name "set-a")) + (ctob/add-token-in-set "set-a" (ctob/make-token :name "set-a-token" + :type :boolean + :value true)) + (ctob/add-set (ctob/make-token-set :name "set-b")) + (ctob/add-token-in-set "set-b" (ctob/make-token :name "set-b-token" + :type :boolean + :value true)) + ;; Ignore this set + (ctob/add-set (ctob/make-token-set :name "inactive-set")) + (ctob/add-token-in-set "inactive-set" (ctob/make-token :name "inactive-set-token" + :type :boolean + :value true))) + + + expected-order (ctob/get-ordered-set-names tokens-lib) + expected-tokens (ctob/get-active-themes-set-tokens tokens-lib) + expected-token-names (mapv key expected-tokens)] + (t/is (= '("set-a" "set-b" "inactive-set") expected-order)) + (t/is (= ["set-a-token" "set-b-token"] expected-token-names))))) (t/testing "token-theme in a lib" diff --git a/frontend/dev/preload.cljs b/frontend/dev/preload.cljs new file mode 100644 index 0000000000..57d3cc0d8e --- /dev/null +++ b/frontend/dev/preload.cljs @@ -0,0 +1,7 @@ +(ns preload + (:require + [devtools.core :as devtools])) + +;; Silence shadow-cljs devtools (ns reloading) +(devtools/set-pref! :dont-display-banner true) +(devtools/set-pref! :min-expandable-sequable-count-for-well-known-types 0) diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index ef001a93d6..6e26f906d8 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -8,7 +8,9 @@ {:target :browser :output-dir "resources/public/js/" :asset-path "/js" - :devtools {:browser-inject :main + :devtools {:preloads [preload devtools.preload] + :log false + :browser-inject :main :watch-dir "resources/public" :reload-strategy :full} :build-options {:manifest-name "manifest.json"} @@ -65,7 +67,8 @@ :compiler-options {:output-feature-set :es2020 :output-wrapper false - :warnings {:fn-deprecated false}} + :warnings {:fn-deprecated false} + :closure-defines {shadow.debug.LogLevel :warning}} :release {:closure-defines {goog.DEBUG false diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index 820911b826..c725f68a09 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -11,7 +11,7 @@ [app.common.files.changes-builder :as pcb] [app.common.geom.point :as gpt] [app.common.types.shape :as cts] - [app.common.uuid :as uuid] + [app.common.types.tokens-lib :as ctob] [app.main.data.changes :as dch] [app.main.data.workspace.shapes :as dwsh] [app.main.refs :as refs] @@ -40,6 +40,13 @@ (watch [_ _ _] (rx/of (dwsh/update-shapes [id] #(merge % attrs)))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TOKENS Getters +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn get-tokens-lib [state] + (get-in state [:workspace-data :tokens-lib])) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TOKENS Actions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -81,11 +88,6 @@ (let [workspace-data (deref refs/workspace-data)] (get (:tokens workspace-data) id))) -(defn get-token-set-data-from-token-set-id - [id] - (let [workspace-data (deref refs/workspace-data)] - (get (:token-sets-index workspace-data) id))) - (defn set-selected-token-set-id [id] (ptk/reify ::set-selected-token-set-id @@ -93,16 +95,8 @@ (update [_ state] (wtts/assoc-selected-token-set-id state id)))) -(defn get-token-set-tokens - [token-set file] - (map #(get-in file [:tokens %]) (:tokens token-set))) - (defn create-token-theme [token-theme] - (let [new-token-theme (merge - {:id (uuid/next) - :sets #{} - :selected :enabled} - token-theme)] + (let [new-token-theme token-theme] (ptk/reify ::create-token-theme ptk/WatchEvent (watch [it _ _] @@ -111,199 +105,162 @@ (rx/of (dch/commit-changes changes))))))) -(defn update-token-theme [token-theme] +(defn update-token-theme [[group name] token-theme] (ptk/reify ::update-token-theme ptk/WatchEvent (watch [it state _] - (let [prev-token-theme (wtts/get-workspace-token-theme (:id token-theme) state) - changes (-> (pcb/empty-changes it) - (pcb/update-token-theme token-theme prev-token-theme))] + (let [tokens-lib (get-tokens-lib state) + prev-token-theme (some-> tokens-lib (ctob/get-theme group name)) + changes (pcb/update-token-theme (pcb/empty-changes it) token-theme prev-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 "Test theme" - :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 +(defn toggle-token-theme-active? [group name] + (ptk/reify ::toggle-token-theme-active? ptk/WatchEvent (watch [it state _] - (let [themes (wtts/get-active-theme-ids state) - new-themes (wtts/toggle-active-theme-id token-theme-id state) + (let [tokens-lib (get-tokens-lib state) + prev-active-token-themes (some-> tokens-lib + (ctob/get-active-theme-paths)) + active-token-themes (some-> tokens-lib + (ctob/toggle-theme-active? group name) + (ctob/get-active-theme-paths)) + active-token-themes' (if (= active-token-themes #{ctob/hidden-token-theme-path}) + active-token-themes + (disj active-token-themes ctob/hidden-token-theme-path)) changes (-> (pcb/empty-changes it) - (pcb/update-active-token-themes new-themes themes))] + (pcb/update-active-token-themes active-token-themes' prev-active-token-themes))] (rx/of (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) -(defn delete-token-theme [token-theme-id] +(defn delete-token-theme [group name] (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))] + (pcb/delete-token-theme group name))] (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" + {: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}))] + (pcb/add-token-set new-token-set))] (rx/of - (set-selected-token-set-id (:id new-token-set)) + (set-selected-token-set-id (:name new-token-set)) (dch/commit-changes changes))))))) -(defn update-token-set [token-set] +(defn update-token-set [set-name token-set] (ptk/reify ::update-token-set ptk/WatchEvent (watch [it state _] - (let [prev-token-set (wtts/get-token-set (:id token-set) state) + (let [prev-token-set (some-> (get-tokens-lib state) + (ctob/get-set set-name)) changes (-> (pcb/empty-changes it) (pcb/update-token-set token-set prev-token-set))] (rx/of (dch/commit-changes changes)))))) -(defn toggle-token-set [{:keys [token-set-id]}] +(defn toggle-token-set [{:keys [token-set-name]}] (ptk/reify ::toggle-token-set ptk/WatchEvent (watch [it state _] - (let [target-theme-id (wtts/get-temp-theme-id state) - active-set-ids (wtts/get-active-set-ids state) - theme (-> (wtts/get-workspace-token-theme target-theme-id state) - (assoc :sets active-set-ids)) + (let [tokens-lib (get-tokens-lib state) + prev-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name) + active-token-set-names (ctob/get-active-themes-set-names tokens-lib) + theme (-> (or (some-> prev-theme + (ctob/set-sets active-token-set-names)) + (ctob/make-hidden-token-theme :sets active-token-set-names)) + (ctob/toggle-set token-set-name)) + prev-active-token-themes (ctob/get-active-theme-paths tokens-lib) 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 #{target-theme-id} (wtts/get-active-theme-ids state)))] + (pcb/update-active-token-themes #{(ctob/token-theme-path ctob/hidden-token-theme-group ctob/hidden-token-theme-name)} prev-active-token-themes)) + changes' (if prev-theme + (pcb/update-token-theme changes theme prev-theme) + (pcb/add-token-theme changes theme))] (rx/of - (dch/commit-changes changes) + (dch/commit-changes changes') (wtu/update-workspace-tokens)))))) -(defn delete-token-set [token-set-id token-set-name] +(defn delete-token-set [token-set-name] (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 token-set-name))] + (pcb/delete-token-set token-set-name))] (rx/of (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) (defn update-create-token - [token] - (let [token (update token :id #(or % (uuid/next)))] - (ptk/reify ::update-create-token - ptk/WatchEvent - (watch [it state _] - (let [token-set (wtts/get-selected-token-set state) - create-set? (not token-set) - token-set (or token-set - {:id (uuid/next) - :name "Global" - :tokens []}) + [{:keys [token prev-token-name]}] + (ptk/reify ::update-create-token + ptk/WatchEvent + (watch [_ state _] + (let [token-set (wtts/get-selected-token-set state) + token-set-name (or (:name token-set) "Global") + changes (if (not token-set) + ;; No set created add a global set + (let [tokens-lib (get-tokens-lib state) + token-set (ctob/make-token-set :name token-set-name :tokens {(:name token) token}) + hidden-theme (ctob/make-hidden-token-theme :sets [token-set-name]) + active-theme-paths (some-> tokens-lib ctob/get-active-theme-paths) + add-to-hidden-theme? (= active-theme-paths #{ctob/hidden-token-theme-path}) + base-changes (pcb/add-token-set (pcb/empty-changes) token-set)] + (cond + (not tokens-lib) (-> base-changes + (pcb/add-token-theme hidden-theme) + (pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{})) - changes (cond-> (pcb/empty-changes it) - create-set? - (pcb/add-token-set token-set)) + add-to-hidden-theme? (let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)] + (-> base-changes + (pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme))) - prev-token-id (d/seek #(= % (:id token)) (:tokens token-set)) - prev-token (get-token-data-from-token-id prev-token-id) - create-token? (not prev-token) - - changes (if create-token? - (pcb/add-token changes (:id token-set) (:name token-set) token) - (pcb/update-token changes (:id token-set) (:name token-set) token prev-token)) - - changes (-> changes - (ensure-token-theme-changes state {:new-set? create-set? - :id (:id token-set)}))] - (rx/of - (set-selected-token-set-id (:id token-set)) - (dch/commit-changes changes))))))) + :else base-changes)) + ;; Either update or add token to existing set + (if-let [prev-token (ctob/get-token token-set (or prev-token-name (:name token)))] + (pcb/update-token (pcb/empty-changes) (:name token-set) token prev-token) + (pcb/add-token (pcb/empty-changes) (:name token-set) token)))] + (rx/of + (set-selected-token-set-id token-set-name) + (dch/commit-changes changes)))))) (defn delete-token - [set-name id name] + [set-name token-name] (dm/assert! (string? set-name)) - (dm/assert! (uuid? id)) - (dm/assert! (string? name)) + (dm/assert! (string? token-name)) (ptk/reify ::delete-token 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-name id name))] + (pcb/delete-token set-name token-name))] (rx/of (dch/commit-changes changes)))))) (defn duplicate-token - [id] - (let [new-token (-> (get-token-data-from-token-id id) - (dissoc :id) - (update :name #(str/concat % "-copy")))] - (update-create-token new-token))) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; TEMP (Move to test) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(comment - (def shape-1 {:r3 3}) - - (def token-1 {:rx 1 - :ry 1}) - - - (def shape-after-token-1-is-applied {:rx 1 - :ry 1 - :r3 3}) - - (def token-2 {:r3 1}) - - - (def shape-after-token-2-is-applied {:rx 1 - :ry 1 - :r3 1}) - - (def token-3 {:r3 1}) - - (def shape-after-token-3-is-applied {:rx 1 - :ry 1}) - - (= (toggle-or-apply-token shape-1 token-1) - shape-after-token-1-is-applied) - (= (toggle-or-apply-token shape-after-token-1-is-applied token-2) - shape-after-token-2-is-applied) - (= (toggle-or-apply-token shape-after-token-2-is-applied token-3) - shape-after-token-3-is-applied) - nil) + [token-name] + (dm/assert! (string? token-name)) + (ptk/reify ::duplicate-token + ptk/WatchEvent + (watch [_ state _] + (when-let [token (some-> (wtts/get-selected-token-set state) + (ctob/get-token token-name) + (update :name #(str/concat % "-copy")))] + (rx/of + (update-create-token {:token token})))))) (defn set-token-type-section-open [token-type open?] @@ -315,7 +272,7 @@ ;; Token Context Menu Functions ------------------------------------------------- (defn show-token-context-menu - [{:keys [position _token-id] :as params}] + [{:keys [position _token-name] :as params}] (dm/assert! (gpt/point? position)) (ptk/reify ::show-token-context-menu ptk/UpdateEvent @@ -329,7 +286,7 @@ (assoc-in state [:workspace-local :token-context-menu] nil)))) (defn show-token-set-context-menu - [{:keys [position _token-set-id] :as params}] + [{:keys [position _token-set-name] :as params}] (dm/assert! (gpt/point? position)) (ptk/reify ::show-token-set-context-menu ptk/UpdateEvent diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 3e8994e36b..8bf9a7c754 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -12,11 +12,11 @@ [app.common.files.helpers :as cph] [app.common.types.shape-tree :as ctt] [app.common.types.shape.layout :as ctl] + [app.common.types.tokens-lib :as ctob] [app.main.data.workspace.state-helpers :as wsh] [app.main.store :as st] [app.main.ui.workspace.tokens.token-set :as wtts] - [okulary.core :as l] - [app.common.types.tokens-lib :as ctob])) + [okulary.core :as l])) ;; ---- Global refs @@ -235,59 +235,6 @@ (def workspace-data (l/derived :workspace-data st/state)) -(def workspace-selected-token-set-id - (l/derived - wtts/get-selected-token-set-id - st/state - =)) - -;; ---- Tokens - -(def tokens-lib - (l/derived :tokens-lib workspace-data)) - -(def workspace-token-theme-groups - (l/derived #(some-> % ctob/get-theme-groups) tokens-lib)) - -(defn workspace-token-theme - [id] - (l/derived #(wtts/get-workspace-theme id %) st/state)) - -(def workspace-active-theme-ids - (l/derived wtts/get-active-theme-ids st/state)) - -(def workspace-temp-theme-id - (l/derived wtts/get-temp-theme-id 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-ordered-token-sets - (l/derived - (fn [data] - (or (wtts/get-workspace-ordered-sets data) {})) - st/state - =)) - -(def workspace-active-theme-sets-tokens - (l/derived wtts/get-active-theme-sets-tokens-names-map st/state =)) - -(def workspace-ordered-token-sets-tokens - (l/derived wtts/get-workspace-ordered-sets-tokens 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 (l/derived (fn [data] (when data @@ -496,6 +443,65 @@ ids))) st/state =)) +;; ---- Token refs + +(def tokens-lib + (l/derived :tokens-lib workspace-data)) + +(def workspace-token-theme-groups + (l/derived (d/nilf ctob/get-theme-groups) tokens-lib)) + +(defn workspace-token-theme + [group name] + (l/derived + (fn [lib] + (when lib + (ctob/get-theme lib group name))) + tokens-lib)) + +(def workspace-token-theme-tree-no-hidden + (l/derived (fn [lib] + (or + (some-> lib + (ctob/delete-theme ctob/hidden-token-theme-group ctob/hidden-token-theme-name) + (ctob/get-theme-tree)) + [])) + tokens-lib)) + +(def workspace-token-themes + (l/derived #(or (some-> % ctob/get-themes) []) tokens-lib)) + +(def workspace-token-themes-no-hidden + (l/derived #(remove ctob/hidden-temporary-theme? %) workspace-token-themes)) + +(def workspace-selected-token-set-id + (l/derived wtts/get-selected-token-set-id st/state)) + +(def workspace-ordered-token-sets + (l/derived #(or (some-> % ctob/get-sets) []) tokens-lib)) + +(def workspace-active-theme-paths + (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) + +(def workspace-active-theme-paths-no-hidden + (l/derived #(disj % ctob/hidden-token-theme-path) workspace-active-theme-paths)) + +(def workspace-active-set-names + (l/derived (d/nilf ctob/get-active-themes-set-names) tokens-lib)) + +(def workspace-active-theme-sets-tokens + (l/derived #(or (some-> % ctob/get-active-themes-set-tokens) {}) tokens-lib)) + +(def workspace-selected-token-set-token + (fn [token-name] + (l/derived + #(some-> (wtts/get-selected-token-set %) + (ctob/get-token token-name)) + st/state))) + +(def workspace-selected-token-set-tokens + (l/derived #(or (wtts/get-selected-token-set-tokens %) {}) st/state)) + ;; ---- Viewer refs (defn lookup-viewer-objects-by-id diff --git a/frontend/src/app/main/ui/components/tab_container.cljs b/frontend/src/app/main/ui/components/tab_container.cljs index 1e3b99079c..0d39e93d81 100644 --- a/frontend/src/app/main/ui/components/tab_container.cljs +++ b/frontend/src/app/main/ui/components/tab_container.cljs @@ -16,6 +16,8 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) +(set! *warn-on-infer* false) + (mf/defc tab-element {::mf/wrap-props false} [{:keys [children]}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index ef32a4e676..d3d95ca1a2 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -11,6 +11,7 @@ [app.common.data.macros :as dm] [app.common.math :as mth] [app.common.types.shape.layout :as ctl] + [app.common.types.tokens-lib :as ctob] [app.config :as cf] [app.main.data.events :as-alias ev] [app.main.data.workspace :as udw] @@ -27,10 +28,11 @@ [app.main.ui.formats :as fmt] [app.main.ui.hooks :as h] [app.main.ui.icons :as i] + [app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.core :as wtc] [app.main.ui.workspace.tokens.editable-select :refer [editable-select]] + [app.main.ui.workspace.tokens.style-dictionary :as sd] [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]] @@ -856,8 +858,10 @@ shape (when-not multiple (first (deref (refs/objects-by-id ids)))) - tokens (mf/deref refs/workspace-selected-token-set-tokens) - spacing-tokens (mf/use-memo (mf/deps tokens) #(:spacing (wtc/group-tokens-by-type tokens))) + tokens (sd/use-active-theme-sets-tokens) + spacing-tokens (mf/use-memo + (mf/deps tokens) + #(ctob/filter-by-type :spacing tokens)) spacing-column-options (mf/use-memo (mf/deps shape spacing-tokens) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 2f9dd5a3a9..1aed9f1734 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -32,7 +32,8 @@ [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [clojure.set :refer [rename-keys union]] - [rumext.v2 :as mf])) + [rumext.v2 :as mf] + [app.common.types.tokens-lib :as ctob])) (def measure-attrs [:proportion-lock @@ -101,28 +102,29 @@ selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) selection-parents (mf/deref selection-parents-ref) - tokens (-> (mf/deref refs/workspace-active-theme-sets-tokens) - (sd/use-resolved-tokens)) - tokens-by-type (mf/use-memo (mf/deps tokens) #(wtc/group-tokens-by-type tokens)) + tokens (sd/use-active-theme-sets-tokens) + tokens-by-type (mf/use-memo + (mf/deps tokens) + #(ctob/group-by-type tokens)) border-radius-tokens (:border-radius tokens-by-type) border-radius-options (mf/use-memo (mf/deps shape border-radius-tokens) - #(wtc/tokens-name-map->select-options + #(wtc/tokens->select-options {:shape shape :tokens border-radius-tokens :attributes (wtty/token-attributes :border-radius)})) sizing-tokens (:sizing tokens-by-type) width-options (mf/use-memo (mf/deps shape sizing-tokens) - #(wtc/tokens-name-map->select-options + #(wtc/tokens->select-options {:shape shape :tokens sizing-tokens :attributes (wtty/token-attributes :sizing) :selected-attributes #{:width}})) height-options (mf/use-memo (mf/deps shape sizing-tokens) - #(wtc/tokens-name-map->select-options + #(wtc/tokens->select-options {:shape shape :tokens sizing-tokens :attributes (wtty/token-attributes :sizing) diff --git a/frontend/src/app/main/ui/workspace/tokens/changes.cljs b/frontend/src/app/main/ui/workspace/tokens/changes.cljs index e1438da95e..0ddd8c8306 100644 --- a/frontend/src/app/main/ui/workspace/tokens/changes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/changes.cljs @@ -8,19 +8,20 @@ (:require [app.common.types.shape.radius :as ctsr] [app.common.types.token :as ctt] - [app.main.data.workspace.colors :as wdc] + [app.common.types.tokens-lib :as ctob] [app.main.data.workspace :as udw] + [app.main.data.workspace.colors :as wdc] [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] [app.main.ui.workspace.tokens.style-dictionary :as sd] + [app.main.ui.workspace.tokens.tinycolor :as tinycolor] [app.main.ui.workspace.tokens.token :as wtt] [beicon.v2.core :as rx] [clojure.set :as set] - [potok.v2.core :as ptk] - [app.main.ui.workspace.tokens.tinycolor :as tinycolor])) + [potok.v2.core :as ptk])) ;; Token Updates --------------------------------------------------------------- @@ -34,21 +35,23 @@ (ptk/reify ::apply-token ptk/WatchEvent (watch [_ state _] - (->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens]))) - (rx/mapcat - (fn [resolved-tokens] - (let [undo-id (js/Symbol) - 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] - (cond-> shape - attributes-to-remove (update :applied-tokens #(apply (partial dissoc %) attributes-to-remove)) - :always (update :applied-tokens merge tokenized-attributes)))) - (when on-update-shape - (on-update-shape resolved-value shape-ids attributes)) - (dwu/commit-undo-transaction undo-id))))))))) + (when-let [tokens (some-> (get-in state [:workspace-data :tokens-lib]) + (ctob/get-active-themes-set-tokens))] + (->> (rx/from (sd/resolve-tokens+ tokens)) + (rx/mapcat + (fn [resolved-tokens] + (let [undo-id (js/Symbol) + 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] + (cond-> shape + attributes-to-remove (update :applied-tokens #(apply (partial dissoc %) attributes-to-remove)) + :always (update :applied-tokens merge tokenized-attributes)))) + (when on-update-shape + (on-update-shape resolved-value shape-ids attributes)) + (dwu/commit-undo-transaction undo-id)))))))))) (defn unapply-token "Removes `attributes` that match `token` for `shape-ids`. diff --git a/frontend/src/app/main/ui/workspace/tokens/common.cljs b/frontend/src/app/main/ui/workspace/tokens/common.cljs index ea0b8bc65f..e81c93999d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/common.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/common.cljs @@ -19,20 +19,10 @@ [cuerdas.core :as str] [goog.events :as events] [rumext.v2 :as mf]) - (:import - goog.events.EventType)) + (:import goog.events.EventType)) ;; Helpers --------------------------------------------------------------------- -(defn workspace-shapes [workspace page-id shape-ids] - (-> (get-in workspace [:pages-index page-id :objects]) - (keep shape-ids))) - -(defn vec-remove - "remove elem in coll" - [pos coll] - (into (subvec coll 0 pos) (subvec coll (inc pos)))) - (defn camel-keys [m] (->> m (d/deep-mapm diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs index 6e4156d034..a50218df7d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -202,16 +202,14 @@ (generic-attribute-actions #{:y} "Y" (assoc context-data :on-update-shape wtch/update-shape-position))))})) (defn default-actions [{:keys [token selected-token-set-id]}] - (let [{:keys [modal]} (wtty/get-token-properties token) - selected-token-set (dt/get-token-set-data-from-token-set-id selected-token-set-id)] + (let [{:keys [modal]} (wtty/get-token-properties token)] [{:title "Delete Token" - :action #(st/emit! (dt/delete-token (:name selected-token-set) (:id token) (:name token)))} + :action #(st/emit! (dt/delete-token selected-token-set-id (:name token)))} {:title "Duplicate Token" - :action #(st/emit! (dt/duplicate-token (:id token)))} + :action #(st/emit! (dt/duplicate-token (:name token)))} {:title "Edit Token" :action (fn [event] - (let [{:keys [key fields]} modal - token (dt/get-token-data-from-token-id (:id token))] + (let [{:keys [key fields]} modal] (st/emit! dt/hide-token-context-menu) (dom/stop-propagation event) (modal/show! key {:x (.-clientX ^js event) @@ -301,19 +299,27 @@ :on-click action :selected? selected?}])]))) +(mf/defc token-context-menu-tree + [{:keys [width] :as mdata}] + (let [objects (mf/deref refs/workspace-page-objects) + selected (mf/deref refs/selected-shapes) + selected-shapes (into [] (keep (d/getf objects)) selected) + token-name (:token-name mdata) + token (mf/deref (refs/workspace-selected-token-set-token token-name)) + selected-token-set-id (mf/deref refs/workspace-selected-token-set-id)] + [:ul {:class (stl/css :context-list)} + [:& menu-tree {:submenu-offset width + :token token + :selected-token-set-id selected-token-set-id + :selected-shapes selected-shapes}]])) + (mf/defc token-context-menu [] (let [mdata (mf/deref tokens-menu-ref) top (+ (get-in mdata [:position :y]) 5) left (+ (get-in mdata [:position :x]) 5) width (mf/use-state 0) - dropdown-ref (mf/use-ref) - objects (mf/deref refs/workspace-page-objects) - selected (mf/deref refs/selected-shapes) - selected-shapes (into [] (keep (d/getf objects)) selected) - token-id (:token-id mdata) - token (get (mf/deref refs/workspace-selected-token-set-tokens) token-id) - selected-token-set-id (mf/deref refs/workspace-selected-token-set-id)] + dropdown-ref (mf/use-ref)] (mf/use-effect (mf/deps mdata) (fn [] @@ -325,9 +331,5 @@ :ref dropdown-ref :style {:top top :left left} :on-context-menu prevent-default} - (when token - [:ul {:class (stl/css :context-list)} - [:& menu-tree {:submenu-offset @width - :token token - :selected-token-set-id selected-token-set-id - :selected-shapes selected-shapes}]])]])) + (when mdata + [:& token-context-menu-tree (assoc mdata :offset @width)])]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/core.cljs b/frontend/src/app/main/ui/workspace/tokens/core.cljs index 489d3f0411..14c2df7254 100644 --- a/frontend/src/app/main/ui/workspace/tokens/core.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/core.cljs @@ -23,17 +23,19 @@ (defn maybe-resolve-token-value [{:keys [value] :as token}] (when value (resolve-token-value token))) -(defn group-tokens-by-type - "Groups tokens by their `:type` property." - [tokens] - (->> (vals tokens) - (group-by :type))) +(defn tokens->select-options [{:keys [shape tokens attributes selected-attributes]}] + (map + (fn [{:keys [name] :as token}] + (cond-> (assoc token :label name) + (wtt/token-applied? token shape (or selected-attributes attributes)) (assoc :selected? true))) + tokens)) (defn tokens-name-map->select-options [{:keys [shape tokens attributes selected-attributes]}] - (->> (wtt/token-names-map tokens) - (map (fn [[_k {:keys [name] :as item}]] - (cond-> (assoc item :label name) - (wtt/token-applied? item shape (or selected-attributes attributes)) (assoc :selected? true)))))) + (map + (fn [[_k {:keys [name] :as token}]] + (cond-> (assoc token :label name) + (wtt/token-applied? token shape (or selected-attributes attributes)) (assoc :selected? true))) + tokens)) ;; JSON export functions ------------------------------------------------------- @@ -46,18 +48,18 @@ (defn export-tokens-file [tokens-json] (let [file-name "tokens.json" file-content (encode-tokens tokens-json) - blob (wapi/create-blob (clj->js file-content) "application/json")] + blob (wapi/create-blob file-content "application/json")] (dom/trigger-download file-name blob))) -(defn transform-tokens-into-json-format [tokens] +(defn tokens->dtcg-map [tokens] (let [global (reduce (fn [acc [_ {:keys [name value type]}]] - (assoc acc name {:$value value - :$type (str/camel type)})) - (sorted-map) tokens)] + (assoc acc name {"$value" value + "$type" (str/camel type)})) + (d/ordered-map) tokens)] {:global global})) (defn download-tokens-as-json [] - (let [all-tokens (deref refs/workspace-selected-token-set-tokens) - transformed-tokens-json (transform-tokens-into-json-format all-tokens)] - (export-tokens-file transformed-tokens-json))) + (let [tokens (deref refs/workspace-active-theme-sets-tokens) + dtcg-format-tokens-map (tokens->dtcg-map tokens)] + (export-tokens-file dtcg-format-tokens-map))) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index b202c3ac80..3bb0339f47 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -10,6 +10,7 @@ ["lodash.debounce" :as debounce] [app.common.colors :as c] [app.common.data :as d] + [app.common.types.tokens-lib :as ctob] [app.main.data.modal :as modal] [app.main.data.tokens :as dt] [app.main.refs :as refs] @@ -99,30 +100,28 @@ Token names should only contain letters and digits separated by . characters.")} (defn validate-token-value+ "Validates token value by resolving the value `input` using `StyleDictionary`. Returns a promise of either resolved tokens or rejects with an error state." - [{:keys [input name-value token tokens]}] - (let [ ;; When creating a new token we dont have a token name yet, + [{:keys [value name-value token tokens]}] + (let [;; 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)] (cond - (empty? (str/trim input)) + (empty? (str/trim value)) (p/rejected {:errors [{:error/code :error/empty-input}]}) - (token-self-reference? token-name input) + (token-self-reference? token-name value) (p/rejected {:errors [(wte/get-error-code :error.token/direct-self-reference)]}) :else - (let [token-id (or (:id token) (random-uuid)) - new-tokens (update tokens token-name merge {:id token-id - :value input - :name token-name - :type (:type token)})] - (-> (sd/resolve-tokens+ new-tokens {:names-map? true}) - (p/then - (fn [resolved-tokens] - (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-name)] - (cond - resolved-value (p/resolved resolved-token) - :else (p/rejected {:errors (or errors (wte/get-error-code :error/unknown-error))})))))))))) + (-> (update tokens token-name merge {:value value + :name token-name + :type (:type token)}) + (sd/resolve-tokens+ {:names-map? true}) + (p/then + (fn [resolved-tokens] + (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-name)] + (cond + resolved-value (p/resolved resolved-token) + :else (p/rejected {:errors (or errors (wte/get-error-code :error/unknown-error))}))))))))) (defn use-debonced-resolve-callback "Resolves a token values using `StyleDictionary`. @@ -141,7 +140,7 @@ Token names should only contain letters and digits separated by . characters.")} (js/setTimeout (fn [] (when (not (timeout-outdated-cb?)) - (-> (validate-token-value+ {:input value + (-> (validate-token-value+ {:value value :name-value @name-ref :token token :tokens tokens}) @@ -203,8 +202,9 @@ Token names should only contain letters and digits separated by . characters.")} color? (wtt/color-token? token) selected-set-tokens (mf/deref refs/workspace-selected-token-set-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}) + resolved-tokens (sd/use-resolved-tokens active-theme-tokens + {:names-map? true + :cache-atom form-token-cache-atom}) token-path (mf/use-memo (mf/deps (:name token)) #(wtt/token-name->path (:name token))) @@ -309,7 +309,7 @@ Token names should only contain letters and digits separated by . characters.")} valid-description?+ (some-> final-description validate-descripion schema-validation->promise)] (-> (p/all [valid-name?+ valid-description?+ - (validate-token-value+ {:input final-value + (validate-token-value+ {:value final-value :name-value final-name :token token :tokens resolved-tokens})]) @@ -317,14 +317,13 @@ Token names should only contain letters and digits separated by . characters.")} ;; 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 (when (and (seq result) (not err)) - (let [new-token (cond-> {:name final-name - :type (or (:type token) token-type) - :value final-value} - final-description (assoc :description final-description) - (:id token) (assoc :id (:id token)))] - (st/emit! (dt/update-create-token new-token)) - (st/emit! (wtu/update-workspace-tokens)) - (modal/hide!)))))))))] + (st/emit! (dt/update-create-token {:token (ctob/make-token :name final-name + :type (or (:type token) token-type) + :value final-value + :description final-description) + :prev-token-name (:name token)})) + (st/emit! (wtu/update-workspace-tokens)) + (modal/hide!))))))))] [:form {:class (stl/css :form-wrapper) :on-submit on-submit} diff --git a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs index 47e00588da..f75bb4ba8f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.tokens.modals.themes (:require-macros [app.main.style :as stl]) (:require + [app.common.types.tokens-lib :as ctob] [app.main.data.modal :as modal] [app.main.data.tokens :as wdt] [app.main.refs :as refs] @@ -15,12 +16,10 @@ [app.main.ui.icons :as i] [app.main.ui.workspace.tokens.common :refer [labeled-input] :as wtco] [app.main.ui.workspace.tokens.sets :as wts] - [app.main.ui.workspace.tokens.token-set :as wtts] - [app.util.dom :as dom] - [rumext.v2 :as mf] - [cuerdas.core :as str] [app.main.ui.workspace.tokens.sets-context :as sets-context] - [app.main.ui.shapes.group :as group])) + [app.util.dom :as dom] + [cuerdas.core :as str] + [rumext.v2 :as mf])) (def ^:private chevron-icon (i/icon-xref :arrow (stl/css :chevron-icon))) @@ -56,29 +55,30 @@ (mf/defc themes-overview [{:keys [set-state]}] - (let [active-theme-ids (mf/deref refs/workspace-active-theme-ids) - themes (mf/deref refs/workspace-ordered-token-themes) + (let [active-theme-ids (mf/deref refs/workspace-active-theme-paths) + themes-groups (mf/deref refs/workspace-token-theme-tree-no-hidden) on-edit-theme (fn [theme e] (dom/prevent-default e) (dom/stop-propagation e) (set-state (fn [_] {:type :edit-theme - :theme-id (:id theme)})))] + :theme-path [(:id theme) (:group theme) (:name theme)]})))] [:div [:ul {:class (stl/css :theme-group-wrapper)} - (for [[group themes] themes] + (for [[group themes] themes-groups] [:li {:key (str "token-theme-group" group)} (when (seq group) [:span {:class (stl/css :theme-group-label)} group]) [:ul {:class (stl/css :theme-group-rows-wrapper)} - (for [{:keys [id name] :as theme} themes - :let [selected? (some? (get active-theme-ids id))]] - [:li {:key (str "token-theme-" id) + (for [[_ {:keys [group name] :as theme}] themes + :let [theme-id (ctob/theme-path theme) + selected? (some? (get active-theme-ids theme-id))]] + [:li {:key theme-id :class (stl/css :theme-row)} [:div {:class (stl/css :theme-row-left)} [:div {:on-click (fn [e] (dom/prevent-default e) (dom/stop-propagation e) - (st/emit! (wdt/toggle-token-theme id)))} + (st/emit! (wdt/toggle-token-theme-active? group name)))} [:& switch {:name (str "Theme" name) :on-change (constantly nil) :selected? selected?}]] @@ -97,7 +97,7 @@ [:button {:on-click (fn [e] (dom/prevent-default e) (dom/stop-propagation e) - (st/emit! (wdt/delete-token-theme id)))} + (st/emit! (wdt/delete-token-theme group name)))} i/delete]]]])]])] [:div {:class (stl/css :button-footer)} [:button {:class (stl/css :create-theme-button) @@ -109,43 +109,35 @@ "Create theme"]]])) (mf/defc edit-theme - [{:keys [token-sets theme theme-groups on-back on-submit]}] + [{:keys [edit? token-sets theme theme-groups on-back on-submit]}] (let [{:keys [dropdown-open? on-open-dropdown on-close-dropdown on-toggle-dropdown]} (wtco/use-dropdown-open-state) - - edit? (some? (:id theme)) - theme-state (mf/use-state {:token-sets token-sets - :theme theme}) - disabled? (-> (get-in @theme-state [:theme :name]) + theme-state (mf/use-state theme) + disabled? (-> (:name @theme-state) (str/trim) (str/empty?)) token-set-active? (mf/use-callback (mf/deps theme-state) - (fn [id] - (get-in @theme-state [:theme :sets id]))) + (fn [set-name] + (get-in @theme-state [:sets set-name]))) on-toggle-token-set (mf/use-callback (mf/deps theme-state) - (fn [token-set-id] - (swap! theme-state (fn [st] - (update st :theme #(wtts/toggle-token-set-to-token-theme token-set-id %)))))) - on-change-field (fn [field] - (fn [e] - (swap! theme-state (fn [st] (assoc-in st field (dom/get-target-val e)))))) + (fn [set-name] + (swap! theme-state #(ctob/toggle-set % set-name)))) + on-change-field (fn [field value] + (swap! theme-state #(assoc % field value))) group-input-ref (mf/use-ref) - on-update-group (on-change-field [:theme :group]) - on-update-name (on-change-field [:theme :name]) + on-update-group (partial on-change-field :group) + on-update-name (partial on-change-field :name) on-save-form (mf/use-callback (mf/deps theme-state on-submit) (fn [e] (dom/prevent-default e) - (let [theme (:theme @theme-state) - final-name (str/trim (:name theme)) - final-group (-> (:group theme) - (str/trim) - (str/lower))] - (when-not (str/empty? final-name) - (cond-> theme - (empty final-group) (dissoc :group) - :always on-submit))) + (let [theme (-> @theme-state + (update :name str/trim) + (update :group str/trim) + (update :description str/trim))] + (when-not (str/empty? (:name theme)) + (on-submit theme))) (on-back)))] [:form {:on-submit on-save-form} [:div {:class (stl/css :edit-theme-wrapper)} @@ -165,23 +157,23 @@ theme-groups) :on-select (fn [{:keys [value]}] (set! (.-value (mf/ref-val group-input-ref)) value) - (swap! theme-state assoc-in [:theme :group] value)) + (on-update-group value)) :on-close on-close-dropdown}]) [:& labeled-input {:label "Group" :input-props {:ref group-input-ref :default-value (:group theme) - :on-change on-update-group} + :on-change (comp on-update-group dom/get-target-val)} :render-right (when (seq theme-groups) (mf/fnc [] - [:button {:class (stl/css :group-drop-down-button) - :type "button" - :on-click (fn [e] - (dom/stop-propagation e) - (on-toggle-dropdown))} - i/arrow]))}]] + [:button {:class (stl/css :group-drop-down-button) + :type "button" + :on-click (fn [e] + (dom/stop-propagation e) + (on-toggle-dropdown))} + i/arrow]))}]] [:& labeled-input {:label "Theme" :input-props {:default-value (:name theme) - :on-change on-update-name}}]] + :on-change (comp on-update-name dom/get-target-val)}}]] [:div {:class (stl/css :sets-list-wrapper)} [:& wts/controlled-sets-list {:token-sets token-sets @@ -195,7 +187,7 @@ [:button {:class (stl/css :button-secondary) :type "button" :on-click (fn [] - (st/emit! (wdt/delete-token-theme (:id theme))) + (st/emit! (wdt/delete-token-theme (:group theme) (:name theme))) (on-back))} "Delete"] [:div]) @@ -212,32 +204,35 @@ (mf/defc controlled-edit-theme [{:keys [state set-state]}] - (let [{:keys [theme-id]} @state + (let [{:keys [theme-path]} @state + [_ theme-group theme-name] theme-path token-sets (mf/deref refs/workspace-ordered-token-sets) - theme (mf/deref (refs/workspace-token-theme theme-id)) + theme (mf/deref (refs/workspace-token-theme theme-group theme-name)) theme-groups (mf/deref refs/workspace-token-theme-groups)] [:& edit-theme - {:token-sets token-sets + {:edit? true + :token-sets token-sets :theme theme :theme-groups theme-groups :on-back #(set-state (constantly {:type :themes-overview})) - :on-submit #(st/emit! (wdt/update-token-theme %))}])) + :on-submit #(st/emit! (wdt/update-token-theme [(:group theme) (:name theme)] %))}])) (mf/defc create-theme [{:keys [set-state]}] (let [token-sets (mf/deref refs/workspace-ordered-token-sets) - theme {:name "" :sets #{}} + theme (ctob/make-token-theme :name "") theme-groups (mf/deref refs/workspace-token-theme-groups)] [:& edit-theme - {:token-sets token-sets + {:edit? false + :token-sets token-sets :theme theme :theme-groups theme-groups :on-back #(set-state (constantly {:type :themes-overview})) :on-submit #(st/emit! (wdt/create-token-theme %))}])) (mf/defc themes - [{:keys [] :as _args}] - (let [themes (mf/deref refs/workspace-ordered-token-themes) + [_] + (let [themes (mf/deref refs/workspace-token-themes-no-hidden) state (mf/use-state (if (empty? themes) {:type :create-theme} {:type :themes-overview})) @@ -258,7 +253,7 @@ (mf/defc modal {::mf/wrap-props false} - [{:keys [] :as _args}] + [_] (let [handle-close-dialog (mf/use-callback #(st/emit! (modal/hide)))] [:div {:class (stl/css :modal-overlay)} [:div {:class (stl/css :modal-dialog)} diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 11db39e78f..75854e9418 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.tokens.sets (:require-macros [app.main.style :as stl]) (:require + [app.main.data.messages :as msg] [app.main.data.tokens :as wdt] [app.main.refs :as refs] [app.main.store :as st] @@ -20,18 +21,18 @@ (def ^:private chevron-icon (i/icon-xref :arrow (stl/css :chevron-icon))) -(defn on-toggle-token-set-click [token-set-id] - (st/emit! (wdt/toggle-token-set {:token-set-id token-set-id}))) +(defn on-toggle-token-set-click [token-set-name] + (st/emit! (wdt/toggle-token-set {:token-set-name token-set-name}))) -(defn on-select-token-set-click [id] - (st/emit! (wdt/set-selected-token-set-id id))) +(defn on-select-token-set-click [name] + (st/emit! (wdt/set-selected-token-set-id name))) -(defn on-delete-token-set-click [id name event] +(defn on-delete-token-set-click [name event] (dom/stop-propagation event) - (st/emit! (wdt/delete-token-set id name))) + (st/emit! (wdt/delete-token-set name))) -(defn on-update-token-set [token-set] - (st/emit! (wdt/update-token-set token-set))) +(defn on-update-token-set [set-name token-set] + (st/emit! (wdt/update-token-set set-name token-set))) (defn on-create-token-set [token-set] (st/emit! (wdt/create-token-set token-set))) @@ -71,21 +72,21 @@ on-submit on-cancel] :as _props}] - (let [{:keys [id name _children]} token-set - selected? (and set? (token-set-selected? id)) - visible? (token-set-active? id) + (let [{:keys [name _children]} token-set + selected? (and set? (token-set-selected? name)) + visible? (token-set-active? name) collapsed? (mf/use-state false) set? true #_(= type :set) group? false #_(= type :group) - editing-node? (editing? id) + editing-node? (editing? name) on-select (mf/use-callback (mf/deps editing-node?) (fn [event] (dom/stop-propagation event) (when-not editing-node? - (on-select id)))) + (on-select name)))) on-context-menu (mf/use-callback - (mf/deps editing-node? id) + (mf/deps editing-node? name) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) @@ -93,11 +94,10 @@ (st/emit! (wdt/show-token-set-context-menu {:position (dom/get-client-position event) - :token-set-id id :token-set-name name})))))] [:div {:class (stl/css :set-item-container) :on-click on-select - :on-double-click #(on-edit id) + :on-double-click #(on-edit name) :on-context-menu on-context-menu} [:div {:class (stl/css-case :set-item-group group? :set-item-set set? @@ -116,14 +116,14 @@ [:* [:div {:class (stl/css :set-name)} name] [:div {:class (stl/css :delete-set)} - [:button {:on-click #(on-delete-token-set-click id name %) + [:button {:on-click #(on-delete-token-set-click name %) :type "button"} i/delete]] (if set? [:span {:class (stl/css :action-btn) :on-click (fn [event] (dom/stop-propagation event) - (on-toggle id))} + (on-toggle name))} (if visible? i/shown i/hide)] nil #_(when (and children (not @collapsed?)) @@ -134,6 +134,12 @@ :set-id child-id :selected-set-id selected-token-set-id)])]))])]])) +(defn warn-on-try-create-token-set-group! [] + (st/emit! (msg/show {:content "Token Set grouping is not supported yet." + :notification-type :toast + :type :warning + :timeout 3000}))) + (mf/defc controlled-sets-list [{:keys [token-sets on-update-token-set @@ -144,22 +150,27 @@ on-select context] :as _props}] - (let [{:keys [editing? new? on-edit on-create on-reset] :as ctx} (or context (sets-context/use-context))] + (let [{:keys [editing? new? on-edit on-create on-reset] :as ctx} (or context (sets-context/use-context)) + avoid-token-set-grouping #(str/replace % "/" "-")] [:ul {:class (stl/css :sets-list)} - (for [[id token-set] token-sets] + (for [token-set token-sets] (when token-set - [:& sets-tree {:key id - :token-set token-set - :token-set-selected? (if new? (constantly false) token-set-selected?) - :token-set-active? token-set-active? - :editing? editing? - :on-select on-select - :on-edit on-edit - :on-toggle on-toggle-token-set - :on-submit #(do - (on-update-token-set %) - (on-reset)) - :on-cancel on-reset}])) + [:& sets-tree + {:key (:name token-set) + :token-set token-set + :token-set-selected? (if new? (constantly false) token-set-selected?) + :token-set-active? token-set-active? + :editing? editing? + :on-select on-select + :on-edit on-edit + :on-toggle on-toggle-token-set + :on-submit #(do + ;; TODO: We don't support set grouping for now so we rename sets for now + (when (str/includes? (:name %) "/") + (warn-on-try-create-token-set-group!)) + (on-update-token-set (avoid-token-set-grouping (:name token-set)) (update % :name avoid-token-set-grouping)) + (on-reset)) + :on-cancel on-reset}])) (when new? [:& sets-tree {:token-set {:name ""} :token-set-selected? (constantly true) @@ -168,7 +179,10 @@ :on-select (constantly nil) :on-edit on-create :on-submit #(do - (on-create-token-set %) + ;; TODO: We don't support set grouping for now so we rename sets for now + (when (str/includes? (:name %) "/") + (warn-on-try-create-token-set-group!)) + (on-create-token-set (update % :name avoid-token-set-grouping)) (on-reset)) :on-cancel on-reset}])])) @@ -178,9 +192,9 @@ selected-token-set-id (mf/deref refs/workspace-selected-token-set-id) token-set-selected? (mf/use-callback (mf/deps selected-token-set-id) - (fn [id] - (= id selected-token-set-id))) - active-token-set-ids (mf/deref refs/workspace-active-set-ids) + (fn [set-name] + (= set-name selected-token-set-id))) + active-token-set-ids (mf/deref refs/workspace-active-set-names) token-set-active? (mf/use-callback (mf/deps active-token-set-ids) (fn [id] diff --git a/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs index 922f44858d..e82ba44c46 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs @@ -28,11 +28,11 @@ [:span {:class (stl/css :title)} title]]) (mf/defc menu - [{:keys [token-set-id token-set-name]}] + [{:keys [token-set-name]}] (let [{:keys [on-edit]} (sets-context/use-context)] [:ul {:class (stl/css :context-list)} - [:& menu-entry {:title "Rename" :on-click #(on-edit token-set-id)}] - [:& menu-entry {:title "Delete" :on-click #(st/emit! (wdt/delete-token-set token-set-id token-set-name))}]])) + [:& menu-entry {:title "Rename" :on-click #(on-edit token-set-name)}] + [:& menu-entry {:title "Delete" :on-click #(st/emit! (wdt/delete-token-set token-set-name))}]])) (mf/defc sets-context-menu [] @@ -41,7 +41,6 @@ left (+ (get-in mdata [:position :x]) 5) width (mf/use-state 0) dropdown-ref (mf/use-ref) - token-set-id (:token-set-id mdata) token-set-name (:token-set-name mdata)] (mf/use-effect (mf/deps mdata) @@ -54,5 +53,4 @@ :ref dropdown-ref :style {:top top :left left} :on-context-menu prevent-default} - [:& menu {:token-set-id token-set-id - :token-set-name token-set-name}]]])) + [:& menu {:token-set-name token-set-name}]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index b76b8e1df8..683265113d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -10,7 +10,6 @@ [app.common.data :as d] [app.main.data.modal :as modal] [app.main.data.tokens :as dt] - [app.main.data.tokens :as wdt] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.color-bullet :refer [color-bullet]] @@ -19,7 +18,6 @@ [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.assets.common :as cmm] [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.core :as wtc] [app.main.ui.workspace.tokens.sets :refer [sets-list]] @@ -34,7 +32,8 @@ [cuerdas.core :as str] [okulary.core :as l] [rumext.v2 :as mf] - [shadow.resource])) + [shadow.resource] + [app.common.types.tokens-lib :as ctob])) (def lens:token-type-open-status (l/derived (l/in [:workspace-tokens :open-status]) st/state)) @@ -42,15 +41,6 @@ (def ^:private download-icon (i/icon-xref :download (stl/css :download-icon))) -(def selected-set-id - (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 @@ -107,7 +97,7 @@ (dom/stop-propagation event) (st/emit! (dt/show-token-context-menu {:type :token :position (dom/get-client-position event) - :token-id (:id token)})))) + :token-name (:name token)})))) on-toggle-open-click (mf/use-fn (mf/deps open? tokens) @@ -117,7 +107,6 @@ (let [{:keys [key fields]} modal] (dom/stop-propagation event) (st/emit! (dt/set-token-type-section-open type true)) - (js/console.log "key" key) (modal/show! key {:x (.-clientX ^js event) :y (.-clientY ^js event) :position :right @@ -151,7 +140,7 @@ (for [token (sort-by :modified-at tokens)] (let [theme-token (get active-theme-tokens (wtt/token-identifier token))] [:& token-pill - {:key (:id token) + {:key (:name token) :token token :theme-token theme-token :highlighted? (wtt/shapes-token-applied? token selected-shapes (or all-attributes attributes)) @@ -162,7 +151,7 @@ "Separate token-types into groups of `:empty` or `:filled` depending if tokens exist for that type. Sort each group alphabetically (by their `:token-key`)." [tokens] - (let [tokens-by-type (wtc/group-tokens-by-type tokens) + (let [tokens-by-type (ctob/group-by-type tokens) {:keys [empty filled]} (->> wtty/token-types (map (fn [[token-key token-type-props]] {:token-key token-key @@ -173,23 +162,6 @@ {:empty (sort-by :token-key empty) :filled (sort-by :token-key filled)})) -(mf/defc tokene-theme-create - [_props] - (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"]])) - (mf/defc edit-button [{:keys [create?]}] [:button {:class (stl/css :themes-button) @@ -200,7 +172,7 @@ (mf/defc themes-sidebar [_props] - (let [ordered-themes (mf/deref refs/workspace-ordered-token-themes)] + (let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden)] [:div {:class (stl/css :theme-sidebar)} [:span {:class (stl/css :themes-header)} "Themes"] [:div {:class (stl/css :theme-select-wrapper)} @@ -241,7 +213,6 @@ 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) diff --git a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs index 72e1337309..eb603f50f5 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -72,34 +72,33 @@ (defn resolve-tokens+ [tokens & {:keys [names-map?] :as config}] - (p/let [sd-tokens (-> (wtt/token-names-tree tokens) - (resolve-sd-tokens+))] - (let [resolved-tokens (reduce - (fn [acc ^js cur] - (let [identifier (if names-map? - (.. cur -original -name) - (uuid (.-uuid (.-id cur)))) - {:keys [type] :as origin-token} (get tokens identifier) - value (.-value cur) - token-or-err (case type - :color (if-let [tc (tinycolor/valid-color value)] - {:value value :unit (tinycolor/color-format tc)} - {:errors [(wte/error-with-value :error.token/invalid-color value)]}) - (or (wtt/parse-token-value value) - (if-let [references (-> (wtt/find-token-references value) - (seq))] - {:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)] - :references references} - {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}))) - output-token (if (:errors token-or-err) - (merge origin-token token-or-err) - (assoc origin-token - :resolved-value (:value token-or-err) - :unit (:unit token-or-err)))] - (assoc acc (wtt/token-identifier output-token) output-token))) - {} sd-tokens)] - (l/debug :hint "Resolved tokens" :js/tokens resolved-tokens) - resolved-tokens))) + (let [{:keys [tree ids-map]} (wtt/token-names-tree-id-map tokens)] + (p/let [sd-tokens (resolve-sd-tokens+ tree)] + (let [resolved-tokens (reduce + (fn [acc ^js cur] + (let [{:keys [type] :as origin-token} (if names-map? + (get tokens (.. cur -original -name)) + (get ids-map (uuid (.-uuid (.-id cur))))) + value (.-value cur) + token-or-err (case type + :color (if-let [tc (tinycolor/valid-color value)] + {:value value :unit (tinycolor/color-format tc)} + {:errors [(wte/error-with-value :error.token/invalid-color value)]}) + (or (wtt/parse-token-value value) + (if-let [references (-> (wtt/find-token-references value) + (seq))] + {:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)] + :references references} + {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}))) + output-token (if (:errors token-or-err) + (merge origin-token token-or-err) + (assoc origin-token + :resolved-value (:value token-or-err) + :unit (:unit token-or-err)))] + (assoc acc (wtt/token-identifier output-token) output-token))) + {} sd-tokens)] + (l/debug :hint "Resolved tokens" :js/tokens resolved-tokens) + resolved-tokens)))) ;; Hooks ----------------------------------------------------------------------- @@ -115,7 +114,7 @@ This hook will return the unresolved tokens as state until they are processed, then the state will be updated with the resolved tokens." - [tokens & {:keys [cache-atom _names-map?] + [tokens & {:keys [cache-atom names-map?] :or {cache-atom !tokens-cache} :as config}] (let [tokens-state (mf/use-state (get @cache-atom tokens))] @@ -124,8 +123,11 @@ (fn [] (let [cached (get @cache-atom tokens)] (cond + (nil? tokens) (if names-map? {} []) ;; The tokens are already processing somewhere - (p/promise? cached) (p/then cached #(reset! tokens-state %)) + (p/promise? cached) (-> cached + (p/then #(reset! tokens-state %)) + #_(p/catch js/console.error)) ;; Get the cached entry (some? cached) (reset! tokens-state cached) ;; No cached entry, start processing diff --git a/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs b/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs index 0f0cc2a4e4..2805949b75 100644 --- a/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.tokens.theme-select (:require-macros [app.main.style :as stl]) (:require + [app.common.types.tokens-lib :as ctob] [app.common.uuid :as uuid] [app.main.data.modal :as modal] [app.main.data.tokens :as wdt] @@ -15,63 +16,58 @@ [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] [app.util.dom :as dom] + [cuerdas.core :as str] [rumext.v2 :as mf])) (mf/defc themes-list - [{:keys [themes active-theme-ids on-close grouped?]}] + [{:keys [themes active-theme-paths on-close grouped?]}] (when (seq themes) [:ul - (for [{:keys [id name]} themes - :let [selected? (get active-theme-ids id)]] - [:li {:key id + (for [[_ {:keys [group name] :as theme}] themes + :let [theme-id (ctob/theme-path theme) + selected? (get active-theme-paths theme-id)]] + [:li {:key theme-id :class (stl/css-case :checked-element true :sub-item grouped? :is-selected selected?) :on-click (fn [e] (dom/stop-propagation e) - (st/emit! (wdt/toggle-token-theme id)) + (st/emit! (wdt/toggle-token-theme-active? group name)) (on-close))} [:span {:class (stl/css :label)} name] [:span {:class (stl/css :check-icon)} i/tick]])])) (mf/defc theme-options - [{:keys [on-close]}] - (let [active-theme-ids (mf/deref refs/workspace-active-theme-ids) - ordered-themes (mf/deref refs/workspace-ordered-token-themes) - grouped-themes (dissoc ordered-themes nil) - ungrouped-themes (get ordered-themes nil)] - [:ul - [:& themes-list {:themes ungrouped-themes - :active-theme-ids active-theme-ids - :on-close on-close}] - (for [[group themes] grouped-themes] - [:li {:key group} - (when group - [:span {:class (stl/css :group)} group]) - [:& themes-list {:themes themes - :active-theme-ids active-theme-ids - :on-close on-close - :grouped? true}]]) - [:li {:class (stl/css-case :checked-element true - :checked-element-button true) - :on-click #(modal/show! :tokens/themes {})} - [:span "Edit themes"] - [:span {:class (stl/css :icon)} i/arrow]]])) + [{:keys [active-theme-paths themes on-close]}] + [:ul + (for [[group themes] themes] + [:li {:key group} + (when (seq group) + [:span {:class (stl/css :group)} group]) + [:& themes-list {:themes themes + :active-theme-paths active-theme-paths + :on-close on-close + :grouped? true}]]) + [:li {:class (stl/css-case :checked-element true + :checked-element-button true) + :on-click #(modal/show! :tokens/themes {})} + [:span "Edit themes"] + [:span {:class (stl/css :icon)} i/arrow]]]) (mf/defc theme-select [{:keys []}] (let [;; Store - temp-theme-id (mf/deref refs/workspace-temp-theme-id) - active-theme-ids (-> (mf/deref refs/workspace-active-theme-ids) - (disj temp-theme-id)) - active-themes-count (count active-theme-ids) - themes (mf/deref refs/workspace-token-themes) + active-theme-paths (mf/deref refs/workspace-active-theme-paths-no-hidden) + active-themes-count (count active-theme-paths) + themes (mf/deref refs/workspace-token-theme-tree-no-hidden) ;; Data current-label (cond (> active-themes-count 1) (str active-themes-count " themes active") - (pos? active-themes-count) (get-in themes [(first active-theme-ids) :name]) + (= active-themes-count 1) (some->> (first active-theme-paths) + (ctob/split-token-theme-path) + (str/join " / ")) :else "No theme active") ;; State @@ -92,4 +88,6 @@ [:& dropdown {:show is-open? :on-close on-close-dropdown} [:div {:ref dropdown-element* :class (stl/css :custom-select-dropdown)} - [:& theme-options {:on-close on-close-dropdown}]]]])) + [:& theme-options {:active-theme-paths active-theme-paths + :themes themes + :on-close on-close-dropdown}]]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/tinycolor.cljs b/frontend/src/app/main/ui/workspace/tokens/tinycolor.cljs index 91fac6bdf1..9a8d74f101 100644 --- a/frontend/src/app/main/ui/workspace/tokens/tinycolor.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/tinycolor.cljs @@ -6,18 +6,18 @@ (:require ["tinycolor2" :as tinycolor])) -(defn tinycolor? [x] +(defn tinycolor? [^js x] (and (instance? tinycolor x) (.isValid x))) (defn valid-color [color-str] (let [tc (tinycolor color-str)] (when (.isValid tc) tc))) -(defn ->hex [tc] +(defn ->hex [^js tc] (assert (tinycolor? tc)) (.toHex tc)) -(defn color-format [tc] +(defn color-format [^js tc] (assert (tinycolor? tc)) (.getFormat tc)) diff --git a/frontend/src/app/main/ui/workspace/tokens/token.cljs b/frontend/src/app/main/ui/workspace/tokens/token.cljs index 5f1a957668..b12ce4e55a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token.cljs @@ -1,17 +1,10 @@ (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] - [app.main.ui.workspace.tokens.tinycolor :as tinycolor])) - -(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])) + [cuerdas.core :as str])) (def parseable-token-value-regexp "Regexp that can be used to parse a number value out of resolved token value. @@ -66,12 +59,6 @@ [token shape token-attributes] (some #(token-attribute-applied? token shape %) token-attributes)) -(defn token-applied-attributes - "Return a set of which `token-attributes` are applied with `token`." - [token shape token-attributes] - (-> (filter #(token-attribute-applied? token shape %) token-attributes) - (set))) - (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] @@ -117,6 +104,19 @@ (->> (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] diff --git a/frontend/src/app/main/ui/workspace/tokens/token_set.cljs b/frontend/src/app/main/ui/workspace/tokens/token_set.cljs index b7b06e59e6..9e1af19c48 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token_set.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token_set.cljs @@ -1,100 +1,18 @@ (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])) + [app.common.types.tokens-lib :as ctob])) + +(defn get-workspace-tokens-lib [state] + (get-in state [:workspace-data :tokens-lib])) ;; Themes ---------------------------------------------------------------------- -(defn get-workspace-themes [state] - (get-in state [:workspace-data :token-themes] [])) - -(defn get-workspace-theme [id state] - (get-in state [:workspace-data :token-themes-index id])) - -(defn get-workspace-themes-index [state] - (get-in state [:workspace-data :token-themes-index] {})) - -(defn get-workspace-theme-groups [state] - (reduce - (fn [acc {:keys [group]}] - (if group - (conj acc group) - acc)) - #{} (vals (get-workspace-themes-index state)))) - -(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`. - Deactivate 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) @@ -110,67 +28,29 @@ (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-active-theme-sets-tokens-names-map [state] + (when-let [lib (get-workspace-tokens-lib state)] + (ctob/get-active-themes-set-tokens lib))) -(defn get-workspace-ordered-sets [state] - ;; TODO Include groups - (let [top-level-set-ids (get-in state [:workspace-data :token-set-groups]) - token-sets (get-workspace-sets state)] - (->> (map (fn [id] [id (get token-sets id)]) top-level-set-ids) - (into (ordered-map))))) - -(defn get-workspace-ordered-sets-tokens [state] - (let [sets (get-workspace-ordered-sets state)] - (reduce - (fn [acc [_ {:keys [tokens] :as sets}]] - (reduce (fn [acc' token-id] - (if-let [token (wtt/get-workspace-token token-id state)] - (assoc acc' (wtt/token-identifier token) token) - acc')) - acc tokens)) - {} sets))) - -(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)) +;; === Set selection (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]))) + (some-> (get-workspace-tokens-lib state) + (ctob/get-sets) + (first) + (:name)))) (defn get-selected-token-set [state] (when-let [id (get-selected-token-set-id state)] - (get-token-set id state))) + (some-> (get-workspace-tokens-lib state) + (ctob/get-set id)))) (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))))) + (some-> (get-selected-token-set state) + :tokens)) (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))) diff --git a/frontend/test/token_tests/helpers/state.cljs b/frontend/test/token_tests/helpers/state.cljs index 283c3125fe..789de70494 100644 --- a/frontend/test/token_tests/helpers/state.cljs +++ b/frontend/test/token_tests/helpers/state.cljs @@ -1,5 +1,6 @@ (ns token-tests.helpers.state (:require + [app.common.types.tokens-lib :as ctob] [app.main.ui.workspace.tokens.style-dictionary :as sd] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) @@ -21,11 +22,10 @@ (ptk/reify ::end+ ptk/WatchEvent (watch [_ state _] - (->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens]))) - (rx/mapcat - (fn [_] - (rx/of - (end)))))))) + (->> (rx/from (-> (get-in state [:workspace-data :tokens-lib]) + (ctob/get-active-themes-set-tokens) + (sd/resolve-tokens+ {:names-map? true}))) + (rx/mapcat #(rx/of (end))))))) (defn stop-on "Helper function to be used with async version of run-store. diff --git a/frontend/test/token_tests/helpers/tokens.cljs b/frontend/test/token_tests/helpers/tokens.cljs index 87db316fb9..73cf124fca 100644 --- a/frontend/test/token_tests/helpers/tokens.cljs +++ b/frontend/test/token_tests/helpers/tokens.cljs @@ -1,16 +1,18 @@ (ns token-tests.helpers.tokens (:require [app.common.test-helpers.ids-map :as thi] - [app.main.ui.workspace.tokens.token :as wtt])) + [app.main.ui.workspace.tokens.token :as wtt] + [app.common.types.tokens-lib :as ctob])) (defn add-token [state label params] (let [id (thi/new-id! label) token (assoc params :id id)] (update-in state [:data :tokens] assoc id token))) -(defn get-token [file label] - (let [id (thi/id label)] - (get-in file [:data :tokens id]))) +(defn get-token [file name] + (some-> (get-in file [:data :tokens-lib]) + (ctob/get-active-themes-set-tokens) + (get name))) (defn apply-token-to-shape [file shape-label token-label attributes] (let [first-page-id (get-in file [:data :pages 0]) diff --git a/frontend/test/token_tests/logic/token_actions_test.cljs b/frontend/test/token_tests/logic/token_actions_test.cljs index c95af99a26..d710c9bf18 100644 --- a/frontend/test/token_tests/logic/token_actions_test.cljs +++ b/frontend/test/token_tests/logic/token_actions_test.cljs @@ -1,14 +1,11 @@ (ns token-tests.logic.token-actions-test (:require - [app.common.pprint :refer [pprint]] [app.common.logging :as log] [app.common.test-helpers.compositions :as ctho] [app.common.test-helpers.files :as cthf] [app.common.test-helpers.shapes :as cths] - [app.main.data.tokens :as wdt] + [app.common.types.tokens-lib :as ctob] [app.main.ui.workspace.tokens.changes :as wtch] - [app.main.ui.workspace.tokens.token :as wtt] - [app.main.ui.workspace.tokens.token-set :as wtts] [cljs.test :as t :include-macros true] [frontend-tests.helpers.pages :as thp] [frontend-tests.helpers.state :as ths] @@ -25,13 +22,13 @@ (cthf/sample-file :file-1 :page-label :page-1)) (def border-radius-token - {:value "12" - :name "borderRadius.sm" + {:name "borderRadius.sm" + :value "12" :type :border-radius}) -(def ^:private reference-border-radius-token - {:value "{borderRadius.sm} * 2" - :name "borderRadius.md" +(def reference-border-radius-token + {:name "borderRadius.md" + :value "{borderRadius.sm} * 2" :type :border-radius}) (defn setup-file-with-tokens @@ -40,45 +37,13 @@ (ctho/add-rect :rect-1 rect-1) (ctho/add-rect :rect-2 rect-2) (ctho/add-rect :rect-3 rect-3) - (toht/add-token :token-1 border-radius-token) - (toht/add-token :token-2 reference-border-radius-token))) - -(t/deftest test-create-token - (t/testing "creates token in new token set" - (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))))))))))))) + (assoc-in [:data :tokens-lib] + (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"})) + (ctob/set-active-themes #{"/Theme A"}) + (ctob/add-set (ctob/make-token-set :name "Set A")) + (ctob/add-token-in-set "Set A" (ctob/make-token border-radius-token)) + (ctob/add-token-in-set "Set A" (ctob/make-token reference-border-radius-token)))))) (t/deftest test-apply-token (t/testing "applies token to shape and updates shape attributes to resolved value" @@ -89,18 +54,18 @@ rect-1 (cths/get-shape file :rect-1) events [(wtch/apply-token {:shape-ids [(:id rect-1)] :attributes #{:rx :ry} - :token (toht/get-token file :token-2) + :token (toht/get-token file "borderRadius.md") :on-update-shape wtch/update-shape-radius-all})]] (tohs/run-store-async store done events (fn [new-state] (let [file' (ths/get-file-from-store new-state) - token-2' (toht/get-token file' :token-2) + token (toht/get-token file' "borderRadius.md") rect-1' (cths/get-shape file' :rect-1)] (t/testing "shape `:applied-tokens` got updated" (t/is (some? (:applied-tokens rect-1'))) - (t/is (= (:rx (:applied-tokens rect-1')) (wtt/token-identifier token-2'))) - (t/is (= (:ry (:applied-tokens rect-1')) (wtt/token-identifier token-2')))) + (t/is (= (:rx (:applied-tokens rect-1')) (:name token))) + (t/is (= (:ry (:applied-tokens rect-1')) (:name token)))) (t/testing "shape radius got update to the resolved token value." (t/is (= (:rx rect-1') 24)) (t/is (= (:ry rect-1') 24)))))))))) @@ -114,22 +79,22 @@ rect-1 (cths/get-shape file :rect-1) events [(wtch/apply-token {:shape-ids [(:id rect-1)] :attributes #{:rx :ry} - :token (toht/get-token file :token-1) + :token (toht/get-token file "borderRadius.sm") :on-update-shape wtch/update-shape-radius-all}) (wtch/apply-token {:shape-ids [(:id rect-1)] :attributes #{:rx :ry} - :token (toht/get-token file :token-2) + :token (toht/get-token file "borderRadius.md") :on-update-shape wtch/update-shape-radius-all})]] (tohs/run-store-async store done events (fn [new-state] (let [file' (ths/get-file-from-store new-state) - token-2' (toht/get-token file' :token-2) + token (toht/get-token file' "borderRadius.md") rect-1' (cths/get-shape file' :rect-1)] (t/testing "shape `:applied-tokens` got updated" (t/is (some? (:applied-tokens rect-1'))) - (t/is (= (:rx (:applied-tokens rect-1')) (wtt/token-identifier token-2'))) - (t/is (= (:ry (:applied-tokens rect-1')) (wtt/token-identifier token-2')))) + (t/is (= (:rx (:applied-tokens rect-1')) (:name token))) + (t/is (= (:ry (:applied-tokens rect-1')) (:name token)))) (t/testing "shape radius got update to the resolved token value." (t/is (= (:rx rect-1') 24)) (t/is (= (:ry rect-1') 24)))))))))) @@ -141,9 +106,9 @@ (let [file (setup-file-with-tokens) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) - events [;; Apply `:token-1` to all border radius attributes + events [;; Apply "borderRadius.sm" to all border radius attributes (wtch/apply-token {:attributes #{:rx :ry :r1 :r2 :r3 :r4} - :token (toht/get-token file :token-1) + :token (toht/get-token file "borderRadius.sm") :shape-ids [(:id rect-1)] :on-update-shape wtch/update-shape-radius-all}) ;; Apply single `:r1` attribute to same shape @@ -151,22 +116,22 @@ ;; but keep `:r4` for testing purposes (wtch/apply-token {:attributes #{:r1} :attributes-to-remove #{:rx :ry :r1 :r2 :r3} - :token (toht/get-token file :token-2) + :token (toht/get-token file "borderRadius.md") :shape-ids [(:id rect-1)] :on-update-shape wtch/update-shape-radius-all})]] (tohs/run-store-async store done events (fn [new-state] (let [file' (ths/get-file-from-store new-state) - token-1' (toht/get-token file' :token-1) - token-2' (toht/get-token file' :token-2) + token-sm (toht/get-token file' "borderRadius.sm") + token-md (toht/get-token file' "borderRadius.md") rect-1' (cths/get-shape file' :rect-1)] (t/testing "other border-radius attributes got removed" (t/is (nil? (:rx (:applied-tokens rect-1'))))) - (t/testing "r1 got applied with :token-2" - (t/is (= (:r1 (:applied-tokens rect-1')) (wtt/token-identifier token-2')))) - (t/testing "while :r4 was kept" - (t/is (= (:r4 (:applied-tokens rect-1')) (wtt/token-identifier token-1')))))))))));))))))))))) + (t/testing "r1 got applied with borderRadius.md" + (t/is (= (:r1 (:applied-tokens rect-1')) (:name token-md)))) + (t/testing "while :r4 was kept with borderRadius.sm" + (t/is (= (:r4 (:applied-tokens rect-1')) (:name token-sm))))))))))) (t/deftest test-apply-dimensions (t/testing "applies dimensions token and updates the shapes width and height" @@ -196,30 +161,62 @@ (t/is (= (:width rect-1') 100)) (t/is (= (:height rect-1') 100)))))))))) -(t/deftest test-apply-sizing - (t/testing "applies sizing token and updates the shapes width and height" +(t/deftest test-apply-dimensions + (t/testing "applies dimensions token and updates the shapes width and height" (t/async done - (let [file (-> (setup-file-with-tokens) - (toht/add-token :token-target {:value "100" - :name "sizing.sm" - :type :sizing})) + (let [dimensions-token {:name "dimensions.sm" + :value "100" + :type :dimensions} + file (-> (setup-file-with-tokens) + (update-in [:data :tokens-lib] + #(ctob/add-token-in-set % "Set A" (ctob/make-token dimensions-token)))) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) events [(wtch/apply-token {:shape-ids [(:id rect-1)] :attributes #{:width :height} - :token (toht/get-token file :token-target) + :token (toht/get-token file "dimensions.sm") :on-update-shape wtch/update-shape-dimensions})]] (tohs/run-store-async store done events (fn [new-state] (let [file' (ths/get-file-from-store new-state) - token-target' (toht/get-token file' :token-target) + token-target' (toht/get-token file' "dimensions.sm") rect-1' (cths/get-shape file' :rect-1)] (t/testing "shape `:applied-tokens` got updated" (t/is (some? (:applied-tokens rect-1'))) - (t/is (= (:width (:applied-tokens rect-1')) (wtt/token-identifier token-target'))) - (t/is (= (:height (:applied-tokens rect-1')) (wtt/token-identifier token-target')))) + (t/is (= (:width (:applied-tokens rect-1')) (:name token-target'))) + (t/is (= (:height (:applied-tokens rect-1')) (:name token-target')))) + (t/testing "shapes width and height got updated" + (t/is (= (:width rect-1') 100)) + (t/is (= (:height rect-1') 100)))))))))) + +(t/deftest test-apply-sizing + (t/testing "applies sizing token and updates the shapes width and height" + (t/async + done + (let [sizing-token {:name "sizing.sm" + :value "100" + :type :sizing} + file (-> (setup-file-with-tokens) + (update-in [:data :tokens-lib] + #(ctob/add-token-in-set % "Set A" (ctob/make-token sizing-token)))) + store (ths/setup-store file) + rect-1 (cths/get-shape file :rect-1) + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:width :height} + :token (toht/get-token file "sizing.sm") + :on-update-shape wtch/update-shape-dimensions})]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state) + token-target' (toht/get-token file' "sizing.sm") + rect-1' (cths/get-shape file' :rect-1)] + (t/testing "shape `:applied-tokens` got updated" + (t/is (some? (:applied-tokens rect-1'))) + (t/is (= (:width (:applied-tokens rect-1')) (:name token-target'))) + (t/is (= (:height (:applied-tokens rect-1')) (:name token-target')))) (t/testing "shapes width and height got updated" (t/is (= (:width rect-1') 100)) (t/is (= (:height rect-1') 100)))))))))) @@ -228,31 +225,36 @@ (t/testing "applies opacity token and updates the shapes opacity" (t/async done - (let [file (-> (setup-file-with-tokens) - (toht/add-token :opacity-float {:value "0.3" - :name "opacity.float" - :type :opacity}) - (toht/add-token :opacity-percent {:value "40%" - :name "opacity.percent" - :type :opacity}) - (toht/add-token :opacity-invalid {:value "100" - :name "opacity.invalid" - :type :opacity})) + (let [opacity-float {:name "opacity.float" + :value "0.3" + :type :opacity} + opacity-percent {:name "opacity.percent" + :value "40%" + :type :opacity} + opacity-invalid {:name "opacity.invalid" + :value "100" + :type :opacity} + file (-> (setup-file-with-tokens) + (update-in [:data :tokens-lib] + #(-> % + (ctob/add-token-in-set "Set A" (ctob/make-token opacity-float)) + (ctob/add-token-in-set "Set A" (ctob/make-token opacity-percent)) + (ctob/add-token-in-set "Set A" (ctob/make-token opacity-invalid))))) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) rect-2 (cths/get-shape file :rect-2) rect-3 (cths/get-shape file :rect-3) events [(wtch/apply-token {:shape-ids [(:id rect-1)] :attributes #{:opacity} - :token (toht/get-token file :opacity-float) + :token (toht/get-token file "opacity.float") :on-update-shape wtch/update-opacity}) (wtch/apply-token {:shape-ids [(:id rect-2)] :attributes #{:opacity} - :token (toht/get-token file :opacity-percent) + :token (toht/get-token file "opacity.percent") :on-update-shape wtch/update-opacity}) (wtch/apply-token {:shape-ids [(:id rect-3)] :attributes #{:opacity} - :token (toht/get-token file :opacity-invalid) + :token (toht/get-token file "opacity.invalid") :on-update-shape wtch/update-opacity})]] (tohs/run-store-async store done events @@ -261,74 +263,78 @@ rect-1' (cths/get-shape file' :rect-1) rect-2' (cths/get-shape file' :rect-2) rect-3' (cths/get-shape file' :rect-3) - token-opacity-float (toht/get-token file' :opacity-float) - token-opacity-percent (toht/get-token file' :opacity-percent) - token-opacity-invalid (toht/get-token file' :opacity-invalid)] + token-opacity-float (toht/get-token file' "opacity.float") + token-opacity-percent (toht/get-token file' "opacity.percent") + token-opacity-invalid (toht/get-token file' "opacity.invalid")] (t/testing "float value got translated to float and applied to opacity" - (t/is (= (:opacity (:applied-tokens rect-1')) (wtt/token-identifier token-opacity-float))) + (t/is (= (:opacity (:applied-tokens rect-1')) (:name token-opacity-float))) (t/is (= (:opacity rect-1') 0.3))) (t/testing "percentage value got translated to float and applied to opacity" - (t/is (= (:opacity (:applied-tokens rect-2')) (wtt/token-identifier token-opacity-percent))) + (t/is (= (:opacity (:applied-tokens rect-2')) (:name token-opacity-percent))) (t/is (= (:opacity rect-2') 0.4))) (t/testing "invalid opacity value got applied but did not change shape" - (t/is (= (:opacity (:applied-tokens rect-3')) (wtt/token-identifier token-opacity-invalid))) + (t/is (= (:opacity (:applied-tokens rect-3')) (:name token-opacity-invalid))) (t/is (nil? (:opacity rect-3'))))))))))) (t/deftest test-apply-rotation (t/testing "applies rotation token and updates the shapes rotation" (t/async done - (let [file (-> (setup-file-with-tokens) - (toht/add-token :token-target {:value "120" - :name "rotation.medium" - :type :rotation})) + (let [rotation-token {:name "rotation.medium" + :value "120" + :type :rotation} + file (-> (setup-file-with-tokens) + (update-in [:data :tokens-lib] + #(ctob/add-token-in-set % "Set A" (ctob/make-token rotation-token)))) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) events [(wtch/apply-token {:shape-ids [(:id rect-1)] :attributes #{:rotation} - :token (toht/get-token file :token-target) + :token (toht/get-token file "rotation.medium") :on-update-shape wtch/update-rotation})]] (tohs/run-store-async store done events (fn [new-state] (let [file' (ths/get-file-from-store new-state) - token-target' (toht/get-token file' :token-target) + token-target' (toht/get-token file' "rotation.medium") rect-1' (cths/get-shape file' :rect-1)] (t/is (some? (:applied-tokens rect-1'))) - (t/is (= (:rotation (:applied-tokens rect-1')) (wtt/token-identifier token-target'))) + (t/is (= (:rotation (:applied-tokens rect-1')) (:name token-target'))) (t/is (= (:rotation rect-1') 120))))))))) (t/deftest test-apply-stroke-width (t/testing "applies stroke-width token and updates the shapes with stroke" (t/async done - (let [file (-> (setup-file-with-tokens {:rect-1 {:strokes [{:stroke-alignment :inner, + (let [stroke-width-token {:name "stroke-width.sm" + :value "10" + :type :stroke-width} + file (-> (setup-file-with-tokens {:rect-1 {:strokes [{:stroke-alignment :inner, :stroke-style :solid, :stroke-color "#000000", :stroke-opacity 1, :stroke-width 5}]}}) - (toht/add-token :token-target {:value "10" - :name "stroke-width.sm" - :type :stroke-width})) + (update-in [:data :tokens-lib] + #(ctob/add-token-in-set % "Set A" (ctob/make-token stroke-width-token)))) store (ths/setup-store file) rect-with-stroke (cths/get-shape file :rect-1) rect-without-stroke (cths/get-shape file :rect-2) events [(wtch/apply-token {:shape-ids [(:id rect-with-stroke) (:id rect-without-stroke)] :attributes #{:stroke-width} - :token (toht/get-token file :token-target) + :token (toht/get-token file "stroke-width.sm") :on-update-shape wtch/update-stroke-width})]] (tohs/run-store-async store done events (fn [new-state] (let [file' (ths/get-file-from-store new-state) - token-target' (toht/get-token file' :token-target) + token-target' (toht/get-token file' "stroke-width.sm") rect-with-stroke' (cths/get-shape file' :rect-1) rect-without-stroke' (cths/get-shape file' :rect-2)] (t/testing "token got applied to rect with stroke and shape stroke got updated" - (t/is (= (:stroke-width (:applied-tokens rect-with-stroke')) (wtt/token-identifier token-target'))) + (t/is (= (:stroke-width (:applied-tokens rect-with-stroke')) (:name token-target'))) (t/is (= (get-in rect-with-stroke' [:strokes 0 :stroke-width]) 10))) (t/testing "token got applied to rect without stroke but shape didnt get updated" - (t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (wtt/token-identifier token-target'))) + (t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target'))) (t/is (empty? (:strokes rect-without-stroke'))))))))))) (t/deftest test-toggle-token-none @@ -342,20 +348,20 @@ events [(wtch/toggle-token {:shapes [rect-1 rect-2] :token-type-props {:attributes #{:rx :ry} :on-update-shape wtch/update-shape-radius-all} - :token (toht/get-token file :token-2)})]] + :token (toht/get-token file "borderRadius.md")})]] (tohs/run-store-async store done events (fn [new-state] (let [file' (ths/get-file-from-store new-state) - token-2' (toht/get-token file' :token-2) - rect-1' (cths/get-shape file' :rect-1) - rect-2' (cths/get-shape file' :rect-2)] + token-2' (toht/get-token file' "borderRadius.md") + rect-1' (cths/get-shape file' :rect-1) + rect-2' (cths/get-shape file' :rect-2)] (t/is (some? (:applied-tokens rect-1'))) (t/is (some? (:applied-tokens rect-2'))) - (t/is (= (:rx (:applied-tokens rect-1')) (wtt/token-identifier token-2'))) - (t/is (= (:rx (:applied-tokens rect-2')) (wtt/token-identifier token-2'))) - (t/is (= (:ry (:applied-tokens rect-1')) (wtt/token-identifier token-2'))) - (t/is (= (:ry (:applied-tokens rect-2')) (wtt/token-identifier token-2'))) + (t/is (= (:rx (:applied-tokens rect-1')) (:name token-2'))) + (t/is (= (:rx (:applied-tokens rect-2')) (:name token-2'))) + (t/is (= (:ry (:applied-tokens rect-1')) (:name token-2'))) + (t/is (= (:ry (:applied-tokens rect-2')) (:name token-2'))) (t/is (= (:rx rect-1') 24)) (t/is (= (:rx rect-2') 24))))))))) @@ -364,8 +370,8 @@ (t/async done (let [file (-> (setup-file-with-tokens) - (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-1 "borderRadius.sm" #{:rx :ry}) + (toht/apply-token-to-shape :rect-3 "borderRadius.md" #{:rx :ry})) store (ths/setup-store file) rect-with-token (cths/get-shape file :rect-1) @@ -373,7 +379,7 @@ rect-with-other-token (cths/get-shape file :rect-3) events [(wtch/toggle-token {:shapes [rect-with-token rect-without-token rect-with-other-token] - :token (toht/get-token file :token-1) + :token (toht/get-token file "borderRadius.sm") :token-type-props {:attributes #{:rx :ry}}})]] (tohs/run-store-async store done events @@ -383,7 +389,7 @@ rect-without-token' (cths/get-shape file' :rect-2) rect-with-other-token' (cths/get-shape file' :rect-3)] - (t/testing "rect-with-token got the token remove" + (t/testing "rect-with-token got the token removed" (t/is (nil? (:rx (:applied-tokens rect-with-token')))) (t/is (nil? (:ry (:applied-tokens rect-with-token'))))) @@ -398,8 +404,8 @@ (t/async done (let [file (-> (setup-file-with-tokens) - (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-1 "borderRadius.md" #{:rx :ry}) + (toht/apply-token-to-shape :rect-3 "borderRadius.md" #{:rx :ry})) store (ths/setup-store file) rect-with-other-token-1 (cths/get-shape file :rect-1) @@ -407,22 +413,22 @@ rect-with-other-token-2 (cths/get-shape file :rect-3) events [(wtch/toggle-token {:shapes [rect-with-other-token-1 rect-without-token rect-with-other-token-2] - :token (toht/get-token file :token-1) + :token (toht/get-token file "borderRadius.sm") :token-type-props {:attributes #{:rx :ry}}})]] (tohs/run-store-async store done events (fn [new-state] (let [file' (ths/get-file-from-store new-state) - target-token (toht/get-token file' :token-1) + target-token (toht/get-token file' "borderRadius.sm") rect-with-other-token-1' (cths/get-shape file' :rect-1) rect-without-token' (cths/get-shape file' :rect-2) rect-with-other-token-2' (cths/get-shape file' :rect-3)] (t/testing "token got applied to all shapes" - (t/is (= (:rx (:applied-tokens rect-with-other-token-1')) (wtt/token-identifier target-token))) - (t/is (= (:rx (:applied-tokens rect-without-token')) (wtt/token-identifier target-token))) - (t/is (= (:rx (:applied-tokens rect-with-other-token-2')) (wtt/token-identifier target-token))) + (t/is (= (:rx (:applied-tokens rect-with-other-token-1')) (:name target-token))) + (t/is (= (:rx (:applied-tokens rect-without-token')) (:name target-token))) + (t/is (= (:rx (:applied-tokens rect-with-other-token-2')) (:name target-token))) - (t/is (= (:ry (:applied-tokens rect-with-other-token-1')) (wtt/token-identifier target-token))) - (t/is (= (:ry (:applied-tokens rect-without-token')) (wtt/token-identifier target-token))) - (t/is (= (:ry (:applied-tokens rect-with-other-token-2')) (wtt/token-identifier target-token))))))))))) + (t/is (= (:ry (:applied-tokens rect-with-other-token-1')) (:name target-token))) + (t/is (= (:ry (:applied-tokens rect-without-token')) (:name target-token))) + (t/is (= (:ry (:applied-tokens rect-with-other-token-2')) (:name target-token))))))))))) diff --git a/frontend/test/token_tests/style_dictionary_test.cljs b/frontend/test/token_tests/style_dictionary_test.cljs index 638fa7e3c1..07b407e132 100644 --- a/frontend/test/token_tests/style_dictionary_test.cljs +++ b/frontend/test/token_tests/style_dictionary_test.cljs @@ -3,39 +3,42 @@ [app.main.ui.workspace.tokens.style-dictionary :as sd] [cljs.test :as t :include-macros true] [promesa.core :as p] - [app.main.ui.workspace.tokens.token :as wtt])) + [app.main.ui.workspace.tokens.token :as wtt] + [app.common.data :as d])) (def border-radius-token - {:id #uuid "8c868278-7c8d-431b-bbc9-7d8f15c8edb9" - :value "12px" + {:value "12px" :name "borderRadius.sm" :type :border-radius}) (def reference-border-radius-token - {:id #uuid "b9448d78-fd5b-4e3d-aa32-445904063f5b" - :value "{borderRadius.sm} * 2" + {:value "{borderRadius.sm} * 2" :name "borderRadius.md-with-dashes" :type :border-radius}) -(def tokens {(:id border-radius-token) border-radius-token - (:id reference-border-radius-token) reference-border-radius-token}) - +(def tokens (d/ordered-map + (:name border-radius-token) border-radius-token + (:name reference-border-radius-token) reference-border-radius-token)) (t/deftest resolve-tokens-test (t/async done (t/testing "resolves tokens using style-dictionary from a ids map" (-> (sd/resolve-tokens+ tokens) - (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)))))))) + (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 (= 12 (get-in resolved-tokens ["borderRadius.sm" :resolved-value]))) + (t/is (= "px" (get-in resolved-tokens ["borderRadius.sm" :unit]))) + (t/is (= 24 (get-in resolved-tokens ["borderRadius.md-with-dashes" :resolved-value]))) + (t/is (= "px" (get-in resolved-tokens ["borderRadius.md-with-dashes" :unit]))) + (done)))))))) (t/deftest resolve-tokens-names-map-test (t/async @@ -48,10 +51,10 @@ (let [expected-tokens {"borderRadius.sm" (assoc border-radius-token :resolved-value 12 - :resolved-unit "px") + :unit "px") "borderRadius.md-with-dashes" (assoc reference-border-radius-token :resolved-value 24 - :resolved-unit "px")}] + :unit "px")}] (t/is (= expected-tokens resolved-tokens)) (done)))))))) diff --git a/frontend/test/token_tests/token_set_test.cljs b/frontend/test/token_tests/token_set_test.cljs deleted file mode 100644 index d199e221cf..0000000000 --- a/frontend/test/token_tests/token_set_test.cljs +++ /dev/null @@ -1,37 +0,0 @@ -(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))))))) diff --git a/frontend/test/token_tests/token_test.cljs b/frontend/test/token_tests/token_test.cljs index 4b38c889cc..0dfaf675d0 100644 --- a/frontend/test/token_tests/token_test.cljs +++ b/frontend/test/token_tests/token_test.cljs @@ -50,10 +50,6 @@ (t/testing "doesn't match passed `:token-attributes`" (t/is (nil? (wtt/token-applied? {:name "a"} {:applied-tokens {:x "a"}} #{:y}))))) -(t/deftest token-applied-attributes - (t/is (= #{:x} (wtt/token-applied-attributes {:name "a"} - {:applied-tokens {:x "a" :y "b"}} - #{:x :missing})))) (t/deftest shapes-ids-by-applied-attributes (t/testing "Returns set of matched attributes that fit the applied token" (let [attributes #{:x :y :z}