diff --git a/frontend/app/files/page.tsx b/frontend/app/files/page.tsx index 6fa8b83..f7cefd0 100644 --- a/frontend/app/files/page.tsx +++ b/frontend/app/files/page.tsx @@ -480,7 +480,20 @@ export default function MyFilesPage() { return (
{ + // 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 */}
{ + 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; diff --git a/frontend/components/FileUpload.tsx b/frontend/components/FileUpload.tsx index 6c489e4..1b3c391 100644 --- a/frontend/components/FileUpload.tsx +++ b/frontend/components/FileUpload.tsx @@ -167,6 +167,7 @@ export default function FileUpload({ >
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]); +}