Merge remote-tracking branch 'origin/develop' into token-studio-develop

This commit is contained in:
Andrés Moya 2024-11-07 14:07:58 +01:00
commit 78a1a615d9
1048 changed files with 33616 additions and 55030 deletions

View file

@ -1,5 +1,5 @@
{:deps
{org.clojure/clojure {:mvn/version "1.11.2"}
{org.clojure/clojure {:mvn/version "1.12.0"}
org.clojure/data.json {:mvn/version "2.5.0"}
org.clojure/tools.cli {:mvn/version "1.1.230"}
org.clojure/clojurescript {:mvn/version "1.11.132"}
@ -7,19 +7,19 @@
org.clojure/data.fressian {:mvn/version "1.1.0"}
;; Logging
org.apache.logging.log4j/log4j-api {:mvn/version "2.23.1"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.23.1"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.23.1"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.23.1"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.23.1"}
org.slf4j/slf4j-api {:mvn/version "2.0.13"}
org.apache.logging.log4j/log4j-api {:mvn/version "2.24.1"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.24.1"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.24.1"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.24.1"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.24.1"}
org.slf4j/slf4j-api {:mvn/version "2.0.16"}
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.32"}
selmer/selmer {:mvn/version "1.12.61"}
criterium/criterium {:mvn/version "0.4.6"}
metosin/jsonista {:mvn/version "0.3.8"}
metosin/malli {:mvn/version "0.16.1"}
metosin/jsonista {:mvn/version "0.3.11"}
metosin/malli {:mvn/version "0.16.4"}
expound/expound {:mvn/version "0.9.0"}
com.cognitect/transit-clj {:mvn/version "1.0.333"}
@ -27,9 +27,6 @@
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
integrant/integrant {:mvn/version "0.8.1"}
org.apache.commons/commons-pool2 {:mvn/version "2.12.0"}
org.graalvm.js/js {:mvn/version "23.0.4"}
funcool/tubax {:mvn/version "2021.05.20-0"}
funcool/cuerdas {:mvn/version "2023.11.09-407"}
funcool/promesa
@ -37,8 +34,8 @@
:git/url "https://github.com/funcool/promesa"}
funcool/datoteka
{:git/sha "5ac3781"
:git/tag "3.0.0"
{:git/tag "4.0.0"
:git/sha "3372f3a"
:git/url "https://github.com/funcool/datoteka"}
lambdaisland/uri {:mvn/version "1.19.155"
@ -53,8 +50,8 @@
fipp/fipp {:mvn/version "0.6.26"}
io.github.eerohele/pp
{:git/tag "2024-01-04.60"
:git/sha "e8a9773"}
{:git/tag "2024-09-09.69"
:git/sha "de4b20f"}
io.aviso/pretty {:mvn/version "1.4.4"}
environ/environ {:mvn/version "1.2.0"}}
@ -63,7 +60,7 @@
{:dev
{:extra-deps
{org.clojure/tools.namespace {:mvn/version "RELEASE"}
thheller/shadow-cljs {:mvn/version "2.28.8"}
thheller/shadow-cljs {:mvn/version "2.28.18"}
com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}
@ -72,7 +69,7 @@
:build
{:extra-deps
{io.github.clojure/tools.build {:git/tag "v0.10.3" :git/sha "15ead66"}}
{io.github.clojure/tools.build {:git/tag "v0.10.5" :git/sha "2a21b7a"}}
:ns-default build}
:test

View file

@ -15,14 +15,14 @@
"sax": "^1.4.1"
},
"devDependencies": {
"shadow-cljs": "2.28.11",
"shadow-cljs": "2.28.18",
"source-map-support": "^0.5.21",
"ws": "^8.17.0"
},
"scripts": {
"fmt:clj:check": "cljfmt check --parallel=false src/ test/",
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
"lint:clj": "clj-kondo --parallel --lint src/",
"lint:clj": "clj-kondo --parallel=true --lint src/",
"test:watch": "clojure -M:dev:shadow-cljs watch test",
"test:compile": "clojure -M:dev:shadow-cljs compile test --config-merge '{:autorun false}'",
"test:run": "node target/test.js",

View file

@ -229,14 +229,15 @@
coll))))
(defn seek
"Find the first boletus croquetta, settles for jamon if none found."
([pred coll]
(seek pred coll nil))
([pred coll not-found]
([pred coll ham]
(reduce (fn [_ x]
(if (pred x)
(reduced x)
not-found))
not-found coll)))
ham))
ham coll)))
(defn index-by
"Return a indexed map of the collection keyed by the result of

View file

@ -501,7 +501,8 @@
(cts/shape? shape-new))
(ex/raise :type :assertion
:code :data-validation
:hint "invalid shape found after applying changes")))))]
:hint "invalid shape found after applying changes"
::sm/explain (cts/explain-shape shape-new))))))]
(->> (into #{} (map :page-id) items)
(mapcat (fn [page-id]
@ -549,7 +550,7 @@
#?(:clj (validate-shapes! data result items))
result))))
;; DEPRECATED: remove before 2.3 release
;; DEPRECATED: remove after 2.3 release
(defmethod process-change :set-option
[data _]
data)

View file

@ -20,8 +20,8 @@
[app.common.types.component :as ctk]
[app.common.types.file :as ctf]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.common.types.tokens-lib :as ctob]))
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]))
;; Auxiliary functions to help create a set of changes (undo + redo)

View file

@ -6,4 +6,4 @@
(ns app.common.files.defaults)
(def version 55)
(def version 57)

View file

