Add drag-and-drop from carousel to file inputs for batch processing
This commit is contained in:
parent
31e2ad7688
commit
be6bf28b7e
5 changed files with 130 additions and 1 deletions
|
|
@ -480,7 +480,20 @@ export default function MyFilesPage() {
|
|||
return (
|
||||
<div
|
||||
key={asset.id}
|
||||
className="bg-forge-dark rounded-lg border border-gray-800 overflow-hidden hover:border-gray-700 transition-colors group"
|
||||
draggable="true"
|
||||
onDragStart={(e) => {
|
||||
// Set asset ID for drag-and-drop to file inputs
|
||||
e.dataTransfer.setData('application/x-asset-id', asset.id);
|
||||
e.dataTransfer.effectAllowed = 'copy';
|
||||
|
||||
// Visual feedback
|
||||
e.currentTarget.style.opacity = '0.5';
|
||||
}}
|
||||
onDragEnd={(e) => {
|
||||
// Reset visual feedback
|
||||
e.currentTarget.style.opacity = '1';
|
||||
}}
|
||||
className="bg-forge-dark rounded-lg border border-gray-800 overflow-hidden hover:border-gray-700 transition-colors group cursor-move"
|
||||
>
|
||||
{/* Thumbnail */}
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -122,3 +122,13 @@ a {
|
|||
.badge-failed {
|
||||
@apply bg-red-900/50 text-red-400;
|
||||
}
|
||||
|
||||
/* Drag-and-drop from carousel */
|
||||
[data-file-drop-zone].drag-over {
|
||||
@apply border-forge-yellow bg-forge-yellow/10 scale-105;
|
||||
box-shadow: 0 0 30px rgba(255, 196, 7, 0.3);
|
||||
}
|
||||
|
||||
[data-file-drop-zone] {
|
||||
@apply transition-all duration-200;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { Maximize, Download, Sparkles, Trash2, RefreshCw } from 'lucide-react';
|
|||
import FileUpload from '@/components/FileUpload';
|
||||
import { modulesApi, assetsApi, jobsApi } from '@/lib/api';
|
||||
import { useStore } from '@/lib/store';
|
||||
import { useDragFromCarousel } from '@/hooks/useDragFromCarousel';
|
||||
|
||||
const scaleOptions = [
|
||||
{ value: 2, label: '2x' },
|
||||
|
|
@ -105,6 +106,38 @@ export default function ImageUpscalePage() {
|
|||
toast.success(`${files.length} images added to queue`);
|
||||
};
|
||||
|
||||
// Handle drag-and-drop from carousel
|
||||
useDragFromCarousel({
|
||||
onAssetDrop: async (assetIds) => {
|
||||
try {
|
||||
const responses = await Promise.all(assetIds.map(id => assetsApi.get(id)));
|
||||
const newItems = responses
|
||||
.map((r: any) => r.data)
|
||||
.filter((asset: any) => {
|
||||
// Only add image assets
|
||||
return asset.file_type === 'image';
|
||||
})
|
||||
.map((asset: any) => ({
|
||||
id: Math.random().toString(36).substring(7),
|
||||
assetId: asset.id,
|
||||
status: 'pending' as const,
|
||||
filename: asset.original_filename || asset.filename
|
||||
}));
|
||||
|
||||
if (newItems.length > 0) {
|
||||
setQueue(prev => [...prev, ...newItems]);
|
||||
toast.success(`${newItems.length} images added from carousel`);
|
||||
} else {
|
||||
toast.error('No valid images in selection');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load dragged assets', err);
|
||||
toast.error('Failed to add assets');
|
||||
}
|
||||
},
|
||||
enabled: mounted
|
||||
});
|
||||
|
||||
const processItem = async (item: QueueItem) => {
|
||||
if (item.status === 'completed' || item.status === 'processing') return item;
|
||||
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ export default function FileUpload({
|
|||
>
|
||||
<div
|
||||
{...getRootProps()}
|
||||
data-file-drop-zone="true"
|
||||
className={clsx(
|
||||
'upload-zone',
|
||||
isDragActive && 'active'
|
||||
|
|
|
|||
72
frontend/hooks/useDragFromCarousel.ts
Normal file
72
frontend/hooks/useDragFromCarousel.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Custom hook for handling drag-and-drop from asset carousel to file inputs
|
||||
* Enables dragging assets from "My Files" carousel into batch processing tools
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface UseDragFromCarouselOptions {
|
||||
onAssetDrop: (assetIds: string[]) => void;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export function useDragFromCarousel({ onAssetDrop, enabled = true }: UseDragFromCarouselOptions) {
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
|
||||
const handleDragOver = (e: DragEvent) => {
|
||||
// Check if dragging from carousel (has asset-id data)
|
||||
const assetId = e.dataTransfer?.getData('application/x-asset-id');
|
||||
if (assetId) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Add visual feedback
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
target.classList.add('drag-over');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragLeave = (e: DragEvent) => {
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
target.classList.remove('drag-over');
|
||||
};
|
||||
|
||||
const handleDrop = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
target.classList.remove('drag-over');
|
||||
|
||||
// Get asset IDs from drag data
|
||||
const assetId = e.dataTransfer?.getData('application/x-asset-id');
|
||||
const assetIds = e.dataTransfer?.getData('application/x-asset-ids');
|
||||
|
||||
if (assetIds) {
|
||||
// Multiple assets
|
||||
onAssetDrop(JSON.parse(assetIds));
|
||||
} else if (assetId) {
|
||||
// Single asset
|
||||
onAssetDrop([assetId]);
|
||||
}
|
||||
};
|
||||
|
||||
// Find all file upload drop zones
|
||||
const dropZones = document.querySelectorAll('[data-file-drop-zone]');
|
||||
|
||||
dropZones.forEach(zone => {
|
||||
zone.addEventListener('dragover', handleDragOver as EventListener);
|
||||
zone.addEventListener('dragleave', handleDragLeave as EventListener);
|
||||
zone.addEventListener('drop', handleDrop as EventListener);
|
||||
});
|
||||
|
||||
return () => {
|
||||
dropZones.forEach(zone => {
|
||||
zone.removeEventListener('dragover', handleDragOver as EventListener);
|
||||
zone.removeEventListener('dragleave', handleDragLeave as EventListener);
|
||||
zone.removeEventListener('drop', handleDrop as EventListener);
|
||||
});
|
||||
};
|
||||
}, [onAssetDrop, enabled]);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue