@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,77 @@
1
+ import type { IContactProvider, ContactItem, ContactGroup } from './types';
2
+
3
+ const FIRST_NAMES = ['Alice', 'Bob', 'Charlie', 'Diana', 'Emma', 'Frank', 'Grace', 'Henry', 'Iris', 'Jack', 'Karen', 'Leo', 'Mia', 'Nathan', 'Olivia', 'Paul', 'Quinn', 'Rachel', 'Sam', 'Tina', 'Uma', 'Victor', 'Wendy', 'Xavier', 'Yara', 'Zach'];
4
+ const LAST_NAMES = ['Anderson', 'Baker', 'Chen', 'Davis', 'Evans', 'Fisher', 'Garcia', 'Hall', 'Ibrahim', 'Jones', 'Kim', 'Lee', 'Martinez', 'Nguyen', 'O\'Brien', 'Park', 'Quinn', 'Robinson', 'Smith', 'Taylor', 'Ueda', 'Vega', 'Wilson', 'Xu', 'Yang', 'Zhang'];
5
+ const COMPANIES = ['Acme Corp', 'TechStart Inc', 'Global Systems', 'DataFlow', 'CloudNine', 'PixelPerfect'];
6
+ const GROUPS_DATA: ContactGroup[] = [
7
+ { id: 'family', name: 'Family', count: 8 },
8
+ { id: 'work', name: 'Work', count: 12 },
9
+ { id: 'friends', name: 'Friends', count: 10 },
10
+ { id: 'vip', name: 'VIP', count: 5 },
11
+ ];
12
+
13
+ function generateContacts(): ContactItem[] {
14
+ const contacts: ContactItem[] = [];
15
+ const now = new Date();
16
+ for (let i = 0; i < FIRST_NAMES.length; i++) {
17
+ contacts.push({
18
+ id: `contact-${i}`,
19
+ type: 'contact',
20
+ title: `${FIRST_NAMES[i]} ${LAST_NAMES[i]}`,
21
+ firstName: FIRST_NAMES[i],
22
+ lastName: LAST_NAMES[i],
23
+ email: `${FIRST_NAMES[i].toLowerCase()}.${LAST_NAMES[i].toLowerCase()}@example.com`,
24
+ phone: `+1 (555) ${String(100 + i).padStart(3, '0')}-${String(1000 + i * 37).slice(0, 4)}`,
25
+ company: COMPANIES[i % COMPANIES.length],
26
+ address: `${100 + i} Main St, City ${i % 10}, ST ${10000 + i}`,
27
+ birthday: new Date(1985 + (i % 20), i % 12, 1 + (i % 28)),
28
+ groups: [GROUPS_DATA[i % GROUPS_DATA.length].id],
29
+ createdAt: now,
30
+ updatedAt: now,
31
+ });
32
+ }
33
+ return contacts;
34
+ }
35
+
36
+ export class MockContactProvider implements IContactProvider {
37
+ private contacts = generateContacts();
38
+
39
+ async listItems() { return this.contacts; }
40
+
41
+ async getItem(id: string) { return this.contacts.find(c => c.id === id) ?? null; }
42
+
43
+ async createItem(item: Omit<ContactItem, 'id' | 'createdAt' | 'updatedAt'>) {
44
+ const now = new Date();
45
+ const created: ContactItem = { ...item, id: `contact-${Date.now()}`, createdAt: now, updatedAt: now } as ContactItem;
46
+ this.contacts.push(created);
47
+ return created;
48
+ }
49
+
50
+ async updateItem(id: string, updates: Partial<ContactItem>) {
51
+ const idx = this.contacts.findIndex(c => c.id === id);
52
+ if (idx === -1) throw new Error('Not found');
53
+ this.contacts[idx] = { ...this.contacts[idx], ...updates, updatedAt: new Date() };
54
+ return this.contacts[idx];
55
+ }
56
+
57
+ async deleteItem(id: string) {
58
+ this.contacts = this.contacts.filter(c => c.id !== id);
59
+ }
60
+
61
+ async search(query: string) {
62
+ const q = query.toLowerCase();
63
+ return this.contacts.filter(c =>
64
+ c.firstName.toLowerCase().includes(q) || c.lastName.toLowerCase().includes(q)
65
+ );
66
+ }
67
+
68
+ async getGroups() { return GROUPS_DATA; }
69
+
70
+ async getByGroup(groupId: string) {
71
+ return this.contacts.filter(c => c.groups?.includes(groupId));
72
+ }
73
+
74
+ async searchByName(name: string) {
75
+ return this.search(name);
76
+ }
77
+ }
@@ -0,0 +1,17 @@
1
+ // Types
2
+ export type { ContactItem, ContactGroup, IContactProvider } from './types';
3
+
4
+ // Model
5
+ export { ContactListModel } from './ContactListModel';
6
+ export type { ContactSortBy } from './ContactListModel';
7
+
8
+ // Components
9
+ export { ContactBrowser, type ContactBrowserProps } from './ContactBrowser';
10
+ export { ContactList, type ContactListProps } from './ContactList';
11
+ export { ContactCard, type ContactCardProps } from './ContactCard';
12
+ export { ContactDetail, type ContactDetailProps } from './ContactDetail';
13
+ export { ContactGroupSidebar, type ContactGroupSidebarProps } from './ContactGroupSidebar';
14
+ export { ContactAvatar, type ContactAvatarProps } from './ContactAvatar';
15
+
16
+ // Mock Provider
17
+ export { MockContactProvider } from './MockContactProvider';
@@ -0,0 +1,26 @@
1
+ import type { ObjectMetadata, IObjectProvider } from '../types-common';
2
+
3
+ export interface ContactItem extends ObjectMetadata {
4
+ type: 'contact';
5
+ firstName: string;
6
+ lastName: string;
7
+ email?: string;
8
+ phone?: string;
9
+ avatar?: string;
10
+ company?: string;
11
+ address?: string;
12
+ birthday?: Date;
13
+ groups?: string[];
14
+ }
15
+
16
+ export interface ContactGroup {
17
+ id: string;
18
+ name: string;
19
+ count: number;
20
+ }
21
+
22
+ export interface IContactProvider extends IObjectProvider<ContactItem> {
23
+ getGroups(): Promise<ContactGroup[]>;
24
+ getByGroup(groupId: string): Promise<ContactItem[]>;
25
+ searchByName(name: string): Promise<ContactItem[]>;
26
+ }
@@ -0,0 +1,15 @@
1
+ import React, { useMemo } from 'react';
2
+ import { CalendarBrowser } from '../calendar/CalendarBrowser';
3
+ import { CalendarModel } from '../calendar/CalendarModel';
4
+ import { MockCalendarProvider } from '../calendar/MockCalendarProvider';
5
+
6
+ export const CalendarBrowserDemo = () => {
7
+ const provider = useMemo(() => new MockCalendarProvider(), []);
8
+ const model = useMemo(() => new CalendarModel(provider), [provider]);
9
+
10
+ return (
11
+ <div className="h-[600px]">
12
+ <CalendarBrowser model={model} />
13
+ </div>
14
+ );
15
+ };
@@ -0,0 +1,15 @@
1
+ import React, { useMemo } from 'react';
2
+ import { ContactBrowser } from '../contacts/ContactBrowser';
3
+ import { ContactListModel } from '../contacts/ContactListModel';
4
+ import { MockContactProvider } from '../contacts/MockContactProvider';
5
+
6
+ export const ContactBrowserDemo = () => {
7
+ const provider = useMemo(() => new MockContactProvider(), []);
8
+ const model = useMemo(() => new ContactListModel(provider), [provider]);
9
+
10
+ return (
11
+ <div className="h-[600px]">
12
+ <ContactBrowser model={model} />
13
+ </div>
14
+ );
15
+ };
@@ -0,0 +1,15 @@
1
+ import React, { useMemo } from 'react';
2
+ import { MediaBrowser } from '../media/MediaBrowser';
3
+ import { MediaBrowserModel } from '../media/MediaBrowserModel';
4
+ import { MockMediaProvider } from '../media/MockMediaProvider';
5
+
6
+ export const MediaBrowserDemo = () => {
7
+ const provider = useMemo(() => new MockMediaProvider(), []);
8
+ const model = useMemo(() => new MediaBrowserModel(provider), [provider]);
9
+
10
+ return (
11
+ <div className="h-[600px]">
12
+ <MediaBrowser model={model} provider={provider} />
13
+ </div>
14
+ );
15
+ };
@@ -0,0 +1,371 @@
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 a document viewer
7
+ * with the dual-panel FileBrowser coordination system.
8
+ *
9
+ * This adapter:
10
+ * - Listens for file selections in the FileBrowser
11
+ * - Coordinates document viewer state with selection changes
12
+ * - Provides document-specific navigation and preview capabilities
13
+ * - Maintains selection synchronization between browser and viewer
14
+ */
15
+ export interface DocumentViewerOptions {
16
+ supportedFileTypes?: string[];
17
+ previewEnabled?: boolean;
18
+ autoOpen?: boolean;
19
+ maxFileSize?: number; // in bytes
20
+ enableCoordination?: boolean;
21
+ }
22
+
23
+ export interface DocumentInfo {
24
+ id: string;
25
+ filePath: string;
26
+ fileName: string;
27
+ fileType: string;
28
+ fileSize: number;
29
+ lastModified?: Date;
30
+ isSupported: boolean;
31
+ previewUrl?: string;
32
+ metadata?: Record<string, any>;
33
+ }
34
+
35
+ export class DocumentViewerAdapter {
36
+ // Configuration
37
+ private options: Required<DocumentViewerOptions>;
38
+
39
+ // State
40
+ currentDocument: DocumentInfo | null = null;
41
+ isLoading = false;
42
+ error: string | null = null;
43
+ previewUrl: string | null = null;
44
+
45
+ // Document cache for performance
46
+ private documentCache = new Map<string, DocumentInfo>();
47
+ private previewCache = new Map<string, string>();
48
+
49
+ // Coordination
50
+ private fileBrowserModel: FileBrowserModel;
51
+ private disposers: Array<() => void> = [];
52
+
53
+ constructor(
54
+ fileBrowserModel: FileBrowserModel,
55
+ options: DocumentViewerOptions = {}
56
+ ) {
57
+ this.fileBrowserModel = fileBrowserModel;
58
+ this.options = {
59
+ supportedFileTypes: ['.pdf', '.doc', '.docx', '.txt', '.md', '.html', '.rtf'],
60
+ previewEnabled: true,
61
+ autoOpen: true,
62
+ maxFileSize: 50 * 1024 * 1024, // 50MB
63
+ enableCoordination: true,
64
+ ...options
65
+ };
66
+
67
+ makeAutoObservable(this);
68
+
69
+ if (this.options.enableCoordination) {
70
+ this.setupCoordination();
71
+ }
72
+
73
+ this.logInfo('DocumentViewerAdapter initialized', {
74
+ supportedTypes: this.options.supportedFileTypes.length,
75
+ coordinationEnabled: this.options.enableCoordination
76
+ });
77
+ }
78
+
79
+ private setupCoordination() {
80
+ // Listen for selection changes in the FileBrowser
81
+ const selectionDisposer = reaction(
82
+ () => ({
83
+ selectedItems: Array.from(this.fileBrowserModel.selectionManager.selectedItems.keys()),
84
+ focusedItem: this.fileBrowserModel.selectionManager.focusedItem
85
+ }),
86
+ ({ selectedItems, focusedItem }) => {
87
+ this.handleSelectionChange(focusedItem);
88
+ },
89
+ { fireImmediately: true }
90
+ );
91
+
92
+ // Listen for navigation changes to clear document state when appropriate
93
+ const navigationDisposer = reaction(
94
+ () => this.fileBrowserModel.navigationManager.currentPath,
95
+ (currentPath) => {
96
+ this.handleNavigationChange(currentPath || null);
97
+ }
98
+ );
99
+
100
+ this.disposers.push(selectionDisposer, navigationDisposer);
101
+ }
102
+
103
+ private async handleSelectionChange(item: FileBrowserItem | null) {
104
+ if (!item) {
105
+ this.clearDocument();
106
+ return;
107
+ }
108
+
109
+ // Only handle file selections (not directories)
110
+ if (item.type !== 'file') {
111
+ this.clearDocument();
112
+ return;
113
+ }
114
+
115
+ // Check if file is supported
116
+ if (!this.isFileSupported(item.name)) {
117
+ this.clearDocument();
118
+ return;
119
+ }
120
+
121
+ // Check file size constraints
122
+ if (item.size && item.size > this.options.maxFileSize) {
123
+ this.setError(`File too large: ${this.formatFileSize(item.size)} (max: ${this.formatFileSize(this.options.maxFileSize)})`);
124
+ return;
125
+ }
126
+
127
+ try {
128
+ await this.loadDocument(item);
129
+ } catch (error) {
130
+ this.setError(`Failed to load document: ${error instanceof Error ? error.message : 'Unknown error'}`);
131
+ }
132
+ }
133
+
134
+ private handleNavigationChange(currentPath: string | null) {
135
+ // Clear document when navigating to a different directory
136
+ // This ensures the viewer doesn't show stale content
137
+ if (this.currentDocument && currentPath) {
138
+ const documentDir = this.getDirectoryPath(this.currentDocument.filePath);
139
+ if (documentDir !== currentPath) {
140
+ this.clearDocument();
141
+ }
142
+ }
143
+ }
144
+
145
+ private async loadDocument(item: FileBrowserItem): Promise<void> {
146
+ this.isLoading = true;
147
+ this.error = null;
148
+
149
+ try {
150
+ // Check cache first
151
+ let documentInfo = this.documentCache.get(item.id);
152
+
153
+ if (!documentInfo) {
154
+ // Create document info
155
+ documentInfo = await this.createDocumentInfo(item);
156
+ this.documentCache.set(item.id, documentInfo);
157
+ }
158
+
159
+ this.currentDocument = documentInfo;
160
+
161
+ // Load preview if enabled and not cached
162
+ if (this.options.previewEnabled && !this.previewCache.has(item.id)) {
163
+ await this.loadPreview(documentInfo);
164
+ }
165
+
166
+ this.logInfo('Document loaded successfully', {
167
+ fileName: documentInfo.fileName,
168
+ fileType: documentInfo.fileType,
169
+ hasPreview: !!this.previewUrl
170
+ });
171
+
172
+ } finally {
173
+ this.isLoading = false;
174
+ }
175
+ }
176
+
177
+ private async createDocumentInfo(item: FileBrowserItem): Promise<DocumentInfo> {
178
+ const fileExtension = this.getFileExtension(item.name);
179
+
180
+ return {
181
+ id: item.id,
182
+ filePath: item.path,
183
+ fileName: item.name,
184
+ fileType: fileExtension,
185
+ fileSize: item.size || 0,
186
+ lastModified: item.lastModified,
187
+ isSupported: this.isFileSupported(item.name),
188
+ metadata: await this.extractMetadata(item)
189
+ };
190
+ }
191
+
192
+ private async loadPreview(documentInfo: DocumentInfo): Promise<void> {
193
+ try {
194
+ // Check preview cache
195
+ const cachedPreview = this.previewCache.get(documentInfo.id);
196
+ if (cachedPreview) {
197
+ this.previewUrl = cachedPreview;
198
+ return;
199
+ }
200
+
201
+ // Generate preview URL based on file type
202
+ const previewUrl = await this.generatePreview(documentInfo);
203
+
204
+ if (previewUrl) {
205
+ this.previewCache.set(documentInfo.id, previewUrl);
206
+ this.previewUrl = previewUrl;
207
+ }
208
+
209
+ } catch (error) {
210
+ this.logError('Failed to load preview', error);
211
+ // Preview failure shouldn't prevent document loading
212
+ }
213
+ }
214
+
215
+ private async generatePreview(documentInfo: DocumentInfo): Promise<string | null> {
216
+ // This would integrate with actual preview generation service
217
+ // For demo purposes, we'll simulate different preview types
218
+
219
+ switch (documentInfo.fileType.toLowerCase()) {
220
+ case '.pdf':
221
+ return `/api/preview/pdf/${encodeURIComponent(documentInfo.filePath)}`;
222
+
223
+ case '.txt':
224
+ case '.md':
225
+ return `/api/preview/text/${encodeURIComponent(documentInfo.filePath)}`;
226
+
227
+ case '.doc':
228
+ case '.docx':
229
+ return `/api/preview/office/${encodeURIComponent(documentInfo.filePath)}`;
230
+
231
+ case '.html':
232
+ return documentInfo.filePath; // Direct viewing for HTML
233
+
234
+ default:
235
+ return null;
236
+ }
237
+ }
238
+
239
+ private async extractMetadata(item: FileBrowserItem): Promise<Record<string, any>> {
240
+ // This would integrate with actual metadata extraction service
241
+ const metadata: Record<string, any> = {
242
+ extractedAt: new Date().toISOString(),
243
+ fileName: item.name,
244
+ fileSize: item.size,
245
+ lastModified: item.lastModified?.toISOString()
246
+ };
247
+
248
+ // Add file-type specific metadata
249
+ const extension = this.getFileExtension(item.name).toLowerCase();
250
+ switch (extension) {
251
+ case '.pdf':
252
+ metadata.estimatedPages = Math.ceil((item.size || 0) / 1024); // Rough estimate
253
+ break;
254
+ case '.txt':
255
+ case '.md':
256
+ metadata.estimatedLines = Math.ceil((item.size || 0) / 50); // Rough estimate
257
+ break;
258
+ }
259
+
260
+ return metadata;
261
+ }
262
+
263
+ // Public API methods
264
+
265
+ /**
266
+ * Open a document by FileBrowser item
267
+ */
268
+ async openDocument(item: FileBrowserItem): Promise<void> {
269
+ // Use selection coordination to open the document
270
+ this.fileBrowserModel.selectionManager.selectItem(item.id);
271
+ }
272
+
273
+ /**
274
+ * Close the current document
275
+ */
276
+ closeDocument(): void {
277
+ this.clearDocument();
278
+ // Optionally clear selection in FileBrowser
279
+ this.fileBrowserModel.selectionManager.clearSelection();
280
+ }
281
+
282
+ /**
283
+ * Navigate to the document's location in FileBrowser
284
+ */
285
+ navigateToDocument(): void {
286
+ if (this.currentDocument) {
287
+ const directoryPath = this.getDirectoryPath(this.currentDocument.filePath);
288
+ this.fileBrowserModel.navigationManager.navigateToWithCoordination(
289
+ directoryPath,
290
+ 'breadcrumb'
291
+ );
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Check if a file type is supported
297
+ */
298
+ isFileSupported(fileName: string): boolean {
299
+ const extension = this.getFileExtension(fileName).toLowerCase();
300
+ return this.options.supportedFileTypes.includes(extension);
301
+ }
302
+
303
+ /**
304
+ * Get current document statistics
305
+ */
306
+ get documentStats() {
307
+ return {
308
+ cacheSize: this.documentCache.size,
309
+ previewCacheSize: this.previewCache.size,
310
+ currentDocument: this.currentDocument?.fileName || null,
311
+ isLoading: this.isLoading,
312
+ hasError: !!this.error
313
+ };
314
+ }
315
+
316
+ // Private utility methods
317
+
318
+ private clearDocument(): void {
319
+ this.currentDocument = null;
320
+ this.previewUrl = null;
321
+ this.error = null;
322
+ }
323
+
324
+ private setError(message: string): void {
325
+ this.error = message;
326
+ this.currentDocument = null;
327
+ this.previewUrl = null;
328
+ this.logError('DocumentViewer error', new Error(message));
329
+ }
330
+
331
+ private getFileExtension(fileName: string): string {
332
+ const lastDotIndex = fileName.lastIndexOf('.');
333
+ return lastDotIndex !== -1 ? fileName.substring(lastDotIndex) : '';
334
+ }
335
+
336
+ private getDirectoryPath(filePath: string): string {
337
+ const lastSlashIndex = filePath.lastIndexOf('/');
338
+ return lastSlashIndex !== -1 ? filePath.substring(0, lastSlashIndex) : '/';
339
+ }
340
+
341
+ private formatFileSize(bytes: number): string {
342
+ const units = ['B', 'KB', 'MB', 'GB'];
343
+ let size = bytes;
344
+ let unitIndex = 0;
345
+
346
+ while (size >= 1024 && unitIndex < units.length - 1) {
347
+ size /= 1024;
348
+ unitIndex++;
349
+ }
350
+
351
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
352
+ }
353
+
354
+ private logInfo(message: string, data?: any): void {
355
+ this.fileBrowserModel.logger?.info(`[DocumentViewerAdapter] ${message}`, data);
356
+ }
357
+
358
+ private logError(message: string, error: any): void {
359
+ this.fileBrowserModel.logger?.error(`[DocumentViewerAdapter] ${message}`, error);
360
+ }
361
+
362
+ // Cleanup
363
+ dispose(): void {
364
+ this.disposers.forEach(dispose => dispose());
365
+ this.disposers = [];
366
+ this.documentCache.clear();
367
+ this.previewCache.clear();
368
+ this.clearDocument();
369
+ this.logInfo('DocumentViewerAdapter disposed');
370
+ }
371
+ }