* chore: remove dead/unreferenced files across ui, vscode Remove 59 unused source files (components, hooks, lib utils, stores, barrels, and orphaned vscode github modules) that are not imported by any entry-reachable code. Also drop a stale test mock for the removed execCommands module. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * refactor: remove unused exported symbols (types, functions, consts, hooks) Remove exported symbols whose identifier is referenced nowhere in the repository (verified via repo-wide search), across ui types/contracts, lib utilities, sync layer, stores, and components. Also drop the few imports/private helpers orphaned by these removals. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * refactor: remove more unused exports (desktop, shortcuts, worktree, vscode) Continue removing repo-wide unreferenced exported functions, consts and types across lib/desktop, shortcuts, worktreeSessionCreator, sync, and vscode gitService, with cascading orphaned helpers/imports cleaned up. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * chore: add dead-code cleanup tooling * refactor: checkpoint dead-code cleanup * refactor: remove dead-code suppressions --------- Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
179 lines
4.1 KiB
TypeScript
179 lines
4.1 KiB
TypeScript
/**
|
|
* JSON Tree utilities for interactive JSON viewing.
|
|
* Provides parsing, tree building, flattening, and path utilities.
|
|
*/
|
|
|
|
type JsonTreeNodeType = 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null';
|
|
|
|
export interface JsonTreeNode {
|
|
id: string;
|
|
key: string;
|
|
value: unknown;
|
|
type: JsonTreeNodeType;
|
|
depth: number;
|
|
children?: JsonTreeNode[];
|
|
path: string[];
|
|
isExpandable: boolean;
|
|
childCount?: number;
|
|
}
|
|
|
|
export interface FlatJsonNode {
|
|
node: JsonTreeNode;
|
|
isExpanded: boolean;
|
|
}
|
|
|
|
export interface JsonTreeOptions {
|
|
maxDepth?: number;
|
|
maxNodes?: number;
|
|
initiallyExpandedDepth?: number;
|
|
}
|
|
|
|
const DEFAULT_OPTIONS: Required<JsonTreeOptions> = {
|
|
maxDepth: 50,
|
|
maxNodes: 100_000,
|
|
initiallyExpandedDepth: 2,
|
|
};
|
|
|
|
function getType(value: unknown): JsonTreeNodeType {
|
|
if (value === null) return 'null';
|
|
if (Array.isArray(value)) return 'array';
|
|
if (typeof value === 'object') return 'object';
|
|
if (typeof value === 'string') return 'string';
|
|
if (typeof value === 'number') return 'number';
|
|
if (typeof value === 'boolean') return 'boolean';
|
|
return 'null';
|
|
}
|
|
|
|
function getNodePath(pathSegments: string[]): string {
|
|
if (pathSegments.length === 0) return 'root';
|
|
let result = 'root';
|
|
for (const segment of pathSegments) {
|
|
if (/^\d+$/.test(segment)) {
|
|
result += `[${segment}]`;
|
|
} else {
|
|
result += `.${segment}`;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
let nodeCount = 0;
|
|
|
|
function buildTreeNode(
|
|
value: unknown,
|
|
key: string,
|
|
path: string[],
|
|
depth: number,
|
|
options: Required<JsonTreeOptions>,
|
|
): JsonTreeNode | null {
|
|
if (nodeCount >= options.maxNodes) return null;
|
|
nodeCount++;
|
|
|
|
const type = getType(value);
|
|
const id = getNodePath(path);
|
|
const isExpandable = type === 'object' || type === 'array';
|
|
|
|
const node: JsonTreeNode = {
|
|
id,
|
|
key,
|
|
value,
|
|
type,
|
|
depth,
|
|
path,
|
|
isExpandable,
|
|
};
|
|
|
|
if (isExpandable && depth < options.maxDepth) {
|
|
const entries = type === 'array'
|
|
? (value as unknown[]).map((v, i) => [String(i), v] as const)
|
|
: Object.entries(value as Record<string, unknown>);
|
|
|
|
node.childCount = entries.length;
|
|
node.children = [];
|
|
for (const [childKey, childValue] of entries) {
|
|
const childPath = [...path, childKey];
|
|
const child = buildTreeNode(childValue, childKey, childPath, depth + 1, options);
|
|
if (child) node.children.push(child);
|
|
}
|
|
} else if (isExpandable) {
|
|
node.childCount = type === 'array'
|
|
? (value as unknown[]).length
|
|
: Object.keys(value as Record<string, unknown>).length;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
export function parseJsonToTree(text: string, options?: JsonTreeOptions): JsonTreeNode | null {
|
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
nodeCount = 0;
|
|
|
|
try {
|
|
const trimmed = text.trim();
|
|
if (!trimmed) return null;
|
|
const parsed = JSON.parse(trimmed);
|
|
return buildTreeNode(parsed, 'root', [], 0, opts);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function flattenTree(root: JsonTreeNode | null, collapsedPaths: Set<string>): FlatJsonNode[] {
|
|
if (!root) return [];
|
|
|
|
const result: FlatJsonNode[] = [];
|
|
|
|
function walk(node: JsonTreeNode) {
|
|
const isExpanded = !collapsedPaths.has(node.id);
|
|
result.push({ node, isExpanded });
|
|
|
|
if (node.isExpandable && isExpanded && node.children) {
|
|
for (const child of node.children) {
|
|
walk(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
walk(root);
|
|
return result;
|
|
}
|
|
|
|
export function getAllExpandableIds(root: JsonTreeNode | null): string[] {
|
|
if (!root) return [];
|
|
const ids: string[] = [];
|
|
|
|
function walk(node: JsonTreeNode) {
|
|
if (node.isExpandable) {
|
|
ids.push(node.id);
|
|
if (node.children) {
|
|
for (const child of node.children) {
|
|
walk(child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
walk(root);
|
|
return ids;
|
|
}
|
|
|
|
export function getExpandableIdsAboveDepth(root: JsonTreeNode | null, maxDepth: number): string[] {
|
|
if (!root) return [];
|
|
const ids: string[] = [];
|
|
|
|
function walk(node: JsonTreeNode) {
|
|
if (node.isExpandable && node.depth >= maxDepth) {
|
|
ids.push(node.id);
|
|
}
|
|
if (node.children) {
|
|
for (const child of node.children) {
|
|
walk(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
walk(root);
|
|
return ids;
|
|
}
|
|
|