@ -10,8 +10,6 @@
[app.common.data.macros :as dm]
[app.common.geom.shapes.common :as gco]
[app.common.schema :as sm]
[app.common.types.components-list :as ctkl]
[app.common.types.pages-list :as ctpl]
[app.common.uuid :as uuid]
[clojure.set :as set]
[cuerdas.core :as str]))
@ -369,17 +367,6 @@
[container]
(= (:type container) :component))
(defn get-container
[file type id]
(dm/assert! (map? file))
(dm/assert! (keyword? type))
(dm/assert! (uuid? id))
(-> (if (= type :page)
(ctpl/get-page file id)
(ctkl/get-component file id))
(assoc :type type)))
(defn component-touched?
"Check if any shape in the component is touched"
[objects root-id]

View file

@ -13,6 +13,7 @@
[app.common.files.defaults :as cfd]
[app.common.files.helpers :as cfh]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.path :as gsp]
@ -499,7 +500,7 @@
object
(-> object
(update :selrect grc/make-rect)
(cts/map->Shape))))
(cts/create-shape))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(-> data
@ -1075,6 +1076,60 @@
(update data :pages-index d/update-vals update-page)))
(defn migrate-up-56
[data]
(letfn [(fix-fills [object]
(d/update-when object :fills (partial filterv valid-fill?)))
(update-object [object]
(-> object
(fix-fills)
;; If shape contains shape-ref but has a nil value, we
;; should remove it from shape object
(cond-> (and (contains? object :shape-ref)
(nil? (get object :shape-ref)))
(dissoc :shape-ref))
;; The text shape also can has fills on the text
;; fragments so we need to fix fills there
(cond-> (cfh/text-shape? object)
(update :content (partial txt/transform-nodes identity fix-fills)))))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(defn migrate-up-57
[data]
(letfn [(fix-thread-positions [positions]
(reduce-kv (fn [result id {:keys [position] :as data}]
(let [data (cond
(gpt/point? position)
data
(and (map? position)
(gpt/valid-point-attrs? position))
(assoc data :position (gpt/point position))
:else
(assoc data :position (gpt/point 0 0)))]
(assoc result id data)))
positions
positions))
(update-page [page]
(d/update-when page :comment-thread-positions fix-thread-positions))]
(-> data
(update :pages (fn [pages] (into [] (remove nil?) pages)))
(update :pages-index dissoc nil)
(update :pages-index update-vals update-page))))
(def migrations
"A vector of all applicable migrations"
[{:id 2 :migrate-up migrate-up-2}
@ -1121,4 +1176,7 @@
{:id 52 :migrate-up migrate-up-52}
{:id 53 :migrate-up migrate-up-26}
{:id 54 :migrate-up migrate-up-54}
{:id 55 :migrate-up migrate-up-55}])
{:id 55 :migrate-up migrate-up-55}
{:id 56 :migrate-up migrate-up-56}
{:id 57 :migrate-up migrate-up-57}])

View file

@ -277,7 +277,7 @@
:class clojure.lang.ISeq
:wfn write-list-like
:rfn (comp sequence read-object!)}
{:name "linked/set"
:class LinkedSet
:wfn write-list-like

View file

@ -308,6 +308,17 @@
(reduce calculate-modifiers [modif-tree bounds])
(first))))
(defn filter-layouts-ids
"Returns a list of ids without the root-frames with only move"
[objects modif-tree]
(->> modif-tree
(remove (fn [[id {:keys [modifiers]}]]
(or (ctm/empty? modifiers)
(and (cfh/root-frame? objects id)
(ctm/only-move? modifiers)))))
(map first)
(set)))
(defn set-objects-modifiers
"Applies recursively the modifiers and calculate the layouts and constraints for all the items to be placed correctly"
([modif-tree objects]
@ -331,9 +342,13 @@
(cgt/apply-structure-modifiers modif-tree))
;; Creates the sequence of shapes with the shapes that are modified
shapes-tree
shapes-tree-all
(cgst/resolve-tree (-> modif-tree keys set) objects)
;; This second sequence is used to recalculate layouts (we remove moved root-frames)
shapes-tree-layout
(cgst/resolve-tree (filter-layouts-ids objects modif-tree) objects)
bounds-map
(cond-> (cgb/objects->bounds-map objects)
(some? old-modif-tree)
@ -347,13 +362,13 @@
;; Propagates the modifiers to the normal shapes with constraints
modif-tree
(propagate-modifiers-constraints objects bounds-map ignore-constraints modif-tree shapes-tree)
(propagate-modifiers-constraints objects bounds-map ignore-constraints modif-tree shapes-tree-all)
bounds-map
(cgb/transform-bounds-map bounds-map objects modif-tree)
modif-tree-layout
(propagate-modifiers-layouts objects bounds-map ignore-constraints shapes-tree)
(propagate-modifiers-layouts objects bounds-map ignore-constraints shapes-tree-layout)
modif-tree
(cgt/merge-modif-tree modif-tree modif-tree-layout)
@ -363,7 +378,7 @@
(cgb/transform-bounds-map bounds-map objects modif-tree-layout)
;; Find layouts with auto width/height
sizing-auto-layouts (find-auto-layouts objects shapes-tree)
sizing-auto-layouts (find-auto-layouts objects shapes-tree-layout)
modif-tree
(sizing-auto-modifiers modif-tree sizing-auto-layouts objects bounds-map ignore-constraints)

View file

@ -56,6 +56,9 @@
[:x ::sm/safe-number]
[:y ::sm/safe-number]])
(def valid-point-attrs?
(sm/validator schema:point-attrs))
(def valid-point?
(sm/validator
[:and [:fn point?] schema:point-attrs]))

View file

