Merge remote-tracking branch 'origin/token-studio-develop' into florian/rename-set-groups

This commit is contained in:
Florian Schroedl 2024-12-10 17:31:38 +01:00
commit 78d743406b
25 changed files with 886 additions and 173 deletions

View file

@ -1166,7 +1166,7 @@
; We need to trigger a sync if the shape has changed any ; We need to trigger a sync if the shape has changed any
; attribute that participates in components synchronization. ; attribute that participates in components synchronization.
(and (= (:type operation) :set) (and (= (:type operation) :set)
(get ctk/sync-attrs (:attr operation)))) (ctk/component-attr? (:attr operation))))
any-sync? (some need-sync? operations)] any-sync? (some need-sync? operations)]
(when any-sync? (when any-sync?
(if page-id (if page-id

View file

@ -27,6 +27,7 @@
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.types.token :as cto]
[app.common.types.typography :as cty] [app.common.types.typography :as cty]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[clojure.set :as set] [clojure.set :as set]
@ -1479,6 +1480,44 @@
[{:type :set-remote-synced [{:type :set-remote-synced
:remote-synced (:remote-synced shape)}]})))))) :remote-synced (:remote-synced shape)}]}))))))
(defn- update-tokens
"Token synchronization algorithm. Copy the applied tokens that have changed
in the origin shape to the dest shape (applying or removing as necessary).
Only the given token attributes are synced."
[changes container dest-shape orig-shape token-attrs]
(let [orig-tokens (get orig-shape :applied-tokens {})
dest-tokens (get dest-shape :applied-tokens {})
dest-tokens' (reduce (fn [dest-tokens' token-attr]
(let [orig-token (get orig-tokens token-attr)
dest-token (get dest-tokens token-attr)]
(if (= orig-token dest-token)
dest-tokens'
(if (nil? orig-token)
(dissoc dest-tokens' token-attr)
(assoc dest-tokens' token-attr orig-token)))))
dest-tokens
token-attrs)]
(if (= dest-tokens dest-tokens')
changes
(-> changes
(update :redo-changes conj (make-change
container
{:type :mod-obj
:id (:id dest-shape)
:operations [{:type :set
:attr :applied-tokens
:val dest-tokens'
:ignore-touched true}]}))
(update :undo-changes conj (make-change
container
{:type :mod-obj
:id (:id dest-shape)
:operations [{:type :set
:attr :applied-tokens
:val dest-tokens
:ignore-touched true}]}))))))
(defn- update-attrs (defn- update-attrs
"The main function that implements the attribute sync algorithm. Copy "The main function that implements the attribute sync algorithm. Copy
attributes that have changed in the origin shape to the dest shape. attributes that have changed in the origin shape to the dest shape.
@ -1511,37 +1550,41 @@
(loop [attrs (->> (seq (keys ctk/sync-attrs)) (loop [attrs (->> (seq (keys ctk/sync-attrs))
;; We don't update the flex-child attrs ;; We don't update the flex-child attrs
(remove ctk/swap-keep-attrs) (remove ctk/swap-keep-attrs)
;; We don't do automatic update of the `layout-grid-cells` property. ;; We don't do automatic update of the `layout-grid-cells` property.
(remove #(= :layout-grid-cells %))) (remove #(= :layout-grid-cells %)))
applied-tokens #{}
roperations [] roperations []
uoperations '()] uoperations '()]
(let [attr (first attrs)] (let [attr (first attrs)]
(if (nil? attr) (if (nil? attr)
(if (empty? roperations) (if (and (empty? roperations) (empty? applied-tokens))
changes changes
(let [all-parents (cfh/get-parent-ids (:objects container) (let [all-parents (cfh/get-parent-ids (:objects container)
(:id dest-shape))] (:id dest-shape))]
(-> changes (cond-> changes
(update :redo-changes conj (make-change (seq roperations)
container (-> (update :redo-changes conj (make-change
{:type :mod-obj container
:id (:id dest-shape) {:type :mod-obj
:operations roperations})) :id (:id dest-shape)
(update :redo-changes conj (make-change :operations roperations}))
container (update :redo-changes conj (make-change
{:type :reg-objects container
:shapes all-parents})) {:type :reg-objects
(update :undo-changes conj (make-change :shapes all-parents}))
container (update :undo-changes conj (make-change
{:type :mod-obj container
:id (:id dest-shape) {:type :mod-obj
:operations (vec uoperations)})) :id (:id dest-shape)
(update :undo-changes concat [(make-change :operations (vec uoperations)}))
container (update :undo-changes concat [(make-change
{:type :reg-objects container
:shapes all-parents})])))) {:type :reg-objects
:shapes all-parents})]))
(seq applied-tokens)
(update-tokens container dest-shape origin-shape applied-tokens))))
(let [;; position-data is a special case because can be affected by :geometry-group and :content-group (let [;; position-data is a special case because can be affected by :geometry-group and :content-group
;; so, if the position-data changes but the geometry is touched we need to reset the position-data ;; so, if the position-data changes but the geometry is touched we need to reset the position-data
;; so it's calculated again ;; so it's calculated again
@ -1564,14 +1607,21 @@
:val (get dest-shape attr) :val (get dest-shape attr)
:ignore-touched true} :ignore-touched true}
attr-group (get ctk/sync-attrs attr)] attr-group (get ctk/sync-attrs attr)
token-attrs (cto/shape-attr->token-attrs attr)
applied-tokens' (cond-> applied-tokens
(not (and (touched attr-group)
omit-touched?))
(into token-attrs))]
(if (or (= (get origin-shape attr) (get dest-shape attr)) (if (or (= (get origin-shape attr) (get dest-shape attr))
(and (touched attr-group) omit-touched?)) (and (touched attr-group) omit-touched?))
(recur (next attrs) (recur (next attrs)
applied-tokens'
roperations roperations
uoperations) uoperations)
(recur (next attrs) (recur (next attrs)
applied-tokens'
(conj roperations roperation) (conj roperations roperation)
(conj uoperations uoperation))))))))) (conj uoperations uoperation)))))))))

View file

@ -0,0 +1,82 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.test-helpers.tokens
(:require
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.shapes :as ths]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]))
(defn add-tokens-lib
[file]
(ctf/update-file-data file #(update % :tokens-lib ctob/ensure-tokens-lib)))
(defn update-tokens-lib
[file f]
(ctf/update-file-data file #(update % :tokens-lib f)))
(defn- set-stroke-width
[shape stroke-width]
(let [strokes (if (seq (:strokes shape))
(:strokes shape)
[{:stroke-style :solid
:stroke-alignment :inner
:stroke-width 1
:stroke-color "#000000"
:stroke-opacity 1}])
new-strokes (update strokes 0 assoc :stroke-width stroke-width)]
(ctn/set-shape-attr shape :strokes new-strokes {:ignore-touched true})))
(defn- set-stroke-color
[shape stroke-color]
(let [strokes (if (seq (:strokes shape))
(:strokes shape)
[{:stroke-style :solid
:stroke-alignment :inner
:stroke-width 1
:stroke-color "#000000"
:stroke-opacity 1}])
new-strokes (update strokes 0 assoc :stroke-color stroke-color)]
(ctn/set-shape-attr shape :strokes new-strokes {:ignore-touched true})))
(defn- set-fill-color
[shape fill-color]
(let [fills (if (seq (:fills shape))
(:fills shape)
[{:fill-color "#000000"
:fill-opacity 1}])
new-fills (update fills 0 assoc :fill-color fill-color)]
(ctn/set-shape-attr shape :fills new-fills {:ignore-touched true})))
(defn apply-token-to-shape
[file shape-label token-name token-attrs shape-attrs resolved-value]
(let [page (thf/current-page file)
shape (ths/get-shape file shape-label)
shape' (as-> shape $
(cto/apply-token-to-shape {:shape $
:token {:name token-name}
:attributes token-attrs})
(reduce (fn [shape attr]
(case attr
:stroke-width (set-stroke-width shape resolved-value)
:stroke-color (set-stroke-color shape resolved-value)
:fill (set-fill-color shape resolved-value)
(ctn/set-shape-attr shape attr resolved-value {:ignore-touched true})))
$
shape-attrs))]
(ctf/update-file-data
file
(fn [file-data]
(ctpl/update-page file-data
(:id page)
#(ctst/set-shape % shape'))))))

View file

@ -140,6 +140,14 @@
:layout-item-z-index :layout-item-z-index
:layout-item-align-self}) :layout-item-align-self})
(defn component-attr?
"Check if some attribute is one that is involved in component syncrhonization.
Note that design tokens also are involved, although they go by an alternate
route and thus they are not part of :sync-attrs."
[attr]
(or (get sync-attrs attr)
(= :applied-tokens attr)))
(defn instance-root? (defn instance-root?
"Check if this shape is the head of a top instance." "Check if this shape is the head of a top instance."
[shape] [shape]

View file

@ -18,6 +18,7 @@
[app.common.types.plugins :as ctpg] [app.common.types.plugins :as ctpg]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.types.token :as ctt]
[app.common.uuid :as uuid])) [app.common.uuid :as uuid]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -540,14 +541,28 @@
;; --- SHAPE UPDATE ;; --- SHAPE UPDATE
(defn- get-token-groups
[shape new-applied-tokens]
(let [old-applied-tokens (d/nilv (:applied-tokens shape) #{})
changed-token-attrs (filter #(not= (get old-applied-tokens %) (get new-applied-tokens %))
ctt/all-keys)
changed-groups (into #{}
(comp (map ctt/token-attr->shape-attr)
(map #(get ctk/sync-attrs %))
(filter some?))
changed-token-attrs)]
changed-groups))
(defn set-shape-attr (defn set-shape-attr
"Assign attribute to shape with touched logic. "Assign attribute to shape with touched logic.
The returned shape will contain a metadata associated with it The returned shape will contain a metadata associated with it
indicating if shape is touched or not." indicating if shape is touched or not."
[shape attr val & {:keys [ignore-touched ignore-geometry]}] [shape attr val & {:keys [ignore-touched ignore-geometry]}]
(let [group (get ctk/sync-attrs attr) (let [group (get ctk/sync-attrs attr)
shape-val (get shape attr) token-groups (when (= attr :applied-tokens)
(get-token-groups shape val))
shape-val (get shape attr)
ignore? ignore?
(or ignore-touched (or ignore-touched
@ -585,9 +600,15 @@
;; set the "touched" flag for the group the attribute belongs to. ;; set the "touched" flag for the group the attribute belongs to.
;; In some cases we need to ignore touched only if the attribute is ;; In some cases we need to ignore touched only if the attribute is
;; geometric (position, width or transformation). ;; geometric (position, width or transformation).
(and in-copy? group (not ignore?) (not equal?) (and in-copy?
(not (and ignore-geometry is-geometry?))) (or (and group (not equal?)) (seq token-groups))
(-> (update :touched ctk/set-touched-group group) (not ignore?) (not (and ignore-geometry is-geometry?)))
(-> (update :touched (fn [touched]
(reduce #(ctk/set-touched-group %1 %2)
touched
(if group
(cons group token-groups)
token-groups))))
(dissoc :remote-synced)) (dissoc :remote-synced))
(nil? val) (nil? val)

View file

@ -6,8 +6,10 @@
(ns app.common.types.token (ns app.common.types.token
(:require (:require
[app.common.data :as d]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.schema.registry :as sr] [app.common.schema.registry :as sr]
[clojure.data :as data]
[clojure.set :as set] [clojure.set :as set]
[malli.util :as mu])) [malli.util :as mu]))
@ -150,6 +152,15 @@
(def rotation-keys (schema-keys ::rotation)) (def rotation-keys (schema-keys ::rotation))
(def all-keys (set/union color-keys
border-radius-keys
stroke-width-keys
sizing-keys
opacity-keys
spacing-keys
dimensions-keys
rotation-keys))
(sm/register! (sm/register!
^{::sm/type ::tokens} ^{::sm/type ::tokens}
[:map {:title "Applied Tokens"}]) [:map {:title "Applied Tokens"}])
@ -163,3 +174,58 @@
::spacing ::spacing
::rotation ::rotation
::dimensions]) ::dimensions])
(defn shape-attr->token-attrs
[shape-attr]
(cond
(= :fills shape-attr) #{:fill}
(= :strokes shape-attr) #{:stroke-color :stroke-width}
(border-radius-keys shape-attr) #{shape-attr}
(sizing-keys shape-attr) #{shape-attr}
(opacity-keys shape-attr) #{shape-attr}
(spacing-keys shape-attr) #{shape-attr}
(rotation-keys shape-attr) #{shape-attr}))
(defn token-attr->shape-attr
[token-attr]
(case token-attr
:fill :fills
:stroke-color :strokes
:stroke-width :strokes
token-attr))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKENS IN SHAPES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- toggle-or-apply-token
"Remove any shape attributes from token if they exists.
Othewise apply token attributes."
[shape token]
(let [[shape-leftover token-leftover _matching] (data/diff (:applied-tokens shape) token)]
(merge {} shape-leftover token-leftover)))
(defn- token-from-attributes [token attributes]
(->> (map (fn [attr] [attr (:name token)]) attributes)
(into {})))
(defn- apply-token-to-attributes [{:keys [shape token attributes]}]
(let [token (token-from-attributes token attributes)]
(toggle-or-apply-token shape token)))
(defn apply-token-to-shape
[{:keys [shape token attributes] :as _props}]
(let [applied-tokens (apply-token-to-attributes {:shape shape
:token token
:attributes attributes})]
(update shape :applied-tokens #(merge % applied-tokens))))
(defn maybe-apply-token-to-shape
"When the passed `:token` is non-nil apply it to the `:applied-tokens` on a shape."
[{:keys [shape token _attributes] :as props}]
(if token
(apply-token-to-shape props)
shape))
(defn unapply-token-id [shape attributes]
(update shape :applied-tokens d/without-keys attributes))

View file

@ -634,6 +634,11 @@ When `before-set-name` is nil, move set to bottom")
(delete-token-from-set [_ set-name token-name] "delete a token from a set") (delete-token-from-set [_ set-name token-name] "delete a token from a set")
(toggle-set-in-theme [_ group-name theme-name set-name] "toggle a set used / not used in a theme") (toggle-set-in-theme [_ group-name theme-name set-name] "toggle a set used / not used in a theme")
(get-active-themes-set-names [_] "set of set names that are active in the the active themes") (get-active-themes-set-names [_] "set of set names that are active in the the active themes")
(sets-at-path-all-active? [_ prefixed-path] "compute active state for child sets at `prefixed-path`.
Will return a value that matches this schema:
`:none` None of the nested sets are active
`:all` All of the nested sets are active
`:partial` Mixed active state of nested sets")
(get-active-themes-set-tokens [_] "set of set names that are active in the the active themes") (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") (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-dtcg-json [_ parsed-json] "Decodes parsed json containing tokens and converts to library")
@ -894,6 +899,19 @@ When `before-set-name` is nil, move set to bottom")
(mapcat :sets) (mapcat :sets)
(get-active-themes this))) (get-active-themes this)))
(sets-at-path-all-active? [this prefixed-path]
(let [active-set-names (get-active-themes-set-names this)]
(if (seq active-set-names)
(let [path-active-set-names (->> (get-sets-at-prefix-path this prefixed-path)
(map :name)
(into #{}))
difference (set/difference path-active-set-names active-set-names)]
(cond
(empty? difference) :all
(seq (set/intersection path-active-set-names active-set-names)) :partial
:else :none))
:none)))
(get-active-themes-set-tokens [this] (get-active-themes-set-tokens [this]
(let [sets-order (get-ordered-set-names this) (let [sets-order (get-ordered-set-names this)
active-themes (get-active-themes this) active-themes (get-active-themes this)

View file

@ -193,7 +193,6 @@
(ths/add-sample-shape :free-shape)) (ths/add-sample-shape :free-shape))
page (thf/current-page file) page (thf/current-page file)
main-root (ths/get-shape file :main-root)
;; ==== Action ;; ==== Action
changes1 (cls/generate-relocate (pcb/empty-changes) changes1 (cls/generate-relocate (pcb/empty-changes)
@ -203,9 +202,6 @@
0 ; to-index 0 ; to-index
#{(thi/id :free-shape)}) ; ids #{(thi/id :free-shape)}) ; ids
updated-file (thf/apply-changes file changes1) updated-file (thf/apply-changes file changes1)
changes2 (cll/generate-sync-file-changes (pcb/empty-changes) changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
@ -491,4 +487,4 @@
(t/is (= (:fill-color fill') "#fabada")) (t/is (= (:fill-color fill') "#fabada"))
(t/is (= (:fill-opacity fill') 1)) (t/is (= (:fill-opacity fill') 1))
(t/is (= (:touched copy2-root') nil)) (t/is (= (:touched copy2-root') nil))
(t/is (= (:touched copy2-child') nil)))) (t/is (= (:touched copy2-child') nil))))

View file

@ -417,8 +417,39 @@
expected-tokens (ctob/get-active-themes-set-tokens tokens-lib) expected-tokens (ctob/get-active-themes-set-tokens tokens-lib)
expected-token-names (mapv key expected-tokens)] expected-token-names (mapv key expected-tokens)]
(t/is (= '("set-a" "set-b" "inactive-set") expected-order)) (t/is (= '("set-a" "set-b" "inactive-set") expected-order))
(t/is (= ["set-a-token" "set-b-token"] expected-token-names))))) (t/is (= ["set-a-token" "set-b-token"] expected-token-names))))
(t/testing "sets-at-path-active-state"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/bam"))
(ctob/add-theme (ctob/make-token-theme :name "none"))
(ctob/add-theme (ctob/make-token-theme :name "partial"
:sets #{"foo/bar/baz"}))
(ctob/add-theme (ctob/make-token-theme :name "all"
:sets #{"foo/bar/baz"
"foo/bar/bam"}))
(ctob/add-theme (ctob/make-token-theme :name "invalid"
:sets #{"foo/missing"})))
expected-none (-> tokens-lib
(ctob/set-active-themes #{"/none"})
(ctob/sets-at-path-all-active? "G-foo"))
expected-all (-> tokens-lib
(ctob/set-active-themes #{"/all"})
(ctob/sets-at-path-all-active? "G-foo"))
expected-partial (-> tokens-lib
(ctob/set-active-themes #{"/partial"})
(ctob/sets-at-path-all-active? "G-foo"))
expected-invalid-none (-> tokens-lib
(ctob/set-active-themes #{"/invalid"})
(ctob/sets-at-path-all-active? "G-foo"))]
(t/is (= :none expected-none))
(t/is (= :all expected-all))
(t/is (= :partial expected-partial))
(t/is (= :none expected-invalid-none)))))
(t/deftest token-theme-in-a-lib (t/deftest token-theme-in-a-lib
(t/testing "add-token-theme" (t/testing "add-token-theme"

View file

@ -6,7 +6,6 @@
(ns app.main.data.tokens (ns app.main.data.tokens
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
@ -15,11 +14,9 @@
[app.main.data.changes :as dch] [app.main.data.changes :as dch]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-set :as wtts] [app.main.ui.workspace.tokens.token-set :as wtts]
[app.main.ui.workspace.tokens.update :as wtu] [app.main.ui.workspace.tokens.update :as wtu]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[clojure.data :as data]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
@ -51,38 +48,6 @@
;; TOKENS Actions ;; TOKENS Actions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn toggle-or-apply-token
"Remove any shape attributes from token if they exists.
Othewise apply token attributes."
[shape token]
(let [[shape-leftover token-leftover _matching] (data/diff (:applied-tokens shape) token)]
(merge {} shape-leftover token-leftover)))
(defn token-from-attributes [token attributes]
(->> (map (fn [attr] [attr (wtt/token-identifier token)]) attributes)
(into {})))
(defn unapply-token-id [shape attributes]
(update shape :applied-tokens d/without-keys attributes))
(defn apply-token-to-attributes [{:keys [shape token attributes]}]
(let [token (token-from-attributes token attributes)]
(toggle-or-apply-token shape token)))
(defn apply-token-to-shape
[{:keys [shape token attributes] :as _props}]
(let [applied-tokens (apply-token-to-attributes {:shape shape
:token token
:attributes attributes})]
(update shape :applied-tokens #(merge % applied-tokens))))
(defn maybe-apply-token-to-shape
"When the passed `:token` is non-nil apply it to the `:applied-tokens` on a shape."
[{:keys [shape token _attributes] :as props}]
(if token
(apply-token-to-shape props)
shape))
(defn get-token-data-from-token-id (defn get-token-data-from-token-id
[id] [id]
(let [workspace-data (deref refs/workspace-data)] (let [workspace-data (deref refs/workspace-data)]

View file

@ -82,7 +82,7 @@
(assoc-in [:workspace-global :picked-shift?] shift?))))) (assoc-in [:workspace-global :picked-shift?] shift?)))))
(defn transform-fill (defn transform-fill
[state ids color transform] [state ids color transform & options]
(let [objects (wsh/lookup-page-objects state) (let [objects (wsh/lookup-page-objects state)
is-text? #(= :text (:type (get objects %))) is-text? #(= :text (:type (get objects %)))
@ -118,8 +118,8 @@
(rx/concat (rx/concat
(rx/of (dwu/start-undo-transaction undo-id)) (rx/of (dwu/start-undo-transaction undo-id))
(rx/from (map #(dwt/update-text-with-function % transform-attrs) text-ids)) (rx/from (map #(apply dwt/update-text-with-function % transform-attrs options) text-ids))
(rx/of (dwsh/update-shapes shape-ids transform-attrs)) (rx/of (dwsh/update-shapes shape-ids transform-attrs options))
(rx/of (dwu/commit-undo-transaction undo-id))))) (rx/of (dwu/commit-undo-transaction undo-id)))))
(defn swap-attrs [shape attr index new-index] (defn swap-attrs [shape attr index new-index]
@ -146,7 +146,7 @@
(rx/of (dwsh/update-shapes shape-ids transform-attrs))))))) (rx/of (dwsh/update-shapes shape-ids transform-attrs)))))))
(defn change-fill (defn change-fill
[ids color position] [ids color position & options]
(ptk/reify ::change-fill (ptk/reify ::change-fill
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
@ -155,18 +155,18 @@
(cond-> (not (contains? shape :fills)) (cond-> (not (contains? shape :fills))
(assoc :fills [])) (assoc :fills []))
(assoc-in [:fills position] (into {} attrs))))] (assoc-in [:fills position] (into {} attrs))))]
(transform-fill state ids color change-fn))))) (apply transform-fill state ids color change-fn options)))))
(defn change-fill-and-clear (defn change-fill-and-clear
[ids color] [ids color & options]
(ptk/reify ::change-fill-and-clear (ptk/reify ::change-fill-and-clear
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [set (fn [shape attrs] (assoc shape :fills [attrs]))] (let [set (fn [shape attrs] (assoc shape :fills [attrs]))]
(transform-fill state ids color set))))) (apply transform-fill state ids color set options)))))
(defn add-fill (defn add-fill
[ids color] [ids color & options]
(dm/assert! (dm/assert!
"expected a valid color struct" "expected a valid color struct"
@ -182,10 +182,10 @@
(let [add (fn [shape attrs] (let [add (fn [shape attrs]
(-> shape (-> shape
(update :fills #(into [attrs] %))))] (update :fills #(into [attrs] %))))]
(transform-fill state ids color add))))) (apply transform-fill state ids color add options)))))
(defn remove-fill (defn remove-fill
[ids color position] [ids color position & options]
(dm/assert! (dm/assert!
"expected a valid color struct" "expected a valid color struct"
@ -203,10 +203,10 @@
(mapv second))) (mapv second)))
remove (fn [shape _] (update shape :fills remove-fill-by-index position))] remove (fn [shape _] (update shape :fills remove-fill-by-index position))]
(transform-fill state ids color remove))))) (apply transform-fill state ids color remove options)))))
(defn remove-all-fills (defn remove-all-fills
[ids color] [ids color & options]
(dm/assert! (dm/assert!
"expected a valid color struct" "expected a valid color struct"
@ -220,7 +220,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [remove-all (fn [shape _] (assoc shape :fills []))] (let [remove-all (fn [shape _] (assoc shape :fills []))]
(transform-fill state ids color remove-all))))) (apply transform-fill state ids color remove-all options)))))
(defn change-hide-fill-on-export (defn change-hide-fill-on-export
[ids hide-fill-on-export] [ids hide-fill-on-export]
@ -237,7 +237,7 @@
(d/merge shape attrs) (d/merge shape attrs)
shape)))))))) shape))))))))
(defn change-stroke (defn change-stroke
[ids attrs index] [ids attrs index & options]
(ptk/reify ::change-stroke (ptk/reify ::change-stroke
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
@ -286,7 +286,8 @@
(assoc :strokes []) (assoc :strokes [])
:always :always
(assoc-in [:strokes index] new-attrs)))))))))) (assoc-in [:strokes index] new-attrs))))
options))))))
(defn change-shadow (defn change-shadow
[ids attrs index] [ids attrs index]

View file

@ -786,7 +786,6 @@
(rx/map #(reset-component %) (rx/from ids)) (rx/map #(reset-component %) (rx/from ids))
(rx/of (dwu/commit-undo-transaction undo-id))))))) (rx/of (dwu/commit-undo-transaction undo-id)))))))
(defn update-component (defn update-component
"Modify the component linked to the shape with the given id, in the "Modify the component linked to the shape with the given id, in the
current page, so that all attributes of its shapes are equal to the current page, so that all attributes of its shapes are equal to the

View file

@ -465,8 +465,10 @@
([] ([]
(apply-modifiers nil)) (apply-modifiers nil))
([{:keys [modifiers undo-transation? stack-undo? ignore-constraints ignore-snap-pixel undo-group] ([{:keys [modifiers undo-transation? stack-undo? ignore-constraints
:or {undo-transation? true stack-undo? false ignore-constraints false ignore-snap-pixel false}}] ignore-snap-pixel ignore-touched undo-group]
:or {undo-transation? true stack-undo? false ignore-constraints false
ignore-snap-pixel false ignore-touched false}}]
(ptk/reify ::apply-modifiers (ptk/reify ::apply-modifiers
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
@ -515,6 +517,7 @@
{:reg-objects? true {:reg-objects? true
:stack-undo? stack-undo? :stack-undo? stack-undo?
:ignore-tree ignore-tree :ignore-tree ignore-tree
:ignore-touched ignore-touched
:undo-group undo-group :undo-group undo-group
;; Attributes that can change in the transform. This way we don't have to check ;; Attributes that can change in the transform. This way we don't have to check
;; all the attributes ;; all the attributes

View file

@ -260,13 +260,13 @@
(rx/of (with-meta event (meta it))))))))) (rx/of (with-meta event (meta it)))))))))
(defn update-layout (defn update-layout
[ids changes] [ids changes & options]
(ptk/reify ::update-layout (ptk/reify ::update-layout
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(let [undo-id (js/Symbol)] (let [undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(dwsh/update-shapes ids (d/patch-object changes)) (dwsh/update-shapes ids (d/patch-object changes) options)
(ptk/data-event :layout/update {:ids ids}) (ptk/data-event :layout/update {:ids ids})
(dwu/commit-undo-transaction undo-id)))))) (dwu/commit-undo-transaction undo-id))))))
@ -516,7 +516,7 @@
(assoc :layout-item-v-sizing :fix)))) (assoc :layout-item-v-sizing :fix))))
(defn update-layout-child (defn update-layout-child
[ids changes] [ids changes & options]
(ptk/reify ::update-layout-child (ptk/reify ::update-layout-child
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
@ -525,8 +525,8 @@
parent-ids (->> ids (map #(cfh/get-parent-id objects %))) parent-ids (->> ids (map #(cfh/get-parent-id objects %)))
undo-id (js/Symbol)] undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(dwsh/update-shapes ids (d/patch-object changes)) (dwsh/update-shapes ids (d/patch-object changes) options)
(dwsh/update-shapes children-ids (partial fix-child-sizing objects changes)) (dwsh/update-shapes children-ids (partial fix-child-sizing objects changes) options)
(dwsh/update-shapes (dwsh/update-shapes
parent-ids parent-ids
(fn [parent objects] (fn [parent objects]
@ -534,7 +534,7 @@
(fix-parent-sizing objects (set ids) changes) (fix-parent-sizing objects (set ids) changes)
(cond-> (ctl/grid-layout? parent) (cond-> (ctl/grid-layout? parent)
(ctl/assign-cells objects)))) (ctl/assign-cells objects))))
{:with-objects? true}) (merge options {:with-objects? true}))
(ptk/data-event :layout/update {:ids ids}) (ptk/data-event :layout/update {:ids ids})
(dwu/commit-undo-transaction undo-id)))))) (dwu/commit-undo-transaction undo-id))))))

