@muhgholy/next-drive 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/README.md +230 -0
- package/dist/client/index.css +48 -0
- package/dist/client/index.css.map +1 -0
- package/dist/client/index.d.ts +128 -0
- package/dist/client/index.js +2863 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index-DE7-rwJm.d.ts +87 -0
- package/dist/server/index.d.ts +115 -0
- package/dist/server/index.js +1413 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,2863 @@
|
|
|
1
|
+
// src/client/context.tsx
|
|
2
|
+
import React, { createContext, useContext, useState, useCallback, useMemo } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var DriveContext = createContext(null);
|
|
5
|
+
var DriveProvider = (props) => {
|
|
6
|
+
const { children, apiEndpoint, initialActiveAccountId = null, initialSelectionMode = { type: "SINGLE" }, defaultSelectedFileIds = [] } = props;
|
|
7
|
+
const [accounts, setAccounts] = useState([]);
|
|
8
|
+
const [activeAccountId, setActiveAccountIdState] = useState(initialActiveAccountId);
|
|
9
|
+
const [currentFolderId, setCurrentFolderId] = useState(null);
|
|
10
|
+
const [path, setPath] = useState([{ id: null, name: "Home" }]);
|
|
11
|
+
const [allItems, setAllItems] = useState({});
|
|
12
|
+
const [searchResults, setSearchResults] = useState([]);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [quota, setQuota] = useState(null);
|
|
16
|
+
const activeCacheKey = activeAccountId || "LOCAL";
|
|
17
|
+
const setActiveAccountId = useCallback((id) => {
|
|
18
|
+
setActiveAccountIdState(id);
|
|
19
|
+
if (id) localStorage.setItem("drive_active_account", id);
|
|
20
|
+
else localStorage.removeItem("drive_active_account");
|
|
21
|
+
setCurrentFolderId(null);
|
|
22
|
+
setPath([{ id: null, name: "Home" }]);
|
|
23
|
+
setSearchResults([]);
|
|
24
|
+
}, []);
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
const stored = localStorage.getItem("drive_active_account");
|
|
27
|
+
if (stored) setActiveAccountIdState(stored);
|
|
28
|
+
}, []);
|
|
29
|
+
const [viewMode, setViewMode] = useState("GRID");
|
|
30
|
+
const [currentView, setCurrentView] = useState("BROWSE");
|
|
31
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
32
|
+
const [searchScope, setSearchScope] = useState("ACTIVE");
|
|
33
|
+
const [groupBy, setGroupBy] = useState("NONE");
|
|
34
|
+
const [sortBy, setSortBy] = useState({ field: "createdAt", order: -1 });
|
|
35
|
+
const items = useMemo(() => {
|
|
36
|
+
const currentItems = allItems[activeCacheKey] || [];
|
|
37
|
+
if (currentView === "TRASH") return currentItems.filter((i) => i.trashedAt !== null);
|
|
38
|
+
if (currentView === "SEARCH") return searchResults;
|
|
39
|
+
return currentItems.filter((i) => i.parentId === currentFolderId && !i.trashedAt);
|
|
40
|
+
}, [allItems, activeCacheKey, currentFolderId, currentView, searchResults]);
|
|
41
|
+
const setItems = useCallback((updater) => {
|
|
42
|
+
setAllItems((prev) => {
|
|
43
|
+
const currentCache = prev[activeCacheKey] || [];
|
|
44
|
+
let newItems;
|
|
45
|
+
if (typeof updater === "function") {
|
|
46
|
+
const viewItems = currentCache.filter((i) => i.parentId === currentFolderId && !i.trashedAt);
|
|
47
|
+
const updatedViewItems = updater(viewItems);
|
|
48
|
+
newItems = [...currentCache.filter((i) => i.parentId !== currentFolderId || i.trashedAt), ...updatedViewItems];
|
|
49
|
+
} else {
|
|
50
|
+
newItems = [...currentCache.filter((i) => i.parentId !== currentFolderId || i.trashedAt), ...updater];
|
|
51
|
+
}
|
|
52
|
+
return { ...prev, [activeCacheKey]: newItems };
|
|
53
|
+
});
|
|
54
|
+
}, [activeCacheKey, currentFolderId]);
|
|
55
|
+
const [selectionMode] = useState(initialSelectionMode);
|
|
56
|
+
const [selectedFileIds, setSelectedFileIds] = useState(defaultSelectedFileIds);
|
|
57
|
+
const [hasMore, setHasMore] = useState(false);
|
|
58
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
59
|
+
const callAPI = useCallback(async (action, options) => {
|
|
60
|
+
const { query, ...fetchOptions } = options || {};
|
|
61
|
+
const params = new URLSearchParams({ action, ...query });
|
|
62
|
+
const url = `${apiEndpoint}?${params.toString()}`;
|
|
63
|
+
const headers = { "Content-Type": "application/json", ...fetchOptions?.headers || {} };
|
|
64
|
+
if (activeAccountId) headers["x-drive-account"] = activeAccountId;
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(url, { ...fetchOptions, headers });
|
|
67
|
+
return await response.json();
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return { status: 0, message: err instanceof Error ? err.message : "Network error" };
|
|
70
|
+
}
|
|
71
|
+
}, [apiEndpoint, activeAccountId]);
|
|
72
|
+
const refreshItems = useCallback(async () => {
|
|
73
|
+
setIsLoading(true);
|
|
74
|
+
setError(null);
|
|
75
|
+
setHasMore(false);
|
|
76
|
+
let response;
|
|
77
|
+
if (currentView === "TRASH") {
|
|
78
|
+
response = await callAPI("trash");
|
|
79
|
+
} else if (currentView === "SEARCH" && searchQuery) {
|
|
80
|
+
response = await callAPI("search", { query: { q: searchQuery, limit: "50", trashed: searchScope === "TRASH" ? "true" : "false" } });
|
|
81
|
+
} else {
|
|
82
|
+
response = await callAPI("list", { query: { folderId: currentFolderId || "root", limit: "50" } });
|
|
83
|
+
}
|
|
84
|
+
if (response.status === 200 && response.data) {
|
|
85
|
+
if (currentView === "SEARCH") {
|
|
86
|
+
setSearchResults(response.data.items);
|
|
87
|
+
} else {
|
|
88
|
+
setAllItems((prev) => {
|
|
89
|
+
const currentCache = prev[activeCacheKey] || [];
|
|
90
|
+
const existingIds = new Set(response.data.items.map((i) => i.id));
|
|
91
|
+
const filtered = currentCache.filter((i) => !existingIds.has(i.id));
|
|
92
|
+
return {
|
|
93
|
+
...prev,
|
|
94
|
+
[activeCacheKey]: [...filtered, ...response.data.items]
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
setHasMore(!!response.data.hasMore);
|
|
99
|
+
} else {
|
|
100
|
+
setError(response.message || "Failed to load items");
|
|
101
|
+
}
|
|
102
|
+
setIsLoading(false);
|
|
103
|
+
}, [callAPI, currentFolderId, currentView, searchQuery, searchScope, activeCacheKey]);
|
|
104
|
+
const refreshAccounts = useCallback(async () => {
|
|
105
|
+
const response = await callAPI("listAccounts");
|
|
106
|
+
if (response.status === 200 && response.data) {
|
|
107
|
+
setAccounts(response.data.accounts);
|
|
108
|
+
}
|
|
109
|
+
}, [callAPI]);
|
|
110
|
+
const loadMore = useCallback(async () => {
|
|
111
|
+
if (!hasMore || isLoading || isLoadingMore || currentView === "TRASH") return;
|
|
112
|
+
setIsLoadingMore(true);
|
|
113
|
+
const currentItems = items;
|
|
114
|
+
const lastItem = currentItems[currentItems.length - 1];
|
|
115
|
+
const afterId = lastItem ? lastItem.id : void 0;
|
|
116
|
+
if (!afterId) {
|
|
117
|
+
setIsLoadingMore(false);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
let response;
|
|
121
|
+
if (currentView === "SEARCH" && searchQuery) {
|
|
122
|
+
setIsLoadingMore(false);
|
|
123
|
+
return;
|
|
124
|
+
} else {
|
|
125
|
+
response = await callAPI("list", {
|
|
126
|
+
query: { folderId: currentFolderId || "root", limit: "50", afterId }
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (response && response.status === 200 && response.data) {
|
|
130
|
+
setAllItems((prev) => {
|
|
131
|
+
const currentCache = prev[activeCacheKey] || [];
|
|
132
|
+
return {
|
|
133
|
+
...prev,
|
|
134
|
+
[activeCacheKey]: [...currentCache, ...response.data.items]
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
setHasMore(!!response.data.hasMore);
|
|
138
|
+
}
|
|
139
|
+
setIsLoadingMore(false);
|
|
140
|
+
}, [callAPI, currentFolderId, hasMore, isLoading, isLoadingMore, items, currentView, searchQuery, activeCacheKey]);
|
|
141
|
+
const refreshQuota = useCallback(async () => {
|
|
142
|
+
const response = await callAPI("quota");
|
|
143
|
+
if (response.status === 200 && response.data) setQuota(response.data);
|
|
144
|
+
}, [callAPI]);
|
|
145
|
+
const navigateToFolder = useCallback((item) => {
|
|
146
|
+
setCurrentView("BROWSE");
|
|
147
|
+
setSearchQuery("");
|
|
148
|
+
if (item === null) {
|
|
149
|
+
setCurrentFolderId(null);
|
|
150
|
+
setPath([{ id: null, name: "Home" }]);
|
|
151
|
+
setSelectedFileIds([]);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
setCurrentFolderId(item.id);
|
|
155
|
+
setPath((prev) => {
|
|
156
|
+
const existingIndex = prev.findIndex((p) => p.id === item.id);
|
|
157
|
+
if (existingIndex !== -1) {
|
|
158
|
+
return prev.slice(0, existingIndex + 1);
|
|
159
|
+
}
|
|
160
|
+
return [...prev, { id: item.id, name: item.name }];
|
|
161
|
+
});
|
|
162
|
+
setSelectedFileIds([]);
|
|
163
|
+
}, []);
|
|
164
|
+
const navigateUp = useCallback(() => {
|
|
165
|
+
setCurrentView("BROWSE");
|
|
166
|
+
setSearchQuery("");
|
|
167
|
+
if (path.length <= 1) return;
|
|
168
|
+
const newPath = path.slice(0, -1);
|
|
169
|
+
setPath(newPath);
|
|
170
|
+
setCurrentFolderId(newPath[newPath.length - 1]?.id || null);
|
|
171
|
+
setSelectedFileIds([]);
|
|
172
|
+
}, [path]);
|
|
173
|
+
const moveItem = useCallback(async (itemId, targetFolderId) => {
|
|
174
|
+
setAllItems((prev) => {
|
|
175
|
+
const currentCache = prev[activeCacheKey] || [];
|
|
176
|
+
const newItems = currentCache.map(
|
|
177
|
+
(item) => item.id === itemId ? { ...item, parentId: targetFolderId === "root" ? null : targetFolderId } : item
|
|
178
|
+
);
|
|
179
|
+
return { ...prev, [activeCacheKey]: newItems };
|
|
180
|
+
});
|
|
181
|
+
await callAPI("move", {
|
|
182
|
+
method: "POST",
|
|
183
|
+
body: JSON.stringify({ ids: [itemId], targetFolderId })
|
|
184
|
+
});
|
|
185
|
+
}, [callAPI, activeCacheKey]);
|
|
186
|
+
const deleteItems = useCallback(async (ids) => {
|
|
187
|
+
setAllItems((prev) => {
|
|
188
|
+
const currentCache = prev[activeCacheKey] || [];
|
|
189
|
+
let newItems;
|
|
190
|
+
if (currentView === "TRASH") {
|
|
191
|
+
newItems = currentCache.filter((item) => !ids.includes(item.id));
|
|
192
|
+
} else {
|
|
193
|
+
newItems = currentCache.map(
|
|
194
|
+
(item) => ids.includes(item.id) ? { ...item, trashedAt: /* @__PURE__ */ new Date() } : item
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return { ...prev, [activeCacheKey]: newItems };
|
|
198
|
+
});
|
|
199
|
+
setSelectedFileIds([]);
|
|
200
|
+
if (currentView === "TRASH") {
|
|
201
|
+
for (const id of ids) {
|
|
202
|
+
await callAPI("deletePermanent", { query: { id } });
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
for (const id of ids) {
|
|
206
|
+
await callAPI("delete", { query: { id } });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}, [callAPI, activeCacheKey, currentView]);
|
|
210
|
+
React.useEffect(() => {
|
|
211
|
+
refreshItems();
|
|
212
|
+
}, [currentFolderId, currentView, refreshItems, activeAccountId]);
|
|
213
|
+
React.useEffect(() => {
|
|
214
|
+
refreshQuota();
|
|
215
|
+
}, [refreshQuota, activeAccountId]);
|
|
216
|
+
React.useEffect(() => {
|
|
217
|
+
refreshAccounts();
|
|
218
|
+
}, [refreshAccounts]);
|
|
219
|
+
return /* @__PURE__ */ jsx(DriveContext.Provider, { value: {
|
|
220
|
+
apiEndpoint,
|
|
221
|
+
currentFolderId,
|
|
222
|
+
path,
|
|
223
|
+
items,
|
|
224
|
+
allItems,
|
|
225
|
+
setItems,
|
|
226
|
+
setAllItems,
|
|
227
|
+
isLoading,
|
|
228
|
+
error,
|
|
229
|
+
quota,
|
|
230
|
+
refreshQuota,
|
|
231
|
+
accounts,
|
|
232
|
+
activeAccountId,
|
|
233
|
+
setActiveAccountId,
|
|
234
|
+
refreshAccounts,
|
|
235
|
+
viewMode,
|
|
236
|
+
setViewMode,
|
|
237
|
+
groupBy,
|
|
238
|
+
setGroupBy,
|
|
239
|
+
sortBy,
|
|
240
|
+
setSortBy,
|
|
241
|
+
currentView,
|
|
242
|
+
setCurrentView,
|
|
243
|
+
searchQuery,
|
|
244
|
+
setSearchQuery,
|
|
245
|
+
searchScope,
|
|
246
|
+
setSearchScope,
|
|
247
|
+
selectionMode,
|
|
248
|
+
selectedFileIds,
|
|
249
|
+
setSelectedFileIds,
|
|
250
|
+
navigateToFolder,
|
|
251
|
+
navigateUp,
|
|
252
|
+
refreshItems,
|
|
253
|
+
callAPI,
|
|
254
|
+
moveItem,
|
|
255
|
+
deleteItems,
|
|
256
|
+
loadMore,
|
|
257
|
+
hasMore,
|
|
258
|
+
isLoadingMore
|
|
259
|
+
}, children });
|
|
260
|
+
};
|
|
261
|
+
var useDrive = () => {
|
|
262
|
+
const context = useContext(DriveContext);
|
|
263
|
+
if (!context) throw new Error("useDrive must be used within a DriveProvider");
|
|
264
|
+
return context;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// src/client/hooks/useUpload.ts
|
|
268
|
+
import { useState as useState2, useCallback as useCallback2, useRef, useEffect } from "react";
|
|
269
|
+
var MAX_CONCURRENT_UPLOADS = 2;
|
|
270
|
+
var getChunkSize = (fileSize) => {
|
|
271
|
+
if (fileSize < 50 * 1024 * 1024) return 2 * 1024 * 1024;
|
|
272
|
+
if (fileSize < 200 * 1024 * 1024) return 4 * 1024 * 1024;
|
|
273
|
+
if (fileSize < 1024 * 1024 * 1024) return 8 * 1024 * 1024;
|
|
274
|
+
return 16 * 1024 * 1024;
|
|
275
|
+
};
|
|
276
|
+
var useUpload = (apiEndpoint, activeAccountId, onUploadComplete) => {
|
|
277
|
+
const [uploads, setUploads] = useState2([]);
|
|
278
|
+
const abortControllers = useRef(/* @__PURE__ */ new Map());
|
|
279
|
+
const filesRef = useRef(/* @__PURE__ */ new Map());
|
|
280
|
+
const metaRef = useRef(/* @__PURE__ */ new Map());
|
|
281
|
+
const updateUpload = useCallback2((id, updates) => {
|
|
282
|
+
setUploads((prev) => prev.map((u) => u.id === id ? { ...u, ...updates } : u));
|
|
283
|
+
}, []);
|
|
284
|
+
const uploadChunk = async (formData) => {
|
|
285
|
+
try {
|
|
286
|
+
const headers = {};
|
|
287
|
+
if (activeAccountId) headers["x-drive-account"] = activeAccountId;
|
|
288
|
+
const response = await fetch(`${apiEndpoint}?action=upload`, { method: "POST", body: formData, headers });
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
const text = await response.text();
|
|
291
|
+
try {
|
|
292
|
+
const json = JSON.parse(text);
|
|
293
|
+
const msg = json.message || json.error?.message || `Status ${response.status}`;
|
|
294
|
+
const canRetry = [408, 429, 502, 503, 504].includes(response.status);
|
|
295
|
+
return [false, msg, canRetry];
|
|
296
|
+
} catch {
|
|
297
|
+
const isRetryable = response.status >= 500 || response.status === 429;
|
|
298
|
+
return [false, `Server error ${response.status}: ${text.slice(0, 100)}`, isRetryable];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const data = await response.json();
|
|
302
|
+
if (data.status !== 200) return [false, data.message || "Upload failed", false];
|
|
303
|
+
return [true, data.data, false];
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return [false, error instanceof Error ? error.message : "Network error", true];
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
const processItem = async (item, file, folderId) => {
|
|
309
|
+
const controller = new AbortController();
|
|
310
|
+
abortControllers.current.set(item.id, controller);
|
|
311
|
+
updateUpload(item.id, { status: "uploading" });
|
|
312
|
+
const chunkSize = getChunkSize(file.size);
|
|
313
|
+
const totalChunks = Math.ceil(file.size / chunkSize);
|
|
314
|
+
let driveId = item.driveId;
|
|
315
|
+
try {
|
|
316
|
+
for (let i = item.currentChunk; i < totalChunks; i++) {
|
|
317
|
+
if (controller.signal.aborted) throw new Error("Cancelled");
|
|
318
|
+
updateUpload(item.id, { currentChunk: i });
|
|
319
|
+
const start = i * chunkSize;
|
|
320
|
+
const end = Math.min(start + chunkSize, file.size);
|
|
321
|
+
const chunk = file.slice(start, end);
|
|
322
|
+
const formData = new FormData();
|
|
323
|
+
formData.append("chunk", chunk);
|
|
324
|
+
formData.append("chunkIndex", String(i));
|
|
325
|
+
formData.append("totalChunks", String(totalChunks));
|
|
326
|
+
formData.append("fileName", file.name);
|
|
327
|
+
formData.append("fileSize", String(file.size));
|
|
328
|
+
formData.append("fileType", file.type);
|
|
329
|
+
formData.append("folderId", folderId || "root");
|
|
330
|
+
if (driveId) formData.append("driveId", driveId);
|
|
331
|
+
let attempts = 0;
|
|
332
|
+
let success = false;
|
|
333
|
+
while (!success && attempts < 3 && !controller.signal.aborted) {
|
|
334
|
+
const [ok, result, canRetry] = await uploadChunk(formData);
|
|
335
|
+
if (ok) {
|
|
336
|
+
success = true;
|
|
337
|
+
if (result.type === "UPLOAD_STARTED" && result.driveId) {
|
|
338
|
+
driveId = result.driveId;
|
|
339
|
+
updateUpload(item.id, { driveId });
|
|
340
|
+
} else if (result.type === "UPLOAD_COMPLETE") {
|
|
341
|
+
if (onUploadComplete) onUploadComplete(result.item);
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
if (!canRetry) throw new Error(result);
|
|
345
|
+
attempts++;
|
|
346
|
+
if (attempts === 3) throw new Error(result);
|
|
347
|
+
await new Promise((r) => setTimeout(r, 1e3 * attempts));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
updateUpload(item.id, { status: "complete", currentChunk: totalChunks });
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (error.message === "Cancelled") {
|
|
354
|
+
updateUpload(item.id, { status: "cancelled" });
|
|
355
|
+
} else {
|
|
356
|
+
updateUpload(item.id, { status: "error", error: error.message });
|
|
357
|
+
}
|
|
358
|
+
} finally {
|
|
359
|
+
abortControllers.current.delete(item.id);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
const uploadFiles = useCallback2(async (files, folderId) => {
|
|
363
|
+
const newUploads = [];
|
|
364
|
+
files.forEach((file) => {
|
|
365
|
+
const id = `upload_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
366
|
+
const chunkSize = getChunkSize(file.size);
|
|
367
|
+
filesRef.current.set(id, file);
|
|
368
|
+
metaRef.current.set(id, { folderId });
|
|
369
|
+
newUploads.push({
|
|
370
|
+
id,
|
|
371
|
+
name: file.name,
|
|
372
|
+
size: file.size,
|
|
373
|
+
status: "queued",
|
|
374
|
+
currentChunk: 0,
|
|
375
|
+
totalChunks: Math.ceil(file.size / chunkSize)
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
setUploads((prev) => [...prev, ...newUploads]);
|
|
379
|
+
}, []);
|
|
380
|
+
const cancelUpload = useCallback2(async (id) => {
|
|
381
|
+
const controller = abortControllers.current.get(id);
|
|
382
|
+
if (controller) {
|
|
383
|
+
controller.abort();
|
|
384
|
+
} else {
|
|
385
|
+
updateUpload(id, { status: "cancelled" });
|
|
386
|
+
}
|
|
387
|
+
const upload = uploads.find((u) => u.id === id);
|
|
388
|
+
if (upload?.driveId) {
|
|
389
|
+
fetch(`${apiEndpoint}?action=cancel&id=${upload.driveId}`, { method: "POST" }).catch(() => {
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}, [apiEndpoint, updateUpload, uploads]);
|
|
393
|
+
const cancelAllUploads = useCallback2(async () => {
|
|
394
|
+
uploads.forEach((u) => {
|
|
395
|
+
if (["queued", "uploading", "pending"].includes(u.status)) {
|
|
396
|
+
cancelUpload(u.id);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}, [uploads, cancelUpload]);
|
|
400
|
+
useEffect(() => {
|
|
401
|
+
const activeCount = uploads.filter((u) => u.status === "uploading").length;
|
|
402
|
+
if (activeCount >= MAX_CONCURRENT_UPLOADS) return;
|
|
403
|
+
const queued = uploads.find((u) => u.status === "queued");
|
|
404
|
+
if (queued) {
|
|
405
|
+
const file = filesRef.current.get(queued.id);
|
|
406
|
+
const meta = metaRef.current.get(queued.id);
|
|
407
|
+
if (file) {
|
|
408
|
+
processItem(queued, file, meta?.folderId || null);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}, [uploads]);
|
|
412
|
+
return { uploads, uploadFiles, cancelUpload, cancelAllUploads };
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// src/client/file-chooser.tsx
|
|
416
|
+
import { useState as useState7, useCallback as useCallback4, useMemo as useMemo3, useEffect as useEffect5 } from "react";
|
|
417
|
+
|
|
418
|
+
// src/client/utils.tsx
|
|
419
|
+
import { clsx } from "clsx";
|
|
420
|
+
import { twMerge } from "tailwind-merge";
|
|
421
|
+
import { File, Folder, Image, Video, Music, FileText, FileCode, FileArchive } from "lucide-react";
|
|
422
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
423
|
+
function cn(...inputs) {
|
|
424
|
+
return twMerge(clsx(inputs));
|
|
425
|
+
}
|
|
426
|
+
var formatBytes = (bytes, decimals = 2) => {
|
|
427
|
+
if (bytes === 0) return "0 Bytes";
|
|
428
|
+
const k = 1024;
|
|
429
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
430
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
431
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
432
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
433
|
+
};
|
|
434
|
+
var getFileIcon = (mime, isFolder, className = "w-6 h-6") => {
|
|
435
|
+
if (isFolder) return /* @__PURE__ */ jsx2(Folder, { className: cn("text-blue-500 fill-blue-500/20", className) });
|
|
436
|
+
if (mime.startsWith("image/")) return /* @__PURE__ */ jsx2(Image, { className: cn("text-purple-500", className) });
|
|
437
|
+
if (mime.startsWith("video/")) return /* @__PURE__ */ jsx2(Video, { className: cn("text-red-500", className) });
|
|
438
|
+
if (mime.startsWith("audio/")) return /* @__PURE__ */ jsx2(Music, { className: cn("text-yellow-500", className) });
|
|
439
|
+
if (mime === "application/pdf") return /* @__PURE__ */ jsx2(FileText, { className: cn("text-orange-500", className) });
|
|
440
|
+
if (mime.includes("text") || mime.includes("document")) return /* @__PURE__ */ jsx2(FileText, { className: cn("text-slate-500", className) });
|
|
441
|
+
if (mime.includes("zip") || mime.includes("compressed")) return /* @__PURE__ */ jsx2(FileArchive, { className: cn("text-amber-500", className) });
|
|
442
|
+
if (mime.includes("javascript") || mime.includes("typescript") || mime.includes("json") || mime.includes("html") || mime.includes("css")) return /* @__PURE__ */ jsx2(FileCode, { className: cn("text-green-500", className) });
|
|
443
|
+
return /* @__PURE__ */ jsx2(File, { className: cn("text-gray-400", className) });
|
|
444
|
+
};
|
|
445
|
+
var matchesMimeFilter = (mime, isFolder, filter) => {
|
|
446
|
+
if (!filter) return true;
|
|
447
|
+
if (isFolder) return true;
|
|
448
|
+
if (filter === "*/*") return true;
|
|
449
|
+
const types = filter.split(",").map((t) => t.trim());
|
|
450
|
+
return types.some((type) => {
|
|
451
|
+
if (type === mime) return true;
|
|
452
|
+
if (type.endsWith("/*")) {
|
|
453
|
+
const prefix = type.slice(0, -2);
|
|
454
|
+
return mime.startsWith(`${prefix}/`);
|
|
455
|
+
}
|
|
456
|
+
return false;
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// src/client/components/drive/explorer.tsx
|
|
461
|
+
import React7, { useMemo as useMemo2, useEffect as useEffect4, useRef as useRef3 } from "react";
|
|
462
|
+
import { Folder as Folder2, Loader2 as Loader22, Info, RotateCcw, ChevronRight as ChevronRight3 } from "lucide-react";
|
|
463
|
+
import { isToday, isYesterday, startOfWeek, subWeeks, isAfter } from "date-fns";
|
|
464
|
+
|
|
465
|
+
// src/client/components/ui/context-menu.tsx
|
|
466
|
+
import * as React2 from "react";
|
|
467
|
+
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
|
468
|
+
import { Check, ChevronRight, Circle } from "lucide-react";
|
|
469
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
470
|
+
var ContextMenu = ContextMenuPrimitive.Root;
|
|
471
|
+
var ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
|
472
|
+
var ContextMenuSubTrigger = React2.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
473
|
+
ContextMenuPrimitive.SubTrigger,
|
|
474
|
+
{
|
|
475
|
+
ref,
|
|
476
|
+
className: cn(
|
|
477
|
+
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
|
478
|
+
inset && "pl-8",
|
|
479
|
+
className
|
|
480
|
+
),
|
|
481
|
+
...props,
|
|
482
|
+
children: [
|
|
483
|
+
children,
|
|
484
|
+
/* @__PURE__ */ jsx3(ChevronRight, { className: "ml-auto h-4 w-4" })
|
|
485
|
+
]
|
|
486
|
+
}
|
|
487
|
+
));
|
|
488
|
+
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
|
|
489
|
+
var ContextMenuSubContent = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx3(
|
|
490
|
+
ContextMenuPrimitive.SubContent,
|
|
491
|
+
{
|
|
492
|
+
ref,
|
|
493
|
+
className: cn(
|
|
494
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
495
|
+
className
|
|
496
|
+
),
|
|
497
|
+
...props
|
|
498
|
+
}
|
|
499
|
+
));
|
|
500
|
+
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
|
|
501
|
+
var ContextMenuContent = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx3(ContextMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx3(
|
|
502
|
+
ContextMenuPrimitive.Content,
|
|
503
|
+
{
|
|
504
|
+
ref,
|
|
505
|
+
className: cn(
|
|
506
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
507
|
+
className
|
|
508
|
+
),
|
|
509
|
+
...props
|
|
510
|
+
}
|
|
511
|
+
) }));
|
|
512
|
+
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
|
|
513
|
+
var ContextMenuItem = React2.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx3(
|
|
514
|
+
ContextMenuPrimitive.Item,
|
|
515
|
+
{
|
|
516
|
+
ref,
|
|
517
|
+
className: cn(
|
|
518
|
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
519
|
+
inset && "pl-8",
|
|
520
|
+
className
|
|
521
|
+
),
|
|
522
|
+
...props
|
|
523
|
+
}
|
|
524
|
+
));
|
|
525
|
+
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
|
|
526
|
+
var ContextMenuCheckboxItem = React2.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
527
|
+
ContextMenuPrimitive.CheckboxItem,
|
|
528
|
+
{
|
|
529
|
+
ref,
|
|
530
|
+
className: cn(
|
|
531
|
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
532
|
+
className
|
|
533
|
+
),
|
|
534
|
+
checked,
|
|
535
|
+
...props,
|
|
536
|
+
children: [
|
|
537
|
+
/* @__PURE__ */ jsx3("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx3(ContextMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx3(Check, { className: "h-4 w-4" }) }) }),
|
|
538
|
+
children
|
|
539
|
+
]
|
|
540
|
+
}
|
|
541
|
+
));
|
|
542
|
+
ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
|
|
543
|
+
var ContextMenuRadioItem = React2.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
544
|
+
ContextMenuPrimitive.RadioItem,
|
|
545
|
+
{
|
|
546
|
+
ref,
|
|
547
|
+
className: cn(
|
|
548
|
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
549
|
+
className
|
|
550
|
+
),
|
|
551
|
+
...props,
|
|
552
|
+
children: [
|
|
553
|
+
/* @__PURE__ */ jsx3("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx3(ContextMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx3(Circle, { className: "h-2 w-2 fill-current" }) }) }),
|
|
554
|
+
children
|
|
555
|
+
]
|
|
556
|
+
}
|
|
557
|
+
));
|
|
558
|
+
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
|
|
559
|
+
var ContextMenuLabel = React2.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx3(
|
|
560
|
+
ContextMenuPrimitive.Label,
|
|
561
|
+
{
|
|
562
|
+
ref,
|
|
563
|
+
className: cn(
|
|
564
|
+
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
|
565
|
+
inset && "pl-8",
|
|
566
|
+
className
|
|
567
|
+
),
|
|
568
|
+
...props
|
|
569
|
+
}
|
|
570
|
+
));
|
|
571
|
+
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
|
|
572
|
+
var ContextMenuSeparator = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx3(
|
|
573
|
+
ContextMenuPrimitive.Separator,
|
|
574
|
+
{
|
|
575
|
+
ref,
|
|
576
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
577
|
+
...props
|
|
578
|
+
}
|
|
579
|
+
));
|
|
580
|
+
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
|
|
581
|
+
var ContextMenuShortcut = ({
|
|
582
|
+
className,
|
|
583
|
+
...props
|
|
584
|
+
}) => {
|
|
585
|
+
return /* @__PURE__ */ jsx3(
|
|
586
|
+
"span",
|
|
587
|
+
{
|
|
588
|
+
className: cn(
|
|
589
|
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
590
|
+
className
|
|
591
|
+
),
|
|
592
|
+
...props
|
|
593
|
+
}
|
|
594
|
+
);
|
|
595
|
+
};
|
|
596
|
+
ContextMenuShortcut.displayName = "ContextMenuShortcut";
|
|
597
|
+
|
|
598
|
+
// src/client/components/dialog.tsx
|
|
599
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
600
|
+
import { AlertCircle } from "lucide-react";
|
|
601
|
+
|
|
602
|
+
// src/client/components/ui/alert.tsx
|
|
603
|
+
import { cva } from "class-variance-authority";
|
|
604
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
605
|
+
var alertVariants = cva(
|
|
606
|
+
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
|
607
|
+
{
|
|
608
|
+
variants: {
|
|
609
|
+
variant: {
|
|
610
|
+
default: "bg-card text-card-foreground",
|
|
611
|
+
destructive: "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90"
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
defaultVariants: {
|
|
615
|
+
variant: "default"
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
);
|
|
619
|
+
function Alert({
|
|
620
|
+
className,
|
|
621
|
+
variant,
|
|
622
|
+
...props
|
|
623
|
+
}) {
|
|
624
|
+
return /* @__PURE__ */ jsx4(
|
|
625
|
+
"div",
|
|
626
|
+
{
|
|
627
|
+
"data-slot": "alert",
|
|
628
|
+
role: "alert",
|
|
629
|
+
className: cn(alertVariants({ variant }), className),
|
|
630
|
+
...props
|
|
631
|
+
}
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
function AlertTitle({ className, ...props }) {
|
|
635
|
+
return /* @__PURE__ */ jsx4(
|
|
636
|
+
"div",
|
|
637
|
+
{
|
|
638
|
+
"data-slot": "alert-title",
|
|
639
|
+
className: cn(
|
|
640
|
+
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
|
641
|
+
className
|
|
642
|
+
),
|
|
643
|
+
...props
|
|
644
|
+
}
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
function AlertDescription({
|
|
648
|
+
className,
|
|
649
|
+
...props
|
|
650
|
+
}) {
|
|
651
|
+
return /* @__PURE__ */ jsx4(
|
|
652
|
+
"div",
|
|
653
|
+
{
|
|
654
|
+
"data-slot": "alert-description",
|
|
655
|
+
className: cn(
|
|
656
|
+
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
|
657
|
+
className
|
|
658
|
+
),
|
|
659
|
+
...props
|
|
660
|
+
}
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/client/components/ui/button.tsx
|
|
665
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
666
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
667
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
668
|
+
var buttonVariants = cva2(
|
|
669
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
670
|
+
{
|
|
671
|
+
variants: {
|
|
672
|
+
variant: {
|
|
673
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
674
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
|
|
675
|
+
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
676
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
677
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
678
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
679
|
+
},
|
|
680
|
+
size: {
|
|
681
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
682
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
683
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
684
|
+
icon: "size-9",
|
|
685
|
+
"icon-sm": "size-8",
|
|
686
|
+
"icon-lg": "size-10"
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
defaultVariants: {
|
|
690
|
+
variant: "default",
|
|
691
|
+
size: "default"
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
);
|
|
695
|
+
function Button({
|
|
696
|
+
className,
|
|
697
|
+
variant,
|
|
698
|
+
size,
|
|
699
|
+
asChild = false,
|
|
700
|
+
...props
|
|
701
|
+
}) {
|
|
702
|
+
const Comp = asChild ? Slot : "button";
|
|
703
|
+
return /* @__PURE__ */ jsx5(
|
|
704
|
+
Comp,
|
|
705
|
+
{
|
|
706
|
+
"data-slot": "button",
|
|
707
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
708
|
+
...props
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/client/components/ui/dialog.tsx
|
|
714
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
715
|
+
import { X } from "lucide-react";
|
|
716
|
+
import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
717
|
+
function Dialog({
|
|
718
|
+
...props
|
|
719
|
+
}) {
|
|
720
|
+
return /* @__PURE__ */ jsx6(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
721
|
+
}
|
|
722
|
+
function DialogPortal({
|
|
723
|
+
...props
|
|
724
|
+
}) {
|
|
725
|
+
return /* @__PURE__ */ jsx6(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
726
|
+
}
|
|
727
|
+
function DialogOverlay({
|
|
728
|
+
className,
|
|
729
|
+
...props
|
|
730
|
+
}) {
|
|
731
|
+
return /* @__PURE__ */ jsx6(
|
|
732
|
+
DialogPrimitive.Overlay,
|
|
733
|
+
{
|
|
734
|
+
"data-slot": "dialog-overlay",
|
|
735
|
+
className: cn(
|
|
736
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
737
|
+
className
|
|
738
|
+
),
|
|
739
|
+
...props
|
|
740
|
+
}
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
function DialogContent({
|
|
744
|
+
className,
|
|
745
|
+
children,
|
|
746
|
+
showCloseButton = true,
|
|
747
|
+
...props
|
|
748
|
+
}) {
|
|
749
|
+
return /* @__PURE__ */ jsxs2(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
750
|
+
/* @__PURE__ */ jsx6(DialogOverlay, {}),
|
|
751
|
+
/* @__PURE__ */ jsxs2(
|
|
752
|
+
DialogPrimitive.Content,
|
|
753
|
+
{
|
|
754
|
+
"data-slot": "dialog-content",
|
|
755
|
+
className: cn(
|
|
756
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
|
757
|
+
className
|
|
758
|
+
),
|
|
759
|
+
...props,
|
|
760
|
+
children: [
|
|
761
|
+
children,
|
|
762
|
+
showCloseButton && /* @__PURE__ */ jsxs2(
|
|
763
|
+
DialogPrimitive.Close,
|
|
764
|
+
{
|
|
765
|
+
"data-slot": "dialog-close",
|
|
766
|
+
className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
767
|
+
children: [
|
|
768
|
+
/* @__PURE__ */ jsx6(X, { className: "size-4" }),
|
|
769
|
+
/* @__PURE__ */ jsx6("span", { className: "sr-only", children: "Close" })
|
|
770
|
+
]
|
|
771
|
+
}
|
|
772
|
+
)
|
|
773
|
+
]
|
|
774
|
+
}
|
|
775
|
+
)
|
|
776
|
+
] });
|
|
777
|
+
}
|
|
778
|
+
function DialogHeader({ className, ...props }) {
|
|
779
|
+
return /* @__PURE__ */ jsx6(
|
|
780
|
+
"div",
|
|
781
|
+
{
|
|
782
|
+
"data-slot": "dialog-header",
|
|
783
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
784
|
+
...props
|
|
785
|
+
}
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
function DialogFooter({ className, ...props }) {
|
|
789
|
+
return /* @__PURE__ */ jsx6(
|
|
790
|
+
"div",
|
|
791
|
+
{
|
|
792
|
+
"data-slot": "dialog-footer",
|
|
793
|
+
className: cn(
|
|
794
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
795
|
+
className
|
|
796
|
+
),
|
|
797
|
+
...props
|
|
798
|
+
}
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
function DialogTitle({
|
|
802
|
+
className,
|
|
803
|
+
...props
|
|
804
|
+
}) {
|
|
805
|
+
return /* @__PURE__ */ jsx6(
|
|
806
|
+
DialogPrimitive.Title,
|
|
807
|
+
{
|
|
808
|
+
"data-slot": "dialog-title",
|
|
809
|
+
className: cn("text-lg leading-none font-semibold", className),
|
|
810
|
+
...props
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
function DialogDescription({
|
|
815
|
+
className,
|
|
816
|
+
...props
|
|
817
|
+
}) {
|
|
818
|
+
return /* @__PURE__ */ jsx6(
|
|
819
|
+
DialogPrimitive.Description,
|
|
820
|
+
{
|
|
821
|
+
"data-slot": "dialog-description",
|
|
822
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
823
|
+
...props
|
|
824
|
+
}
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/client/components/ui/input.tsx
|
|
829
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
830
|
+
function Input({ className, type, ...props }) {
|
|
831
|
+
return /* @__PURE__ */ jsx7(
|
|
832
|
+
"input",
|
|
833
|
+
{
|
|
834
|
+
type,
|
|
835
|
+
"data-slot": "input",
|
|
836
|
+
className: cn(
|
|
837
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
838
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
839
|
+
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
840
|
+
className
|
|
841
|
+
),
|
|
842
|
+
...props
|
|
843
|
+
}
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/client/components/ui/label.tsx
|
|
848
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
849
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
850
|
+
function Label2({
|
|
851
|
+
className,
|
|
852
|
+
...props
|
|
853
|
+
}) {
|
|
854
|
+
return /* @__PURE__ */ jsx8(
|
|
855
|
+
LabelPrimitive.Root,
|
|
856
|
+
{
|
|
857
|
+
"data-slot": "label",
|
|
858
|
+
className: cn(
|
|
859
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
860
|
+
className
|
|
861
|
+
),
|
|
862
|
+
...props
|
|
863
|
+
}
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// src/client/components/dialog.tsx
|
|
868
|
+
import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
869
|
+
var DialogConfirmation = (props) => {
|
|
870
|
+
const { title, description, onConfirm, disableEscapeKeyDown, inputs, onClose, open } = props;
|
|
871
|
+
const [error, setError] = useState3(null);
|
|
872
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
873
|
+
const [inputsValue, setInputsValue] = useState3(
|
|
874
|
+
() => inputs?.reduce((acc, input) => ({
|
|
875
|
+
...acc,
|
|
876
|
+
[input.id]: input.default ?? ""
|
|
877
|
+
}), {}) ?? {}
|
|
878
|
+
);
|
|
879
|
+
const handleConfirm = async () => {
|
|
880
|
+
setIsLoading(true);
|
|
881
|
+
setError(null);
|
|
882
|
+
try {
|
|
883
|
+
const response = await (inputs ? onConfirm(inputsValue) : onConfirm());
|
|
884
|
+
if (response[0]) {
|
|
885
|
+
onClose();
|
|
886
|
+
} else {
|
|
887
|
+
setError(response[1]);
|
|
888
|
+
}
|
|
889
|
+
} catch (err) {
|
|
890
|
+
setError(String(err));
|
|
891
|
+
} finally {
|
|
892
|
+
setIsLoading(false);
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
const handleClose = () => {
|
|
896
|
+
if (isLoading) return;
|
|
897
|
+
onClose();
|
|
898
|
+
};
|
|
899
|
+
const handleOpenChange = (isOpen) => {
|
|
900
|
+
if (!isOpen) {
|
|
901
|
+
handleClose();
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
const handleKeyPress = (event) => {
|
|
905
|
+
if (event.key === "Enter" && isFormValid()) {
|
|
906
|
+
event.preventDefault();
|
|
907
|
+
handleConfirm();
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
const isFormValid = () => {
|
|
911
|
+
if (!inputs) return true;
|
|
912
|
+
return inputs.every(
|
|
913
|
+
(input) => !input.required || inputsValue[input.id] && inputsValue[input.id].trim() !== ""
|
|
914
|
+
);
|
|
915
|
+
};
|
|
916
|
+
useEffect2(() => {
|
|
917
|
+
setInputsValue(
|
|
918
|
+
inputs?.reduce((acc, input) => ({
|
|
919
|
+
...acc,
|
|
920
|
+
[input.id]: input.default ?? ""
|
|
921
|
+
}), {}) ?? {}
|
|
922
|
+
);
|
|
923
|
+
setError(null);
|
|
924
|
+
}, [inputs]);
|
|
925
|
+
return /* @__PURE__ */ jsx9(Dialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs3(
|
|
926
|
+
DialogContent,
|
|
927
|
+
{
|
|
928
|
+
onInteractOutside: (e) => {
|
|
929
|
+
if (disableEscapeKeyDown) {
|
|
930
|
+
e.preventDefault();
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
onEscapeKeyDown: (e) => {
|
|
934
|
+
if (disableEscapeKeyDown) {
|
|
935
|
+
e.preventDefault();
|
|
936
|
+
}
|
|
937
|
+
},
|
|
938
|
+
children: [
|
|
939
|
+
/* @__PURE__ */ jsxs3(DialogHeader, { children: [
|
|
940
|
+
/* @__PURE__ */ jsx9(DialogTitle, { children: title }),
|
|
941
|
+
/* @__PURE__ */ jsx9(DialogDescription, { children: description })
|
|
942
|
+
] }),
|
|
943
|
+
error && /* @__PURE__ */ jsxs3(Alert, { variant: "destructive", children: [
|
|
944
|
+
/* @__PURE__ */ jsx9(AlertCircle, { className: "h-4 w-4" }),
|
|
945
|
+
/* @__PURE__ */ jsx9(AlertTitle, { children: "Error" }),
|
|
946
|
+
/* @__PURE__ */ jsx9(AlertDescription, { children: error })
|
|
947
|
+
] }),
|
|
948
|
+
inputs && inputs.length > 0 && /* @__PURE__ */ jsx9("div", { className: "grid gap-4 py-4", children: inputs.map((input, index) => /* @__PURE__ */ jsxs3("div", { className: "grid gap-2", children: [
|
|
949
|
+
/* @__PURE__ */ jsx9(Label2, { htmlFor: input.id, children: input.name }),
|
|
950
|
+
/* @__PURE__ */ jsx9(
|
|
951
|
+
Input,
|
|
952
|
+
{
|
|
953
|
+
id: input.id,
|
|
954
|
+
required: input.required,
|
|
955
|
+
value: inputsValue[input.id] || "",
|
|
956
|
+
onChange: (e) => setInputsValue({ ...inputsValue, [input.id]: e.target.value }),
|
|
957
|
+
onKeyDown: index === inputs.length - 1 ? handleKeyPress : void 0
|
|
958
|
+
}
|
|
959
|
+
)
|
|
960
|
+
] }, input.id)) }),
|
|
961
|
+
/* @__PURE__ */ jsxs3(DialogFooter, { children: [
|
|
962
|
+
/* @__PURE__ */ jsx9(Button, { variant: "outline", onClick: handleClose, disabled: isLoading, children: "Cancel" }),
|
|
963
|
+
/* @__PURE__ */ jsx9(
|
|
964
|
+
Button,
|
|
965
|
+
{
|
|
966
|
+
onClick: handleConfirm,
|
|
967
|
+
disabled: isLoading || !isFormValid(),
|
|
968
|
+
children: "Confirm"
|
|
969
|
+
}
|
|
970
|
+
)
|
|
971
|
+
] })
|
|
972
|
+
]
|
|
973
|
+
}
|
|
974
|
+
) });
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
// src/client/components/drive/explorer.tsx
|
|
978
|
+
import { Pencil, Trash2 as Trash22, FolderPlus } from "lucide-react";
|
|
979
|
+
|
|
980
|
+
// src/client/components/ui/sheet.tsx
|
|
981
|
+
import * as React3 from "react";
|
|
982
|
+
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
983
|
+
import { cva as cva3 } from "class-variance-authority";
|
|
984
|
+
import { X as X2 } from "lucide-react";
|
|
985
|
+
import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
986
|
+
var Sheet = SheetPrimitive.Root;
|
|
987
|
+
var SheetTrigger = SheetPrimitive.Trigger;
|
|
988
|
+
var SheetPortal = SheetPrimitive.Portal;
|
|
989
|
+
var SheetOverlay = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
|
|
990
|
+
SheetPrimitive.Overlay,
|
|
991
|
+
{
|
|
992
|
+
className: cn(
|
|
993
|
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
994
|
+
className
|
|
995
|
+
),
|
|
996
|
+
...props,
|
|
997
|
+
ref
|
|
998
|
+
}
|
|
999
|
+
));
|
|
1000
|
+
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
|
1001
|
+
var sheetVariants = cva3(
|
|
1002
|
+
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
|
1003
|
+
{
|
|
1004
|
+
variants: {
|
|
1005
|
+
side: {
|
|
1006
|
+
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
1007
|
+
bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
1008
|
+
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
|
1009
|
+
right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm"
|
|
1010
|
+
}
|
|
1011
|
+
},
|
|
1012
|
+
defaultVariants: {
|
|
1013
|
+
side: "right"
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
);
|
|
1017
|
+
var SheetContent = React3.forwardRef(({ side = "right", className, children, ...props }, ref) => /* @__PURE__ */ jsxs4(SheetPortal, { children: [
|
|
1018
|
+
/* @__PURE__ */ jsx10(SheetOverlay, {}),
|
|
1019
|
+
/* @__PURE__ */ jsxs4(
|
|
1020
|
+
SheetPrimitive.Content,
|
|
1021
|
+
{
|
|
1022
|
+
ref,
|
|
1023
|
+
className: cn(sheetVariants({ side }), className),
|
|
1024
|
+
...props,
|
|
1025
|
+
children: [
|
|
1026
|
+
children,
|
|
1027
|
+
/* @__PURE__ */ jsxs4(SheetPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary", children: [
|
|
1028
|
+
/* @__PURE__ */ jsx10(X2, { className: "h-4 w-4" }),
|
|
1029
|
+
/* @__PURE__ */ jsx10("span", { className: "sr-only", children: "Close" })
|
|
1030
|
+
] })
|
|
1031
|
+
]
|
|
1032
|
+
}
|
|
1033
|
+
)
|
|
1034
|
+
] }));
|
|
1035
|
+
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
|
1036
|
+
var SheetHeader = ({
|
|
1037
|
+
className,
|
|
1038
|
+
...props
|
|
1039
|
+
}) => /* @__PURE__ */ jsx10(
|
|
1040
|
+
"div",
|
|
1041
|
+
{
|
|
1042
|
+
className: cn(
|
|
1043
|
+
"flex flex-col space-y-2 text-center sm:text-left",
|
|
1044
|
+
className
|
|
1045
|
+
),
|
|
1046
|
+
...props
|
|
1047
|
+
}
|
|
1048
|
+
);
|
|
1049
|
+
SheetHeader.displayName = "SheetHeader";
|
|
1050
|
+
var SheetFooter = ({
|
|
1051
|
+
className,
|
|
1052
|
+
...props
|
|
1053
|
+
}) => /* @__PURE__ */ jsx10(
|
|
1054
|
+
"div",
|
|
1055
|
+
{
|
|
1056
|
+
className: cn(
|
|
1057
|
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
1058
|
+
className
|
|
1059
|
+
),
|
|
1060
|
+
...props
|
|
1061
|
+
}
|
|
1062
|
+
);
|
|
1063
|
+
SheetFooter.displayName = "SheetFooter";
|
|
1064
|
+
var SheetTitle = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
|
|
1065
|
+
SheetPrimitive.Title,
|
|
1066
|
+
{
|
|
1067
|
+
ref,
|
|
1068
|
+
className: cn("text-lg font-semibold text-foreground", className),
|
|
1069
|
+
...props
|
|
1070
|
+
}
|
|
1071
|
+
));
|
|
1072
|
+
SheetTitle.displayName = SheetPrimitive.Title.displayName;
|
|
1073
|
+
var SheetDescription = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
|
|
1074
|
+
SheetPrimitive.Description,
|
|
1075
|
+
{
|
|
1076
|
+
ref,
|
|
1077
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
1078
|
+
...props
|
|
1079
|
+
}
|
|
1080
|
+
));
|
|
1081
|
+
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
|
1082
|
+
|
|
1083
|
+
// src/client/components/drive/file/details.tsx
|
|
1084
|
+
import { Copy, FileText as FileText2, Calendar, HardDrive, Hash, Film, Image as ImageIcon } from "lucide-react";
|
|
1085
|
+
|
|
1086
|
+
// src/client/components/ui/separator.tsx
|
|
1087
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
1088
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1089
|
+
function Separator2({
|
|
1090
|
+
className,
|
|
1091
|
+
orientation = "horizontal",
|
|
1092
|
+
decorative = true,
|
|
1093
|
+
...props
|
|
1094
|
+
}) {
|
|
1095
|
+
return /* @__PURE__ */ jsx11(
|
|
1096
|
+
SeparatorPrimitive.Root,
|
|
1097
|
+
{
|
|
1098
|
+
"data-slot": "separator",
|
|
1099
|
+
decorative,
|
|
1100
|
+
orientation,
|
|
1101
|
+
className: cn(
|
|
1102
|
+
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
1103
|
+
className
|
|
1104
|
+
),
|
|
1105
|
+
...props
|
|
1106
|
+
}
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// src/client/components/drive/file/details.tsx
|
|
1111
|
+
import { Fragment, jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1112
|
+
var DriveFileDetails = (props) => {
|
|
1113
|
+
const { item, isOpen, onClose, apiEndpoint } = props;
|
|
1114
|
+
if (!item) return null;
|
|
1115
|
+
const isFile = item.information.type === "FILE";
|
|
1116
|
+
const isFolder = item.information.type === "FOLDER";
|
|
1117
|
+
const tokenParam = item.token ? `&token=${item.token}` : "";
|
|
1118
|
+
const thumbnailUrl = `${apiEndpoint}?action=thumbnail&id=${item.id}&size=medium${tokenParam}`;
|
|
1119
|
+
const fileUrl = `${apiEndpoint}?action=serve&id=${item.id}${tokenParam}`;
|
|
1120
|
+
const DetailItem = ({ icon: Icon, label, value }) => /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-3 py-3", children: [
|
|
1121
|
+
/* @__PURE__ */ jsx12(Icon, { className: "size-4 text-muted-foreground mt-0.5" }),
|
|
1122
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex-1 space-y-0.5", children: [
|
|
1123
|
+
/* @__PURE__ */ jsx12("p", { className: "text-xs font-medium text-muted-foreground", children: label }),
|
|
1124
|
+
/* @__PURE__ */ jsx12("div", { className: "text-sm text-foreground break-all", children: value })
|
|
1125
|
+
] })
|
|
1126
|
+
] });
|
|
1127
|
+
return /* @__PURE__ */ jsx12(Sheet, { open: isOpen, onOpenChange: onClose, children: /* @__PURE__ */ jsxs5(SheetContent, { className: "w-[90%] sm:max-w-md overflow-y-auto", children: [
|
|
1128
|
+
/* @__PURE__ */ jsxs5(SheetHeader, { className: "pb-4", children: [
|
|
1129
|
+
/* @__PURE__ */ jsx12(SheetTitle, { children: "Details" }),
|
|
1130
|
+
/* @__PURE__ */ jsx12(SheetDescription, { children: "View information about this item." })
|
|
1131
|
+
] }),
|
|
1132
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-6", children: [
|
|
1133
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-col items-center p-6 bg-muted/20 rounded-lg border", children: [
|
|
1134
|
+
item.information.type === "FILE" && (item.information.mime.startsWith("image/") || item.information.mime.startsWith("video/")) ? /* @__PURE__ */ jsx12("div", { className: "aspect-video w-full rounded-md overflow-hidden bg-background border shadow-sm flex items-center justify-center", children: /* @__PURE__ */ jsx12("img", { src: thumbnailUrl, alt: item.name, className: "object-contain size-full" }) }) : /* @__PURE__ */ jsx12("div", { className: "size-20 bg-muted rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx12(FileText2, { className: "size-10 text-muted-foreground" }) }),
|
|
1135
|
+
/* @__PURE__ */ jsx12("h3", { className: "mt-4 font-medium text-center break-all", children: item.name }),
|
|
1136
|
+
/* @__PURE__ */ jsx12("p", { className: "text-xs text-muted-foreground mt-1", children: item.information.type === "FOLDER" ? "Folder" : item.information.mime })
|
|
1137
|
+
] }),
|
|
1138
|
+
/* @__PURE__ */ jsx12(Separator2, {}),
|
|
1139
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-1", children: [
|
|
1140
|
+
/* @__PURE__ */ jsx12(DetailItem, { icon: HardDrive, label: "Size", value: item.information.type === "FOLDER" ? "-" : formatBytes(item.information.sizeInBytes) }),
|
|
1141
|
+
/* @__PURE__ */ jsx12(DetailItem, { icon: Calendar, label: "Created", value: new Date(item.createdAt).toLocaleString() }),
|
|
1142
|
+
item.information.type === "FILE" && /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1143
|
+
/* @__PURE__ */ jsx12(DetailItem, { icon: FileText2, label: "Type", value: item.information.mime }),
|
|
1144
|
+
item.information.width && /* @__PURE__ */ jsx12(DetailItem, { icon: ImageIcon, label: "Dimensions", value: `${item.information.width} x ${item.information.height}` }),
|
|
1145
|
+
item.information.duration && /* @__PURE__ */ jsx12(DetailItem, { icon: Film, label: "Duration", value: `${Math.round(item.information.duration)}s` }),
|
|
1146
|
+
item.information.hash && /* @__PURE__ */ jsx12(DetailItem, { icon: Hash, label: "Hash (SHA-256)", value: /* @__PURE__ */ jsxs5("span", { className: "font-mono text-xs", children: [
|
|
1147
|
+
item.information.hash.substring(0, 16),
|
|
1148
|
+
"..."
|
|
1149
|
+
] }) })
|
|
1150
|
+
] })
|
|
1151
|
+
] }),
|
|
1152
|
+
/* @__PURE__ */ jsx12(Separator2, {}),
|
|
1153
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
|
|
1154
|
+
item.information.type === "FILE" && /* @__PURE__ */ jsx12(Button, { className: "w-full", variant: "outline", onClick: () => window.open(fileUrl, "_blank"), children: "Download / View" }),
|
|
1155
|
+
/* @__PURE__ */ jsxs5(Button, { className: "w-full", variant: "secondary", onClick: () => {
|
|
1156
|
+
navigator.clipboard.writeText(item.id);
|
|
1157
|
+
}, children: [
|
|
1158
|
+
/* @__PURE__ */ jsx12(Copy, { className: "mr-2 size-3.5" }),
|
|
1159
|
+
" Copy ID"
|
|
1160
|
+
] })
|
|
1161
|
+
] })
|
|
1162
|
+
] })
|
|
1163
|
+
] }) });
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
// src/client/components/drive/path-bar.tsx
|
|
1167
|
+
import { useDroppable } from "@dnd-kit/core";
|
|
1168
|
+
import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1169
|
+
var DroppablePathItem = (props) => {
|
|
1170
|
+
const { id, name, isLast, onClick } = props;
|
|
1171
|
+
const { currentFolderId } = useDrive();
|
|
1172
|
+
const droppableId = `path-${id ?? "root"}`;
|
|
1173
|
+
const { isOver, setNodeRef } = useDroppable({
|
|
1174
|
+
id: droppableId,
|
|
1175
|
+
data: { type: "pathItem", folderId: id }
|
|
1176
|
+
});
|
|
1177
|
+
const isCurrentFolder = id === currentFolderId;
|
|
1178
|
+
if (isLast) {
|
|
1179
|
+
return /* @__PURE__ */ jsx13(
|
|
1180
|
+
"span",
|
|
1181
|
+
{
|
|
1182
|
+
ref: setNodeRef,
|
|
1183
|
+
className: cn(
|
|
1184
|
+
"font-medium text-foreground px-1 text-xs sm:text-sm truncate max-w-30 sm:max-w-none",
|
|
1185
|
+
isOver && !isCurrentFolder && "bg-primary/20 rounded"
|
|
1186
|
+
),
|
|
1187
|
+
"aria-current": "page",
|
|
1188
|
+
title: name,
|
|
1189
|
+
children: name
|
|
1190
|
+
}
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
return /* @__PURE__ */ jsx13(
|
|
1194
|
+
Button,
|
|
1195
|
+
{
|
|
1196
|
+
ref: setNodeRef,
|
|
1197
|
+
variant: "ghost",
|
|
1198
|
+
size: "sm",
|
|
1199
|
+
className: cn(
|
|
1200
|
+
"h-auto font-normal text-xs sm:text-sm px-1.5 sm:px-2 py-0.5 sm:py-1 truncate max-w-25 sm:max-w-37.5",
|
|
1201
|
+
isOver && !isCurrentFolder && "ring-2 ring-primary bg-primary/10 scale-105"
|
|
1202
|
+
),
|
|
1203
|
+
onClick,
|
|
1204
|
+
type: "button",
|
|
1205
|
+
title: name,
|
|
1206
|
+
children: name
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
};
|
|
1210
|
+
var DrivePathBar = () => {
|
|
1211
|
+
const { path, navigateToFolder } = useDrive();
|
|
1212
|
+
return /* @__PURE__ */ jsx13("ol", { className: "flex items-center gap-1 sm:gap-1.5 text-sm text-muted-foreground bg-muted/30 px-2 sm:px-3 py-1.5 sm:py-2 rounded-md border w-full overflow-x-auto flex-nowrap min-w-0", "aria-label": "Breadcrumb", role: "navigation", children: path.map((item, index) => {
|
|
1213
|
+
const isLast = index === path.length - 1;
|
|
1214
|
+
return /* @__PURE__ */ jsxs6("li", { className: "flex items-center gap-1 sm:gap-1.5 shrink-0", children: [
|
|
1215
|
+
index > 0 && /* @__PURE__ */ jsx13("span", { className: "text-muted-foreground/50 text-xs", "aria-hidden": "true", children: "/" }),
|
|
1216
|
+
/* @__PURE__ */ jsx13(
|
|
1217
|
+
DroppablePathItem,
|
|
1218
|
+
{
|
|
1219
|
+
id: item.id,
|
|
1220
|
+
name: item.name,
|
|
1221
|
+
isLast,
|
|
1222
|
+
onClick: () => navigateToFolder(item)
|
|
1223
|
+
}
|
|
1224
|
+
)
|
|
1225
|
+
] }, item.id ?? "root");
|
|
1226
|
+
}) });
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
// src/client/components/drive/upload.tsx
|
|
1230
|
+
import React4, { useState as useState4, useRef as useRef2, useCallback as useCallback3 } from "react";
|
|
1231
|
+
import { Upload as UploadIcon, X as X3, Loader2, CheckCircle2, AlertCircle as AlertCircle2, Clock } from "lucide-react";
|
|
1232
|
+
|
|
1233
|
+
// src/client/components/ui/progress.tsx
|
|
1234
|
+
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
|
1235
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1236
|
+
function Progress({
|
|
1237
|
+
className,
|
|
1238
|
+
value,
|
|
1239
|
+
indicatorClassName,
|
|
1240
|
+
...props
|
|
1241
|
+
}) {
|
|
1242
|
+
return /* @__PURE__ */ jsx14(
|
|
1243
|
+
ProgressPrimitive.Root,
|
|
1244
|
+
{
|
|
1245
|
+
"data-slot": "progress",
|
|
1246
|
+
className: cn(
|
|
1247
|
+
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
|
1248
|
+
className
|
|
1249
|
+
),
|
|
1250
|
+
...props,
|
|
1251
|
+
children: /* @__PURE__ */ jsx14(
|
|
1252
|
+
ProgressPrimitive.Indicator,
|
|
1253
|
+
{
|
|
1254
|
+
"data-slot": "progress-indicator",
|
|
1255
|
+
className: cn("bg-primary h-full w-full flex-1 transition-all", indicatorClassName),
|
|
1256
|
+
style: { transform: `translateX(-${100 - (value || 0)}%)` }
|
|
1257
|
+
}
|
|
1258
|
+
)
|
|
1259
|
+
}
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// src/client/components/drive/upload.tsx
|
|
1264
|
+
import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1265
|
+
var UploadStatusIcon = ({ status }) => {
|
|
1266
|
+
switch (status) {
|
|
1267
|
+
case "complete":
|
|
1268
|
+
return /* @__PURE__ */ jsx15(CheckCircle2, { className: "size-4 text-emerald-500" });
|
|
1269
|
+
case "error":
|
|
1270
|
+
return /* @__PURE__ */ jsx15(AlertCircle2, { className: "size-4 text-destructive" });
|
|
1271
|
+
case "cancelled":
|
|
1272
|
+
return /* @__PURE__ */ jsx15(X3, { className: "size-4 text-muted-foreground" });
|
|
1273
|
+
case "uploading":
|
|
1274
|
+
return /* @__PURE__ */ jsx15(Loader2, { className: "size-4 text-primary animate-spin" });
|
|
1275
|
+
default:
|
|
1276
|
+
return /* @__PURE__ */ jsx15(Clock, { className: "size-4 text-muted-foreground" });
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
var DriveUpload = (props) => {
|
|
1280
|
+
const { compact = false, onComplete } = props;
|
|
1281
|
+
const [isDragging, setIsDragging] = useState4(false);
|
|
1282
|
+
const [showUploadsDialog, setShowUploadsDialog] = useState4(false);
|
|
1283
|
+
const inputRef = useRef2(null);
|
|
1284
|
+
const { currentFolderId, refreshItems, apiEndpoint, activeAccountId } = useDrive();
|
|
1285
|
+
const { uploads, uploadFiles, cancelUpload, cancelAllUploads } = useUpload(apiEndpoint, activeAccountId, () => {
|
|
1286
|
+
refreshItems();
|
|
1287
|
+
onComplete?.(null);
|
|
1288
|
+
});
|
|
1289
|
+
React4.useEffect(() => {
|
|
1290
|
+
if (!showUploadsDialog || uploads.length === 0) return;
|
|
1291
|
+
const allFinished = uploads.every(
|
|
1292
|
+
(u) => ["complete", "error", "cancelled"].includes(u.status)
|
|
1293
|
+
);
|
|
1294
|
+
if (allFinished) {
|
|
1295
|
+
const timer = setTimeout(() => {
|
|
1296
|
+
setShowUploadsDialog(false);
|
|
1297
|
+
}, 2e3);
|
|
1298
|
+
return () => clearTimeout(timer);
|
|
1299
|
+
}
|
|
1300
|
+
}, [uploads, showUploadsDialog]);
|
|
1301
|
+
const handleFiles = useCallback3((files) => {
|
|
1302
|
+
if (!files || files.length === 0) return;
|
|
1303
|
+
uploadFiles(Array.from(files), currentFolderId);
|
|
1304
|
+
setShowUploadsDialog(true);
|
|
1305
|
+
}, [uploadFiles, currentFolderId, refreshItems, onComplete]);
|
|
1306
|
+
const handleDrag = useCallback3((e, dragging) => {
|
|
1307
|
+
e.preventDefault();
|
|
1308
|
+
e.stopPropagation();
|
|
1309
|
+
setIsDragging(dragging);
|
|
1310
|
+
}, []);
|
|
1311
|
+
const handleDrop = useCallback3((e) => {
|
|
1312
|
+
e.preventDefault();
|
|
1313
|
+
e.stopPropagation();
|
|
1314
|
+
setIsDragging(false);
|
|
1315
|
+
handleFiles(e.dataTransfer.files);
|
|
1316
|
+
}, [handleFiles]);
|
|
1317
|
+
const hasUploadsInProgress = uploads.some((u) => ["uploading", "queued", "pending"].includes(u.status));
|
|
1318
|
+
const activeUploads = uploads.filter((u) => ["uploading", "queued", "pending"].includes(u.status));
|
|
1319
|
+
const renderDialog = () => /* @__PURE__ */ jsx15(Dialog, { open: showUploadsDialog, onOpenChange: setShowUploadsDialog, children: /* @__PURE__ */ jsxs7(DialogContent, { className: "sm:max-w-md p-0 gap-0", children: [
|
|
1320
|
+
/* @__PURE__ */ jsxs7(DialogHeader, { className: "px-4 py-3 border-b flex-row items-center justify-between space-y-0", children: [
|
|
1321
|
+
/* @__PURE__ */ jsx15(DialogTitle, { className: "text-base", children: "Upload Status" }),
|
|
1322
|
+
hasUploadsInProgress && /* @__PURE__ */ jsx15(
|
|
1323
|
+
Button,
|
|
1324
|
+
{
|
|
1325
|
+
type: "button",
|
|
1326
|
+
size: "sm",
|
|
1327
|
+
variant: "ghost",
|
|
1328
|
+
className: "text-destructive hover:text-destructive",
|
|
1329
|
+
onClick: cancelAllUploads,
|
|
1330
|
+
children: "Cancel All"
|
|
1331
|
+
}
|
|
1332
|
+
)
|
|
1333
|
+
] }),
|
|
1334
|
+
/* @__PURE__ */ jsxs7("div", { className: "divide-y max-h-80 overflow-y-auto", children: [
|
|
1335
|
+
uploads.length === 0 && /* @__PURE__ */ jsx15("div", { className: "p-4 text-center text-sm text-muted-foreground", children: "No uploads" }),
|
|
1336
|
+
uploads.map((upload) => {
|
|
1337
|
+
const percent = upload.status === "complete" ? 100 : upload.status === "error" || !upload.totalChunks ? 0 : Math.round(upload.currentChunk / upload.totalChunks * 100);
|
|
1338
|
+
return /* @__PURE__ */ jsxs7("div", { className: "px-4 py-2.5", children: [
|
|
1339
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-start gap-2 mb-1.5", children: [
|
|
1340
|
+
/* @__PURE__ */ jsx15(UploadStatusIcon, { status: upload.status }),
|
|
1341
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex-1 min-w-0", children: [
|
|
1342
|
+
/* @__PURE__ */ jsx15("p", { className: "text-sm font-medium truncate", children: upload.name }),
|
|
1343
|
+
/* @__PURE__ */ jsxs7("p", { className: cn(
|
|
1344
|
+
"text-xs",
|
|
1345
|
+
upload.status === "error" ? "text-destructive" : "text-muted-foreground"
|
|
1346
|
+
), children: [
|
|
1347
|
+
upload.status === "uploading" && "Uploading...",
|
|
1348
|
+
upload.status === "queued" && "Waiting in queue",
|
|
1349
|
+
upload.status === "pending" && "Preparing...",
|
|
1350
|
+
upload.status === "complete" && "Upload complete",
|
|
1351
|
+
upload.status === "error" && (upload.error || "Upload failed"),
|
|
1352
|
+
upload.status === "cancelled" && "Upload cancelled"
|
|
1353
|
+
] })
|
|
1354
|
+
] }),
|
|
1355
|
+
["uploading", "queued", "pending"].includes(upload.status) && /* @__PURE__ */ jsx15(
|
|
1356
|
+
Button,
|
|
1357
|
+
{
|
|
1358
|
+
type: "button",
|
|
1359
|
+
size: "icon",
|
|
1360
|
+
variant: "ghost",
|
|
1361
|
+
className: "shrink-0 text-muted-foreground hover:text-destructive",
|
|
1362
|
+
onClick: () => cancelUpload(upload.id),
|
|
1363
|
+
children: /* @__PURE__ */ jsx15(X3, { className: "size-3.5" })
|
|
1364
|
+
}
|
|
1365
|
+
)
|
|
1366
|
+
] }),
|
|
1367
|
+
upload.status === "uploading" && /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 pl-6", children: [
|
|
1368
|
+
/* @__PURE__ */ jsx15(Progress, { value: percent, className: "flex-1" }),
|
|
1369
|
+
/* @__PURE__ */ jsxs7("span", { className: "text-xs tabular-nums text-muted-foreground w-8", children: [
|
|
1370
|
+
percent,
|
|
1371
|
+
"%"
|
|
1372
|
+
] })
|
|
1373
|
+
] })
|
|
1374
|
+
] }, upload.id);
|
|
1375
|
+
})
|
|
1376
|
+
] })
|
|
1377
|
+
] }) });
|
|
1378
|
+
if (compact) {
|
|
1379
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1380
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
|
|
1381
|
+
/* @__PURE__ */ jsx15("input", { ref: inputRef, type: "file", multiple: true, onChange: (e) => {
|
|
1382
|
+
handleFiles(e.target.files);
|
|
1383
|
+
e.target.value = "";
|
|
1384
|
+
}, className: "hidden", "aria-hidden": "true" }),
|
|
1385
|
+
/* @__PURE__ */ jsxs7(
|
|
1386
|
+
Button,
|
|
1387
|
+
{
|
|
1388
|
+
onClick: () => inputRef.current?.click(),
|
|
1389
|
+
type: "button",
|
|
1390
|
+
size: "sm",
|
|
1391
|
+
disabled: hasUploadsInProgress,
|
|
1392
|
+
children: [
|
|
1393
|
+
/* @__PURE__ */ jsx15(UploadIcon, { className: "size-4 mr-1.5" }),
|
|
1394
|
+
" Upload Files"
|
|
1395
|
+
]
|
|
1396
|
+
}
|
|
1397
|
+
),
|
|
1398
|
+
uploads.length > 0 && /* @__PURE__ */ jsxs7(
|
|
1399
|
+
Button,
|
|
1400
|
+
{
|
|
1401
|
+
type: "button",
|
|
1402
|
+
variant: "outline",
|
|
1403
|
+
size: "sm",
|
|
1404
|
+
onClick: () => setShowUploadsDialog(true),
|
|
1405
|
+
children: [
|
|
1406
|
+
activeUploads.length > 0 && /* @__PURE__ */ jsx15(Loader2, { className: "size-3.5 mr-1.5 animate-spin" }),
|
|
1407
|
+
activeUploads.length > 0 ? `Uploading (${activeUploads.length})` : "Upload Status"
|
|
1408
|
+
]
|
|
1409
|
+
}
|
|
1410
|
+
)
|
|
1411
|
+
] }),
|
|
1412
|
+
renderDialog()
|
|
1413
|
+
] });
|
|
1414
|
+
}
|
|
1415
|
+
return /* @__PURE__ */ jsxs7("div", { className: "w-full", children: [
|
|
1416
|
+
/* @__PURE__ */ jsxs7(
|
|
1417
|
+
"div",
|
|
1418
|
+
{
|
|
1419
|
+
className: cn(
|
|
1420
|
+
"flex flex-col items-center justify-center p-8 border-2 border-dashed rounded-lg cursor-pointer transition-colors",
|
|
1421
|
+
isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-primary/50 hover:bg-muted/50"
|
|
1422
|
+
),
|
|
1423
|
+
onDragEnter: (e) => handleDrag(e, true),
|
|
1424
|
+
onDragLeave: (e) => handleDrag(e, false),
|
|
1425
|
+
onDragOver: (e) => handleDrag(e, true),
|
|
1426
|
+
onDrop: handleDrop,
|
|
1427
|
+
onClick: () => inputRef.current?.click(),
|
|
1428
|
+
role: "button",
|
|
1429
|
+
tabIndex: 0,
|
|
1430
|
+
onKeyDown: (e) => e.key === "Enter" && inputRef.current?.click(),
|
|
1431
|
+
children: [
|
|
1432
|
+
/* @__PURE__ */ jsx15("input", { ref: inputRef, type: "file", multiple: true, onChange: (e) => {
|
|
1433
|
+
handleFiles(e.target.files);
|
|
1434
|
+
e.target.value = "";
|
|
1435
|
+
}, className: "hidden", "aria-hidden": "true" }),
|
|
1436
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center gap-2 text-center", children: [
|
|
1437
|
+
/* @__PURE__ */ jsx15("div", { className: "p-3 rounded-full bg-background border shadow-sm", children: /* @__PURE__ */ jsx15(UploadIcon, { className: "size-6 text-muted-foreground" }) }),
|
|
1438
|
+
/* @__PURE__ */ jsx15("div", { className: "text-sm font-medium text-foreground", children: isDragging ? "Drop files here" : "Click or drag files to upload" })
|
|
1439
|
+
] })
|
|
1440
|
+
]
|
|
1441
|
+
}
|
|
1442
|
+
),
|
|
1443
|
+
hasUploadsInProgress && /* @__PURE__ */ jsx15("div", { className: "mt-4 text-center", children: /* @__PURE__ */ jsx15(Button, { variant: "link", onClick: () => setShowUploadsDialog(true), children: "View Upload Progress" }) }),
|
|
1444
|
+
renderDialog()
|
|
1445
|
+
] });
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
// src/client/components/drive/sidebar.tsx
|
|
1449
|
+
import {
|
|
1450
|
+
Database,
|
|
1451
|
+
HardDrive as HardDrive2,
|
|
1452
|
+
Plus,
|
|
1453
|
+
LogOut,
|
|
1454
|
+
Check as Check3,
|
|
1455
|
+
ChevronsUpDown,
|
|
1456
|
+
FolderOpen,
|
|
1457
|
+
Trash2,
|
|
1458
|
+
Menu
|
|
1459
|
+
} from "lucide-react";
|
|
1460
|
+
|
|
1461
|
+
// src/client/components/ui/dropdown-menu.tsx
|
|
1462
|
+
import * as React5 from "react";
|
|
1463
|
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
1464
|
+
import { Check as Check2, ChevronRight as ChevronRight2, Circle as Circle2 } from "lucide-react";
|
|
1465
|
+
import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1466
|
+
var DropdownMenu = DropdownMenuPrimitive.Root;
|
|
1467
|
+
var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
1468
|
+
var DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
1469
|
+
var DropdownMenuSubTrigger = React5.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs8(
|
|
1470
|
+
DropdownMenuPrimitive.SubTrigger,
|
|
1471
|
+
{
|
|
1472
|
+
ref,
|
|
1473
|
+
className: cn(
|
|
1474
|
+
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
|
1475
|
+
inset && "pl-8",
|
|
1476
|
+
className
|
|
1477
|
+
),
|
|
1478
|
+
...props,
|
|
1479
|
+
children: [
|
|
1480
|
+
children,
|
|
1481
|
+
/* @__PURE__ */ jsx16(ChevronRight2, { className: "ml-auto h-4 w-4" })
|
|
1482
|
+
]
|
|
1483
|
+
}
|
|
1484
|
+
));
|
|
1485
|
+
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
|
|
1486
|
+
var DropdownMenuSubContent = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx16(
|
|
1487
|
+
DropdownMenuPrimitive.SubContent,
|
|
1488
|
+
{
|
|
1489
|
+
ref,
|
|
1490
|
+
className: cn(
|
|
1491
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
1492
|
+
className
|
|
1493
|
+
),
|
|
1494
|
+
...props
|
|
1495
|
+
}
|
|
1496
|
+
));
|
|
1497
|
+
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
|
|
1498
|
+
var DropdownMenuContent = React5.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx16(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx16(
|
|
1499
|
+
DropdownMenuPrimitive.Content,
|
|
1500
|
+
{
|
|
1501
|
+
ref,
|
|
1502
|
+
sideOffset,
|
|
1503
|
+
className: cn(
|
|
1504
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
1505
|
+
className
|
|
1506
|
+
),
|
|
1507
|
+
...props
|
|
1508
|
+
}
|
|
1509
|
+
) }));
|
|
1510
|
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
1511
|
+
var DropdownMenuItem = React5.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx16(
|
|
1512
|
+
DropdownMenuPrimitive.Item,
|
|
1513
|
+
{
|
|
1514
|
+
ref,
|
|
1515
|
+
className: cn(
|
|
1516
|
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
1517
|
+
inset && "pl-8",
|
|
1518
|
+
className
|
|
1519
|
+
),
|
|
1520
|
+
...props
|
|
1521
|
+
}
|
|
1522
|
+
));
|
|
1523
|
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
1524
|
+
var DropdownMenuCheckboxItem = React5.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs8(
|
|
1525
|
+
DropdownMenuPrimitive.CheckboxItem,
|
|
1526
|
+
{
|
|
1527
|
+
ref,
|
|
1528
|
+
className: cn(
|
|
1529
|
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
1530
|
+
className
|
|
1531
|
+
),
|
|
1532
|
+
checked,
|
|
1533
|
+
...props,
|
|
1534
|
+
children: [
|
|
1535
|
+
/* @__PURE__ */ jsx16("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx16(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx16(Check2, { className: "h-4 w-4" }) }) }),
|
|
1536
|
+
children
|
|
1537
|
+
]
|
|
1538
|
+
}
|
|
1539
|
+
));
|
|
1540
|
+
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
1541
|
+
var DropdownMenuRadioItem = React5.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs8(
|
|
1542
|
+
DropdownMenuPrimitive.RadioItem,
|
|
1543
|
+
{
|
|
1544
|
+
ref,
|
|
1545
|
+
className: cn(
|
|
1546
|
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
1547
|
+
className
|
|
1548
|
+
),
|
|
1549
|
+
...props,
|
|
1550
|
+
children: [
|
|
1551
|
+
/* @__PURE__ */ jsx16("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx16(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx16(Circle2, { className: "h-2 w-2 fill-current" }) }) }),
|
|
1552
|
+
children
|
|
1553
|
+
]
|
|
1554
|
+
}
|
|
1555
|
+
));
|
|
1556
|
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
1557
|
+
var DropdownMenuLabel = React5.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx16(
|
|
1558
|
+
DropdownMenuPrimitive.Label,
|
|
1559
|
+
{
|
|
1560
|
+
ref,
|
|
1561
|
+
className: cn(
|
|
1562
|
+
"px-2 py-1.5 text-sm font-semibold",
|
|
1563
|
+
inset && "pl-8",
|
|
1564
|
+
className
|
|
1565
|
+
),
|
|
1566
|
+
...props
|
|
1567
|
+
}
|
|
1568
|
+
));
|
|
1569
|
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
1570
|
+
var DropdownMenuSeparator = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx16(
|
|
1571
|
+
DropdownMenuPrimitive.Separator,
|
|
1572
|
+
{
|
|
1573
|
+
ref,
|
|
1574
|
+
className: cn("-mx-1 my-1 h-px bg-muted", className),
|
|
1575
|
+
...props
|
|
1576
|
+
}
|
|
1577
|
+
));
|
|
1578
|
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
1579
|
+
var DropdownMenuShortcut = ({
|
|
1580
|
+
className,
|
|
1581
|
+
...props
|
|
1582
|
+
}) => {
|
|
1583
|
+
return /* @__PURE__ */ jsx16(
|
|
1584
|
+
"span",
|
|
1585
|
+
{
|
|
1586
|
+
className: cn("ml-auto text-xs tracking-widest opacity-60", className),
|
|
1587
|
+
...props
|
|
1588
|
+
}
|
|
1589
|
+
);
|
|
1590
|
+
};
|
|
1591
|
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
|
1592
|
+
|
|
1593
|
+
// src/client/components/drive/storage/indicator.tsx
|
|
1594
|
+
import { useEffect as useEffect3 } from "react";
|
|
1595
|
+
import { Cloud, AlertCircle as AlertCircle3 } from "lucide-react";
|
|
1596
|
+
import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1597
|
+
var DriveStorageIndicator = (props) => {
|
|
1598
|
+
const { compact = false, className } = props;
|
|
1599
|
+
const { quota, refreshQuota } = useDrive();
|
|
1600
|
+
useEffect3(() => {
|
|
1601
|
+
refreshQuota();
|
|
1602
|
+
}, []);
|
|
1603
|
+
if (!quota) return null;
|
|
1604
|
+
const { usedInBytes, totalInBytes, percentage } = quota;
|
|
1605
|
+
const isNearFull = percentage >= 90;
|
|
1606
|
+
const isFull = percentage >= 100;
|
|
1607
|
+
const stateColor = isFull ? "text-destructive" : isNearFull ? "text-yellow-600 dark:text-yellow-500" : "text-primary";
|
|
1608
|
+
const solidColor = isFull ? "bg-destructive" : isNearFull ? "bg-yellow-500" : "bg-primary";
|
|
1609
|
+
if (compact) {
|
|
1610
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn("flex items-center gap-3 text-xs font-medium text-muted-foreground", className), children: [
|
|
1611
|
+
/* @__PURE__ */ jsxs9("span", { className: "shrink-0 flex items-center gap-1.5", children: [
|
|
1612
|
+
/* @__PURE__ */ jsx17(Cloud, { className: "size-3.5" }),
|
|
1613
|
+
"Storage"
|
|
1614
|
+
] }),
|
|
1615
|
+
/* @__PURE__ */ jsx17(Progress, { value: percentage, indicatorClassName: cn("bg-gradient-to-r from-blue-500 to-cyan-500", isNearFull && "from-yellow-500 to-orange-500", isFull && "bg-destructive"), className: "w-24 sm:w-32" }),
|
|
1616
|
+
/* @__PURE__ */ jsxs9("span", { className: "shrink-0 whitespace-nowrap", children: [
|
|
1617
|
+
formatBytes(usedInBytes),
|
|
1618
|
+
" / ",
|
|
1619
|
+
formatBytes(totalInBytes)
|
|
1620
|
+
] })
|
|
1621
|
+
] });
|
|
1622
|
+
}
|
|
1623
|
+
return /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
1624
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between gap-2 mb-2", children: [
|
|
1625
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
|
|
1626
|
+
/* @__PURE__ */ jsx17(Cloud, { className: cn("size-4", stateColor) }),
|
|
1627
|
+
/* @__PURE__ */ jsx17("span", { className: "text-xs font-medium text-muted-foreground", children: "Storage" })
|
|
1628
|
+
] }),
|
|
1629
|
+
/* @__PURE__ */ jsxs9("span", { className: "text-xs font-semibold tabular-nums", children: [
|
|
1630
|
+
percentage,
|
|
1631
|
+
"%"
|
|
1632
|
+
] })
|
|
1633
|
+
] }),
|
|
1634
|
+
/* @__PURE__ */ jsx17(
|
|
1635
|
+
Progress,
|
|
1636
|
+
{
|
|
1637
|
+
value: percentage,
|
|
1638
|
+
className: "h-1.5",
|
|
1639
|
+
indicatorClassName: cn(
|
|
1640
|
+
"transition-all duration-500",
|
|
1641
|
+
isFull ? "bg-destructive" : isNearFull ? "bg-yellow-500" : "bg-gradient-to-r from-blue-500 to-purple-500"
|
|
1642
|
+
)
|
|
1643
|
+
}
|
|
1644
|
+
),
|
|
1645
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex justify-between items-center mt-1.5", children: [
|
|
1646
|
+
/* @__PURE__ */ jsx17("span", { className: "text-[10px] text-muted-foreground tabular-nums", children: formatBytes(usedInBytes) }),
|
|
1647
|
+
/* @__PURE__ */ jsx17("span", { className: "text-[10px] text-muted-foreground tabular-nums", children: formatBytes(totalInBytes) })
|
|
1648
|
+
] }),
|
|
1649
|
+
isNearFull && /* @__PURE__ */ jsxs9("div", { className: cn(
|
|
1650
|
+
"flex items-center gap-1.5 text-[10px] font-medium px-2 py-1 rounded-md mt-2",
|
|
1651
|
+
isFull ? "bg-destructive/10 text-destructive" : "bg-yellow-500/10 text-yellow-600 dark:text-yellow-500"
|
|
1652
|
+
), children: [
|
|
1653
|
+
/* @__PURE__ */ jsx17(AlertCircle3, { className: "size-3 shrink-0" }),
|
|
1654
|
+
/* @__PURE__ */ jsx17("span", { children: isFull ? "Storage full" : "Almost full" })
|
|
1655
|
+
] })
|
|
1656
|
+
] });
|
|
1657
|
+
};
|
|
1658
|
+
|
|
1659
|
+
// src/client/components/drive/sidebar.tsx
|
|
1660
|
+
import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1661
|
+
var SidebarContent = () => {
|
|
1662
|
+
const {
|
|
1663
|
+
accounts,
|
|
1664
|
+
activeAccountId,
|
|
1665
|
+
setActiveAccountId,
|
|
1666
|
+
callAPI,
|
|
1667
|
+
refreshAccounts,
|
|
1668
|
+
currentView,
|
|
1669
|
+
setCurrentView
|
|
1670
|
+
} = useDrive();
|
|
1671
|
+
const currentAccountName = activeAccountId ? accounts.find((a) => a.id === activeAccountId)?.name || "Unknown Account" : "Local Storage";
|
|
1672
|
+
const currentAccountEmail = activeAccountId ? accounts.find((a) => a.id === activeAccountId)?.email : "On this device";
|
|
1673
|
+
return /* @__PURE__ */ jsxs10("div", { className: "w-full h-full flex flex-col bg-muted/10 border-r", children: [
|
|
1674
|
+
/* @__PURE__ */ jsx18("div", { className: "p-3 border-b", children: /* @__PURE__ */ jsxs10(DropdownMenu, { children: [
|
|
1675
|
+
/* @__PURE__ */ jsx18(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs10(Button, { variant: "ghost", className: "w-full justify-between px-2 h-auto min-h-12 py-2 hover:bg-muted/50", children: [
|
|
1676
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 sm:gap-3 text-left min-w-0 flex-1", children: [
|
|
1677
|
+
/* @__PURE__ */ jsx18("div", { className: "size-8 sm:size-9 rounded-md bg-primary/10 flex items-center justify-center shrink-0", children: activeAccountId ? /* @__PURE__ */ jsx18(Database, { className: "size-4" }) : /* @__PURE__ */ jsx18(HardDrive2, { className: "size-4" }) }),
|
|
1678
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-col truncate min-w-0", children: [
|
|
1679
|
+
/* @__PURE__ */ jsx18("span", { className: "text-sm font-semibold truncate", children: currentAccountName }),
|
|
1680
|
+
/* @__PURE__ */ jsx18("span", { className: "text-xs text-muted-foreground truncate font-normal", children: currentAccountEmail })
|
|
1681
|
+
] })
|
|
1682
|
+
] }),
|
|
1683
|
+
/* @__PURE__ */ jsx18(ChevronsUpDown, { className: "size-4 text-muted-foreground shrink-0 opacity-50 ml-1" })
|
|
1684
|
+
] }) }),
|
|
1685
|
+
/* @__PURE__ */ jsxs10(DropdownMenuContent, { className: "w-60", align: "start", children: [
|
|
1686
|
+
/* @__PURE__ */ jsx18(DropdownMenuLabel, { className: "text-xs font-normal text-muted-foreground", children: "Switch Account" }),
|
|
1687
|
+
/* @__PURE__ */ jsxs10(DropdownMenuItem, { onClick: () => setActiveAccountId(null), className: "gap-2", children: [
|
|
1688
|
+
/* @__PURE__ */ jsx18("div", { className: "flex items-center justify-center size-6 rounded bg-muted", children: /* @__PURE__ */ jsx18(HardDrive2, { className: "size-3.5" }) }),
|
|
1689
|
+
/* @__PURE__ */ jsx18("div", { className: "flex flex-col flex-1", children: /* @__PURE__ */ jsx18("span", { className: "text-sm font-medium", children: "Local Storage" }) }),
|
|
1690
|
+
activeAccountId === null && /* @__PURE__ */ jsx18(Check3, { className: "size-3.5 text-primary" })
|
|
1691
|
+
] }),
|
|
1692
|
+
accounts.length > 0 && /* @__PURE__ */ jsx18(DropdownMenuSeparator, {}),
|
|
1693
|
+
accounts.map((account) => /* @__PURE__ */ jsxs10(DropdownMenuItem, { onClick: () => setActiveAccountId(account.id), className: "gap-2 group", children: [
|
|
1694
|
+
/* @__PURE__ */ jsx18("div", { className: "flex items-center justify-center size-6 rounded bg-muted", children: /* @__PURE__ */ jsx18(Database, { className: "size-3.5" }) }),
|
|
1695
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-col flex-1 overflow-hidden", children: [
|
|
1696
|
+
/* @__PURE__ */ jsx18("span", { className: "text-sm font-medium truncate", children: account.name }),
|
|
1697
|
+
/* @__PURE__ */ jsx18("span", { className: "text-xs text-muted-foreground truncate", children: account.email })
|
|
1698
|
+
] }),
|
|
1699
|
+
activeAccountId === account.id ? /* @__PURE__ */ jsx18(Check3, { className: "size-3.5 text-primary" }) : /* @__PURE__ */ jsx18(
|
|
1700
|
+
LogOut,
|
|
1701
|
+
{
|
|
1702
|
+
className: "size-3.5 text-destructive transition-opacity hover:bg-destructive/10 rounded-sm",
|
|
1703
|
+
onClick: async (e) => {
|
|
1704
|
+
e.stopPropagation();
|
|
1705
|
+
if (confirm("Are you sure you want to disconnect this account? Synced files will be deleted from local cache.")) {
|
|
1706
|
+
await callAPI("removeAccount", { query: { id: account.id } });
|
|
1707
|
+
await refreshAccounts();
|
|
1708
|
+
if (activeAccountId === account.id) setActiveAccountId(null);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
)
|
|
1713
|
+
] }, account.id)),
|
|
1714
|
+
/* @__PURE__ */ jsx18(DropdownMenuSeparator, {}),
|
|
1715
|
+
/* @__PURE__ */ jsxs10(DropdownMenuSub, { children: [
|
|
1716
|
+
/* @__PURE__ */ jsxs10(DropdownMenuSubTrigger, { className: "gap-2 text-primary focus:text-primary", children: [
|
|
1717
|
+
/* @__PURE__ */ jsx18(Plus, { className: "size-4" }),
|
|
1718
|
+
/* @__PURE__ */ jsx18("span", { className: "font-medium", children: "Add Storage Account" })
|
|
1719
|
+
] }),
|
|
1720
|
+
/* @__PURE__ */ jsx18(DropdownMenuSubContent, { children: /* @__PURE__ */ jsx18(DropdownMenuItem, { onClick: async () => {
|
|
1721
|
+
const res = await callAPI("getAuthUrl", { query: { provider: "GOOGLE" } });
|
|
1722
|
+
if (res.status !== 200 || !res.data?.url) {
|
|
1723
|
+
alert(res.message || "Failed to initialize account connection");
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
if (res.status === 200 && res.data?.url) {
|
|
1727
|
+
const width = 600;
|
|
1728
|
+
const height = 600;
|
|
1729
|
+
const left = window.screen.width / 2 - width / 2;
|
|
1730
|
+
const top = window.screen.height / 2 - height / 2;
|
|
1731
|
+
window.open(
|
|
1732
|
+
res.data.url,
|
|
1733
|
+
"Connect to Google Drive",
|
|
1734
|
+
`width=${width},height=${height},top=${top},left=${left}`
|
|
1735
|
+
);
|
|
1736
|
+
const listener = (event) => {
|
|
1737
|
+
if (event.data === "oauth-success") {
|
|
1738
|
+
refreshAccounts();
|
|
1739
|
+
window.removeEventListener("message", listener);
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
window.addEventListener("message", listener);
|
|
1743
|
+
}
|
|
1744
|
+
}, children: "Google Drive" }) })
|
|
1745
|
+
] })
|
|
1746
|
+
] })
|
|
1747
|
+
] }) }),
|
|
1748
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex-1 px-3 py-2 space-y-1", children: [
|
|
1749
|
+
/* @__PURE__ */ jsxs10(
|
|
1750
|
+
Button,
|
|
1751
|
+
{
|
|
1752
|
+
variant: currentView !== "TRASH" ? "secondary" : "ghost",
|
|
1753
|
+
className: cn("w-full justify-start gap-3", currentView !== "TRASH" && "bg-primary/10 text-primary hover:bg-primary/15"),
|
|
1754
|
+
onClick: () => setCurrentView("BROWSE"),
|
|
1755
|
+
children: [
|
|
1756
|
+
/* @__PURE__ */ jsx18(FolderOpen, { className: "size-4" }),
|
|
1757
|
+
"My Files"
|
|
1758
|
+
]
|
|
1759
|
+
}
|
|
1760
|
+
),
|
|
1761
|
+
/* @__PURE__ */ jsxs10(
|
|
1762
|
+
Button,
|
|
1763
|
+
{
|
|
1764
|
+
variant: currentView === "TRASH" ? "secondary" : "ghost",
|
|
1765
|
+
className: cn("w-full justify-start gap-3", currentView === "TRASH" && "bg-destructive/10 text-destructive hover:bg-destructive/15"),
|
|
1766
|
+
onClick: () => setCurrentView("TRASH"),
|
|
1767
|
+
children: [
|
|
1768
|
+
/* @__PURE__ */ jsx18(Trash2, { className: "size-4" }),
|
|
1769
|
+
"Trash"
|
|
1770
|
+
]
|
|
1771
|
+
}
|
|
1772
|
+
)
|
|
1773
|
+
] }),
|
|
1774
|
+
/* @__PURE__ */ jsx18("div", { className: "px-3 py-2.5 mt-auto border-t bg-background/50", children: /* @__PURE__ */ jsx18(DriveStorageIndicator, {}) })
|
|
1775
|
+
] });
|
|
1776
|
+
};
|
|
1777
|
+
var DriveSidebar = () => {
|
|
1778
|
+
return /* @__PURE__ */ jsxs10(Fragment4, { children: [
|
|
1779
|
+
/* @__PURE__ */ jsx18("div", { className: "lg:hidden", children: /* @__PURE__ */ jsxs10(Sheet, { children: [
|
|
1780
|
+
/* @__PURE__ */ jsx18(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx18(
|
|
1781
|
+
Button,
|
|
1782
|
+
{
|
|
1783
|
+
variant: "ghost",
|
|
1784
|
+
size: "icon",
|
|
1785
|
+
className: "h-9 w-9",
|
|
1786
|
+
"aria-label": "Open menu",
|
|
1787
|
+
children: /* @__PURE__ */ jsx18(Menu, { className: "h-5 w-5" })
|
|
1788
|
+
}
|
|
1789
|
+
) }),
|
|
1790
|
+
/* @__PURE__ */ jsx18(SheetContent, { side: "left", className: "w-70 sm:w-80 p-0", children: /* @__PURE__ */ jsx18(SidebarContent, {}) })
|
|
1791
|
+
] }) }),
|
|
1792
|
+
/* @__PURE__ */ jsx18("div", { className: "hidden lg:flex w-full h-full", children: /* @__PURE__ */ jsx18(SidebarContent, {}) })
|
|
1793
|
+
] });
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
// src/client/components/drive/explorer.tsx
|
|
1797
|
+
import {
|
|
1798
|
+
DndContext,
|
|
1799
|
+
closestCenter,
|
|
1800
|
+
KeyboardSensor,
|
|
1801
|
+
PointerSensor,
|
|
1802
|
+
useSensor,
|
|
1803
|
+
useSensors
|
|
1804
|
+
} from "@dnd-kit/core";
|
|
1805
|
+
import {
|
|
1806
|
+
arrayMove,
|
|
1807
|
+
SortableContext,
|
|
1808
|
+
sortableKeyboardCoordinates,
|
|
1809
|
+
rectSortingStrategy,
|
|
1810
|
+
useSortable
|
|
1811
|
+
} from "@dnd-kit/sortable";
|
|
1812
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
1813
|
+
import { Fragment as Fragment5, jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1814
|
+
var SortableItem = ({ id, children, disabled, isDragOverTarget }) => {
|
|
1815
|
+
const {
|
|
1816
|
+
attributes,
|
|
1817
|
+
listeners,
|
|
1818
|
+
setNodeRef,
|
|
1819
|
+
transform,
|
|
1820
|
+
transition,
|
|
1821
|
+
isDragging
|
|
1822
|
+
} = useSortable({ id, disabled });
|
|
1823
|
+
const style = {
|
|
1824
|
+
transform: isDragOverTarget ? void 0 : CSS.Transform.toString(transform),
|
|
1825
|
+
transition: isDragOverTarget ? "transform 0.15s ease" : transition,
|
|
1826
|
+
opacity: isDragging ? 0.5 : 1,
|
|
1827
|
+
zIndex: isDragging ? 50 : "auto",
|
|
1828
|
+
position: "relative"
|
|
1829
|
+
};
|
|
1830
|
+
return /* @__PURE__ */ jsx19("div", { ref: setNodeRef, style, ...attributes, ...listeners, children });
|
|
1831
|
+
};
|
|
1832
|
+
var DriveExplorer = (props) => {
|
|
1833
|
+
const { onItemClick, onItemDoubleClick, mimeFilter, className, selectableFolders = false } = props;
|
|
1834
|
+
const {
|
|
1835
|
+
items,
|
|
1836
|
+
isLoading,
|
|
1837
|
+
error,
|
|
1838
|
+
apiEndpoint,
|
|
1839
|
+
currentFolderId,
|
|
1840
|
+
setItems,
|
|
1841
|
+
viewMode,
|
|
1842
|
+
groupBy,
|
|
1843
|
+
sortBy,
|
|
1844
|
+
setSortBy,
|
|
1845
|
+
selectedFileIds,
|
|
1846
|
+
setSelectedFileIds,
|
|
1847
|
+
selectionMode,
|
|
1848
|
+
navigateToFolder,
|
|
1849
|
+
hasMore,
|
|
1850
|
+
loadMore,
|
|
1851
|
+
isLoadingMore,
|
|
1852
|
+
refreshItems,
|
|
1853
|
+
callAPI,
|
|
1854
|
+
currentView,
|
|
1855
|
+
moveItem,
|
|
1856
|
+
deleteItems
|
|
1857
|
+
} = useDrive();
|
|
1858
|
+
const [dialogs, setDialogs] = React7.useState({
|
|
1859
|
+
newFolder: false,
|
|
1860
|
+
rename: false,
|
|
1861
|
+
delete: false
|
|
1862
|
+
});
|
|
1863
|
+
const [itemToDelete, setItemToDelete] = React7.useState(null);
|
|
1864
|
+
const [renameItem, setRenameItem] = React7.useState(null);
|
|
1865
|
+
const [detailsItem, setDetailsItem] = React7.useState(null);
|
|
1866
|
+
const [dragOverFolderId, setDragOverFolderId] = React7.useState(null);
|
|
1867
|
+
const [draggingItemId, setDraggingItemId] = React7.useState(null);
|
|
1868
|
+
const sensors = useSensors(
|
|
1869
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
|
1870
|
+
// Prevent drag on simple click
|
|
1871
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
1872
|
+
);
|
|
1873
|
+
const handleDragStart = (event) => {
|
|
1874
|
+
setDraggingItemId(event.active.id);
|
|
1875
|
+
};
|
|
1876
|
+
const handleDragOver = (event) => {
|
|
1877
|
+
const { over } = event;
|
|
1878
|
+
if (!over) {
|
|
1879
|
+
setDragOverFolderId(null);
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
const overItem = items.find((i) => i.id === over.id);
|
|
1883
|
+
if (overItem?.information.type === "FOLDER" && over.id !== draggingItemId) {
|
|
1884
|
+
setDragOverFolderId(over.id);
|
|
1885
|
+
} else {
|
|
1886
|
+
setDragOverFolderId(null);
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
const handleDragEnd = async (event) => {
|
|
1890
|
+
const { active, over } = event;
|
|
1891
|
+
setDragOverFolderId(null);
|
|
1892
|
+
setDraggingItemId(null);
|
|
1893
|
+
if (!over || active.id === over.id) {
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
const overId = over.id;
|
|
1897
|
+
if (overId.startsWith("path-")) {
|
|
1898
|
+
const targetFolderId = overId.replace("path-", "");
|
|
1899
|
+
await moveItem(active.id, targetFolderId === "root" ? "root" : targetFolderId);
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
const overItem = items.find((i) => i.id === over.id);
|
|
1903
|
+
const activeItem = items.find((i) => i.id === active.id);
|
|
1904
|
+
if (overItem?.information.type === "FOLDER" && activeItem) {
|
|
1905
|
+
await moveItem(active.id, over.id);
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
setItems((prevItems) => {
|
|
1909
|
+
const oldIndex2 = prevItems.findIndex((i) => i.id === active.id);
|
|
1910
|
+
const newIndex2 = prevItems.findIndex((i) => i.id === over.id);
|
|
1911
|
+
return arrayMove(prevItems, oldIndex2, newIndex2);
|
|
1912
|
+
});
|
|
1913
|
+
if (sortBy.field !== "order") {
|
|
1914
|
+
setSortBy({ field: "order", order: 1 });
|
|
1915
|
+
}
|
|
1916
|
+
const oldIndex = items.findIndex((i) => i.id === active.id);
|
|
1917
|
+
const newIndex = items.findIndex((i) => i.id === over.id);
|
|
1918
|
+
const newOrderIds = arrayMove(items, oldIndex, newIndex).map((i) => i.id);
|
|
1919
|
+
await callAPI("reorder", {
|
|
1920
|
+
method: "POST",
|
|
1921
|
+
body: JSON.stringify({ ids: newOrderIds })
|
|
1922
|
+
});
|
|
1923
|
+
};
|
|
1924
|
+
const processedItems = useMemo2(() => {
|
|
1925
|
+
let filtered = items;
|
|
1926
|
+
if (mimeFilter) {
|
|
1927
|
+
filtered = filtered.filter((item) => matchesMimeFilter(item.information.type === "FILE" ? item.information.mime : "", item.information.type === "FOLDER", mimeFilter));
|
|
1928
|
+
}
|
|
1929
|
+
return [...filtered].sort((a, b) => {
|
|
1930
|
+
if (a.information.type === "FOLDER" && b.information.type !== "FOLDER") return -1;
|
|
1931
|
+
if (a.information.type !== "FOLDER" && b.information.type === "FOLDER") return 1;
|
|
1932
|
+
const field = sortBy.field;
|
|
1933
|
+
const order = sortBy.order;
|
|
1934
|
+
if (field === "order") {
|
|
1935
|
+
return 0;
|
|
1936
|
+
}
|
|
1937
|
+
let valA, valB;
|
|
1938
|
+
if (field === "name") {
|
|
1939
|
+
valA = a.name.toLowerCase();
|
|
1940
|
+
valB = b.name.toLowerCase();
|
|
1941
|
+
} else if (field === "size") {
|
|
1942
|
+
valA = a.information.type === "FILE" ? a.information.sizeInBytes : 0;
|
|
1943
|
+
valB = b.information.type === "FILE" ? b.information.sizeInBytes : 0;
|
|
1944
|
+
} else {
|
|
1945
|
+
valA = new Date(a.createdAt).getTime();
|
|
1946
|
+
valB = new Date(b.createdAt).getTime();
|
|
1947
|
+
}
|
|
1948
|
+
if (valA < valB) return -1 * order;
|
|
1949
|
+
if (valA > valB) return 1 * order;
|
|
1950
|
+
return 0;
|
|
1951
|
+
});
|
|
1952
|
+
}, [items, mimeFilter, sortBy]);
|
|
1953
|
+
const groupedItems = useMemo2(() => {
|
|
1954
|
+
if (groupBy === "NONE") return { "All": processedItems };
|
|
1955
|
+
const groups = {
|
|
1956
|
+
"Today": [],
|
|
1957
|
+
"Yesterday": [],
|
|
1958
|
+
"Earlier this Week": [],
|
|
1959
|
+
"Last Week": [],
|
|
1960
|
+
"Older": []
|
|
1961
|
+
};
|
|
1962
|
+
const now = /* @__PURE__ */ new Date();
|
|
1963
|
+
const startOfCurrentWeek = startOfWeek(now);
|
|
1964
|
+
const startOfLastWeek = startOfWeek(subWeeks(now, 1));
|
|
1965
|
+
processedItems.forEach((item) => {
|
|
1966
|
+
const date = new Date(item.createdAt);
|
|
1967
|
+
if (isToday(date)) groups["Today"].push(item);
|
|
1968
|
+
else if (isYesterday(date)) groups["Yesterday"].push(item);
|
|
1969
|
+
else if (isAfter(date, startOfCurrentWeek)) groups["Earlier this Week"].push(item);
|
|
1970
|
+
else if (isAfter(date, startOfLastWeek)) groups["Last Week"].push(item);
|
|
1971
|
+
else groups["Older"].push(item);
|
|
1972
|
+
});
|
|
1973
|
+
return Object.fromEntries(Object.entries(groups).filter(([_, items2]) => items2.length > 0));
|
|
1974
|
+
}, [processedItems, groupBy]);
|
|
1975
|
+
const observerTarget = useRef3(null);
|
|
1976
|
+
useEffect4(() => {
|
|
1977
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1978
|
+
if (entries[0].isIntersecting && hasMore && !isLoadingMore) loadMore();
|
|
1979
|
+
}, { threshold: 0.1 });
|
|
1980
|
+
if (observerTarget.current) observer.observe(observerTarget.current);
|
|
1981
|
+
return () => {
|
|
1982
|
+
if (observerTarget.current) observer.unobserve(observerTarget.current);
|
|
1983
|
+
};
|
|
1984
|
+
}, [hasMore, isLoadingMore, loadMore]);
|
|
1985
|
+
const lastTapTime = useRef3(0);
|
|
1986
|
+
const lastTapItemId = useRef3(null);
|
|
1987
|
+
const tapTimeout = useRef3(null);
|
|
1988
|
+
const handleItemClick = (e, item) => {
|
|
1989
|
+
const isTouchEvent = e.type === "touchend";
|
|
1990
|
+
if (isTouchEvent) {
|
|
1991
|
+
const now = Date.now();
|
|
1992
|
+
const timeSinceLastTap = now - lastTapTime.current;
|
|
1993
|
+
const isSameItem = lastTapItemId.current === item.id;
|
|
1994
|
+
if (timeSinceLastTap < 300 && isSameItem) {
|
|
1995
|
+
if (tapTimeout.current) {
|
|
1996
|
+
clearTimeout(tapTimeout.current);
|
|
1997
|
+
tapTimeout.current = null;
|
|
1998
|
+
}
|
|
1999
|
+
if (item.information.type === "FOLDER" && currentView === "BROWSE") {
|
|
2000
|
+
navigateToFolder(item);
|
|
2001
|
+
} else {
|
|
2002
|
+
onItemDoubleClick?.(item);
|
|
2003
|
+
}
|
|
2004
|
+
lastTapTime.current = 0;
|
|
2005
|
+
lastTapItemId.current = null;
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
lastTapTime.current = now;
|
|
2009
|
+
lastTapItemId.current = item.id;
|
|
2010
|
+
if (tapTimeout.current) clearTimeout(tapTimeout.current);
|
|
2011
|
+
if (item.information.type === "FOLDER" && currentView === "BROWSE" && !selectableFolders) {
|
|
2012
|
+
tapTimeout.current = setTimeout(() => {
|
|
2013
|
+
navigateToFolder(item);
|
|
2014
|
+
if (onItemClick) onItemClick(item);
|
|
2015
|
+
}, 250);
|
|
2016
|
+
} else {
|
|
2017
|
+
if (onItemClick) onItemClick(item);
|
|
2018
|
+
if (selectionMode.type === "MULTIPLE") {
|
|
2019
|
+
if (selectedFileIds.includes(item.id)) {
|
|
2020
|
+
setSelectedFileIds((prev) => prev.filter((id) => id !== item.id));
|
|
2021
|
+
} else {
|
|
2022
|
+
if (selectionMode.maxFile && selectedFileIds.length >= selectionMode.maxFile) return;
|
|
2023
|
+
setSelectedFileIds((prev) => [...prev, item.id]);
|
|
2024
|
+
}
|
|
2025
|
+
} else {
|
|
2026
|
+
setSelectedFileIds([item.id]);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
} else {
|
|
2030
|
+
if (item.information.type === "FOLDER" && !selectableFolders) {
|
|
2031
|
+
if (onItemClick) onItemClick(item);
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
if (onItemClick) onItemClick(item);
|
|
2035
|
+
if (selectionMode.type === "MULTIPLE") {
|
|
2036
|
+
if (selectedFileIds.includes(item.id)) {
|
|
2037
|
+
setSelectedFileIds((prev) => prev.filter((id) => id !== item.id));
|
|
2038
|
+
} else {
|
|
2039
|
+
if (selectionMode.maxFile && selectedFileIds.length >= selectionMode.maxFile) return;
|
|
2040
|
+
setSelectedFileIds((prev) => [...prev, item.id]);
|
|
2041
|
+
}
|
|
2042
|
+
} else {
|
|
2043
|
+
setSelectedFileIds([item.id]);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
const handleItemDoubleClick = (e, item) => {
|
|
2048
|
+
if (item.information.type === "FOLDER") {
|
|
2049
|
+
if (currentView === "BROWSE") navigateToFolder(item);
|
|
2050
|
+
} else onItemDoubleClick?.(item);
|
|
2051
|
+
};
|
|
2052
|
+
useEffect4(() => {
|
|
2053
|
+
return () => {
|
|
2054
|
+
if (tapTimeout.current) clearTimeout(tapTimeout.current);
|
|
2055
|
+
};
|
|
2056
|
+
}, []);
|
|
2057
|
+
const enableDrag = currentView === "BROWSE";
|
|
2058
|
+
const stateContent = (() => {
|
|
2059
|
+
if (isLoading && items.length === 0) {
|
|
2060
|
+
return /* @__PURE__ */ jsx19("div", { className: "flex items-center justify-center py-12 flex-1", children: /* @__PURE__ */ jsx19(Loader22, { className: "size-6 animate-spin text-muted-foreground" }) });
|
|
2061
|
+
}
|
|
2062
|
+
if (error) {
|
|
2063
|
+
return /* @__PURE__ */ jsx19("div", { className: "flex items-center justify-center p-12 text-destructive bg-destructive/10 rounded-lg flex-1", children: error });
|
|
2064
|
+
}
|
|
2065
|
+
if (processedItems.length === 0) {
|
|
2066
|
+
return /* @__PURE__ */ jsxs11(ContextMenu, { children: [
|
|
2067
|
+
/* @__PURE__ */ jsx19(ContextMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs11("div", { className: cn("flex flex-col items-center justify-center py-12 text-center flex-1", className), children: [
|
|
2068
|
+
/* @__PURE__ */ jsx19("div", { className: "size-12 rounded-full bg-muted flex items-center justify-center mb-3", children: /* @__PURE__ */ jsx19(Folder2, { className: "size-6 text-muted-foreground" }) }),
|
|
2069
|
+
/* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: currentView === "SEARCH" ? "No files match your search" : currentView === "TRASH" ? "Trash is empty" : "No files found" }),
|
|
2070
|
+
currentView === "BROWSE" && /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground/60 mt-1", children: "Right-click to create a folder" })
|
|
2071
|
+
] }) }),
|
|
2072
|
+
/* @__PURE__ */ jsx19(ContextMenuContent, { children: currentView === "BROWSE" ? /* @__PURE__ */ jsxs11(ContextMenuItem, { onClick: () => setDialogs((prev) => ({ ...prev, newFolder: true })), children: [
|
|
2073
|
+
/* @__PURE__ */ jsx19(FolderPlus, { className: "mr-2 size-4" }),
|
|
2074
|
+
" New Folder"
|
|
2075
|
+
] }) : /* @__PURE__ */ jsx19("div", { className: "px-2 py-6 text-center", children: /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: "No actions available" }) }) })
|
|
2076
|
+
] });
|
|
2077
|
+
}
|
|
2078
|
+
return null;
|
|
2079
|
+
})();
|
|
2080
|
+
return /* @__PURE__ */ jsx19(DndContext, { sensors, collisionDetection: closestCenter, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsxs11("div", { className: "flex flex-col h-full w-full overflow-hidden bg-background/50", children: [
|
|
2081
|
+
/* @__PURE__ */ jsxs11("div", { className: "h-14 px-3 sm:px-4 border-b bg-background/95 backdrop-blur-sm shrink-0 flex items-center gap-3", children: [
|
|
2082
|
+
/* @__PURE__ */ jsx19("div", { className: "lg:hidden", children: /* @__PURE__ */ jsx19(DriveSidebar, {}) }),
|
|
2083
|
+
/* @__PURE__ */ jsx19("div", { className: "hidden lg:flex flex-1 min-w-0", children: /* @__PURE__ */ jsx19(DrivePathBar, {}) }),
|
|
2084
|
+
/* @__PURE__ */ jsx19(DriveUpload, { compact: true, onComplete: () => refreshItems() })
|
|
2085
|
+
] }),
|
|
2086
|
+
/* @__PURE__ */ jsx19("div", { className: "lg:hidden px-3 py-2 border-b bg-background/95 backdrop-blur-sm shrink-0", children: /* @__PURE__ */ jsx19(DrivePathBar, {}) }),
|
|
2087
|
+
stateContent || /* @__PURE__ */ jsxs11(ContextMenu, { children: [
|
|
2088
|
+
/* @__PURE__ */ jsx19(ContextMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs11("div", { className: cn("flex-1 overflow-y-auto min-h-0 container mx-auto p-2 sm:p-3 md:p-4", className), children: [
|
|
2089
|
+
/* @__PURE__ */ jsx19("div", { className: "space-y-4 sm:space-y-6 pb-8 sm:pb-12", children: Object.entries(groupedItems).map(([groupName, groupItems]) => /* @__PURE__ */ jsxs11("div", { className: "space-y-3", children: [
|
|
2090
|
+
groupBy !== "NONE" && /* @__PURE__ */ jsxs11("h3", { className: "text-sm font-medium text-muted-foreground flex items-center gap-2", children: [
|
|
2091
|
+
groupName,
|
|
2092
|
+
" ",
|
|
2093
|
+
/* @__PURE__ */ jsxs11("span", { className: "text-xs opacity-50", children: [
|
|
2094
|
+
"(",
|
|
2095
|
+
groupItems.length,
|
|
2096
|
+
")"
|
|
2097
|
+
] })
|
|
2098
|
+
] }),
|
|
2099
|
+
/* @__PURE__ */ jsx19(SortableContext, { items: groupItems.map((i) => i.id), strategy: rectSortingStrategy, disabled: !enableDrag, children: /* @__PURE__ */ jsx19("div", { className: cn(
|
|
2100
|
+
viewMode === "GRID" ? "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-2 sm:gap-3 md:gap-4" : "flex flex-col gap-1"
|
|
2101
|
+
), children: groupItems.map((item) => {
|
|
2102
|
+
const isSelected = selectedFileIds.includes(item.id);
|
|
2103
|
+
const isFolder = item.information.type === "FOLDER";
|
|
2104
|
+
const isDragOver = isFolder && dragOverFolderId === item.id;
|
|
2105
|
+
const tokenParam = item.token ? `&token=${item.token}` : "";
|
|
2106
|
+
const fileUrl = `${apiEndpoint}?action=serve&id=${item.id}${tokenParam}`;
|
|
2107
|
+
const thumbnailUrl = `${apiEndpoint}?action=thumbnail&id=${item.id}&size=${viewMode === "GRID" ? "medium" : "small"}${tokenParam}`;
|
|
2108
|
+
const isThumbnailable = !isFolder && item.information.type === "FILE" && (item.information.mime.startsWith("image/") || item.information.mime.startsWith("video/"));
|
|
2109
|
+
return /* @__PURE__ */ jsx19(SortableItem, { id: item.id, disabled: !enableDrag, isDragOverTarget: isDragOver, children: /* @__PURE__ */ jsxs11(ContextMenu, { children: [
|
|
2110
|
+
/* @__PURE__ */ jsx19(ContextMenuTrigger, { children: /* @__PURE__ */ jsx19(
|
|
2111
|
+
"div",
|
|
2112
|
+
{
|
|
2113
|
+
className: cn(
|
|
2114
|
+
"group relative cursor-pointer transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2",
|
|
2115
|
+
viewMode === "GRID" ? "flex flex-col rounded-xl border bg-card hover:bg-accent/50 hover:shadow-sm overflow-hidden" : "flex items-center p-2 rounded-lg hover:bg-accent/50 gap-3 border border-transparent hover:border-border",
|
|
2116
|
+
isSelected && "ring-2 ring-primary border-primary/50 bg-accent/30",
|
|
2117
|
+
isDragOver && "ring-2 ring-primary border-primary scale-[1.02] bg-primary/10 shadow-lg transition-transform"
|
|
2118
|
+
),
|
|
2119
|
+
onClick: (e) => handleItemClick(e, item),
|
|
2120
|
+
onTouchEnd: (e) => {
|
|
2121
|
+
e.preventDefault();
|
|
2122
|
+
handleItemClick(e, item);
|
|
2123
|
+
},
|
|
2124
|
+
onDoubleClick: (e) => handleItemDoubleClick(e, item),
|
|
2125
|
+
role: "button",
|
|
2126
|
+
tabIndex: 0,
|
|
2127
|
+
children: viewMode === "GRID" ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
2128
|
+
/* @__PURE__ */ jsxs11("div", { className: "aspect-square w-full bg-muted/20 flex items-center justify-center overflow-hidden relative", children: [
|
|
2129
|
+
isThumbnailable ? /* @__PURE__ */ jsx19("img", { src: thumbnailUrl, alt: item.name, className: "size-full object-contain transition-transform group-hover:scale-105 duration-300", loading: "lazy" }) : /* @__PURE__ */ jsx19("div", { className: "transition-transform group-hover:scale-110 duration-200", children: getFileIcon(item.information.type === "FILE" ? item.information.mime : "", isFolder, "size-10 text-muted-foreground/70") }),
|
|
2130
|
+
isSelected && /* @__PURE__ */ jsx19("div", { className: "absolute top-2 right-2 size-5 bg-primary rounded-full flex items-center justify-center shadow-sm animate-in zoom-in-50", children: /* @__PURE__ */ jsx19("div", { className: "size-2 bg-primary-foreground rounded-full" }) }),
|
|
2131
|
+
isFolder && currentView === "BROWSE" && /* @__PURE__ */ jsx19("div", { className: "absolute bottom-2 right-2 lg:hidden size-6 bg-primary/90 rounded-full flex items-center justify-center shadow-md", children: /* @__PURE__ */ jsx19(ChevronRight3, { className: "size-3.5 text-primary-foreground" }) })
|
|
2132
|
+
] }),
|
|
2133
|
+
/* @__PURE__ */ jsxs11("div", { className: "p-3", children: [
|
|
2134
|
+
/* @__PURE__ */ jsx19("div", { className: "flex items-start justify-between gap-2", children: /* @__PURE__ */ jsx19("span", { className: "text-sm font-medium truncate w-full text-card-foreground", title: item.name, children: item.name }) }),
|
|
2135
|
+
/* @__PURE__ */ jsx19("div", { className: "flex items-center justify-between mt-1 text-xs text-muted-foreground", children: /* @__PURE__ */ jsx19("span", { children: isFolder ? "Folder" : formatBytes(item.information.type === "FILE" ? item.information.sizeInBytes : 0) }) })
|
|
2136
|
+
] })
|
|
2137
|
+
] }) : /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
2138
|
+
/* @__PURE__ */ jsx19("div", { className: "size-9 shrink-0 rounded-md overflow-hidden bg-muted/40 flex items-center justify-center border", children: isThumbnailable ? /* @__PURE__ */ jsx19("img", { src: thumbnailUrl, alt: item.name, className: "size-full object-contain", loading: "lazy" }) : getFileIcon(item.information.type === "FILE" ? item.information.mime : "", isFolder, "size-4 text-muted-foreground") }),
|
|
2139
|
+
/* @__PURE__ */ jsx19("span", { className: "text-sm font-medium truncate flex-1 text-card-foreground", title: item.name, children: item.name }),
|
|
2140
|
+
/* @__PURE__ */ jsx19("span", { className: "text-xs text-muted-foreground w-20 text-right", children: isFolder ? "-" : formatBytes(item.information.type === "FILE" ? item.information.sizeInBytes : 0) }),
|
|
2141
|
+
isFolder && currentView === "BROWSE" && /* @__PURE__ */ jsx19(ChevronRight3, { className: "size-4 text-muted-foreground lg:hidden shrink-0" })
|
|
2142
|
+
] })
|
|
2143
|
+
}
|
|
2144
|
+
) }),
|
|
2145
|
+
/* @__PURE__ */ jsx19(ContextMenuContent, { children: currentView === "TRASH" ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
2146
|
+
/* @__PURE__ */ jsxs11(ContextMenuItem, { onClick: async () => {
|
|
2147
|
+
await callAPI("restore", { method: "POST", query: { id: item.id } });
|
|
2148
|
+
await refreshItems();
|
|
2149
|
+
}, children: [
|
|
2150
|
+
/* @__PURE__ */ jsx19(RotateCcw, { className: "mr-2 size-4" }),
|
|
2151
|
+
" Restore"
|
|
2152
|
+
] }),
|
|
2153
|
+
/* @__PURE__ */ jsx19(ContextMenuSeparator, {}),
|
|
2154
|
+
/* @__PURE__ */ jsxs11(ContextMenuItem, { className: "text-destructive focus:text-destructive", onClick: () => {
|
|
2155
|
+
setItemToDelete(item);
|
|
2156
|
+
setDialogs((prev) => ({ ...prev, delete: true }));
|
|
2157
|
+
}, children: [
|
|
2158
|
+
/* @__PURE__ */ jsx19(Trash22, { className: "mr-2 size-4" }),
|
|
2159
|
+
" Delete Forever"
|
|
2160
|
+
] })
|
|
2161
|
+
] }) : /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
2162
|
+
/* @__PURE__ */ jsxs11(ContextMenuItem, { onClick: () => setDetailsItem(item), children: [
|
|
2163
|
+
/* @__PURE__ */ jsx19(Info, { className: "mr-2 size-4" }),
|
|
2164
|
+
" Details"
|
|
2165
|
+
] }),
|
|
2166
|
+
/* @__PURE__ */ jsxs11(ContextMenuItem, { onClick: () => {
|
|
2167
|
+
setRenameItem(item);
|
|
2168
|
+
setDialogs((prev) => ({ ...prev, rename: true }));
|
|
2169
|
+
}, children: [
|
|
2170
|
+
/* @__PURE__ */ jsx19(Pencil, { className: "mr-2 size-4" }),
|
|
2171
|
+
" Rename"
|
|
2172
|
+
] }),
|
|
2173
|
+
/* @__PURE__ */ jsx19(ContextMenuSeparator, {}),
|
|
2174
|
+
/* @__PURE__ */ jsxs11(ContextMenuItem, { className: "text-destructive focus:text-destructive", onClick: () => {
|
|
2175
|
+
setItemToDelete(item);
|
|
2176
|
+
setDialogs((prev) => ({ ...prev, delete: true }));
|
|
2177
|
+
}, children: [
|
|
2178
|
+
/* @__PURE__ */ jsx19(Trash22, { className: "mr-2 size-4" }),
|
|
2179
|
+
" Delete"
|
|
2180
|
+
] })
|
|
2181
|
+
] }) })
|
|
2182
|
+
] }) }, item.id);
|
|
2183
|
+
}) }) })
|
|
2184
|
+
] }, groupName)) }),
|
|
2185
|
+
hasMore && /* @__PURE__ */ jsxs11("div", { ref: observerTarget, className: "flex justify-center py-4", children: [
|
|
2186
|
+
isLoadingMore && /* @__PURE__ */ jsx19(Loader22, { className: "size-6 animate-spin text-muted-foreground" }),
|
|
2187
|
+
!isLoadingMore && /* @__PURE__ */ jsx19("div", { className: "h-4 w-full" })
|
|
2188
|
+
] }),
|
|
2189
|
+
/* @__PURE__ */ jsx19(
|
|
2190
|
+
DriveFileDetails,
|
|
2191
|
+
{
|
|
2192
|
+
item: detailsItem,
|
|
2193
|
+
isOpen: !!detailsItem,
|
|
2194
|
+
onClose: () => setDetailsItem(null),
|
|
2195
|
+
apiEndpoint
|
|
2196
|
+
}
|
|
2197
|
+
),
|
|
2198
|
+
/* @__PURE__ */ jsx19(
|
|
2199
|
+
DialogConfirmation,
|
|
2200
|
+
{
|
|
2201
|
+
open: dialogs.newFolder,
|
|
2202
|
+
onClose: () => setDialogs((prev) => ({ ...prev, newFolder: false })),
|
|
2203
|
+
title: "Create New Folder",
|
|
2204
|
+
description: "Enter a name for the new folder",
|
|
2205
|
+
inputs: [{ type: "INPUT", id: "name", name: "Folder name", required: true }],
|
|
2206
|
+
onConfirm: async (inputs) => {
|
|
2207
|
+
try {
|
|
2208
|
+
await callAPI("createFolder", {
|
|
2209
|
+
method: "POST",
|
|
2210
|
+
body: JSON.stringify({ name: inputs.name, parentId: currentFolderId || "root" })
|
|
2211
|
+
});
|
|
2212
|
+
await refreshItems();
|
|
2213
|
+
return [true];
|
|
2214
|
+
} catch (error2) {
|
|
2215
|
+
return [false, error2 instanceof Error ? error2.message : "Failed to create folder"];
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
),
|
|
2220
|
+
/* @__PURE__ */ jsx19(
|
|
2221
|
+
DialogConfirmation,
|
|
2222
|
+
{
|
|
2223
|
+
open: dialogs.rename,
|
|
2224
|
+
onClose: () => {
|
|
2225
|
+
setDialogs((prev) => ({ ...prev, rename: false }));
|
|
2226
|
+
setRenameItem(null);
|
|
2227
|
+
},
|
|
2228
|
+
title: `Rename ${renameItem?.information.type === "FOLDER" ? "Folder" : "File"}`,
|
|
2229
|
+
description: "Enter a new name",
|
|
2230
|
+
inputs: [{ type: "INPUT", id: "name", name: "Name", default: renameItem?.name, required: true }],
|
|
2231
|
+
onConfirm: async (inputs) => {
|
|
2232
|
+
if (!renameItem) return [false, "No item selected"];
|
|
2233
|
+
try {
|
|
2234
|
+
await callAPI("rename", {
|
|
2235
|
+
method: "PATCH",
|
|
2236
|
+
query: { id: renameItem.id },
|
|
2237
|
+
body: JSON.stringify({ newName: inputs.name })
|
|
2238
|
+
});
|
|
2239
|
+
await refreshItems();
|
|
2240
|
+
setRenameItem(null);
|
|
2241
|
+
return [true];
|
|
2242
|
+
} catch (error2) {
|
|
2243
|
+
return [false, error2 instanceof Error ? error2.message : "Failed to rename"];
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
),
|
|
2248
|
+
/* @__PURE__ */ jsx19(
|
|
2249
|
+
DialogConfirmation,
|
|
2250
|
+
{
|
|
2251
|
+
open: dialogs.delete,
|
|
2252
|
+
onClose: () => {
|
|
2253
|
+
setDialogs((prev) => ({ ...prev, delete: false }));
|
|
2254
|
+
setItemToDelete(null);
|
|
2255
|
+
},
|
|
2256
|
+
title: currentView === "TRASH" ? "Delete Permanently?" : "Move to Trash?",
|
|
2257
|
+
description: currentView === "TRASH" ? `This will permanently delete "${itemToDelete?.name}". You cannot undo this action.` : `Are you sure you want to move "${itemToDelete?.name}" to trash?`,
|
|
2258
|
+
onConfirm: async () => {
|
|
2259
|
+
if (!itemToDelete) return [false, "No item selected"];
|
|
2260
|
+
try {
|
|
2261
|
+
await deleteItems([itemToDelete.id]);
|
|
2262
|
+
await refreshItems();
|
|
2263
|
+
setItemToDelete(null);
|
|
2264
|
+
return [true];
|
|
2265
|
+
} catch (error2) {
|
|
2266
|
+
return [false, error2 instanceof Error ? error2.message : "Failed to delete"];
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
)
|
|
2271
|
+
] }) }),
|
|
2272
|
+
/* @__PURE__ */ jsx19(ContextMenuContent, { children: currentView === "BROWSE" ? /* @__PURE__ */ jsxs11(ContextMenuItem, { onClick: () => setDialogs((prev) => ({ ...prev, newFolder: true })), children: [
|
|
2273
|
+
/* @__PURE__ */ jsx19(FolderPlus, { className: "mr-2 size-4" }),
|
|
2274
|
+
" New Folder"
|
|
2275
|
+
] }) : /* @__PURE__ */ jsx19("div", { className: "px-2 py-6 text-center", children: /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: "No actions available" }) }) })
|
|
2276
|
+
] })
|
|
2277
|
+
] }) });
|
|
2278
|
+
};
|
|
2279
|
+
|
|
2280
|
+
// src/client/components/drive/header.tsx
|
|
2281
|
+
import { useState as useState5 } from "react";
|
|
2282
|
+
import {
|
|
2283
|
+
Trash2 as Trash23,
|
|
2284
|
+
ArrowUpDown,
|
|
2285
|
+
LayoutGrid,
|
|
2286
|
+
List,
|
|
2287
|
+
Group as Group3,
|
|
2288
|
+
Calendar as Calendar2,
|
|
2289
|
+
ArrowDownAZ,
|
|
2290
|
+
ArrowUpAZ,
|
|
2291
|
+
ArrowDown01,
|
|
2292
|
+
ArrowUp01,
|
|
2293
|
+
Check as Check4,
|
|
2294
|
+
RotateCcw as RotateCcw2,
|
|
2295
|
+
Menu as Menu2
|
|
2296
|
+
} from "lucide-react";
|
|
2297
|
+
import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2298
|
+
var DriveHeader = () => {
|
|
2299
|
+
const {
|
|
2300
|
+
viewMode,
|
|
2301
|
+
setViewMode,
|
|
2302
|
+
groupBy,
|
|
2303
|
+
setGroupBy,
|
|
2304
|
+
sortBy,
|
|
2305
|
+
setSortBy,
|
|
2306
|
+
selectedFileIds,
|
|
2307
|
+
setItems,
|
|
2308
|
+
apiEndpoint,
|
|
2309
|
+
setSelectedFileIds,
|
|
2310
|
+
currentView,
|
|
2311
|
+
setCurrentView,
|
|
2312
|
+
searchQuery,
|
|
2313
|
+
setSearchQuery,
|
|
2314
|
+
callAPI,
|
|
2315
|
+
refreshItems,
|
|
2316
|
+
searchScope,
|
|
2317
|
+
setSearchScope
|
|
2318
|
+
} = useDrive();
|
|
2319
|
+
const [dialogs, setDialogs] = useState5({ delete: false, emptyTrash: false });
|
|
2320
|
+
return /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2 border-b bg-muted/30 p-2", children: [
|
|
2321
|
+
(selectedFileIds.length > 0 || currentView === "TRASH") && /* @__PURE__ */ jsxs12(DropdownMenu, { children: [
|
|
2322
|
+
/* @__PURE__ */ jsx20(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx20(
|
|
2323
|
+
Button,
|
|
2324
|
+
{
|
|
2325
|
+
type: "button",
|
|
2326
|
+
variant: "outline",
|
|
2327
|
+
size: "sm",
|
|
2328
|
+
className: "gap-1.5",
|
|
2329
|
+
children: /* @__PURE__ */ jsx20(Menu2, { className: "size-4" })
|
|
2330
|
+
}
|
|
2331
|
+
) }),
|
|
2332
|
+
/* @__PURE__ */ jsxs12(DropdownMenuContent, { align: "start", children: [
|
|
2333
|
+
selectedFileIds.length > 0 && currentView === "TRASH" && /* @__PURE__ */ jsxs12(
|
|
2334
|
+
DropdownMenuItem,
|
|
2335
|
+
{
|
|
2336
|
+
onClick: async () => {
|
|
2337
|
+
try {
|
|
2338
|
+
await Promise.all(selectedFileIds.map(
|
|
2339
|
+
(id) => fetch(`${apiEndpoint}?action=restore&id=${id}`, { method: "POST" })
|
|
2340
|
+
));
|
|
2341
|
+
await refreshItems();
|
|
2342
|
+
setSelectedFileIds([]);
|
|
2343
|
+
} catch (error) {
|
|
2344
|
+
console.error("Error restoring files:", error);
|
|
2345
|
+
}
|
|
2346
|
+
},
|
|
2347
|
+
children: [
|
|
2348
|
+
/* @__PURE__ */ jsx20(RotateCcw2, { className: "size-3.5 mr-2" }),
|
|
2349
|
+
"Restore (",
|
|
2350
|
+
selectedFileIds.length,
|
|
2351
|
+
")"
|
|
2352
|
+
]
|
|
2353
|
+
}
|
|
2354
|
+
),
|
|
2355
|
+
selectedFileIds.length > 0 && /* @__PURE__ */ jsxs12(
|
|
2356
|
+
DropdownMenuItem,
|
|
2357
|
+
{
|
|
2358
|
+
onClick: () => setDialogs((prev) => ({ ...prev, delete: true })),
|
|
2359
|
+
className: "text-destructive focus:text-destructive",
|
|
2360
|
+
children: [
|
|
2361
|
+
/* @__PURE__ */ jsx20(Trash23, { className: "size-3.5 mr-2" }),
|
|
2362
|
+
currentView === "TRASH" ? "Delete Forever" : "Delete",
|
|
2363
|
+
" (",
|
|
2364
|
+
selectedFileIds.length,
|
|
2365
|
+
")"
|
|
2366
|
+
]
|
|
2367
|
+
}
|
|
2368
|
+
),
|
|
2369
|
+
currentView === "TRASH" && selectedFileIds.length === 0 && /* @__PURE__ */ jsxs12(
|
|
2370
|
+
DropdownMenuItem,
|
|
2371
|
+
{
|
|
2372
|
+
onClick: () => setDialogs((prev) => ({ ...prev, emptyTrash: true })),
|
|
2373
|
+
className: "text-destructive focus:text-destructive",
|
|
2374
|
+
children: [
|
|
2375
|
+
/* @__PURE__ */ jsx20(Trash23, { className: "size-3.5 mr-2" }),
|
|
2376
|
+
"Empty Trash"
|
|
2377
|
+
]
|
|
2378
|
+
}
|
|
2379
|
+
)
|
|
2380
|
+
] })
|
|
2381
|
+
] }),
|
|
2382
|
+
/* @__PURE__ */ jsxs12(DropdownMenu, { children: [
|
|
2383
|
+
/* @__PURE__ */ jsx20(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs12(
|
|
2384
|
+
Button,
|
|
2385
|
+
{
|
|
2386
|
+
type: "button",
|
|
2387
|
+
variant: groupBy !== "NONE" ? "secondary" : "ghost",
|
|
2388
|
+
size: "sm",
|
|
2389
|
+
className: "gap-1.5",
|
|
2390
|
+
children: [
|
|
2391
|
+
/* @__PURE__ */ jsx20(Group3, { className: "size-3.5" }),
|
|
2392
|
+
/* @__PURE__ */ jsx20("span", { className: "hidden sm:inline", children: groupBy === "NONE" ? "Group" : "Grouped" })
|
|
2393
|
+
]
|
|
2394
|
+
}
|
|
2395
|
+
) }),
|
|
2396
|
+
/* @__PURE__ */ jsxs12(DropdownMenuContent, { align: "start", children: [
|
|
2397
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setGroupBy("NONE"), children: [
|
|
2398
|
+
groupBy === "NONE" && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2399
|
+
/* @__PURE__ */ jsx20("span", { className: cn(groupBy !== "NONE" && "pl-5.5"), children: "No Grouping" })
|
|
2400
|
+
] }),
|
|
2401
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setGroupBy("CREATED_AT"), children: [
|
|
2402
|
+
groupBy === "CREATED_AT" && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2403
|
+
/* @__PURE__ */ jsx20("span", { className: cn(groupBy !== "CREATED_AT" && "pl-5.5"), children: "Created Date" })
|
|
2404
|
+
] })
|
|
2405
|
+
] })
|
|
2406
|
+
] }),
|
|
2407
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex-1 min-w-0 sm:min-w-50 relative", children: [
|
|
2408
|
+
/* @__PURE__ */ jsx20(
|
|
2409
|
+
Input,
|
|
2410
|
+
{
|
|
2411
|
+
type: "text",
|
|
2412
|
+
placeholder: "Search files...",
|
|
2413
|
+
className: "w-full pl-8 pr-7 h-9",
|
|
2414
|
+
value: searchQuery,
|
|
2415
|
+
onChange: (e) => {
|
|
2416
|
+
const val = e.target.value;
|
|
2417
|
+
setSearchQuery(val);
|
|
2418
|
+
if (val.trim().length > 0) {
|
|
2419
|
+
if (currentView !== "SEARCH") {
|
|
2420
|
+
setSearchScope(currentView === "TRASH" ? "TRASH" : "ACTIVE");
|
|
2421
|
+
}
|
|
2422
|
+
setCurrentView("SEARCH");
|
|
2423
|
+
} else if (currentView === "SEARCH") {
|
|
2424
|
+
setCurrentView(searchScope === "TRASH" ? "TRASH" : "BROWSE");
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
),
|
|
2429
|
+
/* @__PURE__ */ jsx20("div", { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none", children: /* @__PURE__ */ jsxs12("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-search", children: [
|
|
2430
|
+
/* @__PURE__ */ jsx20("circle", { cx: "11", cy: "11", r: "8" }),
|
|
2431
|
+
/* @__PURE__ */ jsx20("path", { d: "m21 21-4.3-4.3" })
|
|
2432
|
+
] }) }),
|
|
2433
|
+
searchQuery && /* @__PURE__ */ jsx20(
|
|
2434
|
+
Button,
|
|
2435
|
+
{
|
|
2436
|
+
type: "button",
|
|
2437
|
+
variant: "ghost",
|
|
2438
|
+
size: "icon",
|
|
2439
|
+
className: "absolute right-0.5 top-1/2 -translate-y-1/2 h-7 w-7 text-muted-foreground",
|
|
2440
|
+
onClick: () => {
|
|
2441
|
+
setSearchQuery("");
|
|
2442
|
+
setCurrentView(searchScope === "TRASH" ? "TRASH" : "BROWSE");
|
|
2443
|
+
},
|
|
2444
|
+
children: /* @__PURE__ */ jsxs12("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-x", children: [
|
|
2445
|
+
/* @__PURE__ */ jsx20("path", { d: "M18 6 6 18" }),
|
|
2446
|
+
/* @__PURE__ */ jsx20("path", { d: "m6 6 12 12" })
|
|
2447
|
+
] })
|
|
2448
|
+
}
|
|
2449
|
+
)
|
|
2450
|
+
] }),
|
|
2451
|
+
/* @__PURE__ */ jsxs12(DropdownMenu, { children: [
|
|
2452
|
+
/* @__PURE__ */ jsx20(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs12(Button, { type: "button", variant: "ghost", size: "sm", className: "gap-1.5", children: [
|
|
2453
|
+
/* @__PURE__ */ jsx20(ArrowUpDown, { className: "size-3.5" }),
|
|
2454
|
+
/* @__PURE__ */ jsx20("span", { className: "hidden sm:inline max-w-24 truncate", children: sortBy.field === "id" ? "Default" : sortBy.field === "order" ? "Custom" : sortBy.field === "createdAt" ? sortBy.order === -1 ? "Date: Newest" : "Date: Oldest" : sortBy.field === "name" ? sortBy.order === 1 ? "Name: A to Z" : "Name: Z to A" : sortBy.field === "size" ? sortBy.order === -1 ? "Size: Large" : "Size: Small" : "Sort" })
|
|
2455
|
+
] }) }),
|
|
2456
|
+
/* @__PURE__ */ jsxs12(DropdownMenuContent, { align: "end", children: [
|
|
2457
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setSortBy({ field: "order", order: 1 }), children: [
|
|
2458
|
+
sortBy.field === "order" && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2459
|
+
/* @__PURE__ */ jsx20("span", { className: cn(sortBy.field !== "order" && "pl-5.5"), children: "Custom Order" })
|
|
2460
|
+
] }),
|
|
2461
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setSortBy({ field: "id", order: 1 }), children: [
|
|
2462
|
+
sortBy.field === "id" && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2463
|
+
/* @__PURE__ */ jsx20("span", { className: cn(sortBy.field !== "id" && "pl-5.5"), children: "Default" })
|
|
2464
|
+
] }),
|
|
2465
|
+
/* @__PURE__ */ jsx20(DropdownMenuSeparator, {}),
|
|
2466
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setSortBy({ field: "createdAt", order: -1 }), children: [
|
|
2467
|
+
sortBy.field === "createdAt" && sortBy.order === -1 && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2468
|
+
/* @__PURE__ */ jsx20(Calendar2, { className: cn("size-3.5 mr-2", sortBy.field === "createdAt" && sortBy.order === -1 ? "" : "ml-5.5") }),
|
|
2469
|
+
"Date: Newest"
|
|
2470
|
+
] }),
|
|
2471
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setSortBy({ field: "createdAt", order: 1 }), children: [
|
|
2472
|
+
sortBy.field === "createdAt" && sortBy.order === 1 && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2473
|
+
/* @__PURE__ */ jsx20(Calendar2, { className: cn("size-3.5 mr-2", sortBy.field === "createdAt" && sortBy.order === 1 ? "" : "ml-5.5") }),
|
|
2474
|
+
"Date: Oldest"
|
|
2475
|
+
] }),
|
|
2476
|
+
/* @__PURE__ */ jsx20(DropdownMenuSeparator, {}),
|
|
2477
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setSortBy({ field: "name", order: 1 }), children: [
|
|
2478
|
+
sortBy.field === "name" && sortBy.order === 1 && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2479
|
+
/* @__PURE__ */ jsx20(ArrowDownAZ, { className: cn("size-3.5 mr-2", sortBy.field === "name" && sortBy.order === 1 ? "" : "ml-5.5") }),
|
|
2480
|
+
"Name: A to Z"
|
|
2481
|
+
] }),
|
|
2482
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setSortBy({ field: "name", order: -1 }), children: [
|
|
2483
|
+
sortBy.field === "name" && sortBy.order === -1 && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2484
|
+
/* @__PURE__ */ jsx20(ArrowUpAZ, { className: cn("size-3.5 mr-2", sortBy.field === "name" && sortBy.order === -1 ? "" : "ml-5.5") }),
|
|
2485
|
+
"Name: Z to A"
|
|
2486
|
+
] }),
|
|
2487
|
+
/* @__PURE__ */ jsx20(DropdownMenuSeparator, {}),
|
|
2488
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setSortBy({ field: "size", order: -1 }), children: [
|
|
2489
|
+
sortBy.field === "size" && sortBy.order === -1 && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2490
|
+
/* @__PURE__ */ jsx20(ArrowDown01, { className: cn("size-3.5 mr-2", sortBy.field === "size" && sortBy.order === -1 ? "" : "ml-5.5") }),
|
|
2491
|
+
"Size: Large"
|
|
2492
|
+
] }),
|
|
2493
|
+
/* @__PURE__ */ jsxs12(DropdownMenuItem, { onClick: () => setSortBy({ field: "size", order: 1 }), children: [
|
|
2494
|
+
sortBy.field === "size" && sortBy.order === 1 && /* @__PURE__ */ jsx20(Check4, { className: "size-3.5 mr-2" }),
|
|
2495
|
+
/* @__PURE__ */ jsx20(ArrowUp01, { className: cn("size-3.5 mr-2", sortBy.field === "size" && sortBy.order === 1 ? "" : "ml-5.5") }),
|
|
2496
|
+
"Size: Small"
|
|
2497
|
+
] })
|
|
2498
|
+
] })
|
|
2499
|
+
] }),
|
|
2500
|
+
/* @__PURE__ */ jsx20("div", { className: "w-px h-5 bg-border" }),
|
|
2501
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex bg-muted rounded-md p-0.5 gap-0.5", children: [
|
|
2502
|
+
/* @__PURE__ */ jsx20(
|
|
2503
|
+
Button,
|
|
2504
|
+
{
|
|
2505
|
+
type: "button",
|
|
2506
|
+
variant: "ghost",
|
|
2507
|
+
size: "icon",
|
|
2508
|
+
className: cn("h-8 w-8", viewMode === "GRID" && "bg-white shadow-sm hover:bg-white"),
|
|
2509
|
+
onClick: () => setViewMode("GRID"),
|
|
2510
|
+
"aria-label": "Grid View",
|
|
2511
|
+
"aria-pressed": viewMode === "GRID",
|
|
2512
|
+
children: /* @__PURE__ */ jsx20(LayoutGrid, { className: "size-4" })
|
|
2513
|
+
}
|
|
2514
|
+
),
|
|
2515
|
+
/* @__PURE__ */ jsx20(
|
|
2516
|
+
Button,
|
|
2517
|
+
{
|
|
2518
|
+
type: "button",
|
|
2519
|
+
variant: "ghost",
|
|
2520
|
+
size: "icon",
|
|
2521
|
+
className: cn("h-8 w-8", viewMode === "LIST" && "bg-white shadow-sm hover:bg-white"),
|
|
2522
|
+
onClick: () => setViewMode("LIST"),
|
|
2523
|
+
"aria-label": "List View",
|
|
2524
|
+
"aria-pressed": viewMode === "LIST",
|
|
2525
|
+
children: /* @__PURE__ */ jsx20(List, { className: "size-4" })
|
|
2526
|
+
}
|
|
2527
|
+
)
|
|
2528
|
+
] }),
|
|
2529
|
+
/* @__PURE__ */ jsx20(
|
|
2530
|
+
DialogConfirmation,
|
|
2531
|
+
{
|
|
2532
|
+
open: dialogs.delete,
|
|
2533
|
+
onClose: () => setDialogs((prev) => ({ ...prev, delete: false })),
|
|
2534
|
+
title: currentView === "TRASH" ? "Delete Permanently?" : "Move to Trash?",
|
|
2535
|
+
description: currentView === "TRASH" ? `This will permanently delete ${selectedFileIds.length} item(s). You cannot undo this action.` : `Are you sure you want to move ${selectedFileIds.length} item(s) to trash?`,
|
|
2536
|
+
onConfirm: async () => {
|
|
2537
|
+
if (selectedFileIds.length === 0) return [false, "No files selected"];
|
|
2538
|
+
try {
|
|
2539
|
+
if (currentView === "TRASH") {
|
|
2540
|
+
await Promise.all(selectedFileIds.map(
|
|
2541
|
+
(id) => fetch(`${apiEndpoint}?action=deletePermanent&id=${id}`, { method: "DELETE" })
|
|
2542
|
+
));
|
|
2543
|
+
await refreshItems();
|
|
2544
|
+
setSelectedFileIds([]);
|
|
2545
|
+
} else {
|
|
2546
|
+
const response = await fetch(`${apiEndpoint}?action=deleteMany`, {
|
|
2547
|
+
method: "DELETE",
|
|
2548
|
+
headers: { "Content-Type": "application/json" },
|
|
2549
|
+
body: JSON.stringify({ ids: selectedFileIds })
|
|
2550
|
+
});
|
|
2551
|
+
const result = await response.json();
|
|
2552
|
+
if (result.status === 200) {
|
|
2553
|
+
setItems((prevItems) => prevItems.filter((item) => !selectedFileIds.includes(item.id)));
|
|
2554
|
+
setSelectedFileIds([]);
|
|
2555
|
+
} else {
|
|
2556
|
+
return [false, result.message || "Failed to delete files"];
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
return [true];
|
|
2560
|
+
} catch (error) {
|
|
2561
|
+
return [false, error instanceof Error ? error.message : "Error deleting files"];
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
),
|
|
2566
|
+
/* @__PURE__ */ jsx20(
|
|
2567
|
+
DialogConfirmation,
|
|
2568
|
+
{
|
|
2569
|
+
open: dialogs.emptyTrash,
|
|
2570
|
+
onClose: () => setDialogs((prev) => ({ ...prev, emptyTrash: false })),
|
|
2571
|
+
title: "Empty Trash?",
|
|
2572
|
+
description: "All items in the trash will be permanently deleted. This action cannot be undone.",
|
|
2573
|
+
onConfirm: async () => {
|
|
2574
|
+
try {
|
|
2575
|
+
await callAPI("emptyTrash", { method: "POST" });
|
|
2576
|
+
await refreshItems();
|
|
2577
|
+
return [true];
|
|
2578
|
+
} catch (e) {
|
|
2579
|
+
return [false, e instanceof Error ? e.message : "Failed to empty trash"];
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
)
|
|
2584
|
+
] });
|
|
2585
|
+
};
|
|
2586
|
+
|
|
2587
|
+
// src/client/components/drive/file/preview.tsx
|
|
2588
|
+
import { useState as useState6 } from "react";
|
|
2589
|
+
import { Document, Page, pdfjs } from "react-pdf";
|
|
2590
|
+
import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2591
|
+
if (typeof window !== "undefined") {
|
|
2592
|
+
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`;
|
|
2593
|
+
}
|
|
2594
|
+
var DriveFilePreview = (props) => {
|
|
2595
|
+
const { item, onClose } = props;
|
|
2596
|
+
const { apiEndpoint } = useDrive();
|
|
2597
|
+
const [pdfNumPages, setPdfNumPages] = useState6(null);
|
|
2598
|
+
if (item.information.type === "FOLDER") return null;
|
|
2599
|
+
const mime = item.information.mime;
|
|
2600
|
+
const tokenParam = item.token ? `&token=${item.token}` : "";
|
|
2601
|
+
const fileUrl = `${apiEndpoint}?action=serve&id=${item.id}${tokenParam}`;
|
|
2602
|
+
const renderContent = () => {
|
|
2603
|
+
if (mime.startsWith("image/")) return /* @__PURE__ */ jsx21("img", { src: fileUrl, alt: item.name, className: "max-w-full max-h-[70vh] rounded-md object-contain shadow-sm" });
|
|
2604
|
+
if (mime.startsWith("video/")) return /* @__PURE__ */ jsx21("video", { src: fileUrl, controls: true, autoPlay: true, className: "max-w-full max-h-[70vh] rounded-md shadow-sm bg-black", children: "Your browser does not support video playback." });
|
|
2605
|
+
if (mime.startsWith("audio/")) return /* @__PURE__ */ jsxs13("div", { className: "flex flex-col items-center gap-6 py-8 px-4 w-full", children: [
|
|
2606
|
+
/* @__PURE__ */ jsx21("div", { className: "w-24 h-24 rounded-full bg-primary/10 flex items-center justify-center", children: /* @__PURE__ */ jsx21("span", { className: "text-4xl", children: "\u{1F3B5}" }) }),
|
|
2607
|
+
/* @__PURE__ */ jsxs13("div", { className: "text-center space-y-1", children: [
|
|
2608
|
+
/* @__PURE__ */ jsx21("h4", { className: "font-medium text-lg", children: item.name }),
|
|
2609
|
+
/* @__PURE__ */ jsx21("p", { className: "text-sm text-muted-foreground", children: mime })
|
|
2610
|
+
] }),
|
|
2611
|
+
/* @__PURE__ */ jsx21("audio", { src: fileUrl, controls: true, autoPlay: true, className: "w-full max-w-md mt-2", children: "Your browser does not support audio playback." })
|
|
2612
|
+
] });
|
|
2613
|
+
if (mime === "application/pdf") {
|
|
2614
|
+
return /* @__PURE__ */ jsxs13("div", { className: "max-h-[70vh] overflow-y-auto rounded-md border bg-white dark:bg-zinc-900 mx-auto w-full", children: [
|
|
2615
|
+
/* @__PURE__ */ jsx21(Document, { file: fileUrl, onLoadSuccess: ({ numPages }) => setPdfNumPages(numPages), className: "flex flex-col items-center gap-4 p-4", children: pdfNumPages && Array.from(new Array(Math.min(pdfNumPages, 10)), (_, index) => /* @__PURE__ */ jsx21(
|
|
2616
|
+
Page,
|
|
2617
|
+
{
|
|
2618
|
+
pageNumber: index + 1,
|
|
2619
|
+
renderTextLayer: false,
|
|
2620
|
+
renderAnnotationLayer: false,
|
|
2621
|
+
className: "shadow-md",
|
|
2622
|
+
width: 600
|
|
2623
|
+
},
|
|
2624
|
+
`page_${index + 1}`
|
|
2625
|
+
)) }),
|
|
2626
|
+
pdfNumPages && pdfNumPages > 10 && /* @__PURE__ */ jsxs13("div", { className: "p-4 text-center text-sm text-muted-foreground border-t bg-muted/20", children: [
|
|
2627
|
+
"Showing 10 of ",
|
|
2628
|
+
pdfNumPages,
|
|
2629
|
+
" pages (Preview limited)"
|
|
2630
|
+
] })
|
|
2631
|
+
] });
|
|
2632
|
+
}
|
|
2633
|
+
return /* @__PURE__ */ jsxs13("div", { className: "flex flex-col items-center justify-center gap-6 py-12", children: [
|
|
2634
|
+
/* @__PURE__ */ jsx21("div", { className: "p-4 rounded-full bg-muted", children: /* @__PURE__ */ jsx21("span", { className: "text-4xl opacity-50", children: "\u{1F4C4}" }) }),
|
|
2635
|
+
/* @__PURE__ */ jsxs13("div", { className: "text-center space-y-2", children: [
|
|
2636
|
+
/* @__PURE__ */ jsx21("h4", { className: "font-medium", children: "Preview not available" }),
|
|
2637
|
+
/* @__PURE__ */ jsx21("p", { className: "text-sm text-muted-foreground max-w-xs mx-auto", children: "This file type usually cannot be previewed in the browser." })
|
|
2638
|
+
] }),
|
|
2639
|
+
/* @__PURE__ */ jsx21(Button, { asChild: true, children: /* @__PURE__ */ jsx21("a", { href: fileUrl, download: item.name, children: "Download File" }) })
|
|
2640
|
+
] });
|
|
2641
|
+
};
|
|
2642
|
+
return /* @__PURE__ */ jsx21(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs13(DialogContent, { className: "sm:max-w-5xl max-h-[90vh] flex flex-col gap-0 p-0", children: [
|
|
2643
|
+
/* @__PURE__ */ jsx21(DialogHeader, { className: "px-4 py-3 border-b shrink-0 flex flex-row items-center justify-between", children: /* @__PURE__ */ jsx21(DialogTitle, { className: "truncate pr-8", children: item.name }) }),
|
|
2644
|
+
/* @__PURE__ */ jsx21("div", { className: "p-6 overflow-auto flex items-center justify-center bg-muted/5 min-h-75 flex-1", children: renderContent() })
|
|
2645
|
+
] }) });
|
|
2646
|
+
};
|
|
2647
|
+
|
|
2648
|
+
// src/client/file-chooser.tsx
|
|
2649
|
+
import { Upload as UploadIcon2, X as X4 } from "lucide-react";
|
|
2650
|
+
import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2651
|
+
var DriveFileChooser = (props) => {
|
|
2652
|
+
const {
|
|
2653
|
+
value,
|
|
2654
|
+
onChange,
|
|
2655
|
+
multiple,
|
|
2656
|
+
accept,
|
|
2657
|
+
placeholder,
|
|
2658
|
+
className,
|
|
2659
|
+
disabled,
|
|
2660
|
+
error,
|
|
2661
|
+
helperText
|
|
2662
|
+
} = {
|
|
2663
|
+
multiple: false,
|
|
2664
|
+
placeholder: "Choose files...",
|
|
2665
|
+
className: "",
|
|
2666
|
+
disabled: false,
|
|
2667
|
+
...props
|
|
2668
|
+
};
|
|
2669
|
+
const { items, selectedFileIds, setSelectedFileIds } = useDrive();
|
|
2670
|
+
const [isOpen, setIsOpen] = useState7(false);
|
|
2671
|
+
const [previewFile, setPreviewFile] = useState7(null);
|
|
2672
|
+
useEffect5(() => {
|
|
2673
|
+
if (isOpen) {
|
|
2674
|
+
if (!value) setSelectedFileIds([]);
|
|
2675
|
+
else if (Array.isArray(value)) setSelectedFileIds(value.map((f) => f.id));
|
|
2676
|
+
else setSelectedFileIds([value.id]);
|
|
2677
|
+
}
|
|
2678
|
+
}, [isOpen, value, setSelectedFileIds]);
|
|
2679
|
+
const handleConfirm = useCallback4(() => {
|
|
2680
|
+
const selectedItems = items.filter((item) => selectedFileIds.includes(item.id));
|
|
2681
|
+
const files = selectedItems.map((item) => ({
|
|
2682
|
+
id: item.id,
|
|
2683
|
+
file: { name: item.name, mime: item.information.type === "FILE" ? item.information.mime : "", size: item.information.type === "FILE" ? item.information.sizeInBytes : 0 }
|
|
2684
|
+
}));
|
|
2685
|
+
onChange(multiple ? files : files[0] || null);
|
|
2686
|
+
setIsOpen(false);
|
|
2687
|
+
}, [items, selectedFileIds, multiple, onChange]);
|
|
2688
|
+
const handleRemove = (idToRemove) => {
|
|
2689
|
+
if (multiple && Array.isArray(value)) {
|
|
2690
|
+
onChange(value.filter((f) => f.id !== idToRemove));
|
|
2691
|
+
} else {
|
|
2692
|
+
onChange(null);
|
|
2693
|
+
}
|
|
2694
|
+
};
|
|
2695
|
+
const hasSelection = value && (Array.isArray(value) ? value.length > 0 : true);
|
|
2696
|
+
const displayFiles = useMemo3(() => {
|
|
2697
|
+
if (!value) return [];
|
|
2698
|
+
return Array.isArray(value) ? value : [value];
|
|
2699
|
+
}, [value]);
|
|
2700
|
+
const isSingle = !multiple;
|
|
2701
|
+
return /* @__PURE__ */ jsxs14("div", { className: cn("space-y-1.5", className), children: [
|
|
2702
|
+
!hasSelection && /* @__PURE__ */ jsxs14(
|
|
2703
|
+
"button",
|
|
2704
|
+
{
|
|
2705
|
+
type: "button",
|
|
2706
|
+
onClick: () => !disabled && setIsOpen(true),
|
|
2707
|
+
disabled,
|
|
2708
|
+
className: cn(
|
|
2709
|
+
"w-full flex items-center gap-3 px-3 py-2.5 rounded-md border border-dashed text-left transition-colors",
|
|
2710
|
+
"hover:bg-accent hover:border-accent-foreground/20",
|
|
2711
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2712
|
+
error && "border-destructive bg-destructive/5 hover:bg-destructive/10",
|
|
2713
|
+
disabled && "opacity-50 cursor-not-allowed hover:bg-transparent"
|
|
2714
|
+
),
|
|
2715
|
+
children: [
|
|
2716
|
+
/* @__PURE__ */ jsx22("div", { className: cn(
|
|
2717
|
+
"flex items-center justify-center size-9 rounded-md bg-muted",
|
|
2718
|
+
error && "bg-destructive/10"
|
|
2719
|
+
), children: /* @__PURE__ */ jsx22(UploadIcon2, { className: cn("size-4", error ? "text-destructive" : "text-muted-foreground") }) }),
|
|
2720
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex-1 min-w-0", children: [
|
|
2721
|
+
/* @__PURE__ */ jsx22("p", { className: cn("text-sm font-medium", error && "text-destructive"), children: isSingle ? "Select a file" : "Select files" }),
|
|
2722
|
+
/* @__PURE__ */ jsx22("p", { className: "text-xs text-muted-foreground", children: placeholder })
|
|
2723
|
+
] })
|
|
2724
|
+
]
|
|
2725
|
+
}
|
|
2726
|
+
),
|
|
2727
|
+
hasSelection && isSingle && displayFiles[0] && /* @__PURE__ */ jsxs14("div", { className: cn(
|
|
2728
|
+
"flex items-center gap-2.5 px-2.5 py-2 rounded-md border bg-muted/30",
|
|
2729
|
+
error && "border-destructive",
|
|
2730
|
+
disabled && "opacity-50"
|
|
2731
|
+
), children: [
|
|
2732
|
+
/* @__PURE__ */ jsx22("div", { className: "size-10 shrink-0 rounded overflow-hidden bg-muted flex items-center justify-center", children: getFileIcon(displayFiles[0].file.mime, false, "size-5 text-muted-foreground") }),
|
|
2733
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex-1 min-w-0", children: [
|
|
2734
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm font-medium truncate", children: displayFiles[0].file.name }),
|
|
2735
|
+
/* @__PURE__ */ jsx22("p", { className: "text-xs text-muted-foreground truncate", children: displayFiles[0].file.mime })
|
|
2736
|
+
] }),
|
|
2737
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1 shrink-0", children: [
|
|
2738
|
+
/* @__PURE__ */ jsx22(
|
|
2739
|
+
Button,
|
|
2740
|
+
{
|
|
2741
|
+
type: "button",
|
|
2742
|
+
variant: "ghost",
|
|
2743
|
+
size: "sm",
|
|
2744
|
+
onClick: () => setIsOpen(true),
|
|
2745
|
+
disabled,
|
|
2746
|
+
children: "Change"
|
|
2747
|
+
}
|
|
2748
|
+
),
|
|
2749
|
+
!disabled && /* @__PURE__ */ jsx22(
|
|
2750
|
+
Button,
|
|
2751
|
+
{
|
|
2752
|
+
type: "button",
|
|
2753
|
+
variant: "ghost",
|
|
2754
|
+
size: "icon",
|
|
2755
|
+
onClick: () => handleRemove(displayFiles[0].id),
|
|
2756
|
+
children: /* @__PURE__ */ jsx22(X4, { className: "size-3.5" })
|
|
2757
|
+
}
|
|
2758
|
+
)
|
|
2759
|
+
] })
|
|
2760
|
+
] }),
|
|
2761
|
+
hasSelection && !isSingle && /* @__PURE__ */ jsxs14("div", { className: cn(
|
|
2762
|
+
"rounded-md border",
|
|
2763
|
+
error && "border-destructive",
|
|
2764
|
+
disabled && "opacity-50"
|
|
2765
|
+
), children: [
|
|
2766
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between px-2.5 py-1.5 border-b bg-muted/30", children: [
|
|
2767
|
+
/* @__PURE__ */ jsxs14("span", { className: "text-xs text-muted-foreground", children: [
|
|
2768
|
+
displayFiles.length,
|
|
2769
|
+
" files selected"
|
|
2770
|
+
] }),
|
|
2771
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1", children: [
|
|
2772
|
+
/* @__PURE__ */ jsx22(
|
|
2773
|
+
Button,
|
|
2774
|
+
{
|
|
2775
|
+
type: "button",
|
|
2776
|
+
variant: "ghost",
|
|
2777
|
+
size: "sm",
|
|
2778
|
+
onClick: () => setIsOpen(true),
|
|
2779
|
+
disabled,
|
|
2780
|
+
children: "Add more"
|
|
2781
|
+
}
|
|
2782
|
+
),
|
|
2783
|
+
!disabled && /* @__PURE__ */ jsx22(
|
|
2784
|
+
Button,
|
|
2785
|
+
{
|
|
2786
|
+
type: "button",
|
|
2787
|
+
variant: "ghost",
|
|
2788
|
+
size: "sm",
|
|
2789
|
+
onClick: () => onChange([]),
|
|
2790
|
+
children: "Clear all"
|
|
2791
|
+
}
|
|
2792
|
+
)
|
|
2793
|
+
] })
|
|
2794
|
+
] }),
|
|
2795
|
+
/* @__PURE__ */ jsx22("div", { className: "divide-y max-h-40 overflow-y-auto", children: displayFiles.map((file) => /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2 px-2.5 py-1.5 hover:bg-muted/30", children: [
|
|
2796
|
+
/* @__PURE__ */ jsx22("div", { className: "size-7 shrink-0 rounded overflow-hidden bg-muted flex items-center justify-center", children: getFileIcon(file.file.mime, false, "size-3.5 text-muted-foreground") }),
|
|
2797
|
+
/* @__PURE__ */ jsx22("span", { className: "flex-1 text-sm truncate", children: file.file.name }),
|
|
2798
|
+
!disabled && /* @__PURE__ */ jsx22(
|
|
2799
|
+
Button,
|
|
2800
|
+
{
|
|
2801
|
+
type: "button",
|
|
2802
|
+
variant: "ghost",
|
|
2803
|
+
size: "icon",
|
|
2804
|
+
onClick: () => handleRemove(file.id),
|
|
2805
|
+
children: /* @__PURE__ */ jsx22(X4, { className: "size-3" })
|
|
2806
|
+
}
|
|
2807
|
+
)
|
|
2808
|
+
] }, file.id)) })
|
|
2809
|
+
] }),
|
|
2810
|
+
error && helperText && /* @__PURE__ */ jsx22("p", { className: "text-xs text-destructive", children: helperText }),
|
|
2811
|
+
/* @__PURE__ */ jsx22(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsxs14(DialogContent, { className: "sm:max-w-5xl max-h-[85vh] w-[95vw] flex flex-col gap-0 p-0", children: [
|
|
2812
|
+
/* @__PURE__ */ jsx22(DialogHeader, { className: "px-0 py-0 border-b shrink-0 flex flex-col gap-0", children: /* @__PURE__ */ jsx22("div", { className: "flex items-center justify-between px-3 sm:px-4 py-3 border-b", children: /* @__PURE__ */ jsx22(DialogTitle, { className: "text-sm sm:text-base", children: "File Explorer" }) }) }),
|
|
2813
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex-1 overflow-hidden flex min-h-0 bg-muted/5", children: [
|
|
2814
|
+
/* @__PURE__ */ jsx22("div", { className: "hidden md:flex w-48 lg:w-52 border-r bg-background/50 flex-col", children: /* @__PURE__ */ jsx22(DriveSidebar, {}) }),
|
|
2815
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex-1 flex flex-col min-w-0", children: [
|
|
2816
|
+
/* @__PURE__ */ jsx22(DriveHeader, {}),
|
|
2817
|
+
/* @__PURE__ */ jsx22(
|
|
2818
|
+
DriveExplorer,
|
|
2819
|
+
{
|
|
2820
|
+
mimeFilter: accept,
|
|
2821
|
+
onItemDoubleClick: (item) => {
|
|
2822
|
+
if (item.information.type === "FILE") {
|
|
2823
|
+
setPreviewFile(item);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
)
|
|
2828
|
+
] })
|
|
2829
|
+
] }),
|
|
2830
|
+
/* @__PURE__ */ jsxs14(DialogFooter, { className: "px-3 sm:px-4 py-3 border-t bg-background shrink-0 gap-2 flex-row justify-end", children: [
|
|
2831
|
+
/* @__PURE__ */ jsx22(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsOpen(false), children: "Cancel" }),
|
|
2832
|
+
/* @__PURE__ */ jsxs14(
|
|
2833
|
+
Button,
|
|
2834
|
+
{
|
|
2835
|
+
type: "button",
|
|
2836
|
+
size: "sm",
|
|
2837
|
+
onClick: handleConfirm,
|
|
2838
|
+
disabled: selectedFileIds.length === 0,
|
|
2839
|
+
children: [
|
|
2840
|
+
"Select ",
|
|
2841
|
+
selectedFileIds.length > 0 ? `(${selectedFileIds.length})` : ""
|
|
2842
|
+
]
|
|
2843
|
+
}
|
|
2844
|
+
)
|
|
2845
|
+
] })
|
|
2846
|
+
] }) }),
|
|
2847
|
+
previewFile && /* @__PURE__ */ jsx22(DriveFilePreview, { item: previewFile, onClose: () => setPreviewFile(null) })
|
|
2848
|
+
] });
|
|
2849
|
+
};
|
|
2850
|
+
export {
|
|
2851
|
+
DriveExplorer,
|
|
2852
|
+
DriveFileChooser,
|
|
2853
|
+
DriveFilePreview,
|
|
2854
|
+
DriveHeader,
|
|
2855
|
+
DrivePathBar,
|
|
2856
|
+
DriveProvider,
|
|
2857
|
+
DriveSidebar,
|
|
2858
|
+
DriveStorageIndicator,
|
|
2859
|
+
DriveUpload,
|
|
2860
|
+
useDrive,
|
|
2861
|
+
useUpload
|
|
2862
|
+
};
|
|
2863
|
+
//# sourceMappingURL=index.js.map
|