Create sets inside folders

This commit is contained in:
Florian Schrödl 2025-02-04 10:59:28 +01:00 committed by GitHub
parent 315b389a66
commit ec8109644b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 176 additions and 121 deletions

View file

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

View file

@ -671,18 +671,31 @@ used for managing active sets without a user created theme.")
;; === Import / Export from DTCG format ;; === Import / Export from DTCG format
(defn walk-sets-tree-seq (defn walk-sets-tree-seq
[nodes & {:keys [walk-children?] "Walk sets tree as a flat list.
:or {walk-children? (constantly true)}}]
Options:
`:skip-children-pred`: predicate to skip iterating over a set groups children by checking the path of the set group
`:new-editing-set-path`: append a an item with `:new?` at the given path"
[nodes & {:keys [skip-children-pred new-editing-set-path]
:or {skip-children-pred (constantly false)}}]
(let [walk (fn walk [node {:keys [parent depth] (let [walk (fn walk [node {:keys [parent depth]
:or {parent [] :or {parent []
depth 0} depth 0}
:as opts}] :as opts}]
(lazy-seq (lazy-seq
(if (d/ordered-map? node) (if (d/ordered-map? node)
(mapcat #(walk % opts) node) (let [root (cond-> node
(= [] new-editing-set-path) (assoc :new? true))]
(mapcat #(walk % opts) root))
(let [[k v] node] (let [[k v] node]
(cond (cond
;;; Set ;; New set
(= :new? k) [{:new? true
:group? false
:parent-path parent
:depth depth}]
;; Set
(and v (instance? TokenSet v)) (and v (instance? TokenSet v))
[{:group? false [{:group? false
:path (split-token-set-path (:name v)) :path (split-token-set-path (:name v))
@ -698,12 +711,12 @@ used for managing active sets without a user created theme.")
:path path :path path
:parent-path parent :parent-path parent
:depth depth}] :depth depth}]
(if (walk-children? path) (if (skip-children-pred path)
[item] [item]
(cons (let [v' (cond-> v
item (= path new-editing-set-path) (assoc :new? true))]
(mapcat #(walk % (assoc opts :parent path :depth (inc depth))) v)))))))))] (cons item (mapcat #(walk % (assoc opts :parent path :depth (inc depth))) v'))))))))))]
(walk nodes nil))) (walk (or nodes (d/ordered-map)) nil)))
(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 '.'."

View file

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

View file

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

View file

@ -108,11 +108,9 @@
(dch/commit-changes changes) (dch/commit-changes changes)
(wtu/update-workspace-tokens)))))) (wtu/update-workspace-tokens))))))
(defn create-token-set [token-set] (defn create-token-set [set-name token-set]
(let [new-token-set (merge (let [new-token-set (-> token-set
{:name "Token Set" (update :name #(if (empty? %) set-name (ctob/join-set-path [% set-name]))))]
:tokens []}
token-set)]
(ptk/reify ::create-token-set (ptk/reify ::create-token-set
ptk/WatchEvent ptk/WatchEvent
(watch [it _ _] (watch [it _ _]

View file

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

View file

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

View file

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

View file

@ -201,13 +201,13 @@
(mf/defc add-set-button (mf/defc add-set-button
[{:keys [on-open style]}] [{:keys [on-open style]}]
(let [{:keys [on-create new?]} (sets-context/use-context) (let [{:keys [on-create new-path]} (sets-context/use-context)
on-click #(do on-click #(do
(on-open) (on-open)
(on-create)) (on-create []))
can-edit? (:can-edit (deref refs/permissions))] can-edit? (:can-edit (deref refs/permissions))]
(if (= style "inline") (if (= style "inline")
(when-not new? (when-not new-path
(if can-edit? (if can-edit?
[:div {:class (stl/css :empty-sets-wrapper)} [:div {:class (stl/css :empty-sets-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)} [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
@ -227,9 +227,9 @@
(mf/defc theme-sets-list (mf/defc theme-sets-list
[{:keys [on-open]}] [{:keys [on-open]}]
(let [token-sets (mf/deref refs/workspace-ordered-token-sets) (let [token-sets (mf/deref refs/workspace-ordered-token-sets)
{:keys [new?] :as ctx} (sets-context/use-context)] {:keys [new-path] :as ctx} (sets-context/use-context)]
(if (and (empty? token-sets) (if (and (empty? token-sets)
(not new?)) (not new-path))
[:& add-set-button {:on-open on-open [:& add-set-button {:on-open on-open
:style "inline"}] :style "inline"}]
[:& h/sortable-container {} [:& h/sortable-container {}

View file

@ -6518,6 +6518,10 @@ msgstr "Enter token value or alias"
msgid "workspace.token.grouping-set-alert" msgid "workspace.token.grouping-set-alert"
msgstr "Token Set grouping is not supported yet." msgstr "Token Set grouping is not supported yet."
#: src/app/main/ui/workspace/tokens/sets_context_menu.cljs
msgid "workspace.token.add-set-to-group"
msgstr "Add set to this group"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs:179 #: src/app/main/ui/workspace/tokens/modals/themes.cljs:179
msgid "workspace.token.label.group" msgid "workspace.token.label.group"
msgstr "Group" msgstr "Group"