@anymux/ui-kit 0.1.0
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.
- package/dist/ExplorerLayout-CSIJd7N4.js +105 -0
- package/dist/ExplorerLayout-CSIJd7N4.js.map +1 -0
- package/dist/FileBrowserContext-B6jixa2j.js +11 -0
- package/dist/FileBrowserContext-B6jixa2j.js.map +1 -0
- package/dist/calendar-DSlrbHoj.js +761 -0
- package/dist/calendar-DSlrbHoj.js.map +1 -0
- package/dist/calendar.d.ts +3 -0
- package/dist/calendar.js +3 -0
- package/dist/contacts-DQXTZzHc.js +539 -0
- package/dist/contacts-DQXTZzHc.js.map +1 -0
- package/dist/contacts.d.ts +3 -0
- package/dist/contacts.js +3 -0
- package/dist/file-browser-m5atC3kF.js +6755 -0
- package/dist/file-browser-m5atC3kF.js.map +1 -0
- package/dist/file-browser.d.ts +11 -0
- package/dist/file-browser.js +9 -0
- package/dist/git-B55e6LL-.js +561 -0
- package/dist/git-B55e6LL-.js.map +1 -0
- package/dist/git.d.ts +2 -0
- package/dist/git.js +3 -0
- package/dist/iconMap-V4B8P-Uh.js +206 -0
- package/dist/iconMap-V4B8P-Uh.js.map +1 -0
- package/dist/icons-CIsIOZXR.js +0 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +4 -0
- package/dist/index-BNmNIWBL.d.ts +71 -0
- package/dist/index-BNmNIWBL.d.ts.map +1 -0
- package/dist/index-Bryv_GCG.d.ts +1481 -0
- package/dist/index-Bryv_GCG.d.ts.map +1 -0
- package/dist/index-CuQIjSXs.d.ts +134 -0
- package/dist/index-CuQIjSXs.d.ts.map +1 -0
- package/dist/index-DSu19mq0.d.ts +153 -0
- package/dist/index-DSu19mq0.d.ts.map +1 -0
- package/dist/index-DmsyeHFr.d.ts +149 -0
- package/dist/index-DmsyeHFr.d.ts.map +1 -0
- package/dist/index-DxnJ8FYM.d.ts +17 -0
- package/dist/index-DxnJ8FYM.d.ts.map +1 -0
- package/dist/index-DzfY1Tok.d.ts +32 -0
- package/dist/index-DzfY1Tok.d.ts.map +1 -0
- package/dist/index-Ml_SgiKa.d.ts +1847 -0
- package/dist/index-Ml_SgiKa.d.ts.map +1 -0
- package/dist/index-kHr9udZD.d.ts +1025 -0
- package/dist/index-kHr9udZD.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +15 -0
- package/dist/layout-Ca_4r8ka.js +89 -0
- package/dist/layout-Ca_4r8ka.js.map +1 -0
- package/dist/layout.d.ts +2 -0
- package/dist/layout.js +5 -0
- package/dist/list-CxfT6hix.js +6831 -0
- package/dist/list-CxfT6hix.js.map +1 -0
- package/dist/list.d.ts +2 -0
- package/dist/list.js +5 -0
- package/dist/media-DZ292aKK.js +557 -0
- package/dist/media-DZ292aKK.js.map +1 -0
- package/dist/media.d.ts +3 -0
- package/dist/media.js +3 -0
- package/dist/tree-Dd9Z0Aso.js +3351 -0
- package/dist/tree-Dd9Z0Aso.js.map +1 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.js +6 -0
- package/dist/types-common-CB3kRek8.d.ts +26 -0
- package/dist/types-common-CB3kRek8.d.ts.map +1 -0
- package/dist/utils-B4fdKKsy.js +3 -0
- package/package.json +109 -0
- package/src/calendar/AgendaView.tsx +37 -0
- package/src/calendar/CalendarBrowser.tsx +90 -0
- package/src/calendar/CalendarModel.ts +142 -0
- package/src/calendar/CalendarSidebar.tsx +81 -0
- package/src/calendar/DayView.tsx +76 -0
- package/src/calendar/EventCard.tsx +51 -0
- package/src/calendar/MockCalendarProvider.ts +98 -0
- package/src/calendar/MonthView.tsx +77 -0
- package/src/calendar/WeekView.tsx +129 -0
- package/src/calendar/index.ts +18 -0
- package/src/calendar/types.ts +25 -0
- package/src/contacts/ContactAvatar.tsx +35 -0
- package/src/contacts/ContactBrowser.tsx +56 -0
- package/src/contacts/ContactCard.tsx +37 -0
- package/src/contacts/ContactDetail.tsx +63 -0
- package/src/contacts/ContactGroupSidebar.tsx +40 -0
- package/src/contacts/ContactList.tsx +32 -0
- package/src/contacts/ContactListModel.ts +120 -0
- package/src/contacts/MockContactProvider.ts +77 -0
- package/src/contacts/index.ts +17 -0
- package/src/contacts/types.ts +26 -0
- package/src/demos/CalendarBrowserDemo.tsx +15 -0
- package/src/demos/ContactBrowserDemo.tsx +15 -0
- package/src/demos/MediaBrowserDemo.tsx +15 -0
- package/src/file-browser/adapters/DocumentViewerAdapter.ts +371 -0
- package/src/file-browser/adapters/FileSystemBridge.ts +168 -0
- package/src/file-browser/adapters/GitBrowserAdapter.ts +546 -0
- package/src/file-browser/adapters/README.md +504 -0
- package/src/file-browser/adapters/index.ts +27 -0
- package/src/file-browser/adapters/types.ts +70 -0
- package/src/file-browser/architecture.md +645 -0
- package/src/file-browser/components/CreateItemDialog.tsx +71 -0
- package/src/file-browser/components/DeleteConfirmDialog.tsx +58 -0
- package/src/file-browser/components/FileBrowser.tsx +473 -0
- package/src/file-browser/components/FileBrowserContent.tsx +209 -0
- package/src/file-browser/components/FileBrowserHeader.tsx +151 -0
- package/src/file-browser/components/FileBrowserToolbar.tsx +145 -0
- package/src/file-browser/components/LeftPanel/LeftPanel.tsx +103 -0
- package/src/file-browser/components/LeftPanel/LeftPanelTabs.tsx +70 -0
- package/src/file-browser/components/LeftPanel/TreeNavigationView.tsx +256 -0
- package/src/file-browser/components/PreviewPane.tsx +146 -0
- package/src/file-browser/components/RightPanel/FilePreview.tsx +219 -0
- package/src/file-browser/components/RightPanel/RightPanel.tsx +186 -0
- package/src/file-browser/components/RightPanel/RightPanelToolbar.tsx +113 -0
- package/src/file-browser/components/UploadProgress.tsx +123 -0
- package/src/file-browser/components/ViewerHost.tsx +208 -0
- package/src/file-browser/components/mobile/MobileNavigation.tsx +227 -0
- package/src/file-browser/components/navigation/NavigationButtons.tsx +171 -0
- package/src/file-browser/components/shared/ErrorBoundary.tsx +116 -0
- package/src/file-browser/components/shared/FileBrowserItem.tsx +195 -0
- package/src/file-browser/components/shared/FileIcon.tsx +169 -0
- package/src/file-browser/components/toolbar/ViewModeToggle.tsx +200 -0
- package/src/file-browser/components/views/ListView/ListView.tsx +484 -0
- package/src/file-browser/components/views/ThumbnailView/ThumbnailView.tsx +323 -0
- package/src/file-browser/components/views/TreeView/TreeNode.tsx +186 -0
- package/src/file-browser/components/views/TreeView/TreeNodeList.tsx +191 -0
- package/src/file-browser/components/views/TreeView/TreeView.tsx +200 -0
- package/src/file-browser/components/views/TreemapView/TreemapView.tsx +339 -0
- package/src/file-browser/context/FileBrowserContext.tsx +13 -0
- package/src/file-browser/examples/BasicUsage.tsx +20 -0
- package/src/file-browser/index.ts +98 -0
- package/src/file-browser/models/FileBrowserModel.ts +623 -0
- package/src/file-browser/models/LeftPanelManagerModel.ts +105 -0
- package/src/file-browser/models/NavigationManagerModel.ts +312 -0
- package/src/file-browser/models/ResponsiveLayoutManagerModel.ts +437 -0
- package/src/file-browser/models/RightPanelManagerModel.ts +190 -0
- package/src/file-browser/models/SelectionManagerModel.ts +252 -0
- package/src/file-browser/models/ToolbarManagerModel.ts +144 -0
- package/src/file-browser/models/UploadModel.ts +147 -0
- package/src/file-browser/models/ViewModeManagerModel.ts +185 -0
- package/src/file-browser/models/ViewerHostModel.ts +44 -0
- package/src/file-browser/models/ui/ListViewUIModel.ts +265 -0
- package/src/file-browser/models/ui/PreviewUIModel.ts +297 -0
- package/src/file-browser/models/ui/ThumbnailViewUIModel.ts +254 -0
- package/src/file-browser/models/ui/TreeViewUIModel.ts +128 -0
- package/src/file-browser/models/ui/TreemapViewUIModel.ts +350 -0
- package/src/file-browser/providers/FileSystemListProvider.ts +552 -0
- package/src/file-browser/providers/FileSystemProvider.ts +401 -0
- package/src/file-browser/providers/FileSystemTreeProvider.ts +231 -0
- package/src/file-browser/providers/GitProvider.ts +337 -0
- package/src/file-browser/providers/GitRepositoryProvider.ts +376 -0
- package/src/file-browser/providers/IFileBrowserProvider.ts +56 -0
- package/src/file-browser/providers/MemoryProvider.ts +303 -0
- package/src/file-browser/providers/index.ts +4 -0
- package/src/file-browser/registry/ViewerRegistry.ts +551 -0
- package/src/file-browser/registry/types.ts +144 -0
- package/src/file-browser/scripts/performanceBenchmark.ts +553 -0
- package/src/file-browser/services/ThumbnailCacheService.ts +128 -0
- package/src/file-browser/tasks.md +537 -0
- package/src/file-browser/types/FileBrowserTypes.ts +126 -0
- package/src/file-browser/types/ProviderTypes.ts +155 -0
- package/src/file-browser/types/UITypes.ts +235 -0
- package/src/file-browser/types/ViewModeTypes.ts +150 -0
- package/src/file-browser/utils/gestures.ts +327 -0
- package/src/file-browser/utils/performance.ts +563 -0
- package/src/file-browser/viewers/ImageViewer.tsx +163 -0
- package/src/file-browser/viewers/ImageViewerModel.ts +79 -0
- package/src/file-browser/viewers/TextViewer.tsx +95 -0
- package/src/file-browser/viewers/UnsupportedFileViewer.tsx +57 -0
- package/src/file-browser/viewers/index.ts +61 -0
- package/src/git/BranchList.tsx +128 -0
- package/src/git/CommitGraph.tsx +239 -0
- package/src/git/CommitList.tsx +258 -0
- package/src/git/DiffViewer.tsx +219 -0
- package/src/git/index.ts +4 -0
- package/src/icons/iconMap.ts +146 -0
- package/src/icons/index.ts +9 -0
- package/src/index.ts +13 -0
- package/src/layout/README.md +307 -0
- package/src/layout/components/ExplorerLayout/ExplorerLayout.tsx +178 -0
- package/src/layout/examples/SimpleExample.tsx +60 -0
- package/src/layout/index.ts +6 -0
- package/src/lib/utils.ts +1 -0
- package/src/list/README.md +303 -0
- package/src/list/architecture.md +807 -0
- package/src/list/components/CalculatedGridView.tsx +252 -0
- package/src/list/components/DragPreview.tsx +102 -0
- package/src/list/components/ListContextMenu.tsx +274 -0
- package/src/list/components/ListItem.tsx +761 -0
- package/src/list/components/ListItems.tsx +919 -0
- package/src/list/components/MasonryView.tsx +241 -0
- package/src/list/components/SearchFilter.tsx +44 -0
- package/src/list/components/TreemapView.tsx +709 -0
- package/src/list/components/ViewSizeControls.tsx +205 -0
- package/src/list/components/ViewTypeSelector.tsx +312 -0
- package/src/list/components/VirtualizedDetailsView.tsx +231 -0
- package/src/list/components/VirtualizedGrid.tsx +164 -0
- package/src/list/components/VirtualizedList.tsx +154 -0
- package/src/list/components/VirtualizedMasonryView.tsx +344 -0
- package/src/list/components/shared/EmptyState.tsx +103 -0
- package/src/list/components/shared/ErrorBoundary.tsx +123 -0
- package/src/list/components/shared/ErrorDisplay.tsx +100 -0
- package/src/list/components/shared/ListLoader.tsx +146 -0
- package/src/list/components/shared/LoadingIndicator.tsx +80 -0
- package/src/list/index.ts +92 -0
- package/src/list/models/ListItemsModel.ts +1301 -0
- package/src/list/models/TreemapModel.ts +204 -0
- package/src/list/providers/ListItemsProvider.ts +313 -0
- package/src/list/providers/TestListProvider.ts +604 -0
- package/src/list/tasks.md +937 -0
- package/src/list/types/ListTypes.ts +178 -0
- package/src/list/utils/BenchmarkLogger.ts +243 -0
- package/src/list/utils/DragDropManager.ts +320 -0
- package/src/list/utils/GridLayoutCalculator.ts +290 -0
- package/src/list/utils/ListAccessibility.ts +367 -0
- package/src/list/utils/ListKeyboard.ts +414 -0
- package/src/list/utils/MasonryLayoutCalculator.ts +302 -0
- package/src/list/utils/MasonryLayoutEngine.ts +401 -0
- package/src/list/utils/__tests__/MasonryLayoutEngine.test.ts +157 -0
- package/src/list/utils/__tests__/VirtualizedMasonryView.test.tsx +251 -0
- package/src/media/AlbumSidebar.tsx +48 -0
- package/src/media/MediaBrowser.tsx +92 -0
- package/src/media/MediaBrowserModel.ts +138 -0
- package/src/media/MediaGrid.tsx +50 -0
- package/src/media/MediaList.tsx +49 -0
- package/src/media/MediaPreview.tsx +63 -0
- package/src/media/MediaTimeline.tsx +38 -0
- package/src/media/MockMediaProvider.ts +70 -0
- package/src/media/index.ts +18 -0
- package/src/media/types.ts +21 -0
- package/src/styles/variables.css +60 -0
- package/src/tree/DEVELOPMENT_SUMMARY.md +170 -0
- package/src/tree/__tests__/TreeModel.test.ts +16 -0
- package/src/tree/architecture.md +530 -0
- package/src/tree/components/Tree.tsx +283 -0
- package/src/tree/components/TreeCheckbox.tsx +147 -0
- package/src/tree/components/TreeContextMenu.tsx +139 -0
- package/src/tree/components/TreeNodeList.tsx +329 -0
- package/src/tree/components/TreeTable.tsx +382 -0
- package/src/tree/index.ts +58 -0
- package/src/tree/models/TreeModel.ts +839 -0
- package/src/tree/providers/SimpleTreeProvider.ts +463 -0
- package/src/tree/providers/TestTreeProvider.ts +946 -0
- package/src/tree/providers/TreeProvider.ts +308 -0
- package/src/tree/tasks.md +2046 -0
- package/src/tree/types/TreeTypes.ts +279 -0
- package/src/tree/utils/SelectionTheme.ts +150 -0
- package/src/tree/utils/logger.ts +203 -0
- package/src/types-common.ts +21 -0
|
@@ -0,0 +1,3351 @@
|
|
|
1
|
+
import { cn$1 as cn } from "./utils-B4fdKKsy.js";
|
|
2
|
+
import { resolveIcon$1 as resolveIcon } from "./iconMap-V4B8P-Uh.js";
|
|
3
|
+
import { useFileBrowserContext } from "./FileBrowserContext-B6jixa2j.js";
|
|
4
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { observer } from "mobx-react-lite";
|
|
6
|
+
import { Archive, ArrowDown, ArrowUp, ArrowUpDown, BookOpen, Braces, Check, ChevronDown, ChevronRight, Code, Component as Component$1, Database, Download, FileText, GitBranch, Globe, ImageIcon, Key, Loader2, Minus, MoreVertical, Music, Package, Palette, Play, Search, Settings, TestTube, Zap } from "lucide-react";
|
|
7
|
+
import { flow, makeAutoObservable, observable } from "mobx";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
|
|
10
|
+
//#region src/tree/utils/SelectionTheme.ts
|
|
11
|
+
/**
|
|
12
|
+
* Default selection theme configuration
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_SELECTION_THEME = {
|
|
15
|
+
colorScheme: "primary",
|
|
16
|
+
intensity: "subtle",
|
|
17
|
+
focusStyle: "ring",
|
|
18
|
+
animated: true,
|
|
19
|
+
persistSelection: true
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Generate selection background classes based on color scheme and intensity
|
|
23
|
+
*/
|
|
24
|
+
function getSelectionBackground(colorScheme, intensity) {
|
|
25
|
+
if (colorScheme === "custom") return "";
|
|
26
|
+
const intensityMap = {
|
|
27
|
+
subtle: {
|
|
28
|
+
primary: "bg-primary/10",
|
|
29
|
+
secondary: "bg-secondary/10",
|
|
30
|
+
accent: "bg-accent/20",
|
|
31
|
+
muted: "bg-muted"
|
|
32
|
+
},
|
|
33
|
+
prominent: {
|
|
34
|
+
primary: "bg-primary/20",
|
|
35
|
+
secondary: "bg-secondary/20",
|
|
36
|
+
accent: "bg-accent/40",
|
|
37
|
+
muted: "bg-muted/60"
|
|
38
|
+
},
|
|
39
|
+
"high-contrast": {
|
|
40
|
+
primary: "bg-primary text-primary-foreground",
|
|
41
|
+
secondary: "bg-secondary text-secondary-foreground",
|
|
42
|
+
accent: "bg-accent text-accent-foreground",
|
|
43
|
+
muted: "bg-muted-foreground text-muted"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return intensityMap[intensity]?.[colorScheme] || intensityMap.subtle.primary;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generate hover background classes
|
|
50
|
+
*/
|
|
51
|
+
function getHoverBackground(colorScheme) {
|
|
52
|
+
if (colorScheme === "custom") return "";
|
|
53
|
+
const hoverMap = {
|
|
54
|
+
primary: "hover:bg-primary/5",
|
|
55
|
+
secondary: "hover:bg-secondary/5",
|
|
56
|
+
accent: "hover:bg-accent/10",
|
|
57
|
+
muted: "hover:bg-muted/30"
|
|
58
|
+
};
|
|
59
|
+
return hoverMap[colorScheme] || hoverMap.primary;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate focus indicator classes based on focus style
|
|
63
|
+
*/
|
|
64
|
+
function getFocusIndicator(focusStyle, colorScheme) {
|
|
65
|
+
if (colorScheme === "custom") return focusStyle === "ring" ? "ring-2 ring-[var(--tree-focus-ring)] ring-offset-1" : "border-l-2 border-[var(--tree-focus-border)]";
|
|
66
|
+
const focusMap = {
|
|
67
|
+
ring: {
|
|
68
|
+
primary: "ring-2 ring-primary ring-offset-1",
|
|
69
|
+
secondary: "ring-2 ring-secondary ring-offset-1",
|
|
70
|
+
accent: "ring-2 ring-accent ring-offset-1",
|
|
71
|
+
muted: "ring-2 ring-muted-foreground ring-offset-1"
|
|
72
|
+
},
|
|
73
|
+
outline: {
|
|
74
|
+
primary: "outline-2 outline-primary",
|
|
75
|
+
secondary: "outline-2 outline-secondary",
|
|
76
|
+
accent: "outline-2 outline-accent",
|
|
77
|
+
muted: "outline-2 outline-muted-foreground"
|
|
78
|
+
},
|
|
79
|
+
border: {
|
|
80
|
+
primary: "border-l-2 border-primary",
|
|
81
|
+
secondary: "border-l-2 border-secondary",
|
|
82
|
+
accent: "border-l-2 border-accent",
|
|
83
|
+
muted: "border-l-2 border-muted-foreground"
|
|
84
|
+
},
|
|
85
|
+
underline: {
|
|
86
|
+
primary: "border-b-2 border-primary",
|
|
87
|
+
secondary: "border-b-2 border-secondary",
|
|
88
|
+
accent: "border-b-2 border-accent",
|
|
89
|
+
muted: "border-b-2 border-muted-foreground"
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
return focusMap[focusStyle]?.[colorScheme] || focusMap.ring.primary;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Generate complete selection classes for a tree node
|
|
96
|
+
*/
|
|
97
|
+
function getSelectionClasses(theme, isSelected, isFocused, useCheckboxes = false) {
|
|
98
|
+
const baseClasses = [
|
|
99
|
+
"transition-colors",
|
|
100
|
+
theme.animated ? "duration-150" : "",
|
|
101
|
+
getHoverBackground(theme.colorScheme)
|
|
102
|
+
];
|
|
103
|
+
if (isSelected) baseClasses.push(getSelectionBackground(theme.colorScheme, theme.intensity));
|
|
104
|
+
if (isFocused && !useCheckboxes) baseClasses.push(getFocusIndicator(theme.focusStyle, theme.colorScheme));
|
|
105
|
+
return cn(baseClasses);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Generate CSS custom properties for custom color schemes
|
|
109
|
+
* NOTE: This function is now simplified since we handle custom colors with inline styles
|
|
110
|
+
*/
|
|
111
|
+
function getCustomColorVariables(theme) {
|
|
112
|
+
return {};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/tree/providers/TestTreeProvider.ts
|
|
117
|
+
var TestTreeProvider = class {
|
|
118
|
+
constructor() {
|
|
119
|
+
this.id = "test-tree-provider";
|
|
120
|
+
this.name = "Test Tree Provider";
|
|
121
|
+
this.version = "1.0.0";
|
|
122
|
+
this.isMultiSelectEnabled = true;
|
|
123
|
+
this.isDragDropEnabled = false;
|
|
124
|
+
this.isVirtualizationEnabled = false;
|
|
125
|
+
this.isTableViewEnabled = true;
|
|
126
|
+
this.useCheckboxSelection = false;
|
|
127
|
+
this.allowPartialSelection = true;
|
|
128
|
+
this.config = {};
|
|
129
|
+
this.selectionTheme = {
|
|
130
|
+
...DEFAULT_SELECTION_THEME,
|
|
131
|
+
colorScheme: "primary",
|
|
132
|
+
intensity: "prominent",
|
|
133
|
+
focusStyle: "ring",
|
|
134
|
+
animated: true,
|
|
135
|
+
persistSelection: true
|
|
136
|
+
};
|
|
137
|
+
this.slowLoading = false;
|
|
138
|
+
this.simulateErrors = false;
|
|
139
|
+
this.maxDepth = 3;
|
|
140
|
+
this.nodesPerLevel = 5;
|
|
141
|
+
this.loadingDelay = 300;
|
|
142
|
+
this.updateSelectionTheme = (theme) => {
|
|
143
|
+
this.selectionTheme = {
|
|
144
|
+
...this.selectionTheme,
|
|
145
|
+
...theme
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
this.setMultiSelectEnabled = (enabled) => {
|
|
149
|
+
this.isMultiSelectEnabled = enabled;
|
|
150
|
+
};
|
|
151
|
+
this.setCheckboxSelection = (enabled) => {
|
|
152
|
+
this.useCheckboxSelection = enabled;
|
|
153
|
+
};
|
|
154
|
+
makeAutoObservable(this, {
|
|
155
|
+
id: false,
|
|
156
|
+
name: false,
|
|
157
|
+
version: false,
|
|
158
|
+
isDragDropEnabled: false,
|
|
159
|
+
isVirtualizationEnabled: false,
|
|
160
|
+
isTableViewEnabled: false,
|
|
161
|
+
config: false
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get selection theme configuration
|
|
166
|
+
*/
|
|
167
|
+
getSelectionTheme() {
|
|
168
|
+
return this.selectionTheme;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Load tree nodes
|
|
172
|
+
*/
|
|
173
|
+
async loadNodes(options) {
|
|
174
|
+
await this.simulateDelay();
|
|
175
|
+
if (this.simulateErrors && Math.random() < .3) throw new Error("Simulated loading error - try again!");
|
|
176
|
+
const nodes = this.generateMockNodes("", 0);
|
|
177
|
+
return {
|
|
178
|
+
nodes,
|
|
179
|
+
hasMore: false,
|
|
180
|
+
totalCount: nodes.length
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Load child nodes for a given parent
|
|
185
|
+
*/
|
|
186
|
+
async loadChildren(node, options) {
|
|
187
|
+
await this.simulateDelay();
|
|
188
|
+
if (this.simulateErrors && Math.random() < .3) throw new Error("Simulated loading error - try again!");
|
|
189
|
+
const pathParts = node.path.split("/").filter(Boolean);
|
|
190
|
+
const depth = pathParts.length;
|
|
191
|
+
if (depth >= this.maxDepth) return {
|
|
192
|
+
nodes: [],
|
|
193
|
+
hasMore: false,
|
|
194
|
+
totalCount: 0
|
|
195
|
+
};
|
|
196
|
+
const children = this.generateMockNodes(node.path, depth);
|
|
197
|
+
return {
|
|
198
|
+
nodes: children,
|
|
199
|
+
hasMore: false,
|
|
200
|
+
totalCount: children.length
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Refresh tree data
|
|
205
|
+
*/
|
|
206
|
+
async refresh(path) {
|
|
207
|
+
return path ? this.loadChildren({ path }) : this.loadNodes();
|
|
208
|
+
}
|
|
209
|
+
canExpand(node) {
|
|
210
|
+
return node.type === "directory" && (node.hasChildren ?? true);
|
|
211
|
+
}
|
|
212
|
+
canSelect(node) {
|
|
213
|
+
return !node.disabled;
|
|
214
|
+
}
|
|
215
|
+
getNodeContextMenu(node) {
|
|
216
|
+
const baseItems = [];
|
|
217
|
+
if (node.type === "directory") if (node.name.includes("components")) return [
|
|
218
|
+
{
|
|
219
|
+
id: "open",
|
|
220
|
+
label: "Open Folder"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "separator-1",
|
|
224
|
+
label: "",
|
|
225
|
+
type: "separator"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "new-component",
|
|
229
|
+
label: "New React Component"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: "new-hook",
|
|
233
|
+
label: "New Custom Hook"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "new-story",
|
|
237
|
+
label: "New Storybook Story"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "separator-2",
|
|
241
|
+
label: "",
|
|
242
|
+
type: "separator"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: "run-tests",
|
|
246
|
+
label: "Run Component Tests"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "build-docs",
|
|
250
|
+
label: "Generate Docs"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: "separator-3",
|
|
254
|
+
label: "",
|
|
255
|
+
type: "separator"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "rename",
|
|
259
|
+
label: "Rename Folder"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "delete",
|
|
263
|
+
label: "Delete Folder"
|
|
264
|
+
}
|
|
265
|
+
];
|
|
266
|
+
else if (node.name.includes("config")) return [
|
|
267
|
+
{
|
|
268
|
+
id: "open",
|
|
269
|
+
label: "Open Config Folder"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: "separator-1",
|
|
273
|
+
label: "",
|
|
274
|
+
type: "separator"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "new-env",
|
|
278
|
+
label: "New Environment File"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: "new-config",
|
|
282
|
+
label: "New Config File"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "validate-config",
|
|
286
|
+
label: "Validate Configuration"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "separator-2",
|
|
290
|
+
label: "",
|
|
291
|
+
type: "separator"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "backup-config",
|
|
295
|
+
label: "Backup Configuration"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
id: "restore-config",
|
|
299
|
+
label: "Restore from Backup"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "separator-3",
|
|
303
|
+
label: "",
|
|
304
|
+
type: "separator"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
id: "rename",
|
|
308
|
+
label: "Rename Folder"
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "delete",
|
|
312
|
+
label: "Delete Folder"
|
|
313
|
+
}
|
|
314
|
+
];
|
|
315
|
+
else return [
|
|
316
|
+
{
|
|
317
|
+
id: "open",
|
|
318
|
+
label: "Open Folder"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: "separator-1",
|
|
322
|
+
label: "",
|
|
323
|
+
type: "separator"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
id: "new-file",
|
|
327
|
+
label: "New File"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: "new-folder",
|
|
331
|
+
label: "New Folder"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: "separator-2",
|
|
335
|
+
label: "",
|
|
336
|
+
type: "separator"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: "import-files",
|
|
340
|
+
label: "Import Files"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: "export-folder",
|
|
344
|
+
label: "Export Folder"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: "separator-3",
|
|
348
|
+
label: "",
|
|
349
|
+
type: "separator"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: "rename",
|
|
353
|
+
label: "Rename Folder"
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: "delete",
|
|
357
|
+
label: "Delete Folder"
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: "separator-4",
|
|
361
|
+
label: "",
|
|
362
|
+
type: "separator"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: "copy",
|
|
366
|
+
label: "Copy Folder"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: "cut",
|
|
370
|
+
label: "Cut Folder"
|
|
371
|
+
}
|
|
372
|
+
];
|
|
373
|
+
const fileName = node.name;
|
|
374
|
+
const fileExt = fileName.split(".").pop()?.toLowerCase() || "";
|
|
375
|
+
switch (fileExt) {
|
|
376
|
+
case "js":
|
|
377
|
+
case "jsx": return [
|
|
378
|
+
{
|
|
379
|
+
id: "open",
|
|
380
|
+
label: "Open in Editor"
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: "open-preview",
|
|
384
|
+
label: "Open in Preview"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
id: "separator-1",
|
|
388
|
+
label: "",
|
|
389
|
+
type: "separator"
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
id: "run-file",
|
|
393
|
+
label: "Run JavaScript"
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: "debug-file",
|
|
397
|
+
label: "Debug in Browser"
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
id: "format-code",
|
|
401
|
+
label: "Format with Prettier"
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: "separator-2",
|
|
405
|
+
label: "",
|
|
406
|
+
type: "separator"
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
id: "create-test",
|
|
410
|
+
label: "Create Test File"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: "run-tests",
|
|
414
|
+
label: "Run Tests"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
id: "separator-3",
|
|
418
|
+
label: "",
|
|
419
|
+
type: "separator"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
id: "rename",
|
|
423
|
+
label: "Rename File"
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
id: "duplicate",
|
|
427
|
+
label: "Duplicate File"
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: "delete",
|
|
431
|
+
label: "Delete File"
|
|
432
|
+
}
|
|
433
|
+
];
|
|
434
|
+
case "ts":
|
|
435
|
+
case "tsx": return [
|
|
436
|
+
{
|
|
437
|
+
id: "open",
|
|
438
|
+
label: "Open in Editor"
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: "open-preview",
|
|
442
|
+
label: "Open in Preview"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
id: "separator-1",
|
|
446
|
+
label: "",
|
|
447
|
+
type: "separator"
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
id: "compile-ts",
|
|
451
|
+
label: "Compile TypeScript"
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: "type-check",
|
|
455
|
+
label: "Run Type Check"
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
id: "generate-types",
|
|
459
|
+
label: "Generate Type Definitions"
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
id: "separator-2",
|
|
463
|
+
label: "",
|
|
464
|
+
type: "separator"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: "create-test",
|
|
468
|
+
label: "Create Test File"
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
id: "run-tests",
|
|
472
|
+
label: "Run Tests"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: "separator-3",
|
|
476
|
+
label: "",
|
|
477
|
+
type: "separator"
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
id: "rename",
|
|
481
|
+
label: "Rename File"
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
id: "duplicate",
|
|
485
|
+
label: "Duplicate File"
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
id: "delete",
|
|
489
|
+
label: "Delete File"
|
|
490
|
+
}
|
|
491
|
+
];
|
|
492
|
+
case "css":
|
|
493
|
+
case "scss":
|
|
494
|
+
case "sass":
|
|
495
|
+
case "less": return [
|
|
496
|
+
{
|
|
497
|
+
id: "open",
|
|
498
|
+
label: "Open in Editor"
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
id: "open-preview",
|
|
502
|
+
label: "Live Preview"
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
id: "separator-1",
|
|
506
|
+
label: "",
|
|
507
|
+
type: "separator"
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: "compile-css",
|
|
511
|
+
label: "Compile Stylesheet"
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
id: "optimize-css",
|
|
515
|
+
label: "Optimize CSS"
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
id: "validate-css",
|
|
519
|
+
label: "Validate CSS"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
id: "separator-2",
|
|
523
|
+
label: "",
|
|
524
|
+
type: "separator"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
id: "extract-vars",
|
|
528
|
+
label: "Extract CSS Variables"
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
id: "generate-rtl",
|
|
532
|
+
label: "Generate RTL Version"
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
id: "separator-3",
|
|
536
|
+
label: "",
|
|
537
|
+
type: "separator"
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
id: "rename",
|
|
541
|
+
label: "Rename File"
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
id: "duplicate",
|
|
545
|
+
label: "Duplicate File"
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
id: "delete",
|
|
549
|
+
label: "Delete File"
|
|
550
|
+
}
|
|
551
|
+
];
|
|
552
|
+
case "md":
|
|
553
|
+
case "mdx": return [
|
|
554
|
+
{
|
|
555
|
+
id: "open",
|
|
556
|
+
label: "Open in Editor"
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
id: "open-preview",
|
|
560
|
+
label: "Preview Markdown"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
id: "separator-1",
|
|
564
|
+
label: "",
|
|
565
|
+
type: "separator"
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
id: "export-html",
|
|
569
|
+
label: "Export to HTML"
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
id: "export-pdf",
|
|
573
|
+
label: "Export to PDF"
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
id: "check-links",
|
|
577
|
+
label: "Check Links"
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
id: "separator-2",
|
|
581
|
+
label: "",
|
|
582
|
+
type: "separator"
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
id: "toc-generate",
|
|
586
|
+
label: "Generate Table of Contents"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
id: "word-count",
|
|
590
|
+
label: "Word Count & Stats"
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
id: "separator-3",
|
|
594
|
+
label: "",
|
|
595
|
+
type: "separator"
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
id: "rename",
|
|
599
|
+
label: "Rename File"
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
id: "duplicate",
|
|
603
|
+
label: "Duplicate File"
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
id: "delete",
|
|
607
|
+
label: "Delete File"
|
|
608
|
+
}
|
|
609
|
+
];
|
|
610
|
+
case "json": return [
|
|
611
|
+
{
|
|
612
|
+
id: "open",
|
|
613
|
+
label: "Open in Editor"
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: "open-viewer",
|
|
617
|
+
label: "Open JSON Viewer"
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
id: "separator-1",
|
|
621
|
+
label: "",
|
|
622
|
+
type: "separator"
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
id: "validate-json",
|
|
626
|
+
label: "Validate JSON"
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
id: "format-json",
|
|
630
|
+
label: "Format JSON"
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
id: "minify-json",
|
|
634
|
+
label: "Minify JSON"
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
id: "separator-2",
|
|
638
|
+
label: "",
|
|
639
|
+
type: "separator"
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
id: "compare-json",
|
|
643
|
+
label: "Compare with..."
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
id: "schema-validate",
|
|
647
|
+
label: "Validate Against Schema"
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
id: "separator-3",
|
|
651
|
+
label: "",
|
|
652
|
+
type: "separator"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
id: "rename",
|
|
656
|
+
label: "Rename File"
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
id: "duplicate",
|
|
660
|
+
label: "Duplicate File"
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
id: "delete",
|
|
664
|
+
label: "Delete File"
|
|
665
|
+
}
|
|
666
|
+
];
|
|
667
|
+
case "jpg":
|
|
668
|
+
case "jpeg":
|
|
669
|
+
case "png":
|
|
670
|
+
case "gif":
|
|
671
|
+
case "svg":
|
|
672
|
+
case "webp": return [
|
|
673
|
+
{
|
|
674
|
+
id: "open",
|
|
675
|
+
label: "Open Image"
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
id: "open-editor",
|
|
679
|
+
label: "Edit in Image Editor"
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
id: "separator-1",
|
|
683
|
+
label: "",
|
|
684
|
+
type: "separator"
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
id: "resize-image",
|
|
688
|
+
label: "Resize Image"
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
id: "compress-image",
|
|
692
|
+
label: "Compress Image"
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
id: "convert-format",
|
|
696
|
+
label: "Convert Format"
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
id: "separator-2",
|
|
700
|
+
label: "",
|
|
701
|
+
type: "separator"
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
id: "image-info",
|
|
705
|
+
label: "Image Properties"
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
id: "copy-url",
|
|
709
|
+
label: "Copy Image URL"
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
id: "separator-3",
|
|
713
|
+
label: "",
|
|
714
|
+
type: "separator"
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
id: "rename",
|
|
718
|
+
label: "Rename File"
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
id: "duplicate",
|
|
722
|
+
label: "Duplicate File"
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
id: "delete",
|
|
726
|
+
label: "Delete File"
|
|
727
|
+
}
|
|
728
|
+
];
|
|
729
|
+
default: return [
|
|
730
|
+
{
|
|
731
|
+
id: "open",
|
|
732
|
+
label: "Open File"
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
id: "open-with",
|
|
736
|
+
label: "Open With..."
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
id: "separator-1",
|
|
740
|
+
label: "",
|
|
741
|
+
type: "separator"
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
id: "properties",
|
|
745
|
+
label: "File Properties"
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
id: "permissions",
|
|
749
|
+
label: "Change Permissions"
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
id: "separator-2",
|
|
753
|
+
label: "",
|
|
754
|
+
type: "separator"
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
id: "rename",
|
|
758
|
+
label: "Rename File"
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
id: "duplicate",
|
|
762
|
+
label: "Duplicate File"
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
id: "delete",
|
|
766
|
+
label: "Delete File"
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
id: "separator-3",
|
|
770
|
+
label: "",
|
|
771
|
+
type: "separator"
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
id: "copy",
|
|
775
|
+
label: "Copy File"
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
id: "cut",
|
|
779
|
+
label: "Cut File"
|
|
780
|
+
}
|
|
781
|
+
];
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
getMultiNodeContextMenu(nodes) {
|
|
785
|
+
const hasFiles = nodes.some((n) => n.type === "file");
|
|
786
|
+
const hasFolders = nodes.some((n) => n.type === "directory");
|
|
787
|
+
const allSameType = hasFiles && !hasFolders || hasFolders && !hasFiles;
|
|
788
|
+
if (allSameType && hasFiles) return [
|
|
789
|
+
{
|
|
790
|
+
id: "open-all",
|
|
791
|
+
label: `Open ${nodes.length} Files`
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
id: "separator-1",
|
|
795
|
+
label: "",
|
|
796
|
+
type: "separator"
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
id: "compress-files",
|
|
800
|
+
label: `Create Archive (${nodes.length} files)`
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
id: "copy-paths",
|
|
804
|
+
label: "Copy File Paths"
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
id: "separator-2",
|
|
808
|
+
label: "",
|
|
809
|
+
type: "separator"
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
id: "copy-all",
|
|
813
|
+
label: `Copy ${nodes.length} Files`
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
id: "cut-all",
|
|
817
|
+
label: `Cut ${nodes.length} Files`
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
id: "delete-all",
|
|
821
|
+
label: `Delete ${nodes.length} Files`
|
|
822
|
+
}
|
|
823
|
+
];
|
|
824
|
+
else if (allSameType && hasFolders) return [
|
|
825
|
+
{
|
|
826
|
+
id: "open-all",
|
|
827
|
+
label: `Open ${nodes.length} Folders`
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
id: "separator-1",
|
|
831
|
+
label: "",
|
|
832
|
+
type: "separator"
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
id: "merge-folders",
|
|
836
|
+
label: "Merge Folders"
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
id: "compare-folders",
|
|
840
|
+
label: "Compare Folders"
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
id: "separator-2",
|
|
844
|
+
label: "",
|
|
845
|
+
type: "separator"
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
id: "copy-all",
|
|
849
|
+
label: `Copy ${nodes.length} Folders`
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
id: "cut-all",
|
|
853
|
+
label: `Cut ${nodes.length} Folders`
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
id: "delete-all",
|
|
857
|
+
label: `Delete ${nodes.length} Folders`
|
|
858
|
+
}
|
|
859
|
+
];
|
|
860
|
+
else return [
|
|
861
|
+
{
|
|
862
|
+
id: "open-all",
|
|
863
|
+
label: `Open ${nodes.length} Items`
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
id: "separator-1",
|
|
867
|
+
label: "",
|
|
868
|
+
type: "separator"
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
id: "compress-all",
|
|
872
|
+
label: `Create Archive (${nodes.length} items)`
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
id: "copy-info",
|
|
876
|
+
label: "Copy Selection Info"
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
id: "separator-2",
|
|
880
|
+
label: "",
|
|
881
|
+
type: "separator"
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
id: "copy-all",
|
|
885
|
+
label: `Copy ${nodes.length} Items`
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
id: "cut-all",
|
|
889
|
+
label: `Cut ${nodes.length} Items`
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
id: "delete-all",
|
|
893
|
+
label: `Delete ${nodes.length} Items`
|
|
894
|
+
}
|
|
895
|
+
];
|
|
896
|
+
}
|
|
897
|
+
onContextMenuAction(menuItemId, nodes) {
|
|
898
|
+
const nodeNames = nodes.map((n) => n.name).join(", ");
|
|
899
|
+
console.log(`🎯 Context menu action: ${menuItemId} on nodes: ${nodeNames}`);
|
|
900
|
+
switch (menuItemId) {
|
|
901
|
+
case "open":
|
|
902
|
+
case "open-all":
|
|
903
|
+
console.log(`📂 Opening: ${nodeNames}`);
|
|
904
|
+
alert(`Opening: ${nodeNames}`);
|
|
905
|
+
break;
|
|
906
|
+
case "open-preview":
|
|
907
|
+
console.log(`👁️ Opening preview for: ${nodeNames}`);
|
|
908
|
+
alert(`Preview mode for: ${nodeNames}`);
|
|
909
|
+
break;
|
|
910
|
+
case "open-editor":
|
|
911
|
+
console.log(`✏️ Opening in editor: ${nodeNames}`);
|
|
912
|
+
alert(`Editor opened for: ${nodeNames}`);
|
|
913
|
+
break;
|
|
914
|
+
case "run-file":
|
|
915
|
+
case "run-tests":
|
|
916
|
+
console.log(`▶️ Running: ${nodeNames}`);
|
|
917
|
+
alert(`Executing: ${nodeNames}`);
|
|
918
|
+
break;
|
|
919
|
+
case "debug-file":
|
|
920
|
+
console.log(`🐛 Debugging: ${nodeNames}`);
|
|
921
|
+
alert(`Debug session started for: ${nodeNames}`);
|
|
922
|
+
break;
|
|
923
|
+
case "compile-ts":
|
|
924
|
+
case "compile-css":
|
|
925
|
+
console.log(`🔨 Compiling: ${nodeNames}`);
|
|
926
|
+
alert(`Compilation started for: ${nodeNames}`);
|
|
927
|
+
break;
|
|
928
|
+
case "format-code":
|
|
929
|
+
case "format-json":
|
|
930
|
+
console.log(`🎨 Formatting: ${nodeNames}`);
|
|
931
|
+
alert(`Code formatted: ${nodeNames}`);
|
|
932
|
+
break;
|
|
933
|
+
case "type-check":
|
|
934
|
+
console.log(`🔍 Type checking: ${nodeNames}`);
|
|
935
|
+
alert(`Type check completed for: ${nodeNames}`);
|
|
936
|
+
break;
|
|
937
|
+
case "new-file":
|
|
938
|
+
case "new-folder":
|
|
939
|
+
case "new-component":
|
|
940
|
+
case "new-hook":
|
|
941
|
+
case "new-story":
|
|
942
|
+
console.log(`➕ Creating new ${menuItemId.replace("new-", "")} in: ${nodeNames}`);
|
|
943
|
+
alert(`Creating new ${menuItemId.replace("new-", "")} in: ${nodeNames}`);
|
|
944
|
+
break;
|
|
945
|
+
case "export-html":
|
|
946
|
+
case "export-pdf":
|
|
947
|
+
console.log(`📤 Exporting ${menuItemId.replace("export-", "")}: ${nodeNames}`);
|
|
948
|
+
alert(`Exporting ${menuItemId.replace("export-", "").toUpperCase()}: ${nodeNames}`);
|
|
949
|
+
break;
|
|
950
|
+
case "import-files":
|
|
951
|
+
console.log(`📥 Importing files to: ${nodeNames}`);
|
|
952
|
+
alert(`Import dialog opened for: ${nodeNames}`);
|
|
953
|
+
break;
|
|
954
|
+
case "rename":
|
|
955
|
+
console.log(`✏️ Renaming: ${nodeNames}`);
|
|
956
|
+
alert(`Rename dialog for: ${nodeNames}`);
|
|
957
|
+
break;
|
|
958
|
+
case "duplicate":
|
|
959
|
+
console.log(`📋 Duplicating: ${nodeNames}`);
|
|
960
|
+
alert(`Duplicated: ${nodeNames}`);
|
|
961
|
+
break;
|
|
962
|
+
case "delete":
|
|
963
|
+
case "delete-all":
|
|
964
|
+
console.log(`🗑️ Deleting: ${nodeNames}`);
|
|
965
|
+
alert(`Deleted: ${nodeNames}`);
|
|
966
|
+
break;
|
|
967
|
+
case "copy":
|
|
968
|
+
case "copy-all":
|
|
969
|
+
console.log(`📋 Copying: ${nodeNames}`);
|
|
970
|
+
alert(`Copied to clipboard: ${nodeNames}`);
|
|
971
|
+
break;
|
|
972
|
+
case "cut":
|
|
973
|
+
case "cut-all":
|
|
974
|
+
console.log(`✂️ Cutting: ${nodeNames}`);
|
|
975
|
+
alert(`Cut to clipboard: ${nodeNames}`);
|
|
976
|
+
break;
|
|
977
|
+
case "validate-json":
|
|
978
|
+
case "validate-css":
|
|
979
|
+
case "validate-config":
|
|
980
|
+
console.log(`✅ Validating: ${nodeNames}`);
|
|
981
|
+
alert(`Validation passed for: ${nodeNames}`);
|
|
982
|
+
break;
|
|
983
|
+
case "compress-image":
|
|
984
|
+
case "compress-files":
|
|
985
|
+
case "compress-all":
|
|
986
|
+
console.log(`🗜️ Compressing: ${nodeNames}`);
|
|
987
|
+
alert(`Compression completed for: ${nodeNames}`);
|
|
988
|
+
break;
|
|
989
|
+
case "image-info":
|
|
990
|
+
case "properties":
|
|
991
|
+
console.log(`ℹ️ Showing properties for: ${nodeNames}`);
|
|
992
|
+
alert(`Properties: ${nodeNames}\nSize: 1.2MB\nType: ${nodes[0]?.type || "unknown"}\nModified: ${nodes[0]?.lastModified?.toLocaleDateString() || "unknown"}`);
|
|
993
|
+
break;
|
|
994
|
+
default:
|
|
995
|
+
console.log(`❓ Unknown action: ${menuItemId}`);
|
|
996
|
+
alert(`Action executed: ${menuItemId} on ${nodeNames}`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
getTableColumns() {
|
|
1000
|
+
return [
|
|
1001
|
+
{
|
|
1002
|
+
id: "name",
|
|
1003
|
+
label: "Name",
|
|
1004
|
+
dataKey: "name",
|
|
1005
|
+
width: "300px",
|
|
1006
|
+
sortable: true,
|
|
1007
|
+
filterable: true,
|
|
1008
|
+
isTreeColumn: true,
|
|
1009
|
+
align: "left"
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
id: "type",
|
|
1013
|
+
label: "Type",
|
|
1014
|
+
dataKey: "type",
|
|
1015
|
+
width: "100px",
|
|
1016
|
+
sortable: true,
|
|
1017
|
+
filterable: true,
|
|
1018
|
+
align: "left",
|
|
1019
|
+
formatValue: (value) => value === "directory" ? "Folder" : "File"
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
id: "size",
|
|
1023
|
+
label: "Size",
|
|
1024
|
+
dataKey: "size",
|
|
1025
|
+
width: "100px",
|
|
1026
|
+
sortable: true,
|
|
1027
|
+
align: "right",
|
|
1028
|
+
formatValue: (value, node) => {
|
|
1029
|
+
if (node.type === "directory") return "-";
|
|
1030
|
+
if (!value) return "-";
|
|
1031
|
+
if (value < 1024) return `${value} B`;
|
|
1032
|
+
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
|
|
1033
|
+
if (value < 1024 * 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
1034
|
+
return `${(value / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
id: "lastModified",
|
|
1039
|
+
label: "Modified",
|
|
1040
|
+
dataKey: "lastModified",
|
|
1041
|
+
width: "150px",
|
|
1042
|
+
sortable: true,
|
|
1043
|
+
align: "left",
|
|
1044
|
+
formatValue: (value) => {
|
|
1045
|
+
if (!value) return "-";
|
|
1046
|
+
return value.toLocaleDateString();
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
id: "path",
|
|
1051
|
+
label: "Path",
|
|
1052
|
+
dataKey: "path",
|
|
1053
|
+
width: "200px",
|
|
1054
|
+
sortable: true,
|
|
1055
|
+
filterable: true,
|
|
1056
|
+
align: "left"
|
|
1057
|
+
}
|
|
1058
|
+
];
|
|
1059
|
+
}
|
|
1060
|
+
getDefaultTableSort() {
|
|
1061
|
+
return {
|
|
1062
|
+
columnId: "name",
|
|
1063
|
+
direction: "asc"
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
onTableSort(sort) {
|
|
1067
|
+
console.log("Table sort changed:", sort);
|
|
1068
|
+
}
|
|
1069
|
+
async onTableExport(options) {
|
|
1070
|
+
console.log("Table export requested:", options);
|
|
1071
|
+
alert(`Export to ${options.format.toUpperCase()} format requested`);
|
|
1072
|
+
}
|
|
1073
|
+
async simulateDelay() {
|
|
1074
|
+
const delay = this.slowLoading ? 1e3 : this.loadingDelay;
|
|
1075
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1076
|
+
}
|
|
1077
|
+
generateMockNodes(parentPath, depth) {
|
|
1078
|
+
if (depth >= this.maxDepth) return [];
|
|
1079
|
+
const nodes = [];
|
|
1080
|
+
const fileTypes = [
|
|
1081
|
+
"js",
|
|
1082
|
+
"jsx",
|
|
1083
|
+
"ts",
|
|
1084
|
+
"tsx",
|
|
1085
|
+
"html",
|
|
1086
|
+
"css",
|
|
1087
|
+
"scss",
|
|
1088
|
+
"sass",
|
|
1089
|
+
"less",
|
|
1090
|
+
"md",
|
|
1091
|
+
"mdx",
|
|
1092
|
+
"txt",
|
|
1093
|
+
"pdf",
|
|
1094
|
+
"json",
|
|
1095
|
+
"yml",
|
|
1096
|
+
"yaml",
|
|
1097
|
+
"xml",
|
|
1098
|
+
"env",
|
|
1099
|
+
"config",
|
|
1100
|
+
"jpg",
|
|
1101
|
+
"jpeg",
|
|
1102
|
+
"png",
|
|
1103
|
+
"gif",
|
|
1104
|
+
"svg",
|
|
1105
|
+
"webp",
|
|
1106
|
+
"ico",
|
|
1107
|
+
"mp4",
|
|
1108
|
+
"mp3",
|
|
1109
|
+
"wav",
|
|
1110
|
+
"avi",
|
|
1111
|
+
"zip",
|
|
1112
|
+
"tar",
|
|
1113
|
+
"gz",
|
|
1114
|
+
"exe",
|
|
1115
|
+
"app",
|
|
1116
|
+
"dmg",
|
|
1117
|
+
"py",
|
|
1118
|
+
"php",
|
|
1119
|
+
"java",
|
|
1120
|
+
"cpp",
|
|
1121
|
+
"c",
|
|
1122
|
+
"go",
|
|
1123
|
+
"rs",
|
|
1124
|
+
"rb"
|
|
1125
|
+
];
|
|
1126
|
+
const folderTypes = [
|
|
1127
|
+
{
|
|
1128
|
+
name: "components",
|
|
1129
|
+
type: "react"
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
name: "hooks",
|
|
1133
|
+
type: "react"
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
name: "pages",
|
|
1137
|
+
type: "react"
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
name: "utils",
|
|
1141
|
+
type: "code"
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
name: "lib",
|
|
1145
|
+
type: "code"
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
name: "src",
|
|
1149
|
+
type: "source"
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
name: "config",
|
|
1153
|
+
type: "config"
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
name: "build",
|
|
1157
|
+
type: "build"
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
name: "dist",
|
|
1161
|
+
type: "build"
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
name: ".github",
|
|
1165
|
+
type: "git"
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
name: ".vscode",
|
|
1169
|
+
type: "vscode"
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
name: "assets",
|
|
1173
|
+
type: "assets"
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
name: "images",
|
|
1177
|
+
type: "images"
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
name: "media",
|
|
1181
|
+
type: "media"
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
name: "public",
|
|
1185
|
+
type: "public"
|
|
1186
|
+
},
|
|
1187
|
+
{
|
|
1188
|
+
name: "docs",
|
|
1189
|
+
type: "docs"
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
name: "tests",
|
|
1193
|
+
type: "test"
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
name: "__tests__",
|
|
1197
|
+
type: "test"
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
name: "node_modules",
|
|
1201
|
+
type: "node_modules"
|
|
1202
|
+
}
|
|
1203
|
+
];
|
|
1204
|
+
for (let i = 0; i < this.nodesPerLevel; i++) {
|
|
1205
|
+
const nodeId = `${parentPath || "root"}-${depth}-${i}`.replace(/[^a-zA-Z0-9-_]/g, "-");
|
|
1206
|
+
const shouldBeFolder = depth < this.maxDepth - 1 && (i < this.nodesPerLevel / 2 || Math.random() < .4);
|
|
1207
|
+
if (shouldBeFolder) {
|
|
1208
|
+
const folderType = folderTypes[i % folderTypes.length] || {
|
|
1209
|
+
name: `folder-${i}`,
|
|
1210
|
+
type: "default"
|
|
1211
|
+
};
|
|
1212
|
+
const folderPath = parentPath ? `${parentPath}/${folderType.name}` : `/${folderType.name}`;
|
|
1213
|
+
nodes.push({
|
|
1214
|
+
id: nodeId,
|
|
1215
|
+
name: folderType.name,
|
|
1216
|
+
path: folderPath,
|
|
1217
|
+
type: "directory",
|
|
1218
|
+
hasChildren: depth < this.maxDepth - 1,
|
|
1219
|
+
lastModified: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1e3),
|
|
1220
|
+
metadata: {
|
|
1221
|
+
folderType: folderType.type,
|
|
1222
|
+
description: this.getFolderDescription(folderType.type)
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
} else {
|
|
1226
|
+
const fileType = fileTypes[i % fileTypes.length] || "txt";
|
|
1227
|
+
const fileName = this.generateFileName(fileType, i);
|
|
1228
|
+
const filePath = parentPath ? `${parentPath}/${fileName}` : `/${fileName}`;
|
|
1229
|
+
nodes.push({
|
|
1230
|
+
id: nodeId,
|
|
1231
|
+
name: fileName,
|
|
1232
|
+
path: filePath,
|
|
1233
|
+
type: "file",
|
|
1234
|
+
hasChildren: false,
|
|
1235
|
+
size: Math.floor(Math.random() * 1e6) + 1024,
|
|
1236
|
+
lastModified: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1e3),
|
|
1237
|
+
metadata: {
|
|
1238
|
+
fileType,
|
|
1239
|
+
category: this.getFileCategory(fileType),
|
|
1240
|
+
description: this.getFileDescription(fileType)
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return nodes;
|
|
1246
|
+
}
|
|
1247
|
+
generateFileName(fileType, index) {
|
|
1248
|
+
const fileNames = {
|
|
1249
|
+
"js": [
|
|
1250
|
+
"utils",
|
|
1251
|
+
"helpers",
|
|
1252
|
+
"config",
|
|
1253
|
+
"index",
|
|
1254
|
+
"main"
|
|
1255
|
+
],
|
|
1256
|
+
"jsx": [
|
|
1257
|
+
"App",
|
|
1258
|
+
"Component",
|
|
1259
|
+
"Button",
|
|
1260
|
+
"Modal",
|
|
1261
|
+
"Form"
|
|
1262
|
+
],
|
|
1263
|
+
"ts": [
|
|
1264
|
+
"types",
|
|
1265
|
+
"interfaces",
|
|
1266
|
+
"utils",
|
|
1267
|
+
"service",
|
|
1268
|
+
"api"
|
|
1269
|
+
],
|
|
1270
|
+
"tsx": [
|
|
1271
|
+
"HomePage",
|
|
1272
|
+
"UserCard",
|
|
1273
|
+
"Navigation",
|
|
1274
|
+
"Layout",
|
|
1275
|
+
"Dashboard"
|
|
1276
|
+
],
|
|
1277
|
+
"css": [
|
|
1278
|
+
"styles",
|
|
1279
|
+
"main",
|
|
1280
|
+
"theme",
|
|
1281
|
+
"reset",
|
|
1282
|
+
"variables"
|
|
1283
|
+
],
|
|
1284
|
+
"scss": [
|
|
1285
|
+
"app",
|
|
1286
|
+
"components",
|
|
1287
|
+
"layout",
|
|
1288
|
+
"mixins",
|
|
1289
|
+
"variables"
|
|
1290
|
+
],
|
|
1291
|
+
"md": [
|
|
1292
|
+
"README",
|
|
1293
|
+
"CHANGELOG",
|
|
1294
|
+
"CONTRIBUTING",
|
|
1295
|
+
"DOCS",
|
|
1296
|
+
"API"
|
|
1297
|
+
],
|
|
1298
|
+
"json": [
|
|
1299
|
+
"package",
|
|
1300
|
+
"tsconfig",
|
|
1301
|
+
"config",
|
|
1302
|
+
"settings",
|
|
1303
|
+
"data"
|
|
1304
|
+
],
|
|
1305
|
+
"yml": [
|
|
1306
|
+
"docker-compose",
|
|
1307
|
+
"config",
|
|
1308
|
+
"ci",
|
|
1309
|
+
"deployment",
|
|
1310
|
+
"settings"
|
|
1311
|
+
],
|
|
1312
|
+
"py": [
|
|
1313
|
+
"main",
|
|
1314
|
+
"utils",
|
|
1315
|
+
"models",
|
|
1316
|
+
"views",
|
|
1317
|
+
"tests"
|
|
1318
|
+
],
|
|
1319
|
+
"jpg": [
|
|
1320
|
+
"hero-image",
|
|
1321
|
+
"profile",
|
|
1322
|
+
"banner",
|
|
1323
|
+
"thumbnail",
|
|
1324
|
+
"cover"
|
|
1325
|
+
],
|
|
1326
|
+
"png": [
|
|
1327
|
+
"logo",
|
|
1328
|
+
"icon",
|
|
1329
|
+
"screenshot",
|
|
1330
|
+
"diagram",
|
|
1331
|
+
"chart"
|
|
1332
|
+
]
|
|
1333
|
+
};
|
|
1334
|
+
const names = fileNames[fileType] || ["file"];
|
|
1335
|
+
const baseName = names[index % names.length];
|
|
1336
|
+
return `${baseName}.${fileType}`;
|
|
1337
|
+
}
|
|
1338
|
+
getFolderDescription(folderType) {
|
|
1339
|
+
const descriptions = {
|
|
1340
|
+
"react": "React components and hooks",
|
|
1341
|
+
"source": "Source code files",
|
|
1342
|
+
"code": "Utility functions and libraries",
|
|
1343
|
+
"config": "Configuration files",
|
|
1344
|
+
"build": "Build output and artifacts",
|
|
1345
|
+
"git": "Git workflow configurations",
|
|
1346
|
+
"vscode": "VS Code editor settings",
|
|
1347
|
+
"assets": "Project assets and resources",
|
|
1348
|
+
"images": "Image files and graphics",
|
|
1349
|
+
"media": "Media files (video, audio)",
|
|
1350
|
+
"public": "Public static files",
|
|
1351
|
+
"docs": "Documentation files",
|
|
1352
|
+
"test": "Test files and specs",
|
|
1353
|
+
"node_modules": "Node.js dependencies",
|
|
1354
|
+
"default": "General folder"
|
|
1355
|
+
};
|
|
1356
|
+
return descriptions[folderType] || "General folder";
|
|
1357
|
+
}
|
|
1358
|
+
getFileCategory(fileType) {
|
|
1359
|
+
const categories = {
|
|
1360
|
+
"js": "JavaScript",
|
|
1361
|
+
"jsx": "React",
|
|
1362
|
+
"ts": "TypeScript",
|
|
1363
|
+
"tsx": "React TypeScript",
|
|
1364
|
+
"css": "Stylesheet",
|
|
1365
|
+
"scss": "Sass",
|
|
1366
|
+
"sass": "Sass",
|
|
1367
|
+
"less": "Less",
|
|
1368
|
+
"html": "Markup",
|
|
1369
|
+
"xml": "Markup",
|
|
1370
|
+
"svg": "Vector Graphics",
|
|
1371
|
+
"md": "Markdown",
|
|
1372
|
+
"txt": "Text",
|
|
1373
|
+
"pdf": "Document",
|
|
1374
|
+
"json": "Data",
|
|
1375
|
+
"yml": "Config",
|
|
1376
|
+
"yaml": "Config",
|
|
1377
|
+
"env": "Environment",
|
|
1378
|
+
"jpg": "Image",
|
|
1379
|
+
"png": "Image",
|
|
1380
|
+
"gif": "Image",
|
|
1381
|
+
"mp3": "Audio",
|
|
1382
|
+
"mp4": "Video",
|
|
1383
|
+
"py": "Python",
|
|
1384
|
+
"php": "PHP",
|
|
1385
|
+
"java": "Java",
|
|
1386
|
+
"cpp": "C++",
|
|
1387
|
+
"go": "Go",
|
|
1388
|
+
"zip": "Archive",
|
|
1389
|
+
"tar": "Archive",
|
|
1390
|
+
"default": "File"
|
|
1391
|
+
};
|
|
1392
|
+
return categories[fileType] || categories["default"] || "File";
|
|
1393
|
+
}
|
|
1394
|
+
getFileDescription(fileType) {
|
|
1395
|
+
const descriptions = {
|
|
1396
|
+
"js": "JavaScript source file",
|
|
1397
|
+
"jsx": "React component file",
|
|
1398
|
+
"ts": "TypeScript source file",
|
|
1399
|
+
"tsx": "React TypeScript component",
|
|
1400
|
+
"css": "Cascading Style Sheet",
|
|
1401
|
+
"scss": "Sass stylesheet",
|
|
1402
|
+
"html": "HTML markup file",
|
|
1403
|
+
"md": "Markdown documentation",
|
|
1404
|
+
"json": "JSON data file",
|
|
1405
|
+
"py": "Python script",
|
|
1406
|
+
"jpg": "JPEG image file",
|
|
1407
|
+
"png": "PNG image file",
|
|
1408
|
+
"mp3": "MP3 audio file",
|
|
1409
|
+
"zip": "Compressed archive"
|
|
1410
|
+
};
|
|
1411
|
+
return descriptions[fileType] || `${fileType.toUpperCase()} file`;
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Get custom icon for a node based on file type and category
|
|
1415
|
+
*/
|
|
1416
|
+
getNodeIcon(node) {
|
|
1417
|
+
if (node.type === "directory") {
|
|
1418
|
+
if (node.name.includes("components")) return Component$1;
|
|
1419
|
+
if (node.name.includes("assets") || node.name.includes("images")) return ImageIcon;
|
|
1420
|
+
if (node.name.includes("config") || node.name.includes("settings")) return Settings;
|
|
1421
|
+
if (node.name.includes("docs") || node.name.includes("documentation")) return BookOpen;
|
|
1422
|
+
if (node.name.includes("test") || node.name.includes("__tests__")) return TestTube;
|
|
1423
|
+
if (node.name.includes("lib") || node.name.includes("library")) return Package;
|
|
1424
|
+
return "folder";
|
|
1425
|
+
}
|
|
1426
|
+
const extension = node.name.split(".").pop() || "";
|
|
1427
|
+
switch (extension.toLowerCase()) {
|
|
1428
|
+
case "js":
|
|
1429
|
+
case "jsx": return FileText;
|
|
1430
|
+
case "ts":
|
|
1431
|
+
case "tsx": return Code;
|
|
1432
|
+
case "html":
|
|
1433
|
+
case "htm": return Globe;
|
|
1434
|
+
case "css":
|
|
1435
|
+
case "scss":
|
|
1436
|
+
case "sass":
|
|
1437
|
+
case "less": return Palette;
|
|
1438
|
+
case "md":
|
|
1439
|
+
case "mdx": return FileText;
|
|
1440
|
+
case "txt": return FileText;
|
|
1441
|
+
case "pdf": return FileText;
|
|
1442
|
+
case "json": return Braces;
|
|
1443
|
+
case "yml":
|
|
1444
|
+
case "yaml": return Settings;
|
|
1445
|
+
case "xml": return Code;
|
|
1446
|
+
case "env": return Key;
|
|
1447
|
+
case "config": return Settings;
|
|
1448
|
+
case "jpg":
|
|
1449
|
+
case "jpeg":
|
|
1450
|
+
case "png":
|
|
1451
|
+
case "gif":
|
|
1452
|
+
case "webp":
|
|
1453
|
+
case "bmp":
|
|
1454
|
+
case "tiff":
|
|
1455
|
+
case "ico": return ImageIcon;
|
|
1456
|
+
case "svg": return Zap;
|
|
1457
|
+
case "mp4":
|
|
1458
|
+
case "avi":
|
|
1459
|
+
case "mov":
|
|
1460
|
+
case "mkv": return Play;
|
|
1461
|
+
case "mp3":
|
|
1462
|
+
case "wav":
|
|
1463
|
+
case "flac":
|
|
1464
|
+
case "ogg": return Music;
|
|
1465
|
+
case "zip":
|
|
1466
|
+
case "tar":
|
|
1467
|
+
case "gz":
|
|
1468
|
+
case "7z":
|
|
1469
|
+
case "rar": return Archive;
|
|
1470
|
+
case "exe":
|
|
1471
|
+
case "app":
|
|
1472
|
+
case "deb":
|
|
1473
|
+
case "dmg": return Zap;
|
|
1474
|
+
case "git":
|
|
1475
|
+
case "gitignore": return GitBranch;
|
|
1476
|
+
case "dockerfile": return Package;
|
|
1477
|
+
case "sql": return Database;
|
|
1478
|
+
case "log": return FileText;
|
|
1479
|
+
default: return "file";
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
//#endregion
|
|
1485
|
+
//#region src/tree/providers/SimpleTreeProvider.ts
|
|
1486
|
+
var SimpleTreeProvider = class {
|
|
1487
|
+
constructor(options = {}) {
|
|
1488
|
+
this.id = "simple-tree-provider";
|
|
1489
|
+
this.name = "Simple Tree Provider";
|
|
1490
|
+
this.version = "1.0.0";
|
|
1491
|
+
this.isMultiSelectEnabled = true;
|
|
1492
|
+
this.isDragDropEnabled = false;
|
|
1493
|
+
this.isVirtualizationEnabled = false;
|
|
1494
|
+
this.isTableViewEnabled = false;
|
|
1495
|
+
this.useCheckboxSelection = false;
|
|
1496
|
+
this.allowPartialSelection = true;
|
|
1497
|
+
this.config = {};
|
|
1498
|
+
this.selectionTheme = {
|
|
1499
|
+
...DEFAULT_SELECTION_THEME,
|
|
1500
|
+
colorScheme: "primary",
|
|
1501
|
+
intensity: "prominent",
|
|
1502
|
+
focusStyle: "ring",
|
|
1503
|
+
animated: true,
|
|
1504
|
+
persistSelection: true
|
|
1505
|
+
};
|
|
1506
|
+
this.nodes = [];
|
|
1507
|
+
this.contextMenuItems = [];
|
|
1508
|
+
this.updateSelectionTheme = (theme) => {
|
|
1509
|
+
this.selectionTheme = {
|
|
1510
|
+
...this.selectionTheme,
|
|
1511
|
+
...theme
|
|
1512
|
+
};
|
|
1513
|
+
};
|
|
1514
|
+
this.setMultiSelectEnabled = (enabled) => {
|
|
1515
|
+
this.isMultiSelectEnabled = enabled;
|
|
1516
|
+
};
|
|
1517
|
+
this.setCheckboxSelection = (enabled) => {
|
|
1518
|
+
this.useCheckboxSelection = enabled;
|
|
1519
|
+
};
|
|
1520
|
+
this.nodes = options.data ?? this.createDefaultData();
|
|
1521
|
+
this.contextMenuItems = options.contextMenuItems ?? this.createDefaultContextMenu();
|
|
1522
|
+
this.onSelectionChange = options.onSelectionChange;
|
|
1523
|
+
this.onContextMenuAction = options.onContextMenuAction;
|
|
1524
|
+
makeAutoObservable(this, {
|
|
1525
|
+
id: false,
|
|
1526
|
+
name: false,
|
|
1527
|
+
version: false,
|
|
1528
|
+
isDragDropEnabled: false,
|
|
1529
|
+
isVirtualizationEnabled: false,
|
|
1530
|
+
isTableViewEnabled: false,
|
|
1531
|
+
config: false
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Load root nodes
|
|
1536
|
+
*/
|
|
1537
|
+
async loadNodes(options) {
|
|
1538
|
+
return {
|
|
1539
|
+
nodes: this.nodes,
|
|
1540
|
+
hasMore: false,
|
|
1541
|
+
totalCount: this.nodes.length
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Load children for a specific node
|
|
1546
|
+
*/
|
|
1547
|
+
async loadChildren(node, options) {
|
|
1548
|
+
const children = node.children || [];
|
|
1549
|
+
return {
|
|
1550
|
+
nodes: children,
|
|
1551
|
+
hasMore: false,
|
|
1552
|
+
totalCount: children.length
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Refresh/reload nodes
|
|
1557
|
+
*/
|
|
1558
|
+
async refresh(path) {
|
|
1559
|
+
if (path) {
|
|
1560
|
+
const node = this.findNodeByPath(path);
|
|
1561
|
+
if (node) return this.loadChildren(node);
|
|
1562
|
+
}
|
|
1563
|
+
return this.loadNodes();
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Get context menu for a single node
|
|
1567
|
+
*/
|
|
1568
|
+
getNodeContextMenu(node) {
|
|
1569
|
+
return this.contextMenuItems.filter((item) => {
|
|
1570
|
+
if (item.id === "expand" || item.id === "collapse") return node.hasChildren || node.children && node.children.length > 0;
|
|
1571
|
+
if (item.id === "open") return node.type === "file";
|
|
1572
|
+
return true;
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Get context menu for multiple selected nodes
|
|
1577
|
+
*/
|
|
1578
|
+
getMultiNodeContextMenu(nodes) {
|
|
1579
|
+
return this.contextMenuItems.filter((item) => {
|
|
1580
|
+
return [
|
|
1581
|
+
"delete",
|
|
1582
|
+
"copy",
|
|
1583
|
+
"cut",
|
|
1584
|
+
"properties"
|
|
1585
|
+
].includes(item.id);
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Get selection theme configuration
|
|
1590
|
+
*/
|
|
1591
|
+
getSelectionTheme() {
|
|
1592
|
+
return this.selectionTheme;
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Update the static data
|
|
1596
|
+
*/
|
|
1597
|
+
setData(data) {
|
|
1598
|
+
this.nodes = data;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Get current data
|
|
1602
|
+
*/
|
|
1603
|
+
getData() {
|
|
1604
|
+
return this.nodes;
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Add a new node to the tree
|
|
1608
|
+
*/
|
|
1609
|
+
addNode(parentPath, newNode) {
|
|
1610
|
+
if (parentPath === null) {
|
|
1611
|
+
this.nodes.push(newNode);
|
|
1612
|
+
return true;
|
|
1613
|
+
}
|
|
1614
|
+
const parent = this.findNodeByPath(parentPath);
|
|
1615
|
+
if (parent) {
|
|
1616
|
+
if (!parent.children) parent.children = [];
|
|
1617
|
+
parent.children.push(newNode);
|
|
1618
|
+
parent.hasChildren = true;
|
|
1619
|
+
return true;
|
|
1620
|
+
}
|
|
1621
|
+
return false;
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Remove a node from the tree
|
|
1625
|
+
*/
|
|
1626
|
+
removeNode(nodePath) {
|
|
1627
|
+
const rootIndex = this.nodes.findIndex((node) => node.path === nodePath);
|
|
1628
|
+
if (rootIndex >= 0) {
|
|
1629
|
+
this.nodes.splice(rootIndex, 1);
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
return this.removeNodeFromChildren(this.nodes, nodePath);
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Find a node by its path
|
|
1636
|
+
*/
|
|
1637
|
+
findNodeByPath(path) {
|
|
1638
|
+
const searchNodes = (nodes) => {
|
|
1639
|
+
for (const node of nodes) {
|
|
1640
|
+
if (node.path === path) return node;
|
|
1641
|
+
if (node.children) {
|
|
1642
|
+
const found = searchNodes(node.children);
|
|
1643
|
+
if (found) return found;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return null;
|
|
1647
|
+
};
|
|
1648
|
+
return searchNodes(this.nodes);
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Create default sample data
|
|
1652
|
+
*/
|
|
1653
|
+
createDefaultData() {
|
|
1654
|
+
return [
|
|
1655
|
+
{
|
|
1656
|
+
id: "documents",
|
|
1657
|
+
name: "Documents",
|
|
1658
|
+
path: "/documents",
|
|
1659
|
+
type: "directory",
|
|
1660
|
+
hasChildren: true,
|
|
1661
|
+
children: [
|
|
1662
|
+
{
|
|
1663
|
+
id: "reports",
|
|
1664
|
+
name: "Reports",
|
|
1665
|
+
path: "/documents/reports",
|
|
1666
|
+
type: "directory",
|
|
1667
|
+
hasChildren: true,
|
|
1668
|
+
children: [{
|
|
1669
|
+
id: "quarterly-report",
|
|
1670
|
+
name: "Quarterly Report.pdf",
|
|
1671
|
+
path: "/documents/reports/quarterly-report.pdf",
|
|
1672
|
+
type: "file",
|
|
1673
|
+
size: 1024e3,
|
|
1674
|
+
lastModified: new Date("2024-01-15")
|
|
1675
|
+
}, {
|
|
1676
|
+
id: "annual-report",
|
|
1677
|
+
name: "Annual Report.pdf",
|
|
1678
|
+
path: "/documents/reports/annual-report.pdf",
|
|
1679
|
+
type: "file",
|
|
1680
|
+
size: 2048e3,
|
|
1681
|
+
lastModified: new Date("2024-01-10")
|
|
1682
|
+
}]
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
id: "presentations",
|
|
1686
|
+
name: "Presentations",
|
|
1687
|
+
path: "/documents/presentations",
|
|
1688
|
+
type: "directory",
|
|
1689
|
+
hasChildren: true,
|
|
1690
|
+
children: [{
|
|
1691
|
+
id: "product-demo",
|
|
1692
|
+
name: "Product Demo.pptx",
|
|
1693
|
+
path: "/documents/presentations/product-demo.pptx",
|
|
1694
|
+
type: "file",
|
|
1695
|
+
size: 512e4,
|
|
1696
|
+
lastModified: new Date("2024-01-20")
|
|
1697
|
+
}]
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
id: "readme",
|
|
1701
|
+
name: "README.md",
|
|
1702
|
+
path: "/documents/readme.md",
|
|
1703
|
+
type: "file",
|
|
1704
|
+
size: 2048,
|
|
1705
|
+
lastModified: new Date("2024-01-25")
|
|
1706
|
+
}
|
|
1707
|
+
]
|
|
1708
|
+
},
|
|
1709
|
+
{
|
|
1710
|
+
id: "projects",
|
|
1711
|
+
name: "Projects",
|
|
1712
|
+
path: "/projects",
|
|
1713
|
+
type: "directory",
|
|
1714
|
+
hasChildren: true,
|
|
1715
|
+
children: [{
|
|
1716
|
+
id: "web-app",
|
|
1717
|
+
name: "Web Application",
|
|
1718
|
+
path: "/projects/web-app",
|
|
1719
|
+
type: "directory",
|
|
1720
|
+
hasChildren: true,
|
|
1721
|
+
children: [{
|
|
1722
|
+
id: "src",
|
|
1723
|
+
name: "src",
|
|
1724
|
+
path: "/projects/web-app/src",
|
|
1725
|
+
type: "directory",
|
|
1726
|
+
hasChildren: true,
|
|
1727
|
+
children: [{
|
|
1728
|
+
id: "app-ts",
|
|
1729
|
+
name: "app.ts",
|
|
1730
|
+
path: "/projects/web-app/src/app.ts",
|
|
1731
|
+
type: "file",
|
|
1732
|
+
size: 4096,
|
|
1733
|
+
lastModified: new Date("2024-01-24")
|
|
1734
|
+
}, {
|
|
1735
|
+
id: "components",
|
|
1736
|
+
name: "components",
|
|
1737
|
+
path: "/projects/web-app/src/components",
|
|
1738
|
+
type: "directory",
|
|
1739
|
+
hasChildren: false,
|
|
1740
|
+
children: []
|
|
1741
|
+
}]
|
|
1742
|
+
}, {
|
|
1743
|
+
id: "package-json",
|
|
1744
|
+
name: "package.json",
|
|
1745
|
+
path: "/projects/web-app/package.json",
|
|
1746
|
+
type: "file",
|
|
1747
|
+
size: 1024,
|
|
1748
|
+
lastModified: new Date("2024-01-23")
|
|
1749
|
+
}]
|
|
1750
|
+
}]
|
|
1751
|
+
},
|
|
1752
|
+
{
|
|
1753
|
+
id: "downloads",
|
|
1754
|
+
name: "Downloads",
|
|
1755
|
+
path: "/downloads",
|
|
1756
|
+
type: "directory",
|
|
1757
|
+
hasChildren: false,
|
|
1758
|
+
children: []
|
|
1759
|
+
}
|
|
1760
|
+
];
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Create default context menu items
|
|
1764
|
+
*/
|
|
1765
|
+
createDefaultContextMenu() {
|
|
1766
|
+
return [
|
|
1767
|
+
{
|
|
1768
|
+
id: "open",
|
|
1769
|
+
label: "Open",
|
|
1770
|
+
icon: "file-text",
|
|
1771
|
+
handler: (node) => this.onContextMenuAction?.("open", [node])
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
id: "expand",
|
|
1775
|
+
label: "Expand",
|
|
1776
|
+
icon: "chevron-down",
|
|
1777
|
+
handler: (node) => this.onContextMenuAction?.("expand", [node])
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
id: "collapse",
|
|
1781
|
+
label: "Collapse",
|
|
1782
|
+
icon: "chevron-right",
|
|
1783
|
+
handler: (node) => this.onContextMenuAction?.("collapse", [node])
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
id: "separator-1",
|
|
1787
|
+
label: "",
|
|
1788
|
+
type: "separator"
|
|
1789
|
+
},
|
|
1790
|
+
{
|
|
1791
|
+
id: "copy",
|
|
1792
|
+
label: "Copy",
|
|
1793
|
+
icon: "copy",
|
|
1794
|
+
handler: (node) => this.onContextMenuAction?.("copy", [node])
|
|
1795
|
+
},
|
|
1796
|
+
{
|
|
1797
|
+
id: "cut",
|
|
1798
|
+
label: "Cut",
|
|
1799
|
+
icon: "scissors",
|
|
1800
|
+
handler: (node) => this.onContextMenuAction?.("cut", [node])
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
id: "delete",
|
|
1804
|
+
label: "Delete",
|
|
1805
|
+
icon: "trash-2",
|
|
1806
|
+
handler: (node) => this.onContextMenuAction?.("delete", [node])
|
|
1807
|
+
},
|
|
1808
|
+
{
|
|
1809
|
+
id: "separator-2",
|
|
1810
|
+
label: "",
|
|
1811
|
+
type: "separator"
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
id: "properties",
|
|
1815
|
+
label: "Properties",
|
|
1816
|
+
icon: "info",
|
|
1817
|
+
handler: (node) => this.onContextMenuAction?.("properties", [node])
|
|
1818
|
+
}
|
|
1819
|
+
];
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Helper method to remove node from children recursively
|
|
1823
|
+
*/
|
|
1824
|
+
removeNodeFromChildren(nodes, targetPath) {
|
|
1825
|
+
for (const node of nodes) if (node.children) {
|
|
1826
|
+
const childIndex = node.children.findIndex((child) => child.path === targetPath);
|
|
1827
|
+
if (childIndex >= 0) {
|
|
1828
|
+
node.children.splice(childIndex, 1);
|
|
1829
|
+
node.hasChildren = node.children.length > 0;
|
|
1830
|
+
return true;
|
|
1831
|
+
}
|
|
1832
|
+
if (this.removeNodeFromChildren(node.children, targetPath)) return true;
|
|
1833
|
+
}
|
|
1834
|
+
return false;
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
|
|
1838
|
+
//#endregion
|
|
1839
|
+
//#region src/tree/utils/logger.ts
|
|
1840
|
+
var TreeLogger = class {
|
|
1841
|
+
constructor() {
|
|
1842
|
+
this.isEnabled = true;
|
|
1843
|
+
this.logs = [];
|
|
1844
|
+
this.maxLogs = 1e3;
|
|
1845
|
+
}
|
|
1846
|
+
enable() {
|
|
1847
|
+
this.isEnabled = true;
|
|
1848
|
+
console.log("🌳 TreeComponent logging enabled");
|
|
1849
|
+
}
|
|
1850
|
+
disable() {
|
|
1851
|
+
this.isEnabled = false;
|
|
1852
|
+
console.log("🌳 TreeComponent logging disabled");
|
|
1853
|
+
}
|
|
1854
|
+
log(level, message, context, data) {
|
|
1855
|
+
if (!this.isEnabled) return;
|
|
1856
|
+
const entry = {
|
|
1857
|
+
timestamp: new Date().toISOString(),
|
|
1858
|
+
level,
|
|
1859
|
+
message,
|
|
1860
|
+
context,
|
|
1861
|
+
data
|
|
1862
|
+
};
|
|
1863
|
+
this.logs.push(entry);
|
|
1864
|
+
if (this.logs.length > this.maxLogs) this.logs.shift();
|
|
1865
|
+
const prefix = this.getPrefix(level);
|
|
1866
|
+
const contextStr = context ? this.formatContext(context) : "";
|
|
1867
|
+
const fullMessage = `${prefix} ${message}${contextStr}`;
|
|
1868
|
+
switch (level) {
|
|
1869
|
+
case "debug":
|
|
1870
|
+
console.debug(fullMessage, data || "");
|
|
1871
|
+
break;
|
|
1872
|
+
case "info":
|
|
1873
|
+
console.info(fullMessage, data || "");
|
|
1874
|
+
break;
|
|
1875
|
+
case "warn":
|
|
1876
|
+
console.warn(fullMessage, data || "");
|
|
1877
|
+
break;
|
|
1878
|
+
case "error":
|
|
1879
|
+
console.error(fullMessage, data || "");
|
|
1880
|
+
break;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
getPrefix(level) {
|
|
1884
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
1885
|
+
switch (level) {
|
|
1886
|
+
case "debug": return `🔍 [${timestamp}] TreeComponent:`;
|
|
1887
|
+
case "info": return `ℹ️ [${timestamp}] TreeComponent:`;
|
|
1888
|
+
case "warn": return `⚠️ [${timestamp}] TreeComponent:`;
|
|
1889
|
+
case "error": return `❌ [${timestamp}] TreeComponent:`;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
formatContext(context) {
|
|
1893
|
+
const parts = [];
|
|
1894
|
+
if (context.component) parts.push(`[${context.component}]`);
|
|
1895
|
+
if (context.nodeId) parts.push(`(node: ${context.nodeId})`);
|
|
1896
|
+
if (context.operation) parts.push(`{${context.operation}}`);
|
|
1897
|
+
Object.entries(context).forEach(([key, value]) => {
|
|
1898
|
+
if (![
|
|
1899
|
+
"component",
|
|
1900
|
+
"nodeId",
|
|
1901
|
+
"operation"
|
|
1902
|
+
].includes(key)) parts.push(`${key}=${value}`);
|
|
1903
|
+
});
|
|
1904
|
+
return parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
1905
|
+
}
|
|
1906
|
+
debug(message, context, data) {
|
|
1907
|
+
this.log("debug", message, context, data);
|
|
1908
|
+
}
|
|
1909
|
+
info(message, context, data) {
|
|
1910
|
+
this.log("info", message, context, data);
|
|
1911
|
+
}
|
|
1912
|
+
warn(message, context, data) {
|
|
1913
|
+
this.log("warn", message, context, data);
|
|
1914
|
+
}
|
|
1915
|
+
error(message, context, data) {
|
|
1916
|
+
this.log("error", message, context, data);
|
|
1917
|
+
}
|
|
1918
|
+
selection(operation, nodeId, data) {
|
|
1919
|
+
this.debug(`Selection ${operation}`, {
|
|
1920
|
+
component: "Selection",
|
|
1921
|
+
nodeId,
|
|
1922
|
+
operation
|
|
1923
|
+
}, data);
|
|
1924
|
+
}
|
|
1925
|
+
expansion(operation, nodeId, data) {
|
|
1926
|
+
this.debug(`Expansion ${operation}`, {
|
|
1927
|
+
component: "Expansion",
|
|
1928
|
+
nodeId,
|
|
1929
|
+
operation
|
|
1930
|
+
}, data);
|
|
1931
|
+
}
|
|
1932
|
+
interaction(operation, nodeId, data) {
|
|
1933
|
+
this.debug(`User interaction: ${operation}`, {
|
|
1934
|
+
component: "Interaction",
|
|
1935
|
+
nodeId,
|
|
1936
|
+
operation
|
|
1937
|
+
}, data);
|
|
1938
|
+
}
|
|
1939
|
+
rendering(component, nodeId, data) {
|
|
1940
|
+
this.debug(`Rendering`, {
|
|
1941
|
+
component,
|
|
1942
|
+
nodeId,
|
|
1943
|
+
operation: "render"
|
|
1944
|
+
}, data);
|
|
1945
|
+
}
|
|
1946
|
+
cssClasses(nodeId, classes, selected, focused) {
|
|
1947
|
+
this.debug(`CSS classes applied`, {
|
|
1948
|
+
component: "Styling",
|
|
1949
|
+
nodeId,
|
|
1950
|
+
selected: selected.toString(),
|
|
1951
|
+
focused: focused.toString()
|
|
1952
|
+
}, { classes });
|
|
1953
|
+
}
|
|
1954
|
+
providerCall(method, data) {
|
|
1955
|
+
this.debug(`Provider method called`, {
|
|
1956
|
+
component: "Provider",
|
|
1957
|
+
operation: method
|
|
1958
|
+
}, data);
|
|
1959
|
+
}
|
|
1960
|
+
stateChange(component, property, oldValue, newValue) {
|
|
1961
|
+
this.debug(`State change`, {
|
|
1962
|
+
component,
|
|
1963
|
+
operation: "state-change",
|
|
1964
|
+
property
|
|
1965
|
+
}, {
|
|
1966
|
+
oldValue,
|
|
1967
|
+
newValue
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
getLogs(filter) {
|
|
1971
|
+
if (!filter) return [...this.logs];
|
|
1972
|
+
return this.logs.filter((log) => {
|
|
1973
|
+
if (filter.level && log.level !== filter.level) return false;
|
|
1974
|
+
if (filter.component && log.context?.component !== filter.component) return false;
|
|
1975
|
+
return true;
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
clearLogs() {
|
|
1979
|
+
this.logs = [];
|
|
1980
|
+
console.log("🌳 TreeComponent logs cleared");
|
|
1981
|
+
}
|
|
1982
|
+
exportLogs() {
|
|
1983
|
+
return JSON.stringify(this.logs, null, 2);
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
1986
|
+
const logger = new TreeLogger();
|
|
1987
|
+
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
1988
|
+
logger.enable();
|
|
1989
|
+
window.treeLogger = logger;
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
//#endregion
|
|
1993
|
+
//#region src/tree/models/TreeModel.ts
|
|
1994
|
+
var TreeModel = class {
|
|
1995
|
+
constructor(provider) {
|
|
1996
|
+
this.provider = provider;
|
|
1997
|
+
this.nodes = [];
|
|
1998
|
+
this.nodeMap = observable.map();
|
|
1999
|
+
this.isLoading = false;
|
|
2000
|
+
this.errors = observable.map();
|
|
2001
|
+
this.selectedNodes = observable.map();
|
|
2002
|
+
this.focusedNode = null;
|
|
2003
|
+
this.checkboxStates = observable.map();
|
|
2004
|
+
this.expandedNodes = observable.map();
|
|
2005
|
+
this.loadingNodes = observable.map();
|
|
2006
|
+
this.contextMenuVisible = false;
|
|
2007
|
+
this.contextMenuPosition = {
|
|
2008
|
+
x: 0,
|
|
2009
|
+
y: 0
|
|
2010
|
+
};
|
|
2011
|
+
this.contextMenuItems = [];
|
|
2012
|
+
this.contextMenuNodes = [];
|
|
2013
|
+
this.loadNodes = flow(function* (options) {
|
|
2014
|
+
this.isLoading = true;
|
|
2015
|
+
this.errors.clear();
|
|
2016
|
+
try {
|
|
2017
|
+
const result = yield this.provider.loadNodes(options);
|
|
2018
|
+
this.nodes = result.nodes;
|
|
2019
|
+
this.nodeMap.clear();
|
|
2020
|
+
result.nodes.forEach((node) => this.nodeMap.set(node.id, node));
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
const errorKey = "loadNodes";
|
|
2023
|
+
this.errors.set(errorKey, error instanceof Error ? error : new Error(String(error)));
|
|
2024
|
+
} finally {
|
|
2025
|
+
this.isLoading = false;
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
this.expandNode = flow(function* (node) {
|
|
2029
|
+
logger.expansion("expandNode called", node.id, {
|
|
2030
|
+
hasChildren: node.hasChildren,
|
|
2031
|
+
currentlyExpanded: this.isNodeExpanded(node.id),
|
|
2032
|
+
hasLoadedChildren: !!(node.children && node.children.length > 0)
|
|
2033
|
+
});
|
|
2034
|
+
if (!this.nodeMap.has(node.id)) {
|
|
2035
|
+
logger.expansion("expandNode skipped - node not in tree", node.id);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
this.expandedNodes.set(node.id, true);
|
|
2039
|
+
logger.expansion("expansion state set to true", node.id);
|
|
2040
|
+
if (this.provider.onNodeExpansion) {
|
|
2041
|
+
logger.providerCall("onNodeExpansion", {
|
|
2042
|
+
nodeId: node.id,
|
|
2043
|
+
expanded: true
|
|
2044
|
+
});
|
|
2045
|
+
this.provider.onNodeExpansion(node, true);
|
|
2046
|
+
}
|
|
2047
|
+
if (node.hasChildren && (!node.children || node.children.length === 0)) {
|
|
2048
|
+
logger.expansion("loading children", node.id, { hasChildren: node.hasChildren });
|
|
2049
|
+
this.loadingNodes.set(node.id, true);
|
|
2050
|
+
try {
|
|
2051
|
+
logger.providerCall("loadChildren", { nodeId: node.id });
|
|
2052
|
+
const result = yield this.provider.loadChildren(node);
|
|
2053
|
+
logger.expansion("children loaded successfully", node.id, { childCount: result.nodes.length });
|
|
2054
|
+
const existingNode = this.nodeMap.get(node.id);
|
|
2055
|
+
if (existingNode) {
|
|
2056
|
+
existingNode.children = result.nodes;
|
|
2057
|
+
result.nodes.forEach((child) => {
|
|
2058
|
+
this.nodeMap.set(child.id, child);
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
node.children = result.nodes;
|
|
2062
|
+
const updateNodeInTree = (nodes) => {
|
|
2063
|
+
for (const treeNode of nodes) {
|
|
2064
|
+
if (treeNode.id === node.id) treeNode.children = result.nodes;
|
|
2065
|
+
if (treeNode.children) updateNodeInTree(treeNode.children);
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
updateNodeInTree(this.nodes);
|
|
2069
|
+
if (this.provider.allowPartialSelection && this.provider.useCheckboxSelection) {
|
|
2070
|
+
const parentCheckboxState = this.getCheckboxState(node.id);
|
|
2071
|
+
logger.debug("Applying parent checkbox state to newly loaded children", {
|
|
2072
|
+
component: "Checkbox",
|
|
2073
|
+
nodeId: node.id,
|
|
2074
|
+
operation: "children-loaded-inheritance"
|
|
2075
|
+
}, {
|
|
2076
|
+
parentState: parentCheckboxState,
|
|
2077
|
+
childrenCount: result.nodes.length,
|
|
2078
|
+
childIds: result.nodes.map((child) => child.id)
|
|
2079
|
+
});
|
|
2080
|
+
if (parentCheckboxState === "checked" || parentCheckboxState === "unchecked") result.nodes.forEach((child) => {
|
|
2081
|
+
const previousChildState = this.checkboxStates.get(child.id);
|
|
2082
|
+
this.checkboxStates.set(child.id, parentCheckboxState);
|
|
2083
|
+
logger.debug("Child checkbox state inherited from parent", {
|
|
2084
|
+
component: "Checkbox",
|
|
2085
|
+
nodeId: child.id
|
|
2086
|
+
}, {
|
|
2087
|
+
parentNode: node.id,
|
|
2088
|
+
parentState: parentCheckboxState,
|
|
2089
|
+
previousChildState,
|
|
2090
|
+
newChildState: parentCheckboxState
|
|
2091
|
+
});
|
|
2092
|
+
if (parentCheckboxState === "checked") {
|
|
2093
|
+
this.selectedNodes.set(child.id, child);
|
|
2094
|
+
logger.selection("child auto-selected via parent inheritance", child.id, { parentNode: node.id });
|
|
2095
|
+
} else {
|
|
2096
|
+
this.selectedNodes.delete(child.id);
|
|
2097
|
+
logger.selection("child auto-deselected via parent inheritance", child.id, { parentNode: node.id });
|
|
2098
|
+
}
|
|
2099
|
+
if (child.children && child.children.length > 0) this.updateChildCheckboxes(child, parentCheckboxState);
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
} catch (error) {
|
|
2103
|
+
const errorKey = `loadChildren-${node.id}`;
|
|
2104
|
+
this.errors.set(errorKey, error instanceof Error ? error : new Error(String(error)));
|
|
2105
|
+
this.expandedNodes.set(node.id, false);
|
|
2106
|
+
} finally {
|
|
2107
|
+
this.loadingNodes.delete(node.id);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
});
|
|
2111
|
+
makeAutoObservable(this, {});
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Whether the tree has any nodes loaded
|
|
2115
|
+
*/
|
|
2116
|
+
get hasNodes() {
|
|
2117
|
+
return this.nodes.length > 0;
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Total count of nodes currently loaded
|
|
2121
|
+
*/
|
|
2122
|
+
get nodeCount() {
|
|
2123
|
+
return this.nodes.length;
|
|
2124
|
+
}
|
|
2125
|
+
/**
|
|
2126
|
+
* Whether data has been loaded (regardless of success/failure)
|
|
2127
|
+
*/
|
|
2128
|
+
get isLoaded() {
|
|
2129
|
+
return !this.isLoading && (this.hasNodes || this.errors.size > 0);
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Array of currently selected nodes (computed from selectedNodes map)
|
|
2133
|
+
*/
|
|
2134
|
+
get selectedNodesArray() {
|
|
2135
|
+
return Array.from(this.selectedNodes.values());
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Whether any nodes are currently selected
|
|
2139
|
+
*/
|
|
2140
|
+
get hasSelection() {
|
|
2141
|
+
return this.selectedNodes.size > 0;
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Check if a specific node is selected
|
|
2145
|
+
*/
|
|
2146
|
+
isNodeSelected(nodeId) {
|
|
2147
|
+
return this.selectedNodes.has(nodeId);
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Check if a specific node is expanded
|
|
2151
|
+
*/
|
|
2152
|
+
isNodeExpanded(nodeId) {
|
|
2153
|
+
return this.expandedNodes.get(nodeId) ?? false;
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Check if a specific node is loading
|
|
2157
|
+
*/
|
|
2158
|
+
isNodeLoading(nodeId) {
|
|
2159
|
+
return this.loadingNodes.get(nodeId) ?? false;
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Select a specific node
|
|
2163
|
+
* @param node Node to select
|
|
2164
|
+
*/
|
|
2165
|
+
selectNode(node) {
|
|
2166
|
+
const previousSelection = this.selectedNodesArray.map((n) => n.id);
|
|
2167
|
+
logger.selection("selectNode called", node.id, {
|
|
2168
|
+
previouslySelected: previousSelection,
|
|
2169
|
+
isMultiSelect: this.provider.isMultiSelectEnabled
|
|
2170
|
+
});
|
|
2171
|
+
if (!this.provider.isMultiSelectEnabled) {
|
|
2172
|
+
logger.selection("clearing selection (single-select mode)", node.id);
|
|
2173
|
+
this.selectedNodes.clear();
|
|
2174
|
+
}
|
|
2175
|
+
this.selectedNodes.set(node.id, node);
|
|
2176
|
+
logger.selection("node added to selection", node.id, { totalSelected: this.selectedNodes.size });
|
|
2177
|
+
const previousFocus = this.focusedNode;
|
|
2178
|
+
this.focusedNode = node.id;
|
|
2179
|
+
logger.stateChange("TreeModel", "focusedNode", previousFocus, this.focusedNode);
|
|
2180
|
+
if (this.provider.onSelectionChange) {
|
|
2181
|
+
logger.providerCall("onSelectionChange", {
|
|
2182
|
+
selectedCount: this.selectedNodesArray.length,
|
|
2183
|
+
selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single"
|
|
2184
|
+
});
|
|
2185
|
+
this.provider.onSelectionChange({
|
|
2186
|
+
selectedNodes: this.selectedNodesArray,
|
|
2187
|
+
previousSelection: [],
|
|
2188
|
+
selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single",
|
|
2189
|
+
trigger: "click"
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Deselect a specific node
|
|
2195
|
+
* @param node Node to deselect
|
|
2196
|
+
*/
|
|
2197
|
+
deselectNode(node) {
|
|
2198
|
+
logger.selection("deselectNode called", node.id, { wasSelected: this.selectedNodes.has(node.id) });
|
|
2199
|
+
if (this.selectedNodes.has(node.id)) {
|
|
2200
|
+
this.selectedNodes.delete(node.id);
|
|
2201
|
+
logger.selection("node removed from selection", node.id, { remainingSelected: this.selectedNodes.size });
|
|
2202
|
+
if (this.focusedNode === node.id) {
|
|
2203
|
+
const previousFocus = this.focusedNode;
|
|
2204
|
+
this.focusedNode = null;
|
|
2205
|
+
logger.stateChange("TreeModel", "focusedNode", previousFocus, this.focusedNode);
|
|
2206
|
+
}
|
|
2207
|
+
if (this.provider.onSelectionChange) {
|
|
2208
|
+
logger.providerCall("onSelectionChange", {
|
|
2209
|
+
selectedCount: this.selectedNodesArray.length,
|
|
2210
|
+
selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single"
|
|
2211
|
+
});
|
|
2212
|
+
this.provider.onSelectionChange({
|
|
2213
|
+
selectedNodes: this.selectedNodesArray,
|
|
2214
|
+
previousSelection: [],
|
|
2215
|
+
selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single",
|
|
2216
|
+
trigger: "click"
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
/**
|
|
2222
|
+
* Clear all selected nodes
|
|
2223
|
+
*/
|
|
2224
|
+
clearSelection() {
|
|
2225
|
+
if (this.selectedNodes.size > 0) {
|
|
2226
|
+
this.selectedNodes.clear();
|
|
2227
|
+
this.focusedNode = null;
|
|
2228
|
+
if (this.provider.onSelectionChange) this.provider.onSelectionChange({
|
|
2229
|
+
selectedNodes: [],
|
|
2230
|
+
previousSelection: [],
|
|
2231
|
+
selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single",
|
|
2232
|
+
trigger: "api"
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Select all nodes (only if multi-select is enabled)
|
|
2238
|
+
*/
|
|
2239
|
+
selectAll() {
|
|
2240
|
+
if (!this.provider.isMultiSelectEnabled) return;
|
|
2241
|
+
this.nodes.forEach((node) => {
|
|
2242
|
+
this.selectedNodes.set(node.id, node);
|
|
2243
|
+
});
|
|
2244
|
+
if (this.nodes.length > 0) {
|
|
2245
|
+
const firstNode = this.nodes[0];
|
|
2246
|
+
if (firstNode) this.focusedNode = firstNode.id;
|
|
2247
|
+
}
|
|
2248
|
+
if (this.provider.onSelectionChange) this.provider.onSelectionChange({
|
|
2249
|
+
selectedNodes: this.selectedNodesArray,
|
|
2250
|
+
previousSelection: [],
|
|
2251
|
+
selectionType: "multi",
|
|
2252
|
+
trigger: "api"
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
/**
|
|
2256
|
+
* Get checkbox state for a specific node
|
|
2257
|
+
*/
|
|
2258
|
+
getCheckboxState(nodeId) {
|
|
2259
|
+
return this.checkboxStates.get(nodeId) ?? "unchecked";
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Handle checkbox click for a node
|
|
2263
|
+
*/
|
|
2264
|
+
handleCheckboxChange(node, newState) {
|
|
2265
|
+
logger.interaction("handleCheckboxChange called", node.id, {
|
|
2266
|
+
newState,
|
|
2267
|
+
useCheckboxSelection: this.provider.useCheckboxSelection,
|
|
2268
|
+
allowPartialSelection: this.provider.allowPartialSelection,
|
|
2269
|
+
currentCheckboxState: this.checkboxStates.get(node.id),
|
|
2270
|
+
isCurrentlySelected: this.selectedNodes.has(node.id)
|
|
2271
|
+
});
|
|
2272
|
+
if (!this.provider.useCheckboxSelection) {
|
|
2273
|
+
logger.interaction("handleCheckboxChange aborted - useCheckboxSelection is false", node.id);
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
const previousState = this.checkboxStates.get(node.id);
|
|
2277
|
+
this.checkboxStates.set(node.id, newState);
|
|
2278
|
+
logger.stateChange("TreeModel", "checkboxStates", previousState, newState);
|
|
2279
|
+
const wasSelected = this.selectedNodes.has(node.id);
|
|
2280
|
+
if (newState === "checked") {
|
|
2281
|
+
this.selectedNodes.set(node.id, node);
|
|
2282
|
+
logger.selection("node added via checkbox", node.id, {
|
|
2283
|
+
wasSelected,
|
|
2284
|
+
nowSelected: true
|
|
2285
|
+
});
|
|
2286
|
+
} else {
|
|
2287
|
+
this.selectedNodes.delete(node.id);
|
|
2288
|
+
logger.selection("node removed via checkbox", node.id, {
|
|
2289
|
+
wasSelected,
|
|
2290
|
+
nowSelected: false
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
if (this.provider.allowPartialSelection) {
|
|
2294
|
+
logger.debug("Updating parent/child checkbox relationships", {
|
|
2295
|
+
component: "Checkbox",
|
|
2296
|
+
nodeId: node.id
|
|
2297
|
+
});
|
|
2298
|
+
this.updateChildCheckboxes(node, newState);
|
|
2299
|
+
this.updateParentCheckboxes(node);
|
|
2300
|
+
}
|
|
2301
|
+
if (this.provider.onSelectionChange) {
|
|
2302
|
+
const selectionInfo = {
|
|
2303
|
+
selectedNodes: this.selectedNodesArray,
|
|
2304
|
+
previousSelection: [],
|
|
2305
|
+
selectionType: "multi",
|
|
2306
|
+
trigger: "click"
|
|
2307
|
+
};
|
|
2308
|
+
logger.providerCall("onSelectionChange", {
|
|
2309
|
+
selectedCount: selectionInfo.selectedNodes.length,
|
|
2310
|
+
trigger: selectionInfo.trigger
|
|
2311
|
+
});
|
|
2312
|
+
this.provider.onSelectionChange(selectionInfo);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Update all child checkboxes when parent state changes
|
|
2317
|
+
*/
|
|
2318
|
+
updateChildCheckboxes(node, state) {
|
|
2319
|
+
logger.debug("updateChildCheckboxes called", {
|
|
2320
|
+
component: "Checkbox",
|
|
2321
|
+
nodeId: node.id,
|
|
2322
|
+
operation: "update-children"
|
|
2323
|
+
}, {
|
|
2324
|
+
parentState: state,
|
|
2325
|
+
hasChildren: !!node.children,
|
|
2326
|
+
childrenCount: node.children?.length || 0
|
|
2327
|
+
});
|
|
2328
|
+
if (!node.children || state === "indeterminate") {
|
|
2329
|
+
logger.debug("updateChildCheckboxes skipped", {
|
|
2330
|
+
component: "Checkbox",
|
|
2331
|
+
nodeId: node.id
|
|
2332
|
+
}, {
|
|
2333
|
+
reason: !node.children ? "no-children" : "indeterminate-state",
|
|
2334
|
+
hasChildren: !!node.children,
|
|
2335
|
+
state
|
|
2336
|
+
});
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
node.children.forEach((child) => {
|
|
2340
|
+
const previousState = this.checkboxStates.get(child.id);
|
|
2341
|
+
this.checkboxStates.set(child.id, state);
|
|
2342
|
+
logger.debug("updateChildCheckboxes - child updated", {
|
|
2343
|
+
component: "Checkbox",
|
|
2344
|
+
nodeId: child.id
|
|
2345
|
+
}, {
|
|
2346
|
+
previousState,
|
|
2347
|
+
newState: state,
|
|
2348
|
+
parentNode: node.id
|
|
2349
|
+
});
|
|
2350
|
+
if (state === "checked") {
|
|
2351
|
+
this.selectedNodes.set(child.id, child);
|
|
2352
|
+
logger.selection("child selected via parent", child.id, { parentNode: node.id });
|
|
2353
|
+
} else {
|
|
2354
|
+
this.selectedNodes.delete(child.id);
|
|
2355
|
+
logger.selection("child deselected via parent", child.id, { parentNode: node.id });
|
|
2356
|
+
}
|
|
2357
|
+
this.updateChildCheckboxes(child, state);
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Update parent checkbox state based on children
|
|
2362
|
+
*/
|
|
2363
|
+
updateParentCheckboxes(node) {
|
|
2364
|
+
const parent = this.findParentNode(node.id);
|
|
2365
|
+
logger.debug("updateParentCheckboxes called", {
|
|
2366
|
+
component: "Checkbox",
|
|
2367
|
+
nodeId: node.id,
|
|
2368
|
+
operation: "update-parent"
|
|
2369
|
+
}, {
|
|
2370
|
+
parentFound: !!parent,
|
|
2371
|
+
parentId: parent?.id
|
|
2372
|
+
});
|
|
2373
|
+
if (!parent || !parent.children) {
|
|
2374
|
+
logger.debug("updateParentCheckboxes skipped", {
|
|
2375
|
+
component: "Checkbox",
|
|
2376
|
+
nodeId: node.id
|
|
2377
|
+
}, { reason: !parent ? "no-parent" : "parent-no-children" });
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
const childStates = parent.children.map((child) => this.getCheckboxState(child.id));
|
|
2381
|
+
const checkedCount = childStates.filter((state) => state === "checked").length;
|
|
2382
|
+
const uncheckedCount = childStates.filter((state) => state === "unchecked").length;
|
|
2383
|
+
const indeterminateCount = childStates.filter((state) => state === "indeterminate").length;
|
|
2384
|
+
logger.debug("updateParentCheckboxes - analyzing children", {
|
|
2385
|
+
component: "Checkbox",
|
|
2386
|
+
nodeId: parent.id
|
|
2387
|
+
}, {
|
|
2388
|
+
totalChildren: parent.children.length,
|
|
2389
|
+
checkedCount,
|
|
2390
|
+
uncheckedCount,
|
|
2391
|
+
indeterminateCount,
|
|
2392
|
+
childStates
|
|
2393
|
+
});
|
|
2394
|
+
let parentState;
|
|
2395
|
+
if (checkedCount === parent.children.length) parentState = "checked";
|
|
2396
|
+
else if (checkedCount === 0 && indeterminateCount === 0) parentState = "unchecked";
|
|
2397
|
+
else parentState = "indeterminate";
|
|
2398
|
+
const previousParentState = this.checkboxStates.get(parent.id);
|
|
2399
|
+
this.checkboxStates.set(parent.id, parentState);
|
|
2400
|
+
logger.debug("updateParentCheckboxes - parent state calculated", {
|
|
2401
|
+
component: "Checkbox",
|
|
2402
|
+
nodeId: parent.id
|
|
2403
|
+
}, {
|
|
2404
|
+
previousState: previousParentState,
|
|
2405
|
+
newState: parentState,
|
|
2406
|
+
logic: checkedCount === parent.children.length ? "all-checked" : checkedCount === 0 && indeterminateCount === 0 ? "none-checked" : "mixed"
|
|
2407
|
+
});
|
|
2408
|
+
if (parentState === "checked") {
|
|
2409
|
+
this.selectedNodes.set(parent.id, parent);
|
|
2410
|
+
logger.selection("parent selected via children", parent.id, { newState: parentState });
|
|
2411
|
+
} else {
|
|
2412
|
+
this.selectedNodes.delete(parent.id);
|
|
2413
|
+
logger.selection("parent deselected via children", parent.id, { newState: parentState });
|
|
2414
|
+
}
|
|
2415
|
+
this.updateParentCheckboxes(parent);
|
|
2416
|
+
}
|
|
2417
|
+
/**
|
|
2418
|
+
* Find parent node of a given node ID
|
|
2419
|
+
*/
|
|
2420
|
+
findParentNode(nodeId) {
|
|
2421
|
+
const findParent = (nodes, targetId, parentNode = null) => {
|
|
2422
|
+
for (const node of nodes) {
|
|
2423
|
+
if (node.id === targetId) return parentNode;
|
|
2424
|
+
if (node.children && node.children.length > 0) {
|
|
2425
|
+
const parent = findParent(node.children, targetId, node);
|
|
2426
|
+
if (parent) return parent;
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
return null;
|
|
2430
|
+
};
|
|
2431
|
+
return findParent(this.nodes, nodeId);
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Set focus to a specific node
|
|
2435
|
+
* @param nodeId ID of node to focus
|
|
2436
|
+
*/
|
|
2437
|
+
setFocus(nodeId) {
|
|
2438
|
+
if (this.nodeMap.has(nodeId)) this.focusedNode = nodeId;
|
|
2439
|
+
}
|
|
2440
|
+
/**
|
|
2441
|
+
* Move focus to next visible node
|
|
2442
|
+
*/
|
|
2443
|
+
focusNext() {
|
|
2444
|
+
const flatNodes = this.getFlattenedVisibleNodes();
|
|
2445
|
+
const currentIndex = flatNodes.findIndex((node) => node.id === this.focusedNode);
|
|
2446
|
+
if (currentIndex >= 0 && currentIndex < flatNodes.length - 1) {
|
|
2447
|
+
const nextNode = flatNodes[currentIndex + 1];
|
|
2448
|
+
if (nextNode) {
|
|
2449
|
+
this.focusedNode = nextNode.id;
|
|
2450
|
+
return true;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
return false;
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Move focus to previous visible node
|
|
2457
|
+
*/
|
|
2458
|
+
focusPrevious() {
|
|
2459
|
+
const flatNodes = this.getFlattenedVisibleNodes();
|
|
2460
|
+
const currentIndex = flatNodes.findIndex((node) => node.id === this.focusedNode);
|
|
2461
|
+
if (currentIndex > 0) {
|
|
2462
|
+
const prevNode = flatNodes[currentIndex - 1];
|
|
2463
|
+
if (prevNode) {
|
|
2464
|
+
this.focusedNode = prevNode.id;
|
|
2465
|
+
return true;
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return false;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Move focus to first visible node
|
|
2472
|
+
*/
|
|
2473
|
+
focusFirst() {
|
|
2474
|
+
const flatNodes = this.getFlattenedVisibleNodes();
|
|
2475
|
+
if (flatNodes.length > 0) {
|
|
2476
|
+
const firstNode = flatNodes[0];
|
|
2477
|
+
if (firstNode) {
|
|
2478
|
+
this.focusedNode = firstNode.id;
|
|
2479
|
+
return true;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
return false;
|
|
2483
|
+
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Move focus to last visible node
|
|
2486
|
+
*/
|
|
2487
|
+
focusLast() {
|
|
2488
|
+
const flatNodes = this.getFlattenedVisibleNodes();
|
|
2489
|
+
if (flatNodes.length > 0) {
|
|
2490
|
+
const lastNode = flatNodes[flatNodes.length - 1];
|
|
2491
|
+
if (lastNode) {
|
|
2492
|
+
this.focusedNode = lastNode.id;
|
|
2493
|
+
return true;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return false;
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Get flattened list of all currently visible nodes
|
|
2500
|
+
* @private
|
|
2501
|
+
*/
|
|
2502
|
+
getFlattenedVisibleNodes() {
|
|
2503
|
+
const result = [];
|
|
2504
|
+
const processNodes = (nodes) => {
|
|
2505
|
+
for (const node of nodes) {
|
|
2506
|
+
result.push(node);
|
|
2507
|
+
if (this.isNodeExpanded(node.id) && node.children && node.children.length > 0) processNodes(node.children);
|
|
2508
|
+
}
|
|
2509
|
+
};
|
|
2510
|
+
processNodes(this.nodes);
|
|
2511
|
+
return result;
|
|
2512
|
+
}
|
|
2513
|
+
/**
|
|
2514
|
+
* Collapse a specific node
|
|
2515
|
+
* @param node Node to collapse
|
|
2516
|
+
*/
|
|
2517
|
+
collapseNode(node) {
|
|
2518
|
+
logger.expansion("collapseNode called", node.id, { currentlyExpanded: this.isNodeExpanded(node.id) });
|
|
2519
|
+
this.expandedNodes.set(node.id, false);
|
|
2520
|
+
logger.expansion("expansion state set to false", node.id);
|
|
2521
|
+
if (this.provider.onNodeExpansion) {
|
|
2522
|
+
logger.providerCall("onNodeExpansion", {
|
|
2523
|
+
nodeId: node.id,
|
|
2524
|
+
expanded: false
|
|
2525
|
+
});
|
|
2526
|
+
this.provider.onNodeExpansion(node, false);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
/**
|
|
2530
|
+
* Toggle expansion state of a specific node
|
|
2531
|
+
* @param node Node to toggle
|
|
2532
|
+
*/
|
|
2533
|
+
toggleExpansion(node) {
|
|
2534
|
+
if (this.isNodeExpanded(node.id)) this.collapseNode(node);
|
|
2535
|
+
else this.expandNode(node);
|
|
2536
|
+
}
|
|
2537
|
+
/**
|
|
2538
|
+
* Show context menu for a specific node
|
|
2539
|
+
* @param node Target node
|
|
2540
|
+
* @param event Mouse event
|
|
2541
|
+
*/
|
|
2542
|
+
showContextMenu(node, event) {
|
|
2543
|
+
const menuItems = this.provider.getNodeContextMenu?.(node) || [];
|
|
2544
|
+
if (menuItems.length === 0) return;
|
|
2545
|
+
this.contextMenuVisible = true;
|
|
2546
|
+
this.contextMenuPosition = {
|
|
2547
|
+
x: event.clientX,
|
|
2548
|
+
y: event.clientY
|
|
2549
|
+
};
|
|
2550
|
+
this.contextMenuItems = menuItems;
|
|
2551
|
+
this.contextMenuNodes = [node];
|
|
2552
|
+
}
|
|
2553
|
+
/**
|
|
2554
|
+
* Show context menu for multiple selected nodes
|
|
2555
|
+
* @param nodes Selected nodes
|
|
2556
|
+
* @param event Mouse event
|
|
2557
|
+
*/
|
|
2558
|
+
showMultiNodeContextMenu(nodes, event) {
|
|
2559
|
+
const menuItems = this.provider.getMultiNodeContextMenu?.(nodes) || [];
|
|
2560
|
+
if (menuItems.length === 0) return;
|
|
2561
|
+
this.contextMenuVisible = true;
|
|
2562
|
+
this.contextMenuPosition = {
|
|
2563
|
+
x: event.clientX,
|
|
2564
|
+
y: event.clientY
|
|
2565
|
+
};
|
|
2566
|
+
this.contextMenuItems = menuItems;
|
|
2567
|
+
this.contextMenuNodes = nodes;
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Hide context menu
|
|
2571
|
+
*/
|
|
2572
|
+
hideContextMenu() {
|
|
2573
|
+
this.contextMenuVisible = false;
|
|
2574
|
+
this.contextMenuItems = [];
|
|
2575
|
+
this.contextMenuNodes = [];
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Handle context menu item click
|
|
2579
|
+
* @param menuItem Clicked menu item
|
|
2580
|
+
*/
|
|
2581
|
+
handleContextMenuAction(menuItem) {
|
|
2582
|
+
if (this.provider.onContextMenuAction) this.provider.onContextMenuAction(menuItem.id, this.contextMenuNodes);
|
|
2583
|
+
this.hideContextMenu();
|
|
2584
|
+
}
|
|
2585
|
+
};
|
|
2586
|
+
|
|
2587
|
+
//#endregion
|
|
2588
|
+
//#region src/tree/components/TreeCheckbox.tsx
|
|
2589
|
+
/**
|
|
2590
|
+
* TreeCheckbox Component - 3-state checkbox for tree selection
|
|
2591
|
+
*/
|
|
2592
|
+
const TreeCheckbox = observer(({ state, onChange, disabled = false, className, ariaLabel, size = "md", nodeId = "unknown" }) => {
|
|
2593
|
+
logger.rendering("TreeCheckbox", nodeId, {
|
|
2594
|
+
state,
|
|
2595
|
+
disabled,
|
|
2596
|
+
size,
|
|
2597
|
+
hasOnChange: !!onChange
|
|
2598
|
+
});
|
|
2599
|
+
const handleClick = (event) => {
|
|
2600
|
+
event.stopPropagation();
|
|
2601
|
+
logger.interaction("TreeCheckbox click", nodeId, {
|
|
2602
|
+
currentState: state,
|
|
2603
|
+
disabled,
|
|
2604
|
+
eventType: "click"
|
|
2605
|
+
});
|
|
2606
|
+
if (disabled) {
|
|
2607
|
+
logger.interaction("TreeCheckbox click ignored (disabled)", nodeId);
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
const newState = state === "checked" ? "unchecked" : "checked";
|
|
2611
|
+
logger.interaction("TreeCheckbox state change", nodeId, {
|
|
2612
|
+
from: state,
|
|
2613
|
+
to: newState,
|
|
2614
|
+
trigger: "click"
|
|
2615
|
+
});
|
|
2616
|
+
onChange(newState);
|
|
2617
|
+
};
|
|
2618
|
+
const handleKeyDown = (event) => {
|
|
2619
|
+
if (event.key === " " || event.key === "Enter") {
|
|
2620
|
+
event.preventDefault();
|
|
2621
|
+
logger.interaction("TreeCheckbox keydown", nodeId, {
|
|
2622
|
+
key: event.key,
|
|
2623
|
+
currentState: state
|
|
2624
|
+
});
|
|
2625
|
+
handleClick(event);
|
|
2626
|
+
}
|
|
2627
|
+
};
|
|
2628
|
+
const sizeClasses = {
|
|
2629
|
+
sm: "w-3 h-3",
|
|
2630
|
+
md: "w-4 h-4",
|
|
2631
|
+
lg: "w-5 h-5"
|
|
2632
|
+
};
|
|
2633
|
+
const iconSizeClasses = {
|
|
2634
|
+
sm: "w-2 h-2",
|
|
2635
|
+
md: "w-3 h-3",
|
|
2636
|
+
lg: "w-4 h-4"
|
|
2637
|
+
};
|
|
2638
|
+
const computedClasses = cn(
|
|
2639
|
+
"flex items-center justify-center rounded border cursor-pointer transition-all",
|
|
2640
|
+
"focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1",
|
|
2641
|
+
sizeClasses[size],
|
|
2642
|
+
// Base styles
|
|
2643
|
+
"border-border bg-background",
|
|
2644
|
+
// State-specific styles
|
|
2645
|
+
state === "checked" && "bg-primary border-primary text-primary-foreground",
|
|
2646
|
+
state === "indeterminate" && "bg-primary border-primary text-primary-foreground",
|
|
2647
|
+
state === "unchecked" && "hover:border-primary/50",
|
|
2648
|
+
// Disabled styles
|
|
2649
|
+
disabled && "opacity-50 cursor-not-allowed pointer-events-none",
|
|
2650
|
+
className
|
|
2651
|
+
);
|
|
2652
|
+
logger.cssClasses(`TreeCheckbox-${nodeId}`, computedClasses, state === "checked", false);
|
|
2653
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2654
|
+
role: "checkbox",
|
|
2655
|
+
"aria-checked": state === "indeterminate" ? "mixed" : state === "checked",
|
|
2656
|
+
"aria-label": ariaLabel,
|
|
2657
|
+
className: computedClasses,
|
|
2658
|
+
onClick: handleClick,
|
|
2659
|
+
onKeyDown: handleKeyDown,
|
|
2660
|
+
tabIndex: disabled ? -1 : 0,
|
|
2661
|
+
children: [state === "checked" && /* @__PURE__ */ jsx(Check, { className: cn(iconSizeClasses[size], "stroke-[3]") }), state === "indeterminate" && /* @__PURE__ */ jsx(Minus, { className: cn(iconSizeClasses[size], "stroke-[3]") })]
|
|
2662
|
+
});
|
|
2663
|
+
});
|
|
2664
|
+
|
|
2665
|
+
//#endregion
|
|
2666
|
+
//#region src/tree/components/TreeNodeList.tsx
|
|
2667
|
+
const TreeInlineRename = ({ currentName, onCommit, onCancel }) => {
|
|
2668
|
+
const [value, setValue] = useState(currentName);
|
|
2669
|
+
const inputRef = useRef(null);
|
|
2670
|
+
const mountTimeRef = useRef(Date.now());
|
|
2671
|
+
const committedRef = useRef(false);
|
|
2672
|
+
useEffect(() => {
|
|
2673
|
+
mountTimeRef.current = Date.now();
|
|
2674
|
+
const raf = requestAnimationFrame(() => {
|
|
2675
|
+
const input = inputRef.current;
|
|
2676
|
+
if (input) {
|
|
2677
|
+
input.focus();
|
|
2678
|
+
const dotIndex = currentName.lastIndexOf(".");
|
|
2679
|
+
input.setSelectionRange(0, dotIndex > 0 ? dotIndex : currentName.length);
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
return () => cancelAnimationFrame(raf);
|
|
2683
|
+
}, [currentName]);
|
|
2684
|
+
const commit = () => {
|
|
2685
|
+
if (committedRef.current) return;
|
|
2686
|
+
committedRef.current = true;
|
|
2687
|
+
const trimmed = value.trim();
|
|
2688
|
+
if (trimmed && trimmed !== currentName) onCommit(trimmed);
|
|
2689
|
+
else onCancel();
|
|
2690
|
+
};
|
|
2691
|
+
const handleBlur = () => {
|
|
2692
|
+
if (Date.now() - mountTimeRef.current < 200) return;
|
|
2693
|
+
commit();
|
|
2694
|
+
};
|
|
2695
|
+
return /* @__PURE__ */ jsx("input", {
|
|
2696
|
+
ref: inputRef,
|
|
2697
|
+
value,
|
|
2698
|
+
onChange: (e) => setValue(e.target.value),
|
|
2699
|
+
onKeyDown: (e) => {
|
|
2700
|
+
e.stopPropagation();
|
|
2701
|
+
if (e.key === "Enter") {
|
|
2702
|
+
e.preventDefault();
|
|
2703
|
+
commit();
|
|
2704
|
+
}
|
|
2705
|
+
if (e.key === "Escape") {
|
|
2706
|
+
e.preventDefault();
|
|
2707
|
+
onCancel();
|
|
2708
|
+
}
|
|
2709
|
+
},
|
|
2710
|
+
onBlur: handleBlur,
|
|
2711
|
+
onClick: (e) => e.stopPropagation(),
|
|
2712
|
+
onDoubleClick: (e) => e.stopPropagation(),
|
|
2713
|
+
className: "flex-1 bg-background border border-primary rounded px-1 py-0 text-[13px] outline-none min-w-0"
|
|
2714
|
+
});
|
|
2715
|
+
};
|
|
2716
|
+
/**
|
|
2717
|
+
* TreeNodeList component renders a list of tree nodes with proper nesting and interaction
|
|
2718
|
+
*/
|
|
2719
|
+
const TreeNodeList = observer(({ nodes, treeModel, depth = 0, className }) => {
|
|
2720
|
+
const fileBrowserCtx = useFileBrowserContext();
|
|
2721
|
+
const renderNode = (node) => {
|
|
2722
|
+
const isSelected = treeModel.isNodeSelected(node.id);
|
|
2723
|
+
const isExpanded = treeModel.isNodeExpanded(node.id);
|
|
2724
|
+
const isFocused = treeModel.focusedNode === node.id;
|
|
2725
|
+
const isLoading = treeModel.isNodeLoading(node.id);
|
|
2726
|
+
const isDirectory = node.type === "directory";
|
|
2727
|
+
const hasChildren = node.hasChildren || node.children && node.children.length > 0;
|
|
2728
|
+
const showCheckbox = treeModel.provider.useCheckboxSelection;
|
|
2729
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
2730
|
+
const isRenaming = fileBrowserCtx?.renameState?.itemId === node.id && fileBrowserCtx?.renameState?.source === "tree";
|
|
2731
|
+
const selectionTheme = treeModel.provider.getSelectionTheme?.() || DEFAULT_SELECTION_THEME;
|
|
2732
|
+
const nodeClasses = cn(
|
|
2733
|
+
// Base styles
|
|
2734
|
+
"group flex items-center gap-2 py-1 px-2 text-[13px] cursor-default select-none transition-colors duration-150",
|
|
2735
|
+
"focus:outline-none",
|
|
2736
|
+
// Selection styling - must come before hover so selected state is visible
|
|
2737
|
+
isSelected ? "bg-accent text-accent-foreground" : "hover:bg-muted/50",
|
|
2738
|
+
// Focus styles
|
|
2739
|
+
isFocused && !isSelected && "ring-1 ring-primary/50"
|
|
2740
|
+
);
|
|
2741
|
+
const handleNodeClick = (event) => {
|
|
2742
|
+
event.stopPropagation();
|
|
2743
|
+
logger.interaction("TreeNode clicked", node.id, { name: node.name });
|
|
2744
|
+
if (event.ctrlKey || event.metaKey) {
|
|
2745
|
+
if (treeModel.provider.isMultiSelectEnabled) if (isSelected) treeModel.deselectNode(node);
|
|
2746
|
+
else treeModel.selectNode(node);
|
|
2747
|
+
} else if (!isSelected) treeModel.selectNode(node);
|
|
2748
|
+
};
|
|
2749
|
+
const handleExpandClick = (event) => {
|
|
2750
|
+
event.stopPropagation();
|
|
2751
|
+
logger.interaction("TreeNode expand/collapse clicked", node.id, { name: node.name });
|
|
2752
|
+
if (hasChildren) treeModel.toggleExpansion(node);
|
|
2753
|
+
};
|
|
2754
|
+
const handleContextMenu = (event) => {
|
|
2755
|
+
event.preventDefault();
|
|
2756
|
+
event.stopPropagation();
|
|
2757
|
+
logger.interaction("TreeNode context menu", node.id, { name: node.name });
|
|
2758
|
+
if (!isSelected && treeModel.selectedNodesArray.length <= 1) treeModel.selectNode(node);
|
|
2759
|
+
const rect = {
|
|
2760
|
+
x: event.clientX,
|
|
2761
|
+
y: event.clientY
|
|
2762
|
+
};
|
|
2763
|
+
const selectedNodes = treeModel.selectedNodesArray;
|
|
2764
|
+
if (selectedNodes.length > 1 && isSelected) treeModel.showMultiNodeContextMenu(selectedNodes, event.nativeEvent);
|
|
2765
|
+
else treeModel.showContextMenu(node, event.nativeEvent);
|
|
2766
|
+
};
|
|
2767
|
+
const handleMenuButtonClick = (event) => {
|
|
2768
|
+
event.stopPropagation();
|
|
2769
|
+
logger.interaction("TreeNode menu button clicked", node.id, { name: node.name });
|
|
2770
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
2771
|
+
const fakeEvent = new MouseEvent("contextmenu", {
|
|
2772
|
+
clientX: rect.right - 10,
|
|
2773
|
+
clientY: rect.bottom,
|
|
2774
|
+
bubbles: true,
|
|
2775
|
+
cancelable: true
|
|
2776
|
+
});
|
|
2777
|
+
const selectedNodes = treeModel.selectedNodesArray;
|
|
2778
|
+
if (selectedNodes.length > 1 && isSelected) treeModel.showMultiNodeContextMenu(selectedNodes, fakeEvent);
|
|
2779
|
+
else treeModel.showContextMenu(node, fakeEvent);
|
|
2780
|
+
};
|
|
2781
|
+
const iconSize = "w-4 h-4";
|
|
2782
|
+
const renderIcon = () => {
|
|
2783
|
+
const customIcon = treeModel.provider.getNodeIcon?.(node);
|
|
2784
|
+
if (customIcon) if (typeof customIcon === "string") return resolveIcon(customIcon, iconSize);
|
|
2785
|
+
else {
|
|
2786
|
+
const IconComponent = customIcon;
|
|
2787
|
+
return /* @__PURE__ */ jsx(IconComponent, { className: iconSize });
|
|
2788
|
+
}
|
|
2789
|
+
if (isDirectory) return resolveIcon(isExpanded ? "folder-open" : "folder", iconSize);
|
|
2790
|
+
else return resolveIcon("file", iconSize);
|
|
2791
|
+
};
|
|
2792
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2793
|
+
className: "w-full",
|
|
2794
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2795
|
+
className: nodeClasses,
|
|
2796
|
+
style: { paddingLeft: `${depth * 20 + 8}px` },
|
|
2797
|
+
onClick: handleNodeClick,
|
|
2798
|
+
onContextMenu: handleContextMenu,
|
|
2799
|
+
onMouseEnter: () => setIsHovered(true),
|
|
2800
|
+
onMouseLeave: () => setIsHovered(false),
|
|
2801
|
+
tabIndex: isFocused ? 0 : -1,
|
|
2802
|
+
role: "treeitem",
|
|
2803
|
+
"aria-expanded": hasChildren ? isExpanded : void 0,
|
|
2804
|
+
"aria-selected": isSelected,
|
|
2805
|
+
"aria-level": depth + 1,
|
|
2806
|
+
"aria-label": `${node.type === "directory" ? "Folder" : "File"}: ${node.name}`,
|
|
2807
|
+
children: [
|
|
2808
|
+
/* @__PURE__ */ jsx("div", {
|
|
2809
|
+
className: "flex-shrink-0 w-4 h-4",
|
|
2810
|
+
children: hasChildren && /* @__PURE__ */ jsx("button", {
|
|
2811
|
+
className: "w-4 h-4 flex items-center justify-center hover:bg-muted rounded-sm transition-colors",
|
|
2812
|
+
onClick: handleExpandClick,
|
|
2813
|
+
"aria-label": isExpanded ? "Collapse" : "Expand",
|
|
2814
|
+
children: isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "w-3 h-3 animate-spin" }) : isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3" })
|
|
2815
|
+
})
|
|
2816
|
+
}),
|
|
2817
|
+
showCheckbox && /* @__PURE__ */ jsx(TreeCheckbox, {
|
|
2818
|
+
state: treeModel.getCheckboxState(node.id),
|
|
2819
|
+
onChange: (newState) => {
|
|
2820
|
+
logger.interaction("TreeCheckbox changed", node.id, {
|
|
2821
|
+
name: node.name,
|
|
2822
|
+
newState
|
|
2823
|
+
});
|
|
2824
|
+
treeModel.handleCheckboxChange(node, newState);
|
|
2825
|
+
},
|
|
2826
|
+
nodeId: node.id
|
|
2827
|
+
}),
|
|
2828
|
+
/* @__PURE__ */ jsx("div", {
|
|
2829
|
+
className: "flex-shrink-0 w-4 h-4",
|
|
2830
|
+
children: renderIcon()
|
|
2831
|
+
}),
|
|
2832
|
+
isRenaming && fileBrowserCtx ? /* @__PURE__ */ jsx(TreeInlineRename, {
|
|
2833
|
+
currentName: fileBrowserCtx.renameState.currentName,
|
|
2834
|
+
onCommit: fileBrowserCtx.onRenameCommit,
|
|
2835
|
+
onCancel: fileBrowserCtx.onRenameCancel
|
|
2836
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
2837
|
+
className: "flex-1 truncate",
|
|
2838
|
+
children: node.name
|
|
2839
|
+
}),
|
|
2840
|
+
/* @__PURE__ */ jsx("button", {
|
|
2841
|
+
className: cn(
|
|
2842
|
+
"flex-shrink-0 w-6 h-6 flex items-center justify-center rounded hover:bg-muted/70 transition-colors cursor-pointer",
|
|
2843
|
+
// Show on hover or when node is selected
|
|
2844
|
+
"opacity-0 group-hover:opacity-100",
|
|
2845
|
+
(isSelected || isHovered) && "opacity-100"
|
|
2846
|
+
),
|
|
2847
|
+
onClick: handleMenuButtonClick,
|
|
2848
|
+
"aria-label": "Show context menu",
|
|
2849
|
+
children: /* @__PURE__ */ jsx(MoreVertical, { className: "w-4 h-4" })
|
|
2850
|
+
})
|
|
2851
|
+
]
|
|
2852
|
+
}), isExpanded && hasChildren && node.children && /* @__PURE__ */ jsx(TreeNodeList, {
|
|
2853
|
+
nodes: node.children,
|
|
2854
|
+
treeModel,
|
|
2855
|
+
depth: depth + 1,
|
|
2856
|
+
className
|
|
2857
|
+
})]
|
|
2858
|
+
}, node.id);
|
|
2859
|
+
};
|
|
2860
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2861
|
+
className: cn("w-full", className),
|
|
2862
|
+
children: nodes.map(renderNode)
|
|
2863
|
+
});
|
|
2864
|
+
});
|
|
2865
|
+
|
|
2866
|
+
//#endregion
|
|
2867
|
+
//#region src/tree/components/TreeContextMenu.tsx
|
|
2868
|
+
/**
|
|
2869
|
+
* TreeContextMenu - Context menu component for tree nodes
|
|
2870
|
+
*
|
|
2871
|
+
* Features:
|
|
2872
|
+
* - Renders context menu items with proper styling (no icons)
|
|
2873
|
+
* - Handles click outside to close
|
|
2874
|
+
* - Supports separators and disabled items
|
|
2875
|
+
* - Positioned absolutely at cursor position
|
|
2876
|
+
* - Keyboard navigation support
|
|
2877
|
+
*/
|
|
2878
|
+
const TreeContextMenu = observer(({ items, position, visible, onClose, onItemClick, className = "" }) => {
|
|
2879
|
+
const menuRef = useRef(null);
|
|
2880
|
+
useEffect(() => {
|
|
2881
|
+
if (!visible) return;
|
|
2882
|
+
const handleClickOutside = (event) => {
|
|
2883
|
+
if (menuRef.current && !menuRef.current.contains(event.target)) onClose();
|
|
2884
|
+
};
|
|
2885
|
+
const handleEscape = (event) => {
|
|
2886
|
+
if (event.key === "Escape") onClose();
|
|
2887
|
+
};
|
|
2888
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
2889
|
+
document.addEventListener("keydown", handleEscape);
|
|
2890
|
+
return () => {
|
|
2891
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
2892
|
+
document.removeEventListener("keydown", handleEscape);
|
|
2893
|
+
};
|
|
2894
|
+
}, [visible, onClose]);
|
|
2895
|
+
const handleItemClick = (item, event) => {
|
|
2896
|
+
event.preventDefault();
|
|
2897
|
+
event.stopPropagation();
|
|
2898
|
+
if (item.disabled) return;
|
|
2899
|
+
onItemClick(item);
|
|
2900
|
+
onClose();
|
|
2901
|
+
};
|
|
2902
|
+
if (!visible) return null;
|
|
2903
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2904
|
+
ref: menuRef,
|
|
2905
|
+
className: `
|
|
2906
|
+
bg-popover text-popover-foreground rounded-md border p-1 shadow-md min-w-[160px]
|
|
2907
|
+
fixed z-[100]
|
|
2908
|
+
${className}
|
|
2909
|
+
`,
|
|
2910
|
+
style: {
|
|
2911
|
+
left: position.x,
|
|
2912
|
+
top: position.y
|
|
2913
|
+
},
|
|
2914
|
+
role: "menu",
|
|
2915
|
+
"aria-label": "Context menu",
|
|
2916
|
+
children: items.map((item, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
2917
|
+
className: `
|
|
2918
|
+
px-2 py-1.5 text-sm cursor-default hover:bg-accent hover:text-accent-foreground
|
|
2919
|
+
flex items-center rounded-sm transition-colors duration-150
|
|
2920
|
+
${item.disabled ? "opacity-50 cursor-not-allowed" : ""}
|
|
2921
|
+
`,
|
|
2922
|
+
onClick: (e) => handleItemClick(item, e),
|
|
2923
|
+
role: "menuitem",
|
|
2924
|
+
tabIndex: item.disabled ? -1 : 0,
|
|
2925
|
+
"aria-disabled": item.disabled,
|
|
2926
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
2927
|
+
className: "flex-1",
|
|
2928
|
+
children: item.label
|
|
2929
|
+
})
|
|
2930
|
+
}), item.separator && index < items.length - 1 && /* @__PURE__ */ jsx("div", {
|
|
2931
|
+
className: "h-px bg-border my-1",
|
|
2932
|
+
role: "separator"
|
|
2933
|
+
})] }, item.id))
|
|
2934
|
+
});
|
|
2935
|
+
});
|
|
2936
|
+
TreeContextMenu.displayName = "TreeContextMenu";
|
|
2937
|
+
|
|
2938
|
+
//#endregion
|
|
2939
|
+
//#region src/tree/components/Tree.tsx
|
|
2940
|
+
/**
|
|
2941
|
+
* Tree Component - Reactive tree display with loading states
|
|
2942
|
+
*
|
|
2943
|
+
* This is the main tree component that:
|
|
2944
|
+
* - Creates and manages TreeModel
|
|
2945
|
+
* - Shows loading/error states
|
|
2946
|
+
* - Renders tree nodes
|
|
2947
|
+
* - Follows MobX observer pattern for reactivity
|
|
2948
|
+
* - Handles keyboard navigation
|
|
2949
|
+
*/
|
|
2950
|
+
const Tree = observer(({ provider, model, loadOptions, className }) => {
|
|
2951
|
+
const [internalModel] = useState(() => model || new TreeModel(provider));
|
|
2952
|
+
const treeModel = model || internalModel;
|
|
2953
|
+
const treeRef = useRef(null);
|
|
2954
|
+
useEffect(() => {
|
|
2955
|
+
if (!model) treeModel.loadNodes(loadOptions);
|
|
2956
|
+
}, [
|
|
2957
|
+
treeModel,
|
|
2958
|
+
loadOptions,
|
|
2959
|
+
model
|
|
2960
|
+
]);
|
|
2961
|
+
const getFlattenedNodes = () => {
|
|
2962
|
+
const result = [];
|
|
2963
|
+
const processNodes = (nodes) => {
|
|
2964
|
+
for (const node of nodes) {
|
|
2965
|
+
result.push(node);
|
|
2966
|
+
if (treeModel.isNodeExpanded(node.id) && node.children && node.children.length > 0) processNodes(node.children);
|
|
2967
|
+
}
|
|
2968
|
+
};
|
|
2969
|
+
processNodes(treeModel.nodes);
|
|
2970
|
+
return result;
|
|
2971
|
+
};
|
|
2972
|
+
const navigateToNode = (targetIndex) => {
|
|
2973
|
+
const flatNodes = getFlattenedNodes();
|
|
2974
|
+
if (targetIndex >= 0 && targetIndex < flatNodes.length) {
|
|
2975
|
+
const targetNode = flatNodes[targetIndex];
|
|
2976
|
+
if (targetNode) {
|
|
2977
|
+
treeModel.focusedNode = targetNode.id;
|
|
2978
|
+
treeModel.selectNode(targetNode);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
};
|
|
2982
|
+
const getCurrentNodeIndex = () => {
|
|
2983
|
+
if (!treeModel.focusedNode) return -1;
|
|
2984
|
+
const flatNodes = getFlattenedNodes();
|
|
2985
|
+
return flatNodes.findIndex((node) => node.id === treeModel.focusedNode);
|
|
2986
|
+
};
|
|
2987
|
+
const getParentNode = (nodeId) => {
|
|
2988
|
+
const findParent = (nodes, targetId, parentNode = null) => {
|
|
2989
|
+
for (const node of nodes) {
|
|
2990
|
+
if (node.id === targetId) return parentNode;
|
|
2991
|
+
if (node.children && node.children.length > 0) {
|
|
2992
|
+
const parent = findParent(node.children, targetId, node);
|
|
2993
|
+
if (parent) return parent;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
return null;
|
|
2997
|
+
};
|
|
2998
|
+
return findParent(treeModel.nodes, nodeId);
|
|
2999
|
+
};
|
|
3000
|
+
useEffect(() => {
|
|
3001
|
+
const handleKeyDown = (event) => {
|
|
3002
|
+
if (!treeRef.current?.contains(document.activeElement) && !treeModel.focusedNode) return;
|
|
3003
|
+
const currentIndex = getCurrentNodeIndex();
|
|
3004
|
+
const flatNodes = getFlattenedNodes();
|
|
3005
|
+
const currentNode = treeModel.focusedNode ? treeModel.nodeMap.get(treeModel.focusedNode) : null;
|
|
3006
|
+
switch (event.key) {
|
|
3007
|
+
case "ArrowDown":
|
|
3008
|
+
event.preventDefault();
|
|
3009
|
+
if (currentIndex < flatNodes.length - 1) navigateToNode(currentIndex + 1);
|
|
3010
|
+
break;
|
|
3011
|
+
case "ArrowUp":
|
|
3012
|
+
event.preventDefault();
|
|
3013
|
+
if (currentIndex > 0) navigateToNode(currentIndex - 1);
|
|
3014
|
+
break;
|
|
3015
|
+
case "ArrowRight":
|
|
3016
|
+
event.preventDefault();
|
|
3017
|
+
if (currentNode) {
|
|
3018
|
+
if (currentNode.hasChildren || currentNode.children && currentNode.children.length > 0) {
|
|
3019
|
+
if (!treeModel.isNodeExpanded(currentNode.id)) treeModel.expandNode(currentNode);
|
|
3020
|
+
else if (currentNode.children && currentNode.children.length > 0) {
|
|
3021
|
+
const firstChild = currentNode.children[0];
|
|
3022
|
+
if (firstChild) {
|
|
3023
|
+
treeModel.focusedNode = firstChild.id;
|
|
3024
|
+
treeModel.selectNode(firstChild);
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
break;
|
|
3030
|
+
case "ArrowLeft":
|
|
3031
|
+
event.preventDefault();
|
|
3032
|
+
if (currentNode) if (treeModel.isNodeExpanded(currentNode.id) && (currentNode.hasChildren || currentNode.children && currentNode.children.length > 0)) treeModel.collapseNode(currentNode);
|
|
3033
|
+
else {
|
|
3034
|
+
const parentNode = getParentNode(currentNode.id);
|
|
3035
|
+
if (parentNode) {
|
|
3036
|
+
treeModel.focusedNode = parentNode.id;
|
|
3037
|
+
treeModel.selectNode(parentNode);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
break;
|
|
3041
|
+
case "Home":
|
|
3042
|
+
event.preventDefault();
|
|
3043
|
+
if (flatNodes.length > 0) navigateToNode(0);
|
|
3044
|
+
break;
|
|
3045
|
+
case "End":
|
|
3046
|
+
event.preventDefault();
|
|
3047
|
+
if (flatNodes.length > 0) navigateToNode(flatNodes.length - 1);
|
|
3048
|
+
break;
|
|
3049
|
+
case "Enter":
|
|
3050
|
+
case " ":
|
|
3051
|
+
event.preventDefault();
|
|
3052
|
+
if (currentNode) {
|
|
3053
|
+
if (currentNode.hasChildren || currentNode.children && currentNode.children.length > 0) treeModel.toggleExpansion(currentNode);
|
|
3054
|
+
}
|
|
3055
|
+
break;
|
|
3056
|
+
case "a":
|
|
3057
|
+
if ((event.ctrlKey || event.metaKey) && treeModel.provider.isMultiSelectEnabled) {
|
|
3058
|
+
event.preventDefault();
|
|
3059
|
+
treeModel.selectAll();
|
|
3060
|
+
}
|
|
3061
|
+
break;
|
|
3062
|
+
case "Escape":
|
|
3063
|
+
event.preventDefault();
|
|
3064
|
+
treeModel.clearSelection();
|
|
3065
|
+
treeModel.hideContextMenu();
|
|
3066
|
+
break;
|
|
3067
|
+
default: break;
|
|
3068
|
+
}
|
|
3069
|
+
};
|
|
3070
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
3071
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
3072
|
+
}, [treeModel]);
|
|
3073
|
+
useEffect(() => {
|
|
3074
|
+
if (!treeModel.focusedNode && treeModel.hasNodes && treeModel.nodes.length > 0) {
|
|
3075
|
+
const firstNode = treeModel.nodes[0];
|
|
3076
|
+
if (firstNode) treeModel.focusedNode = firstNode.id;
|
|
3077
|
+
}
|
|
3078
|
+
}, [
|
|
3079
|
+
treeModel.hasNodes,
|
|
3080
|
+
treeModel.nodes.length,
|
|
3081
|
+
treeModel.focusedNode
|
|
3082
|
+
]);
|
|
3083
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3084
|
+
ref: treeRef,
|
|
3085
|
+
className: `h-full overflow-hidden focus-within:outline-none ${className || ""}`,
|
|
3086
|
+
tabIndex: 0,
|
|
3087
|
+
role: "tree",
|
|
3088
|
+
"aria-label": "File tree",
|
|
3089
|
+
children: [
|
|
3090
|
+
treeModel.isLoading && /* @__PURE__ */ jsx("div", {
|
|
3091
|
+
className: "p-4 text-center text-muted-foreground",
|
|
3092
|
+
role: "status",
|
|
3093
|
+
"aria-live": "polite",
|
|
3094
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
3095
|
+
className: "inline-flex items-center gap-2",
|
|
3096
|
+
children: [/* @__PURE__ */ jsx("div", { className: "w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ jsx("span", { children: "Loading tree..." })]
|
|
3097
|
+
})
|
|
3098
|
+
}),
|
|
3099
|
+
!treeModel.isLoading && treeModel.errors.size > 0 && /* @__PURE__ */ jsx("div", {
|
|
3100
|
+
className: "p-4 text-red-600 bg-red-50 border-l-4 border-red-400",
|
|
3101
|
+
role: "alert",
|
|
3102
|
+
children: Array.from(treeModel.errors.entries()).map(([key, error]) => /* @__PURE__ */ jsxs("div", {
|
|
3103
|
+
className: "flex items-start gap-2",
|
|
3104
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3105
|
+
className: "font-semibold",
|
|
3106
|
+
children: "Error:"
|
|
3107
|
+
}), /* @__PURE__ */ jsx("span", { children: error.message })]
|
|
3108
|
+
}, key))
|
|
3109
|
+
}),
|
|
3110
|
+
!treeModel.isLoading && treeModel.errors.size === 0 && !treeModel.hasNodes && /* @__PURE__ */ jsx("div", {
|
|
3111
|
+
className: "p-4 text-center text-muted-foreground",
|
|
3112
|
+
role: "status",
|
|
3113
|
+
children: /* @__PURE__ */ jsx("span", { children: "No items to display" })
|
|
3114
|
+
}),
|
|
3115
|
+
!treeModel.isLoading && treeModel.hasNodes && /* @__PURE__ */ jsx("div", {
|
|
3116
|
+
className: "h-full overflow-auto",
|
|
3117
|
+
children: /* @__PURE__ */ jsx(TreeNodeList, {
|
|
3118
|
+
treeModel,
|
|
3119
|
+
nodes: treeModel.nodes,
|
|
3120
|
+
depth: 0
|
|
3121
|
+
})
|
|
3122
|
+
}),
|
|
3123
|
+
/* @__PURE__ */ jsx(TreeContextMenu, {
|
|
3124
|
+
items: treeModel.contextMenuItems,
|
|
3125
|
+
position: treeModel.contextMenuPosition,
|
|
3126
|
+
visible: treeModel.contextMenuVisible,
|
|
3127
|
+
onClose: () => treeModel.hideContextMenu(),
|
|
3128
|
+
onItemClick: (menuItem) => treeModel.handleContextMenuAction(menuItem)
|
|
3129
|
+
})
|
|
3130
|
+
]
|
|
3131
|
+
});
|
|
3132
|
+
});
|
|
3133
|
+
|
|
3134
|
+
//#endregion
|
|
3135
|
+
//#region src/tree/components/TreeTable.tsx
|
|
3136
|
+
/**
|
|
3137
|
+
* TreeTable Component - Table view of tree data
|
|
3138
|
+
*/
|
|
3139
|
+
const TreeTable = observer(({ treeModel, columns: propColumns, enableAnimations = true, className, enableExport = true, enableFiltering = false, enableSorting = true }) => {
|
|
3140
|
+
const [sortColumn, setSortColumn] = useState(null);
|
|
3141
|
+
const [sortDirection, setSortDirection] = useState("asc");
|
|
3142
|
+
const [filters, setFilters] = useState({});
|
|
3143
|
+
const defaultColumns = useMemo(() => [{
|
|
3144
|
+
id: "name",
|
|
3145
|
+
label: "Name",
|
|
3146
|
+
dataKey: "name",
|
|
3147
|
+
width: "100%",
|
|
3148
|
+
sortable: true,
|
|
3149
|
+
filterable: true,
|
|
3150
|
+
isTreeColumn: true
|
|
3151
|
+
}], []);
|
|
3152
|
+
const columns = propColumns || defaultColumns;
|
|
3153
|
+
const isHeaderVisible = columns.length > 1 || enableFiltering || enableExport;
|
|
3154
|
+
const tableData = useMemo(() => {
|
|
3155
|
+
const flattenNode = (node, level = 0) => {
|
|
3156
|
+
const result = [{
|
|
3157
|
+
node,
|
|
3158
|
+
level
|
|
3159
|
+
}];
|
|
3160
|
+
if (treeModel.isNodeExpanded(node.id) && node.children) for (const child of node.children) result.push(...flattenNode(child, level + 1));
|
|
3161
|
+
return result;
|
|
3162
|
+
};
|
|
3163
|
+
let data = [];
|
|
3164
|
+
const rootNodes = treeModel.nodes;
|
|
3165
|
+
for (const node of rootNodes) data.push(...flattenNode(node));
|
|
3166
|
+
if (Object.values(filters).some((f) => f.trim())) data = data.filter(({ node }) => {
|
|
3167
|
+
return Object.entries(filters).every(([columnKey, filterValue]) => {
|
|
3168
|
+
if (!filterValue.trim()) return true;
|
|
3169
|
+
const column = columns.find((c) => c.id === columnKey);
|
|
3170
|
+
if (!column) return true;
|
|
3171
|
+
let cellValue = "";
|
|
3172
|
+
if (column.renderCell) {
|
|
3173
|
+
const rendered = column.renderCell(node[column.dataKey], node);
|
|
3174
|
+
cellValue = typeof rendered === "string" ? rendered : String(rendered || "");
|
|
3175
|
+
} else if (column.formatValue) cellValue = column.formatValue(node[column.dataKey], node);
|
|
3176
|
+
else cellValue = node[column.dataKey]?.toString() || "";
|
|
3177
|
+
return cellValue.toLowerCase().includes(filterValue.toLowerCase());
|
|
3178
|
+
});
|
|
3179
|
+
});
|
|
3180
|
+
if (sortColumn) {
|
|
3181
|
+
const column = columns.find((c) => c.id === sortColumn);
|
|
3182
|
+
if (column) data.sort((a, b) => {
|
|
3183
|
+
let aValue = "";
|
|
3184
|
+
let bValue = "";
|
|
3185
|
+
if (column.formatValue) {
|
|
3186
|
+
aValue = column.formatValue(a.node[column.dataKey], a.node);
|
|
3187
|
+
bValue = column.formatValue(b.node[column.dataKey], b.node);
|
|
3188
|
+
} else {
|
|
3189
|
+
aValue = a.node[column.dataKey]?.toString() || "";
|
|
3190
|
+
bValue = b.node[column.dataKey]?.toString() || "";
|
|
3191
|
+
}
|
|
3192
|
+
const compareResult = aValue.localeCompare(bValue);
|
|
3193
|
+
return sortDirection === "asc" ? compareResult : -compareResult;
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
return data;
|
|
3197
|
+
}, [
|
|
3198
|
+
treeModel.nodes,
|
|
3199
|
+
sortColumn,
|
|
3200
|
+
sortDirection,
|
|
3201
|
+
filters,
|
|
3202
|
+
columns,
|
|
3203
|
+
treeModel
|
|
3204
|
+
]);
|
|
3205
|
+
const handleSort = useCallback((columnKey) => {
|
|
3206
|
+
if (sortColumn === columnKey) setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
|
|
3207
|
+
else {
|
|
3208
|
+
setSortColumn(columnKey);
|
|
3209
|
+
setSortDirection("asc");
|
|
3210
|
+
}
|
|
3211
|
+
}, [sortColumn]);
|
|
3212
|
+
const handleExport = useCallback(() => {
|
|
3213
|
+
const csvContent = [columns.map((col) => col.label).join(","), ...tableData.map(({ node }) => columns.map((col) => {
|
|
3214
|
+
let value = "";
|
|
3215
|
+
if (col.formatValue) value = col.formatValue(node[col.dataKey], node);
|
|
3216
|
+
else value = node[col.dataKey]?.toString() || "";
|
|
3217
|
+
return `"${value.replace(/"/g, "\"\"")}"`;
|
|
3218
|
+
}).join(","))].join("\n");
|
|
3219
|
+
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
3220
|
+
const link = document.createElement("a");
|
|
3221
|
+
link.href = URL.createObjectURL(blob);
|
|
3222
|
+
link.download = "tree-data.csv";
|
|
3223
|
+
link.click();
|
|
3224
|
+
}, [tableData, columns]);
|
|
3225
|
+
const selectionTheme = treeModel.provider.getSelectionTheme?.() || DEFAULT_SELECTION_THEME;
|
|
3226
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3227
|
+
className: cn("flex flex-col h-full", className),
|
|
3228
|
+
children: [isHeaderVisible && /* @__PURE__ */ jsx("div", {
|
|
3229
|
+
className: "flex-shrink-0 p-3 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60",
|
|
3230
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
3231
|
+
className: "flex items-center justify-between gap-4",
|
|
3232
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3233
|
+
className: "flex items-center gap-2",
|
|
3234
|
+
children: enableFiltering && /* @__PURE__ */ jsxs("div", {
|
|
3235
|
+
className: "flex items-center gap-2",
|
|
3236
|
+
children: [
|
|
3237
|
+
/* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-muted-foreground" }),
|
|
3238
|
+
/* @__PURE__ */ jsx("span", {
|
|
3239
|
+
className: "text-sm text-muted-foreground",
|
|
3240
|
+
children: "Filter:"
|
|
3241
|
+
}),
|
|
3242
|
+
columns.filter((c) => c.filterable).map((column) => /* @__PURE__ */ jsx("input", {
|
|
3243
|
+
type: "text",
|
|
3244
|
+
placeholder: `Filter ${column.label}`,
|
|
3245
|
+
value: filters[column.id] || "",
|
|
3246
|
+
onChange: (e) => setFilters((prev) => ({
|
|
3247
|
+
...prev,
|
|
3248
|
+
[column.id]: e.target.value
|
|
3249
|
+
})),
|
|
3250
|
+
className: "px-2 py-1 text-sm border rounded-md w-32"
|
|
3251
|
+
}, column.id))
|
|
3252
|
+
]
|
|
3253
|
+
})
|
|
3254
|
+
}), enableExport && /* @__PURE__ */ jsxs("button", {
|
|
3255
|
+
onClick: handleExport,
|
|
3256
|
+
className: "flex items-center gap-2 px-3 py-1 text-sm bg-primary text-primary-foreground rounded-md hover:bg-primary/90",
|
|
3257
|
+
children: [/* @__PURE__ */ jsx(Download, { className: "w-4 h-4" }), "Export CSV"]
|
|
3258
|
+
})]
|
|
3259
|
+
})
|
|
3260
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3261
|
+
className: "flex-1 overflow-auto",
|
|
3262
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
3263
|
+
className: "min-w-full",
|
|
3264
|
+
children: [columns.length > 1 && /* @__PURE__ */ jsx("div", {
|
|
3265
|
+
className: "sticky top-0 bg-background border-b z-10",
|
|
3266
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
3267
|
+
className: "flex",
|
|
3268
|
+
children: columns.map((column) => /* @__PURE__ */ jsxs("div", {
|
|
3269
|
+
className: cn("px-2 py-2 text-sm font-medium text-muted-foreground", column.sortable && enableSorting && "cursor-pointer hover:text-foreground", "flex items-center gap-1"),
|
|
3270
|
+
style: { width: column.width },
|
|
3271
|
+
onClick: () => column.sortable && enableSorting && handleSort(column.id),
|
|
3272
|
+
children: [/* @__PURE__ */ jsx("span", { children: column.label }), column.sortable && enableSorting && /* @__PURE__ */ jsx("div", {
|
|
3273
|
+
className: "ml-auto",
|
|
3274
|
+
children: sortColumn === column.id ? sortDirection === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "w-3 h-3 opacity-50" })
|
|
3275
|
+
})]
|
|
3276
|
+
}, column.id))
|
|
3277
|
+
})
|
|
3278
|
+
}), /* @__PURE__ */ jsx("div", { children: tableData.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
3279
|
+
className: "p-8 text-center",
|
|
3280
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
3281
|
+
className: "text-sm text-muted-foreground",
|
|
3282
|
+
children: Object.values(filters).some((f) => f.trim()) ? "No results found for current filters" : "No data available"
|
|
3283
|
+
})
|
|
3284
|
+
}) : tableData.map(({ node, level }) => {
|
|
3285
|
+
const isSelected = treeModel.isNodeSelected(node.id);
|
|
3286
|
+
const isFocused = treeModel.focusedNode === node.id;
|
|
3287
|
+
const isExpanded = treeModel.isNodeExpanded(node.id);
|
|
3288
|
+
const hasChildren = node.hasChildren || node.children && node.children.length > 0;
|
|
3289
|
+
const useCheckboxes = treeModel.provider.useCheckboxSelection;
|
|
3290
|
+
const customColorVars = getCustomColorVariables(selectionTheme);
|
|
3291
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3292
|
+
className: cn("flex w-full cursor-pointer group", getSelectionClasses(selectionTheme, isSelected, isFocused, !!useCheckboxes), useCheckboxes && "select-none"),
|
|
3293
|
+
style: customColorVars,
|
|
3294
|
+
onClick: (event) => {
|
|
3295
|
+
if (useCheckboxes) return;
|
|
3296
|
+
const isMultiSelectKeyPressed = event.ctrlKey || event.metaKey;
|
|
3297
|
+
if (treeModel.provider.isMultiSelectEnabled && isMultiSelectKeyPressed) if (isSelected) treeModel.deselectNode(node);
|
|
3298
|
+
else treeModel.selectNode(node);
|
|
3299
|
+
else if (treeModel.provider.isMultiSelectEnabled) {
|
|
3300
|
+
treeModel.clearSelection();
|
|
3301
|
+
treeModel.selectNode(node);
|
|
3302
|
+
} else if (isSelected) treeModel.clearSelection();
|
|
3303
|
+
else treeModel.selectNode(node);
|
|
3304
|
+
},
|
|
3305
|
+
onDoubleClick: () => hasChildren && treeModel.toggleExpansion(node),
|
|
3306
|
+
children: columns.map((column, columnIndex) => {
|
|
3307
|
+
let cellContent = "";
|
|
3308
|
+
if (column.renderCell) cellContent = column.renderCell(node[column.dataKey], node);
|
|
3309
|
+
else if (column.formatValue) cellContent = column.formatValue(node[column.dataKey], node);
|
|
3310
|
+
else cellContent = node[column.dataKey]?.toString() || "";
|
|
3311
|
+
if (column.isTreeColumn) return /* @__PURE__ */ jsxs("div", {
|
|
3312
|
+
className: "flex items-center px-2 py-1 text-sm",
|
|
3313
|
+
style: { width: column.width },
|
|
3314
|
+
children: [
|
|
3315
|
+
/* @__PURE__ */ jsx("div", {
|
|
3316
|
+
style: { width: level * 16 },
|
|
3317
|
+
className: "flex-shrink-0"
|
|
3318
|
+
}),
|
|
3319
|
+
/* @__PURE__ */ jsx("div", {
|
|
3320
|
+
className: "flex-shrink-0 w-4 h-4 mr-1",
|
|
3321
|
+
children: hasChildren && /* @__PURE__ */ jsx("button", {
|
|
3322
|
+
onClick: (e) => {
|
|
3323
|
+
e.stopPropagation();
|
|
3324
|
+
treeModel.toggleExpansion(node);
|
|
3325
|
+
},
|
|
3326
|
+
className: "w-4 h-4 flex items-center justify-center transition-transform duration-200 hover:bg-accent/20",
|
|
3327
|
+
children: isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3" })
|
|
3328
|
+
})
|
|
3329
|
+
}),
|
|
3330
|
+
/* @__PURE__ */ jsx("span", {
|
|
3331
|
+
className: "flex-1 truncate",
|
|
3332
|
+
children: cellContent
|
|
3333
|
+
})
|
|
3334
|
+
]
|
|
3335
|
+
}, column.id);
|
|
3336
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3337
|
+
className: "px-2 py-1 text-sm truncate",
|
|
3338
|
+
style: { width: column.width },
|
|
3339
|
+
children: cellContent
|
|
3340
|
+
}, column.id);
|
|
3341
|
+
})
|
|
3342
|
+
}, node.id);
|
|
3343
|
+
}) })]
|
|
3344
|
+
})
|
|
3345
|
+
})]
|
|
3346
|
+
});
|
|
3347
|
+
});
|
|
3348
|
+
|
|
3349
|
+
//#endregion
|
|
3350
|
+
export { DEFAULT_SELECTION_THEME, SimpleTreeProvider, TestTreeProvider, Tree, TreeCheckbox, TreeContextMenu, TreeModel, TreeNodeList, TreeTable, getCustomColorVariables, getSelectionClasses, logger };
|
|
3351
|
+
//# sourceMappingURL=tree-Dd9Z0Aso.js.map
|