@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,530 @@
|
|
|
1
|
+
# TreeComponent Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The TreeComponent is a reusable, high-performance tree component built with React, MobX, and TypeScript. It provides a flexible, accessible, and extensible tree interface that can work with any data source through the Provider pattern. The provider can work with or without React, supporting both web and non-web environments.
|
|
6
|
+
|
|
7
|
+
**🔄 Migration Status**: This TreeComponent is designed to replace the existing FileBrowser TreeView components. Once fully implemented and tested, it will replace the legacy tree components in the FileBrowser system, providing better performance, accessibility, and maintainability.
|
|
8
|
+
|
|
9
|
+
## Design Principles
|
|
10
|
+
|
|
11
|
+
- **Provider-Driven Architecture**: TreeProvider model drives ALL behavior (selection, drag-drop, virtualization)
|
|
12
|
+
- **Direct Provider Injection**: No registry pattern - provider passed directly to Tree component
|
|
13
|
+
- **MobX Best Practices**:
|
|
14
|
+
- Use `makeAutoObservable(this, {})` in constructors (NO decorators)
|
|
15
|
+
- Use `flow` for async actions
|
|
16
|
+
- Use `observable.map()` instead of plain Map for reactive collections
|
|
17
|
+
- Use composition over inheritance
|
|
18
|
+
- **All State in Provider**: multiSelect, dragDrop, virtualization configured in provider
|
|
19
|
+
- **Callback-Based**: Tree component calls provider callbacks for all interactions
|
|
20
|
+
- **Node-Level Customization**: Per-node context menus and render overrides
|
|
21
|
+
- **Environment Agnostic**: Provider works with and without React
|
|
22
|
+
- **Component Composition**: Small, focused components for maintainability
|
|
23
|
+
- **Accessibility First**: Full ARIA support for screen readers and keyboard navigation
|
|
24
|
+
- **Performance**: Virtualization support for large trees
|
|
25
|
+
- **Type Safety**: Comprehensive TypeScript coverage
|
|
26
|
+
|
|
27
|
+
## MobX Best Practices (CRITICAL)
|
|
28
|
+
|
|
29
|
+
**⚠️ STRICT REQUIREMENTS - These patterns must be followed exactly:**
|
|
30
|
+
|
|
31
|
+
### 1. No Decorators - Use makeAutoObservable
|
|
32
|
+
```typescript
|
|
33
|
+
// ❌ NEVER use decorators
|
|
34
|
+
class TreeModel {
|
|
35
|
+
@observable nodes = []
|
|
36
|
+
@action selectNode() {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ✅ ALWAYS use makeAutoObservable
|
|
40
|
+
class TreeModel {
|
|
41
|
+
nodes = []
|
|
42
|
+
|
|
43
|
+
constructor() {
|
|
44
|
+
makeAutoObservable(this, {}); // Empty overrides object
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
selectNode() {} // Automatically becomes action
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Observable Collections - Use observable.map()
|
|
52
|
+
```typescript
|
|
53
|
+
// ❌ NEVER use plain Map/Set
|
|
54
|
+
selectedNodes = new Map<string, TreeNodeData>()
|
|
55
|
+
expandedNodes = new Set<string>()
|
|
56
|
+
|
|
57
|
+
// ✅ ALWAYS use observable.map()
|
|
58
|
+
selectedNodes = observable.map<string, TreeNodeData>()
|
|
59
|
+
expandedNodes = observable.map<string, boolean>()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Async Actions - Use flow
|
|
63
|
+
```typescript
|
|
64
|
+
// ❌ NEVER use async/await directly in actions
|
|
65
|
+
async loadNodes() {
|
|
66
|
+
this.isLoading = true;
|
|
67
|
+
const result = await api.loadNodes();
|
|
68
|
+
this.nodes = result; // ⚠️ Not in action!
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ✅ ALWAYS use flow for async operations
|
|
72
|
+
loadNodes = flow(function* (this: TreeModel) {
|
|
73
|
+
this.isLoading = true;
|
|
74
|
+
const result = yield api.loadNodes();
|
|
75
|
+
this.nodes = result; // ✅ Automatically in action
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 4. Composition Over Inheritance
|
|
80
|
+
```typescript
|
|
81
|
+
// ❌ NEVER use inheritance (MobX issues)
|
|
82
|
+
class BaseModel {}
|
|
83
|
+
class TreeModel extends BaseModel {}
|
|
84
|
+
|
|
85
|
+
// ✅ ALWAYS use composition
|
|
86
|
+
class TreeModel {
|
|
87
|
+
selectionManager: TreeSelectionModel;
|
|
88
|
+
expansionManager: TreeExpansionModel;
|
|
89
|
+
|
|
90
|
+
constructor() {
|
|
91
|
+
makeAutoObservable(this, {});
|
|
92
|
+
this.selectionManager = new TreeSelectionModel(this);
|
|
93
|
+
this.expansionManager = new TreeExpansionModel(this);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 5. Computed Properties for Performance
|
|
99
|
+
```typescript
|
|
100
|
+
class TreeModel {
|
|
101
|
+
selectedNodes = observable.map<string, TreeNodeData>()
|
|
102
|
+
|
|
103
|
+
constructor() {
|
|
104
|
+
makeAutoObservable(this, {});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ✅ Computed properties are automatically optimized
|
|
108
|
+
get selectedNodesArray() {
|
|
109
|
+
return Array.from(this.selectedNodes.values());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get hasSelection() {
|
|
113
|
+
return this.selectedNodes.size > 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 6. Observer Components for Lists
|
|
119
|
+
```typescript
|
|
120
|
+
// ❌ NEVER render observables directly in map
|
|
121
|
+
const NodeList = observer(() => (
|
|
122
|
+
<div>
|
|
123
|
+
{nodes.map(node => <div key={node.id}>{node.name}</div>)} {/* Won't react! */}
|
|
124
|
+
</div>
|
|
125
|
+
))
|
|
126
|
+
|
|
127
|
+
// ✅ ALWAYS extract to separate observer components
|
|
128
|
+
const NodeItem = observer(({ node }) => <div>{node.name}</div>)
|
|
129
|
+
|
|
130
|
+
const NodeList = observer(() => (
|
|
131
|
+
<div>
|
|
132
|
+
{nodes.map(node => <NodeItem key={node.id} node={node} />)}
|
|
133
|
+
</div>
|
|
134
|
+
))
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Architecture Overview
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
TreeComponent
|
|
141
|
+
↓ receives
|
|
142
|
+
TreeProvider (MobX Model)
|
|
143
|
+
↓ provides data & callbacks
|
|
144
|
+
TreeModel (MobX State)
|
|
145
|
+
↓ manages
|
|
146
|
+
Tree Components (React UI)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Core Components
|
|
150
|
+
|
|
151
|
+
### 1. TreeProvider (Model Layer)
|
|
152
|
+
**Location**: `/providers/TreeProvider.ts`
|
|
153
|
+
|
|
154
|
+
**Purpose**: Defines the contract for data loading, node rendering, and interaction callbacks. Works independently of React.
|
|
155
|
+
|
|
156
|
+
**Key Features**:
|
|
157
|
+
- Data loading methods (`loadNodes`, `loadChildren`, `refresh`, `search`)
|
|
158
|
+
- Per-node context menus (`getNodeContextMenu`, `getMultiNodeContextMenu`)
|
|
159
|
+
- Node rendering overrides (`getNodeRenderer`, `getDefaultRenderer`)
|
|
160
|
+
- Capability checks (`canExpand`, `canSelect`, `canDrag`, `canDrop`)
|
|
161
|
+
- Event callbacks (`onSelectionChange`, `onDragDrop`, `onContextMenuAction`)
|
|
162
|
+
- Non-React node rendering (`TreeNodeRenderer.renderText`, `renderIcon`)
|
|
163
|
+
|
|
164
|
+
### 2. TreeModel (State Layer)
|
|
165
|
+
**Location**: `/models/TreeModel.ts`
|
|
166
|
+
|
|
167
|
+
**Purpose**: MobX observable state container that manages tree data, selection, expansion, and UI state.
|
|
168
|
+
|
|
169
|
+
**State Management**:
|
|
170
|
+
```typescript
|
|
171
|
+
class TreeModel {
|
|
172
|
+
// Core Data - using observable.map for reactive collections
|
|
173
|
+
nodes: TreeNodeData[] = []
|
|
174
|
+
nodeMap = observable.map<string, TreeNodeData>()
|
|
175
|
+
|
|
176
|
+
// UI State - using observable.map instead of Set/Map
|
|
177
|
+
selectedNodes = observable.map<string, TreeNodeData>()
|
|
178
|
+
expandedNodes = observable.map<string, boolean>()
|
|
179
|
+
focusedNode: string | null = null
|
|
180
|
+
|
|
181
|
+
// Loading State
|
|
182
|
+
isLoading: boolean = false
|
|
183
|
+
loadingNodes = observable.map<string, boolean>()
|
|
184
|
+
errors = observable.map<string, Error>()
|
|
185
|
+
|
|
186
|
+
// Interaction State
|
|
187
|
+
draggedNodes: TreeNodeData[] = []
|
|
188
|
+
dropTarget: TreeNodeData | null = null
|
|
189
|
+
contextMenuNode: TreeNodeData | null = null
|
|
190
|
+
|
|
191
|
+
constructor(public provider: TreeProvider) {
|
|
192
|
+
makeAutoObservable(this, {
|
|
193
|
+
// Most defaults are correct, empty overrides object
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Computed properties for derived state
|
|
198
|
+
get selectedNodesArray(): TreeNodeData[] {
|
|
199
|
+
return Array.from(this.selectedNodes.values());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
get hasSelection(): boolean {
|
|
203
|
+
return this.selectedNodes.size > 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
get isNodeSelected() {
|
|
207
|
+
return (nodeId: string) => this.selectedNodes.has(nodeId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Async actions using flow
|
|
211
|
+
loadNodes = flow(function* (this: TreeModel, options?: TreeLoadOptions) {
|
|
212
|
+
this.isLoading = true;
|
|
213
|
+
this.errors.clear();
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const result = yield this.provider.loadNodes(options);
|
|
217
|
+
this.nodes = result.nodes;
|
|
218
|
+
|
|
219
|
+
// Update node map for fast lookups
|
|
220
|
+
this.nodeMap.clear();
|
|
221
|
+
result.nodes.forEach(node => this.nodeMap.set(node.id, node));
|
|
222
|
+
|
|
223
|
+
} catch (error) {
|
|
224
|
+
this.errors.set('loadNodes', error);
|
|
225
|
+
} finally {
|
|
226
|
+
this.isLoading = false;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 3. Tree Component (UI Layer)
|
|
233
|
+
**Location**: `/components/Tree.tsx`
|
|
234
|
+
|
|
235
|
+
**Purpose**: Main React component that receives TreeProvider as prop and renders the tree UI.
|
|
236
|
+
|
|
237
|
+
**Component Interface**:
|
|
238
|
+
```typescript
|
|
239
|
+
interface TreeProps {
|
|
240
|
+
provider: TreeProvider;
|
|
241
|
+
className?: string;
|
|
242
|
+
height?: number;
|
|
243
|
+
// All behavior controlled by provider - no separate props
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const Tree = observer(({ provider, className, height }: TreeProps) => {
|
|
247
|
+
const [model] = useState(() => new TreeModel(provider))
|
|
248
|
+
|
|
249
|
+
// All configuration comes from provider:
|
|
250
|
+
// - provider.isMultiSelectEnabled
|
|
251
|
+
// - provider.isDragDropEnabled
|
|
252
|
+
// - provider.isVirtualizationEnabled
|
|
253
|
+
// - provider.getVirtualizationConfig()
|
|
254
|
+
|
|
255
|
+
// Component delegates all actions to model,
|
|
256
|
+
// which calls provider callbacks
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## File & Folder Structure
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
fs-ui/src/TreeComponent/
|
|
264
|
+
├── architecture.md
|
|
265
|
+
├── providers/
|
|
266
|
+
│ └── TreeProvider.ts # Provider interface
|
|
267
|
+
├── models/
|
|
268
|
+
│ ├── TreeModel.ts # Main MobX state model
|
|
269
|
+
│ ├── TreeNode.ts # Individual node state
|
|
270
|
+
│ ├── TreeSelection.ts # Selection state management
|
|
271
|
+
│ ├── TreeExpansion.ts # Expansion state management
|
|
272
|
+
│ └── TreeLoading.ts # Loading state management
|
|
273
|
+
├── components/
|
|
274
|
+
│ ├── Tree.tsx # Main tree component
|
|
275
|
+
│ ├── TreeNode.tsx # Individual node component
|
|
276
|
+
│ ├── TreeNodeList.tsx # Node list container
|
|
277
|
+
│ ├── TreeContextMenu.tsx # Context menu component
|
|
278
|
+
│ └── shared/
|
|
279
|
+
│ ├── TreeIcon.tsx # Icon rendering
|
|
280
|
+
│ ├── TreeLoader.tsx # Loading indicators
|
|
281
|
+
│ └── TreeVirtualization.tsx # Virtual scrolling
|
|
282
|
+
├── renderers/
|
|
283
|
+
│ ├── DefaultNodeRenderer.ts # Default node rendering
|
|
284
|
+
│ ├── TextNodeRenderer.ts # Text-only rendering
|
|
285
|
+
│ └── CustomNodeRenderer.tsx # Custom React rendering
|
|
286
|
+
├── types/
|
|
287
|
+
│ ├── TreeTypes.ts # Core type definitions
|
|
288
|
+
│ ├── TreeEvents.ts # Event type definitions
|
|
289
|
+
│ └── TreeOptions.ts # Configuration types
|
|
290
|
+
├── utils/
|
|
291
|
+
│ ├── TreeUtils.ts # Tree manipulation utilities
|
|
292
|
+
│ ├── TreeKeyboard.ts # Keyboard navigation
|
|
293
|
+
│ └── TreeAccessibility.ts # ARIA utilities
|
|
294
|
+
└── index.ts # Public exports
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Data Flow
|
|
298
|
+
|
|
299
|
+
### 1. Initialization
|
|
300
|
+
```
|
|
301
|
+
1. Tree component receives TreeProvider
|
|
302
|
+
2. TreeModel created with provider
|
|
303
|
+
3. TreeModel calls provider.initialize()
|
|
304
|
+
4. TreeModel calls provider.loadNodes()
|
|
305
|
+
5. Tree renders with initial data
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### 2. User Interactions
|
|
309
|
+
```
|
|
310
|
+
User clicks node
|
|
311
|
+
↓
|
|
312
|
+
TreeNode component handles event
|
|
313
|
+
↓
|
|
314
|
+
Calls TreeModel.selectNode()
|
|
315
|
+
↓
|
|
316
|
+
TreeModel updates @observable selectedNodes
|
|
317
|
+
↓
|
|
318
|
+
TreeModel calls provider.onSelectionChange()
|
|
319
|
+
↓
|
|
320
|
+
UI re-renders via MobX observer
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 3. Context Menus
|
|
324
|
+
```
|
|
325
|
+
User right-clicks node
|
|
326
|
+
↓
|
|
327
|
+
TreeNode calls provider.getNodeContextMenu(node)
|
|
328
|
+
↓
|
|
329
|
+
TreeContextMenu renders menu items
|
|
330
|
+
↓
|
|
331
|
+
User clicks menu item
|
|
332
|
+
↓
|
|
333
|
+
TreeContextMenu calls provider.onContextMenuAction()
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 4. Custom Rendering
|
|
337
|
+
```
|
|
338
|
+
TreeNode needs to render
|
|
339
|
+
↓
|
|
340
|
+
Calls provider.getNodeRenderer(node)
|
|
341
|
+
↓
|
|
342
|
+
If custom renderer exists:
|
|
343
|
+
- Use renderer.renderComponent (React)
|
|
344
|
+
- Or renderer.renderText (non-React)
|
|
345
|
+
↓
|
|
346
|
+
Otherwise use default rendering
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## State Management Details
|
|
350
|
+
|
|
351
|
+
### TreeModel State Structure (MobX Best Practices)
|
|
352
|
+
```typescript
|
|
353
|
+
class TreeModel {
|
|
354
|
+
// Core Data - using observable.map for reactive collections
|
|
355
|
+
nodes: TreeNodeData[] = []
|
|
356
|
+
nodeMap = observable.map<string, TreeNodeData>()
|
|
357
|
+
|
|
358
|
+
// UI State - using observable.map instead of Set/Map
|
|
359
|
+
selectedNodes = observable.map<string, TreeNodeData>()
|
|
360
|
+
expandedNodes = observable.map<string, boolean>()
|
|
361
|
+
focusedNode: string | null = null
|
|
362
|
+
|
|
363
|
+
// Loading State
|
|
364
|
+
isLoading: boolean = false
|
|
365
|
+
loadingNodes = observable.map<string, boolean>()
|
|
366
|
+
errors = observable.map<string, Error>()
|
|
367
|
+
|
|
368
|
+
// Interaction State
|
|
369
|
+
draggedNodes: TreeNodeData[] = []
|
|
370
|
+
dropTarget: TreeNodeData | null = null
|
|
371
|
+
contextMenuNode: TreeNodeData | null = null
|
|
372
|
+
|
|
373
|
+
constructor(public provider: TreeProvider) {
|
|
374
|
+
makeAutoObservable(this, {
|
|
375
|
+
// Most defaults are correct, empty overrides object
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Computed properties for derived state
|
|
380
|
+
get selectedNodesArray(): TreeNodeData[] {
|
|
381
|
+
return Array.from(this.selectedNodes.values());
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
get hasSelection(): boolean {
|
|
385
|
+
return this.selectedNodes.size > 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
get isNodeSelected() {
|
|
389
|
+
return (nodeId: string) => this.selectedNodes.has(nodeId);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Async actions using flow
|
|
393
|
+
loadNodes = flow(function* (this: TreeModel, options?: TreeLoadOptions) {
|
|
394
|
+
this.isLoading = true;
|
|
395
|
+
this.errors.clear();
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
const result = yield this.provider.loadNodes(options);
|
|
399
|
+
this.nodes = result.nodes;
|
|
400
|
+
|
|
401
|
+
// Update node map for fast lookups
|
|
402
|
+
this.nodeMap.clear();
|
|
403
|
+
result.nodes.forEach(node => this.nodeMap.set(node.id, node));
|
|
404
|
+
|
|
405
|
+
} catch (error) {
|
|
406
|
+
this.errors.set('loadNodes', error);
|
|
407
|
+
} finally {
|
|
408
|
+
this.isLoading = false;
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Provider Callback Integration
|
|
415
|
+
All user interactions flow through the provider:
|
|
416
|
+
|
|
417
|
+
- **Selection**: `provider.onSelectionChange(selectionInfo)`
|
|
418
|
+
- **Expansion**: `provider.onNodeExpansion(node, expanded)`
|
|
419
|
+
- **Double-click**: `provider.onNodeDoubleClick(node)`
|
|
420
|
+
- **Drag & Drop**: `provider.onDragDrop(dragInfo)`
|
|
421
|
+
- **Context Menu**: `provider.onContextMenuAction(menuId, nodes)`
|
|
422
|
+
- **Focus**: `provider.onNodeFocus(node)`
|
|
423
|
+
|
|
424
|
+
## Node Rendering System
|
|
425
|
+
|
|
426
|
+
### Multi-Environment Support
|
|
427
|
+
The TreeProvider supports rendering in multiple environments:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
interface TreeNodeRenderer {
|
|
431
|
+
// Non-React environments (Node.js, CLI tools)
|
|
432
|
+
renderText?(node: TreeNodeData): string
|
|
433
|
+
renderIcon?(node: TreeNodeData): string
|
|
434
|
+
getDisplayProps?(node: TreeNodeData): DisplayProps
|
|
435
|
+
|
|
436
|
+
// React environments
|
|
437
|
+
renderComponent?: React.ComponentType<{node: TreeNodeData}>
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Per-Node Customization
|
|
442
|
+
Each node can have its own renderer:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
// Provider can customize per node
|
|
446
|
+
provider.getNodeRenderer(fileNode) // Returns FileRenderer
|
|
447
|
+
provider.getNodeRenderer(dirNode) // Returns DirectoryRenderer
|
|
448
|
+
provider.getDefaultRenderer() // Returns fallback renderer
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Context Menu Customization
|
|
452
|
+
Each node can have different context menus:
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// Different menus per node type
|
|
456
|
+
provider.getNodeContextMenu(fileNode) // [Edit, Delete, Rename]
|
|
457
|
+
provider.getNodeContextMenu(dirNode) // [New File, New Folder, Delete]
|
|
458
|
+
provider.getMultiNodeContextMenu(nodes) // [Delete Selected, Move]
|
|
459
|
+
provider.getEmptySpaceContextMenu() // [New File, New Folder, Paste]
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Usage Examples
|
|
463
|
+
|
|
464
|
+
### Basic Usage
|
|
465
|
+
```typescript
|
|
466
|
+
// Create provider with configuration
|
|
467
|
+
const fileSystemProvider = new FileSystemTreeProvider(fileSystem, {
|
|
468
|
+
multiSelectEnabled: true,
|
|
469
|
+
dragDropEnabled: true,
|
|
470
|
+
virtualizationEnabled: true
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
// Use in React - all behavior comes from provider
|
|
474
|
+
<Tree provider={fileSystemProvider} />
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Custom Provider Implementation
|
|
478
|
+
```typescript
|
|
479
|
+
class GitTreeProvider implements TreeProvider {
|
|
480
|
+
constructor(private gitRepo: GitRepository) {}
|
|
481
|
+
|
|
482
|
+
async loadNodes() {
|
|
483
|
+
return { nodes: await this.gitRepo.getFiles() }
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
getNodeContextMenu(node: TreeNodeData) {
|
|
487
|
+
if (node.type === 'file') {
|
|
488
|
+
return [
|
|
489
|
+
{ id: 'edit', label: 'Edit File', icon: 'edit' },
|
|
490
|
+
{ id: 'delete', label: 'Delete', icon: 'trash' },
|
|
491
|
+
{ type: 'separator' },
|
|
492
|
+
{ id: 'commit', label: 'Commit Changes', icon: 'git-commit' }
|
|
493
|
+
]
|
|
494
|
+
}
|
|
495
|
+
return []
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
onContextMenuAction(menuId: string, nodes: TreeNodeData[]) {
|
|
499
|
+
switch (menuId) {
|
|
500
|
+
case 'edit': this.editFile(nodes[0]); break
|
|
501
|
+
case 'delete': this.deleteFiles(nodes); break
|
|
502
|
+
case 'commit': this.commitChanges(nodes); break
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Performance Considerations
|
|
509
|
+
|
|
510
|
+
- **MobX Reactivity**: Only affected components re-render on state changes
|
|
511
|
+
- **Virtualization**: Built-in support for large trees
|
|
512
|
+
- **Lazy Loading**: Provider-driven on-demand loading
|
|
513
|
+
- **Memory Management**: Automatic cleanup of unused nodes
|
|
514
|
+
- **Debounced Actions**: Built-in debouncing for frequent operations
|
|
515
|
+
|
|
516
|
+
## Accessibility Features
|
|
517
|
+
|
|
518
|
+
- **ARIA Support**: Full screen reader compatibility
|
|
519
|
+
- **Keyboard Navigation**: Arrow keys, space, enter
|
|
520
|
+
- **Focus Management**: Proper focus handling and visibility
|
|
521
|
+
- **High Contrast**: Respects system accessibility preferences
|
|
522
|
+
- **Voice Control**: Semantic markup for voice navigation
|
|
523
|
+
|
|
524
|
+
## Integration Points
|
|
525
|
+
|
|
526
|
+
### With FileBrowser
|
|
527
|
+
```typescript
|
|
528
|
+
const fileBrowserProvider = new FileBrowserTreeProvider(fileSystem)
|
|
529
|
+
<Tree provider={fileBrowserProvider} />
|
|
530
|
+
```
|