View file

@ -432,7 +432,7 @@
(txt/transform-nodes (some-fn txt/is-text-node? txt/is-paragraph-node?) migrate-node content)) (txt/transform-nodes (some-fn txt/is-text-node? txt/is-paragraph-node?) migrate-node content))
(defn update-text-with-function (defn update-text-with-function
[id update-node-fn] [id update-node-fn & options]
(ptk/reify ::update-text-with-function (ptk/reify ::update-text-with-function
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@ -464,7 +464,7 @@
(-> shape (-> shape
(dissoc :fills) (dissoc :fills)
(d/update-when :content update-content)))] (d/update-when :content update-content)))]
(rx/of (dwsh/update-shapes shape-ids update-shape))))) (rx/of (dwsh/update-shapes shape-ids update-shape options)))))
ptk/EffectEvent ptk/EffectEvent
(effect [_ state _] (effect [_ state _]

View file

@ -301,7 +301,7 @@
(defn update-dimensions (defn update-dimensions
"Change size of shapes, from the sideber options form. "Change size of shapes, from the sideber options form.
Will ignore pixel snap used in the options side panel" Will ignore pixel snap used in the options side panel"
[ids attr value] [ids attr value & options]
(dm/assert! (number? value)) (dm/assert! (number? value))
(dm/assert! (dm/assert!
"expected valid coll of uuids" "expected valid coll of uuids"
@ -324,7 +324,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (dwm/apply-modifiers))))) (rx/of (dwm/apply-modifiers options)))))
(defn change-orientation (defn change-orientation
"Change orientation of shapes, from the sidebar options form. "Change orientation of shapes, from the sidebar options form.
@ -402,7 +402,7 @@
"Rotate shapes a fixed angle, from a keyboard action." "Rotate shapes a fixed angle, from a keyboard action."
([ids rotation] ([ids rotation]
(increase-rotation ids rotation nil)) (increase-rotation ids rotation nil))
([ids rotation params] ([ids rotation params & options]
(ptk/reify ::increase-rotation (ptk/reify ::increase-rotation
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
@ -411,7 +411,7 @@
shapes (->> ids (map #(get objects %)))] shapes (->> ids (map #(get objects %)))]
(rx/concat (rx/concat
(rx/of (dwm/set-delta-rotation-modifiers rotation shapes params)) (rx/of (dwm/set-delta-rotation-modifiers rotation shapes params))
(rx/of (dwm/apply-modifiers)))))))) (rx/of (dwm/apply-modifiers options))))))))
;; -- Move ---------------------------------------------------------- ;; -- Move ----------------------------------------------------------

