@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,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';
|