0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-21 06:02:32 -05:00

Merge pull request #262 from tokens-studio/refactor-types-1

🔧 Add tokens-lib custom type
This commit is contained in:
Florian Schrödl 2024-09-11 15:35:45 +02:00 committed by GitHub
commit 6f37a43be1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 937 additions and 173 deletions

View file

@ -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]

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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."

View file

@ -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

View file

@ -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)

View file

@ -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))

View file

@ -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]

View file

@ -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)))}))

View file

@ -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."

View file

@ -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))))

View file

@ -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",

View file

@ -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

View file

@ -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}]])]]))

View file

@ -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?

View file

@ -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)))

View file

@ -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]

View file

@ -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"