View file

@ -502,6 +502,14 @@
(def workspace-active-theme-paths (def workspace-active-theme-paths
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))
(defn token-sets-at-path-all-active?
[prefixed-path]
(l/derived
(fn [lib]
(when lib
(ctob/sets-at-path-all-active? lib prefixed-path)))
tokens-lib))
(def workspace-active-theme-paths-no-hidden (def workspace-active-theme-paths-no-hidden
(l/derived #(disj % ctob/hidden-token-theme-path) workspace-active-theme-paths)) (l/derived #(disj % ctob/hidden-token-theme-path) workspace-active-theme-paths))

View file

@ -13,9 +13,9 @@
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.types.shape.radius :as ctsr] [app.common.types.shape.radius :as ctsr]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.main.constants :refer [size-presets]] [app.main.constants :refer [size-presets]]
[app.main.data.tokens :as dt]
[app.main.data.workspace :as udw] [app.main.data.workspace :as udw]
[app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
@ -342,7 +342,7 @@
(let [token-value (wtc/maybe-resolve-token-value token)] (let [token-value (wtc/maybe-resolve-token-value token)]
(st/emit! (st/emit!
(change-radius (fn [shape] (change-radius (fn [shape]
(-> (dt/unapply-token-id shape (wtty/token-attributes :border-radius)) (-> (cto/unapply-token-id shape (wtty/token-attributes :border-radius))
(ctsr/set-radius-1 token-value)))))))) (ctsr/set-radius-1 token-value))))))))
on-radius-1-change on-radius-1-change
@ -352,9 +352,9 @@
(let [token-value (wtc/maybe-resolve-token-value value)] (let [token-value (wtc/maybe-resolve-token-value value)]
(st/emit! (st/emit!
(change-radius (fn [shape] (change-radius (fn [shape]
(-> (dt/maybe-apply-token-to-shape {:token (when token-value value) (-> (cto/maybe-apply-token-to-shape {:token (when token-value value)
:shape shape :shape shape
:attributes (wtty/token-attributes :border-radius)}) :attributes (wtty/token-attributes :border-radius)})
(ctsr/set-radius-1 (or token-value value))))))))) (ctsr/set-radius-1 (or token-value value)))))))))
on-radius-multi-change on-radius-multi-change

