@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,168 @@
1
+ import { makeAutoObservable } from 'mobx';
2
+ import type { IFileSystem, Dirent, Stats } from '@anymux/file-system';
3
+
4
+ export interface ScanProgress {
5
+ directoriesScanned: number;
6
+ filesFound: number;
7
+ totalSize: number;
8
+ }
9
+
10
+ export interface FileSystemItem {
11
+ id: string;
12
+ path: string;
13
+ name: string;
14
+ type: 'file' | 'directory';
15
+ size?: number;
16
+ modified?: Date;
17
+ isDirectory: boolean;
18
+ children?: FileSystemItem[];
19
+ }
20
+
21
+ export class FileSystemBridge {
22
+ constructor(private fileSystem: IFileSystem) {
23
+ makeAutoObservable(this, {
24
+ fileSystem: false,
25
+ });
26
+ }
27
+
28
+ // Tree operations (directories only)
29
+ async loadDirectoryTree(path: string): Promise<FileSystemItem[]> {
30
+ const dirents = await this.fileSystem.readdir(path, { withFileTypes: true });
31
+
32
+ return dirents
33
+ .filter(dirent => dirent.isDirectory())
34
+ .map(dirent => ({
35
+ id: `${path}/${dirent.name}`.replace(/\/\//g, '/'),
36
+ path: `${path}/${dirent.name}`.replace(/\/\//g, '/'),
37
+ name: dirent.name,
38
+ type: 'directory' as const,
39
+ isDirectory: true
40
+ }));
41
+ }
42
+
43
+ // List operations (all items)
44
+ async loadDirectoryContents(path: string): Promise<FileSystemItem[]> {
45
+ const dirents = await this.fileSystem.readdir(path, { withFileTypes: true });
46
+
47
+ const items = await Promise.all(
48
+ dirents.map(async (dirent) => {
49
+ const itemPath = `${path}/${dirent.name}`.replace(/\/\//g, '/');
50
+ let stats: Stats | null = null;
51
+
52
+ try {
53
+ stats = await this.fileSystem.stat(itemPath);
54
+ } catch (err) {
55
+ console.warn(`[FileSystemBridge] stat failed for ${itemPath}:`, err instanceof Error ? err.message : err);
56
+ }
57
+
58
+ return {
59
+ id: itemPath,
60
+ path: itemPath,
61
+ name: dirent.name,
62
+ type: dirent.isDirectory() ? 'directory' as const : 'file' as const,
63
+ size: stats?.size,
64
+ modified: stats?.mtime,
65
+ isDirectory: dirent.isDirectory()
66
+ };
67
+ })
68
+ );
69
+
70
+ // Sort: directories first, then files, both alphabetically
71
+ return items.sort((a, b) => {
72
+ if (a.type !== b.type) {
73
+ return a.type === 'directory' ? -1 : 1;
74
+ }
75
+ return a.name.localeCompare(b.name);
76
+ });
77
+ }
78
+
79
+ // Recursive directory scan for treemap — returns nested FileSystemItem tree
80
+ async loadDirectoryRecursive(
81
+ path: string,
82
+ maxDepth = 3,
83
+ onProgress?: (progress: ScanProgress) => void,
84
+ _progress?: ScanProgress,
85
+ ): Promise<FileSystemItem[]> {
86
+ const progress = _progress ?? { directoriesScanned: 0, filesFound: 0, totalSize: 0 };
87
+ const dirents = await this.fileSystem.readdir(path, { withFileTypes: true });
88
+
89
+ const items = await Promise.all(
90
+ dirents.map(async (dirent) => {
91
+ const itemPath = `${path}/${dirent.name}`.replace(/\/\//g, '/');
92
+ const isDir = dirent.isDirectory();
93
+ let stats: Stats | null = null;
94
+
95
+ try {
96
+ stats = await this.fileSystem.stat(itemPath);
97
+ } catch {
98
+ // skip stat errors
99
+ }
100
+
101
+ if (isDir) {
102
+ progress.directoriesScanned++;
103
+ } else {
104
+ progress.filesFound++;
105
+ if (stats?.size) progress.totalSize += stats.size;
106
+ }
107
+ onProgress?.(progress);
108
+
109
+ const item: FileSystemItem = {
110
+ id: itemPath,
111
+ path: itemPath,
112
+ name: dirent.name,
113
+ type: isDir ? 'directory' : 'file',
114
+ size: stats?.size,
115
+ modified: stats?.mtime,
116
+ isDirectory: isDir,
117
+ };
118
+
119
+ // Recursively load subdirectories
120
+ if (isDir && maxDepth > 0) {
121
+ try {
122
+ item.children = await this.loadDirectoryRecursive(itemPath, maxDepth - 1, onProgress, progress);
123
+ } catch {
124
+ item.children = [];
125
+ }
126
+ }
127
+
128
+ return item;
129
+ })
130
+ );
131
+
132
+ return items;
133
+ }
134
+
135
+ // File operations
136
+ async readFile(path: string): Promise<Buffer | string> {
137
+ return this.fileSystem.readFile(path);
138
+ }
139
+
140
+ async writeFile(path: string, content: Buffer | string): Promise<void> {
141
+ return this.fileSystem.writeFile(path, content);
142
+ }
143
+
144
+ async deleteItem(path: string, isDirectory: boolean): Promise<void> {
145
+ if (isDirectory) {
146
+ return this.fileSystem.rmdir(path, { recursive: true });
147
+ } else {
148
+ return this.fileSystem.unlink(path);
149
+ }
150
+ }
151
+
152
+ async createDirectory(path: string): Promise<void> {
153
+ await this.fileSystem.mkdir(path, { recursive: true });
154
+ }
155
+
156
+ async renameItem(oldPath: string, newPath: string): Promise<void> {
157
+ return this.fileSystem.rename(oldPath, newPath);
158
+ }
159
+
160
+ async exists(path: string): Promise<boolean> {
161
+ try {
162
+ await this.fileSystem.access(path);
163
+ return true;
164
+ } catch {
165
+ return false;
166
+ }
167
+ }
168
+ }
@@ -0,0 +1,546 @@
1
+ import { makeAutoObservable, reaction } from 'mobx';
2
+ import { FileBrowserModel } from '../models/FileBrowserModel';
3
+ import { FileBrowserItem } from '../types/FileBrowserTypes';
4
+
5
+ /**
6
+ * Example adapter demonstrating how to integrate Git repository functionality
7
+ * with the dual-panel FileBrowser coordination system.
8
+ *
9
+ * This adapter:
10
+ * - Extends FileBrowser with Git-specific features
11
+ * - Provides Git status information for files and directories
12
+ * - Coordinates Git operations with FileBrowser navigation
13
+ * - Maintains branch and commit selection across panels
14
+ * - Demonstrates advanced dual-panel coordination patterns
15
+ */
16
+
17
+ export interface GitBrowserOptions {
18
+ repositoryPath?: string;
19
+ autoFetch?: boolean;
20
+ showHiddenFiles?: boolean;
21
+ defaultBranch?: string;
22
+ enableCoordination?: boolean;
23
+ statusRefreshInterval?: number; // ms
24
+ }
25
+
26
+ export interface GitFileStatus {
27
+ staged: boolean;
28
+ modified: boolean;
29
+ untracked: boolean;
30
+ conflicted: boolean;
31
+ deleted: boolean;
32
+ added: boolean;
33
+ }
34
+
35
+ export interface GitCommit {
36
+ hash: string;
37
+ message: string;
38
+ author: string;
39
+ date: Date;
40
+ parents: string[];
41
+ }
42
+
43
+ export interface GitBranch {
44
+ name: string;
45
+ isRemote: boolean;
46
+ isCurrent: boolean;
47
+ commit: string;
48
+ upstream?: string;
49
+ }
50
+
51
+ export interface GitRepositoryInfo {
52
+ path: string;
53
+ currentBranch: string;
54
+ branches: GitBranch[];
55
+ commits: GitCommit[];
56
+ status: Map<string, GitFileStatus>;
57
+ isClean: boolean;
58
+ hasUncommitted: boolean;
59
+ }
60
+
61
+ export class GitBrowserAdapter {
62
+ // Configuration
63
+ private options: Required<GitBrowserOptions>;
64
+
65
+ // Git-specific state
66
+ repositoryInfo: GitRepositoryInfo | null = null;
67
+ selectedCommit: GitCommit | null = null;
68
+ selectedBranch: GitBranch | null = null;
69
+ isLoading = false;
70
+ error: string | null = null;
71
+
72
+ // File status cache for performance
73
+ private statusCache = new Map<string, GitFileStatus>();
74
+ private refreshTimer: number | null = null;
75
+
76
+ // Coordination
77
+ private fileBrowserModel: FileBrowserModel;
78
+ private disposers: Array<() => void> = [];
79
+
80
+ constructor(
81
+ fileBrowserModel: FileBrowserModel,
82
+ options: GitBrowserOptions = {}
83
+ ) {
84
+ this.fileBrowserModel = fileBrowserModel;
85
+ this.options = {
86
+ repositoryPath: '',
87
+ autoFetch: false,
88
+ showHiddenFiles: false,
89
+ defaultBranch: 'main',
90
+ enableCoordination: true,
91
+ statusRefreshInterval: 5000, // 5 seconds
92
+ ...options
93
+ };
94
+
95
+ makeAutoObservable(this);
96
+
97
+ if (this.options.enableCoordination) {
98
+ this.setupCoordination();
99
+ }
100
+
101
+ this.setupStatusRefresh();
102
+
103
+ this.logInfo('GitBrowserAdapter initialized', {
104
+ repositoryPath: this.options.repositoryPath,
105
+ coordinationEnabled: this.options.enableCoordination,
106
+ refreshInterval: this.options.statusRefreshInterval
107
+ });
108
+ }
109
+
110
+ private setupCoordination() {
111
+ // Listen for selection changes to provide Git context
112
+ const selectionDisposer = reaction(
113
+ () => ({
114
+ selectedItems: Array.from(this.fileBrowserModel.selectionManager.selectedItems.keys()),
115
+ focusedItem: this.fileBrowserModel.selectionManager.focusedItem
116
+ }),
117
+ ({ selectedItems, focusedItem }) => {
118
+ this.handleSelectionChange(focusedItem);
119
+ },
120
+ { fireImmediately: true }
121
+ );
122
+
123
+ // Listen for navigation changes to update Git context
124
+ const navigationDisposer = reaction(
125
+ () => this.fileBrowserModel.navigationManager.currentPath,
126
+ (currentPath) => {
127
+ this.handleNavigationChange(currentPath || '/');
128
+ }
129
+ );
130
+
131
+ // Listen for left panel mode changes (for Git-specific views)
132
+ const leftPanelDisposer = reaction(
133
+ () => this.fileBrowserModel.leftPanel.currentMode,
134
+ (mode) => {
135
+ this.handleLeftPanelModeChange(mode);
136
+ }
137
+ );
138
+
139
+ this.disposers.push(selectionDisposer, navigationDisposer, leftPanelDisposer);
140
+ }
141
+
142
+ private setupStatusRefresh() {
143
+ if (this.options.statusRefreshInterval > 0) {
144
+ this.refreshTimer = window.setInterval(() => {
145
+ this.refreshGitStatus();
146
+ }, this.options.statusRefreshInterval);
147
+ }
148
+ }
149
+
150
+ private async handleSelectionChange(item: FileBrowserItem | null) {
151
+ if (!item || !this.repositoryInfo) return;
152
+
153
+ // Provide Git status for the selected item
154
+ const status = this.getFileStatus(item.path);
155
+ if (status) {
156
+ this.logInfo(`Git status for ${item.name}`, status);
157
+ }
158
+
159
+ // If it's a commit file, load commit details
160
+ if (this.isCommitReference(item)) {
161
+ await this.loadCommitDetails(item);
162
+ }
163
+ }
164
+
165
+ private async handleNavigationChange(currentPath: string) {
166
+ if (!this.repositoryInfo) return;
167
+
168
+ // Check if we've navigated outside the repository
169
+ if (!this.isPathInRepository(currentPath)) {
170
+ this.logInfo('Navigated outside repository');
171
+ return;
172
+ }
173
+
174
+ // Update Git status for the current directory
175
+ await this.refreshGitStatus();
176
+ }
177
+
178
+ private handleLeftPanelModeChange(mode: string | undefined) {
179
+ if (!mode) return;
180
+
181
+ switch (mode) {
182
+ case 'git-history':
183
+ this.loadRepositoryHistory();
184
+ break;
185
+ case 'git-branches':
186
+ this.loadBranches();
187
+ break;
188
+ case 'git-status':
189
+ this.loadWorkingTreeStatus();
190
+ break;
191
+ }
192
+ }
193
+
194
+ // Git operations
195
+
196
+ /**
197
+ * Initialize Git repository in the current path
198
+ */
199
+ async initializeRepository(path: string): Promise<void> {
200
+ this.isLoading = true;
201
+ this.error = null;
202
+
203
+ try {
204
+ // Simulate Git init operation
205
+ await this.simulateGitOperation('init', { path });
206
+
207
+ this.repositoryInfo = {
208
+ path,
209
+ currentBranch: this.options.defaultBranch,
210
+ branches: [{
211
+ name: this.options.defaultBranch,
212
+ isRemote: false,
213
+ isCurrent: true,
214
+ commit: 'initial'
215
+ }],
216
+ commits: [],
217
+ status: new Map(),
218
+ isClean: true,
219
+ hasUncommitted: false
220
+ };
221
+
222
+ this.logInfo('Repository initialized', { path });
223
+
224
+ } catch (error) {
225
+ this.setError(`Failed to initialize repository: ${error}`);
226
+ } finally {
227
+ this.isLoading = false;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Load repository information
233
+ */
234
+ async loadRepository(path: string): Promise<void> {
235
+ this.isLoading = true;
236
+ this.error = null;
237
+
238
+ try {
239
+ // Simulate loading repository data
240
+ const repoData = await this.simulateGitOperation('load', { path });
241
+
242
+ this.repositoryInfo = repoData as GitRepositoryInfo;
243
+ this.options.repositoryPath = path;
244
+
245
+ // Update FileBrowser to show Git-enhanced items
246
+ await this.enhanceFileBrowserItems();
247
+
248
+ this.logInfo('Repository loaded', {
249
+ path,
250
+ branch: this.repositoryInfo.currentBranch,
251
+ commits: this.repositoryInfo.commits.length
252
+ });
253
+
254
+ } catch (error) {
255
+ this.setError(`Failed to load repository: ${error}`);
256
+ } finally {
257
+ this.isLoading = false;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Switch to a different branch
263
+ */
264
+ async switchBranch(branchName: string): Promise<void> {
265
+ if (!this.repositoryInfo) return;
266
+
267
+ this.isLoading = true;
268
+
269
+ try {
270
+ // Simulate git checkout
271
+ await this.simulateGitOperation('checkout', { branch: branchName });
272
+
273
+ // Update repository info
274
+ this.repositoryInfo.currentBranch = branchName;
275
+ this.repositoryInfo.branches.forEach(branch => {
276
+ branch.isCurrent = branch.name === branchName;
277
+ });
278
+
279
+ this.selectedBranch = this.repositoryInfo.branches.find(b => b.name === branchName) || null;
280
+
281
+ // Refresh file browser to show branch-specific content
282
+ await this.enhanceFileBrowserItems();
283
+
284
+ this.logInfo('Switched to branch', { branchName });
285
+
286
+ } catch (error) {
287
+ this.setError(`Failed to switch branch: ${error}`);
288
+ } finally {
289
+ this.isLoading = false;
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Get Git status for a specific file
295
+ */
296
+ getFileStatus(filePath: string): GitFileStatus | null {
297
+ if (!this.repositoryInfo) return null;
298
+ return this.repositoryInfo.status.get(filePath) || null;
299
+ }
300
+
301
+ /**
302
+ * Stage file changes
303
+ */
304
+ async stageFile(filePath: string): Promise<void> {
305
+ if (!this.repositoryInfo) return;
306
+
307
+ try {
308
+ await this.simulateGitOperation('add', { files: [filePath] });
309
+
310
+ // Update status
311
+ const status = this.repositoryInfo.status.get(filePath);
312
+ if (status) {
313
+ status.staged = true;
314
+ this.repositoryInfo.status.set(filePath, status);
315
+ }
316
+
317
+ this.logInfo('File staged', { filePath });
318
+
319
+ } catch (error) {
320
+ this.setError(`Failed to stage file: ${error}`);
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Commit staged changes
326
+ */
327
+ async commitChanges(message: string): Promise<void> {
328
+ if (!this.repositoryInfo) return;
329
+
330
+ try {
331
+ const commitHash = await this.simulateGitOperation('commit', { message });
332
+
333
+ // Create new commit
334
+ const newCommit: GitCommit = {
335
+ hash: commitHash as string,
336
+ message,
337
+ author: 'User',
338
+ date: new Date(),
339
+ parents: this.repositoryInfo.commits.length > 0 ? [this.repositoryInfo.commits[0]!.hash] : []
340
+ };
341
+
342
+ // Update repository state
343
+ this.repositoryInfo.commits.unshift(newCommit);
344
+ this.repositoryInfo.isClean = true;
345
+ this.repositoryInfo.hasUncommitted = false;
346
+
347
+ // Clear staged status
348
+ this.repositoryInfo.status.forEach((status, path) => {
349
+ if (status.staged) {
350
+ status.staged = false;
351
+ this.repositoryInfo!.status.set(path, status);
352
+ }
353
+ });
354
+
355
+ this.logInfo('Changes committed', { message, hash: commitHash });
356
+
357
+ } catch (error) {
358
+ this.setError(`Failed to commit changes: ${error}`);
359
+ }
360
+ }
361
+
362
+ // Coordination helpers
363
+
364
+ /**
365
+ * Enhance FileBrowser items with Git information
366
+ */
367
+ private async enhanceFileBrowserItems(): Promise<void> {
368
+ // This would add Git status icons, branch information, etc.
369
+ // to the FileBrowser items through the coordination system
370
+
371
+ if (this.repositoryInfo) {
372
+ this.logInfo('Enhanced FileBrowser items with Git data');
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Load repository history for left panel
378
+ */
379
+ private async loadRepositoryHistory(): Promise<void> {
380
+ if (!this.repositoryInfo) return;
381
+
382
+ // This would populate the left panel with commit history
383
+ this.logInfo('Loading repository history for left panel');
384
+ }
385
+
386
+ /**
387
+ * Load branches for left panel
388
+ */
389
+ private async loadBranches(): Promise<void> {
390
+ if (!this.repositoryInfo) return;
391
+
392
+ // This would populate the left panel with branch list
393
+ this.logInfo('Loading branches for left panel');
394
+ }
395
+
396
+ /**
397
+ * Load working tree status for left panel
398
+ */
399
+ private async loadWorkingTreeStatus(): Promise<void> {
400
+ if (!this.repositoryInfo) return;
401
+
402
+ // This would populate the left panel with file status
403
+ this.logInfo('Loading working tree status for left panel');
404
+ }
405
+
406
+ /**
407
+ * Refresh Git status for current directory
408
+ */
409
+ private async refreshGitStatus(): Promise<void> {
410
+ if (!this.repositoryInfo) return;
411
+
412
+ try {
413
+ // Simulate git status check
414
+ const statusData = await this.simulateGitOperation('status', {});
415
+
416
+ // Update status cache
417
+ if (statusData && typeof statusData === 'object') {
418
+ const statusMap = statusData as Map<string, GitFileStatus>;
419
+ this.repositoryInfo.status = statusMap;
420
+ }
421
+
422
+ } catch (error) {
423
+ this.logError('Failed to refresh Git status', error);
424
+ }
425
+ }
426
+
427
+ // Utility methods
428
+
429
+ private isCommitReference(item: FileBrowserItem): boolean {
430
+ // Check if item represents a commit (e.g., in history view)
431
+ return item.name.match(/^[a-f0-9]{7,40}$/) !== null;
432
+ }
433
+
434
+ private isPathInRepository(path: string): boolean {
435
+ if (!this.repositoryInfo) return false;
436
+ return path.startsWith(this.repositoryInfo.path);
437
+ }
438
+
439
+ private async loadCommitDetails(item: FileBrowserItem): Promise<void> {
440
+ if (!this.repositoryInfo) return;
441
+
442
+ // Find commit by hash prefix
443
+ const commit = this.repositoryInfo.commits.find(c =>
444
+ c.hash.startsWith(item.name)
445
+ );
446
+
447
+ if (commit) {
448
+ this.selectedCommit = commit;
449
+ this.logInfo('Selected commit', { hash: commit.hash, message: commit.message });
450
+ }
451
+ }
452
+
453
+ private async simulateGitOperation(operation: string, params: any): Promise<any> {
454
+ // Simulate Git operations with realistic delays
455
+ await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200));
456
+
457
+ switch (operation) {
458
+ case 'init':
459
+ return { success: true };
460
+
461
+ case 'load':
462
+ return {
463
+ path: params.path,
464
+ currentBranch: 'main',
465
+ branches: [
466
+ { name: 'main', isRemote: false, isCurrent: true, commit: 'abc123' },
467
+ { name: 'develop', isRemote: false, isCurrent: false, commit: 'def456' }
468
+ ],
469
+ commits: [
470
+ {
471
+ hash: 'abc123',
472
+ message: 'Initial commit',
473
+ author: 'User',
474
+ date: new Date(),
475
+ parents: []
476
+ }
477
+ ],
478
+ status: new Map(),
479
+ isClean: true,
480
+ hasUncommitted: false
481
+ };
482
+
483
+ case 'checkout':
484
+ return { success: true };
485
+
486
+ case 'add':
487
+ return { success: true };
488
+
489
+ case 'commit':
490
+ return `commit_${Date.now()}`;
491
+
492
+ case 'status':
493
+ return new Map();
494
+
495
+ default:
496
+ throw new Error(`Unknown Git operation: ${operation}`);
497
+ }
498
+ }
499
+
500
+ private setError(message: string): void {
501
+ this.error = message;
502
+ this.logError('GitBrowser error', new Error(message));
503
+ }
504
+
505
+ private logInfo(message: string, data?: any): void {
506
+ this.fileBrowserModel.logger?.info(`[GitBrowserAdapter] ${message}`, data);
507
+ }
508
+
509
+ private logError(message: string, error: any): void {
510
+ this.fileBrowserModel.logger?.error(`[GitBrowserAdapter] ${message}`, error);
511
+ }
512
+
513
+ // Public API
514
+
515
+ /**
516
+ * Get adapter statistics
517
+ */
518
+ get stats() {
519
+ return {
520
+ isLoaded: !!this.repositoryInfo,
521
+ repositoryPath: this.repositoryInfo?.path || null,
522
+ currentBranch: this.repositoryInfo?.currentBranch || null,
523
+ commitCount: this.repositoryInfo?.commits.length || 0,
524
+ isClean: this.repositoryInfo?.isClean || true,
525
+ statusCacheSize: this.statusCache.size
526
+ };
527
+ }
528
+
529
+ // Cleanup
530
+ dispose(): void {
531
+ this.disposers.forEach(dispose => dispose());
532
+ this.disposers = [];
533
+
534
+ if (this.refreshTimer) {
535
+ clearInterval(this.refreshTimer);
536
+ this.refreshTimer = null;
537
+ }
538
+
539
+ this.statusCache.clear();
540
+ this.repositoryInfo = null;
541
+ this.selectedCommit = null;
542
+ this.selectedBranch = null;
543
+
544
+ this.logInfo('GitBrowserAdapter disposed');
545
+ }
546
+ }