@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.
Files changed (244) hide show
  1. package/dist/ExplorerLayout-CSIJd7N4.js +105 -0
  2. package/dist/ExplorerLayout-CSIJd7N4.js.map +1 -0
  3. package/dist/FileBrowserContext-B6jixa2j.js +11 -0
  4. package/dist/FileBrowserContext-B6jixa2j.js.map +1 -0
  5. package/dist/calendar-DSlrbHoj.js +761 -0
  6. package/dist/calendar-DSlrbHoj.js.map +1 -0
  7. package/dist/calendar.d.ts +3 -0
  8. package/dist/calendar.js +3 -0
  9. package/dist/contacts-DQXTZzHc.js +539 -0
  10. package/dist/contacts-DQXTZzHc.js.map +1 -0
  11. package/dist/contacts.d.ts +3 -0
  12. package/dist/contacts.js +3 -0
  13. package/dist/file-browser-m5atC3kF.js +6755 -0
  14. package/dist/file-browser-m5atC3kF.js.map +1 -0
  15. package/dist/file-browser.d.ts +11 -0
  16. package/dist/file-browser.js +9 -0
  17. package/dist/git-B55e6LL-.js +561 -0
  18. package/dist/git-B55e6LL-.js.map +1 -0
  19. package/dist/git.d.ts +2 -0
  20. package/dist/git.js +3 -0
  21. package/dist/iconMap-V4B8P-Uh.js +206 -0
  22. package/dist/iconMap-V4B8P-Uh.js.map +1 -0
  23. package/dist/icons-CIsIOZXR.js +0 -0
  24. package/dist/icons.d.ts +2 -0
  25. package/dist/icons.js +4 -0
  26. package/dist/index-BNmNIWBL.d.ts +71 -0
  27. package/dist/index-BNmNIWBL.d.ts.map +1 -0
  28. package/dist/index-Bryv_GCG.d.ts +1481 -0
  29. package/dist/index-Bryv_GCG.d.ts.map +1 -0
  30. package/dist/index-CuQIjSXs.d.ts +134 -0
  31. package/dist/index-CuQIjSXs.d.ts.map +1 -0
  32. package/dist/index-DSu19mq0.d.ts +153 -0
  33. package/dist/index-DSu19mq0.d.ts.map +1 -0
  34. package/dist/index-DmsyeHFr.d.ts +149 -0
  35. package/dist/index-DmsyeHFr.d.ts.map +1 -0
  36. package/dist/index-DxnJ8FYM.d.ts +17 -0
  37. package/dist/index-DxnJ8FYM.d.ts.map +1 -0
  38. package/dist/index-DzfY1Tok.d.ts +32 -0
  39. package/dist/index-DzfY1Tok.d.ts.map +1 -0
  40. package/dist/index-Ml_SgiKa.d.ts +1847 -0
  41. package/dist/index-Ml_SgiKa.d.ts.map +1 -0
  42. package/dist/index-kHr9udZD.d.ts +1025 -0
  43. package/dist/index-kHr9udZD.d.ts.map +1 -0
  44. package/dist/index.d.ts +11 -0
  45. package/dist/index.js +15 -0
  46. package/dist/layout-Ca_4r8ka.js +89 -0
  47. package/dist/layout-Ca_4r8ka.js.map +1 -0
  48. package/dist/layout.d.ts +2 -0
  49. package/dist/layout.js +5 -0
  50. package/dist/list-CxfT6hix.js +6831 -0
  51. package/dist/list-CxfT6hix.js.map +1 -0
  52. package/dist/list.d.ts +2 -0
  53. package/dist/list.js +5 -0
  54. package/dist/media-DZ292aKK.js +557 -0
  55. package/dist/media-DZ292aKK.js.map +1 -0
  56. package/dist/media.d.ts +3 -0
  57. package/dist/media.js +3 -0
  58. package/dist/tree-Dd9Z0Aso.js +3351 -0
  59. package/dist/tree-Dd9Z0Aso.js.map +1 -0
  60. package/dist/tree.d.ts +2 -0
  61. package/dist/tree.js +6 -0
  62. package/dist/types-common-CB3kRek8.d.ts +26 -0
  63. package/dist/types-common-CB3kRek8.d.ts.map +1 -0
  64. package/dist/utils-B4fdKKsy.js +3 -0
  65. package/package.json +109 -0
  66. package/src/calendar/AgendaView.tsx +37 -0
  67. package/src/calendar/CalendarBrowser.tsx +90 -0
  68. package/src/calendar/CalendarModel.ts +142 -0
  69. package/src/calendar/CalendarSidebar.tsx +81 -0
  70. package/src/calendar/DayView.tsx +76 -0
  71. package/src/calendar/EventCard.tsx +51 -0
  72. package/src/calendar/MockCalendarProvider.ts +98 -0
  73. package/src/calendar/MonthView.tsx +77 -0
  74. package/src/calendar/WeekView.tsx +129 -0
  75. package/src/calendar/index.ts +18 -0
  76. package/src/calendar/types.ts +25 -0
  77. package/src/contacts/ContactAvatar.tsx +35 -0
  78. package/src/contacts/ContactBrowser.tsx +56 -0
  79. package/src/contacts/ContactCard.tsx +37 -0
  80. package/src/contacts/ContactDetail.tsx +63 -0
  81. package/src/contacts/ContactGroupSidebar.tsx +40 -0
  82. package/src/contacts/ContactList.tsx +32 -0
  83. package/src/contacts/ContactListModel.ts +120 -0
  84. package/src/contacts/MockContactProvider.ts +77 -0
  85. package/src/contacts/index.ts +17 -0
  86. package/src/contacts/types.ts +26 -0
  87. package/src/demos/CalendarBrowserDemo.tsx +15 -0
  88. package/src/demos/ContactBrowserDemo.tsx +15 -0
  89. package/src/demos/MediaBrowserDemo.tsx +15 -0
  90. package/src/file-browser/adapters/DocumentViewerAdapter.ts +371 -0
  91. package/src/file-browser/adapters/FileSystemBridge.ts +168 -0
  92. package/src/file-browser/adapters/GitBrowserAdapter.ts +546 -0
  93. package/src/file-browser/adapters/README.md +504 -0
  94. package/src/file-browser/adapters/index.ts +27 -0
  95. package/src/file-browser/adapters/types.ts +70 -0
  96. package/src/file-browser/architecture.md +645 -0
  97. package/src/file-browser/components/CreateItemDialog.tsx +71 -0
  98. package/src/file-browser/components/DeleteConfirmDialog.tsx +58 -0
  99. package/src/file-browser/components/FileBrowser.tsx +473 -0
  100. package/src/file-browser/components/FileBrowserContent.tsx +209 -0
  101. package/src/file-browser/components/FileBrowserHeader.tsx +151 -0
  102. package/src/file-browser/components/FileBrowserToolbar.tsx +145 -0
  103. package/src/file-browser/components/LeftPanel/LeftPanel.tsx +103 -0
  104. package/src/file-browser/components/LeftPanel/LeftPanelTabs.tsx +70 -0
  105. package/src/file-browser/components/LeftPanel/TreeNavigationView.tsx +256 -0
  106. package/src/file-browser/components/PreviewPane.tsx +146 -0
  107. package/src/file-browser/components/RightPanel/FilePreview.tsx +219 -0
  108. package/src/file-browser/components/RightPanel/RightPanel.tsx +186 -0
  109. package/src/file-browser/components/RightPanel/RightPanelToolbar.tsx +113 -0
  110. package/src/file-browser/components/UploadProgress.tsx +123 -0
  111. package/src/file-browser/components/ViewerHost.tsx +208 -0
  112. package/src/file-browser/components/mobile/MobileNavigation.tsx +227 -0
  113. package/src/file-browser/components/navigation/NavigationButtons.tsx +171 -0
  114. package/src/file-browser/components/shared/ErrorBoundary.tsx +116 -0
  115. package/src/file-browser/components/shared/FileBrowserItem.tsx +195 -0
  116. package/src/file-browser/components/shared/FileIcon.tsx +169 -0
  117. package/src/file-browser/components/toolbar/ViewModeToggle.tsx +200 -0
  118. package/src/file-browser/components/views/ListView/ListView.tsx +484 -0
  119. package/src/file-browser/components/views/ThumbnailView/ThumbnailView.tsx +323 -0
  120. package/src/file-browser/components/views/TreeView/TreeNode.tsx +186 -0
  121. package/src/file-browser/components/views/TreeView/TreeNodeList.tsx +191 -0
  122. package/src/file-browser/components/views/TreeView/TreeView.tsx +200 -0
  123. package/src/file-browser/components/views/TreemapView/TreemapView.tsx +339 -0
  124. package/src/file-browser/context/FileBrowserContext.tsx +13 -0
  125. package/src/file-browser/examples/BasicUsage.tsx +20 -0
  126. package/src/file-browser/index.ts +98 -0
  127. package/src/file-browser/models/FileBrowserModel.ts +623 -0
  128. package/src/file-browser/models/LeftPanelManagerModel.ts +105 -0
  129. package/src/file-browser/models/NavigationManagerModel.ts +312 -0
  130. package/src/file-browser/models/ResponsiveLayoutManagerModel.ts +437 -0
  131. package/src/file-browser/models/RightPanelManagerModel.ts +190 -0
  132. package/src/file-browser/models/SelectionManagerModel.ts +252 -0
  133. package/src/file-browser/models/ToolbarManagerModel.ts +144 -0
  134. package/src/file-browser/models/UploadModel.ts +147 -0
  135. package/src/file-browser/models/ViewModeManagerModel.ts +185 -0
  136. package/src/file-browser/models/ViewerHostModel.ts +44 -0
  137. package/src/file-browser/models/ui/ListViewUIModel.ts +265 -0
  138. package/src/file-browser/models/ui/PreviewUIModel.ts +297 -0
  139. package/src/file-browser/models/ui/ThumbnailViewUIModel.ts +254 -0
  140. package/src/file-browser/models/ui/TreeViewUIModel.ts +128 -0
  141. package/src/file-browser/models/ui/TreemapViewUIModel.ts +350 -0
  142. package/src/file-browser/providers/FileSystemListProvider.ts +552 -0
  143. package/src/file-browser/providers/FileSystemProvider.ts +401 -0
  144. package/src/file-browser/providers/FileSystemTreeProvider.ts +231 -0
  145. package/src/file-browser/providers/GitProvider.ts +337 -0
  146. package/src/file-browser/providers/GitRepositoryProvider.ts +376 -0
  147. package/src/file-browser/providers/IFileBrowserProvider.ts +56 -0
  148. package/src/file-browser/providers/MemoryProvider.ts +303 -0
  149. package/src/file-browser/providers/index.ts +4 -0
  150. package/src/file-browser/registry/ViewerRegistry.ts +551 -0
  151. package/src/file-browser/registry/types.ts +144 -0
  152. package/src/file-browser/scripts/performanceBenchmark.ts +553 -0
  153. package/src/file-browser/services/ThumbnailCacheService.ts +128 -0
  154. package/src/file-browser/tasks.md +537 -0
  155. package/src/file-browser/types/FileBrowserTypes.ts +126 -0
  156. package/src/file-browser/types/ProviderTypes.ts +155 -0
  157. package/src/file-browser/types/UITypes.ts +235 -0
  158. package/src/file-browser/types/ViewModeTypes.ts +150 -0
  159. package/src/file-browser/utils/gestures.ts +327 -0
  160. package/src/file-browser/utils/performance.ts +563 -0
  161. package/src/file-browser/viewers/ImageViewer.tsx +163 -0
  162. package/src/file-browser/viewers/ImageViewerModel.ts +79 -0
  163. package/src/file-browser/viewers/TextViewer.tsx +95 -0
  164. package/src/file-browser/viewers/UnsupportedFileViewer.tsx +57 -0
  165. package/src/file-browser/viewers/index.ts +61 -0
  166. package/src/git/BranchList.tsx +128 -0
  167. package/src/git/CommitGraph.tsx +239 -0
  168. package/src/git/CommitList.tsx +258 -0
  169. package/src/git/DiffViewer.tsx +219 -0
  170. package/src/git/index.ts +4 -0
  171. package/src/icons/iconMap.ts +146 -0
  172. package/src/icons/index.ts +9 -0
  173. package/src/index.ts +13 -0
  174. package/src/layout/README.md +307 -0
  175. package/src/layout/components/ExplorerLayout/ExplorerLayout.tsx +178 -0
  176. package/src/layout/examples/SimpleExample.tsx +60 -0
  177. package/src/layout/index.ts +6 -0
  178. package/src/lib/utils.ts +1 -0
  179. package/src/list/README.md +303 -0
  180. package/src/list/architecture.md +807 -0
  181. package/src/list/components/CalculatedGridView.tsx +252 -0
  182. package/src/list/components/DragPreview.tsx +102 -0
  183. package/src/list/components/ListContextMenu.tsx +274 -0
  184. package/src/list/components/ListItem.tsx +761 -0
  185. package/src/list/components/ListItems.tsx +919 -0
  186. package/src/list/components/MasonryView.tsx +241 -0
  187. package/src/list/components/SearchFilter.tsx +44 -0
  188. package/src/list/components/TreemapView.tsx +709 -0
  189. package/src/list/components/ViewSizeControls.tsx +205 -0
  190. package/src/list/components/ViewTypeSelector.tsx +312 -0
  191. package/src/list/components/VirtualizedDetailsView.tsx +231 -0
  192. package/src/list/components/VirtualizedGrid.tsx +164 -0
  193. package/src/list/components/VirtualizedList.tsx +154 -0
  194. package/src/list/components/VirtualizedMasonryView.tsx +344 -0
  195. package/src/list/components/shared/EmptyState.tsx +103 -0
  196. package/src/list/components/shared/ErrorBoundary.tsx +123 -0
  197. package/src/list/components/shared/ErrorDisplay.tsx +100 -0
  198. package/src/list/components/shared/ListLoader.tsx +146 -0
  199. package/src/list/components/shared/LoadingIndicator.tsx +80 -0
  200. package/src/list/index.ts +92 -0
  201. package/src/list/models/ListItemsModel.ts +1301 -0
  202. package/src/list/models/TreemapModel.ts +204 -0
  203. package/src/list/providers/ListItemsProvider.ts +313 -0
  204. package/src/list/providers/TestListProvider.ts +604 -0
  205. package/src/list/tasks.md +937 -0
  206. package/src/list/types/ListTypes.ts +178 -0
  207. package/src/list/utils/BenchmarkLogger.ts +243 -0
  208. package/src/list/utils/DragDropManager.ts +320 -0
  209. package/src/list/utils/GridLayoutCalculator.ts +290 -0
  210. package/src/list/utils/ListAccessibility.ts +367 -0
  211. package/src/list/utils/ListKeyboard.ts +414 -0
  212. package/src/list/utils/MasonryLayoutCalculator.ts +302 -0
  213. package/src/list/utils/MasonryLayoutEngine.ts +401 -0
  214. package/src/list/utils/__tests__/MasonryLayoutEngine.test.ts +157 -0
  215. package/src/list/utils/__tests__/VirtualizedMasonryView.test.tsx +251 -0
  216. package/src/media/AlbumSidebar.tsx +48 -0
  217. package/src/media/MediaBrowser.tsx +92 -0
  218. package/src/media/MediaBrowserModel.ts +138 -0
  219. package/src/media/MediaGrid.tsx +50 -0
  220. package/src/media/MediaList.tsx +49 -0
  221. package/src/media/MediaPreview.tsx +63 -0
  222. package/src/media/MediaTimeline.tsx +38 -0
  223. package/src/media/MockMediaProvider.ts +70 -0
  224. package/src/media/index.ts +18 -0
  225. package/src/media/types.ts +21 -0
  226. package/src/styles/variables.css +60 -0
  227. package/src/tree/DEVELOPMENT_SUMMARY.md +170 -0
  228. package/src/tree/__tests__/TreeModel.test.ts +16 -0
  229. package/src/tree/architecture.md +530 -0
  230. package/src/tree/components/Tree.tsx +283 -0
  231. package/src/tree/components/TreeCheckbox.tsx +147 -0
  232. package/src/tree/components/TreeContextMenu.tsx +139 -0
  233. package/src/tree/components/TreeNodeList.tsx +329 -0
  234. package/src/tree/components/TreeTable.tsx +382 -0
  235. package/src/tree/index.ts +58 -0
  236. package/src/tree/models/TreeModel.ts +839 -0
  237. package/src/tree/providers/SimpleTreeProvider.ts +463 -0
  238. package/src/tree/providers/TestTreeProvider.ts +946 -0
  239. package/src/tree/providers/TreeProvider.ts +308 -0
  240. package/src/tree/tasks.md +2046 -0
  241. package/src/tree/types/TreeTypes.ts +279 -0
  242. package/src/tree/utils/SelectionTheme.ts +150 -0
  243. package/src/tree/utils/logger.ts +203 -0
  244. package/src/types-common.ts +21 -0