View file

@ -95,20 +95,9 @@
(when (ctsr/has-radius? shape) (when (ctsr/has-radius? shape)
(ctsr/set-radius-1 shape value))) (ctsr/set-radius-1 shape value)))
{:reg-objects? true {:reg-objects? true
:ignore-touched true
:attrs ctt/border-radius-keys})) :attrs ctt/border-radius-keys}))
(defn update-opacity [value shape-ids]
(when (<= 0 value 1)
(dwsh/update-shapes shape-ids #(assoc % :opacity value))))
(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)))))
(defn update-shape-radius-single-corner [value shape-ids attributes] (defn update-shape-radius-single-corner [value shape-ids attributes]
(dwsh/update-shapes shape-ids (dwsh/update-shapes shape-ids
(fn [shape] (fn [shape]
@ -117,8 +106,23 @@
(:rx shape) (ctsr/switch-to-radius-4) (:rx shape) (ctsr/switch-to-radius-4)
:always (ctsr/set-radius-4 (first attributes) value)))) :always (ctsr/set-radius-4 (first attributes) value))))
{:reg-objects? true {:reg-objects? true
:ignore-touched true
:attrs [:rx :ry :r1 :r2 :r3 :r4]})) :attrs [:rx :ry :r1 :r2 :r3 :r4]}))
(defn update-opacity [value shape-ids]
(when (<= 0 value 1)
(dwsh/update-shapes shape-ids
#(assoc % :opacity value)
{:ignore-touched true})))
(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)))))
(defn update-stroke-width (defn update-stroke-width
[value shape-ids] [value shape-ids]
(dwsh/update-shapes shape-ids (dwsh/update-shapes shape-ids
@ -126,6 +130,7 @@
(when (seq (:strokes shape)) (when (seq (:strokes shape))
(assoc-in shape [:strokes 0 :stroke-width] value))) (assoc-in shape [:strokes 0 :stroke-width] value)))
{:reg-objects? true {:reg-objects? true
:ignore-touched true
:attrs [:strokes]})) :attrs [:strokes]}))
(defn update-color [f value shape-ids] (defn update-color [f value shape-ids]
@ -133,7 +138,7 @@
(tinycolor/valid-color) (tinycolor/valid-color)
(tinycolor/->hex) (tinycolor/->hex)
(str "#"))] (str "#"))]
(f shape-ids {:color color} 0))) (apply f shape-ids {:color color} 0 [:ignore-touched true])))
(defn update-fill (defn update-fill
[value shape-ids] [value shape-ids]
@ -156,8 +161,8 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (rx/of
(when (:width attributes) (dwt/update-dimensions shape-ids :width value)) (when (:width attributes) (dwt/update-dimensions shape-ids :width value :ignore-touched true))
(when (:height attributes) (dwt/update-dimensions shape-ids :height value)))))) (when (:height attributes) (dwt/update-dimensions shape-ids :height value :ignore-touched true))))))
(defn- attributes->layout-gap [attributes value] (defn- attributes->layout-gap [attributes value]
(let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap}) (let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap})
@ -165,7 +170,9 @@
{:layout-gap layout-gap})) {:layout-gap layout-gap}))
(defn update-layout-padding [value shape-ids attrs] (defn update-layout-padding [value shape-ids attrs]
(dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat value))})) (dwsl/update-layout shape-ids
{:layout-padding (zipmap attrs (repeat value))}
:ignore-touched true))
(defn update-layout-spacing [value shape-ids attributes] (defn update-layout-spacing [value shape-ids attributes]
(ptk/reify ::update-layout-spacing (ptk/reify ::update-layout-spacing
@ -177,7 +184,9 @@
(map :id))) (map :id)))
layout-attributes (attributes->layout-gap attributes value)] layout-attributes (attributes->layout-gap attributes value)]
(rx/of (rx/of
(dwsl/update-layout layout-shape-ids layout-attributes)))))) (dwsl/update-layout layout-shape-ids
layout-attributes
:ignore-touched true))))))
(defn update-shape-position [value shape-ids attributes] (defn update-shape-position [value shape-ids attributes]
(ptk/reify ::update-shape-position (ptk/reify ::update-shape-position
@ -195,4 +204,4 @@
:layout-item-max-w value :layout-item-max-w value
:layout-item-max-h value} :layout-item-max-h value}
(select-keys attributes))] (select-keys attributes))]
(dwsl/update-layout-child shape-ids props))))) (dwsl/update-layout-child shape-ids props :ignore-touched true)))))

