Skip to content

Commit d6fe958

Browse files
authored
SY-2442 - Various Minor Console Fixes (#1234)
* [console] - various minor fixes * [console] - various PR touchups * [pluto] - improved tree specs * [ops/ts] - touchups * [pluto] - addressed PR comments' * [pluto] - addressed PR comments'
1 parent def0cb6 commit d6fe958

13 files changed

Lines changed: 1062 additions & 42 deletions

File tree

console/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@synnaxlabs/console",
33
"private": true,
4-
"version": "0.41.1",
4+
"version": "0.41.2",
55
"type": "module",
66
"scripts": {
77
"dev": "tauri dev",

console/src/group/services/ontology.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,25 +106,20 @@ const useUngroupSelection = (): ((props: Ontology.TreeContextMenuProps) => void)
106106
} = props;
107107
if (selection.parentID == null) return;
108108
// Sort the groups by depth that way deeper nested groups are ungrouped first.
109-
selection.resources.sort((a, b) => {
110-
const a_depth =
111-
selection.nodes.find((n) => n.key === a.id.toString())?.depth ?? 0;
112-
const b_depth =
113-
selection.nodes.find((n) => n.key === b.id.toString())?.depth ?? 0;
114-
return b_depth - a_depth;
115-
});
109+
selection.nodes.sort((a, b) => b.depth - a.depth);
116110
const prevNodes = Tree.deepCopy(nodes);
117-
const isLevel0 = selection.nodes.some(({ depth }) => depth === 0);
118111
setNodes([
119-
...selection.resources.reduce((acc, { id }) => {
120-
const children =
121-
Tree.findNode({ tree: nodes, key: id.toString() })?.children ?? [];
112+
...selection.nodes.reduce((acc, { key }) => {
113+
const children = Tree.findNode({ tree: nodes, key })?.children ?? [];
122114
acc = Tree.moveNode({
123115
tree: acc,
124-
destination: isLevel0 ? null : selection.parentID.toString(),
116+
destination:
117+
selection.parentID.toString() === selection.rootID.toString()
118+
? null
119+
: selection.parentID.toString(),
125120
keys: children.map((c) => c.key),
126121
});
127-
acc = Tree.removeNode({ tree: acc, keys: id.toString() });
122+
acc = Tree.removeNode({ tree: acc, keys: key });
128123
return acc;
129124
}, nodes),
130125
]);

console/src/layouts/Main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const Main = (): ReactElement => (
9797
style={{ paddingRight: "1rem", paddingBottom: "1rem" }}
9898
>
9999
<Nav.Left />
100-
<Align.Space size="tiny" grow>
100+
<Align.Space size="tiny" grow style={{ width: 0 }}>
101101
<Align.Space x size="tiny" grow style={{ height: 0 }}>
102102
<Layout.Nav.Drawer location="left" menuItems={Nav.DRAWER_ITEMS} />
103103
<Mosaic />

console/src/layouts/Mosaic.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ const NavTop = (): ReactElement | null => {
326326
startIcon={<Icon.Visualize />}
327327
endIcon={
328328
<Align.Space style={{ marginLeft: "0.5rem", marginRight: "-1rem" }}>
329-
<Triggers.Text level="small" shade={11} weight={450} trigger={["v"]} />
329+
<Triggers.Text level="small" shade={9} weight={450} trigger={["V"]} />
330330
</Align.Space>
331331
}
332332
>

console/src/ontology/Tree.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -376,15 +376,13 @@ const Internal = ({ root }: TreeProps): ReactElement => {
376376
);
377377
const firstNodeOfMinDepth = dropped.find(({ data }) => data?.depth === minDepth);
378378
if (firstNodeOfMinDepth == null) return [];
379-
// Find the parent where the node is being dropped.
379+
const moved = dropped.filter(({ data }) => data?.depth === minDepth);
380+
const keys = moved.map(({ key }) => key as string);
380381
const parent = Core.findNodeParent({
381382
tree: nodesSnapshot,
382383
key: firstNodeOfMinDepth.key.toString(),
383384
});
384-
if (parent == null) return [];
385-
const moved = dropped.filter(({ data }) => data?.depth === minDepth);
386-
const keys = moved.map(({ key }) => key as string);
387-
const sourceID = new ontology.ID(parent.key);
385+
const sourceID = new ontology.ID(parent?.key ?? root.toString());
388386
treeProps.contract(...keys);
389387
dropMutation.mutate({
390388
source: sourceID,
@@ -393,7 +391,7 @@ const Internal = ({ root }: TreeProps): ReactElement => {
393391
});
394392
return moved;
395393
},
396-
[client, treeProps.contract],
394+
[client, treeProps.contract, root],
397395
);
398396

399397
const getRenameProps = useCallback(

pluto/src/tree/Tree.spec.tsx

Lines changed: 269 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,22 @@
99

1010
import { describe, expect, it } from "vitest";
1111

12-
import { type Node, sortAndSplice } from "@/tree/core";
12+
import {
13+
deepCopy,
14+
findNode,
15+
findNodeParent,
16+
findNodes,
17+
flatten,
18+
getAllNodesOfMinDepth,
19+
getDescendants,
20+
moveNode,
21+
type Node,
22+
type NodeWithPosition,
23+
removeNode,
24+
setNode,
25+
sortAndSplice,
26+
updateNode,
27+
} from "@/tree/core";
1328

1429
describe("Tree", () => {
1530
describe("sortAndSplice", () => {
@@ -40,4 +55,257 @@ describe("Tree", () => {
4055
]);
4156
});
4257
});
58+
59+
describe("flatten", () => {
60+
it("should correctly flatten a tree structure", () => {
61+
const nodes: Node[] = [
62+
{
63+
key: "1",
64+
name: "parent1",
65+
children: [
66+
{ key: "1-1", name: "child1" },
67+
{ key: "1-2", name: "child2" },
68+
],
69+
},
70+
{ key: "2", name: "parent2" },
71+
];
72+
const expanded = ["1"];
73+
const result = flatten({ nodes, expanded });
74+
expect(result).toEqual([
75+
{
76+
key: "1",
77+
name: "parent1",
78+
depth: 0,
79+
expanded: true,
80+
index: 0,
81+
path: "1/",
82+
children: [
83+
{ key: "1-1", name: "child1" },
84+
{ key: "1-2", name: "child2" },
85+
],
86+
},
87+
{
88+
key: "1-1",
89+
name: "child1",
90+
depth: 1,
91+
expanded: false,
92+
index: 0,
93+
path: "1/1-1/",
94+
},
95+
{
96+
key: "1-2",
97+
name: "child2",
98+
depth: 1,
99+
expanded: false,
100+
index: 1,
101+
path: "1/1-2/",
102+
},
103+
{ key: "2", name: "parent2", depth: 0, expanded: false, index: 1, path: "2/" },
104+
]);
105+
});
106+
107+
it("should respect sort option", () => {
108+
const nodes: Node[] = [
109+
{ key: "2", name: "B" },
110+
{ key: "1", name: "A" },
111+
];
112+
const result = flatten({ nodes, expanded: [], sort: true });
113+
expect(result[0].name).toBe("A");
114+
expect(result[1].name).toBe("B");
115+
});
116+
});
117+
118+
describe("moveNode", () => {
119+
it("should move nodes to root when destination is null", () => {
120+
const tree: Node[] = [
121+
{
122+
key: "1",
123+
name: "parent",
124+
children: [{ key: "2", name: "child" }],
125+
},
126+
];
127+
const result = moveNode({ tree, destination: null, keys: "2" });
128+
expect(result).toHaveLength(2);
129+
expect(result.find((n) => n.key === "2")).toBeDefined();
130+
});
131+
132+
it("should move node to new parent", () => {
133+
const tree: Node[] = [
134+
{ key: "1", name: "parent1" },
135+
{ key: "2", name: "parent2", children: [] },
136+
];
137+
const result = moveNode({ tree, destination: "2", keys: "1" });
138+
expect(result[0].children?.[0].key).toBe("1");
139+
});
140+
});
141+
142+
describe("removeNode", () => {
143+
it("should remove node from root level", () => {
144+
const tree: Node[] = [
145+
{ key: "1", name: "node1" },
146+
{ key: "2", name: "node2" },
147+
];
148+
const result = removeNode({ tree, keys: "1" });
149+
expect(result).toHaveLength(1);
150+
expect(result[0].key).toBe("2");
151+
});
152+
153+
it("should remove node from nested level", () => {
154+
const tree: Node[] = [
155+
{
156+
key: "1",
157+
name: "parent",
158+
children: [{ key: "2", name: "child" }],
159+
},
160+
];
161+
const result = removeNode({ tree, keys: "2" });
162+
expect(result[0].children).toHaveLength(0);
163+
});
164+
});
165+
166+
describe("setNode", () => {
167+
it("should add nodes to root when destination is null", () => {
168+
const tree: Node[] = [{ key: "1", name: "existing" }];
169+
const addition: Node = { key: "2", name: "new" };
170+
const result = setNode({ tree, destination: null, additions: addition });
171+
expect(result).toHaveLength(2);
172+
expect(result.find((n) => n.key === "2")).toBeDefined();
173+
});
174+
175+
it("should add nodes to specified parent", () => {
176+
const tree: Node[] = [{ key: "1", name: "parent", children: [] }];
177+
const addition: Node = { key: "2", name: "child" };
178+
const result = setNode({ tree, destination: "1", additions: addition });
179+
expect(result[0].children).toHaveLength(1);
180+
expect(result[0].children?.[0].key).toBe("2");
181+
});
182+
});
183+
184+
describe("updateNode", () => {
185+
it("should update node properties", () => {
186+
const tree: Node[] = [{ key: "1", name: "old" }];
187+
const result = updateNode({
188+
tree,
189+
key: "1",
190+
updater: (node) => ({ ...node, name: "new" }),
191+
});
192+
expect(result[0].name).toBe("new");
193+
});
194+
195+
it("should throw on missing node when throwOnMissing is true", () => {
196+
const tree: Node[] = [];
197+
expect(() =>
198+
updateNode({
199+
tree,
200+
key: "missing",
201+
updater: (node) => node,
202+
}),
203+
).toThrow();
204+
});
205+
});
206+
207+
describe("findNode", () => {
208+
it("should find node and return position info", () => {
209+
const tree: Node[] = [
210+
{
211+
key: "1",
212+
name: "parent",
213+
children: [{ key: "2", name: "child" }],
214+
},
215+
];
216+
const result = findNode({ tree, key: "2" });
217+
expect(result?.depth).toBe(1);
218+
expect(result?.position).toBe(0);
219+
});
220+
221+
it("should return null for non-existent node", () => {
222+
const tree: Node[] = [{ key: "1", name: "node" }];
223+
const result = findNode({ tree, key: "missing" });
224+
expect(result).toBeNull();
225+
});
226+
});
227+
228+
describe("findNodes", () => {
229+
it("should find multiple nodes", () => {
230+
const tree: Node[] = [
231+
{ key: "1", name: "node1" },
232+
{ key: "2", name: "node2" },
233+
];
234+
const result = findNodes({ tree, keys: ["1", "2"] });
235+
expect(result).toHaveLength(2);
236+
});
237+
});
238+
239+
describe("findNodeParent", () => {
240+
it("should find parent of nested node", () => {
241+
const tree: Node[] = [
242+
{
243+
key: "1",
244+
name: "parent",
245+
children: [{ key: "2", name: "child" }],
246+
},
247+
];
248+
const result = findNodeParent({ tree, key: "2" });
249+
expect(result?.key).toBe("1");
250+
});
251+
252+
it("should return null for root level node", () => {
253+
const tree: Node[] = [{ key: "1", name: "root" }];
254+
const result = findNodeParent({ tree, key: "1" });
255+
expect(result).toBeNull();
256+
});
257+
});
258+
259+
describe("deepCopy", () => {
260+
it("should create deep copy of tree structure", () => {
261+
const original: Node[] = [
262+
{
263+
key: "1",
264+
name: "parent",
265+
children: [{ key: "2", name: "child" }],
266+
},
267+
];
268+
const copy = deepCopy(original);
269+
copy[0].name = "modified";
270+
expect(original[0].name).toBe("parent");
271+
});
272+
});
273+
274+
describe("getDescendants", () => {
275+
it("should return all descendants of nodes", () => {
276+
const node: Node = {
277+
key: "1",
278+
name: "parent",
279+
children: [
280+
{ key: "2", name: "child1" },
281+
{
282+
key: "3",
283+
name: "child2",
284+
children: [{ key: "4", name: "grandchild" }],
285+
},
286+
],
287+
};
288+
const result = getDescendants(node);
289+
expect(result).toHaveLength(4);
290+
expect(result.map((n) => n.key)).toEqual(["1", "2", "3", "4"]);
291+
});
292+
});
293+
294+
describe("getAllNodesOfMinDepth", () => {
295+
it("should return nodes at minimum depth", () => {
296+
const nodes: NodeWithPosition[] = [
297+
{ key: "1", name: "root1", depth: 0, position: 0 },
298+
{ key: "2", name: "root2", depth: 0, position: 1 },
299+
{ key: "3", name: "child", depth: 1, position: 0 },
300+
];
301+
const result = getAllNodesOfMinDepth(nodes);
302+
expect(result).toHaveLength(2);
303+
expect(result.every((n) => n.depth === 0)).toBe(true);
304+
});
305+
306+
it("should return empty array for empty input", () => {
307+
const result = getAllNodesOfMinDepth([]);
308+
expect(result).toHaveLength(0);
309+
});
310+
});
43311
});

pluto/src/tree/core.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,8 @@ export interface RemoveNodeProps {
134134

135135
export const removeNode = ({ tree, keys }: RemoveNodeProps): Node[] => {
136136
keys = toArray(keys);
137-
const treeKeys = tree.map((node) => node.key);
138137
keys.forEach((key) => {
139-
const index = treeKeys.indexOf(key);
138+
const index = tree.findIndex((node) => node.key === key);
140139
if (index !== -1) tree.splice(index, 1);
141140
else {
142141
const parent = findNodeParent({ tree, key });

0 commit comments

Comments
 (0)