agent-ecosystem/src/renderer/utils/fileTreeBuilder.ts
iliya 52ef9fd0a8 feat: add FAQ section to README and improve cross-platform handling in code
- Introduced a comprehensive FAQ section in the README to address common user queries regarding app installation, code handling, agent communication, and project management.
- Enhanced cross-platform keyboard shortcut handling in the Electron app for better user experience on macOS and Windows/Linux.
- Updated signal handling in the standalone process to ensure proper shutdown behavior across platforms.
- Improved WSL user resolution logic to support default user retrieval for better compatibility.
- Enhanced notification handling to support cross-platform features and improve user feedback.
- Refactored SSH connection management to include additional key file types and improve authentication handling.
- Updated team management services to ensure consistent process termination across platforms.
- Improved project path handling in team provisioning to accommodate different operating systems.
- Enhanced editor components to utilize shared utility functions for path management, improving code maintainability.
2026-03-02 22:56:56 +02:00

84 lines
2.5 KiB
TypeScript

/**
* Generic tree builder — converts a flat list of items with paths
* into a hierarchical tree structure with single-child directory collapsing.
*
* Used by ReviewFileTree (FileChangeSummary) and EditorFileTree (FileTreeEntry).
*/
import { splitPath as splitPathCrossPlatform } from '@shared/utils/platformPath';
export interface TreeNode<T> {
name: string;
fullPath: string;
isFile: boolean;
data?: T;
children: TreeNode<T>[];
}
/**
* Build a hierarchical tree from a flat list of items.
*
* @param items - Flat list of items (files/entries)
* @param getPath - Extract relative path from item (using '/' separator)
* @param options.collapse - Merge single-child intermediate directories (default: true)
*/
export function buildTree<T>(
items: T[],
getPath: (item: T) => string,
options?: { collapse?: boolean }
): TreeNode<T>[] {
const root: TreeNode<T> = { name: '', fullPath: '', isFile: false, children: [] };
for (const item of items) {
const parts = splitPathCrossPlatform(getPath(item));
let current = root;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
const isLast = i === parts.length - 1;
const fullPath = parts.slice(0, i + 1).join('/');
let child = current.children.find((c) => c.name === part);
if (!child) {
child = {
name: part,
fullPath,
isFile: isLast,
data: isLast ? item : undefined,
children: [],
};
current.children.push(child);
}
current = child;
}
}
if (options?.collapse === false) {
return root.children;
}
// Collapse children individually — root itself has empty name and must not participate
return root.children.map(collapseTree);
}
/** Merge single-child intermediate directories: a/ → b/ → c becomes a/b/c */
function collapseTree<T>(node: TreeNode<T>): TreeNode<T> {
const collapsed: TreeNode<T> = { ...node, children: node.children.map(collapseTree) };
if (!collapsed.isFile && collapsed.children.length === 1 && !collapsed.children[0].isFile) {
const child = collapsed.children[0];
return {
...child,
name: `${collapsed.name}/${child.name}`,
children: child.children,
};
}
return collapsed;
}
/** Sort tree nodes: directories first, then alphabetical */
export function sortTreeNodes<T>(nodes: TreeNode<T>[]): TreeNode<T>[] {
return [...nodes].sort((a, b) => {
if (a.isFile !== b.isFile) return a.isFile ? 1 : -1;
return a.name.localeCompare(b.name);
});
}