Compare commits

...

11 Commits

Author SHA1 Message Date
Elhombretecla
f352fb11b3 Add status tooltips 2025-02-05 11:07:56 +01:00
Elhombretecla
1bc25e6d7d Add new colors 2025-02-05 11:07:56 +01:00
Elhombretecla
6f3a69210b Add file persistance functionality 2025-02-05 11:07:56 +01:00
Elhombretecla
7dbf1fd7ef Add status notification bubble 2025-02-05 11:07:56 +01:00
Andrei Fëdorov
55610545d8 🎉 Import legacy format tokens (#5695) 2025-02-05 11:07:56 +01:00
Florian Schrödl
d49b492302 Create sets inside folders 2025-02-05 11:07:56 +01:00
Andrey Antukh
87d8d81528 🐛 Fix name generation and handling on creating objects (files, tokens, ...) (#5745)
*  Update function implementation

*  Add tests for a new function implementation

*  Update function usages according to new signature

*  Update docstrings

*  Use simpler assertion

* 💄 Replace concat with cons on name-seq

* 🐛 Fix incorrect error handling on legacy workspace redirect

---------

Co-authored-by: Andrey Fedorov <oran9e.red@gmail.com>
2025-02-05 11:07:56 +01:00
Andrés Moya
bba97ddcca 🐛 Regenerate text shapes after changing token values 2025-02-05 11:07:56 +01:00
Eva Marco
38e19282b4 🐛 Fix thumbnail regeration when changing sets of tokens 2025-02-05 11:07:56 +01:00
Andrés Moya
4b031c1478 Apply token value changes to shapes in all pages 2025-02-05 11:07:56 +01:00
Andrey Antukh
cbd97539e7 📎 Update changelog (add entry for 2.6.0) 2025-02-05 11:07:56 +01:00
33 changed files with 1565 additions and 367 deletions

View File

@@ -1,5 +1,18 @@
# CHANGELOG
## 2.6.0 (Unreleased)
### :rocket: Epics and highlights
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
### :sparkles: New features
### :bug: Bugs fixed
## 2.5.0 (Unreleased)
### :rocket: Epics and highlights

View File

@@ -1071,7 +1071,7 @@
groups (d/group-by #(first (cfh/split-path (:path %))) assets)
;; If there is a group called as the generic-name we have to preserve it
unames (into #{} (keep str) (keys groups))
groups (rename-keys groups {generic-name (cfh/generate-unique-name unames generic-name)})
groups (rename-keys groups {generic-name (cfh/generate-unique-name generic-name unames)})
;; Split large groups in chunks of max-group-size elements
groups (loop [groups (seq groups)

View File

@@ -9,7 +9,6 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.shapes.common :as gco]
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[clojure.set :as set]
[clojure.walk :as walk]
@@ -400,31 +399,51 @@
elements)]
(into #{} (keep :name) elements)))
(defn- extract-numeric-suffix
[basename]
(if-let [[_ p1 p2] (re-find #"(.*) ([0-9]+)$" basename)]
[p1 (+ 1 (d/parse-integer p2))]
[basename 1]))
(defn- name-seq
"Creates a lazy, infinite sequence of names starting with `base-name`,
followed by variants with suffixes applied. The sequence follows this pattern:
- `base-name`
- `(str base-name (suffix-fn 1))`
- `(str base-name (suffix-fn 2))`
- `(str base-name (suffix-fn 3))`, etc."
[base-name suffix-fn]
(cons base-name
(map #(str/concat base-name (suffix-fn %))
(iterate inc 1))))
(defn ^:private get-suffix
"Default suffix impelemtation"
[copy-count]
(str/concat " " copy-count))
(defn generate-unique-name
"A unique name generator"
[used basename]
"Generates a unique name by selecting the first available name from a generated sequence.
The sequence consists of `base-name` and its variants, avoiding conflicts with `existing-names`.
Parameters:
- `base-name` - string used as the base for name generation.
- `existing-names` - a collection of existing names to check for uniqueness.
- Options:
- `:suffix-fn` - a function that generates suffixes, given an integer (default: `get-suffix`).
- `:immediate-suffix?` - if `true`, the base name is considered taken, and suffixing starts immediately.
Returns:
- A unique name not present in `existing-names`."
[base-name existing-names & {:keys [suffix-fn immediate-suffix?]
:or {suffix-fn get-suffix}}]
(dm/assert!
"expected a set of strings"
(sm/check-set-of-strings! used))
(coll? existing-names))
(dm/assert!
"expected a string for `basename`."
(string? basename))
(if-not (contains? used basename)
basename
(let [[prefix initial] (extract-numeric-suffix basename)]
(loop [counter initial]
(let [candidate (str prefix " " counter)]
(if (contains? used candidate)
(recur (inc counter))
candidate))))))
(string? base-name))
(let [existing-name-set (cond-> (set existing-names)
immediate-suffix? (conj base-name))
names (name-seq base-name suffix-fn)]
(->> names
(remove #(contains? existing-name-set %))
first)))
(defn walk-pages
"Go through all pages of a file and apply a function to each one"
@@ -724,7 +743,7 @@
(defn split-by-last-period
"Splits a string into two parts:
the text before and including the last period,
the text before and including the last period,
and the text after the last period."
[s]
(if-let [last-period (str/last-index-of s ".")]

View File

@@ -2011,7 +2011,7 @@
has-flow? (partial ctp/get-frame-flow flows)]
(reduce (fn [changes frame-id]
(let [name (cfh/generate-unique-name @unames "Flow 1")
(let [name (cfh/generate-unique-name "Flow" @unames :immediate-suffix? true)
frame-id (get ids-map frame-id)
flow-id (uuid/next)
new-flow {:id flow-id

View File

@@ -52,7 +52,7 @@
[tokens-lib {:keys [from-index to-index position collapsed-paths]
:or {collapsed-paths #{}}}]
(let [tree (-> (ctob/get-set-tree tokens-lib)
(ctob/walk-sets-tree-seq :walk-children? #(contains? collapsed-paths %)))
(ctob/walk-sets-tree-seq :skip-children-pred #(contains? collapsed-paths %)))
from (nth tree from-index)
to (nth tree to-index)
before (case position

View File

@@ -670,19 +670,83 @@ used for managing active sets without a user created theme.")
;; === Import / Export from DTCG format
(def ^:private legacy-node?
(sm/validator
[:or
[:map
["value" :string]
["type" :string]]
[:map
["value" [:sequential [:map ["type" :string]]]]
["type" :string]]
[:map
["value" :map]
["type" :string]]]))
(def ^:private dtcg-node?
(sm/validator
[:or
[:map
["$value" :string]
["$type" :string]]
[:map
["$value" [:sequential [:map ["$type" :string]]]]
["$type" :string]]
[:map
["$value" :map]
["$type" :string]]]))
(defn has-legacy-format?
"Searches through parsed token file and returns:
- true when first node satisfies `legacy-node?` predicate
- false when first node satisfies `dtcg-node?` predicate
- nil if neither combination is found"
([data]
(has-legacy-format? data legacy-node? dtcg-node?))
([data legacy-node? dtcg-node?]
(let [branch? map?
children (fn [node] (vals node))
check-node (fn [node]
(cond
(legacy-node? node) true
(dtcg-node? node) false
:else nil))
walk (fn walk [node]
(lazy-seq
(cons
(check-node node)
(when (branch? node)
(mapcat walk (children node))))))]
(->> (walk data)
(filter some?)
first))))
(defn walk-sets-tree-seq
[nodes & {:keys [walk-children?]
:or {walk-children? (constantly true)}}]
"Walk sets tree as a flat list.
Options:
`:skip-children-pred`: predicate to skip iterating over a set groups children by checking the path of the set group
`:new-editing-set-path`: append a an item with `:new?` at the given path"
[nodes & {:keys [skip-children-pred new-editing-set-path]
:or {skip-children-pred (constantly false)}}]
(let [walk (fn walk [node {:keys [parent depth]
:or {parent []
depth 0}
:as opts}]
(lazy-seq
(if (d/ordered-map? node)
(mapcat #(walk % opts) node)
(let [root (cond-> node
(= [] new-editing-set-path) (assoc :new? true))]
(mapcat #(walk % opts) root))
(let [[k v] node]
(cond
;;; Set
;; New set
(= :new? k) [{:new? true
:group? false
:parent-path parent
:depth depth}]
;; Set
(and v (instance? TokenSet v))
[{:group? false
:path (split-token-set-path (:name v))
@@ -698,12 +762,12 @@ used for managing active sets without a user created theme.")
:path path
:parent-path parent
:depth depth}]
(if (walk-children? path)
(if (skip-children-pred path)
[item]
(cons
item
(mapcat #(walk % (assoc opts :parent path :depth (inc depth))) v)))))))))]
(walk nodes nil)))
(let [v' (cond-> v
(= path new-editing-set-path) (assoc :new? true))]
(cons item (mapcat #(walk % (assoc opts :parent path :depth (inc depth))) v'))))))))))]
(walk (or nodes (d/ordered-map)) nil)))
(defn flatten-nested-tokens-json
"Recursively flatten the dtcg token structure, joining keys with '.'."
@@ -749,6 +813,7 @@ Will return a value that matches this schema:
(get-active-themes-set-tokens [_] "set of set names that are active in the the active themes")
(encode-dtcg [_] "Encodes library to a dtcg compatible json string")
(decode-dtcg-json [_ parsed-json] "Decodes parsed json containing tokens and converts to library")
(decode-legacy-json [_ parsed-json] "Decodes parsed legacy json containing tokens and converts to library")
(get-all-tokens [_] "all tokens in the lib")
(validate [_]))
@@ -1174,6 +1239,27 @@ Will return a value that matches this schema:
lib' themes-data)
lib')))
(decode-legacy-json [this parsed-legacy-json]
(let [other-data (select-keys parsed-legacy-json ["$themes" "$metadata"])
sets-data (dissoc parsed-legacy-json "$themes" "$metadata")
dtcg-sets-data (walk/postwalk
(fn [node]
(cond-> node
(and (map? node)
(contains? node "value")
(sequential? (get node "value")))
(update "value"
(fn [seq-value]
(map #(set/rename-keys % {"type" "$type"}) seq-value)))
(and (map? node)
(and (contains? node "type")
(contains? node "value")))
(set/rename-keys {"value" "$value"
"type" "$type"})))
sets-data)]
(decode-dtcg-json this (merge other-data
dtcg-sets-data))))
(get-all-tokens [this]
(reduce
(fn [tokens' set]

View File

@@ -0,0 +1,38 @@
;; 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.files.helpers-test
(:require
[app.common.files.helpers :as cfh]
[clojure.test :as t]))
(t/deftest test-generate-unique-name
(t/testing "Test unique name generation"
(let [suffix-fn #(str "-copy-" %)]
(t/is (cfh/generate-unique-name "base-name"
#{"base-name" "base-name-copy-1"}
:suffix-fn suffix-fn)
"base-name-copy-2")
(t/is (cfh/generate-unique-name "base-name"
#{"base-name-copy-2"}
:suffix-fn suffix-fn)
"base-name-copy-1")
(t/is (cfh/generate-unique-name "base-name"
#{"base-namec-copy"}
:suffix-fn suffix-fn)
"base-name-copy-1")
(t/is (cfh/generate-unique-name "base-name"
#{"base-name"}
:suffix-fn suffix-fn)
"base-name-copy-1")))
(t/testing "Test unique name generation with immidate suffix and default suffix-fn"
(t/is (cfh/generate-unique-name "base-name" #{} :immediate-suffix? true)
"base-name 1")
(t/is (cfh/generate-unique-name "base-name"
#{"base-name 1" "base-name 2"}
:immediate-suffix? true)
"base-name 3")))

View File

@@ -0,0 +1,810 @@
{
"core": {
"dimension": {
"scale": {
"value": "2",
"type": "dimension"
},
"xs": {
"value": "4",
"type": "dimension"
},
"sm": {
"value": "{dimension.xs} * {dimension.scale}",
"type": "dimension"
},
"md": {
"value": "{dimension.sm} * {dimension.scale}",
"type": "dimension"
},
"lg": {
"value": "{dimension.md} * {dimension.scale}",
"type": "dimension"
},
"xl": {
"value": "{dimension.lg} * {dimension.scale}",
"type": "dimension"
}
},
"spacing": {
"xs": {
"value": "{dimension.xs}",
"type": "spacing"
},
"sm": {
"value": "{dimension.sm}",
"type": "spacing"
},
"md": {
"value": "{dimension.md}",
"type": "spacing"
},
"lg": {
"value": "{dimension.lg}",
"type": "spacing"
},
"xl": {
"value": "{dimension.xl}",
"type": "spacing"
},
"multi-value": {
"value": "{dimension.sm} {dimension.xl}",
"type": "spacing",
"$description": "You can have multiple values in a single spacing token"
}
},
"borderRadius": {
"sm": {
"value": "4",
"type": "borderRadius"
},
"lg": {
"value": "8",
"type": "borderRadius"
},
"xl": {
"value": "16",
"type": "borderRadius"
},
"multi-value": {
"value": "{borderRadius.sm} {borderRadius.lg}",
"type": "borderRadius",
"$description": "You can have multiple values in a single radius token. Read more on these: https://docs.tokens.studio/available-tokens/border-radius-tokens#single--multiple-values"
}
},
"colors": {
"black": {
"value": "#000000",
"type": "color"
},
"white": {
"value": "#ffffff",
"type": "color"
},
"gray": {
"100": {
"value": "#f7fafc",
"type": "color"
},
"200": {
"value": "#edf2f7",
"type": "color"
},
"300": {
"value": "#e2e8f0",
"type": "color"
},
"400": {
"value": "#cbd5e0",
"type": "color"
},
"500": {
"value": "#a0aec0",
"type": "color"
},
"600": {
"value": "#718096",
"type": "color"
},
"700": {
"value": "#4a5568",
"type": "color"
},
"800": {
"value": "#2d3748",
"type": "color"
},
"900": {
"value": "#1a202c",
"type": "color"
}
},
"red": {
"100": {
"value": "#fff5f5",
"type": "color"
},
"200": {
"value": "#fed7d7",
"type": "color"
},
"300": {
"value": "#feb2b2",
"type": "color"
},
"400": {
"value": "#fc8181",
"type": "color"
},
"500": {
"value": "#f56565",
"type": "color"
},
"600": {
"value": "#e53e3e",
"type": "color"
},
"700": {
"value": "#c53030",
"type": "color"
},
"800": {
"value": "#9b2c2c",
"type": "color"
},
"900": {
"value": "#742a2a",
"type": "color"
}
},
"orange": {
"100": {
"value": "#fffaf0",
"type": "color"
},
"200": {
"value": "#feebc8",
"type": "color"
},
"300": {
"value": "#fbd38d",
"type": "color"
},
"400": {
"value": "#f6ad55",
"type": "color"
},
"500": {
"value": "#ed8936",
"type": "color"
},
"600": {
"value": "#dd6b20",
"type": "color"
},
"700": {
"value": "#c05621",
"type": "color"
},
"800": {
"value": "#9c4221",
"type": "color"
},
"900": {
"value": "#7b341e",
"type": "color"
}
},
"yellow": {
"100": {
"value": "#fffff0",
"type": "color"
},
"200": {
"value": "#fefcbf",
"type": "color"
},
"300": {
"value": "#faf089",
"type": "color"
},
"400": {
"value": "#f6e05e",
"type": "color"
},
"500": {
"value": "#ecc94b",
"type": "color"
},
"600": {
"value": "#d69e2e",
"type": "color"
},
"700": {
"value": "#b7791f",
"type": "color"
},
"800": {
"value": "#975a16",
"type": "color"
},
"900": {
"value": "#744210",
"type": "color"
}
},
"green": {
"100": {
"value": "#f0fff4",
"type": "color"
},
"200": {
"value": "#c6f6d5",
"type": "color"
},
"300": {
"value": "#9ae6b4",
"type": "color"
},
"400": {
"value": "#68d391",
"type": "color"
},
"500": {
"value": "#48bb78",
"type": "color"
},
"600": {
"value": "#38a169",
"type": "color"
},
"700": {
"value": "#2f855a",
"type": "color"
},
"800": {
"value": "#276749",
"type": "color"
},
"900": {
"value": "#22543d",
"type": "color"
}
},
"teal": {
"100": {
"value": "#e6fffa",
"type": "color"
},
"200": {
"value": "#b2f5ea",
"type": "color"
},
"300": {
"value": "#81e6d9",
"type": "color"
},
"400": {
"value": "#4fd1c5",
"type": "color"
},
"500": {
"value": "#38b2ac",
"type": "color"
},
"600": {
"value": "#319795",
"type": "color"
},
"700": {
"value": "#2c7a7b",
"type": "color"
},
"800": {
"value": "#285e61",
"type": "color"
},
"900": {
"value": "#234e52",
"type": "color"
}
},
"blue": {
"100": {
"value": "#ebf8ff",
"type": "color"
},
"200": {
"value": "#bee3f8",
"type": "color"
},
"300": {
"value": "#90cdf4",
"type": "color"
},
"400": {
"value": "#63b3ed",
"type": "color"
},
"500": {
"value": "#4299e1",
"type": "color"
},
"600": {
"value": "#3182ce",
"type": "color"
},
"700": {
"value": "#2b6cb0",
"type": "color"
},
"800": {
"value": "#2c5282",
"type": "color"
},
"900": {
"value": "#2a4365",
"type": "color"
}
},
"indigo": {
"100": {
"value": "#ebf4ff",
"type": "color"
},
"200": {
"value": "#c3dafe",
"type": "color"
},
"300": {
"value": "#a3bffa",
"type": "color"
},
"400": {
"value": "#7f9cf5",
"type": "color"
},
"500": {
"value": "#667eea",
"type": "color"
},
"600": {
"value": "#5a67d8",
"type": "color"
},
"700": {
"value": "#4c51bf",
"type": "color"
},
"800": {
"value": "#434190",
"type": "color"
},
"900": {
"value": "#3c366b",
"type": "color"
}
},
"purple": {
"100": {
"value": "#faf5ff",
"type": "color"
},
"200": {
"value": "#e9d8fd",
"type": "color"
},
"300": {
"value": "#d6bcfa",
"type": "color"
},
"400": {
"value": "#b794f4",
"type": "color"
},
"500": {
"value": "#9f7aea",
"type": "color"
},
"600": {
"value": "#805ad5",
"type": "color"
},
"700": {
"value": "#6b46c1",
"type": "color"
},
"800": {
"value": "#553c9a",
"type": "color"
},
"900": {
"value": "#44337a",
"type": "color"
}
},
"pink": {
"100": {
"value": "#fff5f7",
"type": "color"
},
"200": {
"value": "#fed7e2",
"type": "color"
},
"300": {
"value": "#fbb6ce",
"type": "color"
},
"400": {
"value": "#f687b3",
"type": "color"
},
"500": {
"value": "#ed64a6",
"type": "color"
},
"600": {
"value": "#d53f8c",
"type": "color"
},
"700": {
"value": "#b83280",
"type": "color"
},
"800": {
"value": "#97266d",
"type": "color"
},
"900": {
"value": "#702459",
"type": "color"
}
}
},
"opacity": {
"low": {
"value": "10%",
"type": "opacity"
},
"md": {
"value": "50%",
"type": "opacity"
},
"high": {
"value": "90%",
"type": "opacity"
}
},
"fontFamilies": {
"heading": {
"value": "Inter",
"type": "fontFamilies"
},
"body": {
"value": "Roboto",
"type": "fontFamilies"
}
},
"lineHeights": {
"heading": {
"value": "110%",
"type": "lineHeights"
},
"body": {
"value": "140%",
"type": "lineHeights"
}
},
"letterSpacing": {
"default": {
"value": "0",
"type": "letterSpacing"
},
"increased": {
"value": "150%",
"type": "letterSpacing"
},
"decreased": {
"value": "-5%",
"type": "letterSpacing"
}
},
"paragraphSpacing": {
"h1": {
"value": "32",
"type": "paragraphSpacing"
},
"h2": {
"value": "26",
"type": "paragraphSpacing"
}
},
"fontWeights": {
"headingRegular": {
"value": "Regular",
"type": "fontWeights"
},
"headingBold": {
"value": "Bold",
"type": "fontWeights"
},
"bodyRegular": {
"value": "Regular",
"type": "fontWeights"
},
"bodyBold": {
"value": "Bold",
"type": "fontWeights"
}
},
"fontSizes": {
"h1": {
"value": "{fontSizes.h2} * 1.25",
"type": "fontSizes"
},
"h2": {
"value": "{fontSizes.h3} * 1.25",
"type": "fontSizes"
},
"h3": {
"value": "{fontSizes.h4} * 1.25",
"type": "fontSizes"
},
"h4": {
"value": "{fontSizes.h5} * 1.25",
"type": "fontSizes"
},
"h5": {
"value": "{fontSizes.h6} * 1.25",
"type": "fontSizes"
},
"h6": {
"value": "{fontSizes.body} * 1",
"type": "fontSizes"
},
"body": {
"value": "16",
"type": "fontSizes"
},
"sm": {
"value": "{fontSizes.body} * 0.85",
"type": "fontSizes"
},
"xs": {
"value": "{fontSizes.body} * 0.65",
"type": "fontSizes"
}
}
},
"light": {
"fg": {
"default": {
"value": "{colors.black}",
"type": "color"
},
"muted": {
"value": "{colors.gray.700}",
"type": "color"
},
"subtle": {
"value": "{colors.gray.500}",
"type": "color"
}
},
"bg": {
"default": {
"value": "{colors.white}",
"type": "color"
},
"muted": {
"value": "{colors.gray.100}",
"type": "color"
},
"subtle": {
"value": "{colors.gray.200}",
"type": "color"
}
},
"accent": {
"default": {
"value": "{colors.indigo.400}",
"type": "color"
},
"onAccent": {
"value": "{colors.white}",
"type": "color"
},
"bg": {
"value": "{colors.indigo.200}",
"type": "color"
}
},
"shadows": {
"default": {
"value": "{colors.gray.900}",
"type": "color"
}
}
},
"dark": {
"fg": {
"default": {
"value": "{colors.white}",
"type": "color"
},
"muted": {
"value": "{colors.gray.300}",
"type": "color"
},
"subtle": {
"value": "{colors.gray.500}",
"type": "color"
}
},
"bg": {
"default": {
"value": "{colors.gray.900}",
"type": "color"
},
"muted": {
"value": "{colors.gray.700}",
"type": "color"
},
"subtle": {
"value": "{colors.gray.600}",
"type": "color"
}
},
"accent": {
"default": {
"value": "{colors.indigo.600}",
"type": "color"
},
"onAccent": {
"value": "{colors.white}",
"type": "color"
},
"bg": {
"value": "{colors.indigo.800}",
"type": "color"
}
},
"shadows": {
"default": {
"value": "rgba({colors.black}, 0.3)",
"type": "color"
}
}
},
"theme": {
"button": {
"primary": {
"background": {
"value": "{accent.default}",
"type": "color"
},
"text": {
"value": "{accent.onAccent}",
"type": "color"
}
},
"borderRadius": {
"value": "{borderRadius.lg}",
"type": "borderRadius"
},
"borderWidth": {
"value": "{dimension.sm}",
"type": "borderWidth"
}
},
"card": {
"borderRadius": {
"value": "{borderRadius.lg}",
"type": "borderRadius"
},
"background": {
"value": "{bg.default}",
"type": "color"
},
"padding": {
"value": "{dimension.md}",
"type": "dimension"
}
},
"boxShadow": {
"default": {
"value": [
{
"x": 5,
"y": 5,
"spread": 3,
"color": "rgba({shadows.default}, 0.15)",
"blur": 5,
"type": "dropShadow"
},
{
"x": 4,
"y": 4,
"spread": 6,
"color": "#00000033",
"blur": 5,
"type": "innerShadow"
}
],
"type": "boxShadow"
}
},
"typography": {
"H1": {
"Bold": {
"value": {
"fontFamily": "{fontFamilies.heading}",
"fontWeight": "{fontWeights.headingBold}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.h1}",
"paragraphSpacing": "{paragraphSpacing.h1}",
"letterSpacing": "{letterSpacing.decreased}"
},
"type": "typography"
},
"Regular": {
"value": {
"fontFamily": "{fontFamilies.heading}",
"fontWeight": "{fontWeights.headingRegular}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.h1}",
"paragraphSpacing": "{paragraphSpacing.h1}",
"letterSpacing": "{letterSpacing.decreased}"
},
"type": "typography"
}
},
"H2": {
"Bold": {
"value": {
"fontFamily": "{fontFamilies.heading}",
"fontWeight": "{fontWeights.headingBold}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.h2}",
"paragraphSpacing": "{paragraphSpacing.h2}",
"letterSpacing": "{letterSpacing.decreased}"
},
"type": "typography"
},
"Regular": {
"value": {
"fontFamily": "{fontFamilies.heading}",
"fontWeight": "{fontWeights.headingRegular}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.h2}",
"paragraphSpacing": "{paragraphSpacing.h2}",
"letterSpacing": "{letterSpacing.decreased}"
},
"type": "typography"
}
},
"Body": {
"value": {
"fontFamily": "{fontFamilies.body}",
"fontWeight": "{fontWeights.bodyRegular}",
"lineHeight": "{lineHeights.heading}",
"fontSize": "{fontSizes.body}",
"paragraphSpacing": "{paragraphSpacing.h2}"
},
"type": "typography"
}
}
},
"$themes": [ {
"name": "theme-1",
"group": "group-1",
"description": null,
"is-source": false,
"modified-at": "2024-01-01T00:00:00.000+00:00",
"selectedTokenSets": {"light": "enabled"}
} ],
"$metadata": {
"tokenSetOrder": ["core", "light", "dark", "theme"]
}
}

View File

@@ -1145,6 +1145,41 @@
(t/is (= (count themes-tree') 1))
(t/is (nil? token-theme'))))))
#?(:clj
(t/deftest legacy-json-decoding
(t/testing "decode-legacy-json"
(let [json (-> (slurp "test/common_tests/types/data/tokens-multi-set-legacy-example.json")
(tr/decode-str))
lib (ctob/decode-legacy-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)))
token-theme (ctob/get-theme lib "group-1" "theme-1")]
(t/is (= '("core" "light" "dark" "theme") (ctob/get-ordered-set-names lib)))
(t/testing "set exists in theme"
(t/is (= (:group token-theme) "group-1"))
(t/is (= (:name token-theme) "theme-1"))
(t/is (= (:sets token-theme) #{"light"})))
(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/deftest dtcg-encoding-decoding
(t/testing "decode-dtcg-json"

View File

@@ -95,6 +95,9 @@ export class WorkspacePage extends BaseWebSocketPage {
this.tokenContextMenuForToken = page.getByTestId(
"tokens-context-menu-for-token",
);
this.tokenContextMenuForSet = page.getByTestId(
"tokens-context-menu-for-set",
);
}
async goToWorkspace({

View File

@@ -26,6 +26,7 @@ const setupEmptyTokensFile = async (page) => {
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
tokenSetItems: workspacePage.tokenSetItems,
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
};
};
@@ -58,6 +59,7 @@ const setupTokensFile = async (page) => {
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
tokensSidebar: workspacePage.tokensSidebar,
tokenContextMenuForToken: workspacePage.tokenContextMenuForToken,
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
};
};
@@ -223,27 +225,43 @@ test.describe("Tokens: Sets Tab", () => {
await setInput.press(finalKey);
};
// test("User creates sets tree structure by entering a set path", async ({
// page,
// }) => {
// const {
// workspacePage,
// tokenThemesSetsSidebar,
// tokenSetItems,
// tokenSetGroupItems,
// } = await setupEmptyTokensFile(page);
//
// const tokensTabButton = tokenThemesSetsSidebar
// .getByRole("button", { name: "Add set" })
// .click();
//
// await createSet(tokenThemesSetsSidebar, "core/colors/light");
// await createSet(tokenThemesSetsSidebar, "core/colors/dark");
//
// // User cancels during editing
// await createSet(tokenThemesSetsSidebar, "core/colors/dark", "Escape");
//
// await expect(tokenSetItems).toHaveCount(2);
// await expect(tokenSetGroupItems).toHaveCount(2);
// });
test("User creates sets tree structure by entering a set path", async ({
page,
}) => {
const {
workspacePage,
tokenThemesSetsSidebar,
tokenSetItems,
tokenSetGroupItems,
tokenContextMenuForSet,
} = await setupEmptyTokensFile(page);
const tokensTabButton = tokenThemesSetsSidebar
.getByRole("button", { name: "Add set" })
.click();
await createSet(tokenThemesSetsSidebar, "core/colors/light");
await createSet(tokenThemesSetsSidebar, "core/colors/dark");
// User cancels during editing
await createSet(tokenThemesSetsSidebar, "core/colors/dark", "Escape");
await expect(tokenSetItems).toHaveCount(2);
await expect(tokenSetGroupItems).toHaveCount(2);
// Create set in group
await tokenThemesSetsSidebar
.getByRole("button", { name: "Collapse core" })
.click({ button: "right" });
await expect(tokenContextMenuForSet).toBeVisible();
await tokenContextMenuForSet.getByText("Add set to this group").click();
const setInput = tokenThemesSetsSidebar.locator("input:focus");
await expect(setInput).toBeVisible();
await setInput.fill("sizes/small");
await setInput.press("Enter");
await expect(tokenSetItems).toHaveCount(3);
await expect(tokenSetGroupItems).toHaveCount(3);
});
});

View File

@@ -35,7 +35,7 @@
--status-color-success-200: #a7e8d9; // Used on Register confirmation text
--status-color-success-500: #2d9f8f; // Used on accept icon, and status widget
--status-color-warning-500: #fe4811; // Used on status widget, some buttons and warnings icons and elements
--status-color-warning-500: #f5a91b; // Used on status widget, some buttons and warnings icons and elements
--status-color-error-500: #ff3277; // Used on discard icon, some borders and svg, and on status widget

View File

@@ -25,6 +25,7 @@
[app.util.time :as dt]
[beicon.v2.core :as rx]
[clojure.set :as set]
[cuerdas.core :as str]
[potok.v2.core :as ptk]))
(log/set-level! :warn)
@@ -247,9 +248,9 @@
(ptk/reify ::create-project
ptk/WatchEvent
(watch [_ state _]
(let [projects (get state :projects)
unames (cfh/get-used-names projects)
name (cfh/generate-unique-name unames (str (tr "dashboard.new-project-prefix") " 1"))
(let [unames (cfh/get-used-names (get state :projects))
base-name (tr "dashboard.new-project-prefix")
name (cfh/generate-unique-name base-name unames :immediate-suffix? true)
team-id (:current-team-id state)
params {:name name
:team-id team-id}
@@ -280,13 +281,18 @@
:name name})
ptk/WatchEvent
(watch [_ _ _]
(watch [_ state _]
(let [{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)
new-name (str name " " (tr "dashboard.copy-suffix"))]
projects (get state :projects)
unames (cfh/get-used-names projects)
suffix-fn (fn [copy-count]
(str/concat " "
(tr "dashboard.copy-suffix")
(when (> copy-count 1)
(str " " copy-count))))
new-name (cfh/generate-unique-name name unames :suffix-fn suffix-fn)]
(->> (rp/cmd! :duplicate-project {:project-id id :name new-name})
(rx/tap on-success)
(rx/map project-duplicated)
@@ -477,15 +483,15 @@
(let [{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)
files (get state :files)
unames (cfh/get-used-names files)
name (or name (cfh/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1")))
features (-> (features/get-team-enabled-features state)
(set/difference cfeat/frontend-only-features))
params (-> params
(assoc :name name)
(assoc :features features))]
unames (cfh/get-used-names (get state :files))
base-name (tr "dashboard.new-file-prefix")
name (or name
(cfh/generate-unique-name base-name unames :immediate-suffix? true))
features (-> (features/get-team-enabled-features state)
(set/difference cfeat/frontend-only-features))
params (-> params
(assoc :name name)
(assoc :features features))]
(->> (rp/cmd! :create-file params)
(rx/tap on-success)
@@ -500,13 +506,17 @@
(dm/assert! (string? name))
(ptk/reify ::duplicate-file
ptk/WatchEvent
(watch [_ _ _]
(watch [_ state _]
(let [{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)
new-name (str name " " (tr "dashboard.copy-suffix"))]
unames (cfh/get-used-names (get state :files))
suffix-fn (fn [copy-count]
(str/concat " "
(tr "dashboard.copy-suffix")
(when (> copy-count 1)
(str " " copy-count))))
new-name (cfh/generate-unique-name name unames :suffix-fn suffix-fn)]
(->> (rp/cmd! :duplicate-file {:file-id id :name new-name})
(rx/tap on-success)
(rx/map file-created)
@@ -589,10 +599,10 @@
name (if in-project?
(let [files (get state :files)
unames (cfh/get-used-names files)]
(cfh/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1")))
(cfh/generate-unique-name (tr "dashboard.new-file-prefix") unames :immediate-suffix? true))
(let [projects (get state :projects)
unames (cfh/get-used-names projects)]
(cfh/generate-unique-name unames (str (tr "dashboard.new-project-prefix") " 1"))))
(cfh/generate-unique-name (tr "dashboard.new-project-prefix") unames :immediate-suffix? true)))
params (if in-project?
{:project-id (:project-id pparams)
:name name}

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.logic.tokens :as clt]
[app.common.types.shape :as cts]
@@ -107,11 +108,9 @@
(dch/commit-changes changes)
(wtu/update-workspace-tokens))))))
(defn create-token-set [token-set]
(let [new-token-set (merge
{:name "Token Set"
:tokens []}
token-set)]
(defn create-token-set [set-name token-set]
(let [new-token-set (-> token-set
(update :name #(if (empty? %) set-name (ctob/join-set-path [% set-name]))))]
(ptk/reify ::create-token-set
ptk/WatchEvent
(watch [it _ _]
@@ -286,10 +285,21 @@
(ptk/reify ::duplicate-token
ptk/WatchEvent
(watch [_ state _]
(when-let [token (some-> (dwts/get-selected-token-set-token state token-name)
(update :name #(str/concat % "-copy")))]
(rx/of
(update-create-token {:token token}))))))
(let [token-set (dwts/get-selected-token-set state)
token (some-> token-set (ctob/get-token token-name))
tokens (some-> token-set (ctob/get-tokens))
suffix-fn (fn [copy-count]
(let [suffix (tr "workspace.token.duplicate-suffix")]
(str/concat "-"
suffix
(when (> copy-count 1)
(str "-" copy-count)))))
unames (map :name tokens)
copy-name (cfh/generate-unique-name token-name unames :suffix-fn suffix-fn)]
(when token
(rx/of
(update-create-token
{:token (assoc token :name copy-name)})))))))
(defn set-token-type-section-open
[token-type open?]

View File

@@ -507,10 +507,8 @@
(watch [it state _]
(let [pages (-> (dsh/lookup-file-data state)
(get :pages-index))
unames (cfh/get-used-names pages)
name (cfh/generate-unique-name unames "Page 1")
name (cfh/generate-unique-name "Page" unames :immediate-suffix? true)
changes (-> (pcb/empty-changes it)
(pcb/add-empty-page id name))]
@@ -527,7 +525,13 @@
page (get pages page-id)
unames (cfh/get-used-names pages)
name (cfh/generate-unique-name unames (:name page))
suffix-fn (fn [copy-count]
(str/concat " "
(tr "dashboard.copy-suffix")
(when (> copy-count 1)
(str " " copy-count))))
base-name (:name page)
name (cfh/generate-unique-name base-name unames :suffix-fn suffix-fn)
objects (update-vals (:objects page) #(dissoc % :use-for-thumbnail))
main-instances-ids (set (keep #(when (ctk/main-instance? (val %)) (key %)) objects))

View File

@@ -84,7 +84,9 @@
(defn transform-fill
([state ids color transform] (transform-fill state ids color transform nil))
([state ids color transform options]
(let [objects (dsh/lookup-page-objects state)
(let [page-id (or (get options :page-id)
(get state :current-page-id))
objects (dsh/lookup-page-objects state page-id)
is-text? #(= :text (:type (get objects %)))
text-ids (filter is-text? ids)
shape-ids (remove is-text? ids)

View File

@@ -44,9 +44,9 @@
(dsh/lookup-page state page-id)
(dsh/lookup-page state))
flows (get page :flows)
unames (cfh/get-used-names (vals flows))
name (or name (cfh/generate-unique-name unames "Flow 1"))
unames (cfh/get-used-names (vals (get page :flows)))
name (or name
(cfh/generate-unique-name "Flow" unames :immediate-suffix? true))
flow-id (or flow-id (uuid/next))

View File

@@ -187,5 +187,3 @@
(if (= val ::does-not-exist)
(swap! storage/user dissoc skey)
(swap! storage/user assoc skey val)))))

View File

@@ -328,14 +328,17 @@
(defn- calculate-modifiers
([state modif-tree]
(calculate-modifiers state false false modif-tree))
(calculate-modifiers state false false modif-tree nil))
([state ignore-constraints ignore-snap-pixel modif-tree]
(calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree nil))
([state ignore-constraints ignore-snap-pixel modif-tree page-id]
(calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id nil))
([state ignore-constraints ignore-snap-pixel modif-tree params]
(let [objects
(dsh/lookup-page-objects state)
([state ignore-constraints ignore-snap-pixel modif-tree page-id params]
(let [page-id
(or page-id (:current-page-id state))
objects
(dsh/lookup-page-objects state page-id)
snap-pixel?
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
@@ -402,7 +405,9 @@
(ptk/reify ::set-modifiers
ptk/UpdateEvent
(update [_ state]
(assoc state :workspace-modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree params))))))
(let [page-id (:current-page-id state)
modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)]
(assoc state :workspace-modifiers modifiers))))))
(def ^:private
xf-rotation-shape
@@ -438,11 +443,12 @@
;; - It consideres the center for everyshape instead of the center of the total selrect
;; - The angle param is the desired final value, not a delta
(defn set-delta-rotation-modifiers
[angle shapes {:keys [center delta?] :or {center nil delta? false}}]
[angle shapes {:keys [center delta? page-id] :or {center nil delta? false}}]
(ptk/reify ::set-delta-rotation-modifiers
ptk/UpdateEvent
(update [_ state]
(let [objects (dsh/lookup-page-objects state)
(let [page-id (or page-id (:current-page-id state))
objects (dsh/lookup-page-objects state page-id)
ids
(->> shapes
(remove #(get % :blocked false))
@@ -466,18 +472,19 @@
(apply-modifiers nil))
([{:keys [modifiers undo-transation? stack-undo? ignore-constraints
ignore-snap-pixel ignore-touched undo-group]
ignore-snap-pixel ignore-touched undo-group page-id]
:or {undo-transation? true stack-undo? false ignore-constraints false
ignore-snap-pixel false ignore-touched false}}]
(ptk/reify ::apply-modifiers
ptk/WatchEvent
(watch [_ state _]
(let [text-modifiers (get state :workspace-text-modifier)
objects (dsh/lookup-page-objects state)
(let [text-modifiers (get state :workspace-text-modifier)
page-id (or page-id (:current-page-id state))
objects (dsh/lookup-page-objects state page-id)
object-modifiers
(if (some? modifiers)
(calculate-modifiers state ignore-constraints ignore-snap-pixel modifiers)
(calculate-modifiers state ignore-constraints ignore-snap-pixel modifiers page-id)
(get state :workspace-modifiers))
ids
@@ -519,6 +526,7 @@
:ignore-tree ignore-tree
:ignore-touched ignore-touched
:undo-group undo-group
:page-id page-id
;; Attributes that can change in the transform. This way we don't have to check
;; all the attributes
:attrs [:selrect

View File

@@ -524,7 +524,9 @@
(ptk/reify ::update-layout-child
ptk/WatchEvent
(watch [_ state _]
(let [objects (dsh/lookup-page-objects state)
(let [page-id (or (get options :page-id)
(get state :current-page-id))
objects (dsh/lookup-page-objects state page-id)
children-ids (->> ids (mapcat #(cfh/get-children-ids objects %)))
parent-ids (->> ids (map #(cfh/get-parent-id objects %)))
undo-id (js/Symbol)]

View File

@@ -446,8 +446,10 @@
(when (or
(and (features/active-feature? state "text-editor/v2") (nil? (:workspace-editor state)))
(and (not (features/active-feature? state "text-editor/v2")) (nil? (get-in state [:workspace-editor-state id]))))
(let [objects (dsh/lookup-page-objects state)
shape (get objects id)
(let [page-id (or (get options :page-id)
(get state :current-page-id))
objects (dsh/lookup-page-objects state page-id)
shape (get objects id)
update-node? (some-fn txt/is-text-node? txt/is-paragraph-node?)
shape-ids

View File

@@ -314,7 +314,10 @@
(ptk/reify ::update-dimensions
ptk/UpdateEvent
(update [_ state]
(let [objects (dsh/lookup-page-objects state)
(let [page-id (or (get options :page-id)
(get state :current-page-id))
objects (dsh/lookup-page-objects state page-id)
get-modifier
(fn [shape] (ctm/change-dimensions-modifiers shape attr value))
modif-tree
@@ -403,18 +406,18 @@
"Rotate shapes a fixed angle, from a keyboard action."
([ids rotation]
(increase-rotation ids rotation nil))
([ids rotation params & options]
([ids rotation params & {:as options}]
(ptk/reify ::increase-rotation
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
(let [page-id (or (:page-id options)
(:current-page-id state))
objects (dsh/lookup-page-objects state page-id)
shapes (->> ids (map #(get objects %)))]
shapes (->> ids (map #(get objects %)))
options (assoc options :page-id page-id)]
(rx/concat
(rx/of (dwm/set-delta-rotation-modifiers rotation shapes params))
(rx/of (dwm/set-delta-rotation-modifiers rotation shapes (assoc params :page-id page-id)))
(rx/of (dwm/apply-modifiers options))))))))
;; -- Move ----------------------------------------------------------
(declare start-move)
@@ -788,13 +791,14 @@
(defn update-position
"Move shapes to a new position"
([id position] (update-position id position nil))
([id position opts]
([id position options]
(dm/assert! (uuid? id))
(ptk/reify ::update-position
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
(let [page-id (or (get options :page-id)
(get state :current-page-id))
objects (dsh/lookup-page-objects state page-id)
shape (get objects id)
;; FIXME: performance rect
@@ -809,8 +813,9 @@
modif-tree (dwm/create-modif-tree [id] (ctm/move-modifiers delta))]
(rx/of (dwm/apply-modifiers {:modifiers modif-tree
:page-id page-id
:ignore-constraints false
:ignore-touched (:ignore-touched opts)
:ignore-touched (:ignore-touched options)
:ignore-snap-pixel true})))))))
(defn position-shapes

View File

@@ -22,7 +22,11 @@
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
[rumext.v2 :as mf]
[okulary.core :as l]))
(def ref:persistence-status
(l/derived :status refs/persistence))
;; --- Header Component
@@ -108,6 +112,25 @@
{:class (stl/css :file-name)
:title file-name
:on-double-click start-editing-name}
;;-- Persistende state widget
[:div {:class (case (mf/deref ref:persistence-status)
:pending (stl/css :status-notification :pending-status)
:saving (stl/css :status-notification :saving-status)
:saved (stl/css :status-notification :saved-status)
:error (stl/css :status-notification :error-status)
(stl/css :status-notification))
:title (case (mf/deref ref:persistence-status)
:pending (tr "workspace.header.saving")
:saving (tr "workspace.header.saving")
:saved (tr "workspace.header.saved")
:error (tr "workspace.header.save-error")
nil)}
(case (mf/deref ref:persistence-status)
:pending i/status-alert
:saving i/status-alert
:saved i/status-tick
:error i/status-wrong
nil)]
file-name])]
(when ^boolean shared?
[:span {:class (stl/css :shared-badge)} i/library])

View File

@@ -49,6 +49,9 @@
@include smallTitleTipography;
text-transform: none;
color: var(--title-foreground-color-hover);
align-items: center;
display: flex;
flex-direction: row;
}
.file-name-input {
@@ -80,3 +83,35 @@
width: $s-16;
}
}
.status-notification {
width: $s-6;
height: $s-6;
border-radius: 50%;
margin-right: $s-4;
flex-shrink: 0;
background-color: var(--status-widget-background-color-pending);
&.pending-status {
background-color: var(--status-widget-background-color-warning);
}
&.saving-status {
background-color: var(--status-widget-background-color-pending);
}
&.saved-status {
background-color: var(--status-widget-background-color-success);
animation: jump 0.3s ease-out;
}
&.error-status {
background-color: var(--status-widget-background-color-error);
}
}
@keyframes jump {
0% { transform: translateY(0); }
50% { transform: translateY(-4px); }
100% { transform: translateY(0); }
}

View File

@@ -32,39 +32,6 @@
(def ref:persistence-status
(l/derived :status refs/persistence))
;; --- Persistence state Widget
(mf/defc persistence-state-widget
{::mf/wrap [mf/memo]
::mf/wrap-props false}
[]
(let [status (mf/deref ref:persistence-status)
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)]
(when-not workspace-read-only?
[:div {:class (stl/css :persistence-status-widget)}
(case status
:pending
[:div {:class (stl/css :status-icon :pending-status)
:title (tr "workspace.header.unsaved")}
i/status-alert]
:saving
[:div {:class (stl/css :status-icon :pending-status)
:title (tr "workspace.header.unsaved")}
i/status-alert]
:saved
[:div {:class (stl/css :status-icon :saved-status)
:title (tr "workspace.header.saved")}
i/status-tick]
:error
[:div {:class (stl/css :status-icon :error-status)
:title "There was an error saving the data. Please refresh if this persists."}
i/status-wrong]
nil)])))
;; --- Zoom Widget
(mf/defc zoom-widget-workspace
@@ -216,8 +183,6 @@
[:div {:class (stl/css :users-section)}
[:& active-sessions]]
[:& persistence-state-widget]
[:& export-progress-widget]
[:div {:class (stl/css :separator)}]

View File

@@ -52,8 +52,10 @@
(dwu/start-undo-transaction undo-id)
(dwsh/update-shapes shape-ids (fn [shape]
(cond-> shape
attributes-to-remove (update :applied-tokens #(apply (partial dissoc %) attributes-to-remove))
:always (update :applied-tokens merge tokenized-attributes))))
attributes-to-remove
(update :applied-tokens #(apply (partial dissoc %) attributes-to-remove))
:always
(update :applied-tokens merge tokenized-attributes))))
(when on-update-shape
(on-update-shape resolved-value shape-ids attributes))
(dwu/commit-undo-transaction undo-id))))))))))
@@ -94,122 +96,159 @@
;; Shape Updates ---------------------------------------------------------------
(defn update-shape-radius-all [value shape-ids]
(dwsh/update-shapes shape-ids
(fn [shape]
(ctsr/set-radius-to-all-corners shape value))
{:reg-objects? true
:ignore-touched true
:attrs ctt/border-radius-keys}))
(defn update-shape-radius-all
([value shape-ids attributes] (update-shape-radius-all value shape-ids attributes nil))
([value shape-ids _attributes page-id] ; The attributes param is needed to have the same arity that other update functions
(dwsh/update-shapes shape-ids
(fn [shape]
(ctsr/set-radius-to-all-corners shape value))
{:reg-objects? true
:ignore-touched true
:page-id page-id
:attrs ctt/border-radius-keys})))
(defn update-shape-radius-for-corners [value shape-ids attributes]
(dwsh/update-shapes shape-ids
(fn [shape]
(ctsr/set-radius-for-corners shape attributes value))
{:reg-objects? true
:ignore-touched true
:attrs ctt/border-radius-keys}))
(defn update-shape-radius-for-corners
([value shape-ids attributes] (update-shape-radius-for-corners value shape-ids attributes nil))
([value shape-ids attributes page-id]
(dwsh/update-shapes shape-ids
(fn [shape]
(ctsr/set-radius-for-corners shape attributes value))
{:reg-objects? true
:ignore-touched true
:page-id page-id
:attrs ctt/border-radius-keys})))
(defn update-opacity [value shape-ids]
(when (<= 0 value 1)
(dwsh/update-shapes shape-ids
#(assoc % :opacity value)
{:ignore-touched true})))
(defn update-opacity
([value shape-ids attributes] (update-opacity value shape-ids attributes nil))
([value shape-ids _attributes page-id] ; The attributes param is needed to have the same arity that other update functions
(when (<= 0 value 1)
(dwsh/update-shapes shape-ids
#(assoc % :opacity value)
{:ignore-touched true
:page-id page-id}))))
(defn update-rotation [value shape-ids]
(ptk/reify ::update-shape-rotation
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(udw/trigger-bounding-box-cloaking shape-ids)
(udw/increase-rotation shape-ids value nil :ignore-touched true)))))
;; FIXME: if attributes are not always present, maybe we have an
;; options here for pass optional value and preserve the correct and
;; uniform api across all functions (?)
(defn update-rotation
([value shape-ids attributes] (update-rotation value shape-ids attributes nil))
([value shape-ids _attributes page-id] ; The attributes param is needed to have the same arity that other update functions
(ptk/reify ::update-shape-rotation
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(udw/trigger-bounding-box-cloaking shape-ids)
(udw/increase-rotation shape-ids value nil
{:page-id page-id
:ignore-touched true}))))))
(defn update-stroke-width
[value shape-ids]
(dwsh/update-shapes shape-ids
(fn [shape]
(when (seq (:strokes shape))
(assoc-in shape [:strokes 0 :stroke-width] value)))
{:reg-objects? true
:ignore-touched true
:attrs [:strokes]}))
([value shape-ids attributes] (update-stroke-width value shape-ids attributes nil))
([value shape-ids _attributes page-id] ; The attributes param is needed to have the same arity that other update functions
(dwsh/update-shapes shape-ids
(fn [shape]
(when (seq (:strokes shape))
(assoc-in shape [:strokes 0 :stroke-width] value)))
{:reg-objects? true
:ignore-touched true
:page-id page-id
:attrs [:strokes]})))
(defn update-color [f value shape-ids]
(defn update-color [f value shape-ids page-id]
(when-let [tc (tinycolor/valid-color value)]
(let [hex (tinycolor/->hex-string tc)
opacity (tinycolor/alpha tc)]
(f shape-ids {:color hex :opacity opacity} 0 {:ignore-touched true}))))
(f shape-ids {:color hex :opacity opacity} 0 {:ignore-touched true
:page-id page-id}))))
(defn update-fill
[value shape-ids]
(update-color wdc/change-fill value shape-ids))
([value shape-ids attributes] (update-fill value shape-ids attributes nil))
([value shape-ids _attributes page-id] ; The attributes param is needed to have the same arity that other update functions
(update-color wdc/change-fill value shape-ids page-id)))
(defn update-stroke-color
[value shape-ids]
(update-color wdc/change-stroke-color value shape-ids))
([value shape-ids attributes] (update-stroke-color value shape-ids attributes nil))
([value shape-ids _attributes page-id] ; The attributes param is needed to have the same arity that other update functions
(update-color wdc/change-stroke-color value shape-ids page-id)))
(defn update-fill-stroke [value shape-ids attributes]
(ptk/reify ::update-fill-stroke
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(when (:fill attributes) (update-fill value shape-ids))
(when (:stroke-color attributes) (update-stroke-color value shape-ids))))))
(defn update-fill-stroke
([value shape-ids attributes] (update-fill-stroke value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::update-fill-stroke
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(when (:fill attributes) (update-fill value shape-ids attributes page-id))
(when (:stroke-color attributes) (update-stroke-color value shape-ids attributes page-id)))))))
(defn update-shape-dimensions [value shape-ids attributes]
(ptk/reify ::update-shape-dimensions
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(when (:width attributes) (dwt/update-dimensions shape-ids :width value {:ignore-touched true}))
(when (:height attributes) (dwt/update-dimensions shape-ids :height value {:ignore-touched true}))))))
(defn update-shape-dimensions
([value shape-ids attributes] (update-shape-dimensions value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::update-shape-dimensions
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(when (:width attributes) (dwt/update-dimensions shape-ids :width value {:ignore-touched true :page-id page-id}))
(when (:height attributes) (dwt/update-dimensions shape-ids :height value {:ignore-touched true :page-id page-id})))))))
(defn- attributes->layout-gap [attributes value]
(let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap})
(zipmap (repeat value)))]
{:layout-gap layout-gap}))
(defn- shape-ids-with-layout [state shape-ids]
(->> (dsh/lookup-shapes state shape-ids)
(defn- shape-ids-with-layout [state page-id shape-ids]
(->> (dsh/lookup-shapes state page-id shape-ids)
(eduction
(filter ctsl/any-layout?)
(map :id))))
(defn update-layout-padding [value shape-ids attrs]
(ptk/reify ::update-layout-padding
ptk/WatchEvent
(watch [_ state _]
(let [ids-with-layout (shape-ids-with-layout state shape-ids)]
(rx/of
(dwsl/update-layout ids-with-layout
{:layout-padding (zipmap attrs (repeat value))}
{:ignore-touched true}))))))
(defn update-layout-padding
([value shape-ids attrs] (update-layout-padding value shape-ids attrs nil))
([value shape-ids attrs page-id]
(ptk/reify ::update-layout-padding
ptk/WatchEvent
(watch [_ state _]
(let [ids-with-layout (shape-ids-with-layout state (or page-id (:current-page-id state)) shape-ids)]
(rx/of
(dwsl/update-layout ids-with-layout
{:layout-padding (zipmap attrs (repeat value))}
{:ignore-touched true
:page-id page-id})))))))
(defn update-layout-spacing [value shape-ids attributes]
(ptk/reify ::update-layout-spacing
ptk/WatchEvent
(watch [_ state _]
(let [ids-with-layout (shape-ids-with-layout state shape-ids)
layout-attributes (attributes->layout-gap attributes value)]
(rx/of
(dwsl/update-layout ids-with-layout
layout-attributes
{:ignore-touched true}))))))
(defn update-layout-spacing
([value shape-ids attributes] (update-layout-spacing value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::update-layout-spacing
ptk/WatchEvent
(watch [_ state _]
(let [ids-with-layout (shape-ids-with-layout state page-id shape-ids)
layout-attributes (attributes->layout-gap attributes value)]
(rx/of
(dwsl/update-layout ids-with-layout
layout-attributes
{:ignore-touched true
:page-id page-id})))))))
(defn update-shape-position [value shape-ids attributes]
(ptk/reify ::update-shape-position
ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(map #(dwt/update-position % (zipmap attributes (repeat value)) {:ignore-touched true}) shape-ids)))))
(defn update-shape-position
([value shape-ids attributes] (update-shape-position value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::update-shape-position
ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(map #(dwt/update-position % (zipmap attributes (repeat value)) {:ignore-touched true :page-id page-id}) shape-ids))))))
(defn update-layout-sizing-limits [value shape-ids attributes]
(ptk/reify ::update-layout-sizing-limits
ptk/WatchEvent
(watch [_ _ _]
(let [props (-> {:layout-item-min-w value
:layout-item-min-h value
:layout-item-max-w value
:layout-item-max-h value}
(select-keys attributes))]
(dwsl/update-layout-child shape-ids props {:ignore-touched true})))))
(defn update-layout-sizing-limits
([value shape-ids attributes] (update-layout-sizing-limits value shape-ids attributes nil))
([value shape-ids attributes page-id]
(ptk/reify ::update-layout-sizing-limits
ptk/WatchEvent
(watch [_ _ _]
(let [props (-> {:layout-item-min-w value
:layout-item-min-h value
:layout-item-max-w value
:layout-item-max-h value}
(select-keys attributes))]
(dwsl/update-layout-child shape-ids props {:ignore-touched true
:page-id page-id}))))))

View File

@@ -37,14 +37,14 @@
(st/emit! (dwts/set-selected-token-set-name set-name)))
(defn on-update-token-set [set-name token-set]
(st/emit! (wdt/update-token-set set-name token-set)))
(st/emit! (wdt/update-token-set set-name (ctob/update-name token-set set-name))))
(defn on-update-token-set-group [set-group-path set-group-fname]
(st/emit! (wdt/rename-token-set-group set-group-path set-group-fname)))
(defn on-create-token-set [_ token-set]
(defn on-create-token-set [set-name token-set]
(st/emit! (ptk/event ::ev/event {::ev/name "create-tokens-set"}))
(st/emit! (wdt/create-token-set token-set)))
(st/emit! (wdt/create-token-set set-name token-set)))
(mf/defc editing-label
[{:keys [default-value on-cancel on-submit]}]
@@ -243,7 +243,7 @@
on-edit-submit'
(mf/use-fn
(mf/deps set on-edit-submit)
#(on-edit-submit set-name (ctob/update-name set %)))
#(on-edit-submit % set))
on-drag
(mf/use-fn
@@ -319,7 +319,7 @@
on-toggle-set-group
set-node]
:as props}]
(let [{:keys [on-edit] :as ctx} (sets-context/use-context)
(let [{:keys [on-edit new-path] :as ctx} (sets-context/use-context)
collapsed-paths (mf/use-state #{})
collapsed?
(mf/use-fn
@@ -331,29 +331,11 @@
(swap! collapsed-paths #(if (contains? % path)
(disj % path)
(conj % path)))))]
(for [[index {:keys [group? path parent-path depth] :as node}]
(d/enumerate (ctob/walk-sets-tree-seq set-node :walk-children? #(contains? @collapsed-paths %)))]
(if (not group?)
(let [editing-id (sets-context/set-path->id path)]
[:& sets-tree-set
{:key editing-id
:set (:set node)
:label (last path)
:active? active?
:selected? (selected? (get-in node [:set :name]))
:draggable? draggable?
:on-select on-select
:tree-path path
:tree-depth depth
:tree-index index
:tree-parent-path parent-path
:on-toggle on-toggle-set
:editing-id editing-id
:editing? editing?
:on-edit on-edit
:on-edit-reset on-edit-reset
:on-edit-submit on-edit-submit-set
:collapsed-paths collapsed-paths}])
(for [[index {:keys [new? group? path parent-path depth] :as node}]
(d/enumerate (ctob/walk-sets-tree-seq set-node {:skip-children-pred #(contains? @collapsed-paths %)
:new-editing-set-path new-path}))]
(cond
group?
(let [editing-id (sets-context/set-group-path->id path)]
[:& sets-tree-set-group
{:key editing-id
@@ -374,6 +356,49 @@
:collapsed? (collapsed? path)
:on-toggle-collapse on-toggle-collapse
:on-toggle on-toggle-set-group
:collapsed-paths collapsed-paths}])
new?
(let [editing-id (sets-context/set-path->id path)]
[:& sets-tree-set
{:key editing-id
:set (ctob/make-token-set :name (if (empty? parent-path)
""
(ctob/join-set-path parent-path)))
:label ""
:active? (constantly true)
:selected? (constantly true)
:on-select (constantly nil)
:tree-path path
:tree-depth depth
:tree-index index
:tree-parent-path parent-path
:on-toggle (constantly nil)
:editing-id editing-id
:editing? (constantly true)
:on-edit-reset on-edit-reset
:on-edit-submit on-create-token-set}])
:else
(let [editing-id (sets-context/set-path->id path)]
[:& sets-tree-set
{:key editing-id
:set (:set node)
:label (last path)
:active? active?
:selected? (selected? (get-in node [:set :name]))
:draggable? draggable?
:on-select on-select
:tree-path path
:tree-depth depth
:tree-index index
:tree-parent-path parent-path
:on-toggle on-toggle-set
:editing-id editing-id
:editing? editing?
:on-edit on-edit
:on-edit-reset on-edit-reset
:on-edit-submit on-edit-submit-set
:collapsed-paths collapsed-paths}])))))
(mf/defc controlled-sets-list
@@ -390,45 +415,31 @@
on-select
context]
:as _props}]
(let [{:keys [editing? new? on-edit on-reset] :as ctx} (or context (sets-context/use-context))
(let [{:keys [editing? on-edit on-reset new-path] :as ctx} (or context (sets-context/use-context))
theme-modal? (= origin "theme-modal")
can-edit? (:can-edit (deref refs/permissions))
draggable? (and (not theme-modal?) can-edit?)]
[:fieldset {:class (stl/css :sets-list)}
(if (and theme-modal?
(empty? token-sets))
(empty? token-sets)
(not new-path))
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)}
(tr "workspace.token.no-sets-create")]
(if (and theme-modal?
(empty? token-sets))
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)}
(tr "workspace.token.no-sets-create")]
[:*
[:& sets-tree
{:draggable? draggable?
:set-node token-sets
:selected? token-set-selected?
:on-select on-select
:active? token-set-active?
:group-active? token-set-group-active?
:on-toggle-set on-toggle-token-set
:on-toggle-set-group on-toggle-token-set-group
:editing? editing?
:on-edit on-edit
:on-edit-reset on-reset
:on-edit-submit-set on-update-token-set
:on-edit-submit-group on-update-token-set-group}]
(when new?
[:& sets-tree-set
{:set (ctob/make-token-set :name "")
:label ""
:selected? (constantly true)
:active? (constantly true)
:editing? (constantly true)
:on-select (constantly nil)
:on-edit (constantly nil)
:on-edit-reset on-reset
:on-edit-submit on-create-token-set}])]))]))
[:& sets-tree
{:draggable? draggable?
:set-node token-sets
:selected? token-set-selected?
:on-select on-select
:active? token-set-active?
:group-active? token-set-group-active?
:on-toggle-set on-toggle-token-set
:on-toggle-set-group on-toggle-token-set-group
:editing? editing?
:on-create-token-set on-create-token-set
:on-edit on-edit
:on-edit-reset on-reset
:on-edit-submit-set on-update-token-set
:on-edit-submit-group on-update-token-set-group}])]))
(mf/defc sets-list

View File

@@ -15,14 +15,12 @@
(defn set-path->id [set-path]
(dm/str "set-" set-path))
(def initial {:editing-id nil
:new? false})
(def initial {})
(def context (mf/create-context initial))
(def static-context
{:editing? (constantly false)
:new? false
:on-edit (constantly nil)
:on-create (constantly nil)
:on-reset (constantly nil)})
@@ -37,7 +35,7 @@
(defn use-context []
(let [ctx (mf/use-ctx context)
{:keys [editing-id new?]} @ctx
{:keys [editing-id new-path]} @ctx
editing? (mf/use-callback
(mf/deps editing-id)
#(= editing-id %))
@@ -45,11 +43,12 @@
(fn [editing-id]
(reset! ctx (assoc @ctx :editing-id editing-id))))
on-create (mf/use-fn
#(swap! ctx assoc :editing-id (random-uuid) :new? true))
(fn [path]
(swap! ctx assoc :editing-id (random-uuid) :new-path path)))
on-reset (mf/use-fn
#(reset! ctx initial))]
{:editing? editing?
:new? new?
:new-path new-path
:on-edit on-edit
:on-create on-create
:on-reset on-reset}))

View File

@@ -35,19 +35,27 @@
(mf/defc menu
[{:keys [group? path]}]
(let [{:keys [on-edit]} (sets-context/use-context)
edit-name (mf/use-fn
(mf/deps group?)
(fn []
(let [path (if group?
(sets-context/set-group-path->id path)
(sets-context/set-path->id path))]
(on-edit path))))
delete-set (mf/use-fn #(st/emit! (wdt/delete-token-set-path group? path)))]
(let [{:keys [on-create on-edit]} (sets-context/use-context)
create-set-at-path
(mf/use-fn
(mf/deps path)
#(on-create path))
edit-name
(mf/use-fn
(mf/deps group?)
(fn []
(let [path (if group?
(sets-context/set-group-path->id path)
(sets-context/set-path->id path))]
(on-edit path))))
delete-set
(mf/use-fn
#(st/emit! (wdt/delete-token-set-path group? path)))]
[:ul {:class (stl/css :context-list)}
;; TODO Implement
;; (when (ctob/prefixed-set-path-final-group? prefixed-set-path)
;; [:& menu-entry {:title "Add set to this group" :on-click js/console.log}])
(when group?
[:& menu-entry {:title (tr "workspace.token.add-set-to-group") :on-click create-set-at-path}])
[:& menu-entry {:title (tr "labels.rename") :on-click edit-name}]
[:& menu-entry {:title (tr "labels.delete") :on-click delete-set}]]))
@@ -66,6 +74,7 @@
[:& dropdown {:show (boolean mdata)
:on-close #(st/emit! wdt/hide-token-set-context-menu)}
[:div {:class (stl/css :token-set-context-menu)
:data-testid "tokens-context-menu-for-set"
:ref dropdown-ref
:style {:top top :left left}
:on-context-menu prevent-default}

View File

@@ -110,7 +110,6 @@
(fn [event token]
(dom/stop-propagation event)
(when (seq selected-shapes)
(st/emit!
(wtch/toggle-token {:token token
:shapes selected-shapes
@@ -202,13 +201,13 @@
(mf/defc add-set-button
[{:keys [on-open style]}]
(let [{:keys [on-create new?]} (sets-context/use-context)
(let [{:keys [on-create new-path]} (sets-context/use-context)
on-click #(do
(on-open)
(on-create))
(on-create []))
can-edit? (:can-edit (deref refs/permissions))]
(if (= style "inline")
(when-not new?
(when-not new-path
(if can-edit?
[:div {:class (stl/css :empty-sets-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
@@ -228,9 +227,9 @@
(mf/defc theme-sets-list
[{:keys [on-open]}]
(let [token-sets (mf/deref refs/workspace-ordered-token-sets)
{:keys [new?] :as ctx} (sets-context/use-context)]
{:keys [new-path] :as ctx} (sets-context/use-context)]
(if (and (empty? token-sets)
(not new?))
(not new-path))
[:& add-set-button {:on-open on-open
:style "inline"}]
[:& h/sortable-container {}
@@ -325,6 +324,7 @@
:type :toast
:level :error})))))
(set! (.-value (mf/ref-val input-ref)) "")))
on-export (fn []
(st/emit! (ptk/event ::ev/event {::ev/name "export-tokens"}))
(let [tokens-json (some-> (deref refs/tokens-lib)
@@ -353,7 +353,6 @@
[:> dropdown-menu-item* {:class (stl/css :import-export-menu-item)
:on-click on-display-file-explorer}
(tr "labels.import")])
[:> dropdown-menu-item* {:class (stl/css :import-export-menu-item)
:on-click on-export}
(tr "labels.export")]]]))

View File

@@ -188,7 +188,9 @@
(throw (wte/error-ex-info :error.import/json-parse-error data e))))))
(rx/map (fn [json-data]
(try
(ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json-data)
(if-not (ctob/has-legacy-format? json-data)
(ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json-data)
(ctob/decode-legacy-json (ctob/ensure-tokens-lib nil) json-data))
(catch js/Error e
(throw (wte/error-ex-info :error.import/invalid-json-data json-data e))))))
(rx/mapcat (fn [tokens-lib]

View File

@@ -1,8 +1,11 @@
(ns app.main.ui.workspace.tokens.update
(:require
[app.common.files.helpers :as cfh]
[app.common.types.token :as ctt]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.thumbnails :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.style-dictionary :as wtsd]
@@ -86,44 +89,82 @@
(->> (map (fn [[value attrs]] [attrs {value #{object-id}}]) attrs-values-map)
(into {})))
(defn collect-shapes-update-info [resolved-tokens shapes]
(reduce
(fn [acc [object-id {:keys [applied-tokens] :as shape}]]
(if (seq applied-tokens)
(let [applied-tokens (-> (invert-collect-key-vals applied-tokens resolved-tokens shape)
(shape-ids-by-values object-id)
(split-attribute-groups))]
(deep-merge acc applied-tokens))
acc))
{} shapes))
(defn- collect-shapes-update-info [resolved-tokens objects]
(loop [items (seq objects)
frame-ids #{}
text-ids []
tokens {}]
(if-let [[shape-id {:keys [applied-tokens] :as shape}] (first items)]
(let [applied-tokens
(-> (invert-collect-key-vals applied-tokens resolved-tokens shape)
(shape-ids-by-values shape-id)
(split-attribute-groups))
(defn actionize-shapes-update-info [shapes-update-info]
parent-frame-id
(cfh/get-shape-id-root-frame objects shape-id)]
(recur (rest items)
(if parent-frame-id
(conj frame-ids parent-frame-id)
frame-ids)
(if (cfh/text-shape? shape)
(conj text-ids shape-id)
text-ids)
(deep-merge tokens applied-tokens)))
[tokens frame-ids text-ids])))
;; FIXME: revisit this
(defn- actionize-shapes-update-info [page-id shapes-update-info]
(mapcat (fn [[attrs update-infos]]
(let [action (some attribute-actions-map attrs)]
(map
(fn [[v shape-ids]]
(action v shape-ids attrs))
(action v shape-ids attrs page-id))
update-infos)))
shapes-update-info))
(defn update-tokens [state resolved-tokens]
(->> (dsh/lookup-page-objects state)
(collect-shapes-update-info resolved-tokens)
(actionize-shapes-update-info)))
(defn update-tokens
[state resolved-tokens]
(let [file-id (get state :current-file-id)
current-page-id (get state :current-page-id)
fdata (dsh/lookup-file-data state file-id)]
(->> (rx/from (:pages fdata))
(rx/mapcat
(fn [page-id]
(let [page
(dsh/get-page fdata page-id)
(defn update-workspace-tokens []
[attrs frame-ids text-ids]
(collect-shapes-update-info resolved-tokens (:objects page))
actions
(actionize-shapes-update-info page-id attrs)]
(rx/merge
(rx/from actions)
(->> (rx/from frame-ids)
(rx/mapcat (fn [frame-id]
(rx/of (dwt/clear-thumbnail file-id page-id frame-id "frame")
(dwt/clear-thumbnail file-id page-id frame-id "component")))))
(when (not= page-id current-page-id) ;; Texts in the current page have already their position-data regenerated
(rx/of (dwsh/update-shapes text-ids ;; after change. But those on other pages need to be specifically reset.
(fn [shape]
(dissoc shape :position-data))
{:page-id page-id
:ignore-touched true}))))))))))
(defn update-workspace-tokens
[]
(ptk/reify ::update-workspace-tokens
ptk/WatchEvent
(watch [_ state _]
(->>
(rx/from
(->
(wtts/get-active-theme-sets-tokens-names-map state)
(wtsd/resolve-tokens+)))
(rx/mapcat
(fn [sd-tokens]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(update-tokens state sd-tokens)
(rx/of (dwu/commit-undo-transaction undo-id))))))))))
(let [tokens (-> (wtts/get-active-theme-sets-tokens-names-map state)
(wtsd/resolve-tokens+))]
(->> (rx/from tokens)
(rx/mapcat (fn [sd-tokens]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(update-tokens state sd-tokens)
(rx/of (dwu/commit-undo-transaction undo-id)))))))))))

View File

@@ -1164,6 +1164,10 @@ msgstr "Cannot drop a parent set to an own child path."
msgid "errors.drag-drop.set-exists"
msgstr "Cannot complete drop, a set with same name already exists at path %s."
#: src/app/main/data/tokens.cljs:294
msgid "workspace.token.duplicate-suffix"
msgstr "copy"
#: src/app/main/ui/auth/verify_token.cljs:84, src/app/main/ui/settings/change_email.cljs:29
msgid "errors.email-already-exists"
msgstr "Email already used"
@@ -1882,6 +1886,10 @@ msgstr "Expired"
msgid "labels.export"
msgstr "Export"
#: src/app/main/ui/exports/assets.cljs:177
msgid "labels.import"
msgstr "Import"
#: src/app/main/ui/settings/feedback.cljs:48
msgid "labels.feedback-disabled"
msgstr "Feedback disabled"
@@ -6535,6 +6543,10 @@ msgstr "Enter token value or alias"
msgid "workspace.token.grouping-set-alert"
msgstr "Token Set grouping is not supported yet."
#: src/app/main/ui/workspace/tokens/sets_context_menu.cljs
msgid "workspace.token.add-set-to-group"
msgstr "Add set to this group"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs:179
msgid "workspace.token.label.group"
msgstr "Group"