@chunkflowjs/upload-component-react 0.0.1-alpha.1 → 0.0.1-alpha.3
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/dist/index.mjs +23 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { formatFileSize } from "@chunkflowjs/shared";
|
|
4
|
-
import { UploadStatus } from "@chunkflowjs/protocol";
|
|
5
4
|
import { useUploadList } from "@chunkflowjs/upload-client-react";
|
|
6
5
|
|
|
7
6
|
//#region src/UploadButton.tsx
|
|
@@ -220,6 +219,29 @@ function UploadProgress({ task, showSpeed = true, showRemainingTime = true, clas
|
|
|
220
219
|
});
|
|
221
220
|
}
|
|
222
221
|
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region ../protocol/dist/index.mjs
|
|
224
|
+
/**
|
|
225
|
+
* Upload status enumeration
|
|
226
|
+
*/
|
|
227
|
+
let UploadStatus = /* @__PURE__ */ function(UploadStatus) {
|
|
228
|
+
/** Initial state, not started */
|
|
229
|
+
UploadStatus["IDLE"] = "idle";
|
|
230
|
+
/** Calculating file hash */
|
|
231
|
+
UploadStatus["HASHING"] = "hashing";
|
|
232
|
+
/** Uploading chunks */
|
|
233
|
+
UploadStatus["UPLOADING"] = "uploading";
|
|
234
|
+
/** Upload paused by user */
|
|
235
|
+
UploadStatus["PAUSED"] = "paused";
|
|
236
|
+
/** Upload completed successfully */
|
|
237
|
+
UploadStatus["SUCCESS"] = "success";
|
|
238
|
+
/** Upload failed with error */
|
|
239
|
+
UploadStatus["ERROR"] = "error";
|
|
240
|
+
/** Upload cancelled by user */
|
|
241
|
+
UploadStatus["CANCELLED"] = "cancelled";
|
|
242
|
+
return UploadStatus;
|
|
243
|
+
}({});
|
|
244
|
+
|
|
223
245
|
//#endregion
|
|
224
246
|
//#region src/UploadList.tsx
|
|
225
247
|
/**
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["matchAccept","FileValidationError"],"sources":["../src/UploadButton.tsx","../src/UploadProgress.tsx","../src/UploadList.tsx","../src/UploadDropzone.tsx","../src/index.ts"],"sourcesContent":["import { useRef, ChangeEvent, ReactNode } from \"react\";\n\n/**\n * Props for the UploadButton component\n */\nexport interface UploadButtonProps {\n /**\n * Accepted file types (e.g., \"image/*\", \".pdf,.doc\")\n */\n accept?: string;\n\n /**\n * Allow multiple file selection\n * @default false\n */\n multiple?: boolean;\n\n /**\n * Maximum file size in bytes\n */\n maxSize?: number;\n\n /**\n * Callback when files are selected\n */\n onSelect?: (files: File[]) => void;\n\n /**\n * Callback when file validation fails\n */\n onError?: (error: FileValidationError) => void;\n\n /**\n * Custom button content\n */\n children?: ReactNode;\n\n /**\n * CSS class name for the button\n */\n className?: string;\n\n /**\n * Disable the button\n * @default false\n */\n disabled?: boolean;\n}\n\n/**\n * File validation error\n */\nexport class FileValidationError extends Error {\n constructor(\n message: string,\n public code: \"FILE_TOO_LARGE\" | \"INVALID_FILE_TYPE\",\n public file: File,\n ) {\n super(message);\n this.name = \"FileValidationError\";\n }\n}\n\n/**\n * UploadButton component\n *\n * A button component that triggers file selection and validates files\n * before passing them to the onSelect callback.\n *\n * @example\n * ```tsx\n * <UploadButton\n * accept=\"image/*\"\n * maxSize={10 * 1024 * 1024} // 10MB\n * multiple\n * onSelect={(files) => console.log('Selected:', files)}\n * >\n * Select Files\n * </UploadButton>\n * ```\n */\nexport function UploadButton({\n accept,\n multiple = false,\n maxSize,\n onSelect,\n onError,\n children,\n className,\n disabled = false,\n}: UploadButtonProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleClick = () => {\n if (!disabled) {\n inputRef.current?.click();\n }\n };\n\n const validateFile = (file: File): FileValidationError | null => {\n // Validate file size\n if (maxSize && file.size > maxSize) {\n return new FileValidationError(\n `File \"${file.name}\" size ${file.size} bytes exceeds maximum ${maxSize} bytes`,\n \"FILE_TOO_LARGE\",\n file,\n );\n }\n\n // Validate file type\n if (accept && !matchAccept(file, accept)) {\n return new FileValidationError(\n `File \"${file.name}\" type \"${file.type}\" is not accepted`,\n \"INVALID_FILE_TYPE\",\n file,\n );\n }\n\n return null;\n };\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files || []);\n\n if (files.length === 0) {\n return;\n }\n\n // Validate all files\n const validFiles: File[] = [];\n const errors: FileValidationError[] = [];\n\n for (const file of files) {\n const error = validateFile(file);\n if (error) {\n errors.push(error);\n } else {\n validFiles.push(file);\n }\n }\n\n // Report errors\n if (errors.length > 0 && onError) {\n errors.forEach((error) => onError(error));\n }\n\n // Call onSelect with valid files\n if (validFiles.length > 0 && onSelect) {\n onSelect(validFiles);\n }\n\n // Reset input value to allow selecting the same file again\n if (inputRef.current) {\n inputRef.current.value = \"\";\n }\n };\n\n return (\n <>\n <button onClick={handleClick} className={className} disabled={disabled} type=\"button\">\n {children || \"Select Files\"}\n </button>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n onChange={handleChange}\n style={{ display: \"none\" }}\n aria-hidden=\"true\"\n />\n </>\n );\n}\n\n/**\n * Check if a file matches the accept pattern\n *\n * @param file - The file to check\n * @param accept - The accept pattern (e.g., \"image/star\", \".pdf,.doc\", \"star/star\")\n * @returns true if the file matches the pattern\n */\nfunction matchAccept(file: File, accept: string): boolean {\n // Accept all files if pattern is \"*/*\" or \"*\"\n if (accept === \"*/*\" || accept === \"*\") {\n return true;\n }\n\n const patterns = accept.split(\",\").map((p) => p.trim());\n\n for (const pattern of patterns) {\n // Accept all files\n if (pattern === \"*/*\" || pattern === \"*\") {\n return true;\n }\n\n // Exact MIME type match (e.g., \"image/jpeg\")\n if (pattern === file.type) {\n return true;\n }\n\n // Wildcard MIME type match (e.g., \"image/*\")\n if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n if (file.type.startsWith(prefix + \"/\")) {\n return true;\n }\n }\n\n // File extension match (e.g., \".pdf\")\n if (pattern.startsWith(\".\")) {\n if (file.name.toLowerCase().endsWith(pattern.toLowerCase())) {\n return true;\n }\n }\n }\n\n return false;\n}\n","import { useEffect, useState, CSSProperties } from \"react\";\nimport type { UploadTask } from \"@chunkflowjs/core\";\nimport { formatFileSize } from \"@chunkflowjs/shared\";\n\n/**\n * Props for the UploadProgress component\n */\nexport interface UploadProgressProps {\n /**\n * The upload task to display progress for\n */\n task: UploadTask;\n\n /**\n * Whether to show upload speed\n * @default true\n */\n showSpeed?: boolean;\n\n /**\n * Whether to show remaining time\n * @default true\n */\n showRemainingTime?: boolean;\n\n /**\n * CSS class name for the container\n */\n className?: string;\n\n /**\n * Custom styles for the container\n */\n style?: CSSProperties;\n\n /**\n * CSS class name for the progress bar\n */\n progressBarClassName?: string;\n\n /**\n * CSS class name for the progress fill\n */\n progressFillClassName?: string;\n\n /**\n * CSS class name for the progress info\n */\n progressInfoClassName?: string;\n}\n\n/**\n * Format time in seconds to human-readable format\n *\n * @param seconds - Time in seconds\n * @returns Formatted string (e.g., \"1m 30s\", \"45s\")\n */\nfunction formatTime(seconds: number): string {\n if (seconds <= 0 || !isFinite(seconds)) {\n return \"0s\";\n }\n\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const secs = Math.floor(seconds % 60);\n\n const parts: string[] = [];\n\n if (hours > 0) {\n parts.push(`${hours}h`);\n }\n if (minutes > 0) {\n parts.push(`${minutes}m`);\n }\n if (secs > 0 || parts.length === 0) {\n parts.push(`${secs}s`);\n }\n\n return parts.join(\" \");\n}\n\n/**\n * UploadProgress component\n *\n * Displays upload progress with a progress bar, percentage, speed, and remaining time.\n * Automatically updates as the upload progresses.\n *\n * @example\n * ```tsx\n * <UploadProgress\n * task={uploadTask}\n * showSpeed={true}\n * showRemainingTime={true}\n * />\n * ```\n */\nexport function UploadProgress({\n task,\n showSpeed = true,\n showRemainingTime = true,\n className,\n style,\n progressBarClassName,\n progressFillClassName,\n progressInfoClassName,\n}: UploadProgressProps) {\n const [progress, setProgress] = useState(task.getProgress());\n\n useEffect(() => {\n // Update progress when the task emits progress events\n const handleProgress = () => {\n setProgress(task.getProgress());\n };\n\n // Subscribe to progress events\n task.on(\"progress\", handleProgress);\n\n // Also update on other state changes that might affect progress\n task.on(\"start\", handleProgress);\n task.on(\"success\", handleProgress);\n task.on(\"error\", handleProgress);\n task.on(\"pause\", handleProgress);\n task.on(\"resume\", handleProgress);\n\n // Initial update\n handleProgress();\n\n // Cleanup: unsubscribe from events\n return () => {\n task.off(\"progress\", handleProgress);\n task.off(\"start\", handleProgress);\n task.off(\"success\", handleProgress);\n task.off(\"error\", handleProgress);\n task.off(\"pause\", handleProgress);\n task.off(\"resume\", handleProgress);\n };\n }, [task]);\n\n const defaultStyles = {\n container: {\n width: \"100%\",\n padding: \"8px\",\n } as CSSProperties,\n progressBar: {\n width: \"100%\",\n height: \"8px\",\n backgroundColor: \"#e0e0e0\",\n borderRadius: \"4px\",\n overflow: \"hidden\",\n marginBottom: \"8px\",\n } as CSSProperties,\n progressFill: {\n height: \"100%\",\n backgroundColor: \"#4caf50\",\n transition: \"width 0.3s ease\",\n borderRadius: \"4px\",\n } as CSSProperties,\n progressInfo: {\n display: \"flex\",\n justifyContent: \"space-between\",\n fontSize: \"14px\",\n color: \"#666\",\n } as CSSProperties,\n };\n\n return (\n <div\n className={className}\n style={style || defaultStyles.container}\n data-testid=\"upload-progress\"\n >\n {/* Progress bar */}\n <div\n className={progressBarClassName}\n style={progressBarClassName ? undefined : defaultStyles.progressBar}\n data-testid=\"progress-bar\"\n >\n <div\n className={progressFillClassName}\n style={\n progressFillClassName\n ? { width: `${progress.percentage}%` }\n : { ...defaultStyles.progressFill, width: `${progress.percentage}%` }\n }\n data-testid=\"progress-fill\"\n />\n </div>\n\n {/* Progress info */}\n <div\n className={progressInfoClassName}\n style={progressInfoClassName ? undefined : defaultStyles.progressInfo}\n data-testid=\"progress-info\"\n >\n <span data-testid=\"progress-percentage\">{progress.percentage.toFixed(1)}%</span>\n\n <div style={{ display: \"flex\", gap: \"16px\" }}>\n {showSpeed && progress.speed > 0 && (\n <span data-testid=\"progress-speed\">{formatFileSize(progress.speed)}/s</span>\n )}\n\n {showRemainingTime && progress.remainingTime > 0 && (\n <span data-testid=\"progress-remaining-time\">\n {formatTime(progress.remainingTime)} remaining\n </span>\n )}\n </div>\n </div>\n </div>\n );\n}\n","import { ReactNode, CSSProperties, useState, useEffect } from \"react\";\nimport type { UploadTask } from \"@chunkflowjs/core\";\nimport { UploadStatus } from \"@chunkflowjs/protocol\";\nimport { formatFileSize } from \"@chunkflowjs/shared\";\nimport { useUploadList } from \"@chunkflowjs/upload-client-react\";\nimport { UploadProgress } from \"./UploadProgress\";\n\n/**\n * Props for the UploadList component\n */\nexport interface UploadListProps {\n /**\n * CSS class name for the container\n */\n className?: string;\n\n /**\n * Custom styles for the container\n */\n style?: CSSProperties;\n\n /**\n * Base URL for file downloads (e.g., \"http://localhost:3001\")\n * If provided, relative file URLs will be converted to absolute URLs\n */\n baseURL?: string;\n\n /**\n * Custom render function for each upload item\n * If provided, this will be used instead of the default item renderer\n */\n renderItem?: (task: UploadTask, actions: UploadItemActions) => ReactNode;\n\n /**\n * Whether to show completed uploads\n * @default true\n */\n showCompleted?: boolean;\n\n /**\n * Whether to show failed uploads\n * @default true\n */\n showFailed?: boolean;\n\n /**\n * Whether to show cancelled uploads\n * @default true\n */\n showCancelled?: boolean;\n\n /**\n * Maximum number of items to display\n * If not set, all items will be displayed\n */\n maxItems?: number;\n\n /**\n * Message to display when there are no uploads\n * @default \"No uploads\"\n */\n emptyMessage?: string;\n}\n\n/**\n * Actions available for each upload item\n */\nexport interface UploadItemActions {\n /** Pause the upload */\n pause: () => void;\n /** Resume the upload */\n resume: () => void;\n /** Cancel the upload */\n cancel: () => void;\n /** Remove the upload from the list */\n remove: () => void;\n}\n\n/**\n * Props for the default upload item component\n */\ninterface DefaultUploadItemProps {\n task: UploadTask;\n onRemove: () => void;\n baseURL?: string;\n}\n\n/**\n * Default upload item component\n *\n * Displays file info, progress, and action buttons\n */\nfunction DefaultUploadItem({ task, onRemove, baseURL }: DefaultUploadItemProps) {\n const status = task.getStatus();\n const [fileUrl, setFileUrl] = useState<string | null>(null);\n\n // Listen to success event to get fileUrl\n useEffect(() => {\n const handleSuccess = ({ fileUrl }: { fileUrl: string }) => {\n // Convert relative URL to absolute URL if baseURL is provided\n const absoluteUrl = baseURL && fileUrl.startsWith(\"/\") ? `${baseURL}${fileUrl}` : fileUrl;\n setFileUrl(absoluteUrl);\n };\n\n task.on(\"success\", handleSuccess);\n\n return () => {\n task.off(\"success\", handleSuccess);\n };\n }, [task, baseURL]);\n\n const handleClick = () => {\n // Only open file if upload is successful and we have a URL\n if (status === UploadStatus.SUCCESS && fileUrl) {\n window.open(fileUrl, \"_blank\");\n }\n };\n\n const defaultStyles = {\n container: {\n padding: \"16px\",\n border: \"1px solid var(--border, #333)\",\n borderRadius: \"8px\",\n marginBottom: \"8px\",\n backgroundColor: \"var(--darker-bg, #0f0f0f)\",\n cursor: status === UploadStatus.SUCCESS && fileUrl ? \"pointer\" : \"default\",\n transition: \"all 0.2s\",\n } as CSSProperties,\n fileInfo: {\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"8px\",\n } as CSSProperties,\n fileName: {\n fontWeight: 500,\n fontSize: \"14px\",\n color: \"var(--text, #e0e0e0)\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\" as const,\n flex: 1,\n marginRight: \"8px\",\n } as CSSProperties,\n fileSize: {\n fontSize: \"12px\",\n color: \"var(--text-dim, #999)\",\n } as CSSProperties,\n status: {\n fontSize: \"12px\",\n padding: \"2px 8px\",\n borderRadius: \"4px\",\n marginLeft: \"8px\",\n } as CSSProperties,\n actions: {\n display: \"flex\",\n gap: \"8px\",\n marginTop: \"8px\",\n } as CSSProperties,\n button: {\n padding: \"4px 12px\",\n fontSize: \"12px\",\n border: \"1px solid var(--border, #333)\",\n borderRadius: \"4px\",\n backgroundColor: \"var(--card-bg, #1e1e1e)\",\n color: \"var(--text, #e0e0e0)\",\n cursor: \"pointer\",\n transition: \"all 0.2s\",\n } as CSSProperties,\n };\n\n const getStatusStyle = (status: UploadStatus): CSSProperties => {\n const baseStyle = defaultStyles.status;\n switch (status) {\n case UploadStatus.UPLOADING:\n return { ...baseStyle, backgroundColor: \"rgba(33, 150, 243, 0.15)\", color: \"#42a5f5\" };\n case UploadStatus.SUCCESS:\n return { ...baseStyle, backgroundColor: \"rgba(76, 175, 80, 0.15)\", color: \"#66bb6a\" };\n case UploadStatus.ERROR:\n return { ...baseStyle, backgroundColor: \"rgba(244, 67, 54, 0.15)\", color: \"#ef5350\" };\n case UploadStatus.PAUSED:\n return { ...baseStyle, backgroundColor: \"rgba(255, 152, 0, 0.15)\", color: \"#ffa726\" };\n case UploadStatus.CANCELLED:\n return { ...baseStyle, backgroundColor: \"rgba(158, 158, 158, 0.15)\", color: \"#bdbdbd\" };\n default:\n return { ...baseStyle, backgroundColor: \"rgba(158, 158, 158, 0.15)\", color: \"#bdbdbd\" };\n }\n };\n\n const getStatusText = (status: UploadStatus): string => {\n switch (status) {\n case UploadStatus.IDLE:\n return \"Idle\";\n case UploadStatus.HASHING:\n return \"Hashing\";\n case UploadStatus.UPLOADING:\n return \"Uploading\";\n case UploadStatus.PAUSED:\n return \"Paused\";\n case UploadStatus.SUCCESS:\n return \"Success\";\n case UploadStatus.ERROR:\n return \"Error\";\n case UploadStatus.CANCELLED:\n return \"Cancelled\";\n default:\n return \"Unknown\";\n }\n };\n\n return (\n <div\n style={defaultStyles.container}\n data-testid=\"upload-item\"\n onClick={handleClick}\n onMouseEnter={(e) => {\n if (status === UploadStatus.SUCCESS && fileUrl) {\n e.currentTarget.style.backgroundColor = \"var(--card-bg, #1e1e1e)\";\n }\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = \"var(--darker-bg, #0f0f0f)\";\n }}\n title={status === UploadStatus.SUCCESS && fileUrl ? \"Click to open file\" : \"\"}\n >\n {/* File info */}\n <div style={defaultStyles.fileInfo}>\n <div style={{ display: \"flex\", alignItems: \"center\", flex: 1, minWidth: 0 }}>\n <span style={defaultStyles.fileName} title={task.file.name}>\n {task.file.name}\n </span>\n <span style={defaultStyles.fileSize}>{formatFileSize(task.file.size)}</span>\n </div>\n <span style={getStatusStyle(status)} data-testid=\"upload-status\">\n {getStatusText(status)}\n </span>\n </div>\n\n {/* Progress bar (only show for active uploads) */}\n {(status === UploadStatus.UPLOADING ||\n status === UploadStatus.PAUSED ||\n status === UploadStatus.HASHING) && <UploadProgress task={task} />}\n\n {/* Actions */}\n <div style={defaultStyles.actions} onClick={(e) => e.stopPropagation()}>\n {status === UploadStatus.UPLOADING && (\n <button\n onClick={() => task.pause()}\n style={defaultStyles.button}\n data-testid=\"pause-button\"\n type=\"button\"\n >\n Pause\n </button>\n )}\n\n {status === UploadStatus.PAUSED && (\n <button\n onClick={() => task.resume()}\n style={defaultStyles.button}\n data-testid=\"resume-button\"\n type=\"button\"\n >\n Resume\n </button>\n )}\n\n {(status === UploadStatus.UPLOADING || status === UploadStatus.PAUSED) && (\n <button\n onClick={() => task.cancel()}\n style={defaultStyles.button}\n data-testid=\"cancel-button\"\n type=\"button\"\n >\n Cancel\n </button>\n )}\n\n {status === UploadStatus.SUCCESS && fileUrl && (\n <button\n onClick={handleClick}\n style={defaultStyles.button}\n data-testid=\"open-button\"\n type=\"button\"\n >\n Open\n </button>\n )}\n\n <button\n onClick={onRemove}\n style={defaultStyles.button}\n data-testid=\"remove-button\"\n type=\"button\"\n >\n Remove\n </button>\n </div>\n </div>\n );\n}\n\n/**\n * UploadList component\n *\n * Displays a list of upload tasks with progress bars and action buttons.\n * Integrates with useUploadList hook to automatically sync with the upload manager.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <UploadList />\n *\n * // With custom styling\n * <UploadList\n * className=\"my-upload-list\"\n * showCompleted={false}\n * maxItems={10}\n * />\n *\n * // With custom item renderer\n * <UploadList\n * renderItem={(task, actions) => (\n * <div>\n * <h3>{task.file.name}</h3>\n * <button onClick={actions.pause}>Pause</button>\n * <button onClick={actions.remove}>Remove</button>\n * </div>\n * )}\n * />\n * ```\n */\nexport function UploadList({\n className,\n style,\n baseURL,\n renderItem,\n showCompleted = true,\n showFailed = true,\n showCancelled = true,\n maxItems,\n emptyMessage = \"No uploads\",\n}: UploadListProps) {\n const { tasks, removeTask } = useUploadList();\n\n // Filter tasks based on props\n const filteredTasks = tasks.filter((task) => {\n const status = task.getStatus();\n\n if (!showCompleted && status === UploadStatus.SUCCESS) {\n return false;\n }\n\n if (!showFailed && status === UploadStatus.ERROR) {\n return false;\n }\n\n if (!showCancelled && status === UploadStatus.CANCELLED) {\n return false;\n }\n\n return true;\n });\n\n // Limit number of items if maxItems is set\n const displayTasks = maxItems ? filteredTasks.slice(0, maxItems) : filteredTasks;\n\n const defaultStyles = {\n container: {\n width: \"100%\",\n } as CSSProperties,\n empty: {\n padding: \"32px\",\n textAlign: \"center\" as const,\n color: \"var(--text-dim, #999)\",\n fontSize: \"14px\",\n } as CSSProperties,\n };\n\n // Show empty message if no tasks\n if (displayTasks.length === 0) {\n return (\n <div\n className={className}\n style={style || defaultStyles.container}\n data-testid=\"upload-list-empty\"\n >\n <div style={defaultStyles.empty}>{emptyMessage}</div>\n </div>\n );\n }\n\n return (\n <div className={className} style={style || defaultStyles.container} data-testid=\"upload-list\">\n {displayTasks.map((task) => {\n const actions: UploadItemActions = {\n pause: () => task.pause(),\n resume: () => task.resume(),\n cancel: () => task.cancel(),\n remove: () => removeTask(task.id),\n };\n\n return (\n <div key={task.id} data-testid=\"upload-list-item\">\n {renderItem ? (\n renderItem(task, actions)\n ) : (\n <DefaultUploadItem task={task} onRemove={actions.remove} baseURL={baseURL} />\n )}\n </div>\n );\n })}\n </div>\n );\n}\n","import { useState, useRef, DragEvent, ReactNode, CSSProperties } from \"react\";\n\n/**\n * Props for the UploadDropzone component\n */\nexport interface UploadDropzoneProps {\n /**\n * Accepted file types (e.g., \"image/*\", \".pdf,.doc\")\n */\n accept?: string;\n\n /**\n * Maximum file size in bytes\n */\n maxSize?: number;\n\n /**\n * Allow multiple file selection\n * @default true\n */\n multiple?: boolean;\n\n /**\n * Callback when files are dropped\n */\n onDrop?: (files: File[]) => void;\n\n /**\n * Callback when file validation fails\n */\n onError?: (error: FileValidationError) => void;\n\n /**\n * Custom content to display in the dropzone\n */\n children?: ReactNode;\n\n /**\n * CSS class name for the container\n */\n className?: string;\n\n /**\n * Custom styles for the container\n */\n style?: CSSProperties;\n\n /**\n * CSS class name when dragging over\n */\n draggingClassName?: string;\n\n /**\n * Disable the dropzone\n * @default false\n */\n disabled?: boolean;\n}\n\n/**\n * File validation error\n */\nexport class FileValidationError extends Error {\n constructor(\n message: string,\n public code: \"FILE_TOO_LARGE\" | \"INVALID_FILE_TYPE\",\n public file: File,\n ) {\n super(message);\n this.name = \"FileValidationError\";\n }\n}\n\n/**\n * Check if a file matches the accept pattern\n *\n * @param file - The file to check\n * @param accept - The accept pattern (e.g., \"image/star\", \".pdf,.doc\", \"star/star\")\n * @returns true if the file matches the pattern\n */\nfunction matchAccept(file: File, accept: string): boolean {\n // Accept all files if pattern is \"*/*\" or \"*\"\n if (accept === \"*/*\" || accept === \"*\") {\n return true;\n }\n\n const patterns = accept.split(\",\").map((p) => p.trim());\n\n for (const pattern of patterns) {\n // Accept all files\n if (pattern === \"*/*\" || pattern === \"*\") {\n return true;\n }\n\n // Exact MIME type match (e.g., \"image/jpeg\")\n if (pattern === file.type) {\n return true;\n }\n\n // Wildcard MIME type match (e.g., \"image/*\")\n if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n if (file.type.startsWith(prefix + \"/\")) {\n return true;\n }\n }\n\n // File extension match (e.g., \".pdf\")\n if (pattern.startsWith(\".\")) {\n if (file.name.toLowerCase().endsWith(pattern.toLowerCase())) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * UploadDropzone component\n *\n * A drag-and-drop zone for file uploads with visual feedback.\n * Validates files based on type and size before passing them to the onDrop callback.\n *\n * @example\n * ```tsx\n * <UploadDropzone\n * accept=\"image/*\"\n * maxSize={10 * 1024 * 1024} // 10MB\n * multiple\n * onDrop={(files) => console.log('Dropped:', files)}\n * >\n * <p>Drag and drop files here, or click to select</p>\n * </UploadDropzone>\n * ```\n */\nexport function UploadDropzone({\n accept,\n maxSize,\n multiple = true,\n onDrop,\n onError,\n children,\n className,\n style,\n draggingClassName,\n disabled = false,\n}: UploadDropzoneProps) {\n const [isDragging, setIsDragging] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const dragCounterRef = useRef(0);\n\n const validateFile = (file: File): FileValidationError | null => {\n // Validate file size\n if (maxSize && file.size > maxSize) {\n return new FileValidationError(\n `File \"${file.name}\" size ${file.size} bytes exceeds maximum ${maxSize} bytes`,\n \"FILE_TOO_LARGE\",\n file,\n );\n }\n\n // Validate file type\n if (accept && !matchAccept(file, accept)) {\n return new FileValidationError(\n `File \"${file.name}\" type \"${file.type}\" is not accepted`,\n \"INVALID_FILE_TYPE\",\n file,\n );\n }\n\n return null;\n };\n\n const processFiles = (files: File[]) => {\n if (files.length === 0) {\n return;\n }\n\n // Validate all files\n const validFiles: File[] = [];\n const errors: FileValidationError[] = [];\n\n for (const file of files) {\n const error = validateFile(file);\n if (error) {\n errors.push(error);\n } else {\n validFiles.push(file);\n }\n }\n\n // Report errors\n if (errors.length > 0 && onError) {\n errors.forEach((error) => onError(error));\n }\n\n // Call onDrop with valid files\n if (validFiles.length > 0 && onDrop) {\n onDrop(validFiles);\n }\n };\n\n const handleDragEnter = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n dragCounterRef.current++;\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {\n setIsDragging(true);\n }\n };\n\n const handleDragLeave = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n dragCounterRef.current--;\n if (dragCounterRef.current === 0) {\n setIsDragging(false);\n }\n };\n\n const handleDragOver = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n // Set dropEffect to indicate this is a copy operation\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = \"copy\";\n }\n };\n\n const handleDrop = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n setIsDragging(false);\n dragCounterRef.current = 0;\n\n const files = Array.from(e.dataTransfer?.files || []);\n\n // Limit to single file if multiple is false\n const filesToProcess = multiple ? files : files.slice(0, 1);\n\n processFiles(filesToProcess);\n };\n\n const handleClick = () => {\n if (!disabled) {\n inputRef.current?.click();\n }\n };\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files || []);\n processFiles(files);\n\n // Reset input value to allow selecting the same file again\n if (inputRef.current) {\n inputRef.current.value = \"\";\n }\n };\n\n const defaultStyles = {\n container: {\n border: \"2px dashed #ccc\",\n borderRadius: \"8px\",\n padding: \"32px\",\n textAlign: \"center\" as const,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n transition: \"all 0.3s ease\",\n backgroundColor: disabled ? \"#f5f5f5\" : \"#fafafa\",\n opacity: disabled ? 0.6 : 1,\n } as CSSProperties,\n dragging: {\n borderColor: \"#4caf50\",\n backgroundColor: \"#e8f5e9\",\n } as CSSProperties,\n content: {\n color: \"#666\",\n fontSize: \"14px\",\n } as CSSProperties,\n };\n\n const containerStyle = {\n ...defaultStyles.container,\n ...(isDragging && !disabled ? defaultStyles.dragging : {}),\n ...style,\n };\n\n const containerClassName = [className, isDragging && !disabled ? draggingClassName : \"\"]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <>\n <div\n className={containerClassName || undefined}\n style={containerStyle}\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n onClick={handleClick}\n data-testid=\"upload-dropzone\"\n data-dragging={isDragging}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled}\n onKeyDown={(e) => {\n if (!disabled && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n handleClick();\n }\n }}\n >\n {children || (\n <div style={defaultStyles.content} data-testid=\"dropzone-default-content\">\n <p>\n <strong>Drag and drop files here</strong>\n </p>\n <p>or click to select files</p>\n </div>\n )}\n </div>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n onChange={handleInputChange}\n style={{ display: \"none\" }}\n aria-hidden=\"true\"\n disabled={disabled}\n />\n </>\n );\n}\n","// React components exports\nexport { UploadButton, FileValidationError } from \"./UploadButton\";\nexport type { UploadButtonProps } from \"./UploadButton\";\n\nexport { UploadProgress } from \"./UploadProgress\";\nexport type { UploadProgressProps } from \"./UploadProgress\";\n\nexport { UploadList } from \"./UploadList\";\nexport type { UploadListProps, UploadItemActions } from \"./UploadList\";\n\nexport { UploadDropzone } from \"./UploadDropzone\";\nexport type { UploadDropzoneProps } from \"./UploadDropzone\";\n\n// Placeholder export to allow build to succeed\nexport const UPLOAD_COMPONENT_REACT_VERSION = \"0.0.0\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["matchAccept","FileValidationError"],"sources":["../src/UploadButton.tsx","../src/UploadProgress.tsx","../../protocol/dist/index.mjs","../src/UploadList.tsx","../src/UploadDropzone.tsx","../src/index.ts"],"sourcesContent":["import { useRef, ChangeEvent, ReactNode } from \"react\";\n\n/**\n * Props for the UploadButton component\n */\nexport interface UploadButtonProps {\n /**\n * Accepted file types (e.g., \"image/*\", \".pdf,.doc\")\n */\n accept?: string;\n\n /**\n * Allow multiple file selection\n * @default false\n */\n multiple?: boolean;\n\n /**\n * Maximum file size in bytes\n */\n maxSize?: number;\n\n /**\n * Callback when files are selected\n */\n onSelect?: (files: File[]) => void;\n\n /**\n * Callback when file validation fails\n */\n onError?: (error: FileValidationError) => void;\n\n /**\n * Custom button content\n */\n children?: ReactNode;\n\n /**\n * CSS class name for the button\n */\n className?: string;\n\n /**\n * Disable the button\n * @default false\n */\n disabled?: boolean;\n}\n\n/**\n * File validation error\n */\nexport class FileValidationError extends Error {\n constructor(\n message: string,\n public code: \"FILE_TOO_LARGE\" | \"INVALID_FILE_TYPE\",\n public file: File,\n ) {\n super(message);\n this.name = \"FileValidationError\";\n }\n}\n\n/**\n * UploadButton component\n *\n * A button component that triggers file selection and validates files\n * before passing them to the onSelect callback.\n *\n * @example\n * ```tsx\n * <UploadButton\n * accept=\"image/*\"\n * maxSize={10 * 1024 * 1024} // 10MB\n * multiple\n * onSelect={(files) => console.log('Selected:', files)}\n * >\n * Select Files\n * </UploadButton>\n * ```\n */\nexport function UploadButton({\n accept,\n multiple = false,\n maxSize,\n onSelect,\n onError,\n children,\n className,\n disabled = false,\n}: UploadButtonProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleClick = () => {\n if (!disabled) {\n inputRef.current?.click();\n }\n };\n\n const validateFile = (file: File): FileValidationError | null => {\n // Validate file size\n if (maxSize && file.size > maxSize) {\n return new FileValidationError(\n `File \"${file.name}\" size ${file.size} bytes exceeds maximum ${maxSize} bytes`,\n \"FILE_TOO_LARGE\",\n file,\n );\n }\n\n // Validate file type\n if (accept && !matchAccept(file, accept)) {\n return new FileValidationError(\n `File \"${file.name}\" type \"${file.type}\" is not accepted`,\n \"INVALID_FILE_TYPE\",\n file,\n );\n }\n\n return null;\n };\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files || []);\n\n if (files.length === 0) {\n return;\n }\n\n // Validate all files\n const validFiles: File[] = [];\n const errors: FileValidationError[] = [];\n\n for (const file of files) {\n const error = validateFile(file);\n if (error) {\n errors.push(error);\n } else {\n validFiles.push(file);\n }\n }\n\n // Report errors\n if (errors.length > 0 && onError) {\n errors.forEach((error) => onError(error));\n }\n\n // Call onSelect with valid files\n if (validFiles.length > 0 && onSelect) {\n onSelect(validFiles);\n }\n\n // Reset input value to allow selecting the same file again\n if (inputRef.current) {\n inputRef.current.value = \"\";\n }\n };\n\n return (\n <>\n <button onClick={handleClick} className={className} disabled={disabled} type=\"button\">\n {children || \"Select Files\"}\n </button>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n onChange={handleChange}\n style={{ display: \"none\" }}\n aria-hidden=\"true\"\n />\n </>\n );\n}\n\n/**\n * Check if a file matches the accept pattern\n *\n * @param file - The file to check\n * @param accept - The accept pattern (e.g., \"image/star\", \".pdf,.doc\", \"star/star\")\n * @returns true if the file matches the pattern\n */\nfunction matchAccept(file: File, accept: string): boolean {\n // Accept all files if pattern is \"*/*\" or \"*\"\n if (accept === \"*/*\" || accept === \"*\") {\n return true;\n }\n\n const patterns = accept.split(\",\").map((p) => p.trim());\n\n for (const pattern of patterns) {\n // Accept all files\n if (pattern === \"*/*\" || pattern === \"*\") {\n return true;\n }\n\n // Exact MIME type match (e.g., \"image/jpeg\")\n if (pattern === file.type) {\n return true;\n }\n\n // Wildcard MIME type match (e.g., \"image/*\")\n if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n if (file.type.startsWith(prefix + \"/\")) {\n return true;\n }\n }\n\n // File extension match (e.g., \".pdf\")\n if (pattern.startsWith(\".\")) {\n if (file.name.toLowerCase().endsWith(pattern.toLowerCase())) {\n return true;\n }\n }\n }\n\n return false;\n}\n","import { useEffect, useState, CSSProperties } from \"react\";\nimport type { UploadTask } from \"@chunkflowjs/core\";\nimport { formatFileSize } from \"@chunkflowjs/shared\";\n\n/**\n * Props for the UploadProgress component\n */\nexport interface UploadProgressProps {\n /**\n * The upload task to display progress for\n */\n task: UploadTask;\n\n /**\n * Whether to show upload speed\n * @default true\n */\n showSpeed?: boolean;\n\n /**\n * Whether to show remaining time\n * @default true\n */\n showRemainingTime?: boolean;\n\n /**\n * CSS class name for the container\n */\n className?: string;\n\n /**\n * Custom styles for the container\n */\n style?: CSSProperties;\n\n /**\n * CSS class name for the progress bar\n */\n progressBarClassName?: string;\n\n /**\n * CSS class name for the progress fill\n */\n progressFillClassName?: string;\n\n /**\n * CSS class name for the progress info\n */\n progressInfoClassName?: string;\n}\n\n/**\n * Format time in seconds to human-readable format\n *\n * @param seconds - Time in seconds\n * @returns Formatted string (e.g., \"1m 30s\", \"45s\")\n */\nfunction formatTime(seconds: number): string {\n if (seconds <= 0 || !isFinite(seconds)) {\n return \"0s\";\n }\n\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const secs = Math.floor(seconds % 60);\n\n const parts: string[] = [];\n\n if (hours > 0) {\n parts.push(`${hours}h`);\n }\n if (minutes > 0) {\n parts.push(`${minutes}m`);\n }\n if (secs > 0 || parts.length === 0) {\n parts.push(`${secs}s`);\n }\n\n return parts.join(\" \");\n}\n\n/**\n * UploadProgress component\n *\n * Displays upload progress with a progress bar, percentage, speed, and remaining time.\n * Automatically updates as the upload progresses.\n *\n * @example\n * ```tsx\n * <UploadProgress\n * task={uploadTask}\n * showSpeed={true}\n * showRemainingTime={true}\n * />\n * ```\n */\nexport function UploadProgress({\n task,\n showSpeed = true,\n showRemainingTime = true,\n className,\n style,\n progressBarClassName,\n progressFillClassName,\n progressInfoClassName,\n}: UploadProgressProps) {\n const [progress, setProgress] = useState(task.getProgress());\n\n useEffect(() => {\n // Update progress when the task emits progress events\n const handleProgress = () => {\n setProgress(task.getProgress());\n };\n\n // Subscribe to progress events\n task.on(\"progress\", handleProgress);\n\n // Also update on other state changes that might affect progress\n task.on(\"start\", handleProgress);\n task.on(\"success\", handleProgress);\n task.on(\"error\", handleProgress);\n task.on(\"pause\", handleProgress);\n task.on(\"resume\", handleProgress);\n\n // Initial update\n handleProgress();\n\n // Cleanup: unsubscribe from events\n return () => {\n task.off(\"progress\", handleProgress);\n task.off(\"start\", handleProgress);\n task.off(\"success\", handleProgress);\n task.off(\"error\", handleProgress);\n task.off(\"pause\", handleProgress);\n task.off(\"resume\", handleProgress);\n };\n }, [task]);\n\n const defaultStyles = {\n container: {\n width: \"100%\",\n padding: \"8px\",\n } as CSSProperties,\n progressBar: {\n width: \"100%\",\n height: \"8px\",\n backgroundColor: \"#e0e0e0\",\n borderRadius: \"4px\",\n overflow: \"hidden\",\n marginBottom: \"8px\",\n } as CSSProperties,\n progressFill: {\n height: \"100%\",\n backgroundColor: \"#4caf50\",\n transition: \"width 0.3s ease\",\n borderRadius: \"4px\",\n } as CSSProperties,\n progressInfo: {\n display: \"flex\",\n justifyContent: \"space-between\",\n fontSize: \"14px\",\n color: \"#666\",\n } as CSSProperties,\n };\n\n return (\n <div\n className={className}\n style={style || defaultStyles.container}\n data-testid=\"upload-progress\"\n >\n {/* Progress bar */}\n <div\n className={progressBarClassName}\n style={progressBarClassName ? undefined : defaultStyles.progressBar}\n data-testid=\"progress-bar\"\n >\n <div\n className={progressFillClassName}\n style={\n progressFillClassName\n ? { width: `${progress.percentage}%` }\n : { ...defaultStyles.progressFill, width: `${progress.percentage}%` }\n }\n data-testid=\"progress-fill\"\n />\n </div>\n\n {/* Progress info */}\n <div\n className={progressInfoClassName}\n style={progressInfoClassName ? undefined : defaultStyles.progressInfo}\n data-testid=\"progress-info\"\n >\n <span data-testid=\"progress-percentage\">{progress.percentage.toFixed(1)}%</span>\n\n <div style={{ display: \"flex\", gap: \"16px\" }}>\n {showSpeed && progress.speed > 0 && (\n <span data-testid=\"progress-speed\">{formatFileSize(progress.speed)}/s</span>\n )}\n\n {showRemainingTime && progress.remainingTime > 0 && (\n <span data-testid=\"progress-remaining-time\">\n {formatTime(progress.remainingTime)} remaining\n </span>\n )}\n </div>\n </div>\n </div>\n );\n}\n","//#region src/types.ts\n/**\n* Upload status enumeration\n*/\nlet UploadStatus = /* @__PURE__ */ function(UploadStatus) {\n\t/** Initial state, not started */\n\tUploadStatus[\"IDLE\"] = \"idle\";\n\t/** Calculating file hash */\n\tUploadStatus[\"HASHING\"] = \"hashing\";\n\t/** Uploading chunks */\n\tUploadStatus[\"UPLOADING\"] = \"uploading\";\n\t/** Upload paused by user */\n\tUploadStatus[\"PAUSED\"] = \"paused\";\n\t/** Upload completed successfully */\n\tUploadStatus[\"SUCCESS\"] = \"success\";\n\t/** Upload failed with error */\n\tUploadStatus[\"ERROR\"] = \"error\";\n\t/** Upload cancelled by user */\n\tUploadStatus[\"CANCELLED\"] = \"cancelled\";\n\treturn UploadStatus;\n}({});\n\n//#endregion\nexport { UploadStatus };\n//# sourceMappingURL=index.mjs.map","import { ReactNode, CSSProperties, useState, useEffect } from \"react\";\nimport type { UploadTask } from \"@chunkflowjs/core\";\nimport { UploadStatus } from \"@chunkflowjs/protocol\";\nimport { formatFileSize } from \"@chunkflowjs/shared\";\nimport { useUploadList } from \"@chunkflowjs/upload-client-react\";\nimport { UploadProgress } from \"./UploadProgress\";\n\n/**\n * Props for the UploadList component\n */\nexport interface UploadListProps {\n /**\n * CSS class name for the container\n */\n className?: string;\n\n /**\n * Custom styles for the container\n */\n style?: CSSProperties;\n\n /**\n * Base URL for file downloads (e.g., \"http://localhost:3001\")\n * If provided, relative file URLs will be converted to absolute URLs\n */\n baseURL?: string;\n\n /**\n * Custom render function for each upload item\n * If provided, this will be used instead of the default item renderer\n */\n renderItem?: (task: UploadTask, actions: UploadItemActions) => ReactNode;\n\n /**\n * Whether to show completed uploads\n * @default true\n */\n showCompleted?: boolean;\n\n /**\n * Whether to show failed uploads\n * @default true\n */\n showFailed?: boolean;\n\n /**\n * Whether to show cancelled uploads\n * @default true\n */\n showCancelled?: boolean;\n\n /**\n * Maximum number of items to display\n * If not set, all items will be displayed\n */\n maxItems?: number;\n\n /**\n * Message to display when there are no uploads\n * @default \"No uploads\"\n */\n emptyMessage?: string;\n}\n\n/**\n * Actions available for each upload item\n */\nexport interface UploadItemActions {\n /** Pause the upload */\n pause: () => void;\n /** Resume the upload */\n resume: () => void;\n /** Cancel the upload */\n cancel: () => void;\n /** Remove the upload from the list */\n remove: () => void;\n}\n\n/**\n * Props for the default upload item component\n */\ninterface DefaultUploadItemProps {\n task: UploadTask;\n onRemove: () => void;\n baseURL?: string;\n}\n\n/**\n * Default upload item component\n *\n * Displays file info, progress, and action buttons\n */\nfunction DefaultUploadItem({ task, onRemove, baseURL }: DefaultUploadItemProps) {\n const status = task.getStatus();\n const [fileUrl, setFileUrl] = useState<string | null>(null);\n\n // Listen to success event to get fileUrl\n useEffect(() => {\n const handleSuccess = ({ fileUrl }: { fileUrl: string }) => {\n // Convert relative URL to absolute URL if baseURL is provided\n const absoluteUrl = baseURL && fileUrl.startsWith(\"/\") ? `${baseURL}${fileUrl}` : fileUrl;\n setFileUrl(absoluteUrl);\n };\n\n task.on(\"success\", handleSuccess);\n\n return () => {\n task.off(\"success\", handleSuccess);\n };\n }, [task, baseURL]);\n\n const handleClick = () => {\n // Only open file if upload is successful and we have a URL\n if (status === UploadStatus.SUCCESS && fileUrl) {\n window.open(fileUrl, \"_blank\");\n }\n };\n\n const defaultStyles = {\n container: {\n padding: \"16px\",\n border: \"1px solid var(--border, #333)\",\n borderRadius: \"8px\",\n marginBottom: \"8px\",\n backgroundColor: \"var(--darker-bg, #0f0f0f)\",\n cursor: status === UploadStatus.SUCCESS && fileUrl ? \"pointer\" : \"default\",\n transition: \"all 0.2s\",\n } as CSSProperties,\n fileInfo: {\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"8px\",\n } as CSSProperties,\n fileName: {\n fontWeight: 500,\n fontSize: \"14px\",\n color: \"var(--text, #e0e0e0)\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\" as const,\n flex: 1,\n marginRight: \"8px\",\n } as CSSProperties,\n fileSize: {\n fontSize: \"12px\",\n color: \"var(--text-dim, #999)\",\n } as CSSProperties,\n status: {\n fontSize: \"12px\",\n padding: \"2px 8px\",\n borderRadius: \"4px\",\n marginLeft: \"8px\",\n } as CSSProperties,\n actions: {\n display: \"flex\",\n gap: \"8px\",\n marginTop: \"8px\",\n } as CSSProperties,\n button: {\n padding: \"4px 12px\",\n fontSize: \"12px\",\n border: \"1px solid var(--border, #333)\",\n borderRadius: \"4px\",\n backgroundColor: \"var(--card-bg, #1e1e1e)\",\n color: \"var(--text, #e0e0e0)\",\n cursor: \"pointer\",\n transition: \"all 0.2s\",\n } as CSSProperties,\n };\n\n const getStatusStyle = (status: UploadStatus): CSSProperties => {\n const baseStyle = defaultStyles.status;\n switch (status) {\n case UploadStatus.UPLOADING:\n return { ...baseStyle, backgroundColor: \"rgba(33, 150, 243, 0.15)\", color: \"#42a5f5\" };\n case UploadStatus.SUCCESS:\n return { ...baseStyle, backgroundColor: \"rgba(76, 175, 80, 0.15)\", color: \"#66bb6a\" };\n case UploadStatus.ERROR:\n return { ...baseStyle, backgroundColor: \"rgba(244, 67, 54, 0.15)\", color: \"#ef5350\" };\n case UploadStatus.PAUSED:\n return { ...baseStyle, backgroundColor: \"rgba(255, 152, 0, 0.15)\", color: \"#ffa726\" };\n case UploadStatus.CANCELLED:\n return { ...baseStyle, backgroundColor: \"rgba(158, 158, 158, 0.15)\", color: \"#bdbdbd\" };\n default:\n return { ...baseStyle, backgroundColor: \"rgba(158, 158, 158, 0.15)\", color: \"#bdbdbd\" };\n }\n };\n\n const getStatusText = (status: UploadStatus): string => {\n switch (status) {\n case UploadStatus.IDLE:\n return \"Idle\";\n case UploadStatus.HASHING:\n return \"Hashing\";\n case UploadStatus.UPLOADING:\n return \"Uploading\";\n case UploadStatus.PAUSED:\n return \"Paused\";\n case UploadStatus.SUCCESS:\n return \"Success\";\n case UploadStatus.ERROR:\n return \"Error\";\n case UploadStatus.CANCELLED:\n return \"Cancelled\";\n default:\n return \"Unknown\";\n }\n };\n\n return (\n <div\n style={defaultStyles.container}\n data-testid=\"upload-item\"\n onClick={handleClick}\n onMouseEnter={(e) => {\n if (status === UploadStatus.SUCCESS && fileUrl) {\n e.currentTarget.style.backgroundColor = \"var(--card-bg, #1e1e1e)\";\n }\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = \"var(--darker-bg, #0f0f0f)\";\n }}\n title={status === UploadStatus.SUCCESS && fileUrl ? \"Click to open file\" : \"\"}\n >\n {/* File info */}\n <div style={defaultStyles.fileInfo}>\n <div style={{ display: \"flex\", alignItems: \"center\", flex: 1, minWidth: 0 }}>\n <span style={defaultStyles.fileName} title={task.file.name}>\n {task.file.name}\n </span>\n <span style={defaultStyles.fileSize}>{formatFileSize(task.file.size)}</span>\n </div>\n <span style={getStatusStyle(status)} data-testid=\"upload-status\">\n {getStatusText(status)}\n </span>\n </div>\n\n {/* Progress bar (only show for active uploads) */}\n {(status === UploadStatus.UPLOADING ||\n status === UploadStatus.PAUSED ||\n status === UploadStatus.HASHING) && <UploadProgress task={task} />}\n\n {/* Actions */}\n <div style={defaultStyles.actions} onClick={(e) => e.stopPropagation()}>\n {status === UploadStatus.UPLOADING && (\n <button\n onClick={() => task.pause()}\n style={defaultStyles.button}\n data-testid=\"pause-button\"\n type=\"button\"\n >\n Pause\n </button>\n )}\n\n {status === UploadStatus.PAUSED && (\n <button\n onClick={() => task.resume()}\n style={defaultStyles.button}\n data-testid=\"resume-button\"\n type=\"button\"\n >\n Resume\n </button>\n )}\n\n {(status === UploadStatus.UPLOADING || status === UploadStatus.PAUSED) && (\n <button\n onClick={() => task.cancel()}\n style={defaultStyles.button}\n data-testid=\"cancel-button\"\n type=\"button\"\n >\n Cancel\n </button>\n )}\n\n {status === UploadStatus.SUCCESS && fileUrl && (\n <button\n onClick={handleClick}\n style={defaultStyles.button}\n data-testid=\"open-button\"\n type=\"button\"\n >\n Open\n </button>\n )}\n\n <button\n onClick={onRemove}\n style={defaultStyles.button}\n data-testid=\"remove-button\"\n type=\"button\"\n >\n Remove\n </button>\n </div>\n </div>\n );\n}\n\n/**\n * UploadList component\n *\n * Displays a list of upload tasks with progress bars and action buttons.\n * Integrates with useUploadList hook to automatically sync with the upload manager.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <UploadList />\n *\n * // With custom styling\n * <UploadList\n * className=\"my-upload-list\"\n * showCompleted={false}\n * maxItems={10}\n * />\n *\n * // With custom item renderer\n * <UploadList\n * renderItem={(task, actions) => (\n * <div>\n * <h3>{task.file.name}</h3>\n * <button onClick={actions.pause}>Pause</button>\n * <button onClick={actions.remove}>Remove</button>\n * </div>\n * )}\n * />\n * ```\n */\nexport function UploadList({\n className,\n style,\n baseURL,\n renderItem,\n showCompleted = true,\n showFailed = true,\n showCancelled = true,\n maxItems,\n emptyMessage = \"No uploads\",\n}: UploadListProps) {\n const { tasks, removeTask } = useUploadList();\n\n // Filter tasks based on props\n const filteredTasks = tasks.filter((task) => {\n const status = task.getStatus();\n\n if (!showCompleted && status === UploadStatus.SUCCESS) {\n return false;\n }\n\n if (!showFailed && status === UploadStatus.ERROR) {\n return false;\n }\n\n if (!showCancelled && status === UploadStatus.CANCELLED) {\n return false;\n }\n\n return true;\n });\n\n // Limit number of items if maxItems is set\n const displayTasks = maxItems ? filteredTasks.slice(0, maxItems) : filteredTasks;\n\n const defaultStyles = {\n container: {\n width: \"100%\",\n } as CSSProperties,\n empty: {\n padding: \"32px\",\n textAlign: \"center\" as const,\n color: \"var(--text-dim, #999)\",\n fontSize: \"14px\",\n } as CSSProperties,\n };\n\n // Show empty message if no tasks\n if (displayTasks.length === 0) {\n return (\n <div\n className={className}\n style={style || defaultStyles.container}\n data-testid=\"upload-list-empty\"\n >\n <div style={defaultStyles.empty}>{emptyMessage}</div>\n </div>\n );\n }\n\n return (\n <div className={className} style={style || defaultStyles.container} data-testid=\"upload-list\">\n {displayTasks.map((task) => {\n const actions: UploadItemActions = {\n pause: () => task.pause(),\n resume: () => task.resume(),\n cancel: () => task.cancel(),\n remove: () => removeTask(task.id),\n };\n\n return (\n <div key={task.id} data-testid=\"upload-list-item\">\n {renderItem ? (\n renderItem(task, actions)\n ) : (\n <DefaultUploadItem task={task} onRemove={actions.remove} baseURL={baseURL} />\n )}\n </div>\n );\n })}\n </div>\n );\n}\n","import { useState, useRef, DragEvent, ReactNode, CSSProperties } from \"react\";\n\n/**\n * Props for the UploadDropzone component\n */\nexport interface UploadDropzoneProps {\n /**\n * Accepted file types (e.g., \"image/*\", \".pdf,.doc\")\n */\n accept?: string;\n\n /**\n * Maximum file size in bytes\n */\n maxSize?: number;\n\n /**\n * Allow multiple file selection\n * @default true\n */\n multiple?: boolean;\n\n /**\n * Callback when files are dropped\n */\n onDrop?: (files: File[]) => void;\n\n /**\n * Callback when file validation fails\n */\n onError?: (error: FileValidationError) => void;\n\n /**\n * Custom content to display in the dropzone\n */\n children?: ReactNode;\n\n /**\n * CSS class name for the container\n */\n className?: string;\n\n /**\n * Custom styles for the container\n */\n style?: CSSProperties;\n\n /**\n * CSS class name when dragging over\n */\n draggingClassName?: string;\n\n /**\n * Disable the dropzone\n * @default false\n */\n disabled?: boolean;\n}\n\n/**\n * File validation error\n */\nexport class FileValidationError extends Error {\n constructor(\n message: string,\n public code: \"FILE_TOO_LARGE\" | \"INVALID_FILE_TYPE\",\n public file: File,\n ) {\n super(message);\n this.name = \"FileValidationError\";\n }\n}\n\n/**\n * Check if a file matches the accept pattern\n *\n * @param file - The file to check\n * @param accept - The accept pattern (e.g., \"image/star\", \".pdf,.doc\", \"star/star\")\n * @returns true if the file matches the pattern\n */\nfunction matchAccept(file: File, accept: string): boolean {\n // Accept all files if pattern is \"*/*\" or \"*\"\n if (accept === \"*/*\" || accept === \"*\") {\n return true;\n }\n\n const patterns = accept.split(\",\").map((p) => p.trim());\n\n for (const pattern of patterns) {\n // Accept all files\n if (pattern === \"*/*\" || pattern === \"*\") {\n return true;\n }\n\n // Exact MIME type match (e.g., \"image/jpeg\")\n if (pattern === file.type) {\n return true;\n }\n\n // Wildcard MIME type match (e.g., \"image/*\")\n if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n if (file.type.startsWith(prefix + \"/\")) {\n return true;\n }\n }\n\n // File extension match (e.g., \".pdf\")\n if (pattern.startsWith(\".\")) {\n if (file.name.toLowerCase().endsWith(pattern.toLowerCase())) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * UploadDropzone component\n *\n * A drag-and-drop zone for file uploads with visual feedback.\n * Validates files based on type and size before passing them to the onDrop callback.\n *\n * @example\n * ```tsx\n * <UploadDropzone\n * accept=\"image/*\"\n * maxSize={10 * 1024 * 1024} // 10MB\n * multiple\n * onDrop={(files) => console.log('Dropped:', files)}\n * >\n * <p>Drag and drop files here, or click to select</p>\n * </UploadDropzone>\n * ```\n */\nexport function UploadDropzone({\n accept,\n maxSize,\n multiple = true,\n onDrop,\n onError,\n children,\n className,\n style,\n draggingClassName,\n disabled = false,\n}: UploadDropzoneProps) {\n const [isDragging, setIsDragging] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const dragCounterRef = useRef(0);\n\n const validateFile = (file: File): FileValidationError | null => {\n // Validate file size\n if (maxSize && file.size > maxSize) {\n return new FileValidationError(\n `File \"${file.name}\" size ${file.size} bytes exceeds maximum ${maxSize} bytes`,\n \"FILE_TOO_LARGE\",\n file,\n );\n }\n\n // Validate file type\n if (accept && !matchAccept(file, accept)) {\n return new FileValidationError(\n `File \"${file.name}\" type \"${file.type}\" is not accepted`,\n \"INVALID_FILE_TYPE\",\n file,\n );\n }\n\n return null;\n };\n\n const processFiles = (files: File[]) => {\n if (files.length === 0) {\n return;\n }\n\n // Validate all files\n const validFiles: File[] = [];\n const errors: FileValidationError[] = [];\n\n for (const file of files) {\n const error = validateFile(file);\n if (error) {\n errors.push(error);\n } else {\n validFiles.push(file);\n }\n }\n\n // Report errors\n if (errors.length > 0 && onError) {\n errors.forEach((error) => onError(error));\n }\n\n // Call onDrop with valid files\n if (validFiles.length > 0 && onDrop) {\n onDrop(validFiles);\n }\n };\n\n const handleDragEnter = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n dragCounterRef.current++;\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {\n setIsDragging(true);\n }\n };\n\n const handleDragLeave = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n dragCounterRef.current--;\n if (dragCounterRef.current === 0) {\n setIsDragging(false);\n }\n };\n\n const handleDragOver = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n // Set dropEffect to indicate this is a copy operation\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = \"copy\";\n }\n };\n\n const handleDrop = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n setIsDragging(false);\n dragCounterRef.current = 0;\n\n const files = Array.from(e.dataTransfer?.files || []);\n\n // Limit to single file if multiple is false\n const filesToProcess = multiple ? files : files.slice(0, 1);\n\n processFiles(filesToProcess);\n };\n\n const handleClick = () => {\n if (!disabled) {\n inputRef.current?.click();\n }\n };\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files || []);\n processFiles(files);\n\n // Reset input value to allow selecting the same file again\n if (inputRef.current) {\n inputRef.current.value = \"\";\n }\n };\n\n const defaultStyles = {\n container: {\n border: \"2px dashed #ccc\",\n borderRadius: \"8px\",\n padding: \"32px\",\n textAlign: \"center\" as const,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n transition: \"all 0.3s ease\",\n backgroundColor: disabled ? \"#f5f5f5\" : \"#fafafa\",\n opacity: disabled ? 0.6 : 1,\n } as CSSProperties,\n dragging: {\n borderColor: \"#4caf50\",\n backgroundColor: \"#e8f5e9\",\n } as CSSProperties,\n content: {\n color: \"#666\",\n fontSize: \"14px\",\n } as CSSProperties,\n };\n\n const containerStyle = {\n ...defaultStyles.container,\n ...(isDragging && !disabled ? defaultStyles.dragging : {}),\n ...style,\n };\n\n const containerClassName = [className, isDragging && !disabled ? draggingClassName : \"\"]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <>\n <div\n className={containerClassName || undefined}\n style={containerStyle}\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n onClick={handleClick}\n data-testid=\"upload-dropzone\"\n data-dragging={isDragging}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled}\n onKeyDown={(e) => {\n if (!disabled && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n handleClick();\n }\n }}\n >\n {children || (\n <div style={defaultStyles.content} data-testid=\"dropzone-default-content\">\n <p>\n <strong>Drag and drop files here</strong>\n </p>\n <p>or click to select files</p>\n </div>\n )}\n </div>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n onChange={handleInputChange}\n style={{ display: \"none\" }}\n aria-hidden=\"true\"\n disabled={disabled}\n />\n </>\n );\n}\n","// React components exports\nexport { UploadButton, FileValidationError } from \"./UploadButton\";\nexport type { UploadButtonProps } from \"./UploadButton\";\n\nexport { UploadProgress } from \"./UploadProgress\";\nexport type { UploadProgressProps } from \"./UploadProgress\";\n\nexport { UploadList } from \"./UploadList\";\nexport type { UploadListProps, UploadItemActions } from \"./UploadList\";\n\nexport { UploadDropzone } from \"./UploadDropzone\";\nexport type { UploadDropzoneProps } from \"./UploadDropzone\";\n\n// Placeholder export to allow build to succeed\nexport const UPLOAD_COMPONENT_REACT_VERSION = \"0.0.0\";\n"],"mappings":"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"}
|