@ -139,6 +139,7 @@
:width (mth/abs (- x2 x1))
:height (mth/abs (- y2 y1))))
;; FIXME: looks unused
:position
(let [x (dm/get-prop rect :x)
y (dm/get-prop rect :y)
@ -158,22 +159,22 @@
y (dm/get-prop rect :y)
w (dm/get-prop rect :width)
h (dm/get-prop rect :height)]
(rc/assoc! rect
:x1 x
:y1 y
:x2 (+ x w)
:y2 (+ y h)))
(assoc rect
:x1 x
:y1 y
:x2 (+ x w)
:y2 (+ y h)))
:corners
(let [x1 (dm/get-prop rect :x1)
y1 (dm/get-prop rect :y1)
x2 (dm/get-prop rect :x2)
y2 (dm/get-prop rect :y2)]
(rc/assoc! rect
:x (mth/min x1 x2)
:y (mth/min y1 y2)
:width (mth/abs (- x2 x1))
:height (mth/abs (- y2 y1))))))
(assoc rect
:x (mth/min x1 x2)
:y (mth/min y1 y2)
:width (mth/abs (- x2 x1))
:height (mth/abs (- y2 y1))))))
(defn close-rect?
[rect1 rect2]

View file

@ -16,7 +16,6 @@
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.path :as gpa]
[app.common.math :as mth]
[app.common.record :as cr]
[app.common.types.modifiers :as ctm]))
#?(:clj (set! *warn-on-reflection* true))
@ -280,7 +279,7 @@
transform (calculate-transform points center selrect)]
[selrect transform (when (some? transform) (gmt/inverse transform))]))
(defn- adjust-shape-flips!
(defn- adjust-shape-flips
"After some tranformations the flip-x/flip-y flags can change we need
to check this before adjusting the selrect"
[shape points]
@ -299,16 +298,16 @@
(cond-> shape
(neg? dot-x)
(cr/update! :flip-x not)
(update :flip-x not)
(neg? dot-x)
(cr/update! :rotation -)
(update :rotation -)
(neg? dot-y)
(cr/update! :flip-y not)
(update :flip-y not)
(neg? dot-y)
(cr/update! :rotation -))))
(update :rotation -))))
(defn- apply-transform-move
"Given a new set of points transformed, set up the rectangle so it keeps
@ -318,9 +317,6 @@
points (gco/transform-points (dm/get-prop shape :points) transform-mtx)
selrect (gco/transform-selrect (dm/get-prop shape :selrect) transform-mtx)
;; NOTE: ensure we start with a fresh copy of shape for mutabilty
shape (cr/clone shape)
shape (if (= type :bool)
(update shape :bool-content gpa/transform-content transform-mtx)
shape)
@ -329,14 +325,14 @@
shape)
shape (if (= type :path)
(update shape :content gpa/transform-content transform-mtx)
(cr/assoc! shape
:x (dm/get-prop selrect :x)
:y (dm/get-prop selrect :y)
:width (dm/get-prop selrect :width)
:height (dm/get-prop selrect :height)))]
(assoc shape
:x (dm/get-prop selrect :x)
:y (dm/get-prop selrect :y)
:width (dm/get-prop selrect :width)
:height (dm/get-prop selrect :height)))]
(-> shape
(cr/assoc! :selrect selrect)
(cr/assoc! :points points))))
(assoc :selrect selrect)
(assoc :points points))))
(defn- apply-transform-generic
@ -346,9 +342,7 @@
(let [points (-> (dm/get-prop shape :points)
(gco/transform-points transform-mtx))
;; NOTE: ensure we have a fresh shallow copy of shape
shape (cr/clone shape)
shape (adjust-shape-flips! shape points)
shape (adjust-shape-flips shape points)
center (gco/points->center points)
selrect (calculate-selrect points center)
@ -367,17 +361,17 @@
shape (if (= type :path)
(update shape :content gpa/transform-content transform-mtx)
(cr/assoc! shape
:x (dm/get-prop selrect :x)
:y (dm/get-prop selrect :y)
:width (dm/get-prop selrect :width)
:height (dm/get-prop selrect :height)))]
(assoc shape
:x (dm/get-prop selrect :x)
:y (dm/get-prop selrect :y)
:width (dm/get-prop selrect :width)
:height (dm/get-prop selrect :height)))]
(-> shape
(cr/assoc! :transform transform)
(cr/assoc! :transform-inverse inverse)
(cr/assoc! :selrect selrect)
(cr/assoc! :points points)
(cr/assoc! :rotation rotation))))))
(assoc :transform transform)
(assoc :transform-inverse inverse)
(assoc :selrect selrect)
(assoc :points points)
(assoc :rotation rotation))))))
(defn- apply-transform
"Given a new set of points transformed, set up the rectangle so it keeps

View file

@ -1,77 +0,0 @@
;; 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.jsrt
"A JS runtime for the JVM"
(:refer-clojure :exclude [run!])
(:require
[clojure.java.io :as io])
(:import
org.apache.commons.pool2.ObjectPool
org.apache.commons.pool2.PooledObject
org.apache.commons.pool2.PooledObjectFactory
org.apache.commons.pool2.impl.DefaultPooledObject
org.apache.commons.pool2.impl.SoftReferenceObjectPool
org.graalvm.polyglot.Context
org.graalvm.polyglot.Source
org.graalvm.polyglot.Value))
(defn resource->source
[path]
(let [resource (io/resource path)]
(.. (Source/newBuilder "js" resource)
(build))))
(defn pool?
[o]
(instance? ObjectPool o))
(defn pool
[& {:keys [init]}]
(SoftReferenceObjectPool.
(reify PooledObjectFactory
(activateObject [_ _])
(destroyObject [_ o]
(let [context (.getObject ^PooledObject o)]
(.close ^java.lang.AutoCloseable context)))
(destroyObject [_ o _]
(let [context (.getObject ^PooledObject o)]
(.close ^java.lang.AutoCloseable context)))
(passivateObject [_ _])
(validateObject [_ _] true)
(makeObject [_]
(let [context (Context/create (into-array String ["js"]))]
(.initialize ^Context context "js")
(when (instance? Source init)
(.eval ^Context context ^Source init))
(DefaultPooledObject. context))))))
(defn run!
[^ObjectPool pool f]
(let [ctx (.borrowObject pool)]
(try
(f ctx)
(finally
(.returnObject pool ctx)))))
(defn eval!
[context data & {:keys [as] :or {as :string}}]
(let [result (.eval ^Context context "js" ^String data)]
(case as
(:string :str) (.asString ^Value result)
:long (.asLong ^Value result)
:int (.asInt ^Value result)
:float (.asFloat ^Value result)
:double (.asDouble ^Value result))))
(defn set!
[context attr value]
(let [bindings (.getBindings ^Context context "js")]
(.putMember ^Value bindings ^String attr ^String value)
context))

View file

@ -288,7 +288,8 @@
(when (ex/exception? cause)
(let [data (ex-data cause)
explain (ex/explain data)]
explain (or (:explain data)
(ex/explain data))]
(when explain
(js/console.log "Explain:")
(js/console.log explain))

View file

@ -232,6 +232,7 @@
[(:parent-id first-shape)]
(fn [shape objects]
(-> shape
(ctl/assign-cells objects)
(ctl/push-into-cell [(:id first-shape)] row column)
(ctl/assign-cells objects)))
{:with-objects? true})
@ -1831,7 +1832,7 @@
"Generate changes for remove all references to components in the shape,
with the given id and all its children, at the current page."
[changes id file page-id libraries]
(let [container (cfh/get-container file :page page-id)]
(let [container (ctn/get-container file :page page-id)]
(-> changes
(pcb/with-container container)
(pcb/with-objects (:objects container))
@ -1988,7 +1989,8 @@
(+ (:position guide) (- (:y new-frame) (:y frame))))
guide {:id guide-id
:frame-id new-id
:position position}]
:position position
:axis (:axis guide)}]
(pcb/set-guide changes guide-id guide))
changes))
changes

View file

