@principal-ade/dynamic-file-tree 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +67 -0
  2. package/dist/index.d.ts +22 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.mjs +1796 -0
  5. package/dist/src/components/DirectoryFilterInput.d.ts +21 -0
  6. package/dist/src/components/DirectoryFilterInput.d.ts.map +1 -0
  7. package/dist/src/components/DynamicFileTree.d.ts +19 -0
  8. package/dist/src/components/DynamicFileTree.d.ts.map +1 -0
  9. package/dist/src/components/DynamicFileTree.stories.d.ts +21 -0
  10. package/dist/src/components/DynamicFileTree.stories.d.ts.map +1 -0
  11. package/dist/src/components/FileTreeContainer.d.ts +12 -0
  12. package/dist/src/components/FileTreeContainer.d.ts.map +1 -0
  13. package/dist/src/components/FileTreeContainer.stories.d.ts +12 -0
  14. package/dist/src/components/FileTreeContainer.stories.d.ts.map +1 -0
  15. package/dist/src/components/GitOrderedFileList.d.ts +40 -0
  16. package/dist/src/components/GitOrderedFileList.d.ts.map +1 -0
  17. package/dist/src/components/GitOrderedFileList.stories.d.ts +20 -0
  18. package/dist/src/components/GitOrderedFileList.stories.d.ts.map +1 -0
  19. package/dist/src/components/GitStatusFileTree.d.ts +28 -0
  20. package/dist/src/components/GitStatusFileTree.d.ts.map +1 -0
  21. package/dist/src/components/GitStatusFileTree.stories.d.ts +22 -0
  22. package/dist/src/components/GitStatusFileTree.stories.d.ts.map +1 -0
  23. package/dist/src/components/GitStatusFileTreeContainer.d.ts +20 -0
  24. package/dist/src/components/GitStatusFileTreeContainer.d.ts.map +1 -0
  25. package/dist/src/components/GitStatusFileTreeContainer.stories.d.ts +21 -0
  26. package/dist/src/components/GitStatusFileTreeContainer.stories.d.ts.map +1 -0
  27. package/dist/src/components/MultiFileTree/MultiFileTree.d.ts +20 -0
  28. package/dist/src/components/MultiFileTree/MultiFileTree.d.ts.map +1 -0
  29. package/dist/src/components/MultiFileTree/MultiFileTree.stories.d.ts +20 -0
  30. package/dist/src/components/MultiFileTree/MultiFileTree.stories.d.ts.map +1 -0
  31. package/dist/src/components/MultiFileTree/MultiFileTreeCore.d.ts +21 -0
  32. package/dist/src/components/MultiFileTree/MultiFileTreeCore.d.ts.map +1 -0
  33. package/dist/src/components/MultiFileTree/index.d.ts +4 -0
  34. package/dist/src/components/MultiFileTree/index.d.ts.map +1 -0
  35. package/dist/src/components/MultiFileTree/types.d.ts +44 -0
  36. package/dist/src/components/MultiFileTree/types.d.ts.map +1 -0
  37. package/dist/src/components/OrderedFileList.d.ts +29 -0
  38. package/dist/src/components/OrderedFileList.d.ts.map +1 -0
  39. package/dist/src/components/OrderedFileList.stories.d.ts +23 -0
  40. package/dist/src/components/OrderedFileList.stories.d.ts.map +1 -0
  41. package/dist/src/components/TreeNode.d.ts +20 -0
  42. package/dist/src/components/TreeNode.d.ts.map +1 -0
  43. package/dist/src/hooks/useContainerHeight.d.ts +19 -0
  44. package/dist/src/hooks/useContainerHeight.d.ts.map +1 -0
  45. package/dist/src/utils/multiTree/combineRepositoryTrees.d.ts +20 -0
  46. package/dist/src/utils/multiTree/combineRepositoryTrees.d.ts.map +1 -0
  47. package/dist/src/utils/multiTree/filterFileTree.d.ts +13 -0
  48. package/dist/src/utils/multiTree/filterFileTree.d.ts.map +1 -0
  49. package/dist/src/utils/multiTree/index.d.ts +4 -0
  50. package/dist/src/utils/multiTree/index.d.ts.map +1 -0
  51. package/dist/src/utils/multiTree/pathUtils.d.ts +18 -0
  52. package/dist/src/utils/multiTree/pathUtils.d.ts.map +1 -0
  53. package/package.json +112 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,1796 @@