View file

@ -254,41 +254,41 @@
[{:keys [state set-state]}] [{:keys [state set-state]}]
(let [{:keys [theme-path]} @state (let [{:keys [theme-path]} @state
[_ theme-group theme-name] theme-path [_ theme-group theme-name] theme-path
ordered-token-sets (mf/deref refs/workspace-ordered-token-sets)
token-sets (mf/deref refs/workspace-token-sets-tree) token-sets (mf/deref refs/workspace-token-sets-tree)
theme (mf/deref (refs/workspace-token-theme theme-group theme-name)) theme (mf/deref (refs/workspace-token-theme theme-group theme-name))
theme-state (mf/use-state theme)
lib (-> (ctob/make-tokens-lib)
(ctob/add-theme @theme-state)
(ctob/add-sets ordered-token-sets)
(ctob/activate-theme (:group @theme-state) (:name @theme-state)))
;; Form / Modal handlers
on-back #(set-state (constantly {:type :themes-overview})) on-back #(set-state (constantly {:type :themes-overview}))
on-submit #(st/emit! (wdt/update-token-theme [(:group theme) (:name theme)] %)) on-submit #(st/emit! (wdt/update-token-theme [(:group theme) (:name theme)] %))
{:keys [dropdown-open? _on-open-dropdown on-close-dropdown on-toggle-dropdown]} (wtco/use-dropdown-open-state) {:keys [dropdown-open? _on-open-dropdown on-close-dropdown on-toggle-dropdown]} (wtco/use-dropdown-open-state)
theme-state (mf/use-state theme)
disabled? (-> (:name @theme-state) disabled? (-> (:name @theme-state)
(str/trim) (str/trim)
(str/empty?)) (str/empty?))
token-set-active? (mf/use-callback
(mf/deps theme-state) on-change-field
(fn [set-name] (mf/use-fn
(get-in @theme-state [:sets set-name]))) (fn [field value]
on-toggle-token-set (mf/use-callback (swap! theme-state #(assoc % field value))))
(mf/deps theme-state)
(fn [set-name] on-save-form
(swap! theme-state #(ctob/toggle-set % set-name)))) (mf/use-callback
on-click-token-set (mf/use-callback (mf/deps theme-state on-submit)
(mf/deps on-toggle-token-set) (fn [e]
(fn [prefixed-set-path-str] (dom/prevent-default e)
(let [set-name (ctob/prefixed-set-path-string->set-name-string prefixed-set-path-str)] (let [theme (-> @theme-state
(on-toggle-token-set set-name)))) (update :name str/trim)
on-change-field (fn [field value] (update :group str/trim)
(swap! theme-state #(assoc % field value))) (update :description str/trim))]
on-save-form (mf/use-callback (when-not (str/empty? (:name theme))
(mf/deps theme-state on-submit) (on-submit theme)))
(fn [e] (on-back)))
(dom/prevent-default e)
(let [theme (-> @theme-state
(update :name str/trim)
(update :group str/trim)
(update :description str/trim))]
(when-not (str/empty? (:name theme))
(on-submit theme)))
(on-back)))
close-modal close-modal
(mf/use-fn (mf/use-fn
(fn [e] (fn [e]
@ -300,7 +300,33 @@
(mf/deps theme on-back) (mf/deps theme on-back)
(fn [] (fn []
(st/emit! (wdt/delete-token-theme (:group theme) (:name theme))) (st/emit! (wdt/delete-token-theme (:group theme) (:name theme)))
(on-back)))] (on-back)))
;; Sets tree handlers
token-set-group-active?
(mf/use-callback
(mf/deps theme-state)
(fn [prefixed-path]
(ctob/sets-at-path-all-active? lib prefixed-path)))
token-set-active?
(mf/use-callback
(mf/deps theme-state)
(fn [set-name]
(get-in @theme-state [:sets set-name])))
on-toggle-token-set
(mf/use-callback
(mf/deps theme-state)
(fn [set-name]
(swap! theme-state #(ctob/toggle-set % set-name))))
on-click-token-set
(mf/use-callback
(mf/deps on-toggle-token-set)
(fn [prefixed-set-path-str]
(let [set-name (ctob/prefixed-set-path-string->set-name-string prefixed-set-path-str)]
(on-toggle-token-set set-name))))]
[:div {:class (stl/css :themes-modal-wrapper)} [:div {:class (stl/css :themes-modal-wrapper)}
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :themes-modal-title)} [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :themes-modal-title)}
@ -327,6 +353,7 @@
{:token-sets token-sets {:token-sets token-sets
:token-set-selected? (constantly false) :token-set-selected? (constantly false)
:token-set-active? token-set-active? :token-set-active? token-set-active?
:token-set-group-active? token-set-group-active?
:on-select on-click-token-set :on-select on-click-token-set
:on-toggle-token-set on-toggle-token-set :on-toggle-token-set on-toggle-token-set
:origin "theme-modal" :origin "theme-modal"

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.tokens.sets (ns app.main.ui.workspace.tokens.sets
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.main.data.tokens :as wdt] [app.main.data.tokens :as wdt]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -68,9 +69,28 @@
:auto-focus true :auto-focus true
:default-value default-value}])) :default-value default-value}]))
(mf/defc checkbox
[{:keys [checked aria-label on-click]}]
(let [all? (true? checked)
mixed? (= checked "mixed")
checked? (or all? mixed?)]
[:div {:role "checkbox"
:aria-checked (dm/str checked)
:tabindex 0
:class (stl/css-case :checkbox-style true
:checkbox-checked-style checked?)
:on-click on-click}
(when checked?
[:> icon*
{:aria-label aria-label
:class (stl/css :check-icon)
:size "s"
:id (if mixed? ic/remove ic/tick)}])]))
(mf/defc sets-tree-set-group (mf/defc sets-tree-set-group
[{:keys [label tree-depth tree-path selected? collapsed? editing? on-edit on-edit-reset on-edit-submit]}] [{:keys [label tree-depth tree-path active? selected? collapsed? editing? on-edit on-edit-reset on-edit-submit]}]
(let [editing?' (editing? tree-path) (let [editing?' (editing? tree-path)
active?' (active? tree-path)
on-context-menu on-context-menu
(mf/use-fn (mf/use-fn
(mf/deps editing? tree-path) (mf/deps editing? tree-path)
@ -114,9 +134,16 @@
:on-cancel on-edit-reset :on-cancel on-edit-reset
:on-create on-edit-reset :on-create on-edit-reset
:on-submit on-edit-submit'}] :on-submit on-edit-submit'}]
[:div {:class (stl/css :set-name) [:*
:on-double-click on-double-click} [:div {:class (stl/css :set-name)
label])])) :on-double-click on-double-click}
label]
[:& checkbox
{:checked (case active?'
:all true
:partial "mixed"
:none false)
:arial-label (tr "workspace.token.select-set")}]])]))
(mf/defc sets-tree-set (mf/defc sets-tree-set
[{:keys [set label tree-depth tree-path selected? on-select active? on-toggle editing? on-edit on-edit-reset on-edit-submit]}] [{:keys [set label tree-depth tree-path selected? on-select active? on-toggle editing? on-edit on-edit-reset on-edit-submit]}]
@ -173,18 +200,14 @@
[:div {:class (stl/css :set-name) [:div {:class (stl/css :set-name)
:on-double-click on-double-click} :on-double-click on-double-click}
label] label]
[:button {:type "button" [:& checkbox
:on-click on-checkbox-click {:on-click on-checkbox-click
:class (stl/css-case :checkbox-style true :arial-label (tr "workspace.token.select-set")
:checkbox-checked-style active?')} :checked active?'}]])]))
(when active?'
[:> icon* {:aria-label (tr "workspace.token.select-set")
:class (stl/css :check-icon)
:size "s"
:id ic/tick}])]])]))
(mf/defc sets-tree (mf/defc sets-tree
[{:keys [active? [{:keys [active?
group-active?
editing? editing?
on-edit on-edit
on-edit-reset on-edit-reset
@ -227,6 +250,7 @@
set-group? set-group?
[:& sets-tree-set-group [:& sets-tree-set-group
{:selected? (selected? tree-path) {:selected? (selected? tree-path)
:active? group-active?
:on-select on-select :on-select on-select
:label set-fname :label set-fname
:collapsed? collapsed? :collapsed? collapsed?
@ -249,6 +273,7 @@
:selected? selected? :selected? selected?
:on-toggle on-toggle :on-toggle on-toggle
:active? active? :active? active?
:group-active? group-active?
:editing? editing? :editing? editing?
:on-edit on-edit :on-edit on-edit
:on-edit-reset on-edit-reset :on-edit-reset on-edit-reset
@ -261,6 +286,7 @@
on-update-token-set-group on-update-token-set-group
token-set-selected? token-set-selected?
token-set-active? token-set-active?
token-set-group-active?
on-create-token-set on-create-token-set
on-toggle-token-set on-toggle-token-set
origin origin
@ -268,10 +294,9 @@
context] context]
:as _props}] :as _props}]
(let [{:keys [editing? new? on-edit on-reset] :as ctx} (or context (sets-context/use-context))] (let [{:keys [editing? new? on-edit on-reset] :as ctx} (or context (sets-context/use-context))]
[:ul {:class (stl/css :sets-list)} [:fieldset {:class (stl/css :sets-list)}
(if (and (if (and (= origin "theme-modal")
(= origin "theme-modal") (empty? token-sets))
(empty? token-sets))
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)} [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)}
(tr "workspace.token.no-sets-create")] (tr "workspace.token.no-sets-create")]
(if (and (= origin "theme-modal") (if (and (= origin "theme-modal")
@ -284,6 +309,7 @@
:selected? token-set-selected? :selected? token-set-selected?
:on-select on-select :on-select on-select
:active? token-set-active? :active? token-set-active?
:group-active? token-set-group-active?
:on-toggle on-toggle-token-set :on-toggle on-toggle-token-set
:editing? editing? :editing? editing?
:on-edit on-edit :on-edit on-edit
@ -314,11 +340,15 @@
token-set-active? (mf/use-fn token-set-active? (mf/use-fn
(mf/deps active-token-set-names) (mf/deps active-token-set-names)
(fn [set-name] (fn [set-name]
(get active-token-set-names set-name)))] (get active-token-set-names set-name)))
token-set-group-active? (mf/use-fn
(fn [prefixed-path]
@(refs/token-sets-at-path-all-active? prefixed-path)))]
[:& controlled-sets-list [:& controlled-sets-list
{:token-sets token-sets {:token-sets token-sets
:token-set-selected? token-set-selected? :token-set-selected? token-set-selected?
:token-set-active? token-set-active? :token-set-active? token-set-active?
:token-set-group-active? token-set-group-active?
:on-select on-select-token-set-click :on-select on-select-token-set-click
:origin "set-panel" :origin "set-panel"
:on-toggle-token-set on-toggle-token-set-click :on-toggle-token-set on-toggle-token-set-click

View file

@ -2,8 +2,8 @@
(:require (:require
[app.common.types.token :as ctt] [app.common.types.token :as ctt]
[app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.style-dictionary :as wtsd] [app.main.ui.workspace.tokens.style-dictionary :as wtsd]
[app.main.ui.workspace.tokens.token-set :as wtts] [app.main.ui.workspace.tokens.token-set :as wtts]
@ -112,8 +112,8 @@
update-infos))) update-infos)))
shapes-update-info)) shapes-update-info))
(defn update-tokens [resolved-tokens] (defn update-tokens [state resolved-tokens]
(->> @refs/workspace-page-objects (->> (wsh/lookup-page-objects state)
(collect-shapes-update-info resolved-tokens) (collect-shapes-update-info resolved-tokens)
(actionize-shapes-update-info))) (actionize-shapes-update-info)))
@ -131,5 +131,5 @@
(let [undo-id (js/Symbol)] (let [undo-id (js/Symbol)]
(rx/concat (rx/concat
(rx/of (dwu/start-undo-transaction undo-id)) (rx/of (dwu/start-undo-transaction undo-id))
(update-tokens sd-tokens) (update-tokens state sd-tokens)
(rx/of (dwu/commit-undo-transaction undo-id)))))))))) (rx/of (dwu/commit-undo-transaction undo-id))))))))))

