diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 9bc022f722..b4ae49a657 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.files.helpers :as cfh] + [app.common.files.tokens :as cfo] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.schema :as sm] @@ -29,6 +30,7 @@ [app.common.types.shape-tree :as ctst] [app.common.types.token :as cto] [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] [app.common.types.typographies-list :as ctyl] [app.common.types.typography :as ctt] [app.common.types.variant :as ctv] @@ -397,6 +399,13 @@ [:id ::sm/uuid] [:attrs [:maybe ctob/schema:token-theme-attrs]]]] + [:set-tokens-status + [:map {:title "SetTokensStatus"} + [:type [:= :set-tokens-status]] + [:theme-ids [:set ::sm/uuid]] + [:set-ids [:set ::sm/uuid]]]] + + ;; TODO deprecate this once everyone uses set-tokens-status [:set-active-token-themes [:map {:title "SetActiveTokenThemes"} [:type [:= :set-active-token-themes]] @@ -425,9 +434,9 @@ [:before-group [:maybe :boolean]]]] [:set-base-font-size - [:map {:title "ModBaseFontSize"} - [:type [:= :set-base-font-size]] - [:base-font-size :string]]] + [:map {:title "ModBaseFontSize"} + [:type [:= :set-base-font-size]] + [:base-font-size :string]]] [:set-tokens-file [:map {:title "SetTokensFile"} @@ -993,73 +1002,78 @@ (defmethod process-change :set-token [data {:keys [set-id token-id attrs]}] - (let [data (ctf/ensure-tokens-lib data)] - (update data :tokens-lib - (fn [lib'] - (cond - (not attrs) - (ctob/delete-token lib' set-id token-id) + (-> (cfo/ensure-tokens-lib data) + (cfo/update-tokens-lib + (fn [lib'] + (cond + (not attrs) + (ctob/delete-token lib' set-id token-id) - (not (ctob/get-token lib' set-id token-id)) - (ctob/add-token lib' set-id (ctob/make-token attrs)) + (not (ctob/get-token lib' set-id token-id)) + (ctob/add-token lib' set-id (ctob/make-token attrs)) - :else - (ctob/update-token lib' set-id token-id - (fn [prev-token] - (ctob/make-token (merge prev-token attrs))))))))) + :else + (ctob/update-token lib' set-id token-id + (fn [prev-token] + (ctob/make-token (merge prev-token attrs))))))))) (defmethod process-change :set-token-set [data {:keys [id attrs]}] - (let [data (ctf/ensure-tokens-lib data)] - (update data :tokens-lib - (fn [lib'] - (cond - (not attrs) - (ctob/delete-set lib' id) + (-> (cfo/ensure-tokens-lib data) + (cfo/update-tokens-lib + (fn [lib'] + (cond + (not attrs) + (ctob/delete-set lib' id) - (not (ctob/get-set lib' id)) - (ctob/add-set lib' (ctob/make-token-set attrs)) + (not (ctob/get-set lib' id)) + (ctob/add-set lib' (ctob/make-token-set attrs)) - :else - (ctob/update-set lib' id (fn [_] (ctob/make-token-set attrs)))))))) + :else + (ctob/update-set lib' id (fn [_] (ctob/make-token-set attrs)))))))) (defmethod process-change :set-token-theme [data {:keys [id attrs]}] - (let [data (ctf/ensure-tokens-lib data)] - (update data :tokens-lib - (fn [lib'] - (cond - (not attrs) - (ctob/delete-theme lib' id) + (-> (cfo/ensure-tokens-lib data) + (cfo/update-tokens-lib + (fn [lib'] + (cond + (not attrs) + (ctob/delete-theme lib' id) - (not (ctob/get-theme lib' id)) - (ctob/add-theme lib' (ctob/make-token-theme attrs)) + (not (ctob/get-theme lib' id)) + (ctob/add-theme lib' (ctob/make-token-theme attrs)) - :else - (ctob/update-theme lib' - id - (fn [prev-token-theme] - (ctob/make-token-theme (merge prev-token-theme attrs))))))))) + :else + (ctob/update-theme lib' + id + (fn [prev-token-theme] + (ctob/make-token-theme (merge prev-token-theme attrs))))))))) + +(defmethod process-change :set-tokens-status + [data {:keys [theme-ids set-ids]}] + (-> (cfo/ensure-tokens-lib data) + (cfo/update-tokens-status ctos/set-tokens-status theme-ids set-ids))) (defmethod process-change :set-active-token-themes [data {:keys [theme-paths]}] - (-> (ctf/ensure-tokens-lib data) - (update :tokens-lib ctob/set-active-themes theme-paths))) + (-> (cfo/ensure-tokens-lib data) + (cfo/update-tokens-lib ctob/set-active-themes theme-paths))) (defmethod process-change :rename-token-set-group [data {:keys [set-group-path set-group-fname]}] - (-> (ctf/ensure-tokens-lib data) - (update :tokens-lib ctob/rename-set-group set-group-path set-group-fname))) + (-> (cfo/ensure-tokens-lib data) + (cfo/update-tokens-lib ctob/rename-set-group set-group-path set-group-fname))) (defmethod process-change :move-token-set [data {:keys [from-path to-path before-path before-group] :as changes}] - (-> (ctf/ensure-tokens-lib data) - (update :tokens-lib ctob/move-set from-path to-path before-path before-group))) + (-> (cfo/ensure-tokens-lib data) + (cfo/update-tokens-lib ctob/move-set from-path to-path before-path before-group))) (defmethod process-change :move-token-set-group [data {:keys [from-path to-path before-path before-group]}] - (-> (ctf/ensure-tokens-lib data) - (update :tokens-lib ctob/move-set-group from-path to-path before-path before-group))) + (-> (cfo/ensure-tokens-lib data) + (cfo/update-tokens-lib ctob/move-set-group from-path to-path before-path before-group))) ;; === Design Tokens configuration @@ -1069,12 +1083,11 @@ (defmethod process-change :set-tokens-file [data {:keys [library-id]}] - (assoc data :tokens-file library-id)) - + (cfo/set-tokens-file data library-id)) ;; === Operations -(def decode-shape-attrs +(def decode-shape-attrs (sm/decoder cts/schema:shape-attrs sm/json-transformer)) (defmethod process-operation :assign @@ -1243,3 +1256,22 @@ (defmethod frames-changed :default [_ _] nil) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Design Tokens changes detection +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:private tokens-lib-change-types + "Set of change types that modify the tokens library." + #{:set-tokens-lib + :set-token + :set-token-set + :set-token-theme + :rename-token-set-group + :move-token-set + :move-token-set-group}) + +(defn tokens-lib-changed? + "Check if a commit contains changes that modify the tokens library." + [changes] + (some #(tokens-lib-change-types (:type %)) changes)) diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index c7e8d71288..f579ca10b9 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.common.files.changes :as cfc] [app.common.files.helpers :as cfh] + [app.common.files.tokens :as cfo] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] @@ -22,6 +23,7 @@ [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] [app.common.uuid :as uuid] [clojure.datafy :refer [datafy]])) @@ -1020,6 +1022,21 @@ :attrs (datafy prev-theme)}) (apply-changes-local)))) +(defn set-tokens-status + ([changes tokens-status] + (assert-library! changes) + (assert (ctos/tokens-status? tokens-status)) + (let [theme-ids (ctos/get-active-theme-ids tokens-status) + set-ids (ctos/get-active-set-ids tokens-status) + library-data (::library-data (meta changes)) + prev-tokens-status (cfo/get-tokens-status library-data) + prev-theme-ids (ctos/get-active-theme-ids prev-tokens-status) + prev-set-ids (ctos/get-active-set-ids prev-tokens-status)] + (-> changes + (update :redo-changes conj {:type :set-tokens-status :theme-ids theme-ids :set-ids set-ids}) + (update :undo-changes conj {:type :set-tokens-status :theme-ids prev-theme-ids :set-ids prev-set-ids}) + (apply-changes-local))))) + (defn set-active-token-themes [changes active-theme-paths] (assert-library! changes) diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 147df1d1cc..d7563ce531 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -13,6 +13,7 @@ [app.common.files.comp-processors :as cfcp] [app.common.files.defaults :as cfd] [app.common.files.helpers :as cfh] + [app.common.files.tokens :as cfo] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] @@ -1874,6 +1875,13 @@ (update :pages-index d/update-vals update-container) (d/update-when :components d/update-vals update-container)))) +(defmethod migrate-data "0025-separate-tokens-status" + [data _] + (if-let [tokens-lib (:tokens-lib data)] + (assoc data :tokens-status + (cfo/make-tokens-status-from-lib tokens-lib)) + data)) + (def available-migrations (into (d/ordered-set) ["legacy-2" @@ -1955,4 +1963,5 @@ "0021-fix-shape-svg-attrs" "0022-normalize-component-root-and-resync" "0023-repair-token-themes-with-inexistent-sets" - "0024b-fix-stroke-cap-placement"])) + "0024b-fix-stroke-cap-placement" + "0025-separate-tokens-status"])) diff --git a/common/src/app/common/files/tokens.cljc b/common/src/app/common/files/tokens.cljc index 95ff5da4df..dcf2dd2f38 100644 --- a/common/src/app/common/files/tokens.cljc +++ b/common/src/app/common/files/tokens.cljc @@ -9,13 +9,18 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.i18n :refer [tr]] + [app.common.logging :as log] [app.common.schema :as sm] [app.common.types.token :as cto] [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] [clojure.set :as set] [cuerdas.core :as str] [malli.core :as m])) +;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default +(log/set-level! :warn) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; HIGH LEVEL SCHEMAS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -165,8 +170,8 @@ (some (fn [[token-name _]] (not (ctob/token-name-path-exists? token-name tokens-tree))) new-tokens))))]]) -(defn find-refs [value] - (prn value) + +(defn- find-refs [value] (cond (string? value) (cto/find-token-value-references value) @@ -345,6 +350,8 @@ ;; HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Token + (def parseable-token-value-regexp "Regexp that can be used to parse a number value out of resolved token value. This regexp also trims whitespace around the value." @@ -416,3 +423,128 @@ ;; FIXME: this should be precalculated ? (defn is-reference? [token] (str/includes? (:value token) "{")) + +;; Tokens lib in file data + +(defn make-tokens-status-from-lib + "Make a TokensStatus from a TokensLib, activating the themes and sets + marked as active in the library (to migrate from legacy files)." + [tokens-lib] + (assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib") + (let [active-theme-ids (into #{} + (comp (map :id) + (filter #(not= % ctob/hidden-theme-id))) + (ctob/get-active-themes tokens-lib)) + active-set-ids (into #{} + (comp (map #(ctob/get-set-by-name tokens-lib %)) + (remove nil?) + (map ctob/get-id)) + (ctob/get-active-themes-set-names tokens-lib))] + (ctos/make-tokens-status :active-theme-ids active-theme-ids + :active-set-ids active-set-ids))) + +(defn ensure-tokens-lib + "Ensure file-data has a :tokens-lib or :tokens-file, and also a :tokens-status, creating them if necessary." + [file-data] + (cond-> file-data + (nil? (:tokens-file file-data)) + (update :tokens-lib #(or % (ctob/make-tokens-lib))) + + :always + (update :tokens-status #(or % (ctos/make-tokens-status))))) + +(defn get-tokens-file + [file-data] + (:tokens-file file-data)) + +(defn set-tokens-file + [file-data tokens-file] + (assoc file-data :tokens-file tokens-file)) + +(defn get-tokens-lib + [file-data] + (:tokens-lib file-data)) + +(defn get-tokens-status + [file-data] + (:tokens-status file-data)) + +(defn update-tokens-lib + "Update the tokens-lib inside file-data through a callback function. + The function will receive the tokens lib and the rest of args." + [file-data f & args] + (d/update-when file-data :tokens-lib #(apply f % args))) + +(defn update-tokens-status + "Update the tokens-status inside file-data through a callback function. + The function will receive the tokens status and the rest of args." + [file-data f & args] + (d/update-when file-data :tokens-status #(apply f % args))) + +;; Tokens status with tokens lib + +(defn- calculate-active-sets + "Obtain the set-ids that needs to be active for a particular set of theme ids" + [active-theme-ids tokens-lib] + (let [active-themes (map #(ctob/get-theme tokens-lib %) active-theme-ids) + active-set-names (reduce set/union #{} (map :sets active-themes)) + active-sets (map #(ctob/get-set-by-name tokens-lib %) active-set-names) + active-set-ids (into #{} (map ctob/get-id) active-sets)] + active-set-ids)) + +(defn get-active-themes + "Return an ordered sequence of active themes" + [tokens-status tokens-lib] + (->> (ctob/get-themes tokens-lib) + (filter #(ctos/theme-active? tokens-status (ctob/get-id %))))) + +(defn activate-theme + "Activate a theme and all its sets" + [tokens-status tokens-lib id] + (assert (ctos/tokens-status? tokens-status) "expected valid tokens-status") + (assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib") + (assert (uuid? id) "expected valid theme id") + (if-not (ctos/theme-active? tokens-status id) + (if-let [theme (ctob/get-theme tokens-lib id)] + (let [group-themes (into #{} (ctob/get-themes-in-group tokens-lib (:group theme))) + active-theme-ids (ctos/get-active-theme-ids tokens-status) + active-theme-ids' (-> (set/difference active-theme-ids group-themes) + (conj id)) + active-set-ids' (calculate-active-sets active-theme-ids' tokens-lib)] + (ctos/set-tokens-status tokens-status active-theme-ids' active-set-ids')) + tokens-status) + tokens-status)) + +(defn deactivate-theme + "Deactivate a theme and all its sets that are not in other active themes" + [tokens-status tokens-lib id] + (assert (ctos/tokens-status? tokens-status) "expected valid tokens-status") + (assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib") + (assert (uuid? id) "expected valid theme id") + (if (ctos/theme-active? tokens-status id) + (let [active-theme-ids' (disj (ctos/get-active-theme-ids tokens-status) id) + active-set-ids' (calculate-active-sets active-theme-ids' tokens-lib)] + (ctos/set-tokens-status tokens-status active-theme-ids' active-set-ids')) + tokens-status)) + +(defn sync-tokens-status-with-lib + "Synchronizes tokens status with the current tokens lib: + - Delete any theme or set that no longer exists in the lib." + [tokens-status tokens-lib] + (let [active-theme-ids (ctos/get-active-theme-ids tokens-status) + valid-theme-ids (into #{} + (filter #(some? (ctob/get-theme tokens-lib %))) + active-theme-ids) + active-set-ids (ctos/get-active-set-ids tokens-status) + valid-set-ids (into #{} + (filter #(some? (ctob/get-set tokens-lib %))) + active-set-ids)] + + (if (or (not= active-theme-ids valid-theme-ids) + (not= active-set-ids valid-set-ids)) + (do + (log/info :hint "syncing token status" + :removed-themes (count (set/difference active-theme-ids valid-theme-ids)) + :removed-sets (count (set/difference active-set-ids valid-set-ids))) + (ctos/set-tokens-status tokens-status valid-theme-ids valid-set-ids)) + tokens-status))) \ No newline at end of file diff --git a/common/src/app/common/logic/tokens.cljc b/common/src/app/common/logic/tokens.cljc index ffec485d54..395cfe330e 100644 --- a/common/src/app/common/logic/tokens.cljc +++ b/common/src/app/common/logic/tokens.cljc @@ -7,7 +7,9 @@ (ns app.common.logic.tokens (:require [app.common.files.changes-builder :as pcb] - [app.common.types.tokens-lib :as ctob])) + [app.common.files.tokens :as cfo] + [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos])) (defn- generate-update-active-sets "Copy the active sets from the currently active themes and move them @@ -40,32 +42,45 @@ [changes tokens-lib set-name] (generate-update-active-sets changes tokens-lib #(ctob/toggle-set % set-name))) -(defn- generate-update-active-token-theme - "Change the active state of a theme in `tokens-lib`. If after the change there is - any active theme other than the hidden one, deactivate the hidden theme." - [changes tokens-lib update-fn] - (let [active-token-themes (some-> tokens-lib - (update-fn) - (ctob/get-active-theme-paths)) - active-token-themes' (if (= active-token-themes #{ctob/hidden-theme-path}) - active-token-themes - (disj active-token-themes ctob/hidden-theme-path))] - (pcb/set-active-token-themes changes active-token-themes'))) -(defn generate-set-active-token-theme - "Activate or deactivate a token theme in `tokens-lib`." - [changes tokens-lib id active?] +;; ================== nuevo + +(defn generate-activate-theme + [changes tokens-status tokens-lib id] + (let [tokens-status' (cfo/activate-theme tokens-status tokens-lib id)] + (if (not= tokens-status tokens-status') + (pcb/set-tokens-status changes tokens-status') + changes))) + +(defn generate-deactivate-theme + [changes tokens-status tokens-lib id] + (let [tokens-status' (cfo/deactivate-theme tokens-status tokens-lib id)] + (if (not= tokens-status tokens-status') + (pcb/set-tokens-status changes tokens-status') + changes))) + +(defn generate-set-theme-status + [changes tokens-status tokens-lib id active?] (if active? - (generate-update-active-token-theme changes tokens-lib - #(ctob/activate-theme % id)) - (generate-update-active-token-theme changes tokens-lib - #(ctob/deactivate-theme % id)))) + (generate-activate-theme changes tokens-status tokens-lib id) + (generate-deactivate-theme changes tokens-status tokens-lib id))) -(defn generate-toggle-token-theme - "Toggle the active state of a token theme in `tokens-lib`." - [changes tokens-lib id] - (generate-update-active-token-theme changes tokens-lib - #(ctob/toggle-theme-active % id))) +(defn generate-toggle-theme + [changes tokens-status tokens-lib id] + (let [active? (ctos/theme-active? tokens-status id)] + (if active? + (generate-deactivate-theme changes tokens-status tokens-lib id) + (generate-activate-theme changes tokens-status tokens-lib id)))) + +(defn generate-sync-tokens-status-with-lib + "Synchronizes tokens status with the current tokens lib" + [changes tokens-status tokens-lib] + (let [tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)] + (if (not= tokens-status tokens-status') + (pcb/set-tokens-status changes tokens-status') + changes))) + +;; ======= fin nuevo (defn toggle-token-set-group "Toggle a token set group at `group-path` in `tokens-lib` for a `tokens-lib-theme`." @@ -178,4 +193,4 @@ (reduce (fn [changes set] (pcb/set-token-set changes (ctob/get-id set) nil)) changes - sets))) \ No newline at end of file + sets))) diff --git a/common/src/app/common/test_helpers/tokens.cljc b/common/src/app/common/test_helpers/tokens.cljc index 07301b5a01..11d19c3011 100644 --- a/common/src/app/common/test_helpers/tokens.cljc +++ b/common/src/app/common/test_helpers/tokens.cljc @@ -7,6 +7,7 @@ (ns app.common.test-helpers.tokens (:require [app.common.data :as d] + [app.common.files.tokens :as cfo] [app.common.test-helpers.files :as thf] [app.common.test-helpers.shapes :as ths] [app.common.types.container :as ctn] @@ -19,15 +20,32 @@ (defn get-tokens-lib [file] - (:tokens-lib (ctf/file-data file))) + (-> file (ctf/file-data) (cfo/get-tokens-lib))) + +(defn get-tokens-status + [file] + (-> file (ctf/file-data) (cfo/get-tokens-status))) (defn add-tokens-lib + "Ensure the file has a tokens-lib and a tokens-status in its data, creating empty ones if not" [file] - (ctf/update-file-data file ctf/ensure-tokens-lib)) + (ctf/update-file-data file cfo/ensure-tokens-lib)) (defn update-tokens-lib + "Modify the tokens-lib of a file " [file f] - (ctf/update-file-data file #(update % :tokens-lib f))) + (ctf/update-file-data file #(cfo/update-tokens-lib % f))) + +(defn update-tokens-status + [file f] + (ctf/update-file-data file #(cfo/update-tokens-status % f))) + +(defn sample-file-with-tokens + [& {:keys [lib-fn status-fn file-id] :or {lib-fn identity status-fn identity file-id :file1}}] + (-> (thf/sample-file file-id) + (add-tokens-lib) + (update-tokens-lib lib-fn) + (update-tokens-status status-fn))) (defn get-token [file set-id token-id] diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 08f86bdb5b..186c74181a 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -29,6 +29,7 @@ [app.common.types.shape-tree :as ctst] [app.common.types.text :as txt] [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] [app.common.types.typographies-list :as ctyl] [app.common.types.typography :as cty] [app.common.uuid :as uuid] @@ -86,7 +87,9 @@ [:components {:optional true} schema:components] [:typographies {:optional true} schema:typographies] [:plugin-data {:optional true} schema:plugin-data] - [:tokens-lib {:optional true} ctob/schema:tokens-lib]]) + [:tokens-file {:optional true} ::sm/uuid] ;; The tokens-lib may be in this file or in an external library + [:tokens-lib {:optional true} ctob/schema:tokens-lib] + [:tokens-status {:optional true} ctos/schema:tokens-status]]) (def schema:file "A schema for validate a file data structure; data is optional @@ -196,11 +199,6 @@ (check-file file))) -(defn ensure-tokens-lib - "Ensure file-data has a :tokens-lib key, creating one if necessary." - [file-data] - (update file-data :tokens-lib #(or % (ctob/make-tokens-lib)))) - ;; Helpers (defn file-data @@ -311,6 +309,7 @@ (update-objects-tree container f))))) ;; Asset helpers + (defn find-component-file [file libraries component-file] (if (and (some? file) (= component-file (:id file))) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 9b5beaf72a..ffc816d40e 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -755,11 +755,15 @@ (def ^:private theme-separator "/") -(defn- join-theme-path [group name] - (cpn/join-path [group name] :separator theme-separator :with-spaces? false)) +(defn- join-theme-path [group name with-spaces?] + (let [path (if (and (str/empty? group) with-spaces?) [name] [group name])] + (cpn/join-path path :separator theme-separator :with-spaces? with-spaces?))) -(defn get-theme-path [theme] - (join-theme-path (:group theme) (:name theme))) +(defn get-theme-path + ([theme] + (get-theme-path theme false)) + ([theme with-spaces?] + (join-theme-path (:group theme) (:name theme) with-spaces?))) (defn split-theme-path [path] (cpn/split-group-name path @@ -767,7 +771,7 @@ :with-spaces? false)) (def hidden-theme-path - (join-theme-path hidden-theme-group hidden-theme-name)) + (join-theme-path hidden-theme-group hidden-theme-name false)) ;; === TokenThemes (collection) @@ -778,7 +782,9 @@ (delete-theme [_ id] "delete a theme in the library") (theme-count [_] "get the total number if themes in the library") (get-theme-tree [_] "get a nested tree of all themes in the library") + (get-theme-tree-no-hidden [_] "get a nested tree of all themes in the library except the hidden theme") (get-themes [_] "get an ordered sequence of all themes in the library") + (get-themes-in-group [_ group] "get an ordered sequence of the themes in the group") (get-theme [_ id] "get one theme looking for id") (get-theme-by-name [_ group name] "get one theme looking for group and name") (get-theme-groups [_] "get a sequence of group names by order") @@ -1177,7 +1183,7 @@ Will return a value that matches this schema: (d/dissoc-in [group name]))) (if same-path? active-themes - (disj active-themes (join-theme-path group name))))))) + (disj active-themes (join-theme-path group name false))))))) this)) (delete-theme [this id] @@ -1186,12 +1192,15 @@ Will return a value that matches this schema: (if theme (TokensLib. sets (d/dissoc-in themes [group name]) - (disj active-themes (join-theme-path group name))) + (disj active-themes (join-theme-path group name false))) this))) (get-theme-tree [_] themes) + (get-theme-tree-no-hidden [_] + (d/dissoc-in themes [hidden-theme-group hidden-theme-name])) + (get-theme-groups [_] (into [] (comp (map key) @@ -1202,6 +1211,10 @@ Will return a value that matches this schema: (->> (tree-seq d/ordered-map? vals themes) (filter (partial instance? TokenTheme)))) + (get-themes-in-group [_ group] + (->> (get themes group) + (map (comp get-id val)))) + (theme-count [this] (count (get-themes this))) diff --git a/common/src/app/common/types/tokens_status.cljc b/common/src/app/common/types/tokens_status.cljc new file mode 100644 index 0000000000..c2953cc4c1 --- /dev/null +++ b/common/src/app/common/types/tokens_status.cljc @@ -0,0 +1,147 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns app.common.types.tokens-status + (:require + #?(:clj [app.common.fressian :as fres]) + #?(:clj [clojure.data.json :as c.json]) + [app.common.schema :as sm] + [app.common.schema.generators :as sg] + [app.common.transit :as t] + [clojure.core.protocols :as cp] + [clojure.datafy :refer [datafy]] + [clojure.pprint :as pp])) + +;; TokensStatus datatype contains the activation status of the themes and sets +;; in a tokens library. + +(defprotocol ITokensStatus + (get-active-theme-ids [_] "Return a clojure set of active theme ids") + (get-active-set-ids [_] "Return a clojure set of active set ids") + (theme-active? [_ theme-id] "Check if a theme is active") + (set-active? [_ set-id] "Check if a set is active") + (set-tokens-status [_ theme-ids set-ids] "Set the activation status of the themes and sets")) + +(deftype TokensStatus [active-theme-ids active-set-ids] + cp/Datafiable + (datafy [_] + {:active-theme-ids active-theme-ids + :active-set-ids active-set-ids}) + + #?@(:clj + [c.json/JSONWriter + (-write [this writter options] + (c.json/-write (datafy this) writter options))]) + + ITokensStatus + (get-active-theme-ids [_] + active-theme-ids) + + (get-active-set-ids [_] + active-set-ids) + + (theme-active? [_ theme-id] + (assert (uuid? theme-id)) + (contains? active-theme-ids theme-id)) + + (set-active? [_ set-id] + (assert (uuid? set-id)) + (contains? active-set-ids set-id)) + + (set-tokens-status [_ theme-ids set-ids] + (assert (set? theme-ids)) + (assert (set? set-ids)) + (TokensStatus. theme-ids set-ids))) + +;; === Helper & Predicate === + +(defn map->TokensStatus + [{:keys [active-theme-ids active-set-ids]}] + (TokensStatus. active-theme-ids active-set-ids)) + +(defn tokens-status? + [o] + (instance? TokensStatus o)) + +;; === Schemas, Check functions & Constructor === + +(declare make-tokens-status) + +(def schema:tokens-status-attrs + [:map {:title "TokensStatus"} + [:active-theme-ids {:optional true} [:set {:gen/max 5} ::sm/uuid]] + [:active-set-ids {:optional true} [:set {:gen/max 5} ::sm/uuid]]]) + +(def schema:tokens-status + [:and {:gen/gen (->> (sg/generator schema:tokens-status-attrs) + (sg/fmap #(make-tokens-status %)))} + [:fn tokens-status?]]) + +(def ^:private check-tokens-status-attrs + (sm/check-fn schema:tokens-status-attrs + :hint "expected valid params for tokens-status")) + +(def check-tokens-status + (sm/check-fn schema:tokens-status + :hint "expected valid tokens-status")) + +(defn make-tokens-status + [& {:as attrs}] + (-> attrs + (update :active-theme-ids #(or % #{})) + (update :active-set-ids #(or % #{})) + (check-tokens-status-attrs) + (map->TokensStatus))) + +;; === Pretty-print for debugging === + +(defmethod pp/simple-dispatch TokensStatus [^TokensStatus obj] + (.write *out* "#penpot/tokens-status ") + (pp/pprint-newline :miser) + (pp/pprint (datafy obj))) + +#?(:clj + (do + (defmethod print-method TokensStatus + [^TokensStatus this ^java.io.Writer w] + (.write w "#penpot/tokens-status ") + (print-method (datafy this) w)) + + (defmethod print-dup TokensStatus + [^TokensStatus this ^java.io.Writer w] + (print-method this w))) + + :cljs + (extend-type TokensStatus + cljs.core/IPrintWithWriter + (-pr-writer [this writer opts] + (-write writer "#penpot/tokens-status ") + (-pr-writer (datafy this) writer opts)) + + cljs.core/IEncodeJS + (-clj->js [this] + (clj->js (datafy this))))) + +;; === Transit serialization === + +(t/add-handlers! + {:id "penpot/tokens-status" + :class TokensStatus + :wfn datafy + :rfn #(make-tokens-status %)}) + +;; === Fressian serialization === + +#?(:clj + (fres/add-handlers! + {:name "penpot/tokens-status/v1" + :class TokensStatus + :wfn (fn [n w o] + (fres/write-tag! w n 1) + (fres/write-object! w (datafy o))) + :rfn (fn [r] + (let [obj (fres/read-object! r)] + (make-tokens-status obj)))})) diff --git a/common/test/common_tests/files/tokens_test.cljc b/common/test/common_tests/files/tokens_test.cljc index 63f083ce5e..eb41784f97 100644 --- a/common/test/common_tests/files/tokens_test.cljc +++ b/common/test/common_tests/files/tokens_test.cljc @@ -7,6 +7,12 @@ (ns common-tests.files.tokens-test (:require [app.common.files.tokens :as cfo] + [app.common.test-helpers.files :as thf] + [app.common.test-helpers.ids-map :as thi] + [app.common.test-helpers.tokens :as tht] + [app.common.types.file :as ctf] + [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] [clojure.test :as t])) (t/deftest test-parse-token-value @@ -92,3 +98,343 @@ (t/is (nil? (cfo/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}} {:applied-tokens {:x "a"}}] #{:y}))))) + +;; Tokens lib + +(t/deftest test-ensure-tokens-lib + (t/testing "ensure-tokens-lib should add a tokens-lib and tokens-status to the file data if they are missing, and should not modify them if they already exist" + (let [file (thf/sample-file :file1) + file-data (ctf/file-data file) + file-data' (cfo/ensure-tokens-lib file-data)] + (t/is (contains? file-data' :tokens-lib)) + (t/is (ctob/tokens-lib? (:tokens-lib file-data'))) + (t/is (contains? file-data' :tokens-status)) + (t/is (ctos/tokens-status? (:tokens-status file-data')))))) + +(t/deftest test-update-tokens-lib + (t/testing "update when there is no tokens-lib has no effect" + (let [file (thf/sample-file :file1) + file' (cfo/update-tokens-lib file #(t/is false "This should not be called"))] + (t/is (= file file')))) + + (t/testing "update a tokens-lib applies the changes correctly" + (let [file (-> (thf/sample-file :file1) + (tht/add-tokens-lib)) + file' (ctf/update-file-data file + #(cfo/update-tokens-lib + % + ctob/add-theme + (ctob/make-token-theme :id (thi/new-id! :theme1) + :name "theme 1"))) + + tokens-lib' (tht/get-tokens-lib file')] + (t/is (= 2 (ctob/theme-count tokens-lib'))) ;; Count the hidden theme + (t/is (ctob/token-theme? (ctob/get-theme tokens-lib' (thi/id :theme1))))))) + +(t/deftest test-update-tokens-status + (t/testing "update when there is no tokens-status has no effect" + (let [file (thf/sample-file :file1) + file' (cfo/update-tokens-status file #(t/is false "This should not be called"))] + (t/is (= file file')))) + + (t/testing "update a tokens-status applies the changes correctly" + (let [file (-> (thf/sample-file :file1) + (tht/add-tokens-lib) + (tht/update-tokens-lib + (fn [tokens-lib] + (-> tokens-lib + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme1) :name "theme")))))) + file' (ctf/update-file-data file + #(cfo/update-tokens-status + % + ctos/set-tokens-status + #{(thi/id :theme1)} + #{})) + tokens-status' (tht/get-tokens-status file')] + (t/is (= 1 (count (ctos/get-active-theme-ids tokens-status')))) + (t/is (ctos/theme-active? tokens-status' (thi/id :theme1))) + (t/is (= 0 (count (ctos/get-active-set-ids tokens-status'))))))) + +;; Tokens status with tokens lib + +;; This is a private function but it deserves specific tests for all use cases +(def calculate-active-sets #'app.common.files.tokens/calculate-active-sets) + +(t/deftest test-calculate-active-sets + (t/testing "returns union of sets from all active themes" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-c) :name "set-c")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{"set-a" "set-b"})) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2) + :name "theme-2" + :group "" + :sets #{"set-c"})))] + (t/is (= #{(thi/id :set-a) (thi/id :set-b) (thi/id :set-c)} + (calculate-active-sets #{(thi/id :theme-1) (thi/id :theme-2)} tokens-lib))))) + + (t/testing "deduplicates sets shared by multiple active themes" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{"set-a" "set-b"})) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2) + :name "theme-2" + :group "" + :sets #{"set-b"})))] + (t/is (= #{(thi/id :set-a) (thi/id :set-b)} + (calculate-active-sets #{(thi/id :theme-1) (thi/id :theme-2)} tokens-lib))))) + + (t/testing "returns empty set when active-theme-ids is empty" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{"set-a"})))] + (t/is (= #{} + (calculate-active-sets #{} tokens-lib))))) + + (t/testing "ignores active themes that do not exist in the lib" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{"set-a"})))] + (t/is (= #{(thi/id :set-a)} + (calculate-active-sets #{(thi/id :theme-1) (thi/new-id! :nonexistent)} tokens-lib))))) + + (t/testing "ignores set names that do not exist in the lib" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{"set-a" "nonexistent-set"})))] + (t/is (contains? (calculate-active-sets #{(thi/id :theme-1)} tokens-lib) (thi/id :set-a))) + (t/is (= 2 (count (calculate-active-sets #{(thi/id :theme-1)} tokens-lib)))))) + + (t/testing "returns empty set when themes have no sets" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{})))] + (t/is (= #{} + (calculate-active-sets #{(thi/id :theme-1)} tokens-lib))))) + + (t/testing "returns empty set when there are no themes" + (let [tokens-lib (-> (ctob/make-tokens-lib))] + (t/is (= #{} + (calculate-active-sets #{} tokens-lib)))))) + +(t/deftest test-get-active-themes + (t/testing "returns only themes that are active in the status" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) :name "theme-1" :group "")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2) :name "theme-2" :group "")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-3) :name "theme-3" :group ""))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1) (thi/id :theme-3)} + :active-set-ids #{})] + (t/is (= 2 (count (cfo/get-active-themes tokens-status tokens-lib)))) + (let [active-ids (into #{} (map ctob/get-id) (cfo/get-active-themes tokens-status tokens-lib))] + (t/is (contains? active-ids (thi/id :theme-1))) + (t/is (contains? active-ids (thi/id :theme-3))) + (t/is (not (contains? active-ids (thi/id :theme-2))))))) + + (t/testing "returns empty sequence when no themes are active" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) :name "theme-1" :group ""))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{} + :active-set-ids #{})] + (t/is (empty? (cfo/get-active-themes tokens-status tokens-lib))))) + + (t/testing "returns empty sequence when tokens-lib has no themes" + (let [tokens-lib (ctob/make-tokens-lib) + tokens-status (ctos/make-tokens-status)] + (t/is (empty? (cfo/get-active-themes tokens-status tokens-lib))))) + + (t/testing "active theme ids in status that do not exist in lib are silently ignored" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) :name "theme-1" :group ""))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1) (thi/new-id! :nonexistent)} + :active-set-ids #{})] + (t/is (= 1 (count (cfo/get-active-themes tokens-status tokens-lib))))))) + +(t/deftest test-activate-theme + (t/testing "activating a theme sets it as active" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{"set-a"}))) + tokens-status (ctos/make-tokens-status) + tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/id :theme-1))] + (t/is (ctos/theme-active? tokens-status' (thi/id :theme-1))))) + + (t/testing "activating a theme deactivates other themes in the same group" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "colors" + :sets #{"set-a"})) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2) + :name "theme-2" + :group "colors" + :sets #{"set-b"}))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1)} + :active-set-ids #{(thi/id :set-a)}) + tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/id :theme-2))] + (t/is (ctos/theme-active? tokens-status' (thi/id :theme-2))) + (t/is (not (ctos/theme-active? tokens-status' (thi/id :theme-1)))))) + + (t/testing "themes in different groups can be active simultaneously" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "colors" + :sets #{"set-a"})) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2) + :name "theme-2" + :group "spacing" + :sets #{"set-b"}))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1)} + :active-set-ids #{(thi/id :set-a)}) + tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/id :theme-2))] + (t/is (ctos/theme-active? tokens-status' (thi/id :theme-1))) + (t/is (ctos/theme-active? tokens-status' (thi/id :theme-2))))) + + (t/testing "activating an already-active theme returns status unchanged" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{"set-a"}))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1)} + :active-set-ids #{(thi/id :set-a)}) + tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/id :theme-1))] + (t/is (identical? tokens-status tokens-status')))) + + (t/testing "activating a non-existent theme returns status unchanged" + (let [tokens-lib (ctob/make-tokens-lib) + tokens-status (ctos/make-tokens-status) + tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/new-id! :nonexistent))] + (t/is (identical? tokens-status tokens-status'))))) + +(t/deftest test-deactivate-theme + (t/testing "deactivating an active theme removes it from active themes" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{"set-a"}))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1)} + :active-set-ids #{(thi/id :set-a)}) + tokens-status' (cfo/deactivate-theme tokens-status tokens-lib (thi/id :theme-1))] + (t/is (not (ctos/theme-active? tokens-status' (thi/id :theme-1)))))) + + (t/testing "deactivating a theme removes sets not used by remaining active themes" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group "" + :sets #{"set-a"})) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2) + :name "theme-2" + :group "" + :sets #{"set-b"}))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1) (thi/id :theme-2)} + :active-set-ids #{(thi/id :set-a) (thi/id :set-b)}) + tokens-status' (cfo/deactivate-theme tokens-status tokens-lib (thi/id :theme-1))] + (t/is (not (ctos/theme-active? tokens-status' (thi/id :theme-1)))) + (t/is (ctos/theme-active? tokens-status' (thi/id :theme-2))))) + + (t/testing "deactivating an already-inactive theme returns status unchanged" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :group ""))) + tokens-status (ctos/make-tokens-status) + tokens-status' (cfo/deactivate-theme tokens-status tokens-lib (thi/id :theme-1))] + (t/is (identical? tokens-status tokens-status')))) + + (t/testing "deactivating a non-existent theme returns status unchanged" + (let [tokens-lib (ctob/make-tokens-lib) + tokens-status (ctos/make-tokens-status) + tokens-status' (cfo/deactivate-theme tokens-status tokens-lib (thi/new-id! :nonexistent))] + (t/is (identical? tokens-status tokens-status'))))) + +(t/deftest test-sync-tokens-status-with-lib + (t/testing "removes theme ids that no longer exist in the lib" + (let [theme-1-id (thi/new-id! :theme-1) + set-a-id (thi/new-id! :set-a) + tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id set-a-id :name "set-a")) + (ctob/add-theme (ctob/make-token-theme :id theme-1-id + :name "theme-1" + :group "" + :sets #{"set-a"}))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{theme-1-id (thi/new-id! :removed-theme)} + :active-set-ids #{set-a-id}) + tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)] + (t/is (= #{theme-1-id} (ctos/get-active-theme-ids tokens-status'))) + (t/is (ctos/theme-active? tokens-status' theme-1-id)))) + + (t/testing "removes set ids that no longer exist in the lib" + (let [theme-1-id (thi/new-id! :theme-1) + set-a-id (thi/new-id! :set-a) + tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id set-a-id :name "set-a")) + (ctob/add-theme (ctob/make-token-theme :id theme-1-id + :name "theme-1" + :group "" + :sets #{"set-a"}))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{theme-1-id} + :active-set-ids #{set-a-id (thi/new-id! :removed-set)}) + tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)] + (t/is (= #{set-a-id} (ctos/get-active-set-ids tokens-status'))))) + + (t/testing "returns same status object when everything is valid" + (let [theme-1-id (thi/new-id! :theme-1) + set-a-id (thi/new-id! :set-a) + tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id set-a-id :name "set-a")) + (ctob/add-theme (ctob/make-token-theme :id theme-1-id + :name "theme-1" + :group "" + :sets #{"set-a"}))) + tokens-status (ctos/make-tokens-status :active-theme-ids #{theme-1-id} + :active-set-ids #{set-a-id}) + tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)] + (t/is (identical? tokens-status tokens-status')))) + + (t/testing "returns same status object when both theme and set ids are empty" + (let [tokens-lib (ctob/make-tokens-lib) + tokens-status (ctos/make-tokens-status) + tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)] + (t/is (identical? tokens-status tokens-status')))) + + (t/testing "removes all themes and sets when lib is empty" + (let [tokens-lib (ctob/make-tokens-lib) + tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/new-id! :old-theme)} + :active-set-ids #{(thi/new-id! :old-set)}) + tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)] + (t/is (= #{} (ctos/get-active-theme-ids tokens-status'))) + (t/is (= #{} (ctos/get-active-set-ids tokens-status')))))) diff --git a/common/test/common_tests/logic/token_test.cljc b/common/test/common_tests/logic/token_test.cljc index 6d54874ae4..a8c28ebb83 100644 --- a/common/test/common_tests/logic/token_test.cljc +++ b/common/test/common_tests/logic/token_test.cljc @@ -12,24 +12,20 @@ [app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.tokens :as tht] [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] [app.common.uuid :as uuid] - [clojure.datafy :refer [datafy]] [clojure.test :as t])) (t/use-fixtures :each thi/test-fixture) -(defn- setup-file [lib-fn] - (-> (thf/sample-file :file1) - (tht/add-tokens-lib) - (tht/update-tokens-lib lib-fn))) - (t/deftest generate-toggle-token-set-test (t/testing "toggling an active set will switch to hidden theme without user sets" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/bar")) - (ctob/add-theme (ctob/make-token-theme :name "theme" - :sets #{"foo/bar"})) - (ctob/set-active-themes #{"/theme"}))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar")) + (ctob/add-theme (ctob/make-token-theme :name "theme" + :sets #{"foo/bar"})) + (ctob/set-active-themes #{"/theme"}))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (clt/generate-toggle-token-set (tht/get-tokens-lib file) "foo/bar")) @@ -45,11 +41,12 @@ (t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib))))) (t/testing "toggling an inactive set will switch to hidden theme without user sets" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/bar")) - (ctob/add-theme (ctob/make-token-theme :name "theme" - :sets #{"foo/bar"})) - (ctob/set-active-themes #{"/theme"}))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar")) + (ctob/add-theme (ctob/make-token-theme :name "theme" + :sets #{"foo/bar"})) + (ctob/set-active-themes #{"/theme"}))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (clt/generate-toggle-token-set (tht/get-tokens-lib file) "foo/bar")) @@ -65,10 +62,11 @@ (t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib))))) (t/testing "toggling an set with hidden theme already active will toggle set in hidden theme" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/bar")) - (ctob/add-theme (ctob/make-hidden-theme)) - (ctob/set-active-themes #{ctob/hidden-theme-path}))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar")) + (ctob/add-theme (ctob/make-hidden-theme)) + (ctob/set-active-themes #{ctob/hidden-theme-path}))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) @@ -85,10 +83,11 @@ (t/deftest set-token-theme-test (t/testing "delete token theme" (let [theme-id (uuid/next) - file (setup-file #(-> % - (ctob/add-theme (ctob/make-token-theme :id theme-id - :name "foo" - :group "main")))) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "foo" + :group "main")))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (pcb/set-token-theme theme-id nil)) @@ -107,7 +106,7 @@ theme (ctob/make-token-theme :id theme-id :name "foo" :group "main") - file (setup-file identity) + file (tht/sample-file-with-tokens) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (pcb/set-token-theme theme-id theme)) @@ -126,7 +125,8 @@ prev-theme (ctob/make-token-theme :id theme-id :name prev-theme-name :group "main") - file (setup-file #(ctob/add-theme % prev-theme)) + file (tht/sample-file-with-tokens + :lib-fn #(ctob/add-theme % prev-theme)) new-theme-name "foo1" changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) @@ -149,9 +149,10 @@ :group "main") set-name "bar-set" token-set (ctob/make-token-set :name set-name) - file (setup-file #(-> % - (ctob/add-theme theme) - (ctob/add-set token-set))) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-theme theme) + (ctob/add-set token-set))) theme' (assoc theme :sets #{set-name}) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) @@ -169,13 +170,14 @@ (let [set-name "foo" set-id (uuid/next) token-id (uuid/next) - file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :id set-id - :name set-name)) - (ctob/add-token set-id (ctob/make-token {:name "to.delete.color.red" - :id token-id - :value "red" - :type :color})))) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id set-id + :name set-name)) + (ctob/add-token set-id (ctob/make-token {:name "to.delete.color.red" + :id token-id + :value "red" + :type :color})))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (pcb/set-token set-id token-id nil)) @@ -194,8 +196,9 @@ token (ctob/make-token {:name "to.add.color.red" :value "red" :type :color}) - file (setup-file #(-> % (ctob/add-set (ctob/make-token-set :id set-id - :name set-name)))) + file (tht/sample-file-with-tokens + :lib-fn #(-> % (ctob/add-set (ctob/make-token-set :id set-id + :name set-name)))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (pcb/set-token set-id (:id token) token)) @@ -217,10 +220,11 @@ token (-> prev-token (assoc :name "color.red.changed") (assoc :value "blue")) - file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :id set-id - :name set-name)) - (ctob/add-token set-id prev-token))) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id set-id + :name set-name)) + (ctob/add-token set-id prev-token))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (pcb/set-token set-id (:id prev-token) token)) @@ -237,7 +241,8 @@ (t/testing "delete token set" (let [set-name "foo" set-id (uuid/next) - file (setup-file #(ctob/add-set % (ctob/make-token-set :id set-id :name set-name))) + file (tht/sample-file-with-tokens + :lib-fn #(ctob/add-set % (ctob/make-token-set :id set-id :name set-name))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (pcb/set-token-set set-id nil)) @@ -254,7 +259,7 @@ (let [set-name "foo" set-id (uuid/next) token-set (ctob/make-token-set :id set-id :name set-name) - file (setup-file identity) + file (tht/sample-file-with-tokens) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (pcb/set-token-set set-id token-set)) @@ -271,7 +276,8 @@ (let [set-name "foo" set-id (uuid/next) token-set (ctob/make-token-set :id set-id :name set-name) - file (setup-file #(-> (ctob/add-set % token-set))) + file (tht/sample-file-with-tokens + :lib-fn #(-> (ctob/add-set % token-set))) new-set-name "foo1" changes (-> (pcb/empty-changes) @@ -292,12 +298,13 @@ (t/deftest generate-toggle-token-set-group-test (t/testing "toggling set group with no active sets inside will activate all child sets" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/bar")) - (ctob/add-set (ctob/make-token-set :name "foo/bar/baz")) - (ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child")) - (ctob/add-theme (ctob/make-token-theme :name "theme")) - (ctob/set-active-themes #{"/theme"}))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar")) + (ctob/add-set (ctob/make-token-set :name "foo/bar/baz")) + (ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child")) + (ctob/add-theme (ctob/make-token-theme :name "theme")) + (ctob/set-active-themes #{"/theme"}))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) (clt/generate-toggle-token-set-group (tht/get-tokens-lib file) ["foo" "bar"])) @@ -313,13 +320,14 @@ (t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib))))) (t/testing "toggling set group with partially active sets inside will deactivate all child sets" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/bar")) - (ctob/add-set (ctob/make-token-set :name "foo/bar/baz")) - (ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child")) - (ctob/add-theme (ctob/make-token-theme :name "theme" - :sets #{"foo/bar/baz"})) - (ctob/set-active-themes #{"/theme"}))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar")) + (ctob/add-set (ctob/make-token-set :name "foo/bar/baz")) + (ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child")) + (ctob/add-theme (ctob/make-token-theme :name "theme" + :sets #{"foo/bar/baz"})) + (ctob/set-active-themes #{"/theme"}))) changes (-> (pcb/empty-changes) (pcb/with-library-data (:data file)) @@ -337,9 +345,10 @@ (t/deftest generate-move-token-set-test (t/testing "Ignore dropping set to the same position:" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo")) - (ctob/add-set (ctob/make-token-set :name "bar/baz")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "bar/baz")))) drop (partial clt/generate-move-token-set (pcb/empty-changes) (tht/get-tokens-lib file))] (t/testing "on top of identical" (t/is (= (pcb/empty-changes) @@ -359,10 +368,11 @@ (t/testing "Reorder sets when dropping next to a set:" (t/testing "at top" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo")) - (ctob/add-set (ctob/make-token-set :name "bar")) - (ctob/add-set (ctob/make-token-set :name "baz")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "bar")) + (ctob/add-set (ctob/make-token-set :name "baz")))) lib (tht/get-tokens-lib file) changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 1 :to-index 0 @@ -378,10 +388,11 @@ (t/is (= (ctob/get-set-names lib) undo-sets))))) (t/testing "at bottom" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo")) - (ctob/add-set (ctob/make-token-set :name "bar")) - (ctob/add-set (ctob/make-token-set :name "baz")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "bar")) + (ctob/add-set (ctob/make-token-set :name "baz")))) lib (tht/get-tokens-lib file) changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 0 :to-index 2 @@ -397,9 +408,10 @@ (t/is (= (ctob/get-set-names lib) undo-sets))))) (t/testing "dropping out of set group" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/bar")) - (ctob/add-set (ctob/make-token-set :name "foo")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar")) + (ctob/add-set (ctob/make-token-set :name "foo")))) lib (tht/get-tokens-lib file) changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 1 :to-index 0 @@ -415,9 +427,10 @@ (t/is (= (ctob/get-set-names lib) undo-sets))))) (t/testing "into set group" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/bar")) - (ctob/add-set (ctob/make-token-set :name "foo")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar")) + (ctob/add-set (ctob/make-token-set :name "foo")))) lib (tht/get-tokens-lib file) changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 2 :to-index 1 @@ -434,9 +447,10 @@ (t/testing "edge-cases:" (t/testing "prevent overriding set to identical path" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/foo")) - (ctob/add-set (ctob/make-token-set :name "foo")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/foo")) + (ctob/add-set (ctob/make-token-set :name "foo")))) lib (tht/get-tokens-lib file)] (t/is (thrown? #?(:cljs js/Error :clj Exception) @@ -452,9 +466,10 @@ #"move token set error: path exists")))) (t/testing "dropping below collapsed group doesnt add as child" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo")) - (ctob/add-set (ctob/make-token-set :name "foo/bar")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "foo/bar")))) lib (tht/get-tokens-lib file) changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 0 :to-index 1 @@ -472,9 +487,10 @@ (t/deftest generate-move-token-group-test (t/testing "Ignore dropping set group to the same position" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo")) - (ctob/add-set (ctob/make-token-set :name "bar/baz")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "bar/baz")))) drop (partial clt/generate-move-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file))] (t/testing "on top of identical" (t/is (= (pcb/empty-changes) @@ -494,10 +510,11 @@ (t/testing "Move set groups" (t/testing "to top" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/foo")) - (ctob/add-set (ctob/make-token-set :name "bar/bar")) - (ctob/add-set (ctob/make-token-set :name "baz/baz")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/foo")) + (ctob/add-set (ctob/make-token-set :name "bar/bar")) + (ctob/add-set (ctob/make-token-set :name "baz/baz")))) lib (tht/get-tokens-lib file) changes (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 2 :to-index 0 @@ -514,9 +531,10 @@ (t/is (= (ctob/get-set-names lib) undo-sets))))) (t/testing "to bottom" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/foo")) - (ctob/add-set (ctob/make-token-set :name "bar")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/foo")) + (ctob/add-set (ctob/make-token-set :name "bar")))) lib (tht/get-tokens-lib file) changes (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 0 :to-index 2 @@ -533,9 +551,10 @@ (t/is (= (ctob/get-set-names lib) undo-sets))))) (t/testing "into set group" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/foo")) - (ctob/add-set (ctob/make-token-set :name "bar/bar")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/foo")) + (ctob/add-set (ctob/make-token-set :name "bar/bar")))) lib (tht/get-tokens-lib file) changes (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 0 :to-index 2 @@ -552,9 +571,10 @@ (t/testing "edge-cases:" (t/testing "prevent overriding set to identical path" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/identical/foo")) - (ctob/add-set (ctob/make-token-set :name "identical/bar")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/identical/foo")) + (ctob/add-set (ctob/make-token-set :name "identical/bar")))) lib (tht/get-tokens-lib file)] (t/is (thrown? #?(:cljs js/Error :clj Exception) @@ -564,8 +584,9 @@ #"move token set error: path exists")))) (t/testing "prevent dropping parent to child" - (let [file (setup-file #(-> % - (ctob/add-set (ctob/make-token-set :name "foo/bar/baz")))) + (let [file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar/baz")))) lib (tht/get-tokens-lib file)] (t/is (thrown? #?(:cljs js/Error :clj Exception) @@ -573,3 +594,211 @@ :to-index 1 :position :bot}) #"move token set error: parent-to-child")))))))) + +(t/deftest generate-activate-theme-test + (t/testing "activating an inactive theme creates changes" + (let [set-id (uuid/next) + theme-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id set-id + :name "s1")) + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "t1" + :sets #{"s1"})))) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-activate-theme tokens-status tokens-lib theme-id)) + file' (thf/apply-changes file changes) + tokens-status' (tht/get-tokens-status file') + file'' (thf/apply-undo-changes file' changes) + tokens-status'' (tht/get-tokens-status file'')] + ;; Redo: theme and set are active + (t/is (ctos/theme-active? tokens-status' theme-id)) + (t/is (ctos/set-active? tokens-status' set-id)) + ;; Undo: theme and set are inactive + (t/is (not (ctos/theme-active? tokens-status'' theme-id))) + (t/is (not (ctos/set-active? tokens-status'' set-id))))) + + (t/testing "activating an already-active theme returns same changes" + (let [theme-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "t1" + :sets #{"s1"}))) + :status-fn #(ctos/set-tokens-status % #{theme-id} #{})) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-activate-theme tokens-status tokens-lib theme-id))] + (t/is (= [] (:redo-changes changes))))) + + (t/testing "activating a non-existent theme returns same changes" + (let [non-existent-id (uuid/next) + file (tht/sample-file-with-tokens) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-activate-theme tokens-status tokens-lib non-existent-id))] + (t/is (= [] (:redo-changes changes)))))) + +(t/deftest generate-deactivate-theme-test + (t/testing "deactivating an active theme creates changes" + (let [theme-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "t1" + :sets #{}))) + :status-fn #(ctos/set-tokens-status % #{theme-id} #{})) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-deactivate-theme tokens-status tokens-lib theme-id)) + file' (thf/apply-changes file changes) + tokens-status' (tht/get-tokens-status file') + file'' (thf/apply-undo-changes file' changes) + tokens-status'' (tht/get-tokens-status file'')] + ;; Redo: theme is inactive + (t/is (not (ctos/theme-active? tokens-status' theme-id))) + ;; Undo: theme is active again + (t/is (ctos/theme-active? tokens-status'' theme-id)))) + + (t/testing "deactivating an inactive theme returns same changes" + (let [theme-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :name "s1")) + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "t1" + :sets #{"s1"})))) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-deactivate-theme tokens-status tokens-lib theme-id))] + (t/is (= [] (:redo-changes changes)))))) + +(t/deftest generate-set-theme-status-test + (t/testing "setting active? to true activates the theme" + (let [theme-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "t1" + :sets #{})))) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-set-theme-status tokens-status tokens-lib theme-id true)) + file' (thf/apply-changes file changes) + tokens-status' (tht/get-tokens-status file')] + (t/is (ctos/theme-active? tokens-status' theme-id)))) + + (t/testing "setting active? to false deactivates the theme" + (let [theme-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "t1" + :sets #{}))) + :status-fn #(ctos/set-tokens-status % #{theme-id} #{})) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-set-theme-status tokens-status tokens-lib theme-id false)) + file' (thf/apply-changes file changes) + tokens-status' (tht/get-tokens-status file')] + (t/is (not (ctos/theme-active? tokens-status' theme-id)))))) + +(t/deftest generate-toggle-theme-test + (t/testing "toggling an active theme deactivates it" + (let [theme-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "t1" + :sets #{}))) + :status-fn #(ctos/set-tokens-status % #{theme-id} #{})) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-toggle-theme tokens-status tokens-lib theme-id)) + file' (thf/apply-changes file changes) + tokens-status' (tht/get-tokens-status file') + file'' (thf/apply-undo-changes file' changes) + tokens-status'' (tht/get-tokens-status file'')] + ;; Redo: theme is inactive + (t/is (not (ctos/theme-active? tokens-status' theme-id))) + ;; Undo: theme is active again + (t/is (ctos/theme-active? tokens-status'' theme-id)))) + + (t/testing "toggling an inactive theme activates it" + (let [theme-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "t1" + :sets #{})))) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-toggle-theme tokens-status tokens-lib theme-id)) + file' (thf/apply-changes file changes) + tokens-status' (tht/get-tokens-status file') + file'' (thf/apply-undo-changes file' changes) + tokens-status'' (tht/get-tokens-status file'')] + ;; Redo: theme is active + (t/is (ctos/theme-active? tokens-status' theme-id)) + ;; Undo: theme is inactive + (t/is (not (ctos/theme-active? tokens-status'' theme-id)))))) + +(t/deftest generate-sync-tokens-status-with-lib-test + (t/testing "sync removes stale theme and set ids from status" + (let [stale-theme-id (uuid/next) + stale-set-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn identity + :status-fn #(ctos/set-tokens-status % #{stale-theme-id} #{stale-set-id})) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-sync-tokens-status-with-lib tokens-status tokens-lib)) + file' (thf/apply-changes file changes) + tokens-status' (tht/get-tokens-status file') + file'' (thf/apply-undo-changes file' changes) + tokens-status'' (tht/get-tokens-status file'')] + ;; Redo: stale ids are removed + (t/is (= #{} (ctos/get-active-theme-ids tokens-status'))) + (t/is (= #{} (ctos/get-active-set-ids tokens-status'))) + ;; Undo: stale ids are restored + (t/is (= #{stale-theme-id} (ctos/get-active-theme-ids tokens-status''))) + (t/is (= #{stale-set-id} (ctos/get-active-set-ids tokens-status''))))) + + (t/testing "sync with status already in sync returns same changes" + (let [theme-id (uuid/next) + set-id (uuid/next) + file (tht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id set-id :name "s1")) + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "t1" + :sets #{"s1"}))) + :status-fn #(ctos/set-tokens-status % #{theme-id} #{set-id})) + tokens-status (tht/get-tokens-status file) + tokens-lib (tht/get-tokens-lib file) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (:data file)) + (clt/generate-sync-tokens-status-with-lib tokens-status tokens-lib))] + (t/is (= [] (:redo-changes changes)))))) diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index bb2d5cf204..ea5eb4841c 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -247,6 +247,16 @@ theme' (ctob/toggle-set theme nil)] (t/is (= (:sets theme') #{}))))) +(t/deftest get-theme-path + (let [theme1 (ctob/make-token-theme :name "theme1") + theme2 (ctob/make-token-theme :group "group1" :name "theme2")] + (t/is (= "/theme1" (ctob/get-theme-path theme1))) + (t/is (= "/theme1" (ctob/get-theme-path theme1 false))) + (t/is (= "theme1" (ctob/get-theme-path theme1 true))) + (t/is (= "group1/theme2" (ctob/get-theme-path theme2))) + (t/is (= "group1/theme2" (ctob/get-theme-path theme2 false))) + (t/is (= "group1 / theme2" (ctob/get-theme-path theme2 true))))) + (t/deftest make-tokens-lib (let [tokens-lib (ctob/make-tokens-lib)] (t/is (= (ctob/set-count tokens-lib) 0)))) diff --git a/common/test/common_tests/types/tokens_migrations_test.cljc b/common/test/common_tests/types/tokens_migrations_test.cljc index 04bbd9d7a1..f607c67234 100644 --- a/common/test/common_tests/types/tokens_migrations_test.cljc +++ b/common/test/common_tests/types/tokens_migrations_test.cljc @@ -7,9 +7,10 @@ (ns common-tests.types.tokens-migrations-test (:require [app.common.data :as d] + [app.common.files.tokens :as cfo] [app.common.test-helpers.ids-map :as thi] - [app.common.time :as ct] [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] [clojure.datafy :refer [datafy]] [clojure.test :as t])) @@ -79,4 +80,34 @@ (t/is (= (ctob/get-description theme1') (ctob/get-description theme1))) (t/is (= (ctob/get-id theme2') (ctob/get-id theme2))) (t/is (= (ctob/get-name theme2') (ctob/get-name theme2))) - (t/is (= (ctob/get-description theme2') (ctob/get-description theme2)))))) \ No newline at end of file + (t/is (= (ctob/get-description theme2') (ctob/get-description theme2)))))) + +(t/deftest make-tokens-status-from-tokens-lib + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) + :name "set-a")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) + :name "set-b")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-c) + :name "set-c")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-d) + :name "set-d")) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) + :name "theme-1" + :sets #{"set-a" "set-b"})) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2) + :name "theme-2" + :sets #{"set-b"})) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-3) + :name "theme-3" + :sets #{"set-c" "set-d"})) + (ctob/set-active-themes #{"/theme-1" "/theme-2"})) + tokens-status (cfo/make-tokens-status-from-lib tokens-lib)] + (t/is (ctos/tokens-status? tokens-status)) + (t/is (ctos/check-tokens-status tokens-status)) + (t/is (= (count (ctos/get-active-theme-ids tokens-status)) 2)) + (t/is (ctos/theme-active? tokens-status (thi/id :theme-1))) + (t/is (ctos/theme-active? tokens-status (thi/id :theme-2))) + (t/is (= 2 (count (ctos/get-active-set-ids tokens-status)))) + (t/is (ctos/set-active? tokens-status (thi/id :set-a))) + (t/is (ctos/set-active? tokens-status (thi/id :set-b))))) diff --git a/common/test/common_tests/types/tokens_status_test.cljc b/common/test/common_tests/types/tokens_status_test.cljc new file mode 100644 index 0000000000..d2ebb81dd3 --- /dev/null +++ b/common/test/common_tests/types/tokens_status_test.cljc @@ -0,0 +1,101 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns common-tests.types.tokens-status-test + (:require + #?(:clj [app.common.fressian :as fres]) + #?(:clj [clojure.data.json :as json]) + [app.common.transit :as tr] + [app.common.types.tokens-status :as ctos] + [app.common.uuid :as uuid] + [clojure.datafy :refer [datafy]] + [clojure.test :as t])) + +(t/deftest make-tokens-status + (let [theme-id (uuid/next) + set-id (uuid/next) + status (ctos/make-tokens-status :active-theme-ids #{theme-id} + :active-set-ids #{set-id})] + (t/is (ctos/tokens-status? status)) + (t/is (ctos/check-tokens-status status)) + (t/is (= 1 (count (ctos/get-active-theme-ids status)))) + (t/is (ctos/theme-active? status theme-id)) + (t/is (= 1 (count (ctos/get-active-set-ids status)))) + (t/is (ctos/set-active? status set-id)))) + +(t/deftest make-tokens-status-defaults + (let [status (ctos/make-tokens-status)] + (t/is (ctos/tokens-status? status)) + (t/is (ctos/check-tokens-status status)) + (t/is (= 0 (count (ctos/get-active-theme-ids status)))) + (t/is (= 0 (count (ctos/get-active-set-ids status)))))) + +(t/deftest make-invalid-tokens-status + (t/testing "non-set for active-themes" + (t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) + #"expected valid params for tokens-status" + (ctos/make-tokens-status :active-theme-ids [])))) + (t/testing "non-uuid in active-sets" + (t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) + #"expected valid params for tokens-status" + (ctos/make-tokens-status :active-set-ids #{"not-a-uuid"}))))) + +(t/deftest set-tokens-status + (let [theme1-id (uuid/next) + theme2-id (uuid/next) + theme3-id (uuid/next) + set1-id (uuid/next) + set2-id (uuid/next) + set3-id (uuid/next) + status (-> (ctos/make-tokens-status {:active-theme-ids #{theme3-id} + :active-set-ids #{set3-id}}) + (ctos/set-tokens-status #{theme1-id theme2-id} #{set1-id set2-id}))] + (t/is (= #{theme1-id theme2-id} (ctos/get-active-theme-ids status))) + (t/is (= #{set1-id set2-id} (ctos/get-active-set-ids status))))) + +(t/deftest datafy-tokens-status + (let [theme-id (uuid/next) + set-id (uuid/next) + status (ctos/make-tokens-status :active-theme-ids #{theme-id} + :active-set-ids #{set-id}) + result (datafy status)] + (t/is (map? result)) + (t/is (not (ctos/tokens-status? result))) + (t/is (= (:active-theme-ids result) #{theme-id})) + (t/is (= (:active-set-ids result) #{set-id})))) + +(t/deftest transit-serialization + (let [theme-id (uuid/next) + set-id (uuid/next) + status (ctos/make-tokens-status :active-theme-ids #{theme-id} + :active-set-ids #{set-id}) + encoded (tr/encode-str status) + status' (tr/decode-str encoded)] + (t/is (ctos/tokens-status? status')) + (t/is (= (datafy status') (datafy status))))) + +#?(:clj + (t/deftest fressian-serialization + (let [theme-id (uuid/next) + set-id (uuid/next) + status (ctos/make-tokens-status :active-theme-ids #{theme-id} + :active-set-ids #{set-id}) + encoded (fres/encode status) + status' (fres/decode encoded)] + (t/is (ctos/tokens-status? status')) + (t/is (= (datafy status') (datafy status)))))) + +#?(:clj + (t/deftest json-serialization + (let [theme-id (uuid/next) + set-id (uuid/next) + status (ctos/make-tokens-status :active-theme-ids #{theme-id} + :active-set-ids #{set-id}) + json-str (json/write-str status) + parsed (json/read-str json-str :key-fn keyword)] + (t/is (map? parsed)) + (t/is (= [(str theme-id)] (:active-theme-ids parsed))) + (t/is (= [(str set-id)] (:active-set-ids parsed)))))) diff --git a/frontend/src/app/main/data/helpers.cljs b/frontend/src/app/main/data/helpers.cljs index 9af2d23242..b14db89ad9 100644 --- a/frontend/src/app/main/data/helpers.cljs +++ b/frontend/src/app/main/data/helpers.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] + [app.common.files.tokens :as cfo] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] @@ -37,12 +38,21 @@ ([state file-id] (dm/get-in state [:files file-id :data]))) -(defn lookup-tokens-lib +(defn lookup-tokens-file-data [state] (let [current-file-data (lookup-file-data state) - tokens-file-id (or (:tokens-file current-file-data) (:id current-file-data)) - tokens-file-data (lookup-file-data state tokens-file-id)] - (:tokens-lib tokens-file-data))) + tokens-file-id (or (cfo/get-tokens-file current-file-data) (:id current-file-data))] + (lookup-file-data state tokens-file-id))) + +(defn lookup-tokens-lib + [state] + (let [tokens-file-data (lookup-tokens-file-data state)] + (cfo/get-tokens-lib tokens-file-data))) + +(defn lookup-tokens-status + [state] + (let [current-file-data (lookup-file-data state)] ;; Tokens status is always in the current file + (cfo/get-tokens-status current-file-data))) (defn get-page [fdata page-id] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index b031797599..e274de63fa 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -12,10 +12,12 @@ [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] [app.common.files.shapes-helpers :as cfsh] + [app.common.files.tokens :as cfo] [app.common.geom.point :as gpt] [app.common.logging :as log] [app.common.logic.libraries :as cll] [app.common.logic.shapes :as cls] + [app.common.logic.tokens :as clo] [app.common.logic.variants :as clv] [app.common.path-names :as cpn] [app.common.time :as ct] @@ -1483,6 +1485,47 @@ (rx/take-until stopper-s))))))) +(defn sync-tokens-status-with-lib + [] + (ptk/reify ::sync-tokens-status-with-lib + ptk/WatchEvent + (watch [_ state _] + (let [tokens-lib (dsh/lookup-tokens-lib state) + tokens-status (dsh/lookup-tokens-status state)] + (when (and tokens-lib tokens-status) + (let [data (dsh/lookup-file-data state) + changes (-> (pcb/empty-changes) + (pcb/with-library-data data) + (clo/generate-sync-tokens-status-with-lib tokens-status tokens-lib))] + (rx/of (dch/commit-changes changes)))))))) + +(defn watch-token-changes + "Watch the state for changes that affect the tokens library. If a change is detected, + launches a sync-tokens-status event so the tokens-status is kept in sync with the library." + [] + (ptk/reify ::watch-token-changes + ptk/WatchEvent + (watch [_ _ stream] + (let [stopper-s + (->> stream + (rx/map ptk/type) + (rx/filter (fn [event-type] + (or (= ::dwpg/finalize-page event-type) + (= ::watch-token-changes event-type))))) + + changes-s + (->> stream + (rx/filter dch/commit?) + (rx/map deref) + (rx/filter #(= :local (:source %))) + (rx/observe-on :async))] + + (->> changes-s + (rx/filter (comp ch/tokens-lib-changed? :changes)) + (rx/debounce 5000) + (rx/map (fn [_] (sync-tokens-status-with-lib))) + (rx/take-until stopper-s)))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Backend interactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1528,6 +1571,21 @@ (map #(assoc % :library-of file-id)) (d/index-by :id)))))) +(defn- initialize-tokens-status + [library-id] + (ptk/reify ::initialize-tokens-status + ptk/WatchEvent + (watch [it state _] + (let [library (dm/get-in state [:files library-id]) + library-data (ctf/file-data library)] + (when (some? (cfo/get-tokens-lib library-data)) + (when-let [tokens-status (cfo/get-tokens-status library-data)] + (let [changes (-> (pcb/empty-changes it) + (pcb/with-library-data library-data) + (pcb/set-tokens-status tokens-status))] + (rx/of + (dch/commit-changes changes))))))))) + (defn- load-library-file [file-id library-id] (ptk/reify ::load-library-file @@ -1537,8 +1595,10 @@ (rx/merge (->> (rp/cmd! :get-file {:id library-id :features features}) (rx/merge-map fpmap/resolve-file) - (rx/map (fn [file] - (libraries-fetched file-id [file])))) + (rx/mapcat (fn [file] + (rx/of + (libraries-fetched file-id [file]) + (initialize-tokens-status library-id))))) (->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"}) (rx/map (fn [thumbnails] (fn [state] diff --git a/frontend/src/app/main/data/workspace/pages.cljs b/frontend/src/app/main/data/workspace/pages.cljs index 37d12730b8..9aa0c28c5a 100644 --- a/frontend/src/app/main/data/workspace/pages.cljs +++ b/frontend/src/app/main/data/workspace/pages.cljs @@ -114,6 +114,7 @@ (rx/of (dwth/watch-state-changes file-id page-id))) (rx/of (dwl/watch-component-changes)) + (rx/of (dwl/watch-token-changes)) (let [profile (:profile state) props (get profile :props)] diff --git a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs index 6f5a09de2f..c3a145a2e4 100644 --- a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs +++ b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs @@ -267,11 +267,12 @@ (ptk/reify ::set-token-theme-active ptk/WatchEvent (watch [_ state _] - (let [data (dsh/lookup-file-data state) - tokens-lib (get-tokens-lib state) - changes (-> (pcb/empty-changes) - (pcb/with-library-data data) - (clt/generate-set-active-token-theme tokens-lib id active?))] + (let [data (dsh/lookup-file-data state) + tokens-status (dsh/lookup-tokens-status state) + tokens-lib (dsh/lookup-tokens-lib state) + changes (-> (pcb/empty-changes) + (pcb/with-library-data data) + (clt/generate-set-theme-status tokens-status tokens-lib id active?))] (rx/of (dch/commit-changes changes) (dwtp/propagate-workspace-tokens)))))) @@ -281,11 +282,12 @@ (ptk/reify ::toggle-token-theme-active ptk/WatchEvent (watch [it state _] - (let [data (dsh/lookup-file-data state) - tokens-lib (get-tokens-lib state) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (clt/generate-toggle-token-theme tokens-lib id))] + (let [data (dsh/lookup-tokens-file-data state) + tokens-status (dsh/lookup-tokens-status state) + tokens-lib (dsh/lookup-tokens-lib state) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (clt/generate-toggle-theme tokens-status tokens-lib id))] (rx/of (dch/commit-changes changes) (dwtp/propagate-workspace-tokens)))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 55a6c62436..fc60af512f 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -456,6 +456,9 @@ (def tokens-lib (l/derived dsh/lookup-tokens-lib st/state)) +(def tokens-status + (l/derived dsh/lookup-tokens-status st/state)) + (def workspace-token-theme-groups (l/derived (d/nilf ctob/get-theme-groups) tokens-lib)) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 1986876c9f..64b3c63a36 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -25,6 +25,8 @@ (def libraries (mf/create-context nil)) (def design-tokens (mf/create-context nil)) (def token-inputs (mf/create-context nil)) +(def tokens-lib (mf/create-context nil)) +(def tokens-status (mf/create-context nil)) (def current-scroll (mf/create-context nil)) (def current-zoom (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 6e17e7f0c7..5ad21eea26 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -22,7 +22,7 @@ [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.context :as muc] + [app.main.ui.context :as ctx] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]] @@ -177,7 +177,7 @@ (mf/with-memo [] (mf/html [:> collapse-button* {}]))] - [:> (mf/provider muc/sidebar) {:value :left} + [:> (mf/provider ctx/sidebar) {:value :left} [:aside {:ref parent-ref :id "left-sidebar-aside" :data-testid "left-sidebar" @@ -320,8 +320,8 @@ (mf/with-memo [active-tokens] (delay (ctob/group-by-type active-tokens)))] - [:> (mf/provider muc/sidebar) {:value :right} - [:> (mf/provider muc/active-tokens-by-type) {:value active-tokens-by-type} + [:> (mf/provider ctx/sidebar) {:value :right} + [:> (mf/provider ctx/active-tokens-by-type) {:value active-tokens-by-type} [:aside {:class (stl/css-case :right-settings-bar true :not-expand (not can-be-expanded?) @@ -368,6 +368,9 @@ (let [tokens-lib (mf/deref refs/tokens-lib) + tokens-status + (mf/deref refs/tokens-status) + active-tokens (mf/with-memo [tokens-lib] (if tokens-lib @@ -399,25 +402,26 @@ resolved-active-tokens-force-set (sd/use-resolved-tokens* active-tokens-force-set)] - [:* - (if (:collapse-left-sidebar layout) - [:> collapsed-button*] - [:> left-sidebar* {:layout layout + [:> (mf/provider ctx/tokens-lib) {:value tokens-lib} + [:> (mf/provider ctx/tokens-status) {:value tokens-status} + (if (:collapse-left-sidebar layout) + [:> collapsed-button*] + [:> left-sidebar* {:layout layout + :file file + :page-id page-id + :tokens-lib tokens-lib + :active-tokens active-tokens-force-set + :resolved-active-tokens (if tokenscript? + tokenscript-resolved-active-tokens-force-set + resolved-active-tokens-force-set)}]) + [:> right-sidebar* {:section section + :selected selected + :drawing-tool drawing-tool + :layout layout :file file + :file-id file-id :page-id page-id :tokens-lib tokens-lib - :active-tokens active-tokens-force-set - :resolved-active-tokens (if tokenscript? - tokenscript-resolved-active-tokens-force-set - resolved-active-tokens-force-set)}]) - [:> right-sidebar* {:section section - :selected selected - :drawing-tool drawing-tool - :layout layout - :file file - :file-id file-id - :page-id page-id - :tokens-lib tokens-lib - :active-tokens (if tokenscript? - tokenscript-resolved-active-tokens - resolved-active-tokens)}]])) + :active-tokens (if tokenscript? + tokenscript-resolved-active-tokens + resolved-active-tokens)}]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 460b508a18..2efa03753b 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -26,21 +26,17 @@ (st/emit! (dwtl/toggle-token-set-group path))) (mf/defc sets-list* - [{:keys [tokens-lib selected new-path edition-id]}] + [{:keys [selected new-path edition-id]}] - (let [token-sets + (let [tokens-lib + (mf/use-ctx ctx/tokens-lib) + + token-sets (some-> tokens-lib (ctob/get-set-tree)) can-edit? (mf/use-ctx ctx/can-edit?) - token-set-active? - (mf/use-fn - (mf/deps tokens-lib) - (fn [name] - (when tokens-lib - (ctob/token-set-active? tokens-lib name)))) - token-set-group-active? (mf/use-fn (fn [group-path] @@ -66,7 +62,6 @@ {:tokens-lib tokens-lib :token-sets token-sets - :is-token-set-active token-set-active? :is-token-set-group-active token-set-group-active? :on-select on-select-token-set-click diff --git a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs index e835234ea9..c52e693347 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs @@ -10,6 +10,7 @@ (:require [app.common.data.macros :as dm] [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.store :as st] [app.main.ui.context :as ctx] @@ -313,7 +314,6 @@ [{:keys [is-draggable selected is-token-set-group-active - is-token-set-active on-start-edition on-reset-edition on-edit-submit-set @@ -326,7 +326,9 @@ new-path edition-id]}] - (let [collapsed-paths* (mf/use-state #{}) + (let [tokens-status (mf/use-ctx ctx/tokens-status) + + collapsed-paths* (mf/use-state #{}) collapsed-paths (deref collapsed-paths*) collapsed? @@ -418,7 +420,7 @@ :set token-set :label (peek path) :is-editing (= edition-id id) - :is-active (is-token-set-active (ctob/get-name token-set)) + :is-active (ctos/set-active? tokens-status id) :is-selected (= selected id) :is-draggable is-draggable :is-new false @@ -440,7 +442,6 @@ selected on-update-token-set on-update-token-set-group - is-token-set-active is-token-set-group-active on-create-token-set on-toggle-token-set @@ -453,7 +454,6 @@ edition-id]}] (assert (fn? is-token-set-group-active) "expected a function for `is-token-set-group-active` prop") - (assert (fn? is-token-set-active) "expected a function for `is-token-set-active` prop") (let [theme-modal? (= origin "theme-modal") can-edit? (mf/use-ctx ctx/can-edit?) @@ -492,7 +492,6 @@ :token-sets token-sets :selected selected :on-select on-select - :is-token-set-active is-token-set-active :is-token-set-group-active is-token-set-group-active :on-toggle-set on-toggle-token-set :on-toggle-set-group on-toggle-token-set-group diff --git a/frontend/src/app/main/ui/workspace/tokens/themes/create_modal.cljs b/frontend/src/app/main/ui/workspace/tokens/themes/create_modal.cljs index 5d583a22be..f47f3e9f93 100644 --- a/frontend/src/app/main/ui/workspace/tokens/themes/create_modal.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/themes/create_modal.cljs @@ -19,6 +19,7 @@ [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.context :as ctx] [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.controls.combobox :refer [combobox*]] @@ -450,12 +451,14 @@ ::mf/register modal/components ::mf/register-as :tokens/themes} [] - [:div {:class (stl/css :modal-overlay)} - [:div {:class (stl/css :modal-dialog) - :data-testid "token-theme-update-create-modal"} - [:> icon-button* {:class (stl/css :close-btn) - :on-click modal/hide! - :aria-label (tr "labels.close") - :variant "action" - :icon i/close}] - [:> themes-modal-body*]]]) + (let [tokens-status (mf/deref refs/tokens-status)] + [:> (mf/provider ctx/tokens-status) {:value tokens-status} + [:div {:class (stl/css :modal-overlay)} + [:div {:class (stl/css :modal-dialog) + :data-testid "token-theme-update-create-modal"} + [:> icon-button* {:class (stl/css :close-btn) + :on-click modal/hide! + :aria-label (tr "labels.close") + :variant "action" + :icon i/close}] + [:> themes-modal-body*]]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs index 92f5f6ad03..d06b3bc747 100644 --- a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs @@ -8,13 +8,16 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data.macros :as dm] + [app.common.files.tokens :as cfo] [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] [app.common.uuid :as uuid] [app.main.data.modal :as modal] [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.context :as ctx] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.foundations.typography.text :refer [text*]] [app.main.ui.hooks :as hooks] @@ -24,17 +27,16 @@ [rumext.v2 :as mf])) (mf/defc themes-list* - [{:keys [themes active-theme-paths on-close is-grouped]}] + [{:keys [themes tokens-status on-close is-grouped]}] (when (seq themes) [:ul {:class (stl/css :theme-options)} (for [[_ {:keys [id name] :as theme}] themes - :let [theme-id (ctob/get-theme-path theme) - selected? (get active-theme-paths theme-id) + :let [selected? (ctos/theme-active? tokens-status id) select-theme (fn [e] (dom/stop-propagation e) (st/emit! (dwtl/toggle-token-theme-active id)) (on-close))]] - [:li {:key theme-id + [:li {:key (str "theme-" id) :role "option" :aria-selected selected? :class (stl/css-case @@ -53,42 +55,52 @@ (modal/show! :tokens/themes {})) (mf/defc theme-options* - [{:keys [active-theme-paths themes on-close]}] - [:ul {:class (stl/css :theme-options :custom-select-dropdown) - :role "listbox"} - (for [[group themes] themes] - [:li {:key group - :aria-labelledby (dm/str group "-label") - :role "group"} - (when (seq group) - [:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group]) - [:> themes-list* {:themes themes - :active-theme-paths active-theme-paths - :on-close on-close - :is-grouped true}]]) - [:li {:class (stl/css :separator) - :aria-hidden true}] - [:li {:class (stl/css-case :checked-element true - :checked-element-button true) - :role "option" - :on-click open-tokens-theme-modal} - [:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")] - [:> icon* {:icon-id i/arrow-right :aria-hidden true}]]]) + [{:keys [tokens-lib tokens-status on-close]}] + (let [themes + (mf/with-memo [tokens-lib] + (ctob/get-theme-tree-no-hidden tokens-lib))] + [:ul {:class (stl/css :theme-options :custom-select-dropdown) + :role "listbox"} + (for [[group themes] themes] + [:li {:key group + :aria-labelledby (dm/str group "-label") + :role "group"} + (when (seq group) + [:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group]) + [:> themes-list* {:themes themes + :tokens-status tokens-status + :on-close on-close + :is-grouped true}]]) + [:li {:class (stl/css :separator) + :aria-hidden true}] + [:li {:class (stl/css-case :checked-element true + :checked-element-button true) + :role "option" + :on-click open-tokens-theme-modal} + [:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")] + [:> icon* {:icon-id i/arrow-right :aria-hidden true}]]])) (mf/defc theme-selector* [{:keys []}] (let [;; Store - 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) + tokens-lib (mf/use-ctx ctx/tokens-lib) + tokens-status (mf/use-ctx ctx/tokens-status) + + active-themes + (mf/with-memo [tokens-lib tokens-status] + (cfo/get-active-themes tokens-status tokens-lib)) + + active-themes-count + (mf/with-memo [active-themes] + (count active-themes)) + can-edit? (:can-edit (deref refs/permissions)) + ;; Data current-label (cond (> active-themes-count 1) (tr "workspace.tokens.active-themes" active-themes-count) - (= active-themes-count 1) (some->> (first active-theme-paths) - (ctob/split-theme-path) - (remove empty?) - (str/join " / ")) + (= active-themes-count 1) (-> (first active-themes) + (ctob/get-theme-path true)) :else (tr "workspace.tokens.no-active-theme")) ;; State @@ -140,7 +152,7 @@ [:& dropdown {:show is-open? :on-close on-close-dropdown} - [:> theme-options* {:active-theme-paths active-theme-paths - :themes themes + [:> theme-options* {:tokens-lib tokens-lib + :tokens-status tokens-status :on-close on-close-dropdown}]]]) container))])) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 2296fe8566..36775bb04c 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -48,6 +48,7 @@ [frontend-tests.tokens.logic.token-actions-test] [frontend-tests.tokens.logic.token-data-test] [frontend-tests.tokens.logic.token-remapping-test] + [frontend-tests.tokens.logic.tokens-status-test] [frontend-tests.tokens.style-dictionary-test] [frontend-tests.tokens.token-errors-test] [frontend-tests.tokens.workspace-tokens-remap-test] @@ -119,6 +120,7 @@ 'frontend-tests.tokens.logic.token-remapping-test 'frontend-tests.tokens.style-dictionary-test 'frontend-tests.tokens.token-errors-test + 'frontend-tests.tokens.logic.tokens-status-test 'frontend-tests.tokens.workspace-tokens-remap-test 'frontend-tests.ui.comments-position-modifier-test 'frontend-tests.ui.ds-controls-numeric-input-test diff --git a/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs b/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs index 24a9ad6ca8..753484fb09 100644 --- a/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs +++ b/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs @@ -103,21 +103,21 @@ sets (ctob/get-sets token-lib)] (t/testing "Token lib contains one set" - (t/is (= (count sets) 1)))))))) + (t/is (= (count sets) 1))))))))) - (t/deftest delete-set - (t/async - done - (let [file (setup-file-with-token-lib) - store (ths/setup-store file) - events [(dwtl/delete-token-set (cthi/id :test-token-set))]] +(t/deftest delete-set + (t/async + done + (let [file (setup-file-with-token-lib) + store (ths/setup-store file) + events [(dwtl/delete-token-set (cthi/id :test-token-set))]] - (tohs/run-store-async - store done events - (fn [new-state] - (let [file' (ths/get-file-from-state new-state) - tokens-lib' (toht/get-tokens-lib file') - sets' (ctob/get-sets tokens-lib')] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + tokens-lib' (toht/get-tokens-lib file') + sets' (ctob/get-sets tokens-lib')] - (t/testing "Set has been deleted" - (t/is (= (count sets') 0)))))))))) \ No newline at end of file + (t/testing "Set has been deleted" + (t/is (= (count sets') 0))))))))) \ No newline at end of file diff --git a/frontend/test/frontend_tests/tokens/logic/tokens_status_test.cljs b/frontend/test/frontend_tests/tokens/logic/tokens_status_test.cljs new file mode 100644 index 0000000000..641dcf0f87 --- /dev/null +++ b/frontend/test/frontend_tests/tokens/logic/tokens_status_test.cljs @@ -0,0 +1,118 @@ +(ns frontend-tests.tokens.logic.tokens-status-test + (:require + [app.common.test-helpers.ids-map :as thi] + [app.common.test-helpers.tokens :as tho] + [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos] + [app.main.data.workspace.tokens.library-edit :as dwtl] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.pages :as thp] + [frontend-tests.helpers.state :as ths] + [frontend-tests.tokens.helpers.state :as tohs])) + +(t/use-fixtures :each + {:before thp/reset-idmap!}) + +(defn- setup-file + "Create a file with several token themes and token sets, some active and + some inactive. Returns the file map with :tokens-lib and :tokens-status + in its :data." + [] + (-> (tho/sample-file-with-tokens + :lib-fn #(-> % + ;; Add token sets + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-active) + :name "set-active")) + (ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-inactive) + :name "set-inactive")) + ;; Add themes + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-active) + :name "theme-active" + :group "group-1" + :sets #{"set-active"})) + (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-inactive) + :name "theme-inactive" + :group "group-1" + :sets #{"set-inactive"}))) + :status-fn #(-> % + (ctos/set-tokens-status #{(thi/id :theme-active)} + #{(thi/id :set-active)}))))) + +(defn- get-tokens-status [file] + (get-in file [:data :tokens-status])) + +(t/deftest test-set-token-theme-active-deactivate + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + theme-id (thi/id :theme-active) + set-id (thi/id :set-active) + events [(dwtl/set-token-theme-active theme-id false)]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + status (get-tokens-status file')] + (t/is (not (ctos/theme-active? status theme-id)) + "theme should be inactive after deactivation") + (t/is (not (ctos/set-active? status set-id)) + "set should be inactive when its theme is deactivated"))))))) + +(t/deftest test-set-token-theme-active-activate + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + theme-id (thi/id :theme-inactive) + set-id (thi/id :set-inactive) + events [(dwtl/set-token-theme-active theme-id true)]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + status (get-tokens-status file')] + (t/is (ctos/theme-active? status theme-id) + "theme should be active after activation") + (t/is (ctos/set-active? status set-id) + "set should be active when its theme is activated"))))))) + +(t/deftest test-toggle-token-theme-active-deactivate + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + theme-id (thi/id :theme-active) + set-id (thi/id :set-active) + events [(dwtl/toggle-token-theme-active theme-id)]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + status (get-tokens-status file')] + (t/is (not (ctos/theme-active? status theme-id)) + "active theme should become inactive after toggle") + (t/is (not (ctos/set-active? status set-id)) + "set should be inactive when its theme is toggled off"))))))) + +(t/deftest test-toggle-token-theme-active-activate + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + theme-id (thi/id :theme-inactive) + set-id (thi/id :set-inactive) + events [(dwtl/toggle-token-theme-active theme-id)]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + status (get-tokens-status file')] + (t/is (ctos/theme-active? status theme-id) + "inactive theme should become active after toggle") + (t/is (ctos/set-active? status set-id) + "set should be active when its theme is toggled on")))))))