@@ -0,0 +1,401 @@
1
+ import { IFileSystem } from '@anymux/file-system';
2
+ import { IFileBrowserProvider } from './IFileBrowserProvider';
3
+ import { FileBrowserItem, IconDefinition, PreviewData } from '../types/FileBrowserTypes';
4
+ import { LeftPanelMode } from '../models/LeftPanelManagerModel';
5
+ import { ViewModeDefinition } from '../models/RightPanelManagerModel';
6
+
7
+ interface CacheEntry {
8
+ data: FileBrowserItem[];
9
+ timestamp: number;
10
+ lastAccessed: number;
11
+ accessCount: number;
12
+ }
13
+
14
+ interface CacheOptions {
15
+ maxSize: number;
16
+ ttlMs: number;
17
+ enableLogging: boolean;
18
+ }
19
+
20
+ export class FileSystemProvider implements IFileBrowserProvider {
21
+ readonly id = 'filesystem';
22
+ readonly name = 'File System Provider';
23
+ readonly version = '1.0.0';
24
+
25
+ private fileSystem: IFileSystem;
26
+ private cache = new Map<string, CacheEntry>();
27
+ private cacheOptions: CacheOptions;
28
+
29
+ constructor(fileSystem: IFileSystem, cacheOptions?: Partial<CacheOptions>) {
30
+ this.fileSystem = fileSystem;
31
+ this.cacheOptions = {
32
+ maxSize: 100,
33
+ ttlMs: 30000, // 30 seconds
34
+ enableLogging: false,
35
+ ...cacheOptions
36
+ };
37
+ }
38
+
39
+ async loadItems(path: string): Promise<FileBrowserItem[]> {
40
+ try {
41
+ const normalizedPath = this.normalizePath(path);
42
+
43
+ // Check cache first
44
+ const cached = this.getFromCache(normalizedPath);
45
+ if (cached) {
46
+ this.logCache(`Cache hit for ${normalizedPath}`);
47
+ return cached;
48
+ }
49
+
50
+ this.logCache(`Cache miss for ${normalizedPath}`);
51
+ const items = await this.fileSystem.readdir(normalizedPath, { withFileTypes: true });
52
+
53
+ // Load detailed information for each item
54
+ const itemsWithDetails = await Promise.all(
55
+ items.map(async (item) => {
56
+ const itemPath = this.resolvePath(normalizedPath, item.name);
57
+
58
+ try {
59
+ // Get detailed file information
60
+ const stats = await this.fileSystem.stat(itemPath);
61
+
62
+ return {
63
+ id: itemPath,
64
+ name: item.name,
65
+ path: itemPath,
66
+ type: item.isDirectory() ? 'directory' as const : 'file' as const,
67
+ hasChildren: item.isDirectory(),
68
+ size: stats.size,
69
+ lastModified: new Date(stats.mtime),
70
+ permissions: this.formatPermissions(stats.mode),
71
+ metadata: {
72
+ created: new Date(stats.birthtime || stats.ctime),
73
+ accessed: new Date(stats.atime),
74
+ isSymlink: stats.isSymbolicLink?.() || false,
75
+ blocks: stats.blocks,
76
+ blksize: stats.blksize,
77
+ dev: stats.dev,
78
+ ino: stats.ino,
79
+ nlink: stats.nlink,
80
+ rdev: stats.rdev,
81
+ uid: stats.uid,
82
+ gid: stats.gid,
83
+ }
84
+ };
85
+ } catch (statError) {
86
+ // If stat fails, return basic info without details
87
+ console.warn(`Failed to get stats for ${itemPath}:`, statError);
88
+ return {
89
+ id: itemPath,
90
+ name: item.name,
91
+ path: itemPath,
92
+ type: item.isDirectory() ? 'directory' as const : 'file' as const,
93
+ hasChildren: item.isDirectory(),
94
+ };
95
+ }
96
+ })
97
+ );
98
+
99
+ // Store in cache
100
+ this.setInCache(normalizedPath, itemsWithDetails);
101
+
102
+ return itemsWithDetails;
103
+ } catch (error) {
104
+ // Invalidate cache on error
105
+ this.invalidateCache(this.normalizePath(path));
106
+
107
+ const enhancedError = this.enhanceError(error, path, 'loadItems');
108
+ console.error('Failed to load items:', enhancedError);
109
+ throw enhancedError;
110
+ }
111
+ }
112
+
113
+ normalizePath(path: string): string {
114
+ // Normalize path separators and remove duplicate slashes
115
+ if (!path || path === '') return '/';
116
+
117
+ // Convert backslashes to forward slashes
118
+ let normalized = path.replace(/\\/g, '/');
119
+
120
+ // Remove duplicate slashes
121
+ normalized = normalized.replace(/\/+/g, '/');
122
+
123
+ // Remove trailing slash unless it's root
124
+ if (normalized.length > 1 && normalized.endsWith('/')) {
125
+ normalized = normalized.slice(0, -1);
126
+ }
127
+
128
+ // Ensure it starts with /
129
+ if (!normalized.startsWith('/')) {
130
+ normalized = '/' + normalized;
131
+ }
132
+
133
+ return normalized;
134
+ }
135
+
136
+ private formatPermissions(mode: number | undefined): string {
137
+ if (mode === undefined) return '';
138
+
139
+ // Extract permission bits (last 9 bits)
140
+ const perms = mode & parseInt('777', 8);
141
+
142
+ // Convert to rwx format
143
+ const octal = perms.toString(8).padStart(3, '0');
144
+ let result = '';
145
+
146
+ for (let i = 0; i < 3; i++) {
147
+ const digit = parseInt(octal[i] || '0', 10);
148
+ result += (digit & 4) ? 'r' : '-';
149
+ result += (digit & 2) ? 'w' : '-';
150
+ result += (digit & 1) ? 'x' : '-';
151
+ }
152
+
153
+ return result;
154
+ }
155
+
156
+ private enhanceError(error: any, path: string, operation: string): Error {
157
+ const message = error?.message || 'Unknown error';
158
+ const code = error?.code || 'UNKNOWN';
159
+
160
+ const enhancedError = new Error(
161
+ `${operation} failed for path "${path}": ${message}`
162
+ );
163
+
164
+ // Preserve error properties
165
+ (enhancedError as any).code = code;
166
+ (enhancedError as any).path = path;
167
+ (enhancedError as any).operation = operation;
168
+ (enhancedError as any).originalError = error;
169
+ (enhancedError as any).timestamp = new Date();
170
+
171
+ return enhancedError;
172
+ }
173
+
174
+ getParentPath(path: string): string | null {
175
+ const normalized = this.normalizePath(path);
176
+ if (normalized === '/') return null;
177
+
178
+ const parts = normalized.split('/').filter(Boolean);
179
+ parts.pop();
180
+
181
+ return parts.length === 0 ? '/' : '/' + parts.join('/');
182
+ }
183
+
184
+ resolvePath(basePath: string, relativePath: string): string {
185
+ if (relativePath.startsWith('/')) {
186
+ return this.normalizePath(relativePath);
187
+ }
188
+
189
+ const base = this.normalizePath(basePath);
190
+ const resolved = base === '/' ? `/${relativePath}` : `${base}/${relativePath}`;
191
+ return this.normalizePath(resolved);
192
+ }
193
+
194
+ getItemIcon(item: FileBrowserItem): IconDefinition {
195
+ if (item.type === 'directory') {
196
+ return {
197
+ type: 'lucide',
198
+ name: 'folder',
199
+ color: '#3b82f6'
200
+ };
201
+ }
202
+
203
+ // Simple file type detection based on extension
204
+ const ext = item.name.split('.').pop()?.toLowerCase();
205
+ const iconMap: Record<string, string> = {
206
+ 'js': 'file-code',
207
+ 'ts': 'file-code',
208
+ 'jsx': 'file-code',
209
+ 'tsx': 'file-code',
210
+ 'html': 'file-code',
211
+ 'css': 'file-code',
212
+ 'json': 'file-code',
213
+ 'md': 'file-text',
214
+ 'txt': 'file-text',
215
+ 'png': 'image',
216
+ 'jpg': 'image',
217
+ 'jpeg': 'image',
218
+ 'gif': 'image',
219
+ 'svg': 'image',
220
+ 'pdf': 'file-text',
221
+ };
222
+
223
+ return {
224
+ type: 'lucide',
225
+ name: iconMap[ext || ''] || 'file',
226
+ color: '#6b7280'
227
+ };
228
+ }
229
+
230
+ getItemDisplayName(item: FileBrowserItem): string {
231
+ return item.name;
232
+ }
233
+
234
+ async getItemPreview(item: FileBrowserItem): Promise<PreviewData | null> {
235
+ // Basic preview support - this could be extended
236
+ if (item.type === 'file') {
237
+ const ext = item.name.split('.').pop()?.toLowerCase();
238
+
239
+ if (['png', 'jpg', 'jpeg', 'gif', 'svg'].includes(ext || '')) {
240
+ return {
241
+ type: 'image',
242
+ url: item.path // This would need to be a proper URL in a real implementation
243
+ };
244
+ }
245
+
246
+ if (['txt', 'md', 'json', 'js', 'ts', 'css', 'html'].includes(ext || '')) {
247
+ try {
248
+ // For text files, we could read and preview content
249
+ return {
250
+ type: 'text',
251
+ content: 'Preview not implemented'
252
+ };
253
+ } catch {
254
+ return null;
255
+ }
256
+ }
257
+ }
258
+
259
+ return null;
260
+ }
261
+
262
+ canPerformAction(action: string, items: FileBrowserItem[]): boolean {
263
+ // Basic action support
264
+ const supportedActions = ['delete', 'rename', 'copy', 'move'];
265
+ return supportedActions.includes(action) && items.length > 0;
266
+ }
267
+
268
+ async performAction(action: string, items: FileBrowserItem[]): Promise<any> {
269
+ // This would implement actual file operations
270
+ // For now, just return success
271
+ console.log(`Performing action ${action} on items:`, items);
272
+ return { success: true, message: `${action} completed` };
273
+ }
274
+
275
+ async initialize(): Promise<void> {
276
+ // Any initialization needed
277
+ }
278
+
279
+ dispose(): void {
280
+ // Clear cache
281
+ this.cache.clear();
282
+ }
283
+
284
+ // Cache management methods
285
+ private getFromCache(path: string): FileBrowserItem[] | null {
286
+ const entry = this.cache.get(path);
287
+ if (!entry) return null;
288
+
289
+ const now = Date.now();
290
+
291
+ // Check if expired
292
+ if (now - entry.timestamp > this.cacheOptions.ttlMs) {
293
+ this.cache.delete(path);
294
+ this.logCache(`Cache entry expired for ${path}`);
295
+ return null;
296
+ }
297
+
298
+ // Update access stats
299
+ entry.lastAccessed = now;
300
+ entry.accessCount++;
301
+
302
+ return entry.data;
303
+ }
304
+
305
+ private setInCache(path: string, data: FileBrowserItem[]): void {
306
+ const now = Date.now();
307
+
308
+ // Evict oldest entries if cache is full
309
+ if (this.cache.size >= this.cacheOptions.maxSize) {
310
+ this.evictLeastRecentlyUsed();
311
+ }
312
+
313
+ this.cache.set(path, {
314
+ data: [...data], // Clone to prevent mutations
315
+ timestamp: now,
316
+ lastAccessed: now,
317
+ accessCount: 1
318
+ });
319
+
320
+ this.logCache(`Cached ${data.length} items for ${path}`);
321
+ }
322
+
323
+ private invalidateCache(path: string): void {
324
+ if (this.cache.has(path)) {
325
+ this.cache.delete(path);
326
+ this.logCache(`Invalidated cache for ${path}`);
327
+ }
328
+ }
329
+
330
+ private evictLeastRecentlyUsed(): void {
331
+ let oldestPath: string | null = null;
332
+ let oldestAccess = Date.now();
333
+
334
+ for (const [path, entry] of this.cache.entries()) {
335
+ if (entry.lastAccessed < oldestAccess) {
336
+ oldestAccess = entry.lastAccessed;
337
+ oldestPath = path;
338
+ }
339
+ }
340
+
341
+ if (oldestPath) {
342
+ this.cache.delete(oldestPath);
343
+ this.logCache(`Evicted LRU cache entry: ${oldestPath}`);
344
+ }
345
+ }
346
+
347
+ private logCache(message: string): void {
348
+ if (this.cacheOptions.enableLogging) {
349
+ console.log(`[FileSystemProvider Cache] ${message}`);
350
+ }
351
+ }
352
+
353
+ // Public cache methods for external management
354
+ clearCache(): void {
355
+ this.cache.clear();
356
+ this.logCache('Cache cleared');
357
+ }
358
+
359
+ getCacheStats(): { size: number; maxSize: number; hitRate?: number } {
360
+ return {
361
+ size: this.cache.size,
362
+ maxSize: this.cacheOptions.maxSize
363
+ };
364
+ }
365
+
366
+ // Dual-panel support
367
+ getLeftPanelModes(): LeftPanelMode[] {
368
+ return [
369
+ {
370
+ id: 'tree',
371
+ name: 'Tree',
372
+ icon: 'folder-tree',
373
+ component: {} as any, // Will be set by consumer
374
+ isAvailable: () => true
375
+ }
376
+ ];
377
+ }
378
+
379
+ getRightPanelViewModes(): ViewModeDefinition[] {
380
+ return [
381
+ {
382
+ id: 'list',
383
+ name: 'List',
384
+ icon: 'list',
385
+ applicableFor: (contentType) => contentType === 'folder'
386
+ },
387
+ {
388
+ id: 'thumbnail',
389
+ name: 'Thumbnails',
390
+ icon: 'grid-3x3',
391
+ applicableFor: (contentType) => contentType === 'folder'
392
+ },
393
+ {
394
+ id: 'detail',
395
+ name: 'Details',
396
+ icon: 'info',
397
+ applicableFor: (contentType) => contentType === 'folder'
398
+ }
399
+ ];
400
+ }
401
+ }
@@ -0,0 +1,231 @@
1
+ import { makeAutoObservable } from 'mobx';
2
+ import type { TreeProvider, TreeSelectionInfo } from '../../tree/providers/TreeProvider';
3
+ import type { TreeNodeData, TreeLoadOptions, TreeLoadResult, TreeContextMenuItem } from '../../tree/types/TreeTypes';
4
+ import { FileSystemBridge, type FileSystemItem } from '../adapters/FileSystemBridge';
5
+
6
+ interface FileSystemTreeProviderOptions {
7
+ showFilesInTree: boolean;
8
+ onSelectionChange: (path: string) => void;
9
+ onRefresh?: () => Promise<void>;
10
+ onRenameRequest?: (itemId: string, currentName: string, path: string, source: 'list' | 'tree') => void;
11
+ onDeleteRequest?: (targets: Array<{ path: string; isDirectory: boolean; name: string }>) => void;
12
+ onNewItemRequest?: (parentPath: string, type: 'file' | 'folder') => void;
13
+ }
14
+
15
+ export class FileSystemTreeProvider implements TreeProvider {
16
+ readonly id = 'filesystem-tree';
17
+ readonly name = 'File System Tree';
18
+ readonly version = '1.0.0';
19
+
20
+ isMultiSelectEnabled = false;
21
+ readonly isDragDropEnabled = true;
22
+ readonly isVirtualizationEnabled = false;
23
+ readonly useCheckboxSelection = false;
24
+ readonly allowPartialSelection = false;
25
+ readonly isTableViewEnabled = false;
26
+
27
+ constructor(
28
+ private bridge: FileSystemBridge,
29
+ private options: FileSystemTreeProviderOptions
30
+ ) {
31
+ makeAutoObservable(this, {});
32
+ }
33
+
34
+ async loadNodes(options?: TreeLoadOptions): Promise<TreeLoadResult> {
35
+ const rootItems = await this.bridge.loadDirectoryTree('/');
36
+
37
+ const nodes: TreeNodeData[] = rootItems.map(item => ({
38
+ id: item.id,
39
+ name: item.name,
40
+ path: item.path,
41
+ type: 'directory',
42
+ hasChildren: true,
43
+ isExpanded: false,
44
+ metadata: item
45
+ }));
46
+
47
+ return {
48
+ nodes,
49
+ totalCount: nodes.length
50
+ };
51
+ }
52
+
53
+ async loadChildren(node: TreeNodeData, options?: TreeLoadOptions): Promise<TreeLoadResult> {
54
+ try {
55
+ const items = await this.bridge.loadDirectoryTree(node.path);
56
+
57
+ const childNodes: TreeNodeData[] = items.map(item => ({
58
+ id: item.id,
59
+ name: item.name,
60
+ path: item.path,
61
+ type: 'directory',
62
+ hasChildren: true,
63
+ isExpanded: false,
64
+ metadata: item
65
+ }));
66
+
67
+ return {
68
+ nodes: childNodes,
69
+ totalCount: childNodes.length
70
+ };
71
+ } catch {
72
+ return { nodes: [], totalCount: 0 };
73
+ }
74
+ }
75
+
76
+ async refresh(path?: string): Promise<TreeLoadResult> {
77
+ return this.loadNodes();
78
+ }
79
+
80
+ getNodeContextMenu(node: TreeNodeData): TreeContextMenuItem[] {
81
+ return [
82
+ {
83
+ id: 'open',
84
+ label: 'Open',
85
+ icon: 'folder-open',
86
+ type: 'item',
87
+ handler: () => this.options.onSelectionChange(node.path)
88
+ },
89
+ { id: 'separator1', type: 'separator', label: '' },
90
+ {
91
+ id: 'new-folder',
92
+ label: 'New Folder',
93
+ icon: 'folder-plus',
94
+ type: 'item',
95
+ handler: () => this.requestNewItem(node.path, 'folder')
96
+ },
97
+ {
98
+ id: 'new-file',
99
+ label: 'New File',
100
+ icon: 'file-plus',
101
+ type: 'item',
102
+ handler: () => this.requestNewItem(node.path, 'file')
103
+ },
104
+ { id: 'separator2', type: 'separator', label: '' },
105
+ {
106
+ id: 'rename',
107
+ label: 'Rename',
108
+ icon: 'edit',
109
+ type: 'item',
110
+ handler: () => this.requestRename(node)
111
+ },
112
+ {
113
+ id: 'delete',
114
+ label: 'Delete',
115
+ icon: 'trash',
116
+ type: 'item',
117
+ handler: () => this.requestDelete([node])
118
+ }
119
+ ];
120
+ }
121
+
122
+ getMultiNodeContextMenu(nodes: TreeNodeData[]): TreeContextMenuItem[] {
123
+ return [
124
+ {
125
+ id: 'delete-multiple',
126
+ label: `Delete ${nodes.length} Folders`,
127
+ icon: 'trash',
128
+ type: 'item',
129
+ handler: () => this.requestDelete(nodes)
130
+ }
131
+ ];
132
+ }
133
+
134
+ getEmptySpaceContextMenu(parentNode?: TreeNodeData): TreeContextMenuItem[] {
135
+ const parentPath = parentNode?.path || '/';
136
+ return [
137
+ {
138
+ id: 'new-folder',
139
+ label: 'New Folder',
140
+ icon: 'folder-plus',
141
+ type: 'item',
142
+ handler: () => this.requestNewItem(parentPath, 'folder')
143
+ },
144
+ {
145
+ id: 'new-file',
146
+ label: 'New File',
147
+ icon: 'file-plus',
148
+ type: 'item',
149
+ handler: () => this.requestNewItem(parentPath, 'file')
150
+ }
151
+ ];
152
+ }
153
+
154
+ canExpand(node: TreeNodeData): boolean {
155
+ return node.type === 'directory';
156
+ }
157
+
158
+ canSelect(node: TreeNodeData): boolean {
159
+ return true;
160
+ }
161
+
162
+ onSelectionChange = (selectionInfo: TreeSelectionInfo) => {
163
+ if (selectionInfo.selectedNodes.length > 0) {
164
+ const selectedNode = selectionInfo.selectedNodes[0];
165
+ if (selectedNode) {
166
+ this.options.onSelectionChange(selectedNode.path);
167
+ }
168
+ }
169
+ };
170
+
171
+ onNodeExpansion?(node: TreeNodeData, expanded: boolean): void {}
172
+
173
+ onNodeDoubleClick?(node: TreeNodeData): void {
174
+ this.options.onSelectionChange(node.path);
175
+ }
176
+
177
+ onNodeFocus?(node: TreeNodeData | null): void {}
178
+
179
+ onContextMenuAction?(menuItemId: string, nodes: TreeNodeData[]): void {}
180
+
181
+ private requestRename(node: TreeNodeData) {
182
+ if (this.options.onRenameRequest) {
183
+ const currentName = node.path.split('/').pop() || '';
184
+ this.options.onRenameRequest(node.id, currentName, node.path, 'tree');
185
+ }
186
+ }
187
+
188
+ private requestDelete(nodes: TreeNodeData[]) {
189
+ if (this.options.onDeleteRequest) {
190
+ const targets = nodes.map(n => ({
191
+ path: n.path,
192
+ isDirectory: true,
193
+ name: n.path.split('/').pop() || ''
194
+ }));
195
+ this.options.onDeleteRequest(targets);
196
+ }
197
+ }
198
+
199
+ private requestNewItem(parentPath: string, type: 'file' | 'folder') {
200
+ if (this.options.onNewItemRequest) {
201
+ this.options.onNewItemRequest(parentPath, type);
202
+ }
203
+ }
204
+
205
+ async executeRename(path: string, newName: string): Promise<void> {
206
+ const parentPath = path.split('/').slice(0, -1).join('/') || '/';
207
+ const newPath = `${parentPath}/${newName}`.replace(/\/\//g, '/');
208
+ await this.bridge.renameItem(path, newPath);
209
+ await this.options.onRefresh?.();
210
+ }
211
+
212
+ async executeDelete(targets: Array<{ path: string; isDirectory: boolean }>): Promise<void> {
213
+ await Promise.all(targets.map(t =>
214
+ this.bridge.deleteItem(t.path, t.isDirectory)
215
+ ));
216
+ await this.options.onRefresh?.();
217
+ }
218
+
219
+ async executeCreate(parentPath: string, name: string, type: 'file' | 'folder'): Promise<void> {
220
+ const newPath = `${parentPath}/${name}`.replace(/\/\//g, '/');
221
+ if (type === 'folder') {
222
+ await this.bridge.createDirectory(newPath);
223
+ } else {
224
+ await this.bridge.writeFile(newPath, '');
225
+ }
226
+ await this.options.onRefresh?.();
227
+ }
228
+
229
+ async initialize?(): Promise<void> {}
230
+ dispose?(): void {}
231
+ }