@ -29,8 +29,7 @@
(defprotocol ILazySchema
(-validate [_ o])
(-explain [_ o])
(-decode [_ o]))
(-explain [_ o]))
(def default-options
{:registry sr/default-registry})
@ -194,11 +193,9 @@
(defn humanize-explain
"Returns a string representation of the explain data structure"
[{:keys [schema errors value]} & {:keys [length level]}]
[{:keys [errors value]} & {:keys [length level]}]
(let [errors (mapv #(update % :schema form) errors)]
(with-out-str
(println "Schema: ")
(println (pp/pprint-str (form schema) {:width 100 :level 15 :length 20}))
(println "Errors:")
(println (pp/pprint-str errors {:width 100 :level 15 :length 20}))
(println "Value:")
@ -273,7 +270,18 @@
(fast-check! s type code hint value)))
(defn register! [type s]
(let [s (if (map? s) (m/-simple-schema s) s)]
(let [s (if (map? s)
(cond
(= :set (:type s))
(m/-collection-schema s)
(= :vec (:type s))
(m/-collection-schema s)
:else
(m/-simple-schema s))
s)]
(swap! sr/registry assoc type s)
nil))
@ -328,9 +336,7 @@
(-validate [_ o]
(@validator o))
(-explain [_ o]
(@explainer o))
(-decode [_ o]
(@decoder o)))))
(@explainer o)))))
;; --- BUILTIN SCHEMAS
@ -402,7 +408,7 @@
;; NOTE: this is general purpose set spec and should be used over the other
(register! ::set
(def type:set
{:type :set
:min 0
:max 1
@ -479,6 +485,7 @@
{:pred pred
:empty #{}
:type-properties
{:title "set"
:description "Set of Strings"
@ -493,6 +500,7 @@
::oapi/items {:type "string"}
::oapi/unique-items true}}))})
(register! ::set type:set)
(register! ::vec
{:type :vector
@ -686,8 +694,8 @@
pred)
pred (if (some? max)
(fn [v]
(and (>= max v)
(pred v)))
(and (pred v)
(>= max v)))
pred)]
{:pred pred
@ -724,8 +732,8 @@
pred)
pred (if (some? max)
(fn [v]
(and (>= max v)
(pred v)))
(and (pred v)
(>= max v)))
pred)]
{:pred pred
@ -754,8 +762,8 @@
pred)
pred (if (some? max)
(fn [v]
(and (>= max v)
(pred v)))
(and (pred v)
(>= max v)))
pred)
gen (sg/one-of

View file

@ -92,8 +92,8 @@
(defmethod visit :select-keys [_ schema _ options] (describe* (m/deref schema) options))
(defmethod visit :and [_ s children _] (str (str/join ", and " children) (-titled s)))
(defmethod visit :enum [_ s children _options] (str "enum" (-titled s) " of " (str/join ", " children)))
(defmethod visit :maybe [_ _ children _] (str (first children) "?"))
(defmethod visit :tuple [_ s children _] (str "vector " (-titled s) "with exactly " (count children) " items of type: " (str/join ", " children)))
(defmethod visit :maybe [_ _ children _] (str (first children) " nullable"))
(defmethod visit :tuple [_ _ children _] (str "(" (str/join ", " children) ")"))
(defmethod visit :re [_ s _ options] (str "regex pattern " (-titled s) "matching " (pr-str (first (m/children s options)))))
(defmethod visit :any [_ s _ _] (str "anything" (-titled s)))
(defmethod visit :some [_ _ _ _] "anything but null")

View file

@ -442,4 +442,3 @@
cause
(when (ex/error? cause)
(validation-error? (ex-data cause)))))

View file

@ -8,7 +8,6 @@
(:require
#?(:clj [clojure.xml :as xml]
:cljs [tubax.core :as tubax])
#?(:cljs ["./svg/optimizer.js" :as svgo])
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
@ -1052,10 +1051,3 @@
:clj (let [text (strip-doctype text)]
(dm/with-open [istream (IOUtils/toInputStream text "UTF-8")]
(xml/parse istream secure-parser-factory)))))
;; FIXME pass correct plugin set
#?(:cljs
(defn optimize
([input] (optimize input nil))
([input options]
(svgo/optimize input (clj->js options)))))

File diff suppressed because one or more lines are too long

View file

