@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,761 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { observer } from 'mobx-react-lite';
|
|
3
|
+
import { getItemAccessibilityProps } from '../utils/ListAccessibility';
|
|
4
|
+
import { ListItemData, ListViewType } from '../types/ListTypes';
|
|
5
|
+
import { ListItemsProvider } from '../providers/ListItemsProvider';
|
|
6
|
+
import { ListItemsModel } from '../models/ListItemsModel';
|
|
7
|
+
import { File } from 'lucide-react';
|
|
8
|
+
import { lucideIconMap, iconColorMap } from '../../icons/iconMap';
|
|
9
|
+
import { useFileBrowserContext } from '../../file-browser/context/FileBrowserContext';
|
|
10
|
+
|
|
11
|
+
// AICODE-NOTE: Basic list item component with click handling and flexible rendering
|
|
12
|
+
|
|
13
|
+
export interface ListItemProps {
|
|
14
|
+
item: ListItemData;
|
|
15
|
+
index: number;
|
|
16
|
+
totalItems: number;
|
|
17
|
+
viewType: ListViewType;
|
|
18
|
+
provider?: ListItemsProvider;
|
|
19
|
+
model?: ListItemsModel; // AICODE-NOTE: Model for debug visualization and other reactive state
|
|
20
|
+
itemWidth?: number;
|
|
21
|
+
itemHeight?: number;
|
|
22
|
+
thumbnailSize?: number; // AICODE-NOTE: Precise thumbnail size from calculator
|
|
23
|
+
isSelected?: boolean;
|
|
24
|
+
isFocused?: boolean;
|
|
25
|
+
isDraggedOver?: boolean;
|
|
26
|
+
dragOverPosition?: 'before' | 'after' | 'inside' | null;
|
|
27
|
+
canDrag?: boolean;
|
|
28
|
+
onClick?: (item: ListItemData, event: React.MouseEvent) => void;
|
|
29
|
+
onDoubleClick?: (item: ListItemData, event: React.MouseEvent) => void;
|
|
30
|
+
onContextMenu?: (item: ListItemData, event: React.MouseEvent) => void;
|
|
31
|
+
onDragStart?: (item: ListItemData, event: React.DragEvent) => void;
|
|
32
|
+
onDragOver?: (item: ListItemData, event: React.DragEvent) => void;
|
|
33
|
+
onDragLeave?: (item: ListItemData, event: React.DragEvent) => void;
|
|
34
|
+
onDrop?: (item: ListItemData, event: React.DragEvent) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Inline rename input component
|
|
38
|
+
const InlineRenameInput: React.FC<{
|
|
39
|
+
currentName: string;
|
|
40
|
+
onCommit: (newName: string) => void;
|
|
41
|
+
onCancel: () => void;
|
|
42
|
+
className?: string;
|
|
43
|
+
}> = ({ currentName, onCommit, onCancel, className }) => {
|
|
44
|
+
const [value, setValue] = useState(currentName);
|
|
45
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
46
|
+
const mountTimeRef = useRef(Date.now());
|
|
47
|
+
const committedRef = useRef(false);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
mountTimeRef.current = Date.now();
|
|
51
|
+
const raf = requestAnimationFrame(() => {
|
|
52
|
+
const input = inputRef.current;
|
|
53
|
+
if (input) {
|
|
54
|
+
input.focus();
|
|
55
|
+
const dotIndex = currentName.lastIndexOf('.');
|
|
56
|
+
input.setSelectionRange(0, dotIndex > 0 ? dotIndex : currentName.length);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return () => cancelAnimationFrame(raf);
|
|
60
|
+
}, [currentName]);
|
|
61
|
+
|
|
62
|
+
const commit = () => {
|
|
63
|
+
if (committedRef.current) return;
|
|
64
|
+
committedRef.current = true;
|
|
65
|
+
const trimmed = value.trim();
|
|
66
|
+
if (trimmed && trimmed !== currentName) {
|
|
67
|
+
onCommit(trimmed);
|
|
68
|
+
} else {
|
|
69
|
+
onCancel();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleBlur = () => {
|
|
74
|
+
if (Date.now() - mountTimeRef.current < 200) return;
|
|
75
|
+
commit();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<input
|
|
80
|
+
ref={inputRef}
|
|
81
|
+
value={value}
|
|
82
|
+
onChange={(e) => setValue(e.target.value)}
|
|
83
|
+
onKeyDown={(e) => {
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
if (e.key === 'Enter') { e.preventDefault(); commit(); }
|
|
86
|
+
if (e.key === 'Escape') { e.preventDefault(); onCancel(); }
|
|
87
|
+
}}
|
|
88
|
+
onBlur={handleBlur}
|
|
89
|
+
onClick={(e) => e.stopPropagation()}
|
|
90
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
91
|
+
className={`bg-background border border-primary rounded px-1 py-0 text-sm outline-none ${className || ''}`}
|
|
92
|
+
style={{ minWidth: 60, maxWidth: '100%' }}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// AICODE-NOTE: Memoized component for performance optimization
|
|
98
|
+
const ListItemComponent = observer<ListItemProps>(({
|
|
99
|
+
item,
|
|
100
|
+
index,
|
|
101
|
+
totalItems,
|
|
102
|
+
viewType,
|
|
103
|
+
provider,
|
|
104
|
+
model,
|
|
105
|
+
itemWidth,
|
|
106
|
+
itemHeight,
|
|
107
|
+
thumbnailSize,
|
|
108
|
+
isSelected = false,
|
|
109
|
+
isFocused = false,
|
|
110
|
+
isDraggedOver = false,
|
|
111
|
+
dragOverPosition = null,
|
|
112
|
+
canDrag = false,
|
|
113
|
+
onClick,
|
|
114
|
+
onDoubleClick,
|
|
115
|
+
onContextMenu,
|
|
116
|
+
onDragStart,
|
|
117
|
+
onDragOver,
|
|
118
|
+
onDragLeave,
|
|
119
|
+
onDrop
|
|
120
|
+
}) => {
|
|
121
|
+
// Inline rename support
|
|
122
|
+
const fileBrowserCtx = useFileBrowserContext();
|
|
123
|
+
const isRenaming = fileBrowserCtx?.renameState?.itemId === item.id && fileBrowserCtx?.renameState?.source === 'list';
|
|
124
|
+
|
|
125
|
+
// Resolve thumbnail blob URL from provider cache (reactive via MobX)
|
|
126
|
+
const resolvedThumbnailUrl = item.thumbnailUrl && model ? model.resolveThumbnailUrl(item) : item.imageUrl;
|
|
127
|
+
|
|
128
|
+
const renderNameOrRename = (extraClass?: string) => {
|
|
129
|
+
if (isRenaming && fileBrowserCtx) {
|
|
130
|
+
return (
|
|
131
|
+
<InlineRenameInput
|
|
132
|
+
currentName={fileBrowserCtx.renameState!.currentName}
|
|
133
|
+
onCommit={fileBrowserCtx.onRenameCommit}
|
|
134
|
+
onCancel={fileBrowserCtx.onRenameCancel}
|
|
135
|
+
className={extraClass}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// AICODE-NOTE: Generate accessibility props
|
|
143
|
+
const accessibilityProps = getItemAccessibilityProps({
|
|
144
|
+
item,
|
|
145
|
+
index,
|
|
146
|
+
totalItems,
|
|
147
|
+
isSelected,
|
|
148
|
+
isFocused,
|
|
149
|
+
viewType: viewType.id
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// AICODE-NOTE: Handle clicks with proper event handling and modifiers
|
|
153
|
+
const handleClick = (event: React.MouseEvent) => {
|
|
154
|
+
if (onClick) {
|
|
155
|
+
onClick(item, event);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const handleDoubleClick = (event: React.MouseEvent) => {
|
|
160
|
+
if (onDoubleClick) {
|
|
161
|
+
onDoubleClick(item, event);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const handleContextMenu = (event: React.MouseEvent) => {
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
if (onContextMenu) {
|
|
168
|
+
onContextMenu(item, event);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// AICODE-NOTE: Drag and drop event handlers
|
|
173
|
+
const handleDragStart = (event: React.DragEvent) => {
|
|
174
|
+
if (onDragStart) {
|
|
175
|
+
onDragStart(item, event);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const handleDragOver = (event: React.DragEvent) => {
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
if (onDragOver) {
|
|
182
|
+
onDragOver(item, event);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const handleDragLeave = (event: React.DragEvent) => {
|
|
187
|
+
if (onDragLeave) {
|
|
188
|
+
onDragLeave(item, event);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const handleDrop = (event: React.DragEvent) => {
|
|
193
|
+
event.preventDefault();
|
|
194
|
+
if (onDrop) {
|
|
195
|
+
onDrop(item, event);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// AICODE-NOTE: Import shared resolveIcon for consistent icon rendering
|
|
200
|
+
const resolveItemIcon = (iconName: string | undefined, sizeClass = 'w-4 h-4') => {
|
|
201
|
+
const name = iconName || 'file';
|
|
202
|
+
const MappedIcon = lucideIconMap[name] || File;
|
|
203
|
+
const colorClass = iconColorMap[name] || 'text-gray-400';
|
|
204
|
+
return <MappedIcon className={`${sizeClass} ${colorClass}`} />;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// AICODE-NOTE: Render icon using provider's getItemIcon method
|
|
208
|
+
const renderIcon = (sizeClass = 'w-4 h-4') => {
|
|
209
|
+
const customIcon = provider?.getItemIcon?.(item);
|
|
210
|
+
if (customIcon) {
|
|
211
|
+
if (typeof customIcon === 'string') {
|
|
212
|
+
return resolveItemIcon(customIcon, sizeClass);
|
|
213
|
+
} else {
|
|
214
|
+
const IconComponent = customIcon;
|
|
215
|
+
return <IconComponent className={sizeClass} />;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (item.icon) {
|
|
219
|
+
return resolveItemIcon(item.icon, sizeClass);
|
|
220
|
+
}
|
|
221
|
+
return <File className={`${sizeClass} text-muted-foreground`} />;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// AICODE-NOTE: Render image or icon for list/details views
|
|
225
|
+
const renderImageOrIcon = (size: 'small' | 'medium' | 'large' = 'medium') => {
|
|
226
|
+
const iconSizeClasses = {
|
|
227
|
+
small: 'w-4 h-4',
|
|
228
|
+
medium: 'w-8 h-8',
|
|
229
|
+
large: 'w-12 h-12'
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const containerClasses = {
|
|
233
|
+
small: 'h-6 w-6',
|
|
234
|
+
medium: 'h-12 w-12',
|
|
235
|
+
large: 'h-16 w-16'
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const imgSrc = resolvedThumbnailUrl || item.imageUrl;
|
|
239
|
+
const pxSizes = { small: 24, medium: 48, large: 64 };
|
|
240
|
+
const px = pxSizes[size];
|
|
241
|
+
if (imgSrc) {
|
|
242
|
+
return (
|
|
243
|
+
<img
|
|
244
|
+
src={imgSrc}
|
|
245
|
+
alt={item.name}
|
|
246
|
+
className="object-cover rounded flex-shrink-0"
|
|
247
|
+
style={{ width: px, height: px, maxWidth: px, maxHeight: px }}
|
|
248
|
+
loading="lazy"
|
|
249
|
+
onError={(e) => {
|
|
250
|
+
const target = e.target as HTMLImageElement;
|
|
251
|
+
target.style.display = 'none';
|
|
252
|
+
target.nextElementSibling?.classList.remove('hidden');
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<div className={`${containerClasses[size]} flex items-center justify-center`}>
|
|
260
|
+
{renderIcon(iconSizeClasses[size])}
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// AICODE-NOTE: Get item dimensions for variable sizing
|
|
266
|
+
const getItemDimensions = () => {
|
|
267
|
+
if (item.getDimensions) {
|
|
268
|
+
return item.getDimensions();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (item.aspectRatio && itemWidth) {
|
|
272
|
+
return {
|
|
273
|
+
width: itemWidth,
|
|
274
|
+
height: itemWidth / item.aspectRatio
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return null;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// AICODE-NOTE: Enhanced styles with better selection and focus states
|
|
282
|
+
const baseClasses = `
|
|
283
|
+
flex items-center gap-2 py-1 px-2 text-[13px] cursor-default select-none transition-colors duration-200
|
|
284
|
+
hover:bg-muted/50 border border-transparent rounded-md
|
|
285
|
+
${isSelected ? 'bg-primary/10 border-primary/20' : ''}
|
|
286
|
+
${isFocused ? 'ring-2 ring-primary/50' : ''}
|
|
287
|
+
${isDraggedOver ? 'bg-accent/20 border-accent' : ''}
|
|
288
|
+
${dragOverPosition === 'before' ? 'border-t-2 border-t-primary' : ''}
|
|
289
|
+
${dragOverPosition === 'after' ? 'border-b-2 border-b-primary' : ''}
|
|
290
|
+
${dragOverPosition === 'inside' ? 'bg-primary/5 border-primary/30' : ''}
|
|
291
|
+
`;
|
|
292
|
+
|
|
293
|
+
// AICODE-NOTE: Grid-specific styles with better spacing and sizing
|
|
294
|
+
const gridClasses = `
|
|
295
|
+
flex flex-col items-center justify-start p-3 cursor-default select-none transition-colors duration-200
|
|
296
|
+
hover:bg-muted/50 border border-transparent rounded-lg min-h-[140px] max-w-full
|
|
297
|
+
${isSelected ? 'bg-primary/10 border-primary/20 shadow-sm' : ''}
|
|
298
|
+
${isFocused ? 'ring-2 ring-primary/50' : ''}
|
|
299
|
+
${isDraggedOver ? 'bg-accent/20 border-accent' : ''}
|
|
300
|
+
${dragOverPosition === 'before' ? 'border-t-2 border-t-primary' : ''}
|
|
301
|
+
${dragOverPosition === 'after' ? 'border-b-2 border-b-primary' : ''}
|
|
302
|
+
${dragOverPosition === 'inside' ? 'bg-primary/5 border-primary/30' : ''}
|
|
303
|
+
`;
|
|
304
|
+
|
|
305
|
+
// AICODE-NOTE: Debug styles for layout visualization
|
|
306
|
+
const isDebugMode = model?.debugVisualization ?? false;
|
|
307
|
+
|
|
308
|
+
const debugStyles = isDebugMode ? {
|
|
309
|
+
container: { backgroundColor: 'rgba(0, 255, 255, 0.05)', border: '2px solid rgba(0, 255, 255, 0.3)' },
|
|
310
|
+
imageContainer: { backgroundColor: 'rgba(255, 0, 0, 0.1)', border: '1px solid rgba(255, 0, 0, 0.3)' },
|
|
311
|
+
image: { backgroundColor: 'rgba(0, 255, 0, 0.1)', border: '1px solid rgba(0, 255, 0, 0.5)' },
|
|
312
|
+
icon: { backgroundColor: 'rgba(0, 0, 255, 0.1)', border: '1px solid rgba(0, 0, 255, 0.3)' },
|
|
313
|
+
textContainer: { backgroundColor: 'rgba(255, 255, 0, 0.1)', border: '1px solid rgba(255, 255, 0, 0.3)' },
|
|
314
|
+
text: { backgroundColor: 'rgba(255, 165, 0, 0.1)', border: '1px solid rgba(255, 165, 0, 0.3)' },
|
|
315
|
+
metadata: { backgroundColor: 'rgba(128, 0, 128, 0.1)', border: '1px solid rgba(128, 0, 128, 0.3)' }
|
|
316
|
+
} : {
|
|
317
|
+
container: {},
|
|
318
|
+
imageContainer: {},
|
|
319
|
+
image: {},
|
|
320
|
+
icon: {},
|
|
321
|
+
textContainer: {},
|
|
322
|
+
text: {},
|
|
323
|
+
metadata: {}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// AICODE-NOTE: Grid view layout — cell dimensions are uniform, thumbnail adapts to image aspect ratio
|
|
327
|
+
if (viewType.id === 'grid') {
|
|
328
|
+
const containerWidth = itemWidth || 200;
|
|
329
|
+
const containerHeight = itemHeight || 140;
|
|
330
|
+
const maxThumbWidth = containerWidth - 16;
|
|
331
|
+
const maxThumbHeight = thumbnailSize ?? Math.min(maxThumbWidth, containerHeight - 48);
|
|
332
|
+
|
|
333
|
+
// Get image aspect ratio (w/h) from provider cache if available
|
|
334
|
+
const imageAspectRatio = model?.provider?.getAspectRatio?.(item.path);
|
|
335
|
+
// Fit thumbnail to image aspect ratio within the available cell space
|
|
336
|
+
let thumbWidth = maxThumbWidth;
|
|
337
|
+
let thumbHeight = maxThumbHeight;
|
|
338
|
+
if (resolvedThumbnailUrl && imageAspectRatio && imageAspectRatio > 0) {
|
|
339
|
+
// Start with full width, calculate height
|
|
340
|
+
const heightFromWidth = maxThumbWidth / imageAspectRatio;
|
|
341
|
+
if (heightFromWidth <= maxThumbHeight) {
|
|
342
|
+
// Landscape or moderate: full width, shorter height
|
|
343
|
+
thumbWidth = maxThumbWidth;
|
|
344
|
+
thumbHeight = Math.round(heightFromWidth);
|
|
345
|
+
} else {
|
|
346
|
+
// Portrait: full height, narrower width
|
|
347
|
+
thumbHeight = maxThumbHeight;
|
|
348
|
+
thumbWidth = Math.round(maxThumbHeight * imageAspectRatio);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<div
|
|
354
|
+
className={`${gridClasses} flex flex-col items-center`}
|
|
355
|
+
style={{
|
|
356
|
+
width: containerWidth,
|
|
357
|
+
height: containerHeight,
|
|
358
|
+
minHeight: containerHeight,
|
|
359
|
+
boxSizing: 'border-box',
|
|
360
|
+
...debugStyles.container
|
|
361
|
+
}}
|
|
362
|
+
role={accessibilityProps.role}
|
|
363
|
+
aria-label={accessibilityProps.ariaLabel}
|
|
364
|
+
aria-selected={accessibilityProps.ariaSelected}
|
|
365
|
+
aria-setsize={accessibilityProps.ariaSetSize}
|
|
366
|
+
aria-posinset={accessibilityProps.ariaPosInSet}
|
|
367
|
+
tabIndex={accessibilityProps.tabIndex}
|
|
368
|
+
draggable={canDrag}
|
|
369
|
+
onClick={handleClick}
|
|
370
|
+
onDoubleClick={handleDoubleClick}
|
|
371
|
+
onContextMenu={handleContextMenu}
|
|
372
|
+
onDragStart={handleDragStart}
|
|
373
|
+
onDragOver={handleDragOver}
|
|
374
|
+
onDragLeave={handleDragLeave}
|
|
375
|
+
onDrop={handleDrop}
|
|
376
|
+
>
|
|
377
|
+
{/* AICODE-NOTE: Thumbnail container sized to image aspect ratio */}
|
|
378
|
+
<div
|
|
379
|
+
className="relative rounded-lg overflow-hidden mb-2 flex-shrink-0 flex items-center justify-center"
|
|
380
|
+
style={{
|
|
381
|
+
width: thumbWidth,
|
|
382
|
+
height: thumbHeight,
|
|
383
|
+
...debugStyles.imageContainer
|
|
384
|
+
}}
|
|
385
|
+
>
|
|
386
|
+
{resolvedThumbnailUrl ? (
|
|
387
|
+
<img
|
|
388
|
+
src={resolvedThumbnailUrl}
|
|
389
|
+
alt={item.name}
|
|
390
|
+
className="object-cover"
|
|
391
|
+
loading="lazy"
|
|
392
|
+
style={{
|
|
393
|
+
width: '100%',
|
|
394
|
+
height: '100%',
|
|
395
|
+
...debugStyles.image
|
|
396
|
+
}}
|
|
397
|
+
/>
|
|
398
|
+
) : (
|
|
399
|
+
<div
|
|
400
|
+
className="w-full h-full flex items-center justify-center bg-muted/30 rounded-lg"
|
|
401
|
+
style={{
|
|
402
|
+
...debugStyles.icon
|
|
403
|
+
}}
|
|
404
|
+
>
|
|
405
|
+
{renderIcon('w-12 h-12')}
|
|
406
|
+
</div>
|
|
407
|
+
)}
|
|
408
|
+
|
|
409
|
+
{/* AICODE-NOTE: Selection overlay */}
|
|
410
|
+
{isSelected && (
|
|
411
|
+
<div className="absolute inset-0 bg-primary/20 border-2 border-primary rounded-lg" />
|
|
412
|
+
)}
|
|
413
|
+
|
|
414
|
+
{/* Checkbox overlay for grid items */}
|
|
415
|
+
{(model?.showCheckboxes) && (
|
|
416
|
+
<div className="absolute top-1 left-1 z-10">
|
|
417
|
+
<input
|
|
418
|
+
type="checkbox"
|
|
419
|
+
checked={isSelected}
|
|
420
|
+
onChange={() => {}}
|
|
421
|
+
onClick={(e) => {
|
|
422
|
+
e.stopPropagation();
|
|
423
|
+
model?.selectItem(item, { ctrl: true });
|
|
424
|
+
}}
|
|
425
|
+
className="h-4 w-4 rounded border-muted-foreground/50 accent-primary cursor-pointer bg-background shadow-sm"
|
|
426
|
+
/>
|
|
427
|
+
</div>
|
|
428
|
+
)}
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
{/* AICODE-NOTE: Item name with debug background */}
|
|
432
|
+
<div
|
|
433
|
+
className="text-sm font-medium text-center px-2 leading-tight"
|
|
434
|
+
style={{
|
|
435
|
+
width: containerWidth - 16,
|
|
436
|
+
maxWidth: containerWidth - 16,
|
|
437
|
+
...debugStyles.textContainer
|
|
438
|
+
}}
|
|
439
|
+
>
|
|
440
|
+
<div
|
|
441
|
+
className="truncate"
|
|
442
|
+
style={{
|
|
443
|
+
width: '100%',
|
|
444
|
+
maxWidth: '100%',
|
|
445
|
+
overflow: 'hidden',
|
|
446
|
+
textOverflow: 'ellipsis',
|
|
447
|
+
whiteSpace: 'nowrap',
|
|
448
|
+
...debugStyles.text
|
|
449
|
+
}}
|
|
450
|
+
title={item.name}
|
|
451
|
+
>
|
|
452
|
+
{isRenaming ? renderNameOrRename('text-center') : item.name}
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
|
|
456
|
+
{/* AICODE-NOTE: Optional metadata with debug background */}
|
|
457
|
+
{item.size && (
|
|
458
|
+
<div
|
|
459
|
+
className="text-xs text-muted-foreground mt-1 text-center truncate"
|
|
460
|
+
style={{
|
|
461
|
+
width: containerWidth - 16,
|
|
462
|
+
maxWidth: containerWidth - 16,
|
|
463
|
+
overflow: 'hidden',
|
|
464
|
+
textOverflow: 'ellipsis',
|
|
465
|
+
whiteSpace: 'nowrap',
|
|
466
|
+
...debugStyles.metadata
|
|
467
|
+
}}
|
|
468
|
+
>
|
|
469
|
+
{item.size > 1024 * 1024
|
|
470
|
+
? `${(item.size / (1024 * 1024)).toFixed(1)} MB`
|
|
471
|
+
: `${(item.size / 1024).toFixed(1)} KB`
|
|
472
|
+
}
|
|
473
|
+
</div>
|
|
474
|
+
)}
|
|
475
|
+
</div>
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// AICODE-NOTE: Masonry view layout (similar to grid but with flexible height)
|
|
480
|
+
if (viewType.id === 'masonry-horizontal' || viewType.id === 'masonry-vertical') {
|
|
481
|
+
const customDimensions = getItemDimensions();
|
|
482
|
+
const containerWidth = customDimensions?.width || itemWidth || 200;
|
|
483
|
+
const containerHeight = customDimensions?.height || itemHeight || 140;
|
|
484
|
+
|
|
485
|
+
return (
|
|
486
|
+
<div
|
|
487
|
+
className={gridClasses}
|
|
488
|
+
style={{
|
|
489
|
+
width: containerWidth,
|
|
490
|
+
minHeight: containerHeight + 80,
|
|
491
|
+
boxSizing: 'border-box',
|
|
492
|
+
...debugStyles.container
|
|
493
|
+
}}
|
|
494
|
+
role={accessibilityProps.role}
|
|
495
|
+
aria-label={accessibilityProps.ariaLabel}
|
|
496
|
+
aria-selected={accessibilityProps.ariaSelected}
|
|
497
|
+
aria-setsize={accessibilityProps.ariaSetSize}
|
|
498
|
+
aria-posinset={accessibilityProps.ariaPosInSet}
|
|
499
|
+
tabIndex={accessibilityProps.tabIndex}
|
|
500
|
+
draggable={canDrag}
|
|
501
|
+
onClick={handleClick}
|
|
502
|
+
onDoubleClick={handleDoubleClick}
|
|
503
|
+
onContextMenu={handleContextMenu}
|
|
504
|
+
onDragStart={handleDragStart}
|
|
505
|
+
onDragOver={handleDragOver}
|
|
506
|
+
onDragLeave={handleDragLeave}
|
|
507
|
+
onDrop={handleDrop}
|
|
508
|
+
>
|
|
509
|
+
{/* AICODE-NOTE: Image or icon with variable size */}
|
|
510
|
+
<div
|
|
511
|
+
className="flex items-center justify-center mb-3 flex-1 overflow-hidden rounded-lg"
|
|
512
|
+
style={{
|
|
513
|
+
width: containerWidth - 16,
|
|
514
|
+
height: containerHeight - 20,
|
|
515
|
+
maxWidth: containerWidth - 16,
|
|
516
|
+
maxHeight: containerHeight - 20,
|
|
517
|
+
...debugStyles.imageContainer
|
|
518
|
+
}}
|
|
519
|
+
>
|
|
520
|
+
{resolvedThumbnailUrl ? (
|
|
521
|
+
<img
|
|
522
|
+
src={resolvedThumbnailUrl}
|
|
523
|
+
alt={item.name}
|
|
524
|
+
className="object-contain rounded"
|
|
525
|
+
style={{
|
|
526
|
+
width: 'auto',
|
|
527
|
+
height: 'auto',
|
|
528
|
+
maxWidth: '100%',
|
|
529
|
+
maxHeight: '100%',
|
|
530
|
+
...debugStyles.image
|
|
531
|
+
}}
|
|
532
|
+
loading="lazy"
|
|
533
|
+
/>
|
|
534
|
+
) : (
|
|
535
|
+
<div
|
|
536
|
+
className="text-4xl flex items-center justify-center w-full h-full"
|
|
537
|
+
style={{
|
|
538
|
+
...debugStyles.icon
|
|
539
|
+
}}
|
|
540
|
+
>
|
|
541
|
+
{renderIcon()}
|
|
542
|
+
</div>
|
|
543
|
+
)}
|
|
544
|
+
</div>
|
|
545
|
+
|
|
546
|
+
{/* AICODE-NOTE: Item name with flexible height for masonry */}
|
|
547
|
+
<div
|
|
548
|
+
className="text-sm font-medium text-center px-2 leading-tight"
|
|
549
|
+
style={{
|
|
550
|
+
width: containerWidth - 16,
|
|
551
|
+
maxWidth: containerWidth - 16,
|
|
552
|
+
...debugStyles.textContainer
|
|
553
|
+
}}
|
|
554
|
+
>
|
|
555
|
+
<div
|
|
556
|
+
className="break-words"
|
|
557
|
+
style={{
|
|
558
|
+
width: '100%',
|
|
559
|
+
maxWidth: '100%',
|
|
560
|
+
...debugStyles.text
|
|
561
|
+
}}
|
|
562
|
+
title={item.name}
|
|
563
|
+
>
|
|
564
|
+
{isRenaming ? renderNameOrRename('text-center') : item.name}
|
|
565
|
+
</div>
|
|
566
|
+
</div>
|
|
567
|
+
|
|
568
|
+
{/* AICODE-NOTE: Extended metadata for masonry view */}
|
|
569
|
+
<div
|
|
570
|
+
className="text-xs text-muted-foreground mt-2 space-y-1 text-center"
|
|
571
|
+
style={{
|
|
572
|
+
width: containerWidth - 16,
|
|
573
|
+
maxWidth: containerWidth - 16,
|
|
574
|
+
...debugStyles.metadata
|
|
575
|
+
}}
|
|
576
|
+
>
|
|
577
|
+
{item.size && (
|
|
578
|
+
<div className="truncate">
|
|
579
|
+
{item.size > 1024 * 1024
|
|
580
|
+
? `${(item.size / (1024 * 1024)).toFixed(1)} MB`
|
|
581
|
+
: `${(item.size / 1024).toFixed(1)} KB`
|
|
582
|
+
}
|
|
583
|
+
</div>
|
|
584
|
+
)}
|
|
585
|
+
{item.modified && (
|
|
586
|
+
<div className="truncate">{item.modified.toLocaleDateString()}</div>
|
|
587
|
+
)}
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// AICODE-NOTE: Details view layout — uses CSS grid matching the header in VirtualizedDetailsView
|
|
594
|
+
if (viewType.id === 'details') {
|
|
595
|
+
// Selection/focus classes without flex/gap from baseClasses
|
|
596
|
+
const detailsStateClasses = `
|
|
597
|
+
items-center py-1 px-4 text-[13px] cursor-default select-none transition-colors duration-200
|
|
598
|
+
hover:bg-muted/50 border border-transparent
|
|
599
|
+
${isSelected ? 'bg-primary/10 border-primary/20' : ''}
|
|
600
|
+
${isFocused ? 'ring-2 ring-primary/50' : ''}
|
|
601
|
+
${isDraggedOver ? 'bg-accent/20 border-accent' : ''}
|
|
602
|
+
${dragOverPosition === 'before' ? 'border-t-2 border-t-primary' : ''}
|
|
603
|
+
${dragOverPosition === 'after' ? 'border-b-2 border-b-primary' : ''}
|
|
604
|
+
${dragOverPosition === 'inside' ? 'bg-primary/5 border-primary/30' : ''}
|
|
605
|
+
`;
|
|
606
|
+
|
|
607
|
+
const isCompact = model?.compactMode ?? false;
|
|
608
|
+
const colVis = model?.columnVisibility ?? { type: true, modified: true, size: true };
|
|
609
|
+
const gridCols = model?.detailsGridTemplateColumns ?? '24px 1fr 96px 128px 80px';
|
|
610
|
+
const showCb = model?.showCheckboxes ?? false;
|
|
611
|
+
|
|
612
|
+
return (
|
|
613
|
+
<div
|
|
614
|
+
className={`grid ${detailsStateClasses}`}
|
|
615
|
+
style={{
|
|
616
|
+
gridTemplateColumns: gridCols,
|
|
617
|
+
columnGap: 16,
|
|
618
|
+
...debugStyles.container
|
|
619
|
+
}}
|
|
620
|
+
draggable={canDrag}
|
|
621
|
+
onClick={handleClick}
|
|
622
|
+
onDoubleClick={handleDoubleClick}
|
|
623
|
+
onContextMenu={handleContextMenu}
|
|
624
|
+
onDragStart={handleDragStart}
|
|
625
|
+
onDragOver={handleDragOver}
|
|
626
|
+
onDragLeave={handleDragLeave}
|
|
627
|
+
onDrop={handleDrop}
|
|
628
|
+
>
|
|
629
|
+
{showCb && (
|
|
630
|
+
<div className="flex items-center justify-center">
|
|
631
|
+
<input
|
|
632
|
+
type="checkbox"
|
|
633
|
+
checked={isSelected}
|
|
634
|
+
onChange={() => {}}
|
|
635
|
+
onClick={(e) => {
|
|
636
|
+
e.stopPropagation();
|
|
637
|
+
model?.selectItem(item, { ctrl: true });
|
|
638
|
+
}}
|
|
639
|
+
className="h-3.5 w-3.5 rounded border-muted-foreground/50 accent-primary cursor-pointer"
|
|
640
|
+
/>
|
|
641
|
+
</div>
|
|
642
|
+
)}
|
|
643
|
+
|
|
644
|
+
<div className="flex items-center justify-center" style={debugStyles.icon}>
|
|
645
|
+
{renderImageOrIcon('small')}
|
|
646
|
+
</div>
|
|
647
|
+
|
|
648
|
+
<div className="truncate font-medium min-w-0" style={debugStyles.text} title={item.name}>
|
|
649
|
+
{isRenaming ? renderNameOrRename() : item.name}
|
|
650
|
+
</div>
|
|
651
|
+
|
|
652
|
+
{!isCompact && colVis.type && (
|
|
653
|
+
<div className="text-sm text-muted-foreground truncate min-w-0" style={debugStyles.metadata}>
|
|
654
|
+
{item.type || 'Unknown'}
|
|
655
|
+
</div>
|
|
656
|
+
)}
|
|
657
|
+
|
|
658
|
+
{!isCompact && colVis.modified && (
|
|
659
|
+
<div className="text-sm text-muted-foreground truncate min-w-0" style={debugStyles.metadata}>
|
|
660
|
+
{item.modified ? item.modified.toLocaleDateString() : '-'}
|
|
661
|
+
</div>
|
|
662
|
+
)}
|
|
663
|
+
|
|
664
|
+
{!isCompact && colVis.size && (
|
|
665
|
+
<div className="text-sm text-muted-foreground text-right truncate min-w-0" style={debugStyles.metadata}>
|
|
666
|
+
{item.size ? `${(item.size / 1024).toFixed(1)} KB` : '-'}
|
|
667
|
+
</div>
|
|
668
|
+
)}
|
|
669
|
+
</div>
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// AICODE-NOTE: Default list view layout
|
|
674
|
+
const listCompact = model?.compactMode ?? false;
|
|
675
|
+
const listShowCb = model?.showCheckboxes ?? false;
|
|
676
|
+
return (
|
|
677
|
+
<div
|
|
678
|
+
className={baseClasses}
|
|
679
|
+
style={{
|
|
680
|
+
...debugStyles.container
|
|
681
|
+
}}
|
|
682
|
+
draggable={canDrag}
|
|
683
|
+
onClick={handleClick}
|
|
684
|
+
onDoubleClick={handleDoubleClick}
|
|
685
|
+
onContextMenu={handleContextMenu}
|
|
686
|
+
onDragStart={handleDragStart}
|
|
687
|
+
onDragOver={handleDragOver}
|
|
688
|
+
onDragLeave={handleDragLeave}
|
|
689
|
+
onDrop={handleDrop}
|
|
690
|
+
>
|
|
691
|
+
{listShowCb && (
|
|
692
|
+
<div className="flex items-center flex-shrink-0">
|
|
693
|
+
<input
|
|
694
|
+
type="checkbox"
|
|
695
|
+
checked={isSelected}
|
|
696
|
+
onChange={() => {}}
|
|
697
|
+
onClick={(e) => {
|
|
698
|
+
e.stopPropagation();
|
|
699
|
+
model?.selectItem(item, { ctrl: true });
|
|
700
|
+
}}
|
|
701
|
+
className="h-3.5 w-3.5 rounded border-muted-foreground/50 accent-primary cursor-pointer"
|
|
702
|
+
/>
|
|
703
|
+
</div>
|
|
704
|
+
)}
|
|
705
|
+
|
|
706
|
+
<div
|
|
707
|
+
className="text-lg flex-shrink-0 flex items-center"
|
|
708
|
+
style={{
|
|
709
|
+
...debugStyles.icon
|
|
710
|
+
}}
|
|
711
|
+
>
|
|
712
|
+
{renderImageOrIcon()}
|
|
713
|
+
</div>
|
|
714
|
+
|
|
715
|
+
<div
|
|
716
|
+
className="flex-1 min-w-0"
|
|
717
|
+
style={{
|
|
718
|
+
...debugStyles.textContainer
|
|
719
|
+
}}
|
|
720
|
+
>
|
|
721
|
+
<div
|
|
722
|
+
className="font-medium truncate"
|
|
723
|
+
style={{
|
|
724
|
+
...debugStyles.text
|
|
725
|
+
}}
|
|
726
|
+
title={item.name}
|
|
727
|
+
>
|
|
728
|
+
{isRenaming ? renderNameOrRename() : item.name}
|
|
729
|
+
</div>
|
|
730
|
+
{!listCompact && item.size && (
|
|
731
|
+
<div
|
|
732
|
+
className="text-sm text-muted-foreground"
|
|
733
|
+
style={{
|
|
734
|
+
...debugStyles.metadata
|
|
735
|
+
}}
|
|
736
|
+
>
|
|
737
|
+
{(item.size / 1024).toFixed(1)} KB
|
|
738
|
+
</div>
|
|
739
|
+
)}
|
|
740
|
+
</div>
|
|
741
|
+
|
|
742
|
+
{!listCompact && item.modified && (
|
|
743
|
+
<div
|
|
744
|
+
className="text-xs text-muted-foreground flex-shrink-0"
|
|
745
|
+
style={{
|
|
746
|
+
...debugStyles.metadata
|
|
747
|
+
}}
|
|
748
|
+
>
|
|
749
|
+
{item.modified.toLocaleDateString()}
|
|
750
|
+
</div>
|
|
751
|
+
)}
|
|
752
|
+
</div>
|
|
753
|
+
);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
ListItemComponent.displayName = 'ListItem';
|
|
757
|
+
|
|
758
|
+
// AICODE-NOTE: observer handles efficient re-rendering via MobX observable tracking.
|
|
759
|
+
// Custom React.memo was removed because it blocked MobX-triggered re-renders
|
|
760
|
+
// (e.g., thumbnail cache updates) since resolvedThumbnailUrl is computed inside render.
|
|
761
|
+
export const ListItem = ListItemComponent;
|