mirror of
https://github.com/penpot/penpot.git
synced 2025-12-23 22:48:40 -05:00
Compare commits
11 Commits
develop
...
juan-filen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f352fb11b3 | ||
|
|
1bc25e6d7d | ||
|
|
6f3a69210b | ||
|
|
7dbf1fd7ef | ||
|
|
55610545d8 | ||
|
|
d49b492302 | ||
|
|
87d8d81528 | ||
|
|
bba97ddcca | ||
|
|
38e19282b4 | ||
|
|
4b031c1478 | ||
|
|
cbd97539e7 |
13
CHANGES.md
13
CHANGES.md
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ".")]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
38
common/test/common_tests/files/helpers_test.cljc
Normal file
38
common/test/common_tests/files/helpers_test.cljc
Normal 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")))
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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?]
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -187,5 +187,3 @@
|
||||
(if (= val ::does-not-exist)
|
||||
(swap! storage/user dissoc skey)
|
||||
(swap! storage/user assoc skey val)))))
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
@@ -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)}]
|
||||
|
||||
@@ -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}))))))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}))
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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")]]]))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)))))))))))
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user