@ttt-productions/file-input 0.11.16 → 0.11.18
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/lib/image-utils.d.ts +0 -1
- package/dist/lib/image-utils.d.ts.map +1 -1
- package/dist/lib/image-utils.js +1 -2
- package/dist/lib/image-utils.js.map +1 -1
- package/dist/lib/infer-content-type.d.ts +0 -23
- package/dist/lib/infer-content-type.d.ts.map +1 -1
- package/dist/lib/infer-content-type.js +2 -56
- package/dist/lib/infer-content-type.js.map +1 -1
- package/dist/lib/read-media-meta.d.ts.map +1 -1
- package/dist/lib/read-media-meta.js +5 -15
- package/dist/lib/read-media-meta.js.map +1 -1
- package/dist/lib/validate-media-duration.d.ts +1 -0
- package/dist/lib/validate-media-duration.d.ts.map +1 -1
- package/dist/lib/validate-media-duration.js +11 -11
- package/dist/lib/validate-media-duration.js.map +1 -1
- package/dist/lib/validate-upload-kind.d.ts +0 -34
- package/dist/lib/validate-upload-kind.d.ts.map +1 -1
- package/dist/lib/validate-upload-kind.js +0 -17
- package/dist/lib/validate-upload-kind.js.map +1 -1
- package/dist/react/components/audio-input.d.ts +1 -1
- package/dist/react/components/audio-input.d.ts.map +1 -1
- package/dist/react/components/audio-input.js.map +1 -1
- package/dist/react/components/file-input.d.ts +22 -1
- package/dist/react/components/file-input.d.ts.map +1 -1
- package/dist/react/components/file-input.js +2 -6
- package/dist/react/components/file-input.js.map +1 -1
- package/dist/react/components/image-cropper-modal.d.ts +1 -2
- package/dist/react/components/image-cropper-modal.d.ts.map +1 -1
- package/dist/react/components/image-cropper-modal.js +1 -4
- package/dist/react/components/image-cropper-modal.js.map +1 -1
- package/dist/react/components/image-input.d.ts +1 -1
- package/dist/react/components/image-input.d.ts.map +1 -1
- package/dist/react/components/image-input.js.map +1 -1
- package/dist/react/components/media-constraints-hint.d.ts.map +1 -1
- package/dist/react/components/media-constraints-hint.js +0 -1
- package/dist/react/components/media-constraints-hint.js.map +1 -1
- package/dist/react/components/media-input.d.ts.map +1 -1
- package/dist/react/components/media-input.js +6 -73
- package/dist/react/components/media-input.js.map +1 -1
- package/dist/react/components/photo-capture-modal.d.ts.map +1 -1
- package/dist/react/components/photo-capture-modal.js +5 -8
- package/dist/react/components/photo-capture-modal.js.map +1 -1
- package/dist/react/components/record-dialog.d.ts +0 -4
- package/dist/react/components/record-dialog.d.ts.map +1 -1
- package/dist/react/components/record-dialog.js +35 -106
- package/dist/react/components/record-dialog.js.map +1 -1
- package/dist/react/components/video-input.d.ts +1 -1
- package/dist/react/components/video-input.d.ts.map +1 -1
- package/dist/react/components/video-input.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/types.d.ts +0 -75
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-input.js","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useId } from 'react';\nimport { getSimplifiedMediaType, type SimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport {\n Alert,\n AlertDescription,\n Button,\n Card,\n Input,\n Progress,\n Spinner,\n} from '@ttt-productions/ui-core/react';\nimport { cn } from '@ttt-productions/ui-core';\nimport { Info, X, Upload, Film, Music, Paperclip, AlertTriangle } from 'lucide-react';\n\nimport type { FileInputError, FileInputProps } from '../../types.js';\nimport { validateMediaDuration } from '../../lib/validate-media-duration.js';\nimport { ImageCropperModal } from './image-cropper-modal.js';\n\nconst MIME_MAP: Record<SimplifiedMediaType, string> = {\n image: 'image/jpeg, image/png, image/gif, image/webp, image/svg+xml, image/bmp, image/avif',\n video: 'video/*, .mkv',\n audio: 'audio/*',\n other: '.pdf, .doc, .docx',\n};\n\nfunction err(\n code: FileInputError['code'],\n message: string,\n details?: Record<string, unknown>,\n): FileInputError {\n return { code, message, details };\n}\n\nfunction getSelectedFileLabel(file: File): string {\n switch (getSimplifiedMediaType(file)) {\n case 'image':\n return 'Image selected';\n case 'video':\n return 'Video selected';\n case 'audio':\n return 'Audio selected';\n default:\n return 'File selected';\n }\n}\n\nexport function FileInput(props: FileInputProps) {\n const {\n acceptTypes,\n maxSizeMB,\n selectedFile,\n onChange,\n onError,\n isLoading = false,\n disabled = false,\n buttonLabel = 'Upload File',\n clearLabel = 'Clear selected file',\n className,\n uploadProgress = null,\n variant = 'default',\n size = 'default',\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n defaultShowDetails = false,\n } = props;\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n const lastPreviewUrlRef = useRef<string | null>(null);\n\n const revokeLastPreviewUrl = useCallback(() => {\n if (!lastPreviewUrlRef.current) return;\n try {\n URL.revokeObjectURL(lastPreviewUrlRef.current);\n } catch {}\n lastPreviewUrlRef.current = null;\n }, []);\n\n const makePreviewUrl = useCallback(\n (blob: Blob) => {\n revokeLastPreviewUrl();\n const url = URL.createObjectURL(blob);\n lastPreviewUrlRef.current = url;\n return url;\n },\n [revokeLastPreviewUrl],\n );\n\n useEffect(() => {\n return () => revokeLastPreviewUrl();\n }, [revokeLastPreviewUrl]);\n const uniqueId = useId();\n const inputId = `file-input-${uniqueId}`;\n\n const [internalSelected, setInternalSelected] = useState<File | null>(null);\n const fileValue = selectedFile !== undefined ? selectedFile : internalSelected;\n\n const [localError, setLocalError] = useState<FileInputError | null>(null);\n\n const [isCropperOpen, setIsCropperOpen] = useState(false);\n const [imageToCrop, setImageToCrop] = useState<string | null>(null);\n\n const [showDetails, setShowDetails] = useState(defaultShowDetails);\n\n const acceptString = useMemo(\n () => (acceptTypes.length ? acceptTypes.map((t) => MIME_MAP[t]).join(', ') : undefined),\n [acceptTypes],\n );\n\n const detailsDescriptionParts = useMemo(() => {\n const parts: string[] = [];\n\n if (cropConfig) {\n const ratioDisplay =\n cropConfig.aspectRatioDisplay || `${cropConfig.aspectRatio.toFixed(2)}:1`;\n parts.push(\n `Image will be cropped to a ${ratioDisplay} rect (${cropConfig.outputWidth}x${cropConfig.outputHeight}px).`,\n );\n }\n\n const sizeParts = acceptTypes\n .map((t) =>\n maxSizeMB[t] ? `${t.charAt(0).toUpperCase() + t.slice(1)} (${maxSizeMB[t]}MB)` : null,\n )\n .filter(Boolean) as string[];\n\n if (sizeParts.length) parts.push(`Max sizes: ${sizeParts.join(', ')}.`);\n\n if (videoMaxDurationSec) parts.push(`Max video duration: ${videoMaxDurationSec}s.`);\n if (audioMaxDurationSec) parts.push(`Max audio duration: ${audioMaxDurationSec}s.`);\n\n return parts;\n }, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec]);\n\n const getUploadIcon = () => {\n if (acceptTypes.includes('image')) return <Upload className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes('video')) return <Film className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes('audio')) return <Music className=\"mr-2 icon-xs\" />;\n return <Upload className=\"mr-2 icon-xs\" />;\n };\n\n const clearNative = () => {\n if (fileInputRef.current) fileInputRef.current.value = '';\n };\n\n const emitError = useCallback(\n (e: FileInputError, type: SimplifiedMediaType = 'other') => {\n setLocalError(e);\n onError?.(e);\n onChange({ type, error: e });\n },\n [onError, onChange],\n );\n\n const handleClear = (e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n e.preventDefault();\n setLocalError(null);\n setInternalSelected(null);\n clearNative();\n onChange({ type: 'other' });\n };\n\n const handleFileChange = useCallback(\n async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n clearNative();\n setLocalError(null);\n\n if (!file) {\n setInternalSelected(null);\n onChange({ type: 'other' });\n return;\n }\n\n const fileType = getSimplifiedMediaType(file);\n if (acceptTypes.length && !acceptTypes.includes(fileType)) {\n emitError(\n err('invalid_type', `Invalid file type. Allowed: ${acceptTypes.join(', ')}.`),\n fileType,\n );\n return;\n }\n\n const maxBytes = (maxSizeMB[fileType] ?? Infinity) * 1024 * 1024;\n if (file.size > maxBytes) {\n emitError(\n err('too_large', `File too large. Max ${maxSizeMB[fileType]}MB for ${fileType}.`, {\n size: file.size,\n maxBytes,\n }),\n fileType,\n );\n return;\n }\n\n if (fileType === 'video' && videoMaxDurationSec) {\n const ok = await validateMediaDuration(file, videoMaxDurationSec);\n if (!ok) {\n emitError(\n err('too_long', `Video too long. Max ${videoMaxDurationSec} seconds.`),\n fileType,\n );\n return;\n }\n }\n\n if (fileType === 'audio' && audioMaxDurationSec) {\n const ok = await validateMediaDuration(file, audioMaxDurationSec);\n if (!ok) {\n emitError(\n err('too_long', `Audio too long. Max ${audioMaxDurationSec} seconds.`),\n fileType,\n );\n return;\n }\n }\n\n // cropping path\n if (cropConfig && fileType === 'image') {\n const reader = new FileReader();\n reader.onload = () => {\n setImageToCrop(reader.result as string);\n setIsCropperOpen(true);\n };\n reader.onerror = () => {\n emitError(err('read_failed', 'Failed to read image for cropping.'));\n };\n reader.readAsDataURL(file);\n setInternalSelected(file);\n return;\n }\n\n // normal selection\n setInternalSelected(file);\n const previewUrl = makePreviewUrl(file);\n onChange({ type: fileType, file, previewUrl, revokePreviewUrl: revokeLastPreviewUrl });\n },\n [\n acceptTypes,\n maxSizeMB,\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n emitError,\n makePreviewUrl,\n onChange,\n revokeLastPreviewUrl,\n ],\n );\n\n const handleCropComplete = (blob: Blob | null) => {\n setIsCropperOpen(false);\n\n const original = internalSelected;\n setImageToCrop(null);\n\n if (!blob) {\n emitError(err('crop_failed', 'Cropping failed.'), 'image');\n return;\n }\n\n onChange({\n type: 'image',\n file: original ?? undefined,\n blob,\n previewUrl: makePreviewUrl(blob),\n revokePreviewUrl: revokeLastPreviewUrl,\n });\n };\n\n const isUploading = typeof uploadProgress === 'number' && isLoading;\n\n return (\n <div className={cn('relative w-full', className)}>\n {localError && (\n <Alert variant=\"destructive\" className=\"mb-2\">\n <AlertTriangle className=\"icon-xs\" />\n <AlertDescription>{localError.message}</AlertDescription>\n </Alert>\n )}\n\n <Input\n id={inputId}\n type=\"file\"\n accept={acceptString}\n onChange={handleFileChange}\n className=\"hidden\"\n disabled={disabled || isLoading}\n ref={fileInputRef}\n />\n\n <div className=\"flex items-center gap-2\">\n <label htmlFor={inputId} className=\"w-full flex-grow\">\n <Button\n asChild\n disabled={disabled || isLoading}\n variant={variant}\n size={size}\n className={cn('w-full relative overflow-hidden cursor-pointer', {\n 'icon-xl p-0': size === 'icon',\n })}\n >\n <span className=\"relative w-full h-full center-row\">\n {isUploading && (\n <Progress\n value={uploadProgress ?? 0}\n className=\"absolute left-0 top-0 h-full w-full z-0 bg-accent\"\n />\n )}\n\n <span className=\"z-10 center-row w-full\">\n {isLoading ? (\n <>\n <Spinner size=\"xs\" className=\"mr-2\" />{' '}\n {isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : 'Processing...'}\n </>\n ) : fileValue ? (\n <span className=\"flex items-center justify-between w-full\">\n <span className=\"truncate pr-2\">{getSelectedFileLabel(fileValue)}</span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"icon-sm shrink-0\"\n onClick={handleClear}\n aria-label={clearLabel}\n >\n <X className=\"icon-xs\" />\n </Button>\n </span>\n ) : (\n <>\n {buttonLabel === 'Attach' ? (\n <>\n <Paperclip className=\"mr-2 icon-xs\" /> {buttonLabel}\n </>\n ) : (\n <>\n {getUploadIcon()} {buttonLabel}\n </>\n )}\n </>\n )}\n </span>\n </span>\n </Button>\n </label>\n\n {detailsDescriptionParts.length > 0 && size !== 'icon' && (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"icon\"\n onClick={() => setShowDetails((p) => !p)}\n aria-label=\"Show file processing details\"\n className=\"flex-shrink-0 icon-xl\"\n >\n <Info className=\"icon-sm\" />\n </Button>\n )}\n </div>\n\n {showDetails && detailsDescriptionParts.length > 0 && size !== 'icon' && (\n <Card className=\"page-card w-full mt-2\">\n <div className=\"text-center p-2 space-y-0.5\">\n {detailsDescriptionParts.map((part, i) => (\n <p key={i} className=\"text-small font-bold\">\n {part}\n </p>\n ))}\n </div>\n </Card>\n )}\n\n {cropConfig && imageToCrop && (\n <ImageCropperModal\n isOpen={isCropperOpen}\n onClose={() => {\n setIsCropperOpen(false);\n setImageToCrop(null);\n }}\n imageSrc={imageToCrop}\n aspectRatio={cropConfig.aspectRatio}\n shape={cropConfig.shape}\n outputWidth={cropConfig.outputWidth}\n outputHeight={cropConfig.outputHeight}\n onCropComplete={handleCropComplete}\n />\n )}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"file-input.js","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useId } from 'react';\nimport { getSimplifiedMediaType, type SimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport {\n Alert,\n AlertDescription,\n Button,\n Card,\n Input,\n Progress,\n Spinner,\n type ButtonProps,\n} from '@ttt-productions/ui-core/react';\nimport { cn } from '@ttt-productions/ui-core';\nimport { Info, X, Upload, Film, Music, Paperclip, AlertTriangle } from 'lucide-react';\n\nimport type { CropConfig, FileInputChangePayload, FileInputError } from '../../types.js';\nimport { validateMediaDuration } from '../../lib/validate-media-duration.js';\nimport { ImageCropperModal } from './image-cropper-modal.js';\nimport { releaseObjectUrl } from '../../lib/validate-media-duration.js';\n\nconst MIME_MAP: Record<SimplifiedMediaType, string> = {\n image: 'image/jpeg, image/png, image/gif, image/webp, image/svg+xml, image/bmp, image/avif',\n video: 'video/*, .mkv',\n audio: 'audio/*',\n other: '.pdf, .doc, .docx',\n};\n\nfunction err(\n code: FileInputError['code'],\n message: string,\n details?: Record<string, unknown>,\n): FileInputError {\n return { code, message, details };\n}\n\nfunction getSelectedFileLabel(file: File): string {\n switch (getSimplifiedMediaType(file)) {\n case 'image':\n return 'Image selected';\n case 'video':\n return 'Video selected';\n case 'audio':\n return 'Audio selected';\n default:\n return 'File selected';\n }\n}\n\nexport interface FileInputProps {\n acceptTypes: SimplifiedMediaType[];\n maxSizeMB: Partial<Record<SimplifiedMediaType, number>>;\n selectedFile?: File | null;\n\n onChange: (payload: FileInputChangePayload) => void;\n onError?: (error: FileInputError) => void;\n\n disabled?: boolean;\n isLoading?: boolean;\n buttonLabel?: string;\n clearLabel?: string;\n className?: string;\n\n variant?: ButtonProps['variant'];\n size?: ButtonProps['size'];\n\n uploadProgress?: number | null;\n\n videoMaxDurationSec?: number;\n audioMaxDurationSec?: number;\n\n cropConfig?: CropConfig;\n defaultShowDetails?: boolean;\n}\n\nexport function FileInput(props: FileInputProps) {\n const {\n acceptTypes,\n maxSizeMB,\n selectedFile,\n onChange,\n onError,\n isLoading = false,\n disabled = false,\n buttonLabel = 'Upload File',\n clearLabel = 'Clear selected file',\n className,\n uploadProgress = null,\n variant = 'default',\n size = 'default',\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n defaultShowDetails = false,\n } = props;\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n const lastPreviewUrlRef = useRef<string | null>(null);\n\n const revokeLastPreviewUrl = useCallback(() => {\n if (!lastPreviewUrlRef.current) return;\n releaseObjectUrl(lastPreviewUrlRef.current);\n lastPreviewUrlRef.current = null;\n }, []);\n\n const makePreviewUrl = useCallback(\n (blob: Blob) => {\n revokeLastPreviewUrl();\n const url = URL.createObjectURL(blob);\n lastPreviewUrlRef.current = url;\n return url;\n },\n [revokeLastPreviewUrl],\n );\n\n useEffect(() => {\n return () => revokeLastPreviewUrl();\n }, [revokeLastPreviewUrl]);\n const uniqueId = useId();\n const inputId = `file-input-${uniqueId}`;\n\n const [internalSelected, setInternalSelected] = useState<File | null>(null);\n const fileValue = selectedFile !== undefined ? selectedFile : internalSelected;\n\n const [localError, setLocalError] = useState<FileInputError | null>(null);\n\n const [isCropperOpen, setIsCropperOpen] = useState(false);\n const [imageToCrop, setImageToCrop] = useState<string | null>(null);\n\n const [showDetails, setShowDetails] = useState(defaultShowDetails);\n\n const acceptString = useMemo(\n () => (acceptTypes.length ? acceptTypes.map((t) => MIME_MAP[t]).join(', ') : undefined),\n [acceptTypes],\n );\n\n const detailsDescriptionParts = useMemo(() => {\n const parts: string[] = [];\n\n if (cropConfig) {\n const ratioDisplay =\n cropConfig.aspectRatioDisplay || `${cropConfig.aspectRatio.toFixed(2)}:1`;\n parts.push(\n `Image will be cropped to a ${ratioDisplay} rect (${cropConfig.outputWidth}x${cropConfig.outputHeight}px).`,\n );\n }\n\n const sizeParts = acceptTypes\n .map((t) =>\n maxSizeMB[t] ? `${t.charAt(0).toUpperCase() + t.slice(1)} (${maxSizeMB[t]}MB)` : null,\n )\n .filter(Boolean) as string[];\n\n if (sizeParts.length) parts.push(`Max sizes: ${sizeParts.join(', ')}.`);\n\n if (videoMaxDurationSec) parts.push(`Max video duration: ${videoMaxDurationSec}s.`);\n if (audioMaxDurationSec) parts.push(`Max audio duration: ${audioMaxDurationSec}s.`);\n\n return parts;\n }, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec]);\n\n const getUploadIcon = () => {\n if (acceptTypes.includes('image')) return <Upload className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes('video')) return <Film className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes('audio')) return <Music className=\"mr-2 icon-xs\" />;\n return <Upload className=\"mr-2 icon-xs\" />;\n };\n\n const clearNative = () => {\n if (fileInputRef.current) fileInputRef.current.value = '';\n };\n\n const emitError = useCallback(\n (e: FileInputError, type: SimplifiedMediaType = 'other') => {\n setLocalError(e);\n onError?.(e);\n onChange({ type, error: e });\n },\n [onError, onChange],\n );\n\n const handleClear = (e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n e.preventDefault();\n setLocalError(null);\n setInternalSelected(null);\n clearNative();\n onChange({ type: 'other' });\n };\n\n const handleFileChange = useCallback(\n async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n clearNative();\n setLocalError(null);\n\n if (!file) {\n setInternalSelected(null);\n onChange({ type: 'other' });\n return;\n }\n\n const fileType = getSimplifiedMediaType(file);\n if (acceptTypes.length && !acceptTypes.includes(fileType)) {\n emitError(\n err('invalid_type', `Invalid file type. Allowed: ${acceptTypes.join(', ')}.`),\n fileType,\n );\n return;\n }\n\n const maxBytes = (maxSizeMB[fileType] ?? Infinity) * 1024 * 1024;\n if (file.size > maxBytes) {\n emitError(\n err('too_large', `File too large. Max ${maxSizeMB[fileType]}MB for ${fileType}.`, {\n size: file.size,\n maxBytes,\n }),\n fileType,\n );\n return;\n }\n\n if (fileType === 'video' && videoMaxDurationSec) {\n const ok = await validateMediaDuration(file, videoMaxDurationSec);\n if (!ok) {\n emitError(\n err('too_long', `Video too long. Max ${videoMaxDurationSec} seconds.`),\n fileType,\n );\n return;\n }\n }\n\n if (fileType === 'audio' && audioMaxDurationSec) {\n const ok = await validateMediaDuration(file, audioMaxDurationSec);\n if (!ok) {\n emitError(\n err('too_long', `Audio too long. Max ${audioMaxDurationSec} seconds.`),\n fileType,\n );\n return;\n }\n }\n\n if (cropConfig && fileType === 'image') {\n const reader = new FileReader();\n reader.onload = () => {\n setImageToCrop(reader.result as string);\n setIsCropperOpen(true);\n };\n reader.onerror = () => {\n emitError(err('read_failed', 'Failed to read image for cropping.'));\n };\n reader.readAsDataURL(file);\n setInternalSelected(file);\n return;\n }\n\n setInternalSelected(file);\n const previewUrl = makePreviewUrl(file);\n onChange({ type: fileType, file, previewUrl, revokePreviewUrl: revokeLastPreviewUrl });\n },\n [\n acceptTypes,\n maxSizeMB,\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n emitError,\n makePreviewUrl,\n onChange,\n revokeLastPreviewUrl,\n ],\n );\n\n const handleCropComplete = (blob: Blob | null) => {\n setIsCropperOpen(false);\n\n const original = internalSelected;\n setImageToCrop(null);\n\n if (!blob) {\n emitError(err('crop_failed', 'Cropping failed.'), 'image');\n return;\n }\n\n onChange({\n type: 'image',\n file: original ?? undefined,\n blob,\n previewUrl: makePreviewUrl(blob),\n revokePreviewUrl: revokeLastPreviewUrl,\n });\n };\n\n const isUploading = typeof uploadProgress === 'number' && isLoading;\n\n return (\n <div className={cn('relative w-full', className)}>\n {localError && (\n <Alert variant=\"destructive\" className=\"mb-2\">\n <AlertTriangle className=\"icon-xs\" />\n <AlertDescription>{localError.message}</AlertDescription>\n </Alert>\n )}\n\n <Input\n id={inputId}\n type=\"file\"\n accept={acceptString}\n onChange={handleFileChange}\n className=\"hidden\"\n disabled={disabled || isLoading}\n ref={fileInputRef}\n />\n\n <div className=\"flex items-center gap-2\">\n <label htmlFor={inputId} className=\"w-full flex-grow\">\n <Button\n asChild\n disabled={disabled || isLoading}\n variant={variant}\n size={size}\n className={cn('w-full relative overflow-hidden cursor-pointer', {\n 'icon-xl p-0': size === 'icon',\n })}\n >\n <span className=\"relative w-full h-full center-row\">\n {isUploading && (\n <Progress\n value={uploadProgress ?? 0}\n className=\"absolute left-0 top-0 h-full w-full z-0 bg-accent\"\n />\n )}\n\n <span className=\"z-10 center-row w-full\">\n {isLoading ? (\n <>\n <Spinner size=\"xs\" className=\"mr-2\" />{' '}\n {isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : 'Processing...'}\n </>\n ) : fileValue ? (\n <span className=\"flex items-center justify-between w-full\">\n <span className=\"truncate pr-2\">{getSelectedFileLabel(fileValue)}</span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"icon-sm shrink-0\"\n onClick={handleClear}\n aria-label={clearLabel}\n >\n <X className=\"icon-xs\" />\n </Button>\n </span>\n ) : (\n <>\n {buttonLabel === 'Attach' ? (\n <>\n <Paperclip className=\"mr-2 icon-xs\" /> {buttonLabel}\n </>\n ) : (\n <>\n {getUploadIcon()} {buttonLabel}\n </>\n )}\n </>\n )}\n </span>\n </span>\n </Button>\n </label>\n\n {detailsDescriptionParts.length > 0 && size !== 'icon' && (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"icon\"\n onClick={() => setShowDetails((p) => !p)}\n aria-label=\"Show file processing details\"\n className=\"flex-shrink-0 icon-xl\"\n >\n <Info className=\"icon-sm\" />\n </Button>\n )}\n </div>\n\n {showDetails && detailsDescriptionParts.length > 0 && size !== 'icon' && (\n <Card className=\"page-card w-full mt-2\">\n <div className=\"text-center p-2 space-y-0.5\">\n {detailsDescriptionParts.map((part, i) => (\n <p key={i} className=\"text-small font-bold\">\n {part}\n </p>\n ))}\n </div>\n </Card>\n )}\n\n {cropConfig && imageToCrop && (\n <ImageCropperModal\n isOpen={isCropperOpen}\n onClose={() => {\n setIsCropperOpen(false);\n setImageToCrop(null);\n }}\n imageSrc={imageToCrop}\n aspectRatio={cropConfig.aspectRatio}\n shape={cropConfig.shape}\n outputWidth={cropConfig.outputWidth}\n outputHeight={cropConfig.outputHeight}\n onCropComplete={handleCropComplete}\n />\n )}\n </div>\n );\n}\n"]}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import { type CropEncoding } from '../../lib/image-utils.js';
|
|
3
2
|
export interface ImageCropperModalProps {
|
|
4
3
|
isOpen: boolean;
|
|
@@ -11,5 +10,5 @@ export interface ImageCropperModalProps {
|
|
|
11
10
|
encoding?: CropEncoding;
|
|
12
11
|
onCropComplete: (croppedBlob: Blob | null) => void;
|
|
13
12
|
}
|
|
14
|
-
export declare function ImageCropperModal(props: ImageCropperModalProps):
|
|
13
|
+
export declare function ImageCropperModal(props: ImageCropperModalProps): import("react").JSX.Element | null;
|
|
15
14
|
//# sourceMappingURL=image-cropper-modal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-cropper-modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"image-cropper-modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE5E,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,cAAc,EAAE,CAAC,WAAW,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;CACpD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,sCA+F9D"}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useCallback } from 'react';
|
|
4
4
|
import Cropper from 'react-easy-crop';
|
|
5
|
-
const CropperComponent = Cropper;
|
|
6
5
|
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, Slider, useAsyncAction, } from '@ttt-productions/ui-core/react';
|
|
7
6
|
import { getCroppedImg } from '../../lib/image-utils.js';
|
|
8
7
|
export function ImageCropperModal(props) {
|
|
@@ -13,8 +12,6 @@ export function ImageCropperModal(props) {
|
|
|
13
12
|
const handleCropComplete = useCallback((_a, pixels) => {
|
|
14
13
|
setCroppedAreaPixels(pixels);
|
|
15
14
|
}, []);
|
|
16
|
-
// Confirm renders the crop, then emits it. It is pending for that whole window and
|
|
17
|
-
// ignores a repeat click, so a double click can never emit (and upload) twice.
|
|
18
15
|
const confirm = useAsyncAction(async () => {
|
|
19
16
|
if (!imageSrc || !croppedAreaPixels)
|
|
20
17
|
return;
|
|
@@ -24,6 +21,6 @@ export function ImageCropperModal(props) {
|
|
|
24
21
|
}, { onError: () => onCropComplete(null) });
|
|
25
22
|
if (!imageSrc)
|
|
26
23
|
return null;
|
|
27
|
-
return (_jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && !confirm.pending && onClose(), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Crop Image" }), _jsx(DialogDescription, { children: "Adjust the crop area and zoom level to select the desired portion of your image." })] }), _jsx("div", { className: "relative h-64 w-full bg-muted", children: _jsx(
|
|
24
|
+
return (_jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && !confirm.pending && onClose(), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Crop Image" }), _jsx(DialogDescription, { children: "Adjust the crop area and zoom level to select the desired portion of your image." })] }), _jsx("div", { className: "relative h-64 w-full bg-muted", children: _jsx(Cropper, { image: imageSrc, crop: crop, zoom: zoom, aspect: aspectRatio, cropShape: shape, showGrid: false, onCropChange: setCrop, onZoomChange: setZoom, onCropComplete: handleCropComplete }) }), _jsxs("div", { className: "stack-2", children: [_jsx("label", { htmlFor: "zoom", className: "text-small", children: "Zoom" }), _jsx(Slider, { id: "zoom", min: 1, max: 3, step: 0.1, value: [zoom], onValueChange: (v) => setZoom(v[0]) })] }), _jsxs(DialogFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onClose, disabled: confirm.pending, children: "Cancel" }), _jsx(Button, { type: "button", variant: "default", onClick: () => void confirm.run(), pending: confirm.pending, children: "Confirm" })] })] }) }));
|
|
28
25
|
}
|
|
29
26
|
//# sourceMappingURL=image-cropper-modal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-cropper-modal.js","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,
|
|
1
|
+
{"version":3,"file":"image-cropper-modal.js","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useCallback } from 'react';\nimport Cropper, { type Area } from 'react-easy-crop';\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n DialogDescription,\n Slider,\n useAsyncAction,\n} from '@ttt-productions/ui-core/react';\nimport { getCroppedImg, type CropEncoding } from '../../lib/image-utils.js';\n\nexport interface ImageCropperModalProps {\n isOpen: boolean;\n onClose: () => void;\n imageSrc: string | null;\n aspectRatio: number;\n shape: 'rect' | 'round';\n outputWidth: number;\n outputHeight: number;\n encoding?: CropEncoding;\n onCropComplete: (croppedBlob: Blob | null) => void;\n}\n\nexport function ImageCropperModal(props: ImageCropperModalProps) {\n const {\n isOpen,\n onClose,\n imageSrc,\n aspectRatio,\n shape,\n outputWidth,\n outputHeight,\n encoding,\n onCropComplete,\n } = props;\n\n const [crop, setCrop] = useState({ x: 0, y: 0 });\n const [zoom, setZoom] = useState(1);\n const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);\n\n const handleCropComplete = useCallback((_a: Area, pixels: Area) => {\n setCroppedAreaPixels(pixels);\n }, []);\n\n const confirm = useAsyncAction(\n async () => {\n if (!imageSrc || !croppedAreaPixels) return;\n const blob = await getCroppedImg(\n imageSrc,\n croppedAreaPixels,\n 0,\n { horizontal: false, vertical: false },\n outputWidth,\n outputHeight,\n encoding,\n );\n onCropComplete(blob);\n onClose();\n },\n { onError: () => onCropComplete(null) },\n );\n\n if (!imageSrc) return null;\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && !confirm.pending && onClose()}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Crop Image</DialogTitle>\n <DialogDescription>\n Adjust the crop area and zoom level to select the desired portion of your image.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"relative h-64 w-full bg-muted\">\n <Cropper\n image={imageSrc}\n crop={crop}\n zoom={zoom}\n aspect={aspectRatio}\n cropShape={shape}\n showGrid={false}\n onCropChange={setCrop}\n onZoomChange={setZoom}\n onCropComplete={handleCropComplete}\n />\n </div>\n\n <div className=\"stack-2\">\n <label htmlFor=\"zoom\" className=\"text-small\">\n Zoom\n </label>\n <Slider\n id=\"zoom\"\n min={1}\n max={3}\n step={0.1}\n value={[zoom]}\n onValueChange={(v) => setZoom(v[0])}\n />\n </div>\n\n <DialogFooter className=\"flex-row justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose} disabled={confirm.pending}>\n Cancel\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => void confirm.run()}\n pending={confirm.pending}\n >\n Confirm\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"image-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,+BAEpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-input.js","sourceRoot":"","sources":["../../../src/react/components/image-input.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,UAAU,CAAC,KAA0C;IACnE,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,GAAI,CAAC;AAC1D,CAAC","sourcesContent":["import type { FileInputProps } from '
|
|
1
|
+
{"version":3,"file":"image-input.js","sourceRoot":"","sources":["../../../src/react/components/image-input.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,UAAU,CAAC,KAA0C;IACnE,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,GAAI,CAAC;AAC1D,CAAC","sourcesContent":["import type { FileInputProps } from './file-input.js';\nimport { FileInput } from './file-input.js';\n\nexport function ImageInput(props: Omit<FileInputProps, 'acceptTypes'>) {\n return <FileInput {...props} acceptTypes={['image']} />;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-constraints-hint.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-constraints-hint.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"media-constraints-hint.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-constraints-hint.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,sCA4EpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-constraints-hint.js","sourceRoot":"","sources":["../../../src/react/components/media-constraints-hint.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAQ9C,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,EAAE,GAAG,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACjC,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC;IAC5C,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACrB,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9B,CAAC;AAED,SAAS,SAAS,CAAC,CAAU;IAC3B,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,
|
|
1
|
+
{"version":3,"file":"media-constraints-hint.js","sourceRoot":"","sources":["../../../src/react/components/media-constraints-hint.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport type { MediaOriginSpec } from '@ttt-productions/media-schemas';\nimport { Badge, Card } from '@ttt-productions/ui-core/react';\nimport { cn } from '@ttt-productions/ui-core';\n\nexport interface MediaConstraintsHintProps {\n spec: MediaOriginSpec;\n className?: string;\n title?: string;\n}\n\nfunction fmtBytes(bytes?: number) {\n if (!bytes || bytes <= 0) return null;\n const mb = bytes / (1024 * 1024);\n if (mb < 1024) return `${Math.round(mb)}MB`;\n const gb = mb / 1024;\n return `${gb.toFixed(1)}GB`;\n}\n\nfunction fmtAspect(x?: number) {\n if (!x) return null;\n const candidates: Array<[string, number]> = [\n ['1:1', 1],\n ['4:5', 4 / 5],\n ['3:4', 3 / 4],\n ['9:16', 9 / 16],\n ['16:9', 16 / 9],\n ];\n for (const [label, v] of candidates) {\n if (Math.abs(x - v) <= 0.02) return label;\n }\n return `${x.toFixed(3)}`;\n}\n\nexport function MediaConstraintsHint(props: MediaConstraintsHintProps) {\n const { spec, className, title = 'Upload requirements' } = props;\n\n const acceptKinds = spec.accept?.kinds?.length ? spec.accept.kinds.join(', ') : 'any';\n const acceptMimes = spec.accept?.mimes?.length ? spec.accept.mimes.join(', ') : 'any';\n\n const maxBytes = fmtBytes(spec.maxBytes);\n const maxDur =\n spec.maxDurationSec ??\n spec.processing?.video?.video?.maxDurationSec ??\n spec.processing?.audio?.audio?.maxDurationSec ??\n undefined;\n\n const requiredAspect = fmtAspect(spec.requiredAspectRatio ?? spec.imageCrop?.aspectRatio);\n const requiredDims = (() => {\n const procVid = spec.processing?.video;\n const w = procVid?.requiredWidth;\n const h = procVid?.requiredHeight;\n return w && h ? `${w}×${h}` : null;\n })();\n\n const videoOri =\n spec.videoOrientation && spec.videoOrientation !== 'any' ? spec.videoOrientation : null;\n\n const cropDims = spec.imageCrop\n ? `${spec.imageCrop.outputWidth}×${spec.imageCrop.outputHeight}`\n : null;\n\n const willAutoFormat = !!spec.processing?.video?.allowAutoFormat;\n\n const items: Array<{ k: string; v: string | null }> = [\n { k: 'Kind', v: acceptKinds },\n { k: 'Types', v: acceptMimes },\n { k: 'Max size', v: maxBytes },\n { k: 'Max length', v: maxDur ? `${maxDur}s` : null },\n { k: 'Orientation', v: videoOri },\n { k: 'Aspect', v: requiredAspect },\n { k: 'Exact size', v: requiredDims },\n { k: 'Crop output', v: cropDims },\n { k: 'Auto-format', v: willAutoFormat ? 'yes' : null },\n ].filter((x) => x.v);\n\n if (!items.length) return null;\n\n return (\n <Card className={cn('p-3', className)}>\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-sm font-medium\">{title}</div>\n {willAutoFormat ? <Badge variant=\"secondary\">Auto-format supported</Badge> : null}\n </div>\n\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {items.map((it) => (\n <Badge key={it.k} variant=\"outline\" className=\"gap-1\">\n <span className=\"text-muted-foreground\">{it.k}:</span>\n <span>{it.v}</span>\n </Badge>\n ))}\n </div>\n\n {spec.imageCrop ? (\n <div className=\"mt-2 text-xs text-muted-foreground\">\n Images will be cropped to {fmtAspect(spec.imageCrop.aspectRatio)} and exported as{' '}\n {(spec.imageCrop.format ?? 'jpeg').toUpperCase()}\n {spec.imageCrop.quality ? ` @ ${spec.imageCrop.quality}%` : ''}.\n </div>\n ) : null}\n\n {willAutoFormat ? (\n <div className=\"mt-2 text-xs text-muted-foreground\">\n If your video doesn't match the format, we'll ask before uploading and then auto-format it\n in processing.\n </div>\n ) : null}\n </Card>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KASN,MAAM,OAAO,CAAC;AA0Bf,OAAO,KAAK,EAEV,gBAAgB,EAChB,eAAe,EAEhB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"media-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KASN,MAAM,OAAO,CAAC;AA0Bf,OAAO,KAAK,EAEV,gBAAgB,EAChB,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AAwHxB,eAAO,MAAM,UAAU,0FA6nBtB,CAAC"}
|
|
@@ -15,8 +15,7 @@ import { MediaConstraintsHint } from './media-constraints-hint.js';
|
|
|
15
15
|
import { PhotoCaptureModal } from './photo-capture-modal.js';
|
|
16
16
|
import { readMediaMeta } from '../../lib/read-media-meta.js';
|
|
17
17
|
import { validateMediaDuration } from '../../lib/validate-media-duration.js';
|
|
18
|
-
|
|
19
|
-
* which appends). Generic + app-agnostic: uploaded files are not a backup/storage service. */
|
|
18
|
+
import { releaseObjectUrl } from '../../lib/validate-media-duration.js';
|
|
20
19
|
const DEFAULT_BACKUP_NOTE = "Keep your own copy of files you upload — this isn't a backup or storage service.";
|
|
21
20
|
function err(code, message, details) {
|
|
22
21
|
return { code, message, details };
|
|
@@ -55,11 +54,6 @@ function accepts(spec, file) {
|
|
|
55
54
|
const accept = spec.accept;
|
|
56
55
|
if (!accept)
|
|
57
56
|
return true;
|
|
58
|
-
// FAIL-OPEN on unknown browser metadata (canonical-upload-content-
|
|
59
|
-
// classification): an empty or neutral type is not evidence of anything —
|
|
60
|
-
// rejecting it here used to force MIME fabrication upstream. The file passes
|
|
61
|
-
// through and the SERVER's byte inspector decides. Only a DEFINITIVELY known
|
|
62
|
-
// wrong kind still rejects fast (better UX than a round trip).
|
|
63
57
|
if (!file.type || file.type === NEUTRAL_CONTENT_TYPE)
|
|
64
58
|
return true;
|
|
65
59
|
const kinds = accept.kinds?.filter(Boolean) ?? [];
|
|
@@ -71,7 +65,6 @@ function accepts(spec, file) {
|
|
|
71
65
|
const kindOk = kinds.length === 0 ? true : kinds.includes(kind);
|
|
72
66
|
if (!kindOk)
|
|
73
67
|
return false;
|
|
74
|
-
// If mimes list is empty, accept anything
|
|
75
68
|
if (mimes.length === 0)
|
|
76
69
|
return true;
|
|
77
70
|
return mimes.some((a) => matchMime(a, file.type));
|
|
@@ -85,7 +78,7 @@ function orientationOk(required, actual) {
|
|
|
85
78
|
if (!required || required === 'any')
|
|
86
79
|
return true;
|
|
87
80
|
if (!actual || actual === 'any')
|
|
88
|
-
return true;
|
|
81
|
+
return true;
|
|
89
82
|
return required === actual;
|
|
90
83
|
}
|
|
91
84
|
function aspectOk(required, actual) {
|
|
@@ -93,9 +86,8 @@ function aspectOk(required, actual) {
|
|
|
93
86
|
return true;
|
|
94
87
|
if (!actual)
|
|
95
88
|
return false;
|
|
96
|
-
return Math.abs(actual - required) <= 0.02;
|
|
89
|
+
return Math.abs(actual - required) <= 0.02;
|
|
97
90
|
}
|
|
98
|
-
/** Counts how many input methods are enabled. */
|
|
99
91
|
function countActions(spec) {
|
|
100
92
|
const c = spec.client;
|
|
101
93
|
let n = 0;
|
|
@@ -123,35 +115,24 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
123
115
|
const cropSpec = cropOverride ?? spec.imageCrop;
|
|
124
116
|
const [localError, setLocalError] = useState(null);
|
|
125
117
|
const [showInfo, setShowInfo] = useState(false);
|
|
126
|
-
// Every MediaInput carries the default backup advisory + any surface-specific notes, so the Info
|
|
127
|
-
// affordance always renders (constraints, if any, are shown under the notes).
|
|
128
118
|
const infoNotes = useMemo(() => [DEFAULT_BACKUP_NOTE, ...(helpNotes ?? [])], [helpNotes]);
|
|
129
119
|
const specHasConstraints = hasConstraints(spec);
|
|
130
120
|
const showInfoToggle = specHasConstraints || infoNotes.length > 0;
|
|
131
121
|
const [cropOpen, setCropOpen] = useState(false);
|
|
132
122
|
const [cropSrc, setCropSrc] = useState(null);
|
|
133
123
|
const [pendingCropFile, setPendingCropFile] = useState(null);
|
|
134
|
-
// The claim captured when the crop flow started — cropping/normalization
|
|
135
|
-
// preserves the ORIGINAL action claim (a captured photo stays camera-capture).
|
|
136
124
|
const [pendingCropClaim, setPendingCropClaim] = useState(null);
|
|
137
125
|
const [photoOpen, setPhotoOpen] = useState(false);
|
|
138
126
|
const [recordOpen, setRecordOpen] = useState(false);
|
|
139
127
|
const [recordKind, setRecordKind] = useState('video');
|
|
140
|
-
// Controlled state for the multi-action choice dropdown. Human clicks toggle it
|
|
141
|
-
// via the trigger button; the imperative `openSelection()` handle opens it when
|
|
142
|
-
// more than one action is enabled.
|
|
143
128
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
144
129
|
const [validating, setValidating] = useState(false);
|
|
145
130
|
const [emittedSize, setEmittedSize] = useState(null);
|
|
146
|
-
// Prevent memory leaks from object URLs
|
|
147
131
|
const lastObjectUrlRef = useRef(null);
|
|
148
132
|
const revokeLastObjectUrl = useCallback(() => {
|
|
149
133
|
if (!lastObjectUrlRef.current)
|
|
150
134
|
return;
|
|
151
|
-
|
|
152
|
-
URL.revokeObjectURL(lastObjectUrlRef.current);
|
|
153
|
-
}
|
|
154
|
-
catch { }
|
|
135
|
+
releaseObjectUrl(lastObjectUrlRef.current);
|
|
155
136
|
lastObjectUrlRef.current = null;
|
|
156
137
|
}, []);
|
|
157
138
|
const makeObjectUrl = useCallback((blob) => {
|
|
@@ -166,11 +147,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
166
147
|
};
|
|
167
148
|
}, [revokeLastObjectUrl]);
|
|
168
149
|
const acceptAttr = useMemo(() => {
|
|
169
|
-
// Explicit enabled-format registry projection FIRST (canonical-upload-
|
|
170
|
-
// content-classification): when the origin selects formats, the picker
|
|
171
|
-
// advertises exactly those (MIME aliases + extensions, kind-narrowed for
|
|
172
|
-
// shared A/V containers) instead of wildcards. Discoverability only — the
|
|
173
|
-
// server inspector remains the authority.
|
|
174
150
|
const formats = spec.accept?.formats?.filter(Boolean) ?? [];
|
|
175
151
|
if (formats.length) {
|
|
176
152
|
const tokens = projectAcceptTokens(formats, spec.accept?.kinds ?? undefined);
|
|
@@ -187,10 +163,8 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
187
163
|
return 'video/*';
|
|
188
164
|
if (k.includes('audio') && k.length === 1)
|
|
189
165
|
return 'audio/*';
|
|
190
|
-
return undefined;
|
|
166
|
+
return undefined;
|
|
191
167
|
}, [spec]);
|
|
192
|
-
// Simplified kind of the currently-selected file, used to size and type the
|
|
193
|
-
// inline preview. Null when nothing is selected.
|
|
194
168
|
const previewKind = useMemo(() => (selectedFile ? getSimplifiedMediaType(selectedFile) : null), [selectedFile]);
|
|
195
169
|
const selectedFileLabel = previewKind === 'image'
|
|
196
170
|
? 'Image selected'
|
|
@@ -200,7 +174,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
200
174
|
? 'Audio selected'
|
|
201
175
|
: 'File selected';
|
|
202
176
|
const emit = useCallback((payload) => {
|
|
203
|
-
// fallback resolution; pre-validation error path when kind is unknown or "file"
|
|
204
177
|
const rawKind = payload.meta?.kind;
|
|
205
178
|
const mediaKind = rawKind === 'image' || rawKind === 'video' || rawKind === 'audio' ? rawKind : 'image';
|
|
206
179
|
const resolvedSpec = resolveActiveSpec(spec, mediaKind);
|
|
@@ -214,10 +187,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
214
187
|
}, [emit]);
|
|
215
188
|
const validateAndEmit = useCallback(async (file, previewUrl, action = undefined) => {
|
|
216
189
|
setLocalError(null);
|
|
217
|
-
// Defense-in-depth: normalize the content type before any downstream
|
|
218
|
-
// handling. Recorder/capture files carry their declared kind; a picker
|
|
219
|
-
// file with unknown metadata passes through NEUTRALLY (never fabricated —
|
|
220
|
-
// the server inspector owns classification).
|
|
221
190
|
const declaredKind = action?.source === 'media-recorder' || action?.source === 'camera-capture'
|
|
222
191
|
? action.kind === 'image' || action.kind === 'video' || action.kind === 'audio'
|
|
223
192
|
? action.kind
|
|
@@ -229,18 +198,12 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
229
198
|
return;
|
|
230
199
|
}
|
|
231
200
|
const meta = await readMediaMeta(safeFile);
|
|
232
|
-
// The CLIENT CLAIM: what the user actually did. Recorder/capture claims
|
|
233
|
-
// are strong (the app requested that capture kind); a picker claim is an
|
|
234
|
-
// advisory browser-metadata inference — `file` when nothing narrower is
|
|
235
|
-
// known. Never derived from the (already-normalized) MIME for strong
|
|
236
|
-
// sources; for the picker the meta kind IS the inference.
|
|
237
201
|
const claim = action ?? {
|
|
238
202
|
source: 'file-picker',
|
|
239
203
|
kind: meta.kind === 'image' || meta.kind === 'video' || meta.kind === 'audio'
|
|
240
204
|
? meta.kind
|
|
241
205
|
: 'file',
|
|
242
206
|
};
|
|
243
|
-
// bytes
|
|
244
207
|
if (spec.maxBytes && safeFile.size > spec.maxBytes) {
|
|
245
208
|
fail(err('too_large', `File too large. Max ${Math.round(spec.maxBytes / 1024 / 1024)}MB.`, {
|
|
246
209
|
size: safeFile.size,
|
|
@@ -248,7 +211,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
248
211
|
}));
|
|
249
212
|
return;
|
|
250
213
|
}
|
|
251
|
-
// duration
|
|
252
214
|
const maxDur = spec.maxDurationSec ??
|
|
253
215
|
spec.processing?.video?.video?.maxDurationSec ??
|
|
254
216
|
spec.processing?.audio?.audio?.maxDurationSec;
|
|
@@ -259,7 +221,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
259
221
|
return;
|
|
260
222
|
}
|
|
261
223
|
}
|
|
262
|
-
// image crop enforcement
|
|
263
224
|
if (meta.kind === 'image' && cropSpec) {
|
|
264
225
|
const reader = new FileReader();
|
|
265
226
|
reader.onload = () => {
|
|
@@ -272,7 +233,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
272
233
|
reader.readAsDataURL(safeFile);
|
|
273
234
|
return;
|
|
274
235
|
}
|
|
275
|
-
// video uniform enforcement (orientation/aspect/dimensions)
|
|
276
236
|
if (meta.kind === 'video') {
|
|
277
237
|
const procVid = spec.processing?.video;
|
|
278
238
|
const requiredOri = spec.videoOrientation;
|
|
@@ -287,8 +247,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
287
247
|
const okDims = !requiredW || !requiredH ? true : meta.width === requiredW && meta.height === requiredH;
|
|
288
248
|
if (!okOri || !okAspect || !okDims) {
|
|
289
249
|
if (allowAutoFormat) {
|
|
290
|
-
// Auto-format is always silent — no confirmation prompt. The file is
|
|
291
|
-
// emitted with autoFormat:true and reformatted after upload.
|
|
292
250
|
emit({ file: safeFile, previewUrl, meta, claim, autoFormat: true });
|
|
293
251
|
return;
|
|
294
252
|
}
|
|
@@ -319,9 +277,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
319
277
|
}
|
|
320
278
|
emit({ file: safeFile, previewUrl, meta, claim });
|
|
321
279
|
}, [spec, cropSpec, emit, fail]);
|
|
322
|
-
// A picked, captured, or recorded file is validated (type, meta, duration,
|
|
323
|
-
// dimensions, crop) before it is emitted. The trigger shows pending for that
|
|
324
|
-
// whole window, so a second pick cannot race the first.
|
|
325
280
|
const handleSelected = useCallback(async (file, previewUrl, action = undefined) => {
|
|
326
281
|
setValidating(true);
|
|
327
282
|
try {
|
|
@@ -381,8 +336,7 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
381
336
|
const isPreparing = phase === 'preparing' && isLoading;
|
|
382
337
|
const isFinalizing = phase === 'finalizing' && isLoading;
|
|
383
338
|
const sizeThreshold = progressBarMinBytes ?? DEFAULT_PROGRESS_BAR_MIN_BYTES;
|
|
384
|
-
// Consumers usually
|
|
385
|
-
// the bar judges the size of the file this input emitted.
|
|
339
|
+
// Consumers usually clear selectedFile once it is handed off, so the bar sizes the file this input emitted.
|
|
386
340
|
const trackedSize = selectedFile?.size ?? emittedSize;
|
|
387
341
|
const showRealBar = isUploading &&
|
|
388
342
|
typeof percent === 'number' &&
|
|
@@ -393,14 +347,12 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
393
347
|
const canRecVideo = spec.client?.allowRecordVideo ?? true;
|
|
394
348
|
const canRecAudio = spec.client?.allowRecordAudio ?? true;
|
|
395
349
|
const actionCount = countActions(spec);
|
|
396
|
-
// Warn in dev if no actions are enabled
|
|
397
350
|
useEffect(() => {
|
|
398
351
|
if (actionCount === 0) {
|
|
399
352
|
console.warn('[MediaInput] No input methods enabled in spec.client. ' +
|
|
400
353
|
'At least one of allowPick, allowCapturePhoto, allowRecordVideo, or allowRecordAudio must be true.');
|
|
401
354
|
}
|
|
402
355
|
}, [actionCount]);
|
|
403
|
-
/** Execute an action directly (used for single-action shortcut). */
|
|
404
356
|
const executeAction = useCallback((action) => {
|
|
405
357
|
switch (action) {
|
|
406
358
|
case 'pick':
|
|
@@ -419,12 +371,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
419
371
|
break;
|
|
420
372
|
}
|
|
421
373
|
}, []);
|
|
422
|
-
/**
|
|
423
|
-
* Run the optional pre-select guard, then execute the action. When the guard
|
|
424
|
-
* resolves false, the picker/capture never opens. This is the single choke
|
|
425
|
-
* point for all four actions, so the gate fires on the click for every entry
|
|
426
|
-
* point (single-action button and dropdown items alike).
|
|
427
|
-
*/
|
|
428
374
|
const runAction = useCallback(async (action) => {
|
|
429
375
|
if (onBeforeSelect) {
|
|
430
376
|
const ok = await onBeforeSelect();
|
|
@@ -433,7 +379,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
433
379
|
}
|
|
434
380
|
executeAction(action);
|
|
435
381
|
}, [onBeforeSelect, executeAction]);
|
|
436
|
-
/** Build the list of enabled actions. */
|
|
437
382
|
const actions = useMemo(() => {
|
|
438
383
|
const list = [];
|
|
439
384
|
if (canPick)
|
|
@@ -454,7 +399,6 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
454
399
|
});
|
|
455
400
|
return list;
|
|
456
401
|
}, [canPick, canPhoto, canRecVideo, canRecAudio]);
|
|
457
|
-
/** Label & icon for the single trigger button. */
|
|
458
402
|
const triggerIcon = useMemo(() => {
|
|
459
403
|
if (actions.length === 1)
|
|
460
404
|
return actions[0].icon;
|
|
@@ -463,28 +407,17 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
|
|
|
463
407
|
return _jsx(Plus, { className: "icon-xs" });
|
|
464
408
|
}, [actions, buttonLabel]);
|
|
465
409
|
const triggerLabel = useMemo(() => {
|
|
466
|
-
// If the consumer explicitly set a custom buttonLabel, always respect it.
|
|
467
|
-
// Only use the action's built-in label for single-action when using the default.
|
|
468
410
|
if (buttonLabel !== 'Add media')
|
|
469
411
|
return buttonLabel;
|
|
470
412
|
if (actions.length === 1)
|
|
471
413
|
return actions[0].label;
|
|
472
414
|
return buttonLabel;
|
|
473
415
|
}, [actions, buttonLabel]);
|
|
474
|
-
/** When there's exactly 1 action, click goes straight to it. Otherwise open menu. */
|
|
475
416
|
const handleTriggerClick = useCallback(() => {
|
|
476
417
|
if (actions.length === 1) {
|
|
477
418
|
void runAction(actions[0].kind);
|
|
478
419
|
}
|
|
479
|
-
// If > 1 action, the DropdownMenuTrigger handles opening the menu.
|
|
480
420
|
}, [actions, runAction]);
|
|
481
|
-
/**
|
|
482
|
-
* Imperative entry point: activates the SAME trigger semantics as a human click
|
|
483
|
-
* (single action → run it through `runAction`, honoring `onBeforeSelect`/validation/
|
|
484
|
-
* crop; multiple actions → open the choice dropdown). Guards on disabled/loading and
|
|
485
|
-
* no-enabled-actions exactly as the rendered trigger's `disabled` attribute would.
|
|
486
|
-
* Never touches the hidden input directly, so none of the gates are bypassed.
|
|
487
|
-
*/
|
|
488
421
|
const openSelection = useCallback(() => {
|
|
489
422
|
if (disabled || isLoading || validating || actions.length === 0)
|
|
490
423
|
return;
|