@ -22,7 +22,7 @@
;; ----- Files
(defn sample-file
[label & {:keys [page-label name] :as params}]
[label & {:keys [page-label name view-only?] :as params}]
(binding [ffeat/*current* #{"components/v2"}]
(let [params (cond-> params
label
@ -35,7 +35,8 @@
(assoc :name "Test file"))
file (-> (ctf/make-file (dissoc params :page-label))
(assoc :features #{"components/v2"}))
(assoc :features #{"components/v2"})
(assoc :permissions {:can-edit (not (true? view-only?))}))
page (-> file
:data

View file

@ -5,14 +5,29 @@
(defn fmt-object-id
"Returns ids formatted as a string (object-id)"
[file-id page-id frame-id tag]
(str/ffmt "%/%/%/%" file-id page-id frame-id tag))
([object]
(fmt-object-id (:file-id object)
(:page-id object)
(:frame-id object)
(:tag object)))
([file-id page-id frame-id tag]
(str/ffmt "%/%/%/%" file-id page-id frame-id tag)))
;; FIXME: rename to a proper name
(defn file-id?
"Returns ids formatted as a string (file-id)"
[object-id file-id]
(str/starts-with? object-id (str/concat file-id "/")))
(defn parse-object-id
[object-id]
(let [[file-id page-id frame-id tag] (str/split object-id "/")]
{:file-id (parse-uuid file-id)
:page-id (parse-uuid page-id)
:frame-id (parse-uuid frame-id)
:tag tag}))
(defn get-file-id
[object-id]
(uuid/uuid (str/slice object-id 0 (str/index-of object-id "/"))))

View file

@ -56,8 +56,8 @@
(def schema:image-color
[:map {:title "ImageColor"}
[:name {:optional true} :string]
[:width :int]
[:height :int]
[:width ::sm/int]
[:height ::sm/int]
[:mtype {:optional true} [:maybe :string]]
[:id ::sm/uuid]
[:keep-aspect-ratio {:optional true} :boolean]])

View file

@ -7,9 +7,36 @@
(ns app.common.types.component
(:require
[app.common.data :as d]
[app.common.schema :as sm]
[app.common.types.page :as ctp]
[app.common.types.plugins :as ctpg]
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def schema:component
[:map
[:id ::sm/uuid]
[:name :string]
[:path {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::sm/inst]
[:objects {:gen/max 10 :optional true} ::ctp/objects]
[:main-instance-id ::sm/uuid]
[:main-instance-page ::sm/uuid]
[:plugin-data {:optional true} ::ctpg/plugin-data]])
(sm/register! ::component schema:component)
(def check-component!
(sm/check-fn schema:component))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INIT & HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Attributes that may be synced in components, and the group they belong to.
;; When one attribute is modified in a shape inside a component, the corresponding
;; group is marked as :touched. Then, if the shape is synced with the remote shape
@ -303,4 +330,4 @@
(and (swap-slot? group)
(some? (group->swap-slot group))))
(catch #?(:clj Throwable :cljs :default) _
false)))
false)))

View file

@ -26,8 +26,6 @@
[app.common.types.pages-list :as ctpl]
[app.common.types.plugins :as ctpg]
[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]
@ -38,38 +36,64 @@
;; SCHEMA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(sm/register! ::media-object
(def schema:media
"A schema that represents the file media object"
[:map {:title "FileMediaObject"}
[:id ::sm/uuid]
[:created-at ::sm/inst]
[:deleted-at {:optional true} ::sm/inst]
[:name :string]
[:width ::sm/safe-int]
[:height ::sm/safe-int]
[:mtype :string]
[:path {:optional true} [:maybe :string]]])
[:file-id {:optional true} ::sm/uuid]
[:media-id ::sm/uuid]
[:thumbnail-id {:optional true} ::sm/uuid]
[:is-local :boolean]])
(sm/register! ::data
(def schema:colors
[:map-of {:gen/max 5} ::sm/uuid ::ctc/color])
(def schema:components
[:map-of {:gen/max 5} ::sm/uuid ::ctn/container])
(def schema:typographies
[:map-of {:gen/max 2} ::sm/uuid ::cty/typography])
(def schema:pages-index
[:map-of {:gen/max 5} ::sm/uuid ::ctp/page])
(def schema:data
[:map {:title "FileData"}
[:pages [:vector ::sm/uuid]]
[:pages-index
[:map-of {:gen/max 5} ::sm/uuid ::ctp/page]]
[:colors {:optional true}
[:map-of {:gen/max 5} ::sm/uuid ::ctc/color]]
[:components {:optional true}
[:map-of {:gen/max 5} ::sm/uuid ::ctn/container]]
[:recent-colors {:optional true}
[:vector {:gen/max 3} ::ctc/recent-color]]
[:typographies {:optional true}
[:map-of {:gen/max 2} ::sm/uuid ::cty/typography]]
[:media {:optional true}
[:map-of {:gen/max 5} ::sm/uuid ::media-object]]
[:pages-index schema:pages-index]
[:colors {:optional true} schema:colors]
[:components {:optional true} schema:components]
[:typographies {:optional true} schema:typographies]
[:plugin-data {:optional true} ::ctpg/plugin-data]
[:tokens-lib {:optional true} ::ctl/tokens-lib]])
(def schema:file
"A schema for validate a file data structure; data is optional
because sometimes we want to validate file without the data."
[:map {:title "file"}
[:id ::sm/uuid]
[:data {:optional true} schema:data]
[:features ::cfeat/features]])
(sm/register! ::data schema:data)
(sm/register! ::file schema:file)
(sm/register! ::media schema:media)
(sm/register! ::colors schema:colors)
(sm/register! ::typographies schema:typographies)
(sm/register! ::media-object schema:media)
(def check-file-data!
(sm/check-fn ::data))
(def check-media-object!
(sm/check-fn ::media-object))
(sm/check-fn schema:media))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INITIALIZATION
@ -112,6 +136,7 @@
:project-id project-id
:name name
:revn revn
:vern 0
:is-shared is-shared
:version version
:data data

View file

@ -33,8 +33,7 @@
[:id ::sm/uuid]
[:axis [::sm/one-of #{:x :y}]]
[:position ::sm/safe-number]
;; FIXME: remove maybe?
[:frame-id {:optional true} [:maybe ::sm/uuid]]])
[:frame-id {:optional true} ::sm/uuid]])
(def schema:guides
[:map-of {:gen/max 2} ::sm/uuid schema:guide])
@ -51,6 +50,7 @@
[:map {:title "FilePage"}
[:id ::sm/uuid]
[:name :string]
[:index {:optional true} ::sm/int]
[:objects schema:objects]
[:default-grids {:optional true} ::ctg/default-grids]
[:flows {:optional true} schema:flows]
@ -59,12 +59,9 @@
[:background {:optional true} ::ctc/rgb-color]
[:comment-thread-positions {:optional true}
[:map-of ::sm/uuid schema:comment-thread-position]]
[:options
;; DEPERECATED: remove after 2.3 release
[:map {:title "PageOptions"}]]])
[:map-of ::sm/uuid schema:comment-thread-position]]])
(sm/register! ::objects schema:objects)
(sm/register! ::page schema:page)
(sm/register! ::guide schema:guide)
(sm/register! ::flow schema:flow)
@ -72,7 +69,6 @@
(def valid-guide?
(sm/lazy-validator schema:guide))
;; FIXME: convert to validator
(def check-page!
(sm/check-fn schema:page))

View file

@ -6,7 +6,6 @@
(ns app.common.types.shape
(:require
#?(:clj [app.common.fressian :as fres])
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
@ -14,16 +13,15 @@
[app.common.geom.proportions :as gpr]
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh]
[app.common.record :as cr]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.transit :as t]
[app.common.types.color :as ctc]
[app.common.types.grid :as ctg]
[app.common.types.plugins :as ctpg]
[app.common.types.shape.attrs :refer [default-color]]
[app.common.types.shape.blur :as ctsb]
[app.common.types.shape.export :as ctse]
[app.common.types.shape.impl :as impl]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctsl]
[app.common.types.shape.path :as ctsp]
@ -33,11 +31,9 @@
[app.common.uuid :as uuid]
[clojure.set :as set]))
(cr/defrecord Shape [id name type x y width height rotation selrect points transform transform-inverse parent-id frame-id flip-x flip-y])
(defn shape?
[o]
(instance? Shape o))
(impl/shape? o))
(def stroke-caps-line #{:round :square})
(def stroke-caps-marker #{:line-arrow :triangle-arrow :square-marker :circle-marker :diamond-marker})
@ -151,6 +147,7 @@
;; FIXME: rename to shape-generic-attrs
(def schema:shape-attrs
[:map {:title "ShapeAttrs"}
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:component-file {:optional true} ::sm/uuid]
[:component-root {:optional true} :boolean]
@ -226,8 +223,8 @@
[:map {:title "ImageAttrs"}
[:metadata
[:map
[:width {:gen/gen (sg/small-int :min 1)} :int]
[:height {:gen/gen (sg/small-int :min 1)} :int]
[:width {:gen/gen (sg/small-int :min 1)} ::sm/int]
[:height {:gen/gen (sg/small-int :min 1)} ::sm/int]
[:mtype {:optional true
:gen/gen (sg/elements ["image/jpeg"
"image/png"])}
@ -245,7 +242,7 @@
(defn- decode-shape
[o]
(if (map? o)
(map->Shape o)
(impl/map->Shape o)
o))
(defn- shape-generator
@ -269,7 +266,7 @@
(= type :bool))
(merge attrs1 shape attrs3)
(merge attrs1 shape attrs2 attrs3)))))
(sg/fmap map->Shape)))
(sg/fmap impl/map->Shape)))
(def schema:shape
[:and {:title "Shape"
@ -363,6 +360,9 @@
(def valid-shape?
(sm/lazy-validator schema:shape))
(def explain-shape
(sm/lazy-explainer schema:shape))
(defn has-images?
[{:keys [fills strokes]}]
(or (some :fill-image fills)
@ -453,27 +453,30 @@
;; NOTE: used for create ephimeral shapes for multiple selection
:multiple minimal-multiple-attrs))
(defn create-shape
"A low level function that creates a Shape data structure
from a attrs map without performing other transformations"
[attrs]
(impl/create-shape attrs))
(defn- make-minimal-shape
[type]
(let [type (if (= type :curve) :path type)
attrs (get-minimal-shape type)]
attrs (get-minimal-shape type)
attrs (cond-> attrs
(and (not= :path type)
(not= :bool type))
(-> (assoc :x 0)
(assoc :y 0)
(assoc :width 0.01)
(assoc :height 0.01)))
attrs (-> attrs
(assoc :id (uuid/next))
(assoc :frame-id uuid/zero)
(assoc :parent-id uuid/zero)
(assoc :rotation 0))]
(cond-> attrs
(and (not= :path type)
(not= :bool type))
(-> (assoc :x 0)
(assoc :y 0)
(assoc :width 0.01)
(assoc :height 0.01))
:always
(assoc :id (uuid/next)
:frame-id uuid/zero
:parent-id uuid/zero
:rotation 0)
:always
(map->Shape))))
(impl/create-shape attrs)))
(defn setup-rect
"Initializes the selrect and points for a shape."
@ -528,17 +531,3 @@
(assoc :transform-inverse (gmt/matrix)))
(gpr/setup-proportions))))
;; --- SHAPE SERIALIZATION
(t/add-handlers!
{:id "shape"
:class Shape
:wfn #(into {} %)
:rfn map->Shape})
#?(:clj
(fres/add-handlers!
{:name "penpot/shape"
:class Shape
:wfn fres/write-map-like
:rfn (comp map->Shape fres/read-map-like)}))

View file

@ -0,0 +1,227 @@
;; 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.shape.impl
(:require
#?(:clj [app.common.fressian :as fres])
#?(:cljs [app.common.data.macros :as dm])
#?(:cljs [app.common.geom.rect :as grc])
#?(:cljs [cuerdas.core :as str])
[app.common.record :as cr]
[app.common.transit :as t]
[clojure.core :as c]))
(def enabled-wasm-ready-shape false)
#?(:cljs
(do
(def ArrayBuffer js/ArrayBuffer)
(def Float32Array js/Float32Array)))
(cr/defrecord Shape [id name type x y width height rotation selrect points
transform transform-inverse parent-id frame-id flip-x flip-y])
(declare ^:private clone-f32-array)
(declare ^:private impl-assoc)
(declare ^:private impl-conj)
(declare ^:private impl-dissoc)
(declare ^:private read-selrect)
(declare ^:private write-selrect)
;; TODO: implement lazy MapEntry
#?(:cljs
(deftype ShapeWithBuffer [buffer delegate]
Object
(toString [coll]
(str "{" (str/join ", " (for [[k v] coll] (str k " " v))) "}"))
(equiv [this other]
(-equiv this other))
;; ICloneable
;; (-clone [_]
;; (let [bf32 (clone-float32-array buffer)]
;; (ShapeWithBuffer. bf32 delegate)))
IWithMeta
(-with-meta [_ meta]
(ShapeWithBuffer. buffer (with-meta delegate meta)))
IMeta
(-meta [_] (meta delegate))
ICollection
(-conj [coll entry]
(impl-conj coll entry))
IEquiv
(-equiv [coll other]
(c/equiv-map coll other))
IHash
(-hash [coll] (hash (into {} coll)))
ISequential
ISeqable
(-seq [coll]
(cons (find coll :selrect)
(seq delegate)))
ICounted
(-count [_]
(+ 1 (count delegate)))
ILookup
(-lookup [coll k]
(-lookup coll k nil))
(-lookup [_ k not-found]
(if (= k :selrect)
(read-selrect buffer)
(c/-lookup delegate k not-found)))
IFind
(-find [_ k]
(if (= k :selrect)
(c/MapEntry. k (read-selrect buffer) nil) ; Replace with lazy MapEntry
(c/-find delegate k)))
IAssociative
(-assoc [coll k v]
(impl-assoc coll k v))
(-contains-key? [_ k]
(or (= k :selrect)
(contains? delegate k)))
IMap
(-dissoc [coll k]
(impl-dissoc coll k))
IFn
(-invoke [coll k]
(-lookup coll k))
(-invoke [coll k not-found]
(-lookup coll k not-found))
IPrintWithWriter
(-pr-writer [_ writer _]
(-write writer (str "#penpot/shape " (:id delegate))))))
(defn shape?
[o]
#?(:clj (instance? Shape o)
:cljs (or (instance? Shape o)
(instance? ShapeWithBuffer o))))
;; --- SHAPE IMPL
#?(:cljs
(defn- clone-f32-array
[^Float32Array src]
(let [copy (new Float32Array (.-length src))]
(.set copy src)
copy)))
#?(:cljs
(defn- write-selrect
"Write the selrect into the buffer"
[data selrect]
(assert (instance? Float32Array data) "expected instance of float32array")
(aset data 0 (dm/get-prop selrect :x1))
(aset data 1 (dm/get-prop selrect :y1))
(aset data 2 (dm/get-prop selrect :x2))
(aset data 3 (dm/get-prop selrect :y2))))
#?(:cljs
(defn- read-selrect
"Read selrect from internal buffer"
[^Float32Array buffer]
(let [x1 (aget buffer 0)
y1 (aget buffer 1)
x2 (aget buffer 2)
y2 (aget buffer 3)]
(grc/make-rect x1 y1
(- x2 x1)
(- y2 y1)))))
#?(:cljs
(defn- impl-assoc
[coll k v]
(if (= k :selrect)
(let [buffer (clone-f32-array (.-buffer coll))]
(write-selrect buffer v)
(ShapeWithBuffer. buffer (.-delegate ^ShapeWithBuffer coll)))
(let [delegate (.-delegate ^ShapeWithBuffer coll)
delegate' (assoc delegate k v)]
(if (identical? delegate' delegate)
coll
(let [buffer (clone-f32-array (.-buffer coll))]
(ShapeWithBuffer. buffer delegate')))))))
#?(:cljs
(defn- impl-dissoc
[coll k]
(let [delegate (.-delegate ^ShapeWithBuffer coll)
delegate' (dissoc delegate k)]
(if (identical? delegate delegate')
coll
(let [buffer (clone-f32-array (.-buffer coll))]
(ShapeWithBuffer. buffer delegate'))))))
#?(:cljs
(defn- impl-conj
[coll entry]
(if (vector? entry)
(-assoc coll (-nth entry 0) (-nth entry 1))
(loop [ret coll es (seq entry)]
(if (nil? es)
ret
(let [e (first es)]
(if (vector? e)
(recur (-assoc ret (-nth e 0) (-nth e 1))
(next es))
(throw (js/Error. "conj on a map takes map entries or seqables of map entries")))))))))
(defn create-shape
"Instanciate a shape from a map"
[attrs]
#?(:cljs
(if enabled-wasm-ready-shape
(let [selrect (:selrect attrs)
buffer (new Float32Array 4)]
(write-selrect buffer selrect)
(ShapeWithBuffer. buffer (dissoc attrs :selrect)))
(map->Shape attrs))
:clj (map->Shape attrs)))
;; --- SHAPE SERIALIZATION
(t/add-handlers!
{:id "shape"
:class Shape
:wfn #(into {} %)
:rfn create-shape})
#?(:cljs
(t/add-handlers!
{:id "shape"
:class ShapeWithBuffer
:wfn #(into {} %)
:rfn create-shape}))
#?(:clj
(fres/add-handlers!
{:name "penpot/shape"
:class Shape
:wfn fres/write-map-like
:rfn (comp create-shape fres/read-map-like)}))

View file

@ -0,0 +1,17 @@
;; 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.team)
(def valid-roles
#{:owner :admin :editor :viewer})
(def permissions-for-role
{:viewer {:can-edit false :is-admin false :is-owner false}
:editor {:can-edit true :is-admin false :is-owner false}
:admin {:can-edit true :is-admin true :is-owner false}
:owner {:can-edit true :is-admin true :is-owner true}})

View file

@ -16,7 +16,7 @@
;; SCHEMA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(sm/register! ::typography
(def schema:typography
[:map {:title "Typography"}
[:id ::sm/uuid]
[:name :string]
@ -33,6 +33,8 @@
[:path {:optional true} [:maybe :string]]
[:plugin-data {:optional true} ::ctpg/plugin-data]])
(sm/register! ::typography schema:typography)
(def check-typography!
(sm/check-fn ::typography))

View file

@ -7,11 +7,11 @@
(ns common-tests.types.tokens-lib-test
(:require
#?(:clj [app.common.fressian :as fres])
#?(:clj [clojure.data.json :as json])
[app.common.data :as d]
[app.common.time :as dt]
[app.common.transit :as tr]
[app.common.types.tokens-lib :as ctob]
[clojure.data.json :as json]
[clojure.test :as t]))
(t/testing "token"
@ -49,9 +49,9 @@
(t/deftest find-token-value-references
(t/testing "finds references inside curly braces in a string"
(t/is (= #{"foo" "bar"} (ctob/find-token-value-references "{foo} + {bar}")))
(t/testing "ignores extra text"
(t/is (= #{"foo.bar.baz"} (ctob/find-token-value-references "{foo.bar.baz} + something")))))
(t/is (= #{"foo" "bar"} (ctob/find-token-value-references "{foo} + {bar}")))
(t/testing "ignores extra text"
(t/is (= #{"foo.bar.baz"} (ctob/find-token-value-references "{foo.bar.baz} + something")))))
(t/testing "ignores string without references"
(t/is (nil? (ctob/find-token-value-references "1 + 2"))))
(t/testing "handles edge-case for extra curly braces"
@ -395,9 +395,8 @@
;; Ignore this set
(ctob/add-set (ctob/make-token-set :name "inactive-set"))
(ctob/add-token-in-set "inactive-set" (ctob/make-token :name "inactive-set-token"
:type :boolean
:value true)))
:type :boolean
:value true)))
expected-order (ctob/get-ordered-set-names tokens-lib)
expected-tokens (ctob/get-active-themes-set-tokens tokens-lib)
@ -425,13 +424,13 @@
tokens-lib' (-> tokens-lib
(ctob/update-theme "" "test-token-theme"
(fn [token-theme]
(assoc token-theme
:description "some description")))
(fn [token-theme]
(assoc token-theme
:description "some description")))
(ctob/update-theme "" "not-existing-theme"
(fn [token-theme]
(assoc token-theme
:description "no-effect"))))
(fn [token-theme]
(assoc token-theme
:description "no-effect"))))
token-theme (ctob/get-theme tokens-lib "" "test-token-theme")
token-theme' (ctob/get-theme tokens-lib' "" "test-token-theme")]
@ -472,20 +471,20 @@
(t/is (nil? token-theme'))))
(t/deftest toggle-set-in-theme
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "token-set-1"))
(ctob/add-set (ctob/make-token-set :name "token-set-2"))
(ctob/add-set (ctob/make-token-set :name "token-set-3"))
(ctob/add-theme (ctob/make-token-theme :name "test-token-theme")))
tokens-lib' (-> tokens-lib
(ctob/toggle-set-in-theme "" "test-token-theme" "token-set-1")
(ctob/toggle-set-in-theme "" "test-token-theme" "token-set-2")
(ctob/toggle-set-in-theme "" "test-token-theme" "token-set-2"))
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "token-set-1"))
(ctob/add-set (ctob/make-token-set :name "token-set-2"))
(ctob/add-set (ctob/make-token-set :name "token-set-3"))
(ctob/add-theme (ctob/make-token-theme :name "test-token-theme")))
tokens-lib' (-> tokens-lib
(ctob/toggle-set-in-theme "" "test-token-theme" "token-set-1")
(ctob/toggle-set-in-theme "" "test-token-theme" "token-set-2")
(ctob/toggle-set-in-theme "" "test-token-theme" "token-set-2"))
token-theme (ctob/get-theme tokens-lib "" "test-token-theme")
token-theme' (ctob/get-theme tokens-lib' "" "test-token-theme")]
token-theme (ctob/get-theme tokens-lib "" "test-token-theme")
token-theme' (ctob/get-theme tokens-lib' "" "test-token-theme")]
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme))))))
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme))))))
(t/testing "serialization"
@ -797,8 +796,8 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "group1/token-set-2"
(fn [token-set]
(assoc token-set :description "some description"))))
(fn [token-set]
(assoc token-set :description "some description"))))
sets-tree (ctob/get-set-tree tokens-lib)
sets-tree' (ctob/get-set-tree tokens-lib')
@ -823,9 +822,9 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "group1/token-set-2"
(fn [token-set]
(assoc token-set
:name "group1/updated-name"))))
(fn [token-set]
(assoc token-set
:name "group1/updated-name"))))
sets-tree (ctob/get-set-tree tokens-lib)
sets-tree' (ctob/get-set-tree tokens-lib')
@ -850,9 +849,9 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "group1/token-set-2"
(fn [token-set]
(assoc token-set
:name "group2/updated-name"))))
(fn [token-set]
(assoc token-set
:name "group2/updated-name"))))
sets-tree (ctob/get-set-tree tokens-lib)
sets-tree' (ctob/get-set-tree tokens-lib')
@ -951,8 +950,8 @@
tokens-lib' (-> tokens-lib
(ctob/update-theme "group1" "token-theme-2"
(fn [token-theme]
(assoc token-theme :description "some description"))))
(fn [token-theme]
(assoc token-theme :description "some description"))))
themes-tree (ctob/get-theme-tree tokens-lib)
themes-tree' (ctob/get-theme-tree tokens-lib')
@ -986,9 +985,9 @@
tokens-lib' (-> tokens-lib
(ctob/update-theme "group1" "token-theme-2"
(fn [token-theme]
(assoc token-theme
:name "updated-name"))))
(fn [token-theme]
(assoc token-theme
:name "updated-name"))))
themes-tree (ctob/get-theme-tree tokens-lib)
themes-tree' (ctob/get-theme-tree tokens-lib')
@ -1013,10 +1012,10 @@
tokens-lib' (-> tokens-lib
(ctob/update-theme "group1" "token-theme-2"
(fn [token-theme]
(assoc token-theme
:name "updated-name"
:group "group2"))))
(fn [token-theme]
(assoc token-theme
:name "updated-name"
:group "group2"))))
themes-tree (ctob/get-theme-tree tokens-lib)
themes-tree' (ctob/get-theme-tree tokens-lib')
@ -1049,93 +1048,95 @@
(t/is (= (count themes-tree') 1))
(t/is (nil? token-theme'))))))
(t/testing "dtcg encoding/decoding"
(t/deftest decode-dtcg-json
(let [json (-> (slurp "test/common_tests/types/data/tokens-multi-set-example.json")
(tr/decode-str))
lib (ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json)
get-set-token (fn [set-name token-name]
(some-> (ctob/get-set lib set-name)
(ctob/get-token token-name)
(dissoc :modified-at)))]
(t/is (= '("core" "light" "dark" "theme") (ctob/get-ordered-set-names lib)))
(t/testing "tokens exist in core set"
(t/is (= (get-set-token "core" "colors.red.600")
{:name "colors.red.600"
:type :color
:value "#e53e3e"
:description nil}))
(t/is (= (get-set-token "core" "spacing.multi-value")
{:name "spacing.multi-value"
:type :spacing
:value "{dimension.sm} {dimension.xl}"
:description "You can have multiple values in a single spacing token"}))
(t/is (= (get-set-token "theme" "button.primary.background")
{:name "button.primary.background"
:type :color
:value "{accent.default}"
:description nil})))
(t/testing "invalid tokens got discarded"
(t/is (nil? (get-set-token "typography" "H1.Bold"))))))
#?(:clj
(t/testing "dtcg encoding/decoding"
(t/deftest decode-dtcg-json
(let [json (-> (slurp "test/common_tests/types/data/tokens-multi-set-example.json")
(tr/decode-str))
lib (ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json)
get-set-token (fn [set-name token-name]
(some-> (ctob/get-set lib set-name)
(ctob/get-token token-name)
(dissoc :modified-at)))]
(t/is (= '("core" "light" "dark" "theme") (ctob/get-ordered-set-names lib)))
(t/testing "tokens exist in core set"
(t/is (= (get-set-token "core" "colors.red.600")
{:name "colors.red.600"
:type :color
:value "#e53e3e"
:description nil}))
(t/is (= (get-set-token "core" "spacing.multi-value")
{:name "spacing.multi-value"
:type :spacing
:value "{dimension.sm} {dimension.xl}"
:description "You can have multiple values in a single spacing token"}))
(t/is (= (get-set-token "theme" "button.primary.background")
{:name "button.primary.background"
:type :color
:value "{accent.default}"
:description nil})))
(t/testing "invalid tokens got discarded"
(t/is (nil? (get-set-token "typography" "H1.Bold"))))))
(t/deftest encode-dtcg-json
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "core"
:tokens {"colors.red.600"
(ctob/make-token
{:name "colors.red.600"
:type :color
:value "#e53e3e"})
"spacing.multi-value"
(ctob/make-token
{:name "spacing.multi-value"
:type :spacing
:value "{dimension.sm} {dimension.xl}"
:description "You can have multiple values in a single spacing token"})
"button.primary.background"
(ctob/make-token
{:name "button.primary.background"
:type :color
:value "{accent.default}"})})))
expected (ctob/encode-dtcg tokens-lib)]
(t/is (= {"core"
{"colors" {"red" {"600" {"$value" "#e53e3e"
"$type" "color"}}}
"spacing"
{"multi-value"
{"$value" "{dimension.sm} {dimension.xl}"
"$type" "spacing"
"$description" "You can have multiple values in a single spacing token"}}
"button"
{"primary" {"background" {"$value" "{accent.default}"
"$type" "color"}}}}}
expected))))
(t/deftest encode-dtcg-json
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "core"
:tokens {"colors.red.600"
(ctob/make-token
{:name "colors.red.600"
:type :color
:value "#e53e3e"})
"spacing.multi-value"
(ctob/make-token
{:name "spacing.multi-value"
:type :spacing
:value "{dimension.sm} {dimension.xl}"
:description "You can have multiple values in a single spacing token"})
"button.primary.background"
(ctob/make-token
{:name "button.primary.background"
:type :color
:value "{accent.default}"})})))
expected (ctob/encode-dtcg tokens-lib)]
(t/is (= {"core"
{"colors" {"red" {"600" {"$value" "#e53e3e"
"$type" "color"}}}
"spacing"
{"multi-value"
{"$value" "{dimension.sm} {dimension.xl}"
"$type" "spacing"
"$description" "You can have multiple values in a single spacing token"}}
"button"
{"primary" {"background" {"$value" "{accent.default}"
"$type" "color"}}}}}
expected))))
(t/deftest encode-decode-dtcg-json
(with-redefs [dt/now (constantly #inst "2024-10-16T12:01:20.257840055-00:00")]
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "core"
:tokens {"colors.red.600"
(ctob/make-token
{:name "colors.red.600"
:type :color
:value "#e53e3e"})
"spacing.multi-value"
(ctob/make-token
{:name "spacing.multi-value"
:type :spacing
:value "{dimension.sm} {dimension.xl}"
:description "You can have multiple values in a single spacing token"})
"button.primary.background"
(ctob/make-token
{:name "button.primary.background"
:type :color
:value "{accent.default}"})})))
encoded (ctob/encode-dtcg tokens-lib)
with-prev-tokens-lib (ctob/decode-dtcg-json tokens-lib encoded)
with-empty-tokens-lib (ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) encoded)]
(t/testing "library got updated but data is equal"
(t/is (not= with-prev-tokens-lib tokens-lib))
(t/is (= @with-prev-tokens-lib @tokens-lib)))
(t/testing "fresh tokens library is also equal"
(= @with-empty-tokens-lib @tokens-lib)))))))
(t/deftest encode-decode-dtcg-json
(with-redefs [dt/now (constantly #inst "2024-10-16T12:01:20.257840055-00:00")]
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "core"
:tokens {"colors.red.600"
(ctob/make-token
{:name "colors.red.600"
:type :color
:value "#e53e3e"})
"spacing.multi-value"
(ctob/make-token
{:name "spacing.multi-value"
:type :spacing
:value "{dimension.sm} {dimension.xl}"
:description "You can have multiple values in a single spacing token"})
"button.primary.background"
(ctob/make-token
{:name "button.primary.background"
:type :color
:value "{accent.default}"})})))
encoded (ctob/encode-dtcg tokens-lib)
with-prev-tokens-lib (ctob/decode-dtcg-json tokens-lib encoded)
with-empty-tokens-lib (ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) encoded)]
(t/testing "library got updated but data is equal"
(t/is (not= with-prev-tokens-lib tokens-lib))
(t/is (= @with-prev-tokens-lib @tokens-lib)))
(t/testing "fresh tokens library is also equal"
(= @with-empty-tokens-lib @tokens-lib))))))