@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,256 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { ChevronRight, ChevronDown, Folder, FolderOpen } from 'lucide-react';
4
+ import { LeftPanelManagerModel } from '../../models/LeftPanelManagerModel';
5
+ import { FileBrowserItem } from '../../types/FileBrowserTypes';
6
+
7
+ interface TreeNavigationViewProps {
8
+ leftPanelManager: LeftPanelManagerModel;
9
+ onNavigate?: (path: string) => void;
10
+ onSelectionChange?: (item: any) => void;
11
+ className?: string;
12
+ }
13
+
14
+ interface TreeNodeProps {
15
+ item: FileBrowserItem;
16
+ level: number;
17
+ isExpanded: boolean;
18
+ isSelected: boolean;
19
+ onToggle: (path: string) => void;
20
+ onSelect: (item: FileBrowserItem) => void;
21
+ children?: React.ReactNode;
22
+ }
23
+
24
+ const TreeNode: React.FC<TreeNodeProps> = observer(({
25
+ item,
26
+ level,
27
+ isExpanded,
28
+ isSelected,
29
+ onToggle,
30
+ onSelect,
31
+ children
32
+ }) => {
33
+ const isFolder = item.type === 'directory';
34
+
35
+ const handleToggleClick = (e: React.MouseEvent) => {
36
+ e.stopPropagation();
37
+ if (isFolder) {
38
+ onToggle(item.path);
39
+ }
40
+ };
41
+
42
+ const handleSelectClick = () => {
43
+ onSelect(item);
44
+ };
45
+
46
+ const handleKeyDown = (e: React.KeyboardEvent) => {
47
+ switch (e.key) {
48
+ case 'Enter':
49
+ case ' ':
50
+ e.preventDefault();
51
+ onSelect(item);
52
+ break;
53
+ case 'ArrowRight':
54
+ if (isFolder && !isExpanded) {
55
+ e.preventDefault();
56
+ onToggle(item.path);
57
+ }
58
+ break;
59
+ case 'ArrowLeft':
60
+ if (isFolder && isExpanded) {
61
+ e.preventDefault();
62
+ onToggle(item.path);
63
+ }
64
+ break;
65
+ }
66
+ };
67
+
68
+ return (
69
+ <div className="tree-node">
70
+ <div
71
+ className={`tree-node-item ${isSelected ? 'tree-node-selected' : ''}`}
72
+ style={{ paddingLeft: `${level * 16 + 8}px` }}
73
+ onClick={handleSelectClick}
74
+ onKeyDown={handleKeyDown}
75
+ tabIndex={0}
76
+ role="treeitem"
77
+ aria-expanded={isFolder ? isExpanded : undefined}
78
+ aria-selected={isSelected}
79
+ aria-level={level + 1}
80
+ >
81
+ {/* Expand/Collapse Toggle */}
82
+ {isFolder && (
83
+ <button
84
+ className="tree-node-toggle"
85
+ onClick={handleToggleClick}
86
+ aria-label={isExpanded ? 'Collapse folder' : 'Expand folder'}
87
+ tabIndex={-1}
88
+ >
89
+ {isExpanded ? (
90
+ <ChevronDown size={14} />
91
+ ) : (
92
+ <ChevronRight size={14} />
93
+ )}
94
+ </button>
95
+ )}
96
+
97
+ {/* Icon */}
98
+ <span className="tree-node-icon" aria-hidden="true">
99
+ {isFolder ? (
100
+ isExpanded ? <FolderOpen size={16} /> : <Folder size={16} />
101
+ ) : (
102
+ // File icon would be determined by file type
103
+ <div className="tree-node-file-icon" />
104
+ )}
105
+ </span>
106
+
107
+ {/* Label */}
108
+ <span className="tree-node-label" title={item.name}>
109
+ {item.name}
110
+ </span>
111
+ </div>
112
+
113
+ {/* Children */}
114
+ {isFolder && isExpanded && children && (
115
+ <div className="tree-node-children" role="group">
116
+ {children}
117
+ </div>
118
+ )}
119
+ </div>
120
+ );
121
+ });
122
+
123
+ export const TreeNavigationView: React.FC<TreeNavigationViewProps> = observer(({
124
+ leftPanelManager,
125
+ onNavigate,
126
+ onSelectionChange,
127
+ className = ''
128
+ }) => {
129
+ // For demo purposes, using a simple tree structure
130
+ // In real implementation, this would be loaded from the provider
131
+ const [expandedPaths, setExpandedPaths] = React.useState<Set<string>>(new Set(['/']));
132
+ const [selectedPath, setSelectedPath] = React.useState<string>('/');
133
+
134
+ const handleToggle = (path: string) => {
135
+ setExpandedPaths(prev => {
136
+ const newSet = new Set(prev);
137
+ if (newSet.has(path)) {
138
+ newSet.delete(path);
139
+ } else {
140
+ newSet.add(path);
141
+ }
142
+ return newSet;
143
+ });
144
+ };
145
+
146
+ const handleSelect = (item: FileBrowserItem) => {
147
+ setSelectedPath(item.path);
148
+
149
+ // Use selection coordination through left panel manager
150
+ if (leftPanelManager.fileBrowserModel.selectionManager) {
151
+ leftPanelManager.fileBrowserModel.selectionManager.selectFromTreePanel(item);
152
+ }
153
+
154
+ // Notify parent components (for backward compatibility)
155
+ onSelectionChange?.(item);
156
+ if (item.type === 'directory') {
157
+ // Use navigation coordination for directory navigation
158
+ if (leftPanelManager.fileBrowserModel.navigationManager) {
159
+ leftPanelManager.fileBrowserModel.navigationManager.navigateToWithCoordination(item.path, 'tree');
160
+ }
161
+ onNavigate?.(item.path);
162
+ }
163
+ };
164
+
165
+ // Demo tree data - in real implementation, this would come from provider
166
+ const treeData: FileBrowserItem[] = [
167
+ {
168
+ id: 'root',
169
+ name: 'Root',
170
+ path: '/',
171
+ type: 'directory',
172
+ size: 0,
173
+ lastModified: new Date(),
174
+ children: [
175
+ {
176
+ id: 'src',
177
+ name: 'src',
178
+ path: '/src',
179
+ type: 'directory',
180
+ size: 0,
181
+ lastModified: new Date(),
182
+ children: [
183
+ {
184
+ id: 'components',
185
+ name: 'components',
186
+ path: '/src/components',
187
+ type: 'directory',
188
+ size: 0,
189
+ lastModified: new Date()
190
+ },
191
+ {
192
+ id: 'index.ts',
193
+ name: 'index.ts',
194
+ path: '/src/index.ts',
195
+ type: 'file',
196
+ size: 512,
197
+ lastModified: new Date()
198
+ }
199
+ ]
200
+ },
201
+ {
202
+ id: 'docs',
203
+ name: 'docs',
204
+ path: '/docs',
205
+ type: 'directory',
206
+ size: 0,
207
+ lastModified: new Date()
208
+ },
209
+ {
210
+ id: 'package.json',
211
+ name: 'package.json',
212
+ path: '/package.json',
213
+ type: 'file',
214
+ size: 1024,
215
+ lastModified: new Date()
216
+ }
217
+ ]
218
+ }
219
+ ];
220
+
221
+ const renderTreeNodes = (items: FileBrowserItem[], level = 0): React.ReactNode => {
222
+ return items.map(item => {
223
+ const isExpanded = expandedPaths.has(item.path);
224
+ const isSelected = selectedPath === item.path;
225
+
226
+ return (
227
+ <TreeNode
228
+ key={item.id}
229
+ item={item}
230
+ level={level}
231
+ isExpanded={isExpanded}
232
+ isSelected={isSelected}
233
+ onToggle={handleToggle}
234
+ onSelect={handleSelect}
235
+ >
236
+ {item.children && renderTreeNodes(item.children, level + 1)}
237
+ </TreeNode>
238
+ );
239
+ });
240
+ };
241
+
242
+ return (
243
+ <div
244
+ className={`tree-navigation-view ${className}`}
245
+ role="tree"
246
+ aria-label="File tree navigation"
247
+ >
248
+ {JSON.stringify(treeData)}
249
+ <div className="tree-container">
250
+ {renderTreeNodes(treeData)}
251
+ </div>
252
+ </div>
253
+ );
254
+ });
255
+
256
+ TreeNavigationView.displayName = 'TreeNavigationView';
@@ -0,0 +1,146 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { FileText, ImageIcon, FileQuestion, Loader2, Eye } from 'lucide-react';
4
+ import { cn } from '../../lib/utils';
5
+ import type { PreviewFileState } from '../models/FileBrowserModel';
6
+ import { globalViewerRegistry } from '../registry/ViewerRegistry';
7
+ import { ViewerHost } from './ViewerHost';
8
+
9
+ export interface PreviewPaneProps {
10
+ previewFile: PreviewFileState | null;
11
+ className?: string;
12
+ }
13
+
14
+ /**
15
+ * File preview pane that delegates to ViewerHost for rendering.
16
+ *
17
+ * Uses the plugin registry to resolve the appropriate viewer for the file,
18
+ * rather than hardcoding viewer IDs. Falls back to a generic unsupported
19
+ * placeholder when no plugin with preview capability is found.
20
+ */
21
+ export const PreviewPane: React.FC<PreviewPaneProps> = observer(({
22
+ previewFile,
23
+ className,
24
+ }) => {
25
+ // Empty state: no file selected
26
+ if (!previewFile) {
27
+ return (
28
+ <div className={cn("h-full flex flex-col items-center justify-center text-muted-foreground bg-muted/5", className)}>
29
+ <Eye className="w-10 h-10 mb-3 opacity-40" />
30
+ <p className="text-sm font-medium">No file selected</p>
31
+ <p className="text-xs mt-1 opacity-60">Click a file to preview</p>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ // Loading state
37
+ if (previewFile.isLoading) {
38
+ return (
39
+ <div className={cn("h-full flex flex-col", className)}>
40
+ <PreviewHeader name={previewFile.name} size={previewFile.size} />
41
+ <div className="flex-1 flex items-center justify-center">
42
+ <div className="flex flex-col items-center gap-2 text-muted-foreground">
43
+ <Loader2 className="w-6 h-6 animate-spin" />
44
+ <p className="text-sm">Loading preview...</p>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ );
49
+ }
50
+
51
+ // Resolve viewer from registry using the new plugin API
52
+ const fileCtx = globalViewerRegistry.buildFileMatchContext(
53
+ previewFile.name,
54
+ {
55
+ path: previewFile.path,
56
+ mimeType: previewFile.mimeType,
57
+ size: previewFile.size,
58
+ }
59
+ );
60
+ const resolved = globalViewerRegistry.resolveViewer(fileCtx);
61
+
62
+ // Check if the resolved plugin supports preview mode
63
+ const canPreview = resolved?.plugin.capabilities.canPreview !== false;
64
+
65
+ if (resolved && canPreview) {
66
+ // Delegate rendering to ViewerHost in preview mode
67
+ return (
68
+ <div className={cn("h-full flex flex-col", className)}>
69
+ <PreviewHeader
70
+ name={previewFile.name}
71
+ size={previewFile.size}
72
+ icon={getIconType(resolved.plugin.id)}
73
+ />
74
+ <div className="flex-1 min-h-0">
75
+ <ViewerHost
76
+ viewer={resolved}
77
+ file={{
78
+ path: previewFile.path,
79
+ name: previewFile.name,
80
+ content: previewFile.content,
81
+ size: previewFile.size,
82
+ mimeType: previewFile.mimeType,
83
+ }}
84
+ mode="preview"
85
+ readOnly
86
+ className="h-full"
87
+ />
88
+ </div>
89
+ </div>
90
+ );
91
+ }
92
+
93
+ // Unsupported file type — no plugin can handle preview
94
+ return (
95
+ <div className={cn("h-full flex flex-col", className)}>
96
+ <PreviewHeader name={previewFile.name} size={previewFile.size} icon="unknown" />
97
+ <div className="flex-1 flex items-center justify-center">
98
+ <div className="flex flex-col items-center gap-3 text-center px-4">
99
+ <FileQuestion className="w-12 h-12 text-muted-foreground/50" />
100
+ <div>
101
+ <p className="text-sm font-medium">{previewFile.name}</p>
102
+ <p className="text-xs text-muted-foreground mt-1">
103
+ Preview not available for this file type
104
+ </p>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ );
110
+ });
111
+
112
+ PreviewPane.displayName = 'PreviewPane';
113
+
114
+ // --- Helper to determine icon type from plugin ID ---
115
+
116
+ function getIconType(pluginId: string): 'text' | 'image' | 'unknown' {
117
+ if (pluginId.includes('image')) return 'image';
118
+ if (pluginId.includes('text') || pluginId.includes('code') || pluginId.includes('editor')) return 'text';
119
+ return 'unknown';
120
+ }
121
+
122
+ // --- Sub-components (unchanged from original) ---
123
+
124
+ const PreviewHeader: React.FC<{
125
+ name: string;
126
+ size?: number;
127
+ icon?: 'text' | 'image' | 'unknown';
128
+ }> = ({ name, size, icon }) => {
129
+ const IconComponent = icon === 'image' ? ImageIcon : icon === 'text' ? FileText : FileQuestion;
130
+ return (
131
+ <div className="flex items-center gap-2 px-3 py-2 border-b bg-muted/20 flex-shrink-0 min-h-[36px]">
132
+ <IconComponent className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
133
+ <span className="text-xs font-medium truncate flex-1" title={name}>
134
+ {name}
135
+ </span>
136
+ {size != null && (
137
+ <span className="text-[10px] text-muted-foreground flex-shrink-0">
138
+ {size > 1024 * 1024
139
+ ? `${(size / (1024 * 1024)).toFixed(1)} MB`
140
+ : `${(size / 1024).toFixed(1)} KB`
141
+ }
142
+ </span>
143
+ )}
144
+ </div>
145
+ );
146
+ };
@@ -0,0 +1,219 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { ZoomIn, ZoomOut, RotateCcw, AlertCircle, Loader } from 'lucide-react';
4
+ import { PreviewUIModel } from '../../models/ui/PreviewUIModel';
5
+
6
+ interface FilePreviewProps {
7
+ previewUI: PreviewUIModel;
8
+ selectedItem?: any;
9
+ className?: string;
10
+ }
11
+
12
+ export const FilePreview: React.FC<FilePreviewProps> = observer(({
13
+ previewUI,
14
+ selectedItem,
15
+ className = ''
16
+ }) => {
17
+ const handleZoomIn = () => {
18
+ previewUI.zoomIn();
19
+ };
20
+
21
+ const handleZoomOut = () => {
22
+ previewUI.zoomOut();
23
+ };
24
+
25
+ const handleResetZoom = () => {
26
+ previewUI.resetZoom();
27
+ };
28
+
29
+ const handleRetryPreview = () => {
30
+ previewUI.loadPreview();
31
+ };
32
+
33
+ const renderPreviewContent = () => {
34
+ if (previewUI.isLoading) {
35
+ return (
36
+ <div className="file-preview-loading">
37
+ <Loader className="animate-spin" size={24} />
38
+ <p>Loading preview...</p>
39
+ </div>
40
+ );
41
+ }
42
+
43
+ if (previewUI.hasError) {
44
+ return (
45
+ <div className="file-preview-error">
46
+ <AlertCircle size={24} />
47
+ <p>Failed to load preview</p>
48
+ <p className="file-preview-error-message">{previewUI.error}</p>
49
+ <button
50
+ className="file-preview-retry-button"
51
+ onClick={handleRetryPreview}
52
+ >
53
+ Try Again
54
+ </button>
55
+ </div>
56
+ );
57
+ }
58
+
59
+ if (!previewUI.hasContent) {
60
+ return (
61
+ <div className="file-preview-empty">
62
+ <p>No preview available</p>
63
+ {selectedItem && (
64
+ <p className="file-preview-file-info">
65
+ {selectedItem.name} ({selectedItem.size ? formatFileSize(selectedItem.size) : 'Unknown size'})
66
+ </p>
67
+ )}
68
+ </div>
69
+ );
70
+ }
71
+
72
+ // Render preview based on file type
73
+ return renderPreviewByType();
74
+ };
75
+
76
+ const renderPreviewByType = () => {
77
+ const content = previewUI.content;
78
+
79
+ if (!content) return null;
80
+
81
+ switch (content.type) {
82
+ case 'text':
83
+ return (
84
+ <div className="file-preview-text" style={{ zoom: previewUI.zoom }}>
85
+ <pre className="file-preview-text-content">
86
+ <code>{content.content}</code>
87
+ </pre>
88
+ </div>
89
+ );
90
+
91
+ case 'image':
92
+ return (
93
+ <div className="file-preview-image">
94
+ <img
95
+ src={content.content || '/placeholder-image.png'}
96
+ alt={selectedItem?.name || 'Preview'}
97
+ style={{
98
+ transform: `scale(${previewUI.zoom})`,
99
+ transformOrigin: 'top left'
100
+ }}
101
+ className="file-preview-image-content"
102
+ />
103
+ {content.metadata && (
104
+ <div className="file-preview-image-metadata">
105
+ {content.metadata.width && content.metadata.height && (
106
+ <span>{content.metadata.width} × {content.metadata.height}</span>
107
+ )}
108
+ {content.metadata.format && (
109
+ <span>{content.metadata.format}</span>
110
+ )}
111
+ </div>
112
+ )}
113
+ </div>
114
+ );
115
+
116
+ case 'document':
117
+ return (
118
+ <div className="file-preview-document" style={{ zoom: previewUI.zoom }}>
119
+ <div className="file-preview-document-content">
120
+ <pre>{content.content}</pre>
121
+ </div>
122
+ {content.metadata && (
123
+ <div className="file-preview-document-metadata">
124
+ {content.metadata.pages && (
125
+ <span>{content.metadata.pages} page{content.metadata.pages !== 1 ? 's' : ''}</span>
126
+ )}
127
+ {content.metadata.format && (
128
+ <span>{content.metadata.format}</span>
129
+ )}
130
+ </div>
131
+ )}
132
+ </div>
133
+ );
134
+
135
+ default:
136
+ return (
137
+ <div className="file-preview-unsupported">
138
+ <p>Preview not supported for this file type</p>
139
+ <p>{content.content}</p>
140
+ </div>
141
+ );
142
+ }
143
+ };
144
+
145
+ const renderZoomControls = () => {
146
+ if (!previewUI.canZoom) return null;
147
+
148
+ return (
149
+ <div className="file-preview-zoom-controls">
150
+ <button
151
+ className="file-preview-zoom-button"
152
+ onClick={handleZoomOut}
153
+ disabled={!previewUI.canZoomOut}
154
+ title="Zoom out"
155
+ aria-label="Zoom out"
156
+ >
157
+ <ZoomOut size={16} />
158
+ </button>
159
+
160
+ <span className="file-preview-zoom-level">
161
+ {previewUI.zoomPercentage}%
162
+ </span>
163
+
164
+ <button
165
+ className="file-preview-zoom-button"
166
+ onClick={handleZoomIn}
167
+ disabled={!previewUI.canZoomIn}
168
+ title="Zoom in"
169
+ aria-label="Zoom in"
170
+ >
171
+ <ZoomIn size={16} />
172
+ </button>
173
+
174
+ <button
175
+ className="file-preview-zoom-button"
176
+ onClick={handleResetZoom}
177
+ title="Reset zoom"
178
+ aria-label="Reset zoom"
179
+ >
180
+ <RotateCcw size={16} />
181
+ </button>
182
+ </div>
183
+ );
184
+ };
185
+
186
+ return (
187
+ <div className={`file-preview ${className}`}>
188
+ {/* Preview Header */}
189
+ <div className="file-preview-header">
190
+ <div className="file-preview-title">
191
+ {selectedItem?.name || 'No file selected'}
192
+ </div>
193
+
194
+ {renderZoomControls()}
195
+ </div>
196
+
197
+ {/* Preview Content */}
198
+ <div className="file-preview-content">
199
+ {renderPreviewContent()}
200
+ </div>
201
+ </div>
202
+ );
203
+ });
204
+
205
+ // Helper function
206
+ function formatFileSize(bytes: number): string {
207
+ const units = ['B', 'KB', 'MB', 'GB'];
208
+ let size = bytes;
209
+ let unitIndex = 0;
210
+
211
+ while (size >= 1024 && unitIndex < units.length - 1) {
212
+ size /= 1024;
213
+ unitIndex++;
214
+ }
215
+
216
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
217
+ }
218
+
219
+ FilePreview.displayName = 'FilePreview';