View file

@ -57,7 +57,7 @@
(fn [cause] (fn [cause]
(js/console.log "[error]:" cause)) (js/console.log "[error]:" cause))
(fn [_] (fn [_]
(js/console.log "[complete]")))) #_(js/console.debug "[complete]"))))
(doseq [event events] (doseq [event events]
(ptk/emit! store event)) (ptk/emit! store event))

View file

@ -0,0 +1,397 @@
;; 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 frontend-tests.logic.components-and-tokens
(:require
[app.common.geom.point :as geom]
[app.common.math :as mth]
[app.common.test-helpers.components :as cthc]
[app.common.test-helpers.compositions :as ctho]
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.shapes :as cths]
[app.common.test-helpers.tokens :as ctht]
[app.common.types.tokens-lib :as ctob]
[app.main.data.tokens :as dt]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.update :as wtu]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
[frontend-tests.tokens.helpers.state :as tohs]
[frontend-tests.tokens.helpers.tokens :as toht]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
(defn- setup-base-file
[]
(-> (cthf/sample-file :file1)
(ctht/add-tokens-lib)
(ctht/update-tokens-lib #(-> %
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :name "test-theme"
:sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"})
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "test-token-1"
:type :border-radius
:value 25))
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "test-token-2"
:type :border-radius
:value 50))
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "test-token-3"
:type :border-radius
:value 75))))
(ctho/add-frame :frame1)
(ctht/apply-token-to-shape :frame1 "test-token-1" [:rx :ry] [:rx :ry] 25)))
(defn- setup-file-with-main
[]
(-> (setup-base-file)
(cthc/make-component :component1 :frame1)))
(defn- setup-file-with-copy
[]
(-> (setup-file-with-main)
(cthc/instantiate-component :component1 :c-frame1)))
(t/deftest create-component-with-token
(t/async
done
(let [;; ==== Setup
file (setup-base-file)
store (ths/setup-store file)
;; ==== Action
events
[(dws/select-shape (cthi/id :frame1))
(dwl/add-component)]]
(ths/run-store
store done events
(fn [new-state]
(let [;; ==== Get
file' (ths/get-file-from-store new-state)
frame1' (cths/get-shape file' :frame1)
tokens-frame1' (:applied-tokens frame1')]
;; ==== Check
(t/is (= (count tokens-frame1') 2))
(t/is (= (get tokens-frame1' :rx) "test-token-1"))
(t/is (= (get tokens-frame1' :ry) "test-token-1"))
(t/is (= (get frame1' :rx) 25))
(t/is (= (get frame1' :ry) 25))))))))
(t/deftest create-copy-with-token
(t/async
done
(let [;; ==== Setup
file (setup-file-with-main)
store (ths/setup-store file)
;; ==== Action
events
[(dwl/instantiate-component (:id file)
(cthi/id :component1)
(geom/point 0 0))]]
(ths/run-store
store done events
(fn [new-state]
(let [;; ==== Get
selected (wsh/lookup-selected new-state)
c-frame1' (wsh/lookup-shape new-state (first selected))
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
(t/is (= (count tokens-frame1') 2))
(t/is (= (get tokens-frame1' :rx) "test-token-1"))
(t/is (= (get tokens-frame1' :ry) "test-token-1"))
(t/is (= (get c-frame1' :rx) 25))
(t/is (= (get c-frame1' :ry) 25))))))))
(t/deftest change-token-in-main
(t/async
done
(let [;; ==== Setup
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
events [(wtch/apply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:rx :ry}
:token (toht/get-token file "test-token-2")
:on-update-shape wtch/update-shape-radius-all})]
step2 (fn [_]
(let [events2 [(dwl/sync-file (:id file) (:id file))]]
(ths/run-store
store done events2
(fn [new-state]
(let [;; ==== Get
file' (ths/get-file-from-store new-state)
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
(t/is (= (count tokens-frame1') 2))
(t/is (= (get tokens-frame1' :rx) "test-token-2"))
(t/is (= (get tokens-frame1' :ry) "test-token-2"))
(t/is (= (get c-frame1' :rx) 50))
(t/is (= (get c-frame1' :ry) 50)))))))]
(tohs/run-store-async
store step2 events identity))))
(t/deftest remove-token-in-main
(t/async
done
(let [;; ==== Setup
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
events [(wtch/unapply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:rx :ry}
:token (toht/get-token file "test-token-1")})]
step2 (fn [_]
(let [events2 [(dwl/sync-file (:id file) (:id file))]]
(ths/run-store
store done events2
(fn [new-state]
(let [;; ==== Get
file' (ths/get-file-from-store new-state)
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
(t/is (= (count tokens-frame1') 0))
(t/is (= (get c-frame1' :rx) 25))
(t/is (= (get c-frame1' :ry) 25)))))))]
(tohs/run-store-async
store step2 events identity))))
(t/deftest modify-token
(t/async
done
(let [;; ==== Setup
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
events [(dt/update-create-token {:token (ctob/make-token :name "test-token-1"
:type :border-radius
:value 66)
:prev-token-name "test-token-1"})]
step2 (fn [_]
(let [events2 [(wtu/update-workspace-tokens)
(dwl/sync-file (:id file) (:id file))]]
(tohs/run-store-async
store done events2
(fn [new-state]
(let [;; ==== Get
file' (ths/get-file-from-store new-state)
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
(t/is (= (count tokens-frame1') 2))
(t/is (= (get tokens-frame1' :rx) "test-token-1"))
(t/is (= (get tokens-frame1' :ry) "test-token-1"))
(t/is (= (get c-frame1' :rx) 66))
(t/is (= (get c-frame1' :ry) 66)))))))]
(tohs/run-store-async
store step2 events identity))))
(t/deftest change-token-in-copy-then-change-main
(t/async
done
(let [;; ==== Setup
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
events [(wtch/apply-token {:shape-ids [(cthi/id :c-frame1)]
:attributes #{:rx :ry}
:token (toht/get-token file "test-token-2")
:on-update-shape wtch/update-shape-radius-all})
(wtch/apply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:rx :ry}
:token (toht/get-token file "test-token-3")
:on-update-shape wtch/update-shape-radius-all})]
step2 (fn [_]
(let [events2 [(dwl/sync-file (:id file) (:id file))]]
(ths/run-store
store done events2
(fn [new-state]
(let [;; ==== Get
file' (ths/get-file-from-store new-state)
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
(t/is (= (count tokens-frame1') 2))
(t/is (= (get tokens-frame1' :rx) "test-token-2"))
(t/is (= (get tokens-frame1' :ry) "test-token-2"))
(t/is (= (get c-frame1' :rx) 50))
(t/is (= (get c-frame1' :ry) 50)))))))]
(tohs/run-store-async
store step2 events identity))))
(t/deftest remove-token-in-copy-then-change-main
(t/async
done
(let [;; ==== Setup
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
events [(wtch/unapply-token {:shape-ids [(cthi/id :c-frame1)]
:attributes #{:rx :ry}
:token (toht/get-token file "test-token-1")})
(wtch/apply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:rx :ry}
:token (toht/get-token file "test-token-3")
:on-update-shape wtch/update-shape-radius-all})]
step2 (fn [_]
(let [events2 [(dwl/sync-file (:id file) (:id file))]]
(ths/run-store
store done events2
(fn [new-state]
(let [;; ==== Get
file' (ths/get-file-from-store new-state)
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
(t/is (= (count tokens-frame1') 0))
(t/is (= (get c-frame1' :rx) 25))
(t/is (= (get c-frame1' :ry) 25)))))))]
(tohs/run-store-async
store step2 events identity))))
(t/deftest modify-token-all-types
(t/async
done
(let [;; ==== Setup
file (-> (cthf/sample-file :file1)
(ctht/add-tokens-lib)
(ctht/update-tokens-lib #(-> %
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :name "test-theme"
:sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"})
(ctob/add-token-in-set "token-radius"
(ctob/make-token :name "token-radius"
:type :border-radius
:value 10))
(ctob/add-token-in-set "token-rotation"
(ctob/make-token :name "token-rotation"
:type :rotation
:value 30))
(ctob/add-token-in-set "token-opacity"
(ctob/make-token :name "token-opacity"
:type :opacity
:value 0.7))
(ctob/add-token-in-set "token-stroke-width"
(ctob/make-token :name "token-stroke-width"
:type :stroke-width
:value 2))
(ctob/add-token-in-set "token-color"
(ctob/make-token :name "token-color"
:type :color
:value "#00ff00"))
(ctob/add-token-in-set "token-dimensions"
(ctob/make-token :name "token-dimensions"
:type :dimensions
:value 100))))
(ctho/add-frame :frame1)
(ctht/apply-token-to-shape :frame1 "token-radius" [:rx :ry] [:rx :ry] 10)
(ctht/apply-token-to-shape :frame1 "token-rotation" [:rotation] [:rotation] 30)
(ctht/apply-token-to-shape :frame1 "token-opacity" [:opacity] [:opacity] 0.7)
(ctht/apply-token-to-shape :frame1 "token-stroke-width" [:stroke-width] [:stroke-width] 2)
(ctht/apply-token-to-shape :frame1 "token-color" [:stroke-color] [:stroke-color] "#00ff00")
(ctht/apply-token-to-shape :frame1 "token-color" [:fill] [:fill] "#00ff00")
(ctht/apply-token-to-shape :frame1 "token-dimensions" [:width :height] [:width :height] 100)
(cthc/make-component :component1 :frame1)
(cthc/instantiate-component :component1 :c-frame1))
store (ths/setup-store file)
;; ==== Action
events [(dt/update-create-token {:token (ctob/make-token :name "token-radius"
:type :border-radius
:value 30)
:prev-token-name "token-radius"})
(dt/update-create-token {:token (ctob/make-token :name "token-rotation"
:type :rotation
:value 45)
:prev-token-name "token-rotation"})
(dt/update-create-token {:token (ctob/make-token :name "token-opacity"
:type :opacity
:value 0.9)
:prev-token-name "token-opacity"})
(dt/update-create-token {:token (ctob/make-token :name "token-stroke-width"
:type :stroke-width
:value 8)
:prev-token-name "token-stroke-width"})
(dt/update-create-token {:token (ctob/make-token :name "token-color"
:type :color
:value "#ff0000")
:prev-token-name "token-color"})
(dt/update-create-token {:token (ctob/make-token :name "token-dimensions"
:type :dimensions
:value 200)
:prev-token-name "token-dimensions"})]
step2 (fn [_]
(let [events2 [(wtu/update-workspace-tokens)
(dwl/sync-file (:id file) (:id file))]]
(tohs/run-store-async
store done events2
(fn [new-state]
(let [;; ==== Get
file' (ths/get-file-from-store new-state)
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
(t/is (= (count tokens-frame1') 9))
(t/is (= (get tokens-frame1' :rx) "token-radius"))
(t/is (= (get tokens-frame1' :ry) "token-radius"))
(t/is (= (get tokens-frame1' :rotation) "token-rotation"))
(t/is (= (get tokens-frame1' :opacity) "token-opacity"))
(t/is (= (get tokens-frame1' :stroke-width) "token-stroke-width"))
(t/is (= (get tokens-frame1' :stroke-color) "token-color"))
(t/is (= (get tokens-frame1' :fill) "token-color"))
(t/is (= (get tokens-frame1' :width) "token-dimensions"))
(t/is (= (get tokens-frame1' :height) "token-dimensions"))
(t/is (= (get c-frame1' :rx) 30))
(t/is (= (get c-frame1' :ry) 30))
(t/is (= (get c-frame1' :rotation) 45))
(t/is (= (get c-frame1' :opacity) 0.9))
(t/is (= (get-in c-frame1' [:strokes 0 :stroke-width]) 8))
(t/is (= (get-in c-frame1' [:strokes 0 :stroke-color]) "#ff0000"))
(t/is (= (get-in c-frame1' [:fills 0 :fill-color]) "#ff0000"))
(t/is (mth/close? (get c-frame1' :width) 200))
(t/is (mth/close? (get c-frame1' :height) 200))
(t/is (empty? (:touched c-frame1'))))))))]
(tohs/run-store-async
store step2 events identity))))

View file

@ -4,6 +4,7 @@
[frontend-tests.basic-shapes-test] [frontend-tests.basic-shapes-test]
[frontend-tests.helpers-shapes-test] [frontend-tests.helpers-shapes-test]
[frontend-tests.logic.comp-remove-swap-slots-test] [frontend-tests.logic.comp-remove-swap-slots-test]
[frontend-tests.logic.components-and-tokens]
[frontend-tests.logic.copying-and-duplicating-test] [frontend-tests.logic.copying-and-duplicating-test]
[frontend-tests.logic.frame-guides-test] [frontend-tests.logic.frame-guides-test]
[frontend-tests.logic.groups-test] [frontend-tests.logic.groups-test]
@ -28,6 +29,7 @@
(t/run-tests (t/run-tests
'frontend-tests.helpers-shapes-test 'frontend-tests.helpers-shapes-test
'frontend-tests.logic.comp-remove-swap-slots-test 'frontend-tests.logic.comp-remove-swap-slots-test
'frontend-tests.logic.components-and-tokens
'frontend-tests.logic.copying-and-duplicating-test 'frontend-tests.logic.copying-and-duplicating-test
'frontend-tests.logic.frame-guides-test 'frontend-tests.logic.frame-guides-test
'frontend-tests.logic.groups-test 'frontend-tests.logic.groups-test