♻️ Remove sets context abstraction

This commit is contained in:
Andrey Antukh 2025-02-10 15:58:06 +01:00
parent b228438127
commit e8c85d13ff
12 changed files with 579 additions and 478 deletions

View file

@ -691,6 +691,7 @@ used for managing active sets without a user created theme.")
(filter some?) (filter some?)
first)))) first))))
;; DEPRECATED
(defn walk-sets-tree-seq (defn walk-sets-tree-seq
"Walk sets tree as a flat list. "Walk sets tree as a flat list.
@ -739,6 +740,67 @@ used for managing active sets without a user created theme.")
(cons item (mapcat #(walk % (assoc opts :parent path :depth (inc depth))) v'))))))))))] (cons item (mapcat #(walk % (assoc opts :parent path :depth (inc depth))) v'))))))))))]
(walk (or nodes (d/ordered-map)) nil))) (walk (or nodes (d/ordered-map)) nil)))
(defn sets-tree-seq
"Get tokens 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"
[tree & {:keys [skip-children-pred new-at-path]
:or {skip-children-pred (constantly false)}}]
(let [walk (fn walk [[k v :as node] parent depth]
(lazy-seq
(cond
;; New set
(= :is-new k)
(let [tset (make-token-set :name (if (empty? parent)
""
(join-set-path parent)))]
[{:is-new true
:is-group false
:id ""
:parent-path parent
:token-set tset
:depth depth}])
;; Set
(and v (instance? TokenSet v))
(let [name (:name v)]
[{:is-group false
:path (split-token-set-name name)
:id name
:parent-path parent
:depth depth
:token-set v}])
;; Set group
(and v (d/ordered-map? v))
(let [unprefixed-path (last (split-set-str-path-prefix k))
path (conj parent unprefixed-path)
item {:is-group true
:path path
:id (join-set-path path)
:parent-path parent
:depth depth}]
(if (skip-children-pred path)
[item]
(let [v (cond-> v
(= path new-at-path)
(assoc :is-new true))]
(cons item
(mapcat #(walk % path (inc depth)) v))))))))
tree (cond-> tree
(= [] new-at-path)
(assoc :is-new true))]
(->> tree
(mapcat #(walk % [] 0))
(map-indexed (fn [index item]
(assoc item :index index))))))
(defn flatten-nested-tokens-json (defn flatten-nested-tokens-json
"Recursively flatten the dtcg token structure, joining keys with '.'." "Recursively flatten the dtcg token structure, joining keys with '.'."
[tokens token-path] [tokens token-path]

View file

@ -19,13 +19,23 @@
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.tokens.selected-set :as dwts] [app.main.data.workspace.tokens.selected-set :as dwts]
[app.main.data.workspace.undo :as dwu]
[app.main.ui.workspace.tokens.update :as wtu] [app.main.ui.workspace.tokens.update :as wtu]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
(declare set-selected-token-set-name)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKENS Getters
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-tokens-lib
[state]
(-> (dsh/lookup-file-data state)
(get :tokens-lib)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helpers ;; Helpers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -43,15 +53,6 @@
(watch [_ _ _] (watch [_ _ _]
(rx/of (dwsh/update-shapes [id] #(merge % attrs)))))) (rx/of (dwsh/update-shapes [id] #(merge % attrs))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKENS Getters
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-tokens-lib
[state]
(-> (dsh/lookup-file-data state)
(get :tokens-lib)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKENS Actions ;; TOKENS Actions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -108,19 +109,21 @@
(dch/commit-changes changes) (dch/commit-changes changes)
(wtu/update-workspace-tokens)))))) (wtu/update-workspace-tokens))))))
(declare set-selected-token-set-name) (defn create-token-set
[set-name token-set]
(ptk/reify ::create-token-set
ptk/UpdateEvent
(update [_ state]
;; Clear possible local state
(update state :workspace-tokens dissoc :token-set-new-path))
(defn create-token-set [set-name token-set] ptk/WatchEvent
(let [new-token-set (-> token-set (watch [it _ _]
(update :name #(if (empty? %) set-name (ctob/join-set-path [% set-name]))))] (let [token-set (update token-set :name #(if (empty? %) set-name (ctob/join-set-path [% set-name])))
(ptk/reify ::create-token-set changes (-> (pcb/empty-changes it)
ptk/WatchEvent (pcb/add-token-set token-set))]
(watch [it _ _] (rx/of (set-selected-token-set-name (:name token-set))
(let [changes (-> (pcb/empty-changes it) (dch/commit-changes changes))))))
(pcb/add-token-set new-token-set))]
(rx/of
(set-selected-token-set-name (:name new-token-set))
(dch/commit-changes changes)))))))
(defn rename-token-set-group [set-group-path set-group-fname] (defn rename-token-set-group [set-group-path set-group-fname]
(ptk/reify ::rename-token-set-group (ptk/reify ::rename-token-set-group
@ -143,14 +146,18 @@
(set-selected-token-set-name (:name token-set)) (set-selected-token-set-name (:name token-set))
(dch/commit-changes changes)))))) (dch/commit-changes changes))))))
(defn toggle-token-set [{:keys [token-set-name]}] (defn toggle-token-set
[name]
(assert (string? name) "expected a string for `name`")
(ptk/reify ::toggle-token-set (ptk/reify ::toggle-token-set
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [changes (clt/generate-toggle-token-set (pcb/empty-changes) (get-tokens-lib state) token-set-name)] (let [tlib (get-tokens-lib state)
(rx/of changes (-> (pcb/empty-changes)
(dch/commit-changes changes) (clt/generate-toggle-token-set tlib name))]
(wtu/update-workspace-tokens))))))
(rx/of (dch/commit-changes changes)
(wtu/update-workspace-tokens))))))
(defn toggle-token-set-group [group-path] (defn toggle-token-set-group [group-path]
(ptk/reify ::toggle-token-set-group (ptk/reify ::toggle-token-set-group
@ -224,6 +231,7 @@
(rx/of (rx/of
(drop-error (ex-data e)))))))) (drop-error (ex-data e))))))))
;; FIXME: the the name is very confusing
(defn update-create-token (defn update-create-token
[{:keys [token prev-token-name]}] [{:keys [token prev-token-name]}]
(ptk/reify ::update-create-token (ptk/reify ::update-create-token
@ -241,13 +249,15 @@
add-to-hidden-theme? (= active-theme-paths #{ctob/hidden-token-theme-path}) add-to-hidden-theme? (= active-theme-paths #{ctob/hidden-token-theme-path})
base-changes (pcb/add-token-set (pcb/empty-changes) token-set)] base-changes (pcb/add-token-set (pcb/empty-changes) token-set)]
(cond (cond
(not tokens-lib) (-> base-changes (not tokens-lib)
(pcb/add-token-theme hidden-theme) (-> base-changes
(pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{})) (pcb/add-token-theme hidden-theme)
(pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{}))
add-to-hidden-theme? (let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)] add-to-hidden-theme?
(-> base-changes (let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)]
(pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme))) (-> base-changes
(pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme)))
:else base-changes)) :else base-changes))
(-> (pcb/empty-changes it) (-> (pcb/empty-changes it)
@ -295,49 +305,81 @@
(update-create-token (update-create-token
{:token (assoc token :name copy-name)}))))))) {:token (assoc token :name copy-name)})))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKEN UI OPS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn set-token-type-section-open (defn set-token-type-section-open
[token-type open?] [token-type open?]
(ptk/reify ::set-token-type-section-open (ptk/reify ::set-token-type-section-open
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:workspace-local :token-type-open-status token-type] open?)))) (update-in state [:workspace-tokens :open-status-by-type] assoc token-type open?))))
;; === Token Context Menu (defn assign-token-context-menu
[{:keys [position] :as params}]
(when params
(assert (gpt/point? position) "expected a point instance for `position` param"))
(defn show-token-context-menu
[{:keys [position _token-name] :as params}]
(dm/assert! (gpt/point? position))
(ptk/reify ::show-token-context-menu (ptk/reify ::show-token-context-menu
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:workspace-local :token-context-menu] params)))) (if params
(update state :workspace-tokens assoc :token-context-menu params)
(update state :workspace-tokens dissoc :token-context-menu)))))
(def hide-token-context-menu ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(ptk/reify ::hide-token-context-menu ;; TOKEN-SET UI OPS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn assign-token-set-context-menu
[{:keys [position] :as params}]
(when params
(assert (gpt/point? position) "expected valid point for `position` param"))
(ptk/reify ::assign-token-set-context-menu
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:workspace-local :token-context-menu] nil)))) (if params
(update state :workspace-tokens assoc :token-set-context-menu params)
;; === Token Set Context Menu (update state :workspace-tokens dissoc :token-set-context-menu)))))
(defn show-token-set-context-menu
[{:keys [position _token-set-name] :as params}]
(dm/assert! (gpt/point? position))
(ptk/reify ::show-token-set-context-menu
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :token-set-context-menu] params))))
(def hide-token-set-context-menu
(ptk/reify ::hide-token-set-context-menu
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :token-set-context-menu] nil))))
(defn set-selected-token-set-name (defn set-selected-token-set-name
[name] [name]
(ptk/reify ::set-selected-token-set-name (ptk/reify ::set-selected-token-set-name
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update state :workspace-local assoc :selected-token-set-name name)))) (update state :workspace-tokens assoc :selected-token-set-name name))))
(defn start-token-set-edition
[edition-id]
(assert (string? edition-id) "expected a string for `edition-id`")
(ptk/reify ::start-token-set-edition
ptk/UpdateEvent
(update [_ state]
(update state :workspace-tokens assoc :token-set-edition-id edition-id))))
(defn start-token-set-creation
[path]
(assert (vector? path) "expected a vector for `path`")
(ptk/reify ::start-token-set-creation
ptk/UpdateEvent
(update [_ state]
(update state :workspace-tokens assoc :token-set-new-path path))))
(defn clear-token-set-edition
[]
(ptk/reify ::clear-token-set-edition
ptk/UpdateEvent
(update [_ state]
(update state :workspace-tokens dissoc :token-set-edition-id))))
(defn clear-token-set-creation
[]
(ptk/reify ::clear-token-set-creation
ptk/UpdateEvent
(update [_ state]
(update state :workspace-tokens dissoc :token-set-new-path))))

