diff --git a/common/dev/user.clj b/common/dev/user.clj index 3cb650922..c558def7b 100644 --- a/common/dev/user.clj +++ b/common/dev/user.clj @@ -6,6 +6,8 @@ (ns user (:require + [app.common.data :as d] + [app.common.fressian :as fres] [app.common.pprint :as pp] [app.common.schema :as sm] [app.common.schema.desc-js-like :as smdj] diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index ecf55b115..77e9af51b 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -44,8 +44,8 @@ (defn ordered-map ([] lkm/empty-linked-map) - ([a] (conj lkm/empty-linked-map a)) - ([a & xs] (apply conj lkm/empty-linked-map a xs))) + ([k a] (assoc lkm/empty-linked-map k a)) + ([k a & xs] (apply assoc lkm/empty-linked-map k a xs))) (defn ordered-set? [o] @@ -564,6 +564,41 @@ new-elems (remove p? after)))) +(defn addm-at-index + "Insert an element in an ordered map at an arbitrary index" + [coll index key element] + (assert (ordered-map? coll)) + (-> (ordered-map) + (into (take index coll)) + (assoc key element) + (into (drop index coll)))) + +(defn insertm-at-index + "Insert a map {k v} of elements in an ordered map at an arbitrary index" + [coll index new-elems] + (assert (ordered-map? coll)) + (-> (ordered-map) + (into (take index coll)) + (into new-elems) + (into (drop index coll)))) + +(defn adds-at-index + "Insert an element in an ordered set at an arbitrary index" + [coll index element] + (assert (ordered-set? coll)) + (-> (ordered-set) + (into (take index coll)) + (conj element) + (into (drop index coll)))) + +(defn inserts-at-index + "Insert a list of elements in an ordered set at an arbitrary index" + [coll index new-elems] + (assert (ordered-set? coll)) + (-> (ordered-set) + (into (take index coll)) + (into new-elems) + (into (drop index coll)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Parsing / Conversion diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index d8c05619a..ae9b6362f 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -25,6 +25,7 @@ [app.common.types.shape-tree :as ctst] [app.common.types.token :as cto] [app.common.types.token-theme :as ctot] + [app.common.types.tokens-lib :as ctob] [app.common.types.tokens-list :as ctol] [app.common.types.tokens-theme-list :as ctotl] [app.common.types.typographies-list :as ctyl] @@ -289,28 +290,37 @@ [:map {:title "ModTokenSetChange"} [:type [:= :mod-token-set]] [:id ::sm/uuid] + [:name :string] [:token-set ::ctot/token-set]]] [:del-token-set [:map {:title "DelTokenSetChange"} [:type [:= :del-token-set]] - [:id ::sm/uuid]]] + [:id ::sm/uuid] + [:name :string]]] [:add-token [:map {:title "AddTokenChange"} [:type [:= :add-token]] + [:set-id ::sm/uuid] + [:set-name :string] [:token ::cto/token]]] [:mod-token [:map {:title "ModTokenChange"} [:type [:= :mod-token]] + [:set-id ::sm/uuid] + [:set-name :string] [:id ::sm/uuid] + [:name :string] [:token ::cto/token]]] [:del-token [:map {:title "DelTokenChange"} [:type [:= :del-token]] - [:id ::sm/uuid]]]]]) + [:set-name :string] + [:id ::sm/uuid] + [:name :string]]]]]) (sm/register! ::changes [:sequential {:gen/max 2} ::change]) @@ -780,16 +790,37 @@ ;; -- Tokens (defmethod process-change :add-token - [data {:keys [token]}] - (ctol/add-token data token)) + [data {:keys [set-id set-name token]}] + (-> data + (ctol/add-token set-id token) + (update :tokens-lib + #(-> % + (ctob/ensure-tokens-lib) + (ctob/add-token-in-set set-name (ctob/make-token token)))))) (defmethod process-change :mod-token - [data {:keys [id token]}] - (ctol/update-token data id merge token)) + [data {:keys [set-name id name token]}] + (-> data + (ctol/update-token id merge token) + (update :tokens-lib + #(-> % + (ctob/ensure-tokens-lib) + (ctob/update-token-in-set + set-name + name + (fn [old-token] + (ctob/make-token (merge old-token token)))))))) (defmethod process-change :del-token - [data {:keys [id]}] - (ctol/delete-token data id)) + [data {:keys [set-name id name]}] + (-> data + (ctol/delete-token id) + (update :tokens-lib + #(-> % + (ctob/ensure-tokens-lib) + (ctob/delete-token-from-set + set-name + name))))) (defmethod process-change :add-temporary-token-theme [data {:keys [token-theme]}] @@ -817,15 +848,32 @@ (defmethod process-change :add-token-set [data {:keys [token-set]}] - (ctotl/add-token-set data token-set)) + (-> data + (ctotl/add-token-set token-set) + (update :tokens-lib + #(-> % + (ctob/ensure-tokens-lib) + (ctob/add-set (ctob/make-token-set token-set)))))) (defmethod process-change :mod-token-set - [data {:keys [id token-set]}] - (ctotl/update-token-set data id merge token-set)) + [data {:keys [id name token-set]}] + (-> data + (ctotl/update-token-set id merge token-set) + (update :tokens-lib + #(-> % + (ctob/ensure-tokens-lib) + (ctob/update-set name (fn [prev-set] + (merge prev-set + (dissoc token-set :tokens)))))))) (defmethod process-change :del-token-set - [data {:keys [id]}] - (ctotl/delete-token-set data id)) + [data {:keys [id name]}] + (-> data + (ctotl/delete-token-set id) + (update :tokens-lib + #(-> % + (ctob/ensure-tokens-lib) + (ctob/delete-set name))))) ;; === Operations (defmethod process-operation :set @@ -980,5 +1028,3 @@ (defmethod frames-changed :default [_ _] nil) - - diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index ed779cb5c..09148d542 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -737,48 +737,48 @@ [changes token-set] (-> changes (update :redo-changes conj {:type :add-token-set :token-set token-set}) - (update :undo-changes conj {:type :del-token-set :id (:id token-set)}) + (update :undo-changes conj {:type :del-token-set :id (:id token-set) :name (:name token-set)}) (apply-changes-local))) (defn update-token-set [changes token-set prev-token-set] (-> changes - (update :redo-changes conj {:type :mod-token-set :id (:id token-set) :token-set token-set}) - (update :undo-changes conj {:type :mod-token-set :id (:id token-set) :token-set (or prev-token-set token-set)}) + (update :redo-changes conj {:type :mod-token-set :id (:id token-set) :name (:name prev-token-set) :token-set token-set}) + (update :undo-changes conj {:type :mod-token-set :id (:id token-set) :name (:name prev-token-set) :token-set (or prev-token-set token-set)}) (apply-changes-local))) (defn delete-token-set - [changes token-set-id] + [changes token-set-id token-set-name] (assert-library! changes) (let [library-data (::library-data (meta changes)) prev-token-set (get-in library-data [:token-sets-index token-set-id])] (-> changes - (update :redo-changes conj {:type :del-token-set :id token-set-id}) + (update :redo-changes conj {:type :del-token-set :id token-set-id :name token-set-name}) (update :undo-changes conj {:type :add-token-set :token-set prev-token-set}) (apply-changes-local)))) (defn add-token - [changes token] + [changes set-id set-name token] (-> changes - (update :redo-changes conj {:type :add-token :token token}) - (update :undo-changes conj {:type :del-token :id (:id token)}) + (update :redo-changes conj {:type :add-token :set-id set-id :set-name set-name :token token}) + (update :undo-changes conj {:type :del-token :set-name set-name :id (:id token) :name (:name token)}) (apply-changes-local))) (defn update-token - [changes {:keys [id] :as token} prev-token] + [changes set-id set-name {:keys [id name] :as token} {prev-name :name :as prev-token}] (-> changes - (update :redo-changes conj {:type :mod-token :id id :token token}) - (update :undo-changes conj {:type :mod-token :id id :token (or prev-token token)}) + (update :redo-changes conj {:type :mod-token :set-id set-id :set-name set-name :id id :name prev-name :token token}) + (update :undo-changes conj {:type :mod-token :set-id set-id :set-name set-name :id id :name name :token (or prev-token token)}) (apply-changes-local))) (defn delete-token - [changes token-id] + [changes set-name token-id token-name] (assert-library! changes) (let [library-data (::library-data (meta changes)) prev-token (get-in library-data [:tokens token-id])] (-> changes - (update :redo-changes conj {:type :del-token :id token-id}) - (update :undo-changes conj {:type :add-token :token prev-token}) + (update :redo-changes conj {:type :del-token :set-name set-name :id token-id :name token-name}) + (update :undo-changes conj {:type :add-token :set-id uuid/zero :set-name set-name :token prev-token}) (apply-changes-local)))) (defn add-component diff --git a/common/src/app/common/fressian.clj b/common/src/app/common/fressian.clj index 282620698..4a640cd8c 100644 --- a/common/src/app/common/fressian.clj +++ b/common/src/app/common/fressian.clj @@ -15,6 +15,7 @@ java.time.Instant java.time.OffsetDateTime java.util.List + linked.map.LinkedMap org.fressian.Reader org.fressian.StreamingWriter org.fressian.Writer @@ -109,6 +110,13 @@ (clojure.lang.PersistentArrayMap. (.toArray kvs)) (clojure.lang.PersistentHashMap/create (seq kvs))))) +(defn read-ordered-map + [^Reader rdr] + (let [kvs ^java.util.List (read-object! rdr)] + (reduce #(assoc %1 (first %2) (second %2)) + (d/ordered-map) + (partition-all 2 (seq kvs))))) + (def ^:dynamic *write-handler-lookup* nil) (def ^:dynamic *read-handler-lookup* nil) @@ -225,6 +233,11 @@ :wfn write-map-like :rfn read-map-like} + {:name "linked/map" + :class LinkedMap + :wfn write-map-like + :rfn read-ordered-map} + {:name "clj/keyword" :class clojure.lang.Keyword :wfn write-named diff --git a/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc index 570cfa062..691da5f93 100644 --- a/common/src/app/common/schema.cljc +++ b/common/src/app/common/schema.cljc @@ -308,7 +308,6 @@ ::explain explain})))) true))) - (defn fast-validate! "A fast path for validation process, assumes the ILazySchema protocol implemented on the provided `s` schema. Sould not be used directly." diff --git a/common/src/app/common/time.cljc b/common/src/app/common/time.cljc index 6cd8601d6..8cbfe9541 100644 --- a/common/src/app/common/time.cljc +++ b/common/src/app/common/time.cljc @@ -26,11 +26,18 @@ #?(:clj (Instant/now) :cljs (.local ^js DateTime))) +#?(:clj + (defn is-after? + [one other] + (.isAfter one other))) + (defn instant [s] #?(:clj (Instant/ofEpochMilli s) :cljs (.fromMillis ^js DateTime s #js {:zone "local" :setZone false}))) +;; To check for valid date time we can just use the core inst? function + #?(:cljs (extend-protocol IComparable DateTime @@ -45,7 +52,6 @@ 0 (if (< (inst-ms it) (inst-ms other)) -1 1))))) - #?(:cljs (extend-type DateTime cljs.core/IEquiv diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc index 93ab8d4b2..21673bdb4 100644 --- a/common/src/app/common/transit.cljc +++ b/common/src/app/common/transit.cljc @@ -12,7 +12,7 @@ [app.common.uri :as uri] [cognitect.transit :as t] [lambdaisland.uri :as luri] - [linked.core :as lk] + [linked.map :as lkm] [linked.set :as lks]) #?(:clj (:import @@ -24,6 +24,7 @@ java.time.Instant java.time.OffsetDateTime lambdaisland.uri.URI + linked.map.LinkedMap linked.set.LinkedSet))) (def write-handlers (atom nil)) @@ -118,10 +119,15 @@ {:id "u" :rfn parse-uuid}) + {:id "ordered-map" + :class #?(:clj LinkedMap :cljs lkm/LinkedMap) + :wfn vec + :rfn #(into lkm/empty-linked-map %)} + {:id "ordered-set" :class #?(:clj LinkedSet :cljs lks/LinkedSet) :wfn vec - :rfn #(into (lk/set) %)} + :rfn #(into lks/empty-linked-set %)} {:id "duration" :class #?(:clj Duration :cljs lxn/Duration) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 593ec5d2d..dcf9900f8 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -28,6 +28,7 @@ [app.common.types.shape-tree :as ctst] [app.common.types.token :as cto] [app.common.types.token-theme :as ctt] + [app.common.types.tokens-lib :as ctl] [app.common.types.typographies-list :as ctyl] [app.common.types.typography :as cty] [app.common.uuid :as uuid] @@ -78,7 +79,8 @@ [:token-sets-index {:optional true} [:map-of {:gen/max 10} ::sm/uuid ::ctt/token-set]] [:tokens {:optional true} - [:map-of {:gen/max 100} ::sm/uuid ::cto/token]]]) + [:map-of {:gen/max 100} ::sm/uuid ::cto/token]] + [:tokens-lib {:optional true} ::ctl/tokens-lib]]) (def check-file-data! (sm/check-fn ::data)) diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index 104948105..6de8f9c29 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -47,8 +47,16 @@ :string :typography}) +(defn valid-token-type? + [t] + (token-types t)) + (def token-name-ref :string) +(defn valid-token-name-ref? + [n] + (string? n)) + (sm/register! ::token [:map {:title "Token"} [:id ::sm/uuid] diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc new file mode 100644 index 000000000..7e34404c4 --- /dev/null +++ b/common/src/app/common/types/tokens_lib.cljc @@ -0,0 +1,319 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.types.tokens-lib + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.schema :as sm] + [app.common.time :as dt] + [app.common.transit :as t] + [app.common.types.token :as cto] + #?(:clj [app.common.fressian :as fres]))) + +;; #?(:clj (set! *warn-on-reflection* true)) + +;; === Token + +(defrecord Token [name type value description modified-at]) + +(def schema:token + [:and + [:map {:title "Token"} + [:name cto/token-name-ref] ;; not necessary to have uuid + [:type [::sm/one-of cto/token-types]] + [:value :any] + [:description [:maybe :string]] ;; defrecord always have the attributes, even with nil value + [:modified-at ::sm/inst]] + [:fn (partial instance? Token)]]) + +(sm/register! ::token schema:token) + +(def valid-token? + (sm/validator schema:token)) + +(def check-token! + (sm/check-fn ::token)) + +(defn make-token + [& {:keys [] :as params}] + (let [params (-> params + (dissoc :id) ;; we will remove this when old data structures are removed + (update :modified-at #(or % (dt/now)))) + token (map->Token params)] + + (dm/assert! + "expected valid token" + (check-token! token)) + + token)) + +;; === Token Set + +(defprotocol ITokenSet + (add-token [_ token] "add a token at the end of the list") + (update-token [_ token-name f] "update a token in the list") + (delete-token [_ token-name] "delete a token from the list")) + +(defrecord TokenSet [name description modified-at tokens] + ITokenSet + (add-token [_ token] + (dm/assert! "expected valid token" (check-token! token)) + (TokenSet. name + description + (dt/now) + (assoc tokens (:name token) token))) + + (update-token [this token-name f] + (if-let [token (get tokens token-name)] + (let [token' (-> (make-token (f token)) + (assoc :modified-at (dt/now)))] + (check-token! token') + (TokenSet. name + description + (dt/now) + (if (= (:name token) (:name token')) + (assoc tokens (:name token') token') + (let [index (d/index-of (keys tokens) (:name token))] + (-> tokens + (dissoc (:name token)) + (d/addm-at-index index (:name token') token')))))) + this)) + + (delete-token [_ token-name] + (TokenSet. name + description + (dt/now) + (dissoc tokens token-name)))) + +(def schema:token-set + [:and [:map {:title "TokenSet"} + [:name :string] + [:description [:maybe :string]] + [:modified-at ::sm/inst] + [:tokens [:map-of {:gen/max 5} :string ::token]]] + [:fn (partial instance? TokenSet)]]) + +(sm/register! ::token-set schema:token-set) + +(def valid-token-set? + (sm/validator schema:token-set)) + +(def check-token-set! + (sm/check-fn ::token-set)) + +(defn make-token-set + [& {:keys [] :as params}] + (let [params (-> params + (dissoc :id) + (update :modified-at #(or % (dt/now))) + (update :tokens #(into (d/ordered-map) %))) + token-set (map->TokenSet params)] + + (dm/assert! + "expected valid token set" + (check-token-set! token-set)) + + token-set)) + +;; === TokenSets (collection) + +(defprotocol ITokenSets + (add-set [_ token-set] "add a set to the library, at the end") + (update-set [_ set-name f] "modify a set in the ilbrary") + (delete-set [_ set-name] "delete a set in the library") + (set-count [_] "get the total number if sets in the library") + (get-sets [_] "get an ordered sequence of all sets in the library") + (get-set [_ set-name] "get one set looking for name") + (validate [_])) + +(def schema:token-sets + [:and + [:map-of {:title "TokenSets"} + :string ::token-set] + [:fn d/ordered-map?]]) + +(sm/register! ::token-sets schema:token-sets) + +(def valid-token-sets? + (sm/validator schema:token-sets)) + +(def check-token-sets! + (sm/check-fn ::token-sets)) + +;; === TokenThemes (collection) + +(def valid-token-themes? + (constantly true)) + +;; === Tokens Lib + +(defprotocol ITokensLib + "A library of tokens, sets and themes." + (add-token-in-set [_ set-name token] "add token to a set") + (update-token-in-set [_ set-name token-name f] "update a token in a set") + (delete-token-from-set [_ set-name token-name] "delete a token from a set")) + +(deftype TokensLib [sets themes] + ;; NOTE: This is only for debug purposes, pending to properly + ;; implement the toString and alternative printing. + #?@(:clj [clojure.lang.IDeref + (deref [_] {:sets sets :themes themes})] + :cljs [cljs.core/IDeref + (-deref [_] {:sets sets :themes themes})]) + + #?@(:cljs [cljs.core/IEncodeJS + (-clj->js [_] (js-obj "sets" (clj->js sets) + "themes" (clj->js themes)))]) + + ITokenSets + (add-set [_ token-set] + (dm/assert! "expected valid token set" (check-token-set! token-set)) + (TokensLib. (assoc sets (:name token-set) token-set) + themes)) + + (update-set [this set-name f] + (if-let [set (get sets set-name)] + (let [set' (-> (make-token-set (f set)) + (assoc :modified-at (dt/now)))] + (check-token-set! set') + (TokensLib. (if (= (:name set) (:name set')) + (assoc sets (:name set') set') + (let [index (d/index-of (keys sets) (:name set))] + (-> sets + (dissoc (:name set)) + (d/addm-at-index index (:name set') set')))) + themes)) + this)) + + (delete-set [_ set-name] + (TokensLib. (dissoc sets set-name) + themes)) + + (validate [_] + (and (valid-token-sets? sets) + (valid-token-themes? themes))) + + (set-count [_] + (count sets)) + + (get-sets [_] + (vals sets)) + + (get-set [_ set-name] + (get sets set-name)) + + ITokensLib + (add-token-in-set [this set-name token] + (dm/assert! "expected valid token instance" (check-token! token)) + (if (contains? sets set-name) + (TokensLib. (update sets set-name add-token token) + themes) + this)) + + (update-token-in-set [this set-name token-name f] + (if (contains? sets set-name) + (TokensLib. (update sets set-name + #(update-token % token-name f)) + themes) + this)) + + (delete-token-from-set [this set-name token-name] + (if (contains? sets set-name) + (TokensLib. (update sets set-name + #(delete-token % token-name)) + themes) + this))) + +(defn valid-tokens-lib? + [o] + (and (instance? TokensLib o) + (validate o))) + +(defn check-tokens-lib! + [lib] + (dm/assert! + "expected valid tokens lib" + (valid-tokens-lib? lib))) + +(defn make-tokens-lib + "Create an empty or prepopulated tokens library." + ([] + ;; NOTE: is possible that ordered map is not the most apropriate + ;; data structure and maybe we need a specific that allows us an + ;; easy way to reorder it, or just store inside Tokens data + ;; structure the data and the order separately as we already do + ;; with pages and pages-index. + (make-tokens-lib :sets (d/ordered-map) + :themes (d/ordered-map))) + + ([& {:keys [sets themes]}] + (let [tokens-lib (TokensLib. sets themes)] + + (dm/assert! + "expected valid tokens lib" + (valid-tokens-lib? tokens-lib)) + + tokens-lib))) + +(defn ensure-tokens-lib + [tokens-lib] + (or tokens-lib (make-tokens-lib))) + +(def type:tokens-lib + {:type ::tokens-lib + :pred valid-tokens-lib?}) + +(sm/register! ::tokens-lib type:tokens-lib) + +;; === Serialization handlers for RPC API and database + +(t/add-handlers! + {:id "penpot/tokens-lib" + :class TokensLib + :wfn deref + :rfn #(make-tokens-lib %)} + + {:id "penpot/token-set" + :class TokenSet + :wfn #(into {} %) + :rfn #(make-token-set %)} + + {:id "penpot/token" + :class Token + :wfn #(into {} %) + :rfn #(make-token %)}) + +#?(:clj + (fres/add-handlers! + {:name "penpot/token/v1" + :class Token + :wfn (fn [n w o] + (fres/write-tag! w n 1) + (fres/write-object! w (into {} o))) + :rfn (fn [r] + (let [obj (fres/read-object! r)] + (map->Token obj)))} + + {:name "penpot/token-set/v1" + :class TokenSet + :wfn (fn [n w o] + (fres/write-tag! w n 1) + (fres/write-object! w (into {} o))) + :rfn (fn [r] + (let [obj (fres/read-object! r)] + (map->TokenSet obj)))} + + {:name "penpot/tokens-lib/v1" + :class TokensLib + :wfn (fn [n w o] + (fres/write-tag! w n 2) + (fres/write-object! w (.-sets o)) + (fres/write-object! w (.-themes o))) + :rfn (fn [r] + (let [sets (fres/read-object! r) + themes (fres/read-object! r)] + (->TokensLib sets themes)))})) diff --git a/common/src/app/common/types/tokens_list.cljc b/common/src/app/common/types/tokens_list.cljc index 58479f72e..b31262d4d 100644 --- a/common/src/app/common/types/tokens_list.cljc +++ b/common/src/app/common/types/tokens_list.cljc @@ -21,8 +21,12 @@ (defn add-token "Adds a new token to the file data, setting its `modified-at` timestamp." - [file-data token] - (update file-data :tokens assoc (:id token) (touch token))) + [file-data token-set-id token] + (-> file-data + (update :tokens assoc (:id token) (touch token)) + (d/update-in-when [:token-sets-index token-set-id] #(-> + (update % :tokens conj (:id token)) + (touch))))) (defn get-token "Retrieves a token by its ID from the file data." diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc new file mode 100644 index 000000000..09ad19b17 --- /dev/null +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -0,0 +1,259 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns common-tests.types.tokens-lib-test + (:require + [app.common.data :as d] + [app.common.fressian :as fres] + [app.common.time :as dt] + [app.common.transit :as tr] + [app.common.types.tokens-lib :as ctob] + [clojure.test :as t])) + +(t/deftest make-token + (let [now (dt/now) + token1 (ctob/make-token :name "test-token-1" + :type :boolean + :value true) + token2 (ctob/make-token :name "test-token-2" + :type :numeric + :value 66 + :description "test description" + :modified-at now)] + + (t/is (= (:name token1) "test-token-1")) + (t/is (= (:type token1) :boolean)) + (t/is (= (:value token1) true)) + (t/is (nil? (:description token1))) + (t/is (some? (:modified-at token1))) + (t/is (ctob/valid-token? token1)) + + (t/is (= (:name token2) "test-token-2")) + (t/is (= (:type token2) :numeric)) + (t/is (= (:value token2) 66)) + (t/is (= (:description token2) "test description")) + (t/is (= (:modified-at token2) now)) + (t/is (ctob/valid-token? token2)))) + +(t/deftest invalid-tokens + (let [args {:name 777 + :type :invalid}] + (t/is (thrown-with-msg? Exception #"expected valid token" + (apply ctob/make-token args))) + (t/is (false? (ctob/valid-token? {}))))) + +(t/deftest make-token-set + (let [now (dt/now) + token-set1 (ctob/make-token-set :name "test-token-set-1") + token-set2 (ctob/make-token-set :name "test-token-set-2" + :description "test description" + :modified-at now + :tokens [])] + + (t/is (= (:name token-set1) "test-token-set-1")) + (t/is (nil? (:description token-set1))) + (t/is (some? (:modified-at token-set1))) + (t/is (empty? (:tokens token-set1))) + + (t/is (= (:name token-set2) "test-token-set-2")) + (t/is (= (:description token-set2) "test description")) + (t/is (= (:modified-at token-set2) now)) + (t/is (empty? (:tokens token-set2))))) + +(t/deftest invalid-token-set + (let [args {:name 777 + :description 999}] + (t/is (thrown-with-msg? Exception #"expected valid token set" + (apply ctob/make-token-set args))))) + +(t/deftest make-tokens-lib + (let [tokens-lib (ctob/make-tokens-lib)] + (t/is (= (ctob/set-count tokens-lib) 0)))) + +(t/deftest add-token-set + (let [tokens-lib (ctob/make-tokens-lib) + token-set (ctob/make-token-set :name "test-token-set") + tokens-lib' (ctob/add-set tokens-lib token-set) + + token-sets' (ctob/get-sets tokens-lib') + token-set' (ctob/get-set tokens-lib' "test-token-set")] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (first token-sets') token-set)) + (t/is (= token-set' token-set)))) + +(t/deftest update-token-set + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set"))) + + tokens-lib' (-> tokens-lib + (ctob/update-set "test-token-set" + (fn [token-set] + (assoc token-set + :name "updated-name" + :description "some description"))) + (ctob/update-set "not-existing-set" + (fn [token-set] + (assoc token-set + :name "no-effect")))) + + token-set (ctob/get-set tokens-lib "test-token-set") + token-set' (ctob/get-set tokens-lib' "updated-name")] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (:name token-set') "updated-name")) + (t/is (= (:description token-set') "some description")) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))) + +(t/deftest delete-token-set + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set"))) + + tokens-lib' (-> tokens-lib + (ctob/delete-set "test-token-set") + (ctob/delete-set "not-existing-set")) + + token-set' (ctob/get-set tokens-lib' "updated-name")] + + (t/is (= (ctob/set-count tokens-lib') 0)) + (t/is (nil? token-set')))) + +(t/deftest add-token + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set"))) + token (ctob/make-token :name "test-token" + :type :boolean + :value true) + tokens-lib' (-> tokens-lib + (ctob/add-token-in-set "test-token-set" token) + (ctob/add-token-in-set "not-existing-set" token)) + + token-set (ctob/get-set tokens-lib "test-token-set") + token-set' (ctob/get-set tokens-lib' "test-token-set") + token' (get-in token-set' [:tokens "test-token"])] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (count (:tokens token-set')) 1)) + (t/is (= (:name token') "test-token")) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))) + +(t/deftest update-token + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-1" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-2" + :type :boolean + :value true))) + + tokens-lib' (-> tokens-lib + (ctob/update-token-in-set "test-token-set" "test-token-1" + (fn [token] + (assoc token + :description "some description" + :value false))) + (ctob/update-token-in-set "not-existing-set" "test-token-1" + (fn [token] + (assoc token + :name "no-effect"))) + (ctob/update-token-in-set "test-token-set" "not-existing-token" + (fn [token] + (assoc token + :name "no-effect")))) + + token-set (ctob/get-set tokens-lib "test-token-set") + token-set' (ctob/get-set tokens-lib' "test-token-set") + token (get-in token-set [:tokens "test-token-1"]) + token' (get-in token-set' [:tokens "test-token-1"])] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (count (:tokens token-set')) 2)) + (t/is (= (d/index-of (keys (:tokens token-set')) "test-token-1") 0)) + (t/is (= (:name token') "test-token-1")) + (t/is (= (:description token') "some description")) + (t/is (= (:value token') false)) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))) + (t/is (dt/is-after? (:modified-at token') (:modified-at token))))) + +(t/deftest rename-token + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-1" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-2" + :type :boolean + :value true))) + + tokens-lib' (-> tokens-lib + (ctob/update-token-in-set "test-token-set" "test-token-1" + (fn [token] + (assoc token + :name "updated-name")))) + + token-set (ctob/get-set tokens-lib "test-token-set") + token-set' (ctob/get-set tokens-lib' "test-token-set") + token (get-in token-set [:tokens "test-token-1"]) + token' (get-in token-set' [:tokens "updated-name"])] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (count (:tokens token-set')) 2)) + (t/is (= (d/index-of (keys (:tokens token-set')) "updated-name") 0)) + (t/is (= (:name token') "updated-name")) + (t/is (= (:description token') nil)) + (t/is (= (:value token') true)) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))) + (t/is (dt/is-after? (:modified-at token') (:modified-at token))))) + +(t/deftest delete-token + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token" + :type :boolean + :value true))) + tokens-lib' (-> tokens-lib + (ctob/delete-token-from-set "test-token-set" "test-token") + (ctob/delete-token-from-set "not-existing-set" "test-token") + (ctob/delete-token-from-set "test-set" "not-existing-token")) + + token-set (ctob/get-set tokens-lib "test-token-set") + token-set' (ctob/get-set tokens-lib' "test-token-set") + token' (get-in token-set' [:tokens "test-token"])] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (count (:tokens token-set')) 0)) + (t/is (nil? token')) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))) + +(t/deftest transit-serialization + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" (ctob/make-token :name "test-token" + :type :boolean + :value true))) + encoded-str (tr/encode-str tokens-lib) + tokens-lib' (tr/decode-str encoded-str)] + + (t/is (ctob/valid-tokens-lib? tokens-lib')) + (t/is (= (ctob/set-count tokens-lib') 1)))) + +(t/deftest fressian-serialization + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" (ctob/make-token :name "test-token" + :type :boolean + :value true))) + encoded-blob (fres/encode tokens-lib) + tokens-lib' (fres/decode encoded-blob)] + + (t/is (ctob/valid-tokens-lib? tokens-lib')) + (t/is (= (ctob/set-count tokens-lib') 1)))) diff --git a/frontend/package.json b/frontend/package.json index 3d81cc512..c7d620fc8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -96,6 +96,7 @@ }, "dependencies": { "@tokens-studio/sd-transforms": "^0.16.1", + "bun": "^1.1.25", "compression": "^1.7.4", "date-fns": "^3.6.0", "eventsource-parser": "^1.1.2", diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index 83ace95ba..904811b80 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -81,6 +81,11 @@ (let [workspace-data (deref refs/workspace-data)] (get (:tokens workspace-data) id))) +(defn get-token-set-data-from-token-set-id + [id] + (let [workspace-data (deref refs/workspace-data)] + (get (:token-sets-index workspace-data) id))) + (defn set-selected-token-set-id [id] (ptk/reify ::set-selected-token-set-id @@ -88,6 +93,10 @@ (update [_ state] (wtts/assoc-selected-token-set-id state id)))) +(defn get-token-set-tokens + [token-set file] + (map #(get-in file [:tokens %]) (:tokens token-set))) + (defn create-token-theme [token-theme] (let [new-token-theme (merge {:id (uuid/next) @@ -120,7 +129,7 @@ (not theme-id) (-> changes (pcb/add-temporary-token-theme {:id (uuid/next) - :name "" + :name "Test theme" :sets #{id}})) new-set? (-> changes (pcb/update-token-theme @@ -196,14 +205,14 @@ (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) -(defn delete-token-set [token-set-id] +(defn delete-token-set [token-set-id token-set-name] (ptk/reify ::delete-token-set ptk/WatchEvent (watch [it state _] (let [data (get state :workspace-data) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) - (pcb/delete-token-set token-set-id))] + (pcb/delete-token-set token-set-id token-set-name))] (rx/of (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) @@ -214,46 +223,44 @@ (ptk/reify ::update-create-token ptk/WatchEvent (watch [it state _] - (let [prev-token (get-token-data-from-token-id (:id token)) + (let [token-set (wtts/get-selected-token-set state) + create-set? (not token-set) + token-set (or token-set + {:id (uuid/next) + :name "Global" + :tokens []}) + + changes (cond-> (pcb/empty-changes it) + create-set? + (pcb/add-token-set token-set)) + + prev-token-id (d/seek #(= % (:id token)) (:tokens token-set)) + prev-token (get-token-data-from-token-id prev-token-id) create-token? (not prev-token) - token-changes (if create-token? - (-> (pcb/empty-changes it) - (pcb/add-token token)) - (-> (pcb/empty-changes it) - (pcb/update-token token prev-token))) - token-set (wtts/get-selected-token-set state) - create-set? (not token-set) - new-token-set {:id (uuid/next) - :name "Global" - :tokens [(:id token)]} - selected-token-set-id (if create-set? - (:id new-token-set) - (:id token-set)) - set-changes (cond - create-set? (-> token-changes - (pcb/add-token-set new-token-set)) - :else (let [updated-token-set (if (contains? token-set (:id token)) - token-set - (update token-set :tokens conj (:id token)))] - (-> token-changes - (pcb/update-token-set updated-token-set token-set)))) - theme-changes (-> set-changes + + changes (if create-token? + (pcb/add-token changes (:id token-set) (:name token-set) token) + (pcb/update-token changes (:id token-set) (:name token-set) token prev-token)) + + changes (-> changes (ensure-token-theme-changes state {:new-set? create-set? - :id selected-token-set-id}))] + :id (:id token-set)}))] (rx/of - (set-selected-token-set-id selected-token-set-id) - (dch/commit-changes theme-changes))))))) + (set-selected-token-set-id (:id token-set)) + (dch/commit-changes changes))))))) (defn delete-token - [id] + [set-name id name] + (dm/assert! (string? set-name)) (dm/assert! (uuid? id)) + (dm/assert! (string? name)) (ptk/reify ::delete-token ptk/WatchEvent (watch [it state _] (let [data (get state :workspace-data) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) - (pcb/delete-token id))] + (pcb/delete-token set-name id name))] (rx/of (dch/commit-changes changes)))))) (defn duplicate-token diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs index 56c392f3c..1eba57205 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -201,10 +201,11 @@ (generic-attribute-actions #{:x} "X" (assoc context-data :on-update-shape wtch/update-shape-position)) (generic-attribute-actions #{:y} "Y" (assoc context-data :on-update-shape wtch/update-shape-position))))})) -(defn default-actions [{:keys [token]}] - (let [{:keys [modal]} (wtty/get-token-properties token)] +(defn default-actions [{:keys [token selected-token-set-id]}] + (let [{:keys [modal]} (wtty/get-token-properties token) + selected-token-set (dt/get-token-set-data-from-token-set-id selected-token-set-id)] [{:title "Delete Token" - :action #(st/emit! (dt/delete-token (:id token)))} + :action #(st/emit! (dt/delete-token (:name selected-token-set) (:id token) (:name token)))} {:title "Duplicate Token" :action #(st/emit! (dt/duplicate-token (:id token)))} {:title "Edit Token" @@ -311,7 +312,8 @@ selected (mf/deref refs/selected-shapes) selected-shapes (into [] (keep (d/getf objects)) selected) token-id (:token-id mdata) - token (get (mf/deref refs/workspace-selected-token-set-tokens) token-id)] + token (get (mf/deref refs/workspace-selected-token-set-tokens) token-id) + selected-token-set-id (mf/deref refs/workspace-selected-token-set-id)] (mf/use-effect (mf/deps mdata) (fn [] @@ -327,4 +329,5 @@ [:ul {:class (stl/css :context-list)} [:& menu-tree {:submenu-offset @width :token token + :selected-token-set-id selected-token-set-id :selected-shapes selected-shapes}]])]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 7b2e66f55..06577fcf4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -26,9 +26,9 @@ (defn on-select-token-set-click [id] (st/emit! (wdt/set-selected-token-set-id id))) -(defn on-delete-token-set-click [id event] +(defn on-delete-token-set-click [id name event] (dom/stop-propagation event) - (st/emit! (wdt/delete-token-set id))) + (st/emit! (wdt/delete-token-set id name))) (defn on-update-token-set [token-set] (st/emit! (wdt/update-token-set token-set))) @@ -115,7 +115,7 @@ [:* [:div {:class (stl/css :set-name)} name] [:div {:class (stl/css :delete-set)} - [:button {:on-click #(on-delete-token-set-click id %) + [:button {:on-click #(on-delete-token-set-click id name %) :type "button"} i/delete]] (if set? diff --git a/frontend/src/app/main/ui/workspace/tokens/token_types.cljs b/frontend/src/app/main/ui/workspace/tokens/token_types.cljs index a083bed54..2faadab69 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token_types.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token_types.cljs @@ -13,106 +13,65 @@ (def token-types (ordered-map - [:border-radius - {:title "Border Radius" - :attributes ctt/border-radius-keys - :on-update-shape wtch/update-shape-radius-all - :modal {:key :tokens/border-radius - :fields [{:label "Border Radius" - :key :border-radius}]}}] - [:stroke-width - {:title "Stroke Width" - :attributes ctt/stroke-width-keys - :on-update-shape wtch/update-stroke-width - :modal {:key :tokens/stroke-width - :fields [{:label "Stroke Width" - :key :stroke-width}]}}] + :border-radius + {:title "Border Radius" + :attributes ctt/border-radius-keys + :on-update-shape wtch/update-shape-radius-all + :modal {:key :tokens/border-radius + :fields [{:label "Border Radius" + :key :border-radius}]}} + :stroke-width + {:title "Stroke Width" + :attributes ctt/stroke-width-keys + :on-update-shape wtch/update-stroke-width + :modal {:key :tokens/stroke-width + :fields [{:label "Stroke Width" + :key :stroke-width}]}} - [:sizing - {:title "Sizing" - :attributes #{:width :height} - :all-attributes ctt/sizing-keys - :on-update-shape wtch/update-shape-dimensions - :modal {:key :tokens/sizing - :fields [{:label "Sizing" - :key :sizing}]}}] - [:dimensions - {:title "Dimensions" - :attributes #{:width :height} - :all-attributes (set/union - ctt/spacing-keys - ctt/sizing-keys - ctt/border-radius-keys - ctt/stroke-width-keys) - :on-update-shape wtch/update-shape-dimensions - :modal {:key :tokens/dimensions - :fields [{:label "Dimensions" - :key :dimensions}]}}] + :sizing + {:title "Sizing" + :attributes #{:width :height} + :all-attributes ctt/sizing-keys + :on-update-shape wtch/update-shape-dimensions + :modal {:key :tokens/sizing + :fields [{:label "Sizing" + :key :sizing}]}} + :dimensions + {:title "Dimensions" + :attributes #{:width :height} + :all-attributes (set/union + ctt/spacing-keys + ctt/sizing-keys + ctt/border-radius-keys + ctt/stroke-width-keys) + :on-update-shape wtch/update-shape-dimensions + :modal {:key :tokens/dimensions + :fields [{:label "Dimensions" + :key :dimensions}]}} - [:opacity - {:title "Opacity" - :attributes ctt/opacity-keys - :on-update-shape wtch/update-opacity - :modal {:key :tokens/opacity - :fields [{:label "Opacity" - :key :opacity}]}}] + :opacity + {:title "Opacity" + :attributes ctt/opacity-keys + :on-update-shape wtch/update-opacity + :modal {:key :tokens/opacity + :fields [{:label "Opacity" + :key :opacity}]}} - [:rotation - {:title "Rotation" - :attributes ctt/rotation-keys - :on-update-shape wtch/update-rotation - :modal {:key :tokens/rotation - :fields [{:label "Rotation" - :key :rotation}]}}] - [:spacing - {:title "Spacing" - :attributes #{:column-gap :row-gap} - :all-attributes ctt/spacing-keys - :on-update-shape wtch/update-layout-spacing - :modal {:key :tokens/spacing - :fields [{:label "Spacing" - :key :spacing}]}}] - (comment - [:boolean - {:title "Boolean" - :modal {:key :tokens/boolean - :fields [{:label "Boolean"}]}}] - - [:box-shadow - {:title "Box Shadow" - :modal {:key :tokens/box-shadow - :fields [{:label "Box shadows" - :key :box-shadow - :type :box-shadow}]}}] - - [:numeric - {:title "Numeric" - :modal {:key :tokens/numeric - :fields [{:label "Numeric" - :key :numeric}]}}] - - [:other - {:title "Other" - :modal {:key :tokens/other - :fields [{:label "Other" - :key :other}]}}] - [:string - {:title "String" - :modal {:key :tokens/string - :fields [{:label "String" - :key :string}]}}] - [:typography - {:title "Typography" - :modal {:key :tokens/typography - :fields [{:label "Font" :key :font-family} - {:label "Weight" :key :weight} - {:label "Font Size" :key :font-size} - {:label "Line Height" :key :line-height} - {:label "Letter Spacing" :key :letter-spacing} - {:label "Paragraph Spacing" :key :paragraph-spacing} - {:label "Paragraph Indent" :key :paragraph-indent} - {:label "Text Decoration" :key :text-decoration} - {:label "Text Case" :key :text-case}]}}]))) + :rotation + {:title "Rotation" + :attributes ctt/rotation-keys + :on-update-shape wtch/update-rotation + :modal {:key :tokens/rotation + :fields [{:label "Rotation" + :key :rotation}]}} + :spacing + {:title "Spacing" + :attributes #{:column-gap :row-gap} + :all-attributes ctt/spacing-keys + :on-update-shape wtch/update-layout-spacing + :modal {:key :tokens/spacing + :fields [{:label "Spacing" + :key :spacing}]}})) (defn get-token-properties [token] (get token-types (:type token))) diff --git a/frontend/test/token_tests/logic/token_actions_test.cljs b/frontend/test/token_tests/logic/token_actions_test.cljs index 0fb1ea5ec..c95af99a2 100644 --- a/frontend/test/token_tests/logic/token_actions_test.cljs +++ b/frontend/test/token_tests/logic/token_actions_test.cljs @@ -1,5 +1,6 @@ (ns token-tests.logic.token-actions-test (:require + [app.common.pprint :refer [pprint]] [app.common.logging :as log] [app.common.test-helpers.compositions :as ctho] [app.common.test-helpers.files :as cthf] diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9fe74c9f0..ebde9b8cf 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2427,6 +2427,62 @@ __metadata: languageName: node linkType: hard +"@oven/bun-darwin-aarch64@npm:1.1.25": + version: 1.1.25 + resolution: "@oven/bun-darwin-aarch64@npm:1.1.25" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@oven/bun-darwin-x64-baseline@npm:1.1.25": + version: 1.1.25 + resolution: "@oven/bun-darwin-x64-baseline@npm:1.1.25" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oven/bun-darwin-x64@npm:1.1.25": + version: 1.1.25 + resolution: "@oven/bun-darwin-x64@npm:1.1.25" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oven/bun-linux-aarch64@npm:1.1.25": + version: 1.1.25 + resolution: "@oven/bun-linux-aarch64@npm:1.1.25" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@oven/bun-linux-x64-baseline@npm:1.1.25": + version: 1.1.25 + resolution: "@oven/bun-linux-x64-baseline@npm:1.1.25" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@oven/bun-linux-x64@npm:1.1.25": + version: 1.1.25 + resolution: "@oven/bun-linux-x64@npm:1.1.25" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@oven/bun-windows-x64-baseline@npm:1.1.25": + version: 1.1.25 + resolution: "@oven/bun-windows-x64-baseline@npm:1.1.25" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@oven/bun-windows-x64@npm:1.1.25": + version: 1.1.25 + resolution: "@oven/bun-windows-x64@npm:1.1.25" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -4379,6 +4435,43 @@ __metadata: languageName: node linkType: hard +"bun@npm:^1.1.25": + version: 1.1.25 + resolution: "bun@npm:1.1.25" + dependencies: + "@oven/bun-darwin-aarch64": "npm:1.1.25" + "@oven/bun-darwin-x64": "npm:1.1.25" + "@oven/bun-darwin-x64-baseline": "npm:1.1.25" + "@oven/bun-linux-aarch64": "npm:1.1.25" + "@oven/bun-linux-x64": "npm:1.1.25" + "@oven/bun-linux-x64-baseline": "npm:1.1.25" + "@oven/bun-windows-x64": "npm:1.1.25" + "@oven/bun-windows-x64-baseline": "npm:1.1.25" + dependenciesMeta: + "@oven/bun-darwin-aarch64": + optional: true + "@oven/bun-darwin-x64": + optional: true + "@oven/bun-darwin-x64-baseline": + optional: true + "@oven/bun-linux-aarch64": + optional: true + "@oven/bun-linux-x64": + optional: true + "@oven/bun-linux-x64-baseline": + optional: true + "@oven/bun-windows-x64": + optional: true + "@oven/bun-windows-x64-baseline": + optional: true + bin: + bun: bin/bun.exe + bunx: bin/bun.exe + checksum: 10c0/ac0cec14aaaaae8b5b3818c6868dd417a60fb5d9c4230f5265c19c0367fd588ae9175ce0feac04db5b5684d624187c639fae1854ee2b52cdd2e2ae0a75a7dd9c + conditions: (os=darwin | os=linux | os=win32) & (cpu=arm64 | cpu=x64) + languageName: node + linkType: hard + "bytes@npm:3.0.0": version: 3.0.0 resolution: "bytes@npm:3.0.0" @@ -6819,6 +6912,7 @@ __metadata: "@tokens-studio/sd-transforms": "npm:^0.16.1" "@types/node": "npm:^20.11.20" autoprefixer: "npm:^10.4.19" + bun: "npm:^1.1.25" compression: "npm:^1.7.4" concurrently: "npm:^8.2.2" date-fns: "npm:^3.6.0"