listpage-next 0.0.246 → 0.0.247
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/features/ChatClient/components/ChatContent/ClientContentHeader.js +3 -24
- package/dist/ui/Button/index.d.ts +1 -1
- package/dist/ui/Button/index.js +3 -1
- package/dist/ui/FileManager/FileManager.d.ts +7 -0
- package/dist/ui/FileManager/FileManager.js +278 -0
- package/dist/ui/FileManager/components/AddAssetModal.d.ts +11 -0
- package/dist/ui/FileManager/components/AddAssetModal.js +197 -0
- package/dist/ui/FileManager/components/AssetGrid.d.ts +11 -0
- package/dist/ui/FileManager/components/AssetGrid.js +188 -0
- package/dist/ui/FileManager/components/AssetSelectorModal.d.ts +12 -0
- package/dist/ui/FileManager/components/AssetSelectorModal.js +133 -0
- package/dist/ui/FileManager/components/Breadcrumbs.d.ts +10 -0
- package/dist/ui/FileManager/components/Breadcrumbs.js +25 -0
- package/dist/ui/FileManager/components/CreateFolderModal.d.ts +7 -0
- package/dist/ui/FileManager/components/CreateFolderModal.js +84 -0
- package/dist/ui/FileManager/components/DeleteConfirmModal.d.ts +9 -0
- package/dist/ui/FileManager/components/DeleteConfirmModal.js +33 -0
- package/dist/ui/FileManager/components/FileTypePreview.d.ts +7 -0
- package/dist/ui/FileManager/components/FileTypePreview.js +29 -0
- package/dist/ui/FileManager/components/FolderTree.d.ts +13 -0
- package/dist/ui/FileManager/components/FolderTree.js +137 -0
- package/dist/ui/FileManager/components/HeaderBar.d.ts +13 -0
- package/dist/ui/FileManager/components/HeaderBar.js +37 -0
- package/dist/ui/FileManager/components/InspectorPanel.d.ts +9 -0
- package/dist/ui/FileManager/components/InspectorPanel.js +119 -0
- package/dist/ui/FileManager/components/SearchInput.d.ts +7 -0
- package/dist/ui/FileManager/components/SearchInput.js +19 -0
- package/dist/ui/FileManager/components/Sidebar.d.ts +12 -0
- package/dist/ui/FileManager/components/Sidebar.js +79 -0
- package/dist/ui/FileManager/components/ViewModeToggle.d.ts +8 -0
- package/dist/ui/FileManager/components/ViewModeToggle.js +23 -0
- package/dist/ui/FileManager/index.d.ts +3 -0
- package/dist/ui/FileManager/index.js +3 -0
- package/dist/ui/FileManager/services/config.d.ts +8 -0
- package/dist/ui/FileManager/services/config.js +18 -0
- package/dist/ui/FileManager/services/storageService.d.ts +14 -0
- package/dist/ui/FileManager/services/storageService.js +249 -0
- package/dist/ui/FileManager/types.d.ts +36 -0
- package/dist/ui/FileManager/types.js +0 -0
- package/dist/ui/Modal/ConfirmModal.d.ts +11 -0
- package/dist/ui/Modal/ConfirmModal.js +62 -0
- package/dist/ui/Modal/Modal.d.ts +14 -0
- package/dist/ui/Modal/Modal.js +64 -0
- package/dist/ui/Modal/index.d.ts +2 -0
- package/dist/ui/Modal/index.js +3 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +2 -1
- package/dist/ui.css +755 -0
- package/package.json +4 -2
|
@@ -24,10 +24,6 @@ const HeaderLeft = (props)=>{
|
|
|
24
24
|
const onCollapsed = ()=>{
|
|
25
25
|
setCollapsed(!collapsed);
|
|
26
26
|
};
|
|
27
|
-
const onSubmitTitleChange = async (newTitle)=>{
|
|
28
|
-
await onTitleChange?.(newTitle);
|
|
29
|
-
setTitle(newTitle);
|
|
30
|
-
};
|
|
31
27
|
const showActions = !!collapsed;
|
|
32
28
|
const showTitle = !!title;
|
|
33
29
|
return /*#__PURE__*/ jsxs(Left, {
|
|
@@ -56,23 +52,6 @@ const HeaderLeft = (props)=>{
|
|
|
56
52
|
backgroundColor: '#0000001f'
|
|
57
53
|
},
|
|
58
54
|
type: "vertical"
|
|
59
|
-
}),
|
|
60
|
-
showTitle && /*#__PURE__*/ jsxs(NameContainer, {
|
|
61
|
-
children: [
|
|
62
|
-
/*#__PURE__*/ jsx(NameText, {
|
|
63
|
-
children: /*#__PURE__*/ jsx(Button, {
|
|
64
|
-
onClick: ()=>showUpdateTitleModal(title, onSubmitTitleChange),
|
|
65
|
-
size: "small",
|
|
66
|
-
type: "text",
|
|
67
|
-
iconPosition: "end",
|
|
68
|
-
icon: /*#__PURE__*/ jsx(EditOutlined, {}),
|
|
69
|
-
children: title
|
|
70
|
-
})
|
|
71
|
-
}),
|
|
72
|
-
/*#__PURE__*/ jsx(AiTipText, {
|
|
73
|
-
children: "内容由 AI 生成"
|
|
74
|
-
})
|
|
75
|
-
]
|
|
76
55
|
})
|
|
77
56
|
]
|
|
78
57
|
});
|
|
@@ -83,12 +62,12 @@ const ActionContainer = styled.div`
|
|
|
83
62
|
|
|
84
63
|
gap: 4px;
|
|
85
64
|
`;
|
|
86
|
-
|
|
65
|
+
styled.div`
|
|
87
66
|
display: flex;
|
|
88
67
|
align-items: flex-start;
|
|
89
68
|
flex-direction: column;
|
|
90
69
|
`;
|
|
91
|
-
|
|
70
|
+
styled.span`
|
|
92
71
|
font-size: 14px;
|
|
93
72
|
font-weight: 500;
|
|
94
73
|
color: #000000;
|
|
@@ -97,7 +76,7 @@ const NameText = styled.span`
|
|
|
97
76
|
ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
|
98
77
|
'Segoe UI Symbol', 'Noto Color Emoji';
|
|
99
78
|
`;
|
|
100
|
-
|
|
79
|
+
styled.span`
|
|
101
80
|
font-size: 11px;
|
|
102
81
|
color: #00000080;
|
|
103
82
|
padding: 0 8px;
|
|
@@ -4,7 +4,7 @@ export interface ButtonProps {
|
|
|
4
4
|
disabled?: boolean;
|
|
5
5
|
loading?: boolean;
|
|
6
6
|
children?: ReactNode;
|
|
7
|
-
type?: 'primary' | 'secondary';
|
|
7
|
+
type?: 'primary' | 'secondary' | 'error' | 'text';
|
|
8
8
|
size?: 'large' | 'medium' | 'small';
|
|
9
9
|
}
|
|
10
10
|
export declare const Button: (props: ButtonProps) => import("react/jsx-runtime").JSX.Element;
|
package/dist/ui/Button/index.js
CHANGED
|
@@ -6,7 +6,9 @@ const Button = (props)=>{
|
|
|
6
6
|
const [loading, setLoading] = useState(false);
|
|
7
7
|
const typeClass = {
|
|
8
8
|
primary: 'bg-blue-600 hover:bg-blue-500 text-white shadow-lg shadow-blue-900/20 transition-all transform hover:scale-[1.02] active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 flex items-center gap-2',
|
|
9
|
-
secondary: 'border border-gray-700 text-gray-300 hover:bg-gray-800 hover:text-white active:bg-gray-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-500 focus-visible:ring-offset-2 flex items-center gap-2'
|
|
9
|
+
secondary: 'border border-gray-700 text-gray-300 hover:bg-gray-800 hover:text-white active:bg-gray-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-500 focus-visible:ring-offset-2 flex items-center gap-2',
|
|
10
|
+
error: 'bg-red-600 hover:bg-red-500 text-white rounded-lg text-sm font-medium transition-colors shadow-lg shadow-red-900/20',
|
|
11
|
+
text: 'text-slate-300 hover:text-white transition-colors text-sm font-medium'
|
|
10
12
|
};
|
|
11
13
|
const sizeClass = {
|
|
12
14
|
large: 'px-6 py-2.5 rounded-lg',
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StorageAdapter } from './types';
|
|
3
|
+
export interface FileManagerProps {
|
|
4
|
+
title?: ReactNode;
|
|
5
|
+
storage: StorageAdapter;
|
|
6
|
+
}
|
|
7
|
+
export declare function FileManager({ title, storage }: FileManagerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useRequest } from "ahooks";
|
|
4
|
+
import { Loader2 } from "lucide-react";
|
|
5
|
+
import Sidebar from "./components/Sidebar.js";
|
|
6
|
+
import { AssetGrid } from "./components/AssetGrid.js";
|
|
7
|
+
import { AddAssetModal } from "./components/AddAssetModal.js";
|
|
8
|
+
import { CreateFolderModal } from "./components/CreateFolderModal.js";
|
|
9
|
+
import { DeleteConfirmModal } from "./components/DeleteConfirmModal.js";
|
|
10
|
+
import { InspectorPanel } from "./components/InspectorPanel.js";
|
|
11
|
+
import { HeaderBar } from "./components/HeaderBar.js";
|
|
12
|
+
function FileManager({ title, storage }) {
|
|
13
|
+
const [assets, setAssets] = useState([]);
|
|
14
|
+
const [folders, setFolders] = useState([]);
|
|
15
|
+
const [currentFolderId, setCurrentFolderId] = useState(null);
|
|
16
|
+
const [filters, setFilters] = useState({
|
|
17
|
+
search: '',
|
|
18
|
+
source: 'all'
|
|
19
|
+
});
|
|
20
|
+
const [viewMode, setViewMode] = useState('grid');
|
|
21
|
+
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
|
22
|
+
const [isCreateFolderModalOpen, setIsCreateFolderModalOpen] = useState(false);
|
|
23
|
+
const [createFolderParentId, setCreateFolderParentId] = useState(void 0);
|
|
24
|
+
const [deleteModalState, setDeleteModalState] = useState({
|
|
25
|
+
isOpen: false,
|
|
26
|
+
itemId: null,
|
|
27
|
+
itemType: null,
|
|
28
|
+
itemName: ''
|
|
29
|
+
});
|
|
30
|
+
const [selectedItem, setSelectedItem] = useState(null);
|
|
31
|
+
const { loading } = useRequest(()=>storage.listAssets(), {
|
|
32
|
+
onSuccess: ({ assets: remoteAssets, folders: remoteFolders })=>{
|
|
33
|
+
setAssets(remoteAssets);
|
|
34
|
+
setFolders(remoteFolders);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
useEffect(()=>{
|
|
38
|
+
if (selectedItem && 'image' === selectedItem.type) {
|
|
39
|
+
const item = selectedItem;
|
|
40
|
+
if (!item.width || !item.height) {
|
|
41
|
+
const img = new Image();
|
|
42
|
+
img.src = item.url;
|
|
43
|
+
img.onload = ()=>{
|
|
44
|
+
setAssets((prev)=>prev.map((a)=>a.id === item.id ? {
|
|
45
|
+
...a,
|
|
46
|
+
width: img.naturalWidth,
|
|
47
|
+
height: img.naturalHeight
|
|
48
|
+
} : a));
|
|
49
|
+
setSelectedItem((prev)=>{
|
|
50
|
+
if (prev && prev.id === item.id) return {
|
|
51
|
+
...prev,
|
|
52
|
+
width: img.naturalWidth,
|
|
53
|
+
height: img.naturalHeight
|
|
54
|
+
};
|
|
55
|
+
return prev;
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}, [
|
|
61
|
+
selectedItem?.id
|
|
62
|
+
]);
|
|
63
|
+
const breadcrumbs = useMemo(()=>{
|
|
64
|
+
const path = [];
|
|
65
|
+
let currentId = currentFolderId;
|
|
66
|
+
while(currentId){
|
|
67
|
+
const folder = folders.find((f)=>f.id === currentId);
|
|
68
|
+
if (folder) {
|
|
69
|
+
path.unshift({
|
|
70
|
+
id: folder.id,
|
|
71
|
+
name: folder.name
|
|
72
|
+
});
|
|
73
|
+
currentId = folder.parentId;
|
|
74
|
+
} else break;
|
|
75
|
+
}
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
id: null,
|
|
79
|
+
name: '根目录'
|
|
80
|
+
},
|
|
81
|
+
...path
|
|
82
|
+
];
|
|
83
|
+
}, [
|
|
84
|
+
folders,
|
|
85
|
+
currentFolderId
|
|
86
|
+
]);
|
|
87
|
+
const currentViewItems = useMemo(()=>{
|
|
88
|
+
const currentFolders = folders.filter((f)=>f.parentId === currentFolderId);
|
|
89
|
+
const currentAssets = assets.filter((a)=>a.parentId === currentFolderId);
|
|
90
|
+
let items = [
|
|
91
|
+
...currentFolders,
|
|
92
|
+
...currentAssets
|
|
93
|
+
];
|
|
94
|
+
if (filters.search) {
|
|
95
|
+
const allFolders = folders.filter((f)=>f.name.toLowerCase().includes(filters.search.toLowerCase()));
|
|
96
|
+
const allAssets = assets.filter((a)=>a.name.toLowerCase().includes(filters.search.toLowerCase()));
|
|
97
|
+
return [
|
|
98
|
+
...allFolders,
|
|
99
|
+
...allAssets
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
if ('all' !== filters.source) return items.filter((item)=>{
|
|
103
|
+
if ('folder' === item.type) return false;
|
|
104
|
+
return item.source === filters.source;
|
|
105
|
+
});
|
|
106
|
+
return items;
|
|
107
|
+
}, [
|
|
108
|
+
assets,
|
|
109
|
+
folders,
|
|
110
|
+
currentFolderId,
|
|
111
|
+
filters
|
|
112
|
+
]);
|
|
113
|
+
const handleAddAsset = (newAsset)=>{
|
|
114
|
+
setAssets((prev)=>[
|
|
115
|
+
newAsset,
|
|
116
|
+
...prev
|
|
117
|
+
]);
|
|
118
|
+
};
|
|
119
|
+
const handleBatchImport = (newAssets, newFolders)=>{
|
|
120
|
+
setFolders((prev)=>{
|
|
121
|
+
const exists = new Set(prev.map((f)=>f.id));
|
|
122
|
+
const uniqueNew = newFolders.filter((f)=>!exists.has(f.id));
|
|
123
|
+
return [
|
|
124
|
+
...prev,
|
|
125
|
+
...uniqueNew
|
|
126
|
+
];
|
|
127
|
+
});
|
|
128
|
+
setAssets((prev)=>[
|
|
129
|
+
...newAssets,
|
|
130
|
+
...prev
|
|
131
|
+
]);
|
|
132
|
+
};
|
|
133
|
+
const handleCreateFolder = async (name)=>{
|
|
134
|
+
try {
|
|
135
|
+
const parentId = void 0 === createFolderParentId ? currentFolderId : createFolderParentId;
|
|
136
|
+
const newFolder = await storage.createFolder(name, parentId);
|
|
137
|
+
setFolders((prev)=>[
|
|
138
|
+
...prev,
|
|
139
|
+
newFolder
|
|
140
|
+
]);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
alert('创建文件夹失败');
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const handleDeleteRequest = (id, type, name)=>{
|
|
146
|
+
setDeleteModalState({
|
|
147
|
+
isOpen: true,
|
|
148
|
+
itemId: id,
|
|
149
|
+
itemType: type,
|
|
150
|
+
itemName: name
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
const executeDelete = async ()=>{
|
|
154
|
+
const { itemId, itemType } = deleteModalState;
|
|
155
|
+
if (!itemId || !itemType) return;
|
|
156
|
+
if ('folder' === itemType) try {
|
|
157
|
+
await storage.deleteFolder(itemId);
|
|
158
|
+
setFolders((prev)=>prev.filter((f)=>f.id !== itemId));
|
|
159
|
+
setFolders((prev)=>prev.filter((f)=>!f.id.startsWith(itemId)));
|
|
160
|
+
setAssets((prev)=>prev.filter((a)=>!a.parentId?.startsWith(itemId)));
|
|
161
|
+
} catch (e) {
|
|
162
|
+
console.error(e);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const asset = assets.find((a)=>a.id === itemId);
|
|
166
|
+
setAssets((prev)=>prev.filter((a)=>a.id !== itemId));
|
|
167
|
+
if (asset) await storage.deleteObject(asset.id);
|
|
168
|
+
}
|
|
169
|
+
if (selectedItem?.id === itemId) setSelectedItem(null);
|
|
170
|
+
setDeleteModalState({
|
|
171
|
+
isOpen: false,
|
|
172
|
+
itemId: null,
|
|
173
|
+
itemType: null,
|
|
174
|
+
itemName: ''
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
const handleNavigate = (folderId)=>{
|
|
178
|
+
setCurrentFolderId(folderId);
|
|
179
|
+
setSelectedItem(null);
|
|
180
|
+
setFilters((prev)=>({
|
|
181
|
+
...prev,
|
|
182
|
+
search: ''
|
|
183
|
+
}));
|
|
184
|
+
};
|
|
185
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
186
|
+
className: "flex h-screen bg-slate-950 text-slate-200 font-sans",
|
|
187
|
+
children: [
|
|
188
|
+
/*#__PURE__*/ jsx(Sidebar, {
|
|
189
|
+
title: title,
|
|
190
|
+
folders: folders,
|
|
191
|
+
currentFolderId: currentFolderId,
|
|
192
|
+
onNavigate: handleNavigate,
|
|
193
|
+
onOpenAddModal: ()=>setIsAddModalOpen(true),
|
|
194
|
+
onOpenCreateEmptyFolderModal: (folder)=>{
|
|
195
|
+
setIsCreateFolderModalOpen(true);
|
|
196
|
+
setCreateFolderParentId(folder ? folder.id : null);
|
|
197
|
+
}
|
|
198
|
+
}),
|
|
199
|
+
/*#__PURE__*/ jsxs("div", {
|
|
200
|
+
className: "flex-1 flex flex-col min-w-0 bg-slate-950",
|
|
201
|
+
children: [
|
|
202
|
+
/*#__PURE__*/ jsx(HeaderBar, {
|
|
203
|
+
breadcrumbs: breadcrumbs,
|
|
204
|
+
search: filters.search,
|
|
205
|
+
onSearchChange: (v)=>setFilters((prev)=>({
|
|
206
|
+
...prev,
|
|
207
|
+
search: v
|
|
208
|
+
})),
|
|
209
|
+
onNavigate: handleNavigate,
|
|
210
|
+
onCreateFolder: ()=>{
|
|
211
|
+
setIsCreateFolderModalOpen(true);
|
|
212
|
+
setCreateFolderParentId(void 0);
|
|
213
|
+
},
|
|
214
|
+
viewMode: viewMode,
|
|
215
|
+
onChangeViewMode: setViewMode
|
|
216
|
+
}),
|
|
217
|
+
/*#__PURE__*/ jsxs("div", {
|
|
218
|
+
className: "flex-1 flex overflow-hidden relative",
|
|
219
|
+
children: [
|
|
220
|
+
/*#__PURE__*/ jsx("main", {
|
|
221
|
+
className: "flex-1 relative overflow-hidden flex flex-col",
|
|
222
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
223
|
+
className: "flex-1 overflow-hidden",
|
|
224
|
+
children: loading ? /*#__PURE__*/ jsxs("div", {
|
|
225
|
+
className: "flex flex-col items-center justify-center h-full text-slate-500 space-y-4",
|
|
226
|
+
children: [
|
|
227
|
+
/*#__PURE__*/ jsx(Loader2, {
|
|
228
|
+
className: "w-10 h-10 animate-spin text-indigo-500"
|
|
229
|
+
}),
|
|
230
|
+
/*#__PURE__*/ jsx("p", {
|
|
231
|
+
children: "正在加载数据..."
|
|
232
|
+
})
|
|
233
|
+
]
|
|
234
|
+
}) : /*#__PURE__*/ jsx(AssetGrid, {
|
|
235
|
+
items: currentViewItems,
|
|
236
|
+
viewMode: viewMode,
|
|
237
|
+
onSelect: setSelectedItem,
|
|
238
|
+
onNavigate: handleNavigate,
|
|
239
|
+
onDelete: handleDeleteRequest
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
}),
|
|
243
|
+
selectedItem && 'folder' !== selectedItem.type && /*#__PURE__*/ jsx(InspectorPanel, {
|
|
244
|
+
item: selectedItem,
|
|
245
|
+
folders: folders,
|
|
246
|
+
onDelete: (id, type, name)=>handleDeleteRequest(id, type, name)
|
|
247
|
+
})
|
|
248
|
+
]
|
|
249
|
+
})
|
|
250
|
+
]
|
|
251
|
+
}),
|
|
252
|
+
/*#__PURE__*/ jsx(AddAssetModal, {
|
|
253
|
+
isOpen: isAddModalOpen,
|
|
254
|
+
onClose: ()=>setIsAddModalOpen(false),
|
|
255
|
+
onAdd: handleAddAsset,
|
|
256
|
+
onBatchAdd: handleBatchImport,
|
|
257
|
+
currentFolderId: currentFolderId,
|
|
258
|
+
storage: storage
|
|
259
|
+
}),
|
|
260
|
+
/*#__PURE__*/ jsx(CreateFolderModal, {
|
|
261
|
+
isOpen: isCreateFolderModalOpen,
|
|
262
|
+
onClose: ()=>setIsCreateFolderModalOpen(false),
|
|
263
|
+
onCreate: handleCreateFolder
|
|
264
|
+
}),
|
|
265
|
+
/*#__PURE__*/ jsx(DeleteConfirmModal, {
|
|
266
|
+
isOpen: deleteModalState.isOpen,
|
|
267
|
+
onClose: ()=>setDeleteModalState((prev)=>({
|
|
268
|
+
...prev,
|
|
269
|
+
isOpen: false
|
|
270
|
+
})),
|
|
271
|
+
onConfirm: executeDelete,
|
|
272
|
+
itemName: deleteModalState.itemName,
|
|
273
|
+
itemType: deleteModalState.itemType
|
|
274
|
+
})
|
|
275
|
+
]
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
export { FileManager };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Asset, Folder, StorageAdapter } from '../types';
|
|
3
|
+
export interface AddAssetModalProps {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
onAdd: (asset: Asset) => void;
|
|
7
|
+
onBatchAdd: (assets: Asset[], folders: Folder[]) => void;
|
|
8
|
+
currentFolderId: string | null;
|
|
9
|
+
storage: StorageAdapter;
|
|
10
|
+
}
|
|
11
|
+
export declare const AddAssetModal: React.FC<AddAssetModalProps>;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import { FolderPlus, Loader2, Upload, X } from "lucide-react";
|
|
4
|
+
const AddAssetModal = ({ isOpen, onClose, onAdd, onBatchAdd, currentFolderId, storage })=>{
|
|
5
|
+
const [loading, setLoading] = useState(false);
|
|
6
|
+
const [status, setStatus] = useState('');
|
|
7
|
+
const fileInputRef = useRef(null);
|
|
8
|
+
const folderInputRef = useRef(null);
|
|
9
|
+
if (!isOpen) return null;
|
|
10
|
+
const handleFileUpload = async (e)=>{
|
|
11
|
+
const file = e.target.files?.[0];
|
|
12
|
+
if (!file) return;
|
|
13
|
+
setLoading(true);
|
|
14
|
+
setStatus('正在上传到服务器...');
|
|
15
|
+
try {
|
|
16
|
+
const uploadedAsset = await storage.uploadObject(file, currentFolderId);
|
|
17
|
+
let name = uploadedAsset.name || file.name;
|
|
18
|
+
const newAsset = {
|
|
19
|
+
...uploadedAsset,
|
|
20
|
+
name: name
|
|
21
|
+
};
|
|
22
|
+
onAdd(newAsset);
|
|
23
|
+
setLoading(false);
|
|
24
|
+
onClose();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(error);
|
|
27
|
+
setStatus('文件处理出错');
|
|
28
|
+
setLoading(false);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const handleFolderUpload = async (e)=>{
|
|
32
|
+
const files = e.target.files;
|
|
33
|
+
if (!files || 0 === files.length) return;
|
|
34
|
+
setLoading(true);
|
|
35
|
+
setStatus('正在解析目录结构...');
|
|
36
|
+
const newFolders = [];
|
|
37
|
+
const newAssets = [];
|
|
38
|
+
const createdFolderPaths = new Set();
|
|
39
|
+
try {
|
|
40
|
+
const totalFiles = files.length;
|
|
41
|
+
for(let i = 0; i < files.length; i++){
|
|
42
|
+
const file = files[i];
|
|
43
|
+
const relativePath = file.webkitRelativePath;
|
|
44
|
+
const rootPrefix = currentFolderId || '';
|
|
45
|
+
const fileParts = relativePath.split('/');
|
|
46
|
+
fileParts.pop();
|
|
47
|
+
const relativeFolder = fileParts.join('/');
|
|
48
|
+
const targetFolderId = relativeFolder ? `${rootPrefix}${relativeFolder}/` : rootPrefix;
|
|
49
|
+
setStatus(`正在上传 (${i + 1}/${totalFiles}): ${file.name}`);
|
|
50
|
+
const uploadedAsset = await storage.uploadObject(file, targetFolderId);
|
|
51
|
+
newAssets.push({
|
|
52
|
+
...uploadedAsset,
|
|
53
|
+
name: file.name
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
for(let i = 0; i < files.length; i++){
|
|
57
|
+
const parts = files[i].webkitRelativePath.split('/');
|
|
58
|
+
parts.pop();
|
|
59
|
+
let currentPath = currentFolderId || '';
|
|
60
|
+
for (const part of parts){
|
|
61
|
+
const parent = currentPath || null;
|
|
62
|
+
currentPath += `${part}/`;
|
|
63
|
+
if (!createdFolderPaths.has(currentPath)) {
|
|
64
|
+
newFolders.push({
|
|
65
|
+
id: currentPath,
|
|
66
|
+
name: part,
|
|
67
|
+
parentId: parent,
|
|
68
|
+
createdAt: new Date().toISOString(),
|
|
69
|
+
type: 'folder'
|
|
70
|
+
});
|
|
71
|
+
createdFolderPaths.add(currentPath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
onBatchAdd(newAssets, newFolders);
|
|
76
|
+
setLoading(false);
|
|
77
|
+
onClose();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Batch upload failed', error);
|
|
80
|
+
setStatus('上传过程中出错');
|
|
81
|
+
setLoading(false);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return /*#__PURE__*/ jsx("div", {
|
|
85
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4",
|
|
86
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
87
|
+
className: "bg-slate-900 border border-slate-700 w-full max-w-2xl rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[90vh]",
|
|
88
|
+
children: [
|
|
89
|
+
/*#__PURE__*/ jsxs("div", {
|
|
90
|
+
className: "flex items-center justify-between p-6 border-b border-slate-800 bg-slate-900",
|
|
91
|
+
children: [
|
|
92
|
+
/*#__PURE__*/ jsx("h2", {
|
|
93
|
+
className: "text-xl font-bold text-white",
|
|
94
|
+
children: currentFolderId ? '上传文件到当前文件夹' : '上传文件到根目录'
|
|
95
|
+
}),
|
|
96
|
+
/*#__PURE__*/ jsx("button", {
|
|
97
|
+
onClick: onClose,
|
|
98
|
+
className: "text-slate-400 hover:text-white transition-colors",
|
|
99
|
+
children: /*#__PURE__*/ jsx(X, {
|
|
100
|
+
className: "w-6 h-6"
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
]
|
|
104
|
+
}),
|
|
105
|
+
/*#__PURE__*/ jsx("div", {
|
|
106
|
+
className: "flex border-b border-slate-800",
|
|
107
|
+
children: /*#__PURE__*/ jsx("button", {
|
|
108
|
+
className: "flex-1 py-4 text-sm font-medium transition-colors border-b-2 border-indigo-500 text-indigo-400 bg-slate-800/50",
|
|
109
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
110
|
+
className: "flex items-center justify-center gap-2",
|
|
111
|
+
children: [
|
|
112
|
+
/*#__PURE__*/ jsx(Upload, {
|
|
113
|
+
className: "w-4 h-4"
|
|
114
|
+
}),
|
|
115
|
+
"上传文件"
|
|
116
|
+
]
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
}),
|
|
120
|
+
/*#__PURE__*/ jsx("div", {
|
|
121
|
+
className: "p-8 overflow-y-auto",
|
|
122
|
+
children: loading ? /*#__PURE__*/ jsxs("div", {
|
|
123
|
+
className: "flex flex-col items-center justify-center py-12 space-y-4",
|
|
124
|
+
children: [
|
|
125
|
+
/*#__PURE__*/ jsx(Loader2, {
|
|
126
|
+
className: "w-12 h-12 text-indigo-500 animate-spin"
|
|
127
|
+
}),
|
|
128
|
+
/*#__PURE__*/ jsx("p", {
|
|
129
|
+
className: "text-slate-300 animate-pulse",
|
|
130
|
+
children: status
|
|
131
|
+
})
|
|
132
|
+
]
|
|
133
|
+
}) : /*#__PURE__*/ jsxs("div", {
|
|
134
|
+
className: "flex gap-4",
|
|
135
|
+
children: [
|
|
136
|
+
/*#__PURE__*/ jsxs("div", {
|
|
137
|
+
className: "flex-1 border-2 border-dashed border-slate-700 rounded-xl p-8 flex flex-col items-center justify-center text-center hover:border-indigo-500 hover:bg-slate-800/30 transition-all cursor-pointer group",
|
|
138
|
+
onClick: ()=>fileInputRef.current?.click(),
|
|
139
|
+
children: [
|
|
140
|
+
/*#__PURE__*/ jsx("input", {
|
|
141
|
+
type: "file",
|
|
142
|
+
ref: fileInputRef,
|
|
143
|
+
className: "hidden",
|
|
144
|
+
onChange: handleFileUpload
|
|
145
|
+
}),
|
|
146
|
+
/*#__PURE__*/ jsx("div", {
|
|
147
|
+
className: "w-16 h-16 bg-slate-800 rounded-full flex items-center justify-center mb-4 group-hover:bg-indigo-500/20 transition-colors",
|
|
148
|
+
children: /*#__PURE__*/ jsx(Upload, {
|
|
149
|
+
className: "w-8 h-8 text-slate-400 group-hover:text-indigo-400"
|
|
150
|
+
})
|
|
151
|
+
}),
|
|
152
|
+
/*#__PURE__*/ jsx("h3", {
|
|
153
|
+
className: "text-lg font-medium text-white mb-2",
|
|
154
|
+
children: "上传文件"
|
|
155
|
+
}),
|
|
156
|
+
/*#__PURE__*/ jsx("p", {
|
|
157
|
+
className: "text-sm text-slate-500",
|
|
158
|
+
children: "图片、视频、音频、模型等"
|
|
159
|
+
})
|
|
160
|
+
]
|
|
161
|
+
}),
|
|
162
|
+
/*#__PURE__*/ jsxs("div", {
|
|
163
|
+
className: "flex-1 border-2 border-dashed border-slate-700 rounded-xl p-8 flex flex-col items-center justify-center text-center hover:border-indigo-500 hover:bg-slate-800/30 transition-all cursor-pointer group",
|
|
164
|
+
onClick: ()=>folderInputRef.current?.click(),
|
|
165
|
+
children: [
|
|
166
|
+
/*#__PURE__*/ jsx("input", {
|
|
167
|
+
type: "file",
|
|
168
|
+
ref: folderInputRef,
|
|
169
|
+
className: "hidden",
|
|
170
|
+
webkitdirectory: "",
|
|
171
|
+
directory: "",
|
|
172
|
+
onChange: handleFolderUpload
|
|
173
|
+
}),
|
|
174
|
+
/*#__PURE__*/ jsx("div", {
|
|
175
|
+
className: "w-16 h-16 bg-slate-800 rounded-full flex items-center justify-center mb-4 group-hover:bg-indigo-500/20 transition-colors",
|
|
176
|
+
children: /*#__PURE__*/ jsx(FolderPlus, {
|
|
177
|
+
className: "w-8 h-8 text-slate-400 group-hover:text-indigo-400"
|
|
178
|
+
})
|
|
179
|
+
}),
|
|
180
|
+
/*#__PURE__*/ jsx("h3", {
|
|
181
|
+
className: "text-lg font-medium text-white mb-2",
|
|
182
|
+
children: "上传文件夹"
|
|
183
|
+
}),
|
|
184
|
+
/*#__PURE__*/ jsx("p", {
|
|
185
|
+
className: "text-sm text-slate-500",
|
|
186
|
+
children: "保留目录结构上传"
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
})
|
|
190
|
+
]
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
]
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
export { AddAssetModal };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ViewMode, FileSystemItem } from '../types';
|
|
3
|
+
export interface AssetGridProps {
|
|
4
|
+
items: FileSystemItem[];
|
|
5
|
+
viewMode: ViewMode;
|
|
6
|
+
onSelect: (item: FileSystemItem) => void;
|
|
7
|
+
onNavigate: (folderId: string) => void;
|
|
8
|
+
onDelete?: (id: string, type: 'folder' | 'asset', name: string) => void;
|
|
9
|
+
selectedIds?: Set<string>;
|
|
10
|
+
}
|
|
11
|
+
export declare const AssetGrid: React.FC<AssetGridProps>;
|