import classNames from 'classnames' import { FC, useMemo } from 'react' import { useImagePreview, useOperationState } from './hooks' import { getProgressState, QueueItemContent } from './QueueItemContent' import { FilenameRenderer, ImagePreviewModal, ThumbnailRenderer } from './Subcomponents' import { TruncateContext } from './Truncate' import { TFileAndTransfer, FileItem, TFileItemList, TFileTransfer, TItemRenderer, TQueueItemOperation } from './types' const isImageFile = (item: TFileAndTransfer): boolean => { if (item.file.type?.startsWith('image/')) return true return /\.(jpe?g|png|gif|webp|heic|heif|bmp|svg)$/i.test(item.file.name || '') } // ============================================ // Helper: Transform files with transfer status // ============================================ /** * Combine `files` with their transfer state (`transfers`) and sort by priority. * * Sort order: * - in-progress first * - then errors * - then everything else */ const useFilesAndTransfers = ( files: TFileItemList, transfers: TFileTransfer[], uploadStrategy: 'form' | 'custom', ): TFileAndTransfer[] => { return useMemo(() => { const mapped = files.map((fileItem: FileItem) => { const transfer = transfers.find((t) => t.fileId === fileItem.fileId) return { ...fileItem, progress: transfer?.progress ?? (uploadStrategy === 'form' ? 'done' : 'queued'), errorMessage: transfer?.errorMessage, showProgress: transfer?.showProgress, lastProgress: transfer?.lastProgress, } }) // Sort order: in-progress first, then errors, then the rest const priority = { 'in-progress': 0, error: 1, idle: 2 } as const return mapped.sort((a, b) => priority[getProgressState(a.progress)] - priority[getProgressState(b.progress)]) }, [files, transfers, uploadStrategy]) } // ============================================ // Helper: Filter previewable images // ============================================ /** Returns only uploaded images (used for the preview modal). */ const usePreviewableImages = (filesAndTransfers: TFileAndTransfer[], enabled: boolean): TFileAndTransfer[] => { return useMemo(() => { if (!enabled) return [] return filesAndTransfers.filter((item) => item.progress === 'done' && isImageFile(item)) }, [filesAndTransfers, enabled]) } // ============================================ // QueueDisplay Props // ============================================ /** * Queue list renderer for `PktFileUpload`. * * This component is UI-only; state changes are handled via callbacks / operations passed in. */ interface IQueueDisplay { /** Called when the user cancels/removes a file (also used to cancel transfers). */ cancelTransfer: (fileItemId: string) => void /** Transfer states used for `uploadStrategy="custom"`. */ transfers?: TFileTransfer[] /** Current file items. */ files: TFileItemList /** Operations (rename/comment/remove/etc) rendered per queue item. */ queueItemOperations?: TQueueItemOperation[] /** Custom renderer for each queue item (defaults to filename renderer). */ ItemRenderer?: TItemRenderer /** Number of trailing characters to keep when truncating long filenames. */ truncateTail?: number /** Enable image preview modal (only affects thumbnail view). */ enableImagePreview?: boolean /** Upload mode decides default queue state when no transfer exists. */ uploadStrategy?: 'form' | 'custom' } // ============================================ // QueueDisplay Component // ============================================ export const QueueDisplay: FC = ({ files, cancelTransfer, transfers = [], queueItemOperations = [], ItemRenderer = FilenameRenderer, truncateTail, enableImagePreview = false, uploadStrategy = 'form', }) => { // Transform data const filesAndTransfers = useFilesAndTransfers(files, transfers, uploadStrategy) const previewableImages = usePreviewableImages(filesAndTransfers, enableImagePreview) // State management const operationState = useOperationState(queueItemOperations) const preview = useImagePreview(previewableImages) return ( <> {/* Image preview modal */} {enableImagePreview && previewableImages.length > 0 && ( )} ) } // ============================================ // ItemRenderers Export // ============================================ export const ItemRenderers: Record = { filename: FilenameRenderer, thumbnail: ThumbnailRenderer, }