View file

@ -399,6 +399,7 @@
:workspace-media-objects :workspace-media-objects
:workspace-persistence :workspace-persistence
:workspace-presence :workspace-presence
:workspace-tokens
:workspace-ready :workspace-ready
:workspace-undo) :workspace-undo)
(update :workspace-global dissoc :read-only?) (update :workspace-global dissoc :read-only?)

View file

@ -128,6 +128,10 @@
(def workspace-selrect-transform (def workspace-selrect-transform
(l/derived :workspace-selrect-transform st/state)) (l/derived :workspace-selrect-transform st/state))
(def workspace-tokens
"All tokens related ephimeral state"
(l/derived :workspace-tokens st/state))
;; TODO: rename to workspace-selected (?) ;; TODO: rename to workspace-selected (?)
;; Don't use directly from components, this is a proxy to improve performance of selected-shapes ;; Don't use directly from components, this is a proxy to improve performance of selected-shapes
(def ^:private selected-shapes-data (def ^:private selected-shapes-data
@ -453,12 +457,8 @@
(def workspace-token-themes-no-hidden (def workspace-token-themes-no-hidden
(l/derived #(remove ctob/hidden-temporary-theme? %) workspace-token-themes)) (l/derived #(remove ctob/hidden-temporary-theme? %) workspace-token-themes))
;; FIXME: deprecated
(def workspace-selected-token-set-name
(l/derived dwts/get-selected-token-set-name st/state))
(def selected-token-set-name (def selected-token-set-name
(l/derived (l/key :selected-token-set-name) workspace-local)) (l/derived (l/key :selected-token-set-name) workspace-tokens))
(def workspace-ordered-token-sets (def workspace-ordered-token-sets
(l/derived #(or (some-> % ctob/get-sets) []) tokens-lib)) (l/derived #(or (some-> % ctob/get-sets) []) tokens-lib))
@ -480,10 +480,7 @@
(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))
(def workspace-active-set-names ;; FIXME: deprecated, it should not be implemented with ref (still used in form)
(l/derived (d/nilf ctob/get-active-themes-set-names) tokens-lib))
;; FIXME: deprecated, it should not be implemented with ref
(def workspace-active-theme-sets-tokens (def workspace-active-theme-sets-tokens
(l/derived #(or (some-> % ctob/get-active-themes-set-tokens) {}) tokens-lib)) (l/derived #(or (some-> % ctob/get-active-themes-set-tokens) {}) tokens-lib))
@ -496,7 +493,6 @@
(def workspace-selected-token-set-tokens (def workspace-selected-token-set-tokens
(l/derived #(or (dwts/get-selected-token-set-tokens %) {}) st/state)) (l/derived #(or (dwts/get-selected-token-set-tokens %) {}) st/state))
(def plugins-permissions-peek (def plugins-permissions-peek
(l/derived (fn [state] (l/derived (fn [state]
(dm/get-in state [:plugins-permissions-peek :data])) (dm/get-in state [:plugins-permissions-peek :data]))

View file

@ -218,15 +218,15 @@
:no-selectable true :no-selectable true
:action (fn [event] :action (fn [event]
(let [{:keys [key fields]} modal] (let [{:keys [key fields]} modal]
(st/emit! dt/hide-token-context-menu)
(dom/stop-propagation event) (dom/stop-propagation event)
(modal/show! key {:x (.-clientX ^js event) (st/emit! (dt/assign-token-set-context-menu nil)
:y (.-clientY ^js event) (modal/show key {:x (.-clientX ^js event)
:position :right :y (.-clientY ^js event)
:fields fields :position :right
:action "edit" :fields fields
:selected-token-set-name selected-token-set-name :action "edit"
:token token})))} :selected-token-set-name selected-token-set-name
:token token}))))}
{:title (tr "workspace.token.duplicate") {:title (tr "workspace.token.duplicate")
:no-selectable true :no-selectable true
:action #(st/emit! (dt/duplicate-token (:name token)))} :action #(st/emit! (dt/duplicate-token (:name token)))}
@ -252,8 +252,8 @@
;; Components ------------------------------------------------------------------ ;; Components ------------------------------------------------------------------
(def tokens-menu-ref (def ^:private tokens-menu-ref
(l/derived :token-context-menu refs/workspace-local)) (l/derived :token-context-menu refs/workspace-tokens))
(defn- prevent-default (defn- prevent-default
[event] [event]
@ -355,7 +355,7 @@
selected-shapes (into [] (keep (d/getf objects)) selected) selected-shapes (into [] (keep (d/getf objects)) selected)
token-name (:token-name mdata) token-name (:token-name mdata)
token (mf/deref (refs/workspace-selected-token-set-token token-name)) token (mf/deref (refs/workspace-selected-token-set-token token-name))
selected-token-set-name (mf/deref refs/workspace-selected-token-set-name)] selected-token-set-name (mf/deref refs/selected-token-set-name)]
[:ul {:class (stl/css :context-list)} [:ul {:class (stl/css :context-list)}
[:& menu-tree {:submenu-offset width [:& menu-tree {:submenu-offset width
:submenu-direction direction :submenu-direction direction
@ -394,8 +394,9 @@
(reset! dropdown-direction* (if is-outside? "up" "down")) (reset! dropdown-direction* (if is-outside? "up" "down"))
(mf/set-ref-val! dropdown-direction-change* (inc (mf/ref-val dropdown-direction-change*))))))) (mf/set-ref-val! dropdown-direction-change* (inc (mf/ref-val dropdown-direction-change*)))))))
;; FIXME: perf optimization
[:& dropdown {:show is-open? [:& dropdown {:show is-open?
:on-close #(st/emit! dt/hide-token-context-menu)} :on-close #(st/emit! (dt/assign-token-context-menu nil))}
[:div {:class (stl/css :token-context-menu) [:div {:class (stl/css :token-context-menu)
:data-testid "tokens-context-menu-for-token" :data-testid "tokens-context-menu-for-token"
:ref dropdown-ref :ref dropdown-ref

View file

@ -25,7 +25,6 @@
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.workspace.tokens.components.controls.input-tokens :refer [input-tokens*]] [app.main.ui.workspace.tokens.components.controls.input-tokens :refer [input-tokens*]]
[app.main.ui.workspace.tokens.sets :as wts] [app.main.ui.workspace.tokens.sets :as wts]
[app.main.ui.workspace.tokens.sets-context :as sets-context]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[app.util.keyboard :as k] [app.util.keyboard :as k]
@ -339,8 +338,8 @@
token-set-active? token-set-active?
(mf/use-fn (mf/use-fn
(mf/deps theme-state) (mf/deps theme-state)
(fn [set-name] (fn [name]
(get-in theme-state [:sets set-name]))) (contains? (:sets theme-state) name)))
on-toggle-token-set on-toggle-token-set
(mf/use-fn (mf/use-fn
@ -381,16 +380,15 @@
(tr "workspace.token.set-selection-theme")] (tr "workspace.token.set-selection-theme")]
[:div {:class (stl/css :sets-list-wrapper)} [:div {:class (stl/css :sets-list-wrapper)}
[:& wts/controlled-sets-list [:> wts/controlled-sets-list*
{:token-sets token-sets {:token-sets token-sets
:token-set-selected? (constantly false) :is-token-set-active token-set-active?
:token-set-active? token-set-active? :is-token-set-group-active token-set-group-active?
:token-set-group-active? token-set-group-active?
:on-select on-click-token-set :on-select on-click-token-set
:can-edit false
:on-toggle-token-set on-toggle-token-set :on-toggle-token-set on-toggle-token-set
:on-toggle-token-set-group on-toggle-token-set-group :on-toggle-token-set-group on-toggle-token-set-group
:origin "theme-modal" :origin "theme-modal"}]]
:context sets-context/static-context}]]
[:div {:class (stl/css :edit-theme-footer)} [:div {:class (stl/css :edit-theme-footer)}
[:> button* {:variant "secondary" [:> button* {:variant "secondary"
@ -432,5 +430,4 @@
:aria-label (tr "labels.close") :aria-label (tr "labels.close")
:variant "action" :variant "action"
:icon "close"}] :icon "close"}]
[:& sets-context/provider {} [:& themes-modal-body]]]))
[:& themes-modal-body]]]]))

View file

@ -7,18 +7,17 @@
(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 :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.main.data.event :as ev] [app.main.data.event :as ev]
[app.main.data.tokens :as wdt] [app.main.data.tokens :as dt]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as ic] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as ic]
[app.main.ui.ds.foundations.typography.text :refer [text*]] [app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.hooks :as h] [app.main.ui.hooks :as h]
[app.main.ui.workspace.tokens.sets-context :as sets-context]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
@ -26,42 +25,59 @@
[potok.v2.core :as ptk] [potok.v2.core :as ptk]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(defn on-toggle-token-set-click [token-set-name] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(st/emit! (wdt/toggle-token-set {:token-set-name token-set-name}))) ;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn on-toggle-token-set-group-click [group-path] (defn- on-start-creation
(st/emit! (wdt/toggle-token-set-group group-path))) []
(st/emit! (dt/start-token-set-creation [])))
(defn on-select-token-set-click [set-name] (defn- on-toggle-token-set-click [name]
(st/emit! (wdt/set-selected-token-set-name set-name))) (st/emit! (dt/toggle-token-set name)))
(defn- on-toggle-token-set-group-click [group-path]
(st/emit! (dt/toggle-token-set-group group-path)))
(defn- on-select-token-set-click [set-name]
(st/emit! (dt/set-selected-token-set-name set-name)))
(defn on-update-token-set [set-name token-set] (defn on-update-token-set [set-name token-set]
(st/emit! (wdt/update-token-set (:name token-set) (ctob/update-name token-set set-name)))) (st/emit! (dt/update-token-set (:name token-set) (ctob/update-name token-set set-name))))
(defn on-update-token-set-group [set-group-path set-group-fname] (defn- on-update-token-set-group [path name]
(st/emit! (wdt/rename-token-set-group set-group-path set-group-fname))) (st/emit! (dt/rename-token-set-group path name)))
(defn on-create-token-set [set-name token-set] (defn- on-create-token-set [name token-set]
(st/emit! (ptk/event ::ev/event {::ev/name "create-tokens-set"})) (st/emit! (ptk/data-event ::ev/event {::ev/name "create-token-set" :name name})
(st/emit! (wdt/create-token-set set-name token-set))) (dt/create-token-set name token-set)))
(mf/defc editing-label ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; COMPONENTS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc editing-label*
{::mf/private true}
[{:keys [default-value on-cancel on-submit]}] [{:keys [default-value on-cancel on-submit]}]
(let [ref (mf/use-ref) (let [ref (mf/use-ref)
on-submit-valid (mf/use-fn
(fn [event] on-submit-valid
(let [value (str/trim (dom/get-target-val event))] (mf/use-fn
(if (or (str/empty? value) (mf/deps on-cancel on-submit default-value)
(= value default-value)) (fn [event]
(on-cancel) (let [value (str/trim (dom/get-target-val event))]
(do (if (or (str/empty? value)
(on-submit value) (= value default-value))
(on-cancel)))))) (on-cancel)
on-key-down (mf/use-fn (on-submit value)))))
(fn [event]
(cond on-key-down
(kbd/enter? event) (on-submit-valid event) (mf/use-fn
(kbd/esc? event) (on-cancel))))] (mf/deps on-submit-valid on-cancel)
(fn [event]
(cond
(kbd/enter? event) (on-submit-valid event)
(kbd/esc? event) (on-cancel))))]
[:input [:input
{:class (stl/css :editing-node) {:class (stl/css :editing-node)
:type "text" :type "text"
@ -71,17 +87,12 @@
:auto-focus true :auto-focus true
:default-value default-value}])) :default-value default-value}]))
(mf/defc checkbox (mf/defc checkbox*
[{:keys [checked aria-label on-click disabled]}] [{:keys [checked aria-label on-click disabled]}]
(let [all? (true? checked) (let [all? (true? checked)
mixed? (= checked "mixed") mixed? (= checked "mixed")
checked? (or all? mixed?) checked? (or all? mixed?)]
on-click
(mf/use-fn
(mf/deps disabled)
(fn [e]
(when-not disabled
(on-click e))))]
[:div {:role "checkbox" [:div {:role "checkbox"
:aria-checked (dm/str checked) :aria-checked (dm/str checked)
:disabled disabled :disabled disabled
@ -90,37 +101,59 @@
:class (stl/css-case :checkbox-style true :class (stl/css-case :checkbox-style true
:checkbox-checked-style checked? :checkbox-checked-style checked?
:checkbox-disabled-checked (and checked? disabled) :checkbox-disabled-checked (and checked? disabled)
:checkbox-disabled disabled) :checkbox-disabled disabled)
:on-click on-click} :on-click (when-not disabled on-click)}
(when checked? (when ^boolean checked?
[:> icon* [:> icon*
{:aria-label aria-label {:aria-label aria-label
:class (stl/css :check-icon) :class (stl/css :check-icon)
:size "s" :size "s"
:icon-id (if mixed? ic/remove ic/tick)}])])) :icon-id (if mixed? ic/remove ic/tick)}])]))
(mf/defc sets-tree-set-group (mf/defc inline-add-button*
[{:keys [label tree-depth tree-path active? selected? draggable? on-toggle-collapse on-toggle editing-id editing? on-edit on-edit-reset on-edit-submit collapsed-paths tree-index]}] []
(let [active?' (active? tree-path) (let [can-edit? (mf/use-ctx ctx/can-edit?)]
editing?' (editing? editing-id) (if can-edit?
collapsed? (some? (get @collapsed-paths tree-path)) [:div {:class (stl/css :empty-sets-wrapper)}
can-edit? (:can-edit (deref refs/permissions)) [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
;; Used by playwright to get the correct item by label (tr "workspace.token.no-sets-yet")]
label-id (str editing-id "-label") [:button {:on-click on-start-creation
:class (stl/css :create-set-button)}
(tr "workspace.token.create-one")]]
[:div {:class (stl/css :empty-sets-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
(tr "workspace.token.no-sets-yet")]])))
(mf/defc add-button*
[]
[:> icon-button* {:variant "ghost"
:icon "add"
:on-click on-start-creation
:aria-label (tr "workspace.token.add set")}])
(mf/defc sets-tree-set-group*
{::mf/private true}
[{:keys [id label tree-depth tree-path is-active is-selected is-draggable is-collapsed tree-index on-drop
on-toggle-collapse on-toggle is-editing on-start-edition on-reset-edition on-edit-submit]}]
(let [can-edit?
(mf/use-ctx ctx/can-edit?)
label-id
(str id "-label")
on-context-menu on-context-menu
(mf/use-fn (mf/use-fn
(mf/deps editing?' editing-id can-edit?) (mf/deps is-editing can-edit?)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(when-not editing?' (when-not is-editing
(st/emit! (st/emit! (dt/assign-token-set-context-menu
(wdt/show-token-set-context-menu {:position (dom/get-client-position event)
{:position (dom/get-client-position event) :is-group true
:is-group true :path tree-path})))))
:path tree-path})))))
on-collapse-click on-collapse-click
(mf/use-fn (mf/use-fn
@ -129,9 +162,7 @@
(on-toggle-collapse tree-path))) (on-toggle-collapse tree-path)))
on-double-click on-double-click
(mf/use-fn (mf/use-fn (mf/deps id) #(on-start-edition id))
(mf/deps editing-id can-edit?)
#(on-edit editing-id))
on-checkbox-click on-checkbox-click
(mf/use-fn (mf/use-fn
@ -145,24 +176,18 @@
on-drop on-drop
(mf/use-fn (mf/use-fn
(mf/deps tree-index collapsed-paths) (mf/deps tree-index on-drop)
(fn [position data] (fn [position data]
(let [props {:from-index (:index data) (on-drop tree-index position data)))
:to-index tree-index
:position position
:collapsed-paths @collapsed-paths}]
(if (:group? data)
(st/emit! (wdt/drop-token-set-group props))
(st/emit! (wdt/drop-token-set props))))))
[dprops dref] [dprops dref]
(h/use-sortable (h/use-sortable
:data-type "penpot/token-set" :data-type "penpot/token-set"
:on-drop on-drop :on-drop on-drop
:data {:index tree-index :data {:index tree-index
:group? true} :is-group true}
:detect-center? true :detect-center? true
:draggable? draggable?)] :draggable? is-draggable)]
[:div {:ref dref [:div {:ref dref
:role "button" :role "button"
@ -171,7 +196,7 @@
:style {"--tree-depth" tree-depth} :style {"--tree-depth" tree-depth}
:class (stl/css-case :set-item-container true :class (stl/css-case :set-item-container true
:set-item-group true :set-item-group true
:selected-set selected? :selected-set is-selected
:dnd-over (= (:over dprops) :center) :dnd-over (= (:over dprops) :center)
:dnd-over-top (= (:over dprops) :top) :dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot)) :dnd-over-bot (= (:over dprops) :bot))
@ -180,68 +205,64 @@
{:class (stl/css :set-item-group-collapse-button) {:class (stl/css :set-item-group-collapse-button)
:on-click on-collapse-click :on-click on-collapse-click
:aria-label (tr "labels.collapse") :aria-label (tr "labels.collapse")
:icon (if collapsed? "arrow-right" "arrow-down") :icon (if is-collapsed "arrow-right" "arrow-down")
:variant "action"}] :variant "action"}]
(if editing?' (if is-editing
[:& editing-label [:> editing-label*
{:default-value label {:default-value label
:on-cancel on-edit-reset :on-cancel on-reset-edition
:on-create on-edit-reset
:on-submit on-edit-submit'}] :on-submit on-edit-submit'}]
[:* [:*
[:div {:class (stl/css :set-name) [:div {:class (stl/css :set-name)
:on-double-click on-double-click :on-double-click on-double-click
:id label-id} :id label-id}
label] label]
[:& checkbox [:> checkbox*
{:on-click on-checkbox-click {:on-click on-checkbox-click
:disabled (not can-edit?) :disabled (not can-edit?)
:checked (case active?' :checked (case is-active
:all true :all true
:partial "mixed" :partial "mixed"
:none false) :none false)
:arial-label (tr "workspace.token.select-set")}]])])) :arial-label (tr "workspace.token.select-set")}]])]))
(mf/defc sets-tree-set (mf/defc sets-tree-set*
[{:keys [set label tree-depth tree-path tree-index selected? on-select active? draggable? on-toggle editing-id editing? on-edit on-edit-reset on-edit-submit collapsed-paths]}] [{:keys [id set label tree-depth tree-path tree-index is-selected is-active is-draggable is-editing
(let [set-name (.-name set) on-select on-drop on-toggle on-start-edition on-reset-edition on-edit-submit]}]
editing?' (editing? editing-id) (let [set-name (get set :name)
active?' (some? (active? set-name)) can-edit? (mf/use-ctx ctx/can-edit?)
can-edit? (:can-edit (deref refs/permissions))
on-click on-click
(mf/use-fn (mf/use-fn
(mf/deps editing?' tree-path) (mf/deps is-editing tree-path)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(when-not editing?' (when-not is-editing
(on-select set-name)))) (when (fn? on-select)
(on-select set-name)))))
on-context-menu on-context-menu
(mf/use-fn (mf/use-fn
(mf/deps editing?' tree-path can-edit?) (mf/deps is-editing tree-path can-edit?)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(when (and can-edit? (not editing?')) (when (and can-edit? (not is-editing))
(st/emit! (st/emit! (dt/assign-token-set-context-menu
(wdt/show-token-set-context-menu {:position (dom/get-client-position event)
{:position (dom/get-client-position event) :is-group false
:is-group false :path tree-path})))))
:path tree-path})))))
on-double-click on-double-click
(mf/use-fn (mf/use-fn (mf/deps id) #(on-start-edition id))
(mf/deps editing-id)
(fn []
(on-edit editing-id)))
on-checkbox-click on-checkbox-click
(mf/use-fn (mf/use-fn
(mf/deps set-name) (mf/deps set-name on-toggle)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(on-toggle set-name))) (when (fn? on-toggle)
(on-toggle set-name))))
on-edit-submit' on-edit-submit'
(mf/use-fn (mf/use-fn
@ -252,20 +273,14 @@
(mf/use-fn (mf/use-fn
(mf/deps tree-path) (mf/deps tree-path)
(fn [_] (fn [_]
(when-not selected? (when-not is-selected
(on-select tree-path)))) (on-select tree-path))))
on-drop on-drop
(mf/use-fn (mf/use-fn
(mf/deps tree-index collapsed-paths) (mf/deps tree-index on-drop)
(fn [position data] (fn [position data]
(let [props {:from-index (:index data) (on-drop tree-index position data)))
:to-index tree-index
:position position
:collapsed-paths @collapsed-paths}]
(if (:group? data)
(st/emit! (wdt/drop-token-set-group props))
(st/emit! (wdt/drop-token-set props))))))
[dprops dref] [dprops dref]
(h/use-sortable (h/use-sortable
@ -273,210 +288,280 @@
:on-drag on-drag :on-drag on-drag
:on-drop on-drop :on-drop on-drop
:data {:index tree-index :data {:index tree-index
:group? false} :is-group false}
:draggable? draggable?)] :draggable? is-draggable)
drop-over
(get dprops :over)]
[:div {:ref dref [:div {:ref dref
:role "button" :role "button"
:data-testid "tokens-set-item" :data-testid "tokens-set-item"
:style {"--tree-depth" tree-depth} :style {"--tree-depth" tree-depth}
:class (stl/css-case :set-item-container true :class (stl/css-case :set-item-container true
:selected-set selected? :selected-set is-selected
:dnd-over (= (:over dprops) :center) :dnd-over (= drop-over :center)
:dnd-over-top (= (:over dprops) :top) :dnd-over-top (= drop-over :top)
:dnd-over-bot (= (:over dprops) :bot)) :dnd-over-bot (= drop-over :bot))
:on-click on-click :on-click on-click
:on-context-menu on-context-menu :on-context-menu on-context-menu
:aria-checked active?'} :aria-checked is-active}
[:> icon* [:> icon*
{:icon-id "document" {:icon-id "document"
:class (stl/css-case :icon true :class (stl/css-case :icon true
:root-icon (not tree-depth))}] :root-icon (not tree-depth))}]
(if editing?' (if is-editing
[:& editing-label [:> editing-label*
{:default-value label {:default-value label
:on-cancel on-edit-reset :on-cancel on-reset-edition
:on-create on-edit-reset
:on-submit on-edit-submit'}] :on-submit on-edit-submit'}]
[:* [:*
[: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]
[:& checkbox [:> checkbox*
{:on-click on-checkbox-click {:on-click on-checkbox-click
:disabled (not can-edit?) :disabled (not can-edit?)
:arial-label (tr "workspace.token.select-set") :arial-label (tr "workspace.token.select-set")
:checked active?'}]])])) :checked is-active}]])]))
(mf/defc sets-tree (mf/defc token-sets-tree*
[{:keys [draggable? [{:keys [is-draggable
active? selected
selected? is-token-set-group-active
group-active? is-token-set-active
editing? on-start-edition
on-edit-reset on-reset-edition
on-edit-submit-set on-edit-submit-set
on-edit-submit-group on-edit-submit-group
on-select on-select
on-toggle-set on-toggle-set
on-toggle-set-group on-toggle-set-group
set-node] token-sets
:as props}] new-path
(let [{:keys [on-edit new-path] :as ctx} (sets-context/use-context) edition-id]}]
collapsed-paths (mf/use-state #{})
(let [collapsed-paths* (mf/use-state #{})
collapsed-paths (deref collapsed-paths*)
collapsed? collapsed?
(mf/use-fn (mf/use-fn
#(contains? @collapsed-paths %)) (mf/deps collapsed-paths)
(partial contains? collapsed-paths))
on-drop
(mf/use-fn
(mf/deps collapsed-paths)
(fn [tree-index position data]
(let [props {:from-index (:index data)
:to-index tree-index
:position position
:collapsed-paths collapsed-paths}]
(if (:is-group data)
(st/emit! (dt/drop-token-set-group props))
(st/emit! (dt/drop-token-set props))))))
on-toggle-collapse on-toggle-collapse
(mf/use-fn (mf/use-fn
(fn [path] (fn [path]
(swap! collapsed-paths #(if (contains? % path) (swap! collapsed-paths* #(if (contains? % path)
(disj % path) (disj % path)
(conj % path)))))] (conj % path)))))]
(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
:label (last path)
:active? group-active?
:selected? false
:draggable? draggable?
:on-select on-select
:tree-path path
:tree-depth depth
:tree-index index
:tree-parent-path parent-path
:editing-id editing-id
:editing? editing?
:on-edit on-edit
:on-edit-reset on-edit-reset
:on-edit-submit on-edit-submit-group
:collapsed? (collapsed? path)
:on-toggle-collapse on-toggle-collapse
:on-toggle on-toggle-set-group
:collapsed-paths collapsed-paths}])
new? (for [{:keys [id token-set index is-new is-group path parent-path depth] :as node}
(let [editing-id (sets-context/set-path->id path)] (ctob/sets-tree-seq token-sets
[:& sets-tree-set {:skip-children-pred collapsed?
{:key editing-id :new-at-path new-path})]
:set (ctob/make-token-set :name (if (empty? parent-path) (cond
"" ^boolean is-group
(ctob/join-set-path parent-path))) [:> sets-tree-set-group*
:label "" {:key index
:active? (constantly true) :label (peek path)
:selected? (constantly true) :id id
:on-select (constantly nil) :is-active (is-token-set-group-active path)
:tree-path path :is-selected false
:tree-depth depth :is-draggable is-draggable
:tree-index index :is-editing (= edition-id id)
:tree-parent-path parent-path :is-collapsed (collapsed? path)
:on-toggle (constantly nil) :on-select on-select
:editing-id editing-id
:editing? (constantly true) :tree-path path
:on-edit-reset on-edit-reset :tree-depth depth
:on-edit-submit on-create-token-set}]) :tree-index index
:tree-parent-path parent-path
:on-drop on-drop
:on-start-edition on-start-edition
:on-reset-edition on-reset-edition
:on-edit-submit on-edit-submit-group
:on-toggle-collapse on-toggle-collapse
:on-toggle on-toggle-set-group}]
^boolean is-new
[:> sets-tree-set*
{:key index
:set token-set
:label ""
:id id
:is-editing true
:is-active true
:is-selected true
:tree-path path
:tree-depth depth
:tree-index index
:tree-parent-path parent-path
:on-drop on-drop
:on-reset-edition on-reset-edition
:on-edit-submit on-create-token-set}]
:else :else
(let [editing-id (sets-context/set-path->id path)] [:> sets-tree-set*
[:& sets-tree-set {:key index
{:key editing-id :set token-set
:set (:set node) :id id
:label (last path) :label (peek path)
:active? active? :is-editing (= edition-id id)
:selected? (selected? (get-in node [:set :name])) :is-active (is-token-set-active id)
:draggable? draggable? :is-selected (= selected id)
:on-select on-select :is-draggable is-draggable
:tree-path path :on-select on-select
:tree-depth depth :tree-path path
:tree-index index :tree-depth depth
:tree-parent-path parent-path :tree-index index
:on-toggle on-toggle-set :tree-parent-path parent-path
:editing-id editing-id :on-toggle on-toggle-set
:editing? editing? :edition-id edition-id
:on-edit on-edit :on-start-edition on-start-edition
:on-edit-reset on-edit-reset :on-drop on-drop
:on-edit-submit on-edit-submit-set :on-reset-edition on-reset-edition
:collapsed-paths collapsed-paths}]))))) :on-edit-submit on-edit-submit-set}]))))
(mf/defc controlled-sets-list (mf/defc controlled-sets-list*
{::mf/props :obj}
[{:keys [token-sets [{:keys [token-sets
selected
on-update-token-set on-update-token-set
on-update-token-set-group on-update-token-set-group
token-set-selected? is-token-set-active
token-set-active? is-token-set-group-active
token-set-group-active?
on-create-token-set on-create-token-set
on-toggle-token-set on-toggle-token-set
on-toggle-token-set-group on-toggle-token-set-group
on-start-edition
on-reset-edition
origin origin
on-select on-select
context] new-path
:as _props}] edition-id]}]
(let [{:keys [editing? on-edit on-reset new-path] :as ctx} (or context (sets-context/use-context))
theme-modal? (= origin "theme-modal") (assert (fn? is-token-set-group-active) "expected a function for `is-token-set-group-active` prop")
can-edit? (:can-edit (deref refs/permissions)) (assert (fn? is-token-set-active) "expected a function for `is-token-set-active` prop")
draggable? (and (not theme-modal?) can-edit?)]
(let [theme-modal? (= origin "theme-modal")
can-edit? (mf/use-ctx ctx/can-edit?)
draggable? (and (not theme-modal?) can-edit?)
empty-state? (and theme-modal?
(empty? token-sets)
(not new-path))
;; NOTE: on-reset-edition and on-start-edition function can
;; come as nil, in this case we need to provide a safe
;; fallback for them
on-reset-edition
(mf/use-fn
(mf/deps on-reset-edition)
(fn [v]
(when (fn? on-reset-edition)
(on-reset-edition v))))
on-start-edition
(mf/use-fn
(mf/deps on-start-edition)
(fn [v]
(when (fn? on-start-edition)
(on-start-edition v))))]
[:fieldset {:class (stl/css :sets-list)} [:fieldset {:class (stl/css :sets-list)}
(if (and theme-modal? (if ^boolean empty-state?
(empty? token-sets)
(not new-path))
[:> 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")]
[:& sets-tree
{:draggable? draggable? [:> token-sets-tree*
:set-node token-sets {:is-draggable draggable?
:selected? token-set-selected? :new-path new-path
:edition-id edition-id
:token-sets token-sets
:selected selected
:on-select on-select :on-select on-select
:active? token-set-active? :is-token-set-active is-token-set-active
:group-active? token-set-group-active? :is-token-set-group-active is-token-set-group-active
:on-toggle-set on-toggle-token-set :on-toggle-set on-toggle-token-set
:on-toggle-set-group on-toggle-token-set-group :on-toggle-set-group on-toggle-token-set-group
:editing? editing?
:on-create-token-set on-create-token-set :on-create-token-set on-create-token-set
:on-edit on-edit :on-start-edition on-start-edition
:on-edit-reset on-reset :on-reset-edition on-reset-edition
:on-edit-submit-set on-update-token-set :on-edit-submit-set on-update-token-set
:on-edit-submit-group on-update-token-set-group}])])) :on-edit-submit-group on-update-token-set-group}])]))
(mf/defc sets-list* (mf/defc sets-list*
[{:keys [tokens-lib selected-token-set-name]}] [{:keys [tokens-lib selected new-path edition-id]}]
(let [token-sets (let [token-sets
(some-> tokens-lib (ctob/get-set-tree)) (some-> tokens-lib (ctob/get-set-tree))
token-set-selected? can-edit?
(mf/use-fn (mf/use-ctx ctx/can-edit?)
(mf/deps token-sets selected-token-set-name)
(fn [set-name]
(= set-name selected-token-set-name)))
active-token-set-names active-token-sets-names
(mf/deref refs/workspace-active-set-names) (mf/with-memo [tokens-lib]
(some-> tokens-lib (ctob/get-active-themes-set-names)))
token-set-active? token-set-active?
(mf/use-fn (mf/use-fn
(mf/deps active-token-set-names) (mf/deps active-token-sets-names)
(fn [set-name] (fn [name]
(get active-token-set-names set-name))) (contains? active-token-sets-names name)))
token-set-group-active? token-set-group-active?
(mf/use-fn (mf/use-fn
(fn [group-path] (fn [group-path]
@(refs/token-sets-at-path-all-active group-path)))] ;; FIXME
@(refs/token-sets-at-path-all-active group-path)))
[:& controlled-sets-list on-reset-edition
(mf/use-fn
(mf/deps can-edit?)
(fn [_]
(when can-edit?
(st/emit! (dt/clear-token-set-edition)
(dt/clear-token-set-creation)))))
on-start-edition
(mf/use-fn
(mf/deps can-edit?)
(fn [id]
(when can-edit?
(st/emit! (dt/start-token-set-edition id)))))]
[:> controlled-sets-list*
{:token-sets token-sets {:token-sets token-sets
:token-set-selected? token-set-selected?
:token-set-active? token-set-active? :is-token-set-active token-set-active?
:token-set-group-active? token-set-group-active? :is-token-set-group-active token-set-group-active?
:on-select on-select-token-set-click :on-select on-select-token-set-click
:selected selected
:new-path new-path
:edition-id edition-id
:origin "set-panel" :origin "set-panel"
:can-edit can-edit?
:on-start-edition on-start-edition
:on-reset-edition on-reset-edition
:on-toggle-token-set on-toggle-token-set-click :on-toggle-token-set on-toggle-token-set-click
:on-toggle-token-set-group on-toggle-token-set-group-click :on-toggle-token-set-group on-toggle-token-set-group-click
:on-update-token-set on-update-token-set :on-update-token-set on-update-token-set

View file

@ -4,6 +4,7 @@
// //
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "../../ds/typography.scss" as *;
@import "refactor/common-refactor.scss"; @import "refactor/common-refactor.scss";
.sets-list { .sets-list {
@ -12,6 +13,21 @@
overflow-y: auto; overflow-y: auto;
} }
.empty-sets-wrapper {
padding: $s-12;
padding-inline-start: $s-24;
color: var(--color-foreground-secondary);
}
.create-set-button {
@include use-typography("body-small");
background-color: transparent;
border: none;
appearance: none;
color: var(--color-accent-primary);
cursor: pointer;
}
.set-item-container { .set-item-container {
@include bodySmallTypography; @include bodySmallTypography;
display: flex; display: flex;

View file

@ -1,54 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.workspace.tokens.sets-context
(:require
[app.common.data.macros :as dm]
[rumext.v2 :as mf]))
(defn set-group-path->id [set-group-path]
(dm/str "group-" set-group-path))
(defn set-path->id [set-path]
(dm/str "set-" set-path))
(def initial {})
(def context (mf/create-context initial))
(def static-context
{:editing? (constantly false)
:on-edit (constantly nil)
:on-create (constantly nil)
:on-reset (constantly nil)})
(mf/defc provider
{::mf/wrap-props false}
[props]
(let [children (unchecked-get props "children")
state (mf/use-state initial)]
[:& (mf/provider context) {:value state}
children]))
(defn use-context []
(let [ctx (mf/use-ctx context)
{:keys [editing-id new-path]} @ctx
editing? (mf/use-callback
(mf/deps editing-id)
#(= editing-id %))
on-edit (mf/use-fn
(fn [editing-id]
(reset! ctx (assoc @ctx :editing-id editing-id))))
on-create (mf/use-fn
(fn [path]
(swap! ctx assoc :editing-id (random-uuid) :new-path path)))
on-reset (mf/use-fn
#(reset! ctx initial))]
{:editing? editing?
:new-path new-path
:on-edit on-edit
:on-create on-create
:on-reset on-reset}))

View file

@ -8,18 +8,18 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.data.tokens :as wdt] [app.common.types.tokens-lib :as ctob]
[app.main.data.tokens :as dt]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.workspace.tokens.sets-context :as sets-context]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[okulary.core :as l] [okulary.core :as l]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(def ^:private ref:tokens-sets-context-menu (def ^:private ref:token-sets-context-menu
(l/derived :token-set-context-menu refs/workspace-local)) (l/derived :token-set-context-menu refs/workspace-tokens))
(defn- prevent-default (defn- prevent-default
[event] [event]
@ -34,27 +34,22 @@
[:span {:class (stl/css :title)} title]]) [:span {:class (stl/css :title)} title]])
(mf/defc menu* (mf/defc menu*
{::mf/private true}
[{:keys [is-group path]}] [{:keys [is-group path]}]
(let [{:keys [on-create on-edit]} (sets-context/use-context) (let [create-set-at-path
(mf/use-fn (mf/deps path) #(st/emit! (dt/start-token-set-creation path)))
create-set-at-path
(mf/use-fn
(mf/deps path on-create)
#(on-create path))
edit-name edit-name
(mf/use-fn (mf/use-fn
(mf/deps is-group on-edit) (mf/deps path)
(fn [] (fn []
(let [path (if is-group (let [name (ctob/join-set-path path)]
(sets-context/set-group-path->id path) (st/emit! (dt/start-token-set-edition name)))))
(sets-context/set-path->id path))]
(on-edit path))))
delete-set delete-set
(mf/use-fn (mf/use-fn
(mf/deps is-group path) (mf/deps is-group path)
#(st/emit! (wdt/delete-token-set-path is-group path)))] #(st/emit! (dt/delete-token-set-path is-group path)))]
[:ul {:class (stl/css :context-list)} [:ul {:class (stl/css :context-list)}
(when is-group (when is-group
@ -62,10 +57,10 @@
[:> menu-entry* {:title (tr "labels.rename") :on-click edit-name}] [:> menu-entry* {:title (tr "labels.rename") :on-click edit-name}]
[:> menu-entry* {:title (tr "labels.delete") :on-click delete-set}]])) [:> menu-entry* {:title (tr "labels.delete") :on-click delete-set}]]))
(mf/defc sets-context-menu* (mf/defc token-set-context-menu*
[] []
(let [{:keys [position is-group path]} (let [{:keys [position is-group path]}
(mf/deref ref:tokens-sets-context-menu) (mf/deref ref:token-sets-context-menu)
position-top position-top
(+ (dm/get-prop position :y) 5) (+ (dm/get-prop position :y) 5)
@ -74,8 +69,7 @@
(+ (dm/get-prop position :x) 5) (+ (dm/get-prop position :x) 5)
on-close on-close
(mf/use-fn (mf/use-fn #(st/emit! (dt/assign-token-set-context-menu nil)))]
#(st/emit! wdt/hide-token-set-context-menu))]
[:& dropdown {:show (some? position) [:& dropdown {:show (some? position)
:on-close on-close} :on-close on-close}

View file

@ -8,7 +8,6 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.json :as json] [app.common.json :as json]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.main.data.event :as ev] [app.main.data.event :as ev]
@ -30,9 +29,8 @@
[app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]] [app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]]
[app.main.ui.workspace.tokens.errors :as wte] [app.main.ui.workspace.tokens.errors :as wte]
[app.main.ui.workspace.tokens.sets :refer [sets-list*]] [app.main.ui.workspace.tokens.sets :as tsets]
[app.main.ui.workspace.tokens.sets-context :as sets-context] [app.main.ui.workspace.tokens.sets-context-menu :refer [token-set-context-menu*]]
[app.main.ui.workspace.tokens.sets-context-menu :refer [sets-context-menu*]]
[app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.theme-select :refer [theme-select]] [app.main.ui.workspace.tokens.theme-select :refer [theme-select]]
[app.main.ui.workspace.tokens.token-pill :refer [token-pill*]] [app.main.ui.workspace.tokens.token-pill :refer [token-pill*]]
@ -47,7 +45,7 @@
[shadow.resource])) [shadow.resource]))
(def ref:token-type-open-status (def ref:token-type-open-status
(l/derived #(dm/get-in % [:workspace-local :token-type-open-status]) st/state)) (l/derived (l/key :open-status-by-type) refs/workspace-tokens))
;; Components ------------------------------------------------------------------ ;; Components ------------------------------------------------------------------
@ -85,7 +83,7 @@
(fn [event token] (fn [event token]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(st/emit! (dt/show-token-context-menu (st/emit! (dt/assign-token-context-menu
{:type :token {:type :token
:position (dom/get-client-position event) :position (dom/get-client-position event)
:errors (:errors token) :errors (:errors token)
@ -200,43 +198,7 @@
(tr "workspace.token.no-permission-themes"))} (tr "workspace.token.no-permission-themes"))}
[:& theme-select]]))])) [:& theme-select]]))]))
(mf/defc add-set-button* (mf/defc token-sets-list*
{::mf/private true}
[{:keys [style]}]
(let [{:keys [on-create new-path]}
(sets-context/use-context)
permissions
(mf/use-ctx ctx/permissions)
can-edit?
(get permissions :can-edit)
on-click
(mf/use-fn
(mf/deps on-create)
(fn []
(on-create [])))]
(if (= style "inline")
(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)}
(tr "workspace.token.no-sets-yet")]
[:button {:on-click on-click
:class (stl/css :create-theme-button)}
(tr "workspace.token.create-one")]]
[:div {:class (stl/css :empty-sets-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
(tr "workspace.token.no-sets-yet")]]))
(when can-edit?
[:> icon-button* {:variant "ghost"
:icon "add"
:on-click on-click
:aria-label (tr "workspace.token.add set")}]))))
(mf/defc theme-sets-list*
{::mf/private true} {::mf/private true}
[{:keys [tokens-lib]}] [{:keys [tokens-lib]}]
(let [;; FIXME: This is an inneficient operation just for being (let [;; FIXME: This is an inneficient operation just for being
@ -250,27 +212,31 @@
selected-token-set-name selected-token-set-name
(mf/deref refs/selected-token-set-name) (mf/deref refs/selected-token-set-name)
{:keys [new-path] :as ctx} {:keys [token-set-edition-id
(sets-context/use-context)] token-set-new-path]}
(mf/deref refs/workspace-tokens)]
(if (and (empty? token-sets) (if (and (empty? token-sets)
(not new-path)) (not token-set-new-path))
[:> add-set-button* {:style "inline"}]
[:& h/sortable-container {}
[:> sets-list* {:tokens-lib tokens-lib
:selected-token-set-name selected-token-set-name}]])))
(mf/defc themes-sets-tab* (when-not token-set-new-path
[:> tsets/inline-add-button*])
[:> h/sortable-container {}
[:> tsets/sets-list*
{:tokens-lib tokens-lib
:new-path token-set-new-path
:edition-id token-set-edition-id
:selected selected-token-set-name}]])))
(mf/defc token-sets-section*
{::mf/private true} {::mf/private true}
[{:keys [resize-height] :as props}] [{:keys [resize-height] :as props}]
(let [permissions (let [can-edit?
(mf/use-ctx ctx/permissions) (mf/use-ctx ctx/can-edit?)]
can-edit? [:*
(get permissions :can-edit)] [:> token-set-context-menu*]
[:& sets-context/provider {}
[:> sets-context-menu*]
[:article {:data-testid "token-themes-sets-sidebar" [:article {:data-testid "token-themes-sets-sidebar"
:class (stl/css :sets-section-wrapper) :class (stl/css :sets-section-wrapper)
:style {"--resize-height" (str resize-height "px")}} :style {"--resize-height" (str resize-height "px")}}
@ -279,11 +245,11 @@
[:div {:class (stl/css :sidebar-header)} [:div {:class (stl/css :sidebar-header)}
[:& title-bar {:title (tr "labels.sets")} [:& title-bar {:title (tr "labels.sets")}
(when can-edit? (when can-edit?
[:> add-set-button* {:style "header"}])]] [:> tsets/add-button*])]]
[:> theme-sets-list* props]]]])) [:> token-sets-list* props]]]]))
(mf/defc tokens-tab* (mf/defc tokens-section*
[{:keys [tokens-lib]}] [{:keys [tokens-lib]}]
(let [objects (mf/deref refs/workspace-page-objects) (let [objects (mf/deref refs/workspace-page-objects)
selected (mf/deref refs/selected-shapes) selected (mf/deref refs/selected-shapes)
@ -475,13 +441,14 @@
(mf/deref refs/tokens-lib)] (mf/deref refs/tokens-lib)]
[:div {:class (stl/css :sidebar-wrapper)} [:div {:class (stl/css :sidebar-wrapper)}
[:> themes-sets-tab* {:resize-height size-pages-opened [:> token-sets-section*
:tokens-lib tokens-lib}] {:resize-height size-pages-opened
:tokens-lib tokens-lib}]
[:article {:class (stl/css :tokens-section-wrapper) [:article {:class (stl/css :tokens-section-wrapper)
:data-testid "tokens-sidebar"} :data-testid "tokens-sidebar"}
[:div {:class (stl/css :resize-area-horiz) [:div {:class (stl/css :resize-area-horiz)
:on-pointer-down on-pointer-down-pages :on-pointer-down on-pointer-down-pages
:on-lost-pointer-capture on-lost-pointer-capture-pages :on-lost-pointer-capture on-lost-pointer-capture-pages
:on-pointer-move on-pointer-move-pages}] :on-pointer-move on-pointer-move-pages}]
[:> tokens-tab* {:tokens-lib tokens-lib}]] [:> tokens-section* {:tokens-lib tokens-lib}]]
[:> import-export-button*]])) [:> import-export-button*]]))

View file

@ -54,12 +54,6 @@
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
} }
.empty-sets-wrapper {
padding: $s-12;
padding-inline-start: $s-24;
color: var(--color-foreground-secondary);
}
.sidebar-header { .sidebar-header {
display: flex; display: flex;
align-items: center; align-items: center;