- 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.
84 lines
2.5 KiB
TypeScript
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);
|
|
});
|
|
}
|