1
+ // src/hooks/useContainerHeight.ts
2
+ import { useRef, useEffect, useState } from "react";
3
+ var useContainerHeight = (initialHeight = 600) => {
4
+ const containerRef = useRef(null);
5
+ const [containerHeight, setContainerHeight] = useState(initialHeight);
6
+ useEffect(() => {
7
+ const updateHeight = () => {
8
+ if (containerRef.current) {
9
+ const height = containerRef.current.clientHeight;
10
+ if (height > 0) {
11
+ setContainerHeight(height);
12
+ }
13
+ }
14
+ };
15
+ updateHeight();
16
+ window.addEventListener("resize", updateHeight);
17
+ return () => window.removeEventListener("resize", updateHeight);
18
+ }, []);
19
+ return [containerRef, containerHeight];
20
+ };
21
+ // src/components/DirectoryFilterInput.tsx
22
+ import React, { useState as useState2, useCallback, useRef as useRef2, useEffect as useEffect2 } from "react";
23
+ var DirectoryFilterInput = ({
24
+ fileTree,
25
+ theme,
26
+ filters = [],
27
+ onFiltersChange,
28
+ placeholder = "Type to filter by directory path"
29
+ }) => {
30
+ const [inputValue, setInputValue] = useState2("");
31
+ const [activeFilters, setActiveFilters] = useState2(filters);
32
+ const [showDropdown, setShowDropdown] = useState2(false);
33
+ const [selectedIndex, setSelectedIndex] = useState2(0);
34
+ const [excludeMode, setExcludeMode] = useState2(false);
35
+ const inputRef = useRef2(null);
36
+ const getAllDirectories = useCallback(() => {
37
+ if (!fileTree?.allDirectories)
38
+ return [];
39
+ const directories = new Set;
40
+ fileTree.allDirectories.forEach((dir) => {
41
+ directories.add(dir.relativePath);
42
+ });
43
+ return Array.from(directories).sort();
44
+ }, [fileTree]);
45
+ const getMatchingDirectories = useCallback(() => {
46
+ if (!inputValue)
47
+ return [];
48
+ const allDirs = getAllDirectories();
49
+ const lowerInput = inputValue.toLowerCase();
50
+ return allDirs.filter((dir) => {
51
+ const lowerDir = dir.toLowerCase();
52
+ return lowerDir.includes(lowerInput) && lowerDir !== lowerInput;
53
+ }).slice(0, 10);
54
+ }, [inputValue, getAllDirectories]);
55
+ const matchingDirectories = getMatchingDirectories();
56
+ useEffect2(() => {
57
+ setShowDropdown(inputValue.length > 0 && matchingDirectories.length > 0);
58
+ setSelectedIndex(0);
59
+ }, [inputValue, matchingDirectories.length]);
60
+ const addFilter = useCallback((path, mode) => {
61
+ const newFilter = {
62
+ id: `filter-${Date.now()}-${Math.random()}`,
63
+ path,
64
+ mode
65
+ };
66
+ const updatedFilters = [...activeFilters, newFilter];
67
+ setActiveFilters(updatedFilters);
68
+ onFiltersChange?.(updatedFilters);
69
+ setInputValue("");
70
+ setExcludeMode(false);
71
+ }, [activeFilters, onFiltersChange]);
72
+ const removeFilter = useCallback((filterId) => {
73
+ const updatedFilters = activeFilters.filter((f) => f.id !== filterId);
74
+ setActiveFilters(updatedFilters);
75
+ onFiltersChange?.(updatedFilters);
76
+ }, [activeFilters, onFiltersChange]);
77
+ const toggleFilterMode = useCallback((filterId) => {
78
+ const updatedFilters = activeFilters.map((f) => f.id === filterId ? { ...f, mode: f.mode === "include" ? "exclude" : "include" } : f);
79
+ setActiveFilters(updatedFilters);
80
+ onFiltersChange?.(updatedFilters);
81
+ }, [activeFilters, onFiltersChange]);
82
+ const getCommonPrefix = useCallback(() => {
83
+ if (matchingDirectories.length === 0)
84
+ return null;
85
+ if (matchingDirectories.length === 1) {
86
+ return matchingDirectories[0];
87
+ }
88
+ let commonPrefix = matchingDirectories[0];
89
+ for (let i = 1;i < matchingDirectories.length; i++) {
90
+ const current = matchingDirectories[i];
91
+ let j = 0;
92
+ while (j < commonPrefix.length && j < current.length && commonPrefix[j] === current[j]) {
93
+ j++;
94
+ }
95
+ commonPrefix = commonPrefix.substring(0, j);
96
+ }
97
+ const lastSlash = commonPrefix.lastIndexOf("/");
98
+ if (lastSlash > inputValue.length - 1) {
99
+ return commonPrefix.substring(0, lastSlash + 1);
100
+ }
101
+ return commonPrefix.length > inputValue.length ? commonPrefix : null;
102
+ }, [matchingDirectories, inputValue]);
103
+ const handleKeyDown = useCallback((e) => {
104
+ switch (e.key) {
105
+ case "Tab":
106
+ if (showDropdown && matchingDirectories.length > 0) {
107
+ e.preventDefault();
108
+ const commonPrefix = getCommonPrefix();
109
+ if (commonPrefix) {
110
+ setInputValue(commonPrefix);
111
+ }
112
+ }
113
+ break;
114
+ case "ArrowDown":
115
+ if (showDropdown && matchingDirectories.length > 0) {
116
+ e.preventDefault();
117
+ setSelectedIndex((prev) => prev < matchingDirectories.length - 1 ? prev + 1 : prev);
118
+ }
119
+ break;
120
+ case "ArrowUp":
121
+ if (showDropdown && matchingDirectories.length > 0) {
122
+ e.preventDefault();
123
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : prev);
124
+ }
125
+ break;
126
+ case "Enter":
127
+ e.preventDefault();
128
+ if (showDropdown && matchingDirectories[selectedIndex]) {
129
+ addFilter(matchingDirectories[selectedIndex], excludeMode ? "exclude" : "include");
130
+ } else if (inputValue.trim()) {
131
+ addFilter(inputValue.trim(), excludeMode ? "exclude" : "include");
132
+ }
133
+ setShowDropdown(false);
134
+ break;
135
+ case "Escape":
136
+ if (showDropdown) {
137
+ e.preventDefault();
138
+ setShowDropdown(false);
139
+ }
140
+ break;
141
+ }
142
+ }, [showDropdown, matchingDirectories, selectedIndex, inputValue, excludeMode, addFilter, getCommonPrefix]);
143
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
144
+ className: "relative"
145
+ }, /* @__PURE__ */ React.createElement("div", {
146
+ className: "flex items-center gap-2"
147
+ }, /* @__PURE__ */ React.createElement("input", {
148
+ ref: inputRef,
149
+ type: "text",
150
+ value: inputValue,
151
+ onChange: (e) => setInputValue(e.target.value),
152
+ onKeyDown: handleKeyDown,
153
+ onFocus: () => setShowDropdown(inputValue.length > 0 && matchingDirectories.length > 0),
154
+ placeholder,
155
+ style: {
156
+ flex: 1,
157
+ padding: "8px 12px",
158
+ fontSize: "14px",
159
+ borderRadius: "4px",
160
+ border: `1px solid ${showDropdown ? theme.colors.primary : theme.colors.border}`,
161
+ backgroundColor: theme.colors.backgroundSecondary || theme.colors.background,
162
+ color: theme.colors.text,
163
+ outline: "none",
164
+ transition: "border-color 0.2s"
165
+ }
166
+ }), inputValue && /* @__PURE__ */ React.createElement("button", {
167
+ onClick: () => setExcludeMode(!excludeMode),
168
+ style: {
169
+ padding: "8px 12px",
170
+ fontSize: "12px",
171
+ fontWeight: 500,
172
+ borderRadius: "4px",
173
+ border: `1px solid ${excludeMode ? theme.colors.primary : theme.colors.border}`,
174
+ backgroundColor: excludeMode ? `${theme.colors.primary}20` : theme.colors.backgroundSecondary || theme.colors.background,
175
+ color: excludeMode ? theme.colors.text : theme.colors.textSecondary,
176
+ cursor: "pointer",
177
+ transition: "all 0.2s"
178
+ },
179
+ title: excludeMode ? "Excluding files in this directory" : "Including only files in this directory"
180
+ }, excludeMode ? "Exclude" : "Include")), showDropdown && matchingDirectories.length > 0 && /* @__PURE__ */ React.createElement("div", {
181
+ style: {
182
+ position: "absolute",
183
+ zIndex: 10,
184
+ width: "100%",
185
+ marginTop: "4px",
186
+ borderRadius: "4px",
187
+ border: `1px solid ${theme.colors.primary}`,
188
+ backgroundColor: theme.colors.background,
189
+ boxShadow: `0 4px 6px -1px ${theme.colors.border}40`,
190
+ maxHeight: "256px",
191
+ overflowY: "auto"
192
+ }
193
+ }, matchingDirectories.map((dir, index) => /* @__PURE__ */ React.createElement("div", {
194
+ key: dir,
195
+ style: {
196
+ padding: "8px 12px",
197
+ cursor: "pointer",
198
+ fontSize: "14px",
199
+ backgroundColor: index === selectedIndex ? theme.colors.primary : "transparent",
200
+ color: index === selectedIndex ? "#ffffff" : theme.colors.text || theme.colors.textSecondary,
201
+ transition: "background-color 0.15s, color 0.15s",
202
+ fontWeight: index === selectedIndex ? 500 : 400
203
+ },
204
+ onMouseEnter: () => setSelectedIndex(index),
205
+ onClick: () => {
206
+ addFilter(dir, excludeMode ? "exclude" : "include");
207
+ setShowDropdown(false);
208
+ }
209
+ }, dir)))), activeFilters.length > 0 && /* @__PURE__ */ React.createElement("div", {
210
+ style: { marginTop: "8px", display: "flex", flexWrap: "wrap", gap: "8px" }
211
+ }, activeFilters.map((filter) => /* @__PURE__ */ React.createElement("div", {
212
+ key: filter.id,
213
+ style: {
214
+ display: "flex",
215
+ alignItems: "center",
216
+ gap: "4px",
217
+ padding: "4px 8px",
218
+ borderRadius: "4px",
219
+ fontSize: "12px",
220
+ backgroundColor: filter.mode === "include" ? `${theme.colors.primary}20` : `${theme.colors.error}20`,
221
+ border: `1px solid ${filter.mode === "include" ? theme.colors.primary : theme.colors.error}`,
222
+ color: theme.colors.text
223
+ }
224
+ }, /* @__PURE__ */ React.createElement("span", {
225
+ onClick: () => toggleFilterMode(filter.id),
226
+ style: {
227
+ cursor: "pointer",
228
+ userSelect: "none"
229
+ },
230
+ title: "Click to toggle include/exclude"
231
+ }, filter.mode === "include" ? "✓" : "✗", " ", filter.path), /* @__PURE__ */ React.createElement("button", {
232
+ onClick: () => removeFilter(filter.id),
233
+ style: {
234
+ marginLeft: "4px",
235
+ background: "none",
236
+ border: "none",
237
+ color: theme.colors.textSecondary,
238
+ cursor: "pointer",
239
+ padding: 0,
240
+ fontSize: "14px"
241
+ },
242
+ title: "Remove filter"
243
+ }, "×")))));
244
+ };
245
+ // src/components/TreeNode.tsx
246
+ import { ChevronRight, ChevronDown } from "lucide-react";
247
+ import React2, { useState as useState3 } from "react";
248
+ function TreeNode({
249
+ node,
250
+ style,
251
+ dragHandle,
252
+ theme,
253
+ rightContent,
254
+ extraContent,
255
+ isSelectedDirectory = false,
256
+ nameColor,
257
+ horizontalNodePadding = "8px",
258
+ onContextMenu
259
+ }) {
260
+ const [isHovered, setIsHovered] = useState3(false);
261
+ const isFolder = node.isInternal;
262
+ const caret = isFolder ? /* @__PURE__ */ React2.createElement("span", {
263
+ style: { marginRight: "4px", display: "flex", alignItems: "center" }
264
+ }, node.isOpen ? /* @__PURE__ */ React2.createElement(ChevronDown, {
265
+ size: 16,
266
+ color: theme.colors.text
267
+ }) : /* @__PURE__ */ React2.createElement(ChevronRight, {
268
+ size: 16,
269
+ color: theme.colors.text
270
+ })) : null;
271
+ const backgroundColor = node.isSelected ? `${theme.colors.primary}20` : isSelectedDirectory ? `${theme.colors.primary}15` : isHovered ? `${theme.colors.text}10` : "transparent";
272
+ const border = node.isSelected ? `1px solid ${theme.colors.primary}` : "1px solid transparent";
273
+ const color = nameColor ? nameColor : node.isSelected || isSelectedDirectory ? theme.colors.primary : theme.colors.text;
274
+ return /* @__PURE__ */ React2.createElement("div", {
275
+ style: {
276
+ ...style,
277
+ backgroundColor,
278
+ border,
279
+ color,
280
+ cursor: "pointer",
281
+ paddingLeft: `calc(${horizontalNodePadding} + ${node.level * 16}px)`,
282
+ paddingRight: horizontalNodePadding,
283
+ paddingTop: "3px",
284
+ paddingBottom: "3px",
285
+ display: "flex",
286
+ alignItems: "center",
287
+ justifyContent: "space-between",
288
+ boxSizing: "border-box",
289
+ lineHeight: "20px"
290
+ },
291
+ ref: dragHandle,
292
+ onClick: () => node.isInternal ? node.toggle() : node.select(),
293
+ onContextMenu: (e) => {
294
+ if (onContextMenu) {
295
+ e.preventDefault();
296
+ onContextMenu(e, node);
297
+ }
298
+ },
299
+ onMouseEnter: () => setIsHovered(true),
300
+ onMouseLeave: () => setIsHovered(false)
301
+ }, /* @__PURE__ */ React2.createElement("div", {
302
+ style: { display: "flex", alignItems: "center", minWidth: 0, flex: 1 }
303
+ }, caret, /* @__PURE__ */ React2.createElement("span", {
304
+ style: {
305
+ overflow: "hidden",
306
+ textOverflow: "ellipsis",
307
+ whiteSpace: "nowrap",
308
+ overflowWrap: "normal",
309
+ wordBreak: "normal"
310
+ }
311
+ }, node.data.name), extraContent), rightContent && /* @__PURE__ */ React2.createElement("div", {
312
+ style: { flexShrink: 0 }
313
+ }, rightContent));
314
+ }
315
+ // src/components/DynamicFileTree.tsx
316
+ import React3, { useMemo } from "react";
317
+ import { Tree } from "react-arborist";
318
+ var sortNodes = (a, b) => {
319
+ const aIsDir = !!a.children;
320
+ const bIsDir = !!b.children;
321
+ if (aIsDir && !bIsDir)
322
+ return -1;
323
+ if (!aIsDir && bIsDir)
324
+ return 1;
325
+ return a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
326
+ };
327
+ var transformFileTree = (fileTree) => {
328
+ const transformNode = (node, parentId) => {
329
+ const id = parentId ? `${parentId}/${node.name}` : node.name;
330
+ const arborNode = {
331
+ id,
332
+ name: node.name
333
+ };
334
+ if ("children" in node && node.children) {
335
+ arborNode.children = node.children.map((child) => transformNode(child, id)).sort(sortNodes);
336
+ }
337
+ return arborNode;
338
+ };
339
+ return fileTree.root.children.map((node) => transformNode(node, "")).sort(sortNodes);
340
+ };
341
+ var filterFileTreeNodes = (nodes, selectedDirs, pathPrefix = "") => {
342
+ if (selectedDirs.size === 0)
343
+ return nodes;
344
+ const result = [];
345
+ for (const node of nodes) {
346
+ const currentPath = pathPrefix ? `${pathPrefix}/${node.name}` : node.name;
347
+ const isSelected = selectedDirs.has(currentPath);
348
+ const isAncestor = Array.from(selectedDirs).some((sel) => sel.startsWith(`${currentPath}/`));
349
+ const isDescendant = Array.from(selectedDirs).some((sel) => currentPath.startsWith(`${sel}/`));
350
+ if ("children" in node && node.children) {
351
+ if (isSelected || isDescendant) {
352
+ result.push(node);
353
+ } else if (isAncestor) {
354
+ const filteredChildren = filterFileTreeNodes(node.children, selectedDirs, currentPath);
355
+ if (filteredChildren.length > 0) {
356
+ result.push({ ...node, children: filteredChildren });
357
+ }
358
+ }
359
+ } else {
360
+ if (isDescendant) {
361
+ result.push(node);
362
+ }
363
+ }
364
+ }
365
+ return result;
366
+ };
367
+ var countVisibleNodes = (nodes, defaultOpen, initialOpenState) => {
368
+ let count = 0;
369
+ const countNode = (node) => {
370
+ count++;
371
+ if (node.children && node.children.length > 0) {
372
+ const isOpen = initialOpenState !== undefined ? initialOpenState[node.id] ?? defaultOpen : defaultOpen;
373
+ if (isOpen) {
374
+ node.children.forEach(countNode);
375
+ }
376
+ }
377
+ };
378
+ nodes.forEach(countNode);
379
+ return count;
380
+ };
381
+ var DynamicFileTree = ({
382
+ fileTree,
383
+ theme,
384
+ selectedDirectories = [],
385
+ selectedFile,
386
+ onDirectorySelect,
387
+ onFileSelect,
388
+ initialOpenState,
389
+ defaultOpen = false,
390
+ horizontalNodePadding,
391
+ onContextMenu,
392
+ initialHeight = 600,
393
+ autoHeight = false
394
+ }) => {
395
+ const NodeRenderer = (props) => {
396
+ const nodePath = props.node.data.id;
397
+ const isSelectedOrChild = selectedDirectories.some((selectedDir) => {
398
+ if (nodePath === selectedDir)
399
+ return true;
400
+ return nodePath.startsWith(selectedDir + "/");
401
+ });
402
+ return /* @__PURE__ */ React3.createElement(TreeNode, {
403
+ ...props,
404
+ theme,
405
+ isSelectedDirectory: isSelectedOrChild,
406
+ horizontalNodePadding,
407
+ onContextMenu: (e, node) => {
408
+ if (onContextMenu) {
409
+ onContextMenu(e, node.data.id, !!node.data.children);
410
+ }
411
+ }
412
+ });
413
+ };
414
+ const treeData = useMemo(() => {
415
+ if (!selectedDirectories || selectedDirectories.length === 0) {
416
+ return transformFileTree(fileTree);
417
+ }
418
+ const selectedDirsSet = new Set(selectedDirectories);
419
+ const filteredNodes = filterFileTreeNodes(fileTree.root.children, selectedDirsSet);
420
+ const filteredFileTree = {
421
+ ...fileTree,
422
+ root: { ...fileTree.root, children: filteredNodes }
423
+ };
424
+ return transformFileTree(filteredFileTree);
425
+ }, [fileTree, selectedDirectories]);
426
+ const handleSelect = (selectedNodes) => {
427
+ const selectedFiles = selectedNodes.filter((node) => !node.data.children).map((node) => node.data.id);
428
+ const selectedDirs = selectedNodes.filter((node) => node.data.children).map((node) => node.data.id);
429
+ if (onFileSelect && selectedFiles.length > 0) {
430
+ onFileSelect(selectedFiles[0]);
431
+ }
432
+ if (onDirectorySelect) {
433
+ onDirectorySelect(selectedDirs);
434
+ }
435
+ };
436
+ const calculatedHeight = useMemo(() => {
437
+ if (autoHeight) {
438
+ const visibleNodeCount = countVisibleNodes(treeData, defaultOpen, initialOpenState);
439
+ const rowHeight = 28;
440
+ return visibleNodeCount * rowHeight;
441
+ }
442
+ return initialHeight;
443
+ }, [autoHeight, treeData, defaultOpen, initialOpenState, initialHeight]);
444
+ const [containerRef, containerHeight] = useContainerHeight(calculatedHeight);
445
+ return /* @__PURE__ */ React3.createElement("div", {
446
+ ref: containerRef,
447
+ style: {
448
+ backgroundColor: theme.colors.background,
449
+ color: theme.colors.text,
450
+ fontFamily: theme.fonts.body,
451
+ ...autoHeight ? {} : { height: "100%" }
452
+ }
453
+ }, /* @__PURE__ */ React3.createElement(Tree, {
454
+ initialData: treeData,
455
+ onSelect: handleSelect,
456
+ openByDefault: defaultOpen,
457
+ ...initialOpenState !== undefined && { initialOpenState },
458
+ ...selectedFile !== undefined && { selection: selectedFile },
459
+ width: "100%",
460
+ height: containerHeight,
461
+ rowHeight: 28
462
+ }, NodeRenderer));
463
+ };
464
+ // src/components/OrderedFileList.tsx
465
+ import React4, { useMemo as useMemo2 } from "react";
466
+ var OrderedFileList = ({
467
+ fileTree,
468
+ theme,
469
+ onFileSelect,
470
+ padding = "8px",
471
+ selectedFile,
472
+ sortBy = "lastModified",
473
+ sortOrder = "desc",
474
+ onContextMenu
475
+ }) => {
476
+ const [containerRef, containerHeight] = useContainerHeight();
477
+ const sortedFiles = useMemo2(() => {
478
+ const files = [...fileTree.allFiles];
479
+ files.sort((a, b) => {
480
+ let comparison = 0;
481
+ switch (sortBy) {
482
+ case "lastModified": {
483
+ const timeA = new Date(a.lastModified).getTime();
484
+ const timeB = new Date(b.lastModified).getTime();
485
+ comparison = timeB - timeA;
486
+ break;
487
+ }
488
+ case "size": {
489
+ comparison = b.size - a.size;
490
+ break;
491
+ }
492
+ case "name": {
493
+ comparison = a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
494
+ break;
495
+ }
496
+ }
497
+ return sortOrder === "asc" ? -comparison : comparison;
498
+ });
499
+ return files;
500
+ }, [fileTree.allFiles, sortBy, sortOrder]);
501
+ const formatDate = (date) => {
502
+ const now = new Date;
503
+ const fileDate = new Date(date);
504
+ const diffMs = now.getTime() - fileDate.getTime();
505
+ const diffMins = Math.floor(diffMs / 60000);
506
+ const diffHours = Math.floor(diffMs / 3600000);
507
+ const diffDays = Math.floor(diffMs / 86400000);
508
+ if (diffMins < 1)
509
+ return "Just now";
510
+ if (diffMins < 60)
511
+ return `${diffMins}m ago`;
512
+ if (diffHours < 24)
513
+ return `${diffHours}h ago`;
514
+ if (diffDays < 7)
515
+ return `${diffDays}d ago`;
516
+ return fileDate.toLocaleDateString(undefined, {
517
+ month: "short",
518
+ day: "numeric",
519
+ year: fileDate.getFullYear() !== now.getFullYear() ? "numeric" : undefined
520
+ });
521
+ };
522
+ const formatSize = (bytes) => {
523
+ if (bytes === 0)
524
+ return "0 B";
525
+ const k = 1024;
526
+ const sizes = ["B", "KB", "MB", "GB"];
527
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
528
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
529
+ };
530
+ const handleFileClick = (file) => {
531
+ if (onFileSelect) {
532
+ onFileSelect(file.relativePath);
533
+ }
534
+ };
535
+ const handleContextMenu = (event, file) => {
536
+ if (onContextMenu) {
537
+ event.preventDefault();
538
+ onContextMenu(event, file.relativePath);
539
+ }
540
+ };
541
+ return /* @__PURE__ */ React4.createElement("div", {
542
+ ref: containerRef,
543
+ style: {
544
+ backgroundColor: theme.colors.background,
545
+ color: theme.colors.text,
546
+ fontFamily: theme.fonts.body,
547
+ height: "100%",
548
+ overflow: "auto",
549
+ padding
550
+ }
551
+ }, /* @__PURE__ */ React4.createElement("div", {
552
+ style: {
553
+ display: "flex",
554
+ flexDirection: "column",
555
+ gap: "2px"
556
+ }
557
+ }, sortedFiles.map((file) => {
558
+ const isSelected = selectedFile === file.relativePath;
559
+ return /* @__PURE__ */ React4.createElement("div", {
560
+ key: file.relativePath,
561
+ onClick: () => handleFileClick(file),
562
+ onContextMenu: (e) => handleContextMenu(e, file),
563
+ style: {
564
+ display: "flex",
565
+ alignItems: "center",
566
+ justifyContent: "space-between",
567
+ padding: "8px 12px",
568
+ borderRadius: "4px",
569
+ cursor: "pointer",
570
+ backgroundColor: isSelected ? theme.colors.primary + "20" : "transparent",
571
+ border: isSelected ? `1px solid ${theme.colors.primary}` : "1px solid transparent",
572
+ transition: "all 0.15s ease"
573
+ },
574
+ onMouseEnter: (e) => {
575
+ if (!isSelected) {
576
+ e.currentTarget.style.backgroundColor = theme.colors.primary + "10";
577
+ }
578
+ },
579
+ onMouseLeave: (e) => {
580
+ if (!isSelected) {
581
+ e.currentTarget.style.backgroundColor = "transparent";
582
+ }
583
+ }
584
+ }, /* @__PURE__ */ React4.createElement("div", {
585
+ style: {
586
+ display: "flex",
587
+ flexDirection: "column",
588
+ flex: 1,
589
+ minWidth: 0
590
+ }
591
+ }, /* @__PURE__ */ React4.createElement("div", {
592
+ style: {
593
+ fontSize: "14px",
594
+ fontWeight: isSelected ? 500 : 400,
595
+ overflow: "hidden",
596
+ textOverflow: "ellipsis",
597
+ whiteSpace: "nowrap"
598
+ },
599
+ title: file.relativePath
600
+ }, file.name), /* @__PURE__ */ React4.createElement("div", {
601
+ style: {
602
+ fontSize: "12px",
603
+ color: theme.colors.secondary || theme.colors.text + "99",
604
+ overflow: "hidden",
605
+ textOverflow: "ellipsis",
606
+ whiteSpace: "nowrap"
607
+ },
608
+ title: file.relativePath
609
+ }, file.relativePath)), /* @__PURE__ */ React4.createElement("div", {
610
+ style: {
611
+ display: "flex",
612
+ flexDirection: "column",
613
+ alignItems: "flex-end",
614
+ fontSize: "12px",
615
+ color: theme.colors.secondary || theme.colors.text + "99",
616
+ marginLeft: "16px",
617
+ flexShrink: 0
618
+ }
619
+ }, /* @__PURE__ */ React4.createElement("div", null, formatDate(file.lastModified)), /* @__PURE__ */ React4.createElement("div", null, formatSize(file.size))));
620
+ })), sortedFiles.length === 0 && /* @__PURE__ */ React4.createElement("div", {
621
+ style: {
622
+ display: "flex",
623
+ alignItems: "center",
624
+ justifyContent: "center",
625
+ height: containerHeight,
626
+ color: theme.colors.secondary || theme.colors.text + "99",
627
+ fontSize: "14px"
628
+ }
629
+ }, "No files found"));
630
+ };
631
+ // src/components/GitOrderedFileList.tsx
632
+ import {
633
+ Plus,
634
+ Minus,
635
+ Edit,
636
+ AlertCircle,
637
+ GitBranch,
638
+ FileQuestion
639
+ } from "lucide-react";
640
+ import React5, { useMemo as useMemo3 } from "react";
641
+ var getGitStatusDisplay = (status, theme) => {
642
+ switch (status) {
643
+ case "M":
644
+ case "MM":
645
+ return {
646
+ icon: /* @__PURE__ */ React5.createElement(Edit, {
647
+ size: 14
648
+ }),
649
+ color: theme.colors.primary || "#007bff",
650
+ label: "Modified",
651
+ priority: 2
652
+ };
653
+ case "A":
654
+ return {
655
+ icon: /* @__PURE__ */ React5.createElement(Plus, {
656
+ size: 14
657
+ }),
658
+ color: "#28a745",
659
+ label: "Added",
660
+ priority: 1
661
+ };
662
+ case "D":
663
+ return {
664
+ icon: /* @__PURE__ */ React5.createElement(Minus, {
665
+ size: 14
666
+ }),
667
+ color: "#dc3545",
668
+ label: "Deleted",
669
+ priority: 3
670
+ };
671
+ case "R":
672
+ return {
673
+ icon: /* @__PURE__ */ React5.createElement(GitBranch, {
674
+ size: 14
675
+ }),
676
+ color: "#6f42c1",
677
+ label: "Renamed",
678
+ priority: 4
679
+ };
680
+ case "C":
681
+ return {
682
+ icon: /* @__PURE__ */ React5.createElement(GitBranch, {
683
+ size: 14
684
+ }),
685
+ color: "#20c997",
686
+ label: "Copied",
687
+ priority: 5
688
+ };
689
+ case "U":
690
+ return {
691
+ icon: /* @__PURE__ */ React5.createElement(AlertCircle, {
692
+ size: 14
693
+ }),
694
+ color: "#fd7e14",
695
+ label: "Unmerged",
696
+ priority: 0
697
+ };
698
+ case "??":
699
+ return {
700
+ icon: /* @__PURE__ */ React5.createElement(FileQuestion, {
701
+ size: 14
702
+ }),
703
+ color: "#6c757d",
704
+ label: "Untracked",
705
+ priority: 6
706
+ };
707
+ case "AM":
708
+ return {
709
+ icon: /* @__PURE__ */ React5.createElement(Plus, {
710
+ size: 14
711
+ }),
712
+ color: "#17a2b8",
713
+ label: "Added & Modified",
714
+ priority: 1
715
+ };
716
+ default:
717
+ return null;
718
+ }
719
+ };
720
+ var GitOrderedFileList = ({
721
+ fileTree,
722
+ theme,
723
+ gitStatusData,
724
+ onFileSelect,
725
+ padding = "8px",
726
+ selectedFile,
727
+ sortBy = "lastModified",
728
+ sortOrder = "desc",
729
+ showOnlyChanged = false,
730
+ onContextMenu
731
+ }) => {
732
+ const [containerRef, containerHeight] = useContainerHeight();
733
+ const gitStatusMap = useMemo3(() => {
734
+ const map = new Map;
735
+ gitStatusData.forEach((item) => {
736
+ map.set(item.filePath, item.status);
737
+ });
738
+ return map;
739
+ }, [gitStatusData]);
740
+ const filesWithGitStatus = useMemo3(() => {
741
+ const enhanced = fileTree.allFiles.map((file) => ({
742
+ ...file,
743
+ gitStatus: gitStatusMap.get(file.relativePath)
744
+ }));
745
+ if (showOnlyChanged) {
746
+ return enhanced.filter((file) => file.gitStatus && file.gitStatus !== null);
747
+ }
748
+ return enhanced;
749
+ }, [fileTree.allFiles, gitStatusMap, showOnlyChanged]);
750
+ const sortedFiles = useMemo3(() => {
751
+ const files = [...filesWithGitStatus];
752
+ files.sort((a, b) => {
753
+ let comparison = 0;
754
+ switch (sortBy) {
755
+ case "lastModified": {
756
+ const timeA = new Date(a.lastModified).getTime();
757
+ const timeB = new Date(b.lastModified).getTime();
758
+ comparison = timeB - timeA;
759
+ break;
760
+ }
761
+ case "size": {
762
+ comparison = b.size - a.size;
763
+ break;
764
+ }
765
+ case "name": {
766
+ comparison = a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
767
+ break;
768
+ }
769
+ case "gitStatus": {
770
+ const aDisplay = a.gitStatus ? getGitStatusDisplay(a.gitStatus, theme) : null;
771
+ const bDisplay = b.gitStatus ? getGitStatusDisplay(b.gitStatus, theme) : null;
772
+ const aPriority = aDisplay?.priority ?? 999;
773
+ const bPriority = bDisplay?.priority ?? 999;
774
+ comparison = aPriority - bPriority;
775
+ break;
776
+ }
777
+ }
778
+ return sortOrder === "asc" ? -comparison : comparison;
779
+ });
780
+ return files;
781
+ }, [filesWithGitStatus, sortBy, sortOrder, theme]);
782
+ const formatDate = (date) => {
783
+ const now = new Date;
784
+ const fileDate = new Date(date);
785
+ const diffMs = now.getTime() - fileDate.getTime();
786
+ const diffMins = Math.floor(diffMs / 60000);
787
+ const diffHours = Math.floor(diffMs / 3600000);
788
+ const diffDays = Math.floor(diffMs / 86400000);
789
+ if (diffMins < 1)
790
+ return "Just now";
791
+ if (diffMins < 60)
792
+ return `${diffMins}m ago`;
793
+ if (diffHours < 24)
794
+ return `${diffHours}h ago`;
795
+ if (diffDays < 7)
796
+ return `${diffDays}d ago`;
797
+ return fileDate.toLocaleDateString(undefined, {
798
+ month: "short",
799
+ day: "numeric",
800
+ year: fileDate.getFullYear() !== now.getFullYear() ? "numeric" : undefined
801
+ });
802
+ };
803
+ const formatSize = (bytes) => {
804
+ if (bytes === 0)
805
+ return "0 B";
806
+ const k = 1024;
807
+ const sizes = ["B", "KB", "MB", "GB"];
808
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
809
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
810
+ };
811
+ const handleFileClick = (file) => {
812
+ if (onFileSelect) {
813
+ onFileSelect(file.relativePath);
814
+ }
815
+ };
816
+ const handleContextMenu = (event, file) => {
817
+ if (onContextMenu) {
818
+ event.preventDefault();
819
+ onContextMenu(event, file.relativePath);
820
+ }
821
+ };
822
+ return /* @__PURE__ */ React5.createElement("div", {
823
+ ref: containerRef,
824
+ style: {
825
+ backgroundColor: theme.colors.background,
826
+ color: theme.colors.text,
827
+ fontFamily: theme.fonts.body,
828
+ height: "100%",
829
+ overflow: "auto",
830
+ padding
831
+ }
832
+ }, /* @__PURE__ */ React5.createElement("div", {
833
+ style: {
834
+ display: "flex",
835
+ flexDirection: "column",
836
+ gap: "2px"
837
+ }
838
+ }, sortedFiles.map((file) => {
839
+ const isSelected = selectedFile === file.relativePath;
840
+ const gitDisplay = file.gitStatus ? getGitStatusDisplay(file.gitStatus, theme) : null;
841
+ return /* @__PURE__ */ React5.createElement("div", {
842
+ key: file.relativePath,
843
+ onClick: () => handleFileClick(file),
844
+ onContextMenu: (e) => handleContextMenu(e, file),
845
+ style: {
846
+ display: "flex",
847
+ alignItems: "center",
848
+ justifyContent: "space-between",
849
+ padding: "8px 12px",
850
+ borderRadius: "4px",
851
+ cursor: "pointer",
852
+ backgroundColor: isSelected ? theme.colors.primary + "20" : "transparent",
853
+ border: isSelected ? `1px solid ${theme.colors.primary}` : "1px solid transparent",
854
+ transition: "all 0.15s ease"
855
+ },
856
+ onMouseEnter: (e) => {
857
+ if (!isSelected) {
858
+ e.currentTarget.style.backgroundColor = theme.colors.primary + "10";
859
+ }
860
+ },
861
+ onMouseLeave: (e) => {
862
+ if (!isSelected) {
863
+ e.currentTarget.style.backgroundColor = "transparent";
864
+ }
865
+ }
866
+ }, /* @__PURE__ */ React5.createElement("div", {
867
+ style: {
868
+ display: "flex",
869
+ alignItems: "center",
870
+ flex: 1,
871
+ minWidth: 0,
872
+ gap: "8px"
873
+ }
874
+ }, gitDisplay && /* @__PURE__ */ React5.createElement("div", {
875
+ style: {
876
+ display: "flex",
877
+ alignItems: "center",
878
+ color: gitDisplay.color,
879
+ flexShrink: 0
880
+ },
881
+ title: gitDisplay.label
882
+ }, gitDisplay.icon), /* @__PURE__ */ React5.createElement("div", {
883
+ style: {
884
+ display: "flex",
885
+ flexDirection: "column",
886
+ flex: 1,
887
+ minWidth: 0
888
+ }
889
+ }, /* @__PURE__ */ React5.createElement("div", {
890
+ style: {
891
+ fontSize: "14px",
892
+ fontWeight: isSelected ? 500 : 400,
893
+ overflow: "hidden",
894
+ textOverflow: "ellipsis",
895
+ whiteSpace: "nowrap",
896
+ color: gitDisplay?.color || theme.colors.text
897
+ },
898
+ title: file.relativePath
899
+ }, file.name), /* @__PURE__ */ React5.createElement("div", {
900
+ style: {
901
+ fontSize: "12px",
902
+ color: theme.colors.secondary || theme.colors.text + "99",
903
+ overflow: "hidden",
904
+ textOverflow: "ellipsis",
905
+ whiteSpace: "nowrap"
906
+ },
907
+ title: file.relativePath
908
+ }, file.relativePath))), /* @__PURE__ */ React5.createElement("div", {
909
+ style: {
910
+ display: "flex",
911
+ flexDirection: "column",
912
+ alignItems: "flex-end",
913
+ fontSize: "12px",
914
+ color: theme.colors.secondary || theme.colors.text + "99",
915
+ marginLeft: "16px",
916
+ flexShrink: 0
917
+ }
918
+ }, /* @__PURE__ */ React5.createElement("div", null, formatDate(file.lastModified)), /* @__PURE__ */ React5.createElement("div", null, formatSize(file.size))));
919
+ })), sortedFiles.length === 0 && /* @__PURE__ */ React5.createElement("div", {
920
+ style: {
921
+ display: "flex",
922
+ alignItems: "center",
923
+ justifyContent: "center",
924
+ height: containerHeight,
925
+ color: theme.colors.secondary || theme.colors.text + "99",
926
+ fontSize: "14px"
927
+ }
928
+ }, showOnlyChanged ? "No changed files" : "No files found"));
929
+ };
930
+ // src/components/FileTreeContainer.tsx
931
+ import React6, { useState as useState4, useMemo as useMemo4 } from "react";
932
+ var FileTreeContainer = ({
933
+ fileTree,
934
+ theme,
935
+ selectedFile,
936
+ onFileSelect,
937
+ onContextMenu
938
+ }) => {
939
+ const [filters, setFilters] = useState4([]);
940
+ const selectedDirectories = useMemo4(() => {
941
+ return filters.filter((f) => f.mode === "include").map((f) => f.path);
942
+ }, [filters]);
943
+ return /* @__PURE__ */ React6.createElement("div", {
944
+ style: { display: "flex", flexDirection: "column", height: "100%" }
945
+ }, /* @__PURE__ */ React6.createElement(DirectoryFilterInput, {
946
+ fileTree,
947
+ theme,
948
+ filters,
949
+ onFiltersChange: setFilters
950
+ }), /* @__PURE__ */ React6.createElement("div", {
951
+ style: { flex: 1, marginTop: "1rem", overflow: "hidden" }
952
+ }, /* @__PURE__ */ React6.createElement(DynamicFileTree, {
953
+ fileTree,
954
+ theme,
955
+ selectedDirectories,
956
+ selectedFile,
957
+ onFileSelect,
958
+ onContextMenu
959
+ })));
960
+ };
961
+ // src/components/GitStatusFileTree.tsx
962
+ import {
963
+ Plus as Plus2,
964
+ Minus as Minus2,
965
+ Edit as Edit2,
966
+ AlertCircle as AlertCircle2,
967
+ GitBranch as GitBranch2,
968
+ FileQuestion as FileQuestion2
969
+ } from "lucide-react";
970
+ import React7, { useMemo as useMemo5 } from "react";
971
+ import { Tree as Tree2 } from "react-arborist";
972
+ var getGitStatusDisplay2 = (status, theme) => {
973
+ switch (status) {
974
+ case "M":
975
+ case "MM":
976
+ return {
977
+ icon: /* @__PURE__ */ React7.createElement(Edit2, {
978
+ size: 14
979
+ }),
980
+ color: theme.colors.primary || "#007bff",
981
+ label: "Modified"
982
+ };
983
+ case "A":
984
+ return {
985
+ icon: /* @__PURE__ */ React7.createElement(Plus2, {
986
+ size: 14
987
+ }),
988
+ color: "#28a745",
989
+ label: "Added"
990
+ };
991
+ case "D":
992
+ return {
993
+ icon: /* @__PURE__ */ React7.createElement(Minus2, {
994
+ size: 14
995
+ }),
996
+ color: "#dc3545",
997
+ label: "Deleted"
998
+ };
999
+ case "R":
1000
+ return {
1001
+ icon: /* @__PURE__ */ React7.createElement(GitBranch2, {
1002
+ size: 14
1003
+ }),
1004
+ color: "#6f42c1",
1005
+ label: "Renamed"
1006
+ };
1007
+ case "C":
1008
+ return {
1009
+ icon: /* @__PURE__ */ React7.createElement(GitBranch2, {
1010
+ size: 14
1011
+ }),
1012
+ color: "#20c997",
1013
+ label: "Copied"
1014
+ };
1015
+ case "U":
1016
+ return {
1017
+ icon: /* @__PURE__ */ React7.createElement(AlertCircle2, {
1018
+ size: 14
1019
+ }),
1020
+ color: "#fd7e14",
1021
+ label: "Unmerged"
1022
+ };
1023
+ case "??":
1024
+ return {
1025
+ icon: /* @__PURE__ */ React7.createElement(FileQuestion2, {
1026
+ size: 14
1027
+ }),
1028
+ color: "#6c757d",
1029
+ label: "Untracked"
1030
+ };
1031
+ case "AM":
1032
+ return {
1033
+ icon: /* @__PURE__ */ React7.createElement(Plus2, {
1034
+ size: 14
1035
+ }),
1036
+ color: "#17a2b8",
1037
+ label: "Added & Modified"
1038
+ };
1039
+ default:
1040
+ return null;
1041
+ }
1042
+ };
1043
+ var sortGitNodes = (a, b) => {
1044
+ const aIsDir = !!a.children;
1045
+ const bIsDir = !!b.children;
1046
+ if (aIsDir && !bIsDir)
1047
+ return -1;
1048
+ if (!aIsDir && bIsDir)
1049
+ return 1;
1050
+ return a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
1051
+ };
1052
+ var transformGitFileTree = (fileTree, gitStatusMap) => {
1053
+ const transformNode = (node, parentId) => {
1054
+ const id = parentId ? `${parentId}/${node.name}` : node.name;
1055
+ const gitStatus = gitStatusMap.get(id);
1056
+ const arborNode = {
1057
+ id,
1058
+ name: node.name,
1059
+ gitStatus
1060
+ };
1061
+ if ("children" in node && node.children) {
1062
+ arborNode.children = node.children.map((child) => transformNode(child, id)).sort(sortGitNodes);
1063
+ if (arborNode.children) {
1064
+ const hasChangedChildren = arborNode.children.some((child) => child.gitStatus || child.hasChangedChildren);
1065
+ arborNode.hasChangedChildren = hasChangedChildren;
1066
+ }
1067
+ }
1068
+ return arborNode;
1069
+ };
1070
+ return fileTree.root.children.map((node) => transformNode(node, "")).sort(sortGitNodes);
1071
+ };
1072
+ var filterGitStatusNodes = (nodes, showUnchangedFiles) => {
1073
+ if (showUnchangedFiles)
1074
+ return nodes;
1075
+ const result = [];
1076
+ for (const node of nodes) {
1077
+ if (node.children) {
1078
+ if (node.gitStatus || node.hasChangedChildren) {
1079
+ const filteredChildren = filterGitStatusNodes(node.children, showUnchangedFiles);
1080
+ result.push({ ...node, children: filteredChildren });
1081
+ }
1082
+ } else {
1083
+ if (node.gitStatus) {
1084
+ result.push(node);
1085
+ }
1086
+ }
1087
+ }
1088
+ return result;
1089
+ };
1090
+ var countVisibleNodes2 = (nodes, openByDefault) => {
1091
+ let count = 0;
1092
+ const countNode = (node) => {
1093
+ count++;
1094
+ if (node.children && node.children.length > 0) {
1095
+ if (openByDefault) {
1096
+ node.children.forEach(countNode);
1097
+ }
1098
+ }
1099
+ };
1100
+ nodes.forEach(countNode);
1101
+ return count;
1102
+ };
1103
+ var GitStatusFileTree = ({
1104
+ fileTree,
1105
+ theme,
1106
+ gitStatusData,
1107
+ selectedDirectories = [],
1108
+ selectedFile,
1109
+ onDirectorySelect,
1110
+ onFileSelect,
1111
+ showUnchangedFiles = true,
1112
+ transparentBackground = false,
1113
+ horizontalNodePadding,
1114
+ onContextMenu,
1115
+ openByDefault,
1116
+ initialHeight = 600,
1117
+ autoHeight = false
1118
+ }) => {
1119
+ const gitStatusMap = useMemo5(() => {
1120
+ const map = new Map;
1121
+ gitStatusData.forEach((item) => {
1122
+ map.set(item.filePath, item.status);
1123
+ });
1124
+ return map;
1125
+ }, [gitStatusData]);
1126
+ const NodeRenderer = (props) => {
1127
+ const { node } = props;
1128
+ const gitDisplay = node.data.gitStatus ? getGitStatusDisplay2(node.data.gitStatus, theme) : null;
1129
+ let nameColor;
1130
+ if (gitDisplay) {
1131
+ nameColor = gitDisplay.color;
1132
+ } else if (node.data.hasChangedChildren) {
1133
+ const baseColor = theme.colors.primary || "#007bff";
1134
+ nameColor = baseColor + "80";
1135
+ }
1136
+ const rightContent = gitDisplay ? /* @__PURE__ */ React7.createElement("div", {
1137
+ style: {
1138
+ display: "flex",
1139
+ alignItems: "center",
1140
+ color: gitDisplay.color,
1141
+ marginRight: "8px"
1142
+ },
1143
+ title: gitDisplay.label
1144
+ }, gitDisplay.icon, /* @__PURE__ */ React7.createElement("span", {
1145
+ style: {
1146
+ marginLeft: "4px",
1147
+ fontSize: "12px",
1148
+ fontWeight: "bold"
1149
+ }
1150
+ }, node.data.gitStatus)) : null;
1151
+ return /* @__PURE__ */ React7.createElement(TreeNode, {
1152
+ ...props,
1153
+ theme,
1154
+ rightContent,
1155
+ nameColor,
1156
+ horizontalNodePadding,
1157
+ onContextMenu: (e, node2) => {
1158
+ if (onContextMenu) {
1159
+ onContextMenu(e, node2.data.id, !!node2.data.children);
1160
+ }
1161
+ }
1162
+ });
1163
+ };
1164
+ const treeData = useMemo5(() => {
1165
+ let transformedData = transformGitFileTree(fileTree, gitStatusMap);
1166
+ if (!showUnchangedFiles) {
1167
+ transformedData = filterGitStatusNodes(transformedData, showUnchangedFiles);
1168
+ }
1169
+ if (selectedDirectories && selectedDirectories.length > 0) {}
1170
+ return transformedData;
1171
+ }, [fileTree, gitStatusMap, showUnchangedFiles, selectedDirectories]);
1172
+ const handleSelect = (selectedNodes) => {
1173
+ const selectedFiles = selectedNodes.filter((node) => !node.data.children).map((node) => node.data.id);
1174
+ const selectedDirs = selectedNodes.filter((node) => node.data.children).map((node) => node.data.id);
1175
+ if (onFileSelect && selectedFiles.length > 0) {
1176
+ onFileSelect(selectedFiles[0]);
1177
+ }
1178
+ if (onDirectorySelect) {
1179
+ onDirectorySelect(selectedDirs);
1180
+ }
1181
+ };
1182
+ const calculatedHeight = useMemo5(() => {
1183
+ if (autoHeight) {
1184
+ const visibleNodeCount = countVisibleNodes2(treeData, openByDefault ?? false);
1185
+ const rowHeight = 28;
1186
+ return visibleNodeCount * rowHeight;
1187
+ }
1188
+ return initialHeight;
1189
+ }, [autoHeight, treeData, openByDefault, initialHeight]);
1190
+ const [containerRef, containerHeight] = useContainerHeight(calculatedHeight);
1191
+ return /* @__PURE__ */ React7.createElement("div", {
1192
+ ref: containerRef,
1193
+ style: {
1194
+ backgroundColor: transparentBackground ? "transparent" : theme.colors.background,
1195
+ color: theme.colors.text,
1196
+ fontFamily: theme.fonts.body,
1197
+ ...autoHeight ? {} : { height: "100%" }
1198
+ }
1199
+ }, /* @__PURE__ */ React7.createElement(Tree2, {
1200
+ initialData: treeData,
1201
+ onSelect: handleSelect,
1202
+ ...selectedFile !== undefined && { selection: selectedFile },
1203
+ ...openByDefault !== undefined && { openByDefault },
1204
+ width: "100%",
1205
+ height: containerHeight,
1206
+ rowHeight: 28
1207
+ }, NodeRenderer));
1208
+ };
1209
+ // src/components/GitStatusFileTreeContainer.tsx
1210
+ import { RefreshCw, Eye, EyeOff, AlertCircle as AlertCircle3 } from "lucide-react";
1211
+ import React8, { useState as useState5, useMemo as useMemo6 } from "react";
1212
+ var GitStatusFileTreeContainer = ({
1213
+ fileTree,
1214
+ theme,
1215
+ gitStatusData,
1216
+ selectedFile,
1217
+ onFileSelect,
1218
+ onRefresh,
1219
+ isLoading = false,
1220
+ error = null,
1221
+ showControls = true,
1222
+ onContextMenu,
1223
+ initialHeight,
1224
+ autoHeight
1225
+ }) => {
1226
+ const [filters, setFilters] = useState5([]);
1227
+ const [showUnchangedFiles, setShowUnchangedFiles] = useState5(true);
1228
+ const selectedDirectories = useMemo6(() => {
1229
+ return filters.filter((f) => f.mode === "include").map((f) => f.path);
1230
+ }, [filters]);
1231
+ const changedFilesCount = gitStatusData.length;
1232
+ const hasChanges = changedFilesCount > 0;
1233
+ const handleRefresh = () => {
1234
+ onRefresh?.();
1235
+ };
1236
+ const toggleShowUnchangedFiles = () => {
1237
+ setShowUnchangedFiles(!showUnchangedFiles);
1238
+ };
1239
+ return /* @__PURE__ */ React8.createElement("div", {
1240
+ style: { display: "flex", flexDirection: "column", height: "100%" }
1241
+ }, showControls && /* @__PURE__ */ React8.createElement("div", {
1242
+ style: {
1243
+ padding: "12px",
1244
+ borderBottom: `1px solid ${theme.colors.border || "#e0e0e0"}`,
1245
+ backgroundColor: theme.colors.backgroundSecondary || theme.colors.background
1246
+ }
1247
+ }, /* @__PURE__ */ React8.createElement("div", {
1248
+ style: {
1249
+ display: "flex",
1250
+ alignItems: "center",
1251
+ justifyContent: "space-between",
1252
+ marginBottom: "8px"
1253
+ }
1254
+ }, /* @__PURE__ */ React8.createElement("div", {
1255
+ style: { display: "flex", alignItems: "center", gap: "8px" }
1256
+ }, /* @__PURE__ */ React8.createElement("h3", {
1257
+ style: {
1258
+ margin: 0,
1259
+ fontSize: "14px",
1260
+ fontWeight: "bold",
1261
+ color: theme.colors.text
1262
+ }
1263
+ }, "Git Status"), hasChanges && /* @__PURE__ */ React8.createElement("span", {
1264
+ style: {
1265
+ backgroundColor: theme.colors.primary,
1266
+ color: "#ffffff",
1267
+ padding: "2px 8px",
1268
+ borderRadius: "12px",
1269
+ fontSize: "12px",
1270
+ fontWeight: "bold"
1271
+ }
1272
+ }, changedFilesCount), isLoading && /* @__PURE__ */ React8.createElement(RefreshCw, {
1273
+ size: 16,
1274
+ color: theme.colors.text,
1275
+ className: "git-status-spinner"
1276
+ })), /* @__PURE__ */ React8.createElement("div", {
1277
+ style: { display: "flex", gap: "8px" }
1278
+ }, /* @__PURE__ */ React8.createElement("button", {
1279
+ onClick: toggleShowUnchangedFiles,
1280
+ style: {
1281
+ background: "none",
1282
+ border: `1px solid ${theme.colors.border || "#ccc"}`,
1283
+ borderRadius: "4px",
1284
+ padding: "4px 8px",
1285
+ cursor: "pointer",
1286
+ display: "flex",
1287
+ alignItems: "center",
1288
+ gap: "4px",
1289
+ fontSize: "12px",
1290
+ color: theme.colors.text
1291
+ },
1292
+ title: showUnchangedFiles ? "Hide unchanged files" : "Show all files"
1293
+ }, showUnchangedFiles ? /* @__PURE__ */ React8.createElement(EyeOff, {
1294
+ size: 14
1295
+ }) : /* @__PURE__ */ React8.createElement(Eye, {
1296
+ size: 14
1297
+ }), showUnchangedFiles ? "Hide unchanged" : "Show all"), /* @__PURE__ */ React8.createElement("button", {
1298
+ onClick: handleRefresh,
1299
+ disabled: isLoading,
1300
+ style: {
1301
+ background: "none",
1302
+ border: `1px solid ${theme.colors.border || "#ccc"}`,
1303
+ borderRadius: "4px",
1304
+ padding: "4px 8px",
1305
+ cursor: isLoading ? "not-allowed" : "pointer",
1306
+ display: "flex",
1307
+ alignItems: "center",
1308
+ gap: "4px",
1309
+ fontSize: "12px",
1310
+ color: theme.colors.text,
1311
+ opacity: isLoading ? 0.6 : 1
1312
+ },
1313
+ title: "Refresh git status"
1314
+ }, /* @__PURE__ */ React8.createElement(RefreshCw, {
1315
+ size: 14
1316
+ }), "Refresh"))), error && /* @__PURE__ */ React8.createElement("div", {
1317
+ style: {
1318
+ display: "flex",
1319
+ alignItems: "center",
1320
+ gap: "8px",
1321
+ padding: "8px",
1322
+ backgroundColor: "#fff3cd",
1323
+ border: "1px solid #ffeaa7",
1324
+ borderRadius: "4px",
1325
+ fontSize: "12px",
1326
+ color: "#856404"
1327
+ }
1328
+ }, /* @__PURE__ */ React8.createElement(AlertCircle3, {
1329
+ size: 14
1330
+ }), error), !error && hasChanges && /* @__PURE__ */ React8.createElement("div", {
1331
+ style: {
1332
+ fontSize: "12px",
1333
+ color: theme.colors.textSecondary || "#666",
1334
+ marginTop: "4px"
1335
+ }
1336
+ }, changedFilesCount, " file", changedFilesCount !== 1 ? "s" : "", " with changes")), /* @__PURE__ */ React8.createElement(DirectoryFilterInput, {
1337
+ fileTree,
1338
+ theme,
1339
+ filters,
1340
+ onFiltersChange: setFilters
1341
+ }), /* @__PURE__ */ React8.createElement("div", {
1342
+ style: { flex: 1, marginTop: "1rem", overflow: "hidden" }
1343
+ }, /* @__PURE__ */ React8.createElement(GitStatusFileTree, {
1344
+ fileTree,
1345
+ theme,
1346
+ gitStatusData,
1347
+ selectedDirectories,
1348
+ selectedFile,
1349
+ onFileSelect,
1350
+ showUnchangedFiles,
1351
+ onContextMenu,
1352
+ initialHeight,
1353
+ autoHeight
1354
+ })));
1355
+ };
1356
+ // src/components/MultiFileTree/MultiFileTree.tsx
1357
+ import React10, { useState as useState6, useMemo as useMemo8 } from "react";
1358
+
1359
+ // src/utils/multiTree/pathUtils.ts
1360
+ function extractNameFromPath(path) {
1361
+ if (!path)
1362
+ return "";
1363
+ const parts = path.split("/");
1364
+ return parts[parts.length - 1] || "";
1365
+ }
1366
+ function parseUnifiedPath(path) {
1367
+ const pathSegments = path.split("/");
1368
+ const sourceName = pathSegments[0];
1369
+ const originalPath = pathSegments.slice(1).join("/");
1370
+ return {
1371
+ sourceName,
1372
+ originalPath
1373
+ };
1374
+ }
1375
+ function updateTreePaths(node, prefix) {
1376
+ const originalPath = node.path || "";
1377
+ const updatedPath = `${prefix}/${originalPath}`;
1378
+ const updatedRelativePath = `${prefix}/${node.relativePath || originalPath}`;
1379
+ if ("children" in node) {
1380
+ const dirNode = node;
1381
+ const nodeName = dirNode.name || extractNameFromPath(originalPath);
1382
+ const updatedDir = {
1383
+ ...dirNode,
1384
+ path: updatedPath,
1385
+ name: nodeName,
1386
+ relativePath: updatedRelativePath,
1387
+ depth: (dirNode.depth || 0) + 1,
1388
+ children: (dirNode.children || []).map((child) => updateTreePaths(child, prefix))
1389
+ };
1390
+ return updatedDir;
1391
+ } else {
1392
+ const fileNode = node;
1393
+ const nodeName = fileNode.name || extractNameFromPath(originalPath);
1394
+ return {
1395
+ ...fileNode,
1396
+ path: updatedPath,
1397
+ name: nodeName,
1398
+ relativePath: updatedRelativePath
1399
+ };
1400
+ }
1401
+ }
1402
+
1403
+ // src/utils/multiTree/combineRepositoryTrees.ts
1404
+ function combineRepositoryTrees(sources, options = {}) {
1405
+ const rootDirectoryName = options.rootDirectoryName || "Repositories";
1406
+ if (sources.length === 0) {
1407
+ return {
1408
+ sha: "empty",
1409
+ root: {
1410
+ path: "",
1411
+ name: rootDirectoryName,
1412
+ children: [],
1413
+ fileCount: 0,
1414
+ totalSize: 0,
1415
+ depth: 0,
1416
+ relativePath: ""
1417
+ },
1418
+ allFiles: [],
1419
+ allDirectories: [],
1420
+ stats: {
1421
+ totalFiles: 0,
1422
+ totalDirectories: 0,
1423
+ totalSize: 0,
1424
+ maxDepth: 0
1425
+ },
1426
+ metadata: {
1427
+ id: "combined-tree",
1428
+ timestamp: new Date,
1429
+ sourceType: "combined",
1430
+ sourceInfo: {}
1431
+ }
1432
+ };
1433
+ }
1434
+ const rootChildren = [];
1435
+ const allFiles = [];
1436
+ const allDirectories = [];
1437
+ let totalFileCount = 0;
1438
+ let totalDirectoryCount = 0;
1439
+ let totalSizeBytes = 0;
1440
+ let maxDepth = 0;
1441
+ const rootDir = {
1442
+ path: "",
1443
+ name: rootDirectoryName,
1444
+ children: [],
1445
+ fileCount: 0,
1446
+ totalSize: 0,
1447
+ depth: 0,
1448
+ relativePath: ""
1449
+ };
1450
+ sources.forEach((source) => {
1451
+ const sourceTree = source.tree;
1452
+ if (!sourceTree || !sourceTree.root) {
1453
+ return;
1454
+ }
1455
+ const sourceName = source.name || "unknown-source";
1456
+ const sourceDir = {
1457
+ path: sourceName,
1458
+ name: sourceName,
1459
+ children: sourceTree.root.children ? sourceTree.root.children.map((child) => {
1460
+ return updateTreePaths(child, sourceName);
1461
+ }) : [],
1462
+ fileCount: sourceTree.root.fileCount || 0,
1463
+ totalSize: sourceTree.root.totalSize || 0,
1464
+ depth: 1,
1465
+ relativePath: sourceName
1466
+ };
1467
+ rootChildren.push(sourceDir);
1468
+ allDirectories.push(sourceDir);
1469
+ if (sourceTree.allFiles) {
1470
+ sourceTree.allFiles.forEach((file) => {
1471
+ const updatedFile = {
1472
+ ...file,
1473
+ path: `${sourceName}/${file.path || ""}`,
1474
+ name: file.name || extractNameFromPath(file.path) || "unknown"
1475
+ };
1476
+ allFiles.push(updatedFile);
1477
+ });
1478
+ }
1479
+ if (sourceTree.allDirectories) {
1480
+ sourceTree.allDirectories.forEach((dir) => {
1481
+ const updatedDir = {
1482
+ ...dir,
1483
+ path: `${sourceName}/${dir.path || ""}`,
1484
+ name: dir.name || extractNameFromPath(dir.path) || "unknown"
1485
+ };
1486
+ allDirectories.push(updatedDir);
1487
+ });
1488
+ }
1489
+ if (sourceTree.stats) {
1490
+ totalFileCount += sourceTree.stats.totalFiles || 0;
1491
+ totalDirectoryCount += sourceTree.stats.totalDirectories || 0;
1492
+ totalSizeBytes += sourceTree.stats.totalSize || 0;
1493
+ maxDepth = Math.max(maxDepth, (sourceTree.stats.maxDepth || 0) + 1);
1494
+ }
1495
+ });
1496
+ rootDir.children = rootChildren;
1497
+ rootDir.fileCount = totalFileCount;
1498
+ rootDir.totalSize = totalSizeBytes;
1499
+ allDirectories.unshift(rootDir);
1500
+ return {
1501
+ sha: "combined",
1502
+ root: rootDir,
1503
+ allFiles,
1504
+ allDirectories,
1505
+ stats: {
1506
+ totalFiles: totalFileCount,
1507
+ totalDirectories: totalDirectoryCount + sources.length + 1,
1508
+ totalSize: totalSizeBytes,
1509
+ maxDepth: maxDepth + 1
1510
+ },
1511
+ metadata: {
1512
+ id: "combined-tree",
1513
+ timestamp: new Date,
1514
+ sourceType: "combined",
1515
+ sourceInfo: { sourceCount: sources.length }
1516
+ }
1517
+ };
1518
+ }
1519
+ // src/utils/multiTree/filterFileTree.ts
1520
+ function filterFileTreeByPaths(tree, selectedPaths) {
1521
+ if (selectedPaths.length === 0) {
1522
+ return tree;
1523
+ }
1524
+ const shouldIncludePath = (path) => {
1525
+ return selectedPaths.some((selectedPath) => path.startsWith(selectedPath) || selectedPath.startsWith(path));
1526
+ };
1527
+ const filterDirectory = (dir) => {
1528
+ if (!shouldIncludePath(dir.path)) {
1529
+ return null;
1530
+ }
1531
+ const filteredChildren = dir.children.map((child) => {
1532
+ if ("children" in child) {
1533
+ return filterDirectory(child);
1534
+ } else {
1535
+ return shouldIncludePath(child.path) ? child : null;
1536
+ }
1537
+ }).filter((child) => child !== null);
1538
+ if (filteredChildren.length === 0 && !selectedPaths.includes(dir.path)) {
1539
+ return null;
1540
+ }
1541
+ return {
1542
+ ...dir,
1543
+ children: filteredChildren,
1544
+ fileCount: filteredChildren.filter((c) => !("children" in c)).length
1545
+ };
1546
+ };
1547
+ const filteredRoot = filterDirectory(tree.root);
1548
+ if (!filteredRoot) {
1549
+ return tree;
1550
+ }
1551
+ const allFiles = tree.allFiles.filter((f) => shouldIncludePath(f.path));
1552
+ const allDirectories = tree.allDirectories.filter((d) => shouldIncludePath(d.path));
1553
+ return {
1554
+ ...tree,
1555
+ root: filteredRoot,
1556
+ allFiles,
1557
+ allDirectories,
1558
+ stats: {
1559
+ ...tree.stats,
1560
+ totalFiles: allFiles.length,
1561
+ totalDirectories: allDirectories.length
1562
+ }
1563
+ };
1564
+ }
1565
+ // src/components/MultiFileTree/MultiFileTreeCore.tsx
1566
+ import React9, { useMemo as useMemo7 } from "react";
1567
+ var MultiFileTreeCore = ({
1568
+ sources,
1569
+ theme,
1570
+ selectedDirectories = [],
1571
+ viewMode = "all",
1572
+ onFileSelect,
1573
+ initialOpenState,
1574
+ defaultOpen = false,
1575
+ horizontalNodePadding
1576
+ }) => {
1577
+ const unifiedTree = useMemo7(() => {
1578
+ return combineRepositoryTrees(sources);
1579
+ }, [sources]);
1580
+ const displayTree = useMemo7(() => {
1581
+ if (viewMode === "selected" && selectedDirectories.length > 0) {
1582
+ return filterFileTreeByPaths(unifiedTree, selectedDirectories);
1583
+ }
1584
+ return unifiedTree;
1585
+ }, [unifiedTree, viewMode, selectedDirectories]);
1586
+ const handleFileSelect = (filePath) => {
1587
+ if (!onFileSelect)
1588
+ return;
1589
+ const { sourceName, originalPath } = parseUnifiedPath(filePath);
1590
+ const source = sources.find((s) => s.name === sourceName);
1591
+ if (source) {
1592
+ onFileSelect(source, originalPath);
1593
+ }
1594
+ };
1595
+ return /* @__PURE__ */ React9.createElement(DynamicFileTree, {
1596
+ key: `unified-${viewMode}-${selectedDirectories.join(",")}`,
1597
+ fileTree: displayTree,
1598
+ theme,
1599
+ selectedDirectories,
1600
+ onFileSelect: handleFileSelect,
1601
+ initialOpenState,
1602
+ defaultOpen,
1603
+ horizontalNodePadding
1604
+ });
1605
+ };
1606
+
1607
+ // src/components/MultiFileTree/MultiFileTree.tsx
1608
+ var MultiFileTree = ({
1609
+ sources,
1610
+ theme,
1611
+ showHeader = true,
1612
+ showFilters = true,
1613
+ showViewModeToggle = true,
1614
+ showSelectedFileIndicator = true,
1615
+ initialViewMode = "all",
1616
+ rootDirectoryName = "Repositories",
1617
+ title = "Repository Explorer",
1618
+ onFileSelect,
1619
+ initialOpenState,
1620
+ defaultOpen = false,
1621
+ horizontalNodePadding
1622
+ }) => {
1623
+ const [selectedFile, setSelectedFile] = useState6(null);
1624
+ const [viewMode, setViewMode] = useState6(initialViewMode);
1625
+ const [directoryFilters, setDirectoryFilters] = useState6([]);
1626
+ const unifiedTree = useMemo8(() => {
1627
+ return combineRepositoryTrees(sources, { rootDirectoryName });
1628
+ }, [sources, rootDirectoryName]);
1629
+ const currentSelectedDirs = directoryFilters.filter((f) => f.mode === "include").map((f) => f.path);
1630
+ const handleFileSelect = (source, filePath) => {
1631
+ setSelectedFile({ source, path: filePath });
1632
+ if (onFileSelect) {
1633
+ onFileSelect(source, filePath);
1634
+ }
1635
+ };
1636
+ const handleFiltersChange = (filters) => {
1637
+ setDirectoryFilters(filters);
1638
+ };
1639
+ return /* @__PURE__ */ React10.createElement("div", {
1640
+ style: {
1641
+ width: "100%",
1642
+ height: "100%",
1643
+ display: "flex",
1644
+ flexDirection: "column",
1645
+ gap: "16px"
1646
+ }
1647
+ }, /* @__PURE__ */ React10.createElement("div", {
1648
+ style: {
1649
+ flex: 1,
1650
+ border: `2px solid ${theme.colors.border}`,
1651
+ borderRadius: "12px",
1652
+ overflow: "hidden",
1653
+ backgroundColor: theme.colors.background,
1654
+ display: "flex",
1655
+ flexDirection: "column"
1656
+ }
1657
+ }, showHeader && /* @__PURE__ */ React10.createElement("div", {
1658
+ style: {
1659
+ padding: "16px",
1660
+ borderBottom: `1px solid ${theme.colors.border}`,
1661
+ backgroundColor: theme.colors.backgroundSecondary,
1662
+ display: "flex",
1663
+ justifyContent: "space-between",
1664
+ alignItems: "center",
1665
+ flexWrap: "wrap",
1666
+ gap: "12px"
1667
+ }
1668
+ }, /* @__PURE__ */ React10.createElement("h4", {
1669
+ style: {
1670
+ fontSize: "16px",
1671
+ fontWeight: "600",
1672
+ color: theme.colors.text,
1673
+ margin: 0
1674
+ }
1675
+ }, title), showViewModeToggle && /* @__PURE__ */ React10.createElement("div", {
1676
+ style: {
1677
+ display: "flex",
1678
+ gap: "8px"
1679
+ }
1680
+ }, /* @__PURE__ */ React10.createElement("button", {
1681
+ onClick: () => setViewMode("all"),
1682
+ style: {
1683
+ padding: "6px 12px",
1684
+ fontSize: "13px",
1685
+ fontWeight: "500",
1686
+ backgroundColor: viewMode === "all" ? theme.colors.primary : "transparent",
1687
+ color: viewMode === "all" ? theme.colors.background : theme.colors.text,
1688
+ border: `1px solid ${viewMode === "all" ? theme.colors.primary : theme.colors.border}`,
1689
+ borderRadius: "6px",
1690
+ cursor: "pointer",
1691
+ transition: "all 0.2s ease"
1692
+ }
1693
+ }, "Show All"), /* @__PURE__ */ React10.createElement("button", {
1694
+ onClick: () => setViewMode("selected"),
1695
+ disabled: currentSelectedDirs.length === 0,
1696
+ style: {
1697
+ padding: "6px 12px",
1698
+ fontSize: "13px",
1699
+ fontWeight: "500",
1700
+ backgroundColor: viewMode === "selected" ? theme.colors.primary : "transparent",
1701
+ color: viewMode === "selected" ? theme.colors.background : currentSelectedDirs.length === 0 ? theme.colors.textSecondary : theme.colors.text,
1702
+ border: `1px solid ${viewMode === "selected" ? theme.colors.primary : theme.colors.border}`,
1703
+ borderRadius: "6px",
1704
+ cursor: currentSelectedDirs.length === 0 ? "not-allowed" : "pointer",
1705
+ transition: "all 0.2s ease",
1706
+ opacity: currentSelectedDirs.length === 0 ? 0.5 : 1
1707
+ },
1708
+ title: currentSelectedDirs.length === 0 ? "Select directories below first" : `Show only selected directories (${currentSelectedDirs.length})`
1709
+ }, "Show Selected (", currentSelectedDirs.length, ")"))), showFilters && /* @__PURE__ */ React10.createElement("div", {
1710
+ style: {
1711
+ padding: "16px",
1712
+ borderBottom: `1px solid ${theme.colors.border}`,
1713
+ backgroundColor: theme.colors.background
1714
+ }
1715
+ }, /* @__PURE__ */ React10.createElement("div", {
1716
+ style: {
1717
+ fontSize: "13px",
1718
+ fontWeight: "500",
1719
+ color: theme.colors.text,
1720
+ marginBottom: "8px"
1721
+ }
1722
+ }, "Select Directories to Filter:"), /* @__PURE__ */ React10.createElement(DirectoryFilterInput, {
1723
+ fileTree: unifiedTree,
1724
+ theme,
1725
+ filters: directoryFilters,
1726
+ onFiltersChange: handleFiltersChange
1727
+ }), currentSelectedDirs.length === 0 && /* @__PURE__ */ React10.createElement("div", {
1728
+ style: {
1729
+ fontSize: "12px",
1730
+ color: theme.colors.textSecondary,
1731
+ marginTop: "8px",
1732
+ fontStyle: "italic"
1733
+ }
1734
+ }, 'Type to search and select directories from any repository. Selected directories will be highlighted when you click "Show Selected".'), currentSelectedDirs.length > 0 && viewMode === "all" && /* @__PURE__ */ React10.createElement("div", {
1735
+ style: {
1736
+ fontSize: "12px",
1737
+ color: theme.colors.primary,
1738
+ marginTop: "8px"
1739
+ }
1740
+ }, "✓ ", currentSelectedDirs.length, " director", currentSelectedDirs.length === 1 ? "y" : "ies", ' selected. Click "Show Selected" to filter the view.')), /* @__PURE__ */ React10.createElement("div", {
1741
+ style: {
1742
+ flex: 1,
1743
+ overflow: "auto",
1744
+ padding: "16px"
1745
+ }
1746
+ }, viewMode === "selected" && currentSelectedDirs.length > 0 && /* @__PURE__ */ React10.createElement("div", {
1747
+ style: {
1748
+ fontSize: "13px",
1749
+ color: theme.colors.textSecondary,
1750
+ marginBottom: "12px",
1751
+ padding: "8px 12px",
1752
+ backgroundColor: theme.colors.backgroundSecondary,
1753
+ borderRadius: "6px",
1754
+ border: `1px solid ${theme.colors.border}`
1755
+ }
1756
+ }, "Showing only selected directories:", " ", currentSelectedDirs.map((dir) => {
1757
+ const segments = dir.split("/");
1758
+ return segments.length > 1 ? `${segments[0]}/${segments[segments.length - 1]}` : dir;
1759
+ }).join(", ")), /* @__PURE__ */ React10.createElement(MultiFileTreeCore, {
1760
+ sources,
1761
+ theme,
1762
+ selectedDirectories: currentSelectedDirs,
1763
+ viewMode,
1764
+ onFileSelect: handleFileSelect,
1765
+ initialOpenState,
1766
+ defaultOpen,
1767
+ horizontalNodePadding
1768
+ }))), showSelectedFileIndicator && selectedFile && /* @__PURE__ */ React10.createElement("div", {
1769
+ style: {
1770
+ padding: "12px 16px",
1771
+ backgroundColor: theme.colors.backgroundSecondary,
1772
+ borderRadius: "8px",
1773
+ border: `1px solid ${theme.colors.border}`,
1774
+ fontSize: "12px",
1775
+ color: theme.colors.textSecondary
1776
+ }
1777
+ }, "Selected: ", selectedFile.source.name, " / ", selectedFile.path));
1778
+ };
1779
+ export {
1780
+ useContainerHeight,
1781
+ updateTreePaths,
1782
+ parseUnifiedPath,
1783
+ filterFileTreeByPaths,
1784
+ extractNameFromPath,
1785
+ combineRepositoryTrees,
1786
+ TreeNode,
1787
+ OrderedFileList,
1788
+ MultiFileTreeCore,
1789
+ MultiFileTree,
1790
+ GitStatusFileTreeContainer,
1791
+ GitStatusFileTree,
1792
+ GitOrderedFileList,
1793
+ FileTreeContainer,
1794
+ DynamicFileTree,
1795
+ DirectoryFilterInput
1796
+ };