@anymux/ui-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ExplorerLayout-CSIJd7N4.js +105 -0
- package/dist/ExplorerLayout-CSIJd7N4.js.map +1 -0
- package/dist/FileBrowserContext-B6jixa2j.js +11 -0
- package/dist/FileBrowserContext-B6jixa2j.js.map +1 -0
- package/dist/calendar-DSlrbHoj.js +761 -0
- package/dist/calendar-DSlrbHoj.js.map +1 -0
- package/dist/calendar.d.ts +3 -0
- package/dist/calendar.js +3 -0
- package/dist/contacts-DQXTZzHc.js +539 -0
- package/dist/contacts-DQXTZzHc.js.map +1 -0
- package/dist/contacts.d.ts +3 -0
- package/dist/contacts.js +3 -0
- package/dist/file-browser-m5atC3kF.js +6755 -0
- package/dist/file-browser-m5atC3kF.js.map +1 -0
- package/dist/file-browser.d.ts +11 -0
- package/dist/file-browser.js +9 -0
- package/dist/git-B55e6LL-.js +561 -0
- package/dist/git-B55e6LL-.js.map +1 -0
- package/dist/git.d.ts +2 -0
- package/dist/git.js +3 -0
- package/dist/iconMap-V4B8P-Uh.js +206 -0
- package/dist/iconMap-V4B8P-Uh.js.map +1 -0
- package/dist/icons-CIsIOZXR.js +0 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +4 -0
- package/dist/index-BNmNIWBL.d.ts +71 -0
- package/dist/index-BNmNIWBL.d.ts.map +1 -0
- package/dist/index-Bryv_GCG.d.ts +1481 -0
- package/dist/index-Bryv_GCG.d.ts.map +1 -0
- package/dist/index-CuQIjSXs.d.ts +134 -0
- package/dist/index-CuQIjSXs.d.ts.map +1 -0
- package/dist/index-DSu19mq0.d.ts +153 -0
- package/dist/index-DSu19mq0.d.ts.map +1 -0
- package/dist/index-DmsyeHFr.d.ts +149 -0
- package/dist/index-DmsyeHFr.d.ts.map +1 -0
- package/dist/index-DxnJ8FYM.d.ts +17 -0
- package/dist/index-DxnJ8FYM.d.ts.map +1 -0
- package/dist/index-DzfY1Tok.d.ts +32 -0
- package/dist/index-DzfY1Tok.d.ts.map +1 -0
- package/dist/index-Ml_SgiKa.d.ts +1847 -0
- package/dist/index-Ml_SgiKa.d.ts.map +1 -0
- package/dist/index-kHr9udZD.d.ts +1025 -0
- package/dist/index-kHr9udZD.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +15 -0
- package/dist/layout-Ca_4r8ka.js +89 -0
- package/dist/layout-Ca_4r8ka.js.map +1 -0
- package/dist/layout.d.ts +2 -0
- package/dist/layout.js +5 -0
- package/dist/list-CxfT6hix.js +6831 -0
- package/dist/list-CxfT6hix.js.map +1 -0
- package/dist/list.d.ts +2 -0
- package/dist/list.js +5 -0
- package/dist/media-DZ292aKK.js +557 -0
- package/dist/media-DZ292aKK.js.map +1 -0
- package/dist/media.d.ts +3 -0
- package/dist/media.js +3 -0
- package/dist/tree-Dd9Z0Aso.js +3351 -0
- package/dist/tree-Dd9Z0Aso.js.map +1 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.js +6 -0
- package/dist/types-common-CB3kRek8.d.ts +26 -0
- package/dist/types-common-CB3kRek8.d.ts.map +1 -0
- package/dist/utils-B4fdKKsy.js +3 -0
- package/package.json +109 -0
- package/src/calendar/AgendaView.tsx +37 -0
- package/src/calendar/CalendarBrowser.tsx +90 -0
- package/src/calendar/CalendarModel.ts +142 -0
- package/src/calendar/CalendarSidebar.tsx +81 -0
- package/src/calendar/DayView.tsx +76 -0
- package/src/calendar/EventCard.tsx +51 -0
- package/src/calendar/MockCalendarProvider.ts +98 -0
- package/src/calendar/MonthView.tsx +77 -0
- package/src/calendar/WeekView.tsx +129 -0
- package/src/calendar/index.ts +18 -0
- package/src/calendar/types.ts +25 -0
- package/src/contacts/ContactAvatar.tsx +35 -0
- package/src/contacts/ContactBrowser.tsx +56 -0
- package/src/contacts/ContactCard.tsx +37 -0
- package/src/contacts/ContactDetail.tsx +63 -0
- package/src/contacts/ContactGroupSidebar.tsx +40 -0
- package/src/contacts/ContactList.tsx +32 -0
- package/src/contacts/ContactListModel.ts +120 -0
- package/src/contacts/MockContactProvider.ts +77 -0
- package/src/contacts/index.ts +17 -0
- package/src/contacts/types.ts +26 -0
- package/src/demos/CalendarBrowserDemo.tsx +15 -0
- package/src/demos/ContactBrowserDemo.tsx +15 -0
- package/src/demos/MediaBrowserDemo.tsx +15 -0
- package/src/file-browser/adapters/DocumentViewerAdapter.ts +371 -0
- package/src/file-browser/adapters/FileSystemBridge.ts +168 -0
- package/src/file-browser/adapters/GitBrowserAdapter.ts +546 -0
- package/src/file-browser/adapters/README.md +504 -0
- package/src/file-browser/adapters/index.ts +27 -0
- package/src/file-browser/adapters/types.ts +70 -0
- package/src/file-browser/architecture.md +645 -0
- package/src/file-browser/components/CreateItemDialog.tsx +71 -0
- package/src/file-browser/components/DeleteConfirmDialog.tsx +58 -0
- package/src/file-browser/components/FileBrowser.tsx +473 -0
- package/src/file-browser/components/FileBrowserContent.tsx +209 -0
- package/src/file-browser/components/FileBrowserHeader.tsx +151 -0
- package/src/file-browser/components/FileBrowserToolbar.tsx +145 -0
- package/src/file-browser/components/LeftPanel/LeftPanel.tsx +103 -0
- package/src/file-browser/components/LeftPanel/LeftPanelTabs.tsx +70 -0
- package/src/file-browser/components/LeftPanel/TreeNavigationView.tsx +256 -0
- package/src/file-browser/components/PreviewPane.tsx +146 -0
- package/src/file-browser/components/RightPanel/FilePreview.tsx +219 -0
- package/src/file-browser/components/RightPanel/RightPanel.tsx +186 -0
- package/src/file-browser/components/RightPanel/RightPanelToolbar.tsx +113 -0
- package/src/file-browser/components/UploadProgress.tsx +123 -0
- package/src/file-browser/components/ViewerHost.tsx +208 -0
- package/src/file-browser/components/mobile/MobileNavigation.tsx +227 -0
- package/src/file-browser/components/navigation/NavigationButtons.tsx +171 -0
- package/src/file-browser/components/shared/ErrorBoundary.tsx +116 -0
- package/src/file-browser/components/shared/FileBrowserItem.tsx +195 -0
- package/src/file-browser/components/shared/FileIcon.tsx +169 -0
- package/src/file-browser/components/toolbar/ViewModeToggle.tsx +200 -0
- package/src/file-browser/components/views/ListView/ListView.tsx +484 -0
- package/src/file-browser/components/views/ThumbnailView/ThumbnailView.tsx +323 -0
- package/src/file-browser/components/views/TreeView/TreeNode.tsx +186 -0
- package/src/file-browser/components/views/TreeView/TreeNodeList.tsx +191 -0
- package/src/file-browser/components/views/TreeView/TreeView.tsx +200 -0
- package/src/file-browser/components/views/TreemapView/TreemapView.tsx +339 -0
- package/src/file-browser/context/FileBrowserContext.tsx +13 -0
- package/src/file-browser/examples/BasicUsage.tsx +20 -0
- package/src/file-browser/index.ts +98 -0
- package/src/file-browser/models/FileBrowserModel.ts +623 -0
- package/src/file-browser/models/LeftPanelManagerModel.ts +105 -0
- package/src/file-browser/models/NavigationManagerModel.ts +312 -0
- package/src/file-browser/models/ResponsiveLayoutManagerModel.ts +437 -0
- package/src/file-browser/models/RightPanelManagerModel.ts +190 -0
- package/src/file-browser/models/SelectionManagerModel.ts +252 -0
- package/src/file-browser/models/ToolbarManagerModel.ts +144 -0
- package/src/file-browser/models/UploadModel.ts +147 -0
- package/src/file-browser/models/ViewModeManagerModel.ts +185 -0
- package/src/file-browser/models/ViewerHostModel.ts +44 -0
- package/src/file-browser/models/ui/ListViewUIModel.ts +265 -0
- package/src/file-browser/models/ui/PreviewUIModel.ts +297 -0
- package/src/file-browser/models/ui/ThumbnailViewUIModel.ts +254 -0
- package/src/file-browser/models/ui/TreeViewUIModel.ts +128 -0
- package/src/file-browser/models/ui/TreemapViewUIModel.ts +350 -0
- package/src/file-browser/providers/FileSystemListProvider.ts +552 -0
- package/src/file-browser/providers/FileSystemProvider.ts +401 -0
- package/src/file-browser/providers/FileSystemTreeProvider.ts +231 -0
- package/src/file-browser/providers/GitProvider.ts +337 -0
- package/src/file-browser/providers/GitRepositoryProvider.ts +376 -0
- package/src/file-browser/providers/IFileBrowserProvider.ts +56 -0
- package/src/file-browser/providers/MemoryProvider.ts +303 -0
- package/src/file-browser/providers/index.ts +4 -0
- package/src/file-browser/registry/ViewerRegistry.ts +551 -0
- package/src/file-browser/registry/types.ts +144 -0
- package/src/file-browser/scripts/performanceBenchmark.ts +553 -0
- package/src/file-browser/services/ThumbnailCacheService.ts +128 -0
- package/src/file-browser/tasks.md +537 -0
- package/src/file-browser/types/FileBrowserTypes.ts +126 -0
- package/src/file-browser/types/ProviderTypes.ts +155 -0
- package/src/file-browser/types/UITypes.ts +235 -0
- package/src/file-browser/types/ViewModeTypes.ts +150 -0
- package/src/file-browser/utils/gestures.ts +327 -0
- package/src/file-browser/utils/performance.ts +563 -0
- package/src/file-browser/viewers/ImageViewer.tsx +163 -0
- package/src/file-browser/viewers/ImageViewerModel.ts +79 -0
- package/src/file-browser/viewers/TextViewer.tsx +95 -0
- package/src/file-browser/viewers/UnsupportedFileViewer.tsx +57 -0
- package/src/file-browser/viewers/index.ts +61 -0
- package/src/git/BranchList.tsx +128 -0
- package/src/git/CommitGraph.tsx +239 -0
- package/src/git/CommitList.tsx +258 -0
- package/src/git/DiffViewer.tsx +219 -0
- package/src/git/index.ts +4 -0
- package/src/icons/iconMap.ts +146 -0
- package/src/icons/index.ts +9 -0
- package/src/index.ts +13 -0
- package/src/layout/README.md +307 -0
- package/src/layout/components/ExplorerLayout/ExplorerLayout.tsx +178 -0
- package/src/layout/examples/SimpleExample.tsx +60 -0
- package/src/layout/index.ts +6 -0
- package/src/lib/utils.ts +1 -0
- package/src/list/README.md +303 -0
- package/src/list/architecture.md +807 -0
- package/src/list/components/CalculatedGridView.tsx +252 -0
- package/src/list/components/DragPreview.tsx +102 -0
- package/src/list/components/ListContextMenu.tsx +274 -0
- package/src/list/components/ListItem.tsx +761 -0
- package/src/list/components/ListItems.tsx +919 -0
- package/src/list/components/MasonryView.tsx +241 -0
- package/src/list/components/SearchFilter.tsx +44 -0
- package/src/list/components/TreemapView.tsx +709 -0
- package/src/list/components/ViewSizeControls.tsx +205 -0
- package/src/list/components/ViewTypeSelector.tsx +312 -0
- package/src/list/components/VirtualizedDetailsView.tsx +231 -0
- package/src/list/components/VirtualizedGrid.tsx +164 -0
- package/src/list/components/VirtualizedList.tsx +154 -0
- package/src/list/components/VirtualizedMasonryView.tsx +344 -0
- package/src/list/components/shared/EmptyState.tsx +103 -0
- package/src/list/components/shared/ErrorBoundary.tsx +123 -0
- package/src/list/components/shared/ErrorDisplay.tsx +100 -0
- package/src/list/components/shared/ListLoader.tsx +146 -0
- package/src/list/components/shared/LoadingIndicator.tsx +80 -0
- package/src/list/index.ts +92 -0
- package/src/list/models/ListItemsModel.ts +1301 -0
- package/src/list/models/TreemapModel.ts +204 -0
- package/src/list/providers/ListItemsProvider.ts +313 -0
- package/src/list/providers/TestListProvider.ts +604 -0
- package/src/list/tasks.md +937 -0
- package/src/list/types/ListTypes.ts +178 -0
- package/src/list/utils/BenchmarkLogger.ts +243 -0
- package/src/list/utils/DragDropManager.ts +320 -0
- package/src/list/utils/GridLayoutCalculator.ts +290 -0
- package/src/list/utils/ListAccessibility.ts +367 -0
- package/src/list/utils/ListKeyboard.ts +414 -0
- package/src/list/utils/MasonryLayoutCalculator.ts +302 -0
- package/src/list/utils/MasonryLayoutEngine.ts +401 -0
- package/src/list/utils/__tests__/MasonryLayoutEngine.test.ts +157 -0
- package/src/list/utils/__tests__/VirtualizedMasonryView.test.tsx +251 -0
- package/src/media/AlbumSidebar.tsx +48 -0
- package/src/media/MediaBrowser.tsx +92 -0
- package/src/media/MediaBrowserModel.ts +138 -0
- package/src/media/MediaGrid.tsx +50 -0
- package/src/media/MediaList.tsx +49 -0
- package/src/media/MediaPreview.tsx +63 -0
- package/src/media/MediaTimeline.tsx +38 -0
- package/src/media/MockMediaProvider.ts +70 -0
- package/src/media/index.ts +18 -0
- package/src/media/types.ts +21 -0
- package/src/styles/variables.css +60 -0
- package/src/tree/DEVELOPMENT_SUMMARY.md +170 -0
- package/src/tree/__tests__/TreeModel.test.ts +16 -0
- package/src/tree/architecture.md +530 -0
- package/src/tree/components/Tree.tsx +283 -0
- package/src/tree/components/TreeCheckbox.tsx +147 -0
- package/src/tree/components/TreeContextMenu.tsx +139 -0
- package/src/tree/components/TreeNodeList.tsx +329 -0
- package/src/tree/components/TreeTable.tsx +382 -0
- package/src/tree/index.ts +58 -0
- package/src/tree/models/TreeModel.ts +839 -0
- package/src/tree/providers/SimpleTreeProvider.ts +463 -0
- package/src/tree/providers/TestTreeProvider.ts +946 -0
- package/src/tree/providers/TreeProvider.ts +308 -0
- package/src/tree/tasks.md +2046 -0
- package/src/tree/types/TreeTypes.ts +279 -0
- package/src/tree/utils/SelectionTheme.ts +150 -0
- package/src/tree/utils/logger.ts +203 -0
- package/src/types-common.ts +21 -0
|
@@ -0,0 +1,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
|
+
}
|