mirror of
https://github.com/Noooste/garage-ui.git
synced 2026-08-21 10:06:37 +00:00
refactor: optimize state management and filtering in AccessControl and ObjectsTable components
Signed-off-by: Noooste <83548733+Noooste@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { objectsApi } from '@/lib/api';
|
||||
import type { S3Object, UploadTask } from '@/types';
|
||||
import { toast } from 'sonner';
|
||||
@@ -13,8 +13,9 @@ export function useBucketObjects(bucketName: string | null, currentPath: string
|
||||
const [nextContinuationToken, setNextContinuationToken] = useState<string | undefined>(undefined);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(25);
|
||||
const [currentContinuationToken, setCurrentContinuationToken] = useState<string | undefined>(undefined);
|
||||
const [previousPath, setPreviousPath] = useState<string>(currentPath);
|
||||
const previousPathRef = useRef<string>(currentPath);
|
||||
const [uploadTasks, setUploadTasks] = useState<UploadTask[]>([]);
|
||||
const clearTasksTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const fetchObjects = useCallback(async (continuationToken?: string, isRefresh = false, isNav = false) => {
|
||||
if (!bucketName) return;
|
||||
@@ -46,24 +47,26 @@ export function useBucketObjects(bucketName: string | null, currentPath: string
|
||||
useEffect(() => {
|
||||
if (!bucketName) return;
|
||||
|
||||
// Detect if this is a path change (navigation) or initial load
|
||||
const isPathChange = previousPath !== currentPath && objects.length > 0;
|
||||
setPreviousPath(currentPath);
|
||||
const isPathChange = previousPathRef.current !== currentPath && objects.length > 0;
|
||||
previousPathRef.current = currentPath;
|
||||
|
||||
// Use navigation mode if it's a path change, otherwise use normal loading
|
||||
fetchObjects(undefined, false, isPathChange);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [bucketName, currentPath, itemsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (clearTasksTimerRef.current) clearTimeout(clearTasksTimerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const uploadFiles = useCallback(async (files: File[]) => {
|
||||
if (!bucketName) return false;
|
||||
|
||||
// Check if files are from a folder upload
|
||||
const hasRelativePaths = files.some((file: any) => file.webkitRelativePath);
|
||||
const hasRelativePaths = files.some((file) => !!file.webkitRelativePath);
|
||||
|
||||
// Get unique folders from the files
|
||||
const folders = new Set<string>();
|
||||
files.forEach((file: any) => {
|
||||
files.forEach((file) => {
|
||||
if (file.webkitRelativePath) {
|
||||
const parts = file.webkitRelativePath.split('/');
|
||||
if (parts.length > 1) {
|
||||
@@ -72,9 +75,8 @@ export function useBucketObjects(bucketName: string | null, currentPath: string
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize upload tasks
|
||||
const tasks: UploadTask[] = files.map((file, index) => {
|
||||
const relativePath = (file as any).webkitRelativePath || file.name;
|
||||
const relativePath = file.webkitRelativePath || file.name;
|
||||
const key = currentPath ? `${currentPath}${relativePath}` : relativePath;
|
||||
return {
|
||||
id: `${Date.now()}-${index}`,
|
||||
@@ -88,53 +90,36 @@ export function useBucketObjects(bucketName: string | null, currentPath: string
|
||||
|
||||
setUploadTasks(tasks);
|
||||
|
||||
// Upload files with progress tracking and error handling
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
// Upload files one by one
|
||||
const concurrency = 1;
|
||||
const uploadPromises: Promise<void>[] = [];
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
setUploadTasks(prev => prev.map(t =>
|
||||
t.id === task.id ? { ...t, status: 'uploading' as const } : t
|
||||
));
|
||||
|
||||
for (let i = 0; i < tasks.length; i += concurrency) {
|
||||
const batch = tasks.slice(i, Math.min(i + concurrency, tasks.length));
|
||||
await objectsApi.upload(bucketName, task.key, task.file, (progress) => {
|
||||
setUploadTasks(prev => prev.map(t => {
|
||||
if (t.id !== task.id || t.progress === progress) return t;
|
||||
return { ...t, progress };
|
||||
}));
|
||||
});
|
||||
|
||||
const batchPromises = batch.map(async (task) => {
|
||||
try {
|
||||
// Update task status to uploading
|
||||
setUploadTasks(prev => prev.map(t =>
|
||||
t.id === task.id ? { ...t, status: 'uploading' as const } : t
|
||||
));
|
||||
|
||||
await objectsApi.upload(bucketName, task.key, task.file, (progress) => {
|
||||
setUploadTasks(prev => prev.map(t =>
|
||||
t.id === task.id ? { ...t, progress } : t
|
||||
));
|
||||
});
|
||||
|
||||
// Update task status to completed
|
||||
setUploadTasks(prev => prev.map(t =>
|
||||
t.id === task.id ? { ...t, status: 'completed' as const, progress: 100 } : t
|
||||
));
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
// Update task status to error but continue with other uploads
|
||||
const errorMessage = error instanceof Error ? error.message : 'Upload failed';
|
||||
setUploadTasks(prev => prev.map(t =>
|
||||
t.id === task.id ? { ...t, status: 'error' as const, error: errorMessage } : t
|
||||
));
|
||||
errorCount++;
|
||||
console.error(`Failed to upload ${task.key}:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
uploadPromises.push(...batchPromises);
|
||||
await Promise.all(batchPromises);
|
||||
setUploadTasks(prev => prev.map(t =>
|
||||
t.id === task.id ? { ...t, status: 'completed' as const, progress: 100 } : t
|
||||
));
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Upload failed';
|
||||
setUploadTasks(prev => prev.map(t =>
|
||||
t.id === task.id ? { ...t, status: 'error' as const, error: errorMessage } : t
|
||||
));
|
||||
errorCount++;
|
||||
console.error(`Failed to upload ${task.key}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(uploadPromises);
|
||||
|
||||
// Show summary toast
|
||||
if (errorCount === 0) {
|
||||
if (hasRelativePaths && folders.size > 0) {
|
||||
const folderNames = Array.from(folders).join(', ');
|
||||
@@ -148,9 +133,10 @@ export function useBucketObjects(bucketName: string | null, currentPath: string
|
||||
toast.error(`Failed to upload ${errorCount} file${errorCount > 1 ? 's' : ''}`);
|
||||
}
|
||||
|
||||
// Clear upload tasks after a delay
|
||||
setTimeout(() => {
|
||||
if (clearTasksTimerRef.current) clearTimeout(clearTasksTimerRef.current);
|
||||
clearTasksTimerRef.current = setTimeout(() => {
|
||||
setUploadTasks([]);
|
||||
clearTasksTimerRef.current = null;
|
||||
}, 3000);
|
||||
|
||||
await fetchObjects(currentContinuationToken, true);
|
||||
@@ -161,7 +147,6 @@ export function useBucketObjects(bucketName: string | null, currentPath: string
|
||||
if (!bucketName) return false;
|
||||
|
||||
try {
|
||||
// Optimistically remove the object from the UI
|
||||
setObjects(prev => prev.filter(obj => obj.key !== key));
|
||||
|
||||
await objectsApi.delete(bucketName, key);
|
||||
@@ -170,7 +155,6 @@ export function useBucketObjects(bucketName: string | null, currentPath: string
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Delete object error:', error);
|
||||
// Revert the optimistic update by refetching
|
||||
await fetchObjects(currentContinuationToken, true);
|
||||
return false;
|
||||
}
|
||||
@@ -180,7 +164,6 @@ export function useBucketObjects(bucketName: string | null, currentPath: string
|
||||
if (!bucketName || keys.length === 0) return false;
|
||||
|
||||
try {
|
||||
// Optimistically remove the objects from the UI
|
||||
setObjects(prev => prev.filter(obj => !keys.includes(obj.key)));
|
||||
|
||||
await objectsApi.deleteMultiple(bucketName, keys, currentPath || undefined);
|
||||
@@ -189,7 +172,6 @@ export function useBucketObjects(bucketName: string | null, currentPath: string
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Bulk delete error:', error);
|
||||
// Revert the optimistic update by refetching
|
||||
await fetchObjects(currentContinuationToken, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user