Sidebar elements editor

This commit is contained in:
2026-02-28 02:41:17 +01:00
parent 7d4a087c10
commit b543988f02
4 changed files with 501 additions and 40 deletions
+141 -1
View File
@@ -1,4 +1,4 @@
import { Workspace, TodoEntity, DocEntity, Status } from "./types";
import { Workspace, TodoEntity, DocEntity, Status, LayoutNode } from "./types";
export function createDefaultWorkspace(): Workspace {
const now = Date.now();
@@ -129,3 +129,143 @@ export function listWorkspaces(): Workspace[] {
}
return workspaces;
}
// layout helpers -------------------------------------------------------------------
type RemoveResult = { layout: LayoutNode[]; removedEntityIds: string[] };
function collectEntityIds(node: LayoutNode): string[] {
if (node.type === "item") return [node.entityId];
const ids: string[] = [];
for (const child of node.children) {
ids.push(...collectEntityIds(child));
}
return ids;
}
export function removeLayoutNode(
layout: LayoutNode[],
id: string,
): RemoveResult {
let removed: string[] = [];
function recurse(nodes: LayoutNode[]): LayoutNode[] {
return nodes
.map((node) => {
if (node.type === "group") {
if (node.id === id) {
removed.push(...collectEntityIds(node));
return null;
}
const children = recurse(node.children);
if (children.length !== node.children.length) {
// some children removed
}
return { ...node, children } as LayoutNode;
} else {
if (node.entityId === id) {
removed.push(node.entityId);
return null;
}
return node;
}
})
.filter(Boolean) as LayoutNode[];
}
const newLayout = recurse(layout);
return { layout: newLayout, removedEntityIds: removed };
}
export function renameLayoutGroup(
layout: LayoutNode[],
id: string,
newName: string,
): LayoutNode[] {
return layout.map((node) => {
if (node.type === "group") {
if (node.id === id) {
return { ...node, name: newName };
}
return {
...node,
children: renameLayoutGroup(node.children, id, newName),
};
}
return node;
});
}
export function addLayoutGroup(
layout: LayoutNode[],
parentId: string | null,
group: LayoutNode,
): LayoutNode[] {
if (!parentId) {
return [...layout, group];
}
return layout.map((node) => {
if (node.type === "group") {
if (node.id === parentId) {
return { ...node, children: [...node.children, group] };
}
return {
...node,
children: addLayoutGroup(node.children, parentId, group),
};
}
return node;
});
}
export function addLayoutItem(
layout: LayoutNode[],
parentId: string | null,
item: LayoutNode,
): LayoutNode[] {
if (!parentId) {
return [...layout, item];
}
return layout.map((node) => {
if (node.type === "group") {
if (node.id === parentId) {
return { ...node, children: [...node.children, item] };
}
return {
...node,
children: addLayoutItem(node.children, parentId, item),
};
}
return node;
});
}
// entity creation helpers ----------------------------------------------------------
export function createTodoEntity(name = "New list"): TodoEntity {
const id = crypto.randomUUID();
const now = Date.now();
return {
id,
type: "todo",
name,
config: { statuses: [] },
tags: {},
tasks: {},
createdAt: now,
updatedAt: now,
};
}
export function createDocEntity(name = "New doc"): DocEntity {
const id = crypto.randomUUID();
const now = Date.now();
return {
id,
type: "doc",
name,
content: { version: "2.28.0", time: now, blocks: [] },
createdAt: now,
updatedAt: now,
};
}