@ttt-productions/file-input 0.11.14 → 0.11.16
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.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/image-utils.d.ts +5 -2
- package/dist/lib/image-utils.d.ts.map +1 -1
- package/dist/lib/image-utils.js +16 -9
- package/dist/lib/image-utils.js.map +1 -1
- package/dist/lib/infer-content-type.d.ts +1 -1
- package/dist/lib/infer-content-type.js +29 -29
- package/dist/lib/infer-content-type.js.map +1 -1
- package/dist/lib/read-media-meta.d.ts +1 -1
- package/dist/lib/read-media-meta.d.ts.map +1 -1
- package/dist/lib/read-media-meta.js +20 -13
- package/dist/lib/read-media-meta.js.map +1 -1
- package/dist/lib/validate-media-duration.js +3 -3
- package/dist/lib/validate-media-duration.js.map +1 -1
- package/dist/lib/validate-upload-kind.d.ts.map +1 -1
- package/dist/lib/validate-upload-kind.js.map +1 -1
- package/dist/progress-bar.d.ts +2 -0
- package/dist/progress-bar.d.ts.map +1 -0
- package/dist/progress-bar.js +2 -0
- package/dist/progress-bar.js.map +1 -0
- package/dist/react/components/audio-input.d.ts +2 -2
- package/dist/react/components/audio-input.js +2 -2
- package/dist/react/components/audio-input.js.map +1 -1
- package/dist/react/components/file-input.d.ts +1 -1
- package/dist/react/components/file-input.d.ts.map +1 -1
- package/dist/react/components/file-input.js +59 -42
- package/dist/react/components/file-input.js.map +1 -1
- package/dist/react/components/image-cropper-modal.d.ts +4 -2
- package/dist/react/components/image-cropper-modal.d.ts.map +1 -1
- package/dist/react/components/image-cropper-modal.js +7 -7
- package/dist/react/components/image-cropper-modal.js.map +1 -1
- package/dist/react/components/image-input.d.ts +2 -2
- package/dist/react/components/image-input.js +2 -2
- package/dist/react/components/image-input.js.map +1 -1
- package/dist/react/components/media-constraints-hint.d.ts +1 -1
- package/dist/react/components/media-constraints-hint.d.ts.map +1 -1
- package/dist/react/components/media-constraints-hint.js +26 -24
- package/dist/react/components/media-constraints-hint.js.map +1 -1
- package/dist/react/components/media-input.d.ts +2 -2
- package/dist/react/components/media-input.d.ts.map +1 -1
- package/dist/react/components/media-input.js +107 -89
- package/dist/react/components/media-input.js.map +1 -1
- package/dist/react/components/photo-capture-modal.d.ts +1 -1
- package/dist/react/components/photo-capture-modal.d.ts.map +1 -1
- package/dist/react/components/photo-capture-modal.js +29 -16
- package/dist/react/components/photo-capture-modal.js.map +1 -1
- package/dist/react/components/record-dialog.d.ts +3 -3
- package/dist/react/components/record-dialog.d.ts.map +1 -1
- package/dist/react/components/record-dialog.js +49 -50
- package/dist/react/components/record-dialog.js.map +1 -1
- package/dist/react/components/video-input.d.ts +2 -2
- package/dist/react/components/video-input.js +2 -2
- package/dist/react/components/video-input.js.map +1 -1
- package/dist/react/index.d.ts +8 -8
- package/dist/react/index.js +8 -8
- package/dist/react/index.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,31 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState, useId } from
|
|
4
|
-
import { getSimplifiedMediaType } from
|
|
5
|
-
import { Alert, AlertDescription, Button, Card, Input, Progress, Spinner } from
|
|
6
|
-
import { cn } from
|
|
7
|
-
import { Info, X, Upload, Film, Music, Paperclip, AlertTriangle } from
|
|
8
|
-
import { validateMediaDuration } from
|
|
9
|
-
import { ImageCropperModal } from
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useId } from 'react';
|
|
4
|
+
import { getSimplifiedMediaType } from '@ttt-productions/media-schemas';
|
|
5
|
+
import { Alert, AlertDescription, Button, Card, Input, Progress, Spinner, } from '@ttt-productions/ui-core/react';
|
|
6
|
+
import { cn } from '@ttt-productions/ui-core';
|
|
7
|
+
import { Info, X, Upload, Film, Music, Paperclip, AlertTriangle } from 'lucide-react';
|
|
8
|
+
import { validateMediaDuration } from '../../lib/validate-media-duration.js';
|
|
9
|
+
import { ImageCropperModal } from './image-cropper-modal.js';
|
|
10
10
|
const MIME_MAP = {
|
|
11
|
-
image:
|
|
12
|
-
video:
|
|
13
|
-
audio:
|
|
14
|
-
other:
|
|
11
|
+
image: 'image/jpeg, image/png, image/gif, image/webp, image/svg+xml, image/bmp, image/avif',
|
|
12
|
+
video: 'video/*, .mkv',
|
|
13
|
+
audio: 'audio/*',
|
|
14
|
+
other: '.pdf, .doc, .docx',
|
|
15
15
|
};
|
|
16
16
|
function err(code, message, details) {
|
|
17
17
|
return { code, message, details };
|
|
18
18
|
}
|
|
19
19
|
function getSelectedFileLabel(file) {
|
|
20
20
|
switch (getSimplifiedMediaType(file)) {
|
|
21
|
-
case
|
|
22
|
-
|
|
23
|
-
case
|
|
24
|
-
|
|
21
|
+
case 'image':
|
|
22
|
+
return 'Image selected';
|
|
23
|
+
case 'video':
|
|
24
|
+
return 'Video selected';
|
|
25
|
+
case 'audio':
|
|
26
|
+
return 'Audio selected';
|
|
27
|
+
default:
|
|
28
|
+
return 'File selected';
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
export function FileInput(props) {
|
|
28
|
-
const { acceptTypes, maxSizeMB, selectedFile, onChange, onError, isLoading = false, disabled = false, buttonLabel =
|
|
32
|
+
const { acceptTypes, maxSizeMB, selectedFile, onChange, onError, isLoading = false, disabled = false, buttonLabel = 'Upload File', clearLabel = 'Clear selected file', className, uploadProgress = null, variant = 'default', size = 'default', cropConfig, videoMaxDurationSec, audioMaxDurationSec, defaultShowDetails = false, } = props;
|
|
29
33
|
const fileInputRef = useRef(null);
|
|
30
34
|
const lastPreviewUrlRef = useRef(null);
|
|
31
35
|
const revokeLastPreviewUrl = useCallback(() => {
|
|
@@ -54,7 +58,7 @@ export function FileInput(props) {
|
|
|
54
58
|
const [isCropperOpen, setIsCropperOpen] = useState(false);
|
|
55
59
|
const [imageToCrop, setImageToCrop] = useState(null);
|
|
56
60
|
const [showDetails, setShowDetails] = useState(defaultShowDetails);
|
|
57
|
-
const acceptString = useMemo(() => (acceptTypes.length ? acceptTypes.map((t) => MIME_MAP[t]).join(
|
|
61
|
+
const acceptString = useMemo(() => (acceptTypes.length ? acceptTypes.map((t) => MIME_MAP[t]).join(', ') : undefined), [acceptTypes]);
|
|
58
62
|
const detailsDescriptionParts = useMemo(() => {
|
|
59
63
|
const parts = [];
|
|
60
64
|
if (cropConfig) {
|
|
@@ -62,10 +66,10 @@ export function FileInput(props) {
|
|
|
62
66
|
parts.push(`Image will be cropped to a ${ratioDisplay} rect (${cropConfig.outputWidth}x${cropConfig.outputHeight}px).`);
|
|
63
67
|
}
|
|
64
68
|
const sizeParts = acceptTypes
|
|
65
|
-
.map((t) =>
|
|
69
|
+
.map((t) => maxSizeMB[t] ? `${t.charAt(0).toUpperCase() + t.slice(1)} (${maxSizeMB[t]}MB)` : null)
|
|
66
70
|
.filter(Boolean);
|
|
67
71
|
if (sizeParts.length)
|
|
68
|
-
parts.push(`Max sizes: ${sizeParts.join(
|
|
72
|
+
parts.push(`Max sizes: ${sizeParts.join(', ')}.`);
|
|
69
73
|
if (videoMaxDurationSec)
|
|
70
74
|
parts.push(`Max video duration: ${videoMaxDurationSec}s.`);
|
|
71
75
|
if (audioMaxDurationSec)
|
|
@@ -73,19 +77,19 @@ export function FileInput(props) {
|
|
|
73
77
|
return parts;
|
|
74
78
|
}, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec]);
|
|
75
79
|
const getUploadIcon = () => {
|
|
76
|
-
if (acceptTypes.includes(
|
|
80
|
+
if (acceptTypes.includes('image'))
|
|
77
81
|
return _jsx(Upload, { className: "mr-2 icon-xs" });
|
|
78
|
-
if (acceptTypes.includes(
|
|
82
|
+
if (acceptTypes.includes('video'))
|
|
79
83
|
return _jsx(Film, { className: "mr-2 icon-xs" });
|
|
80
|
-
if (acceptTypes.includes(
|
|
84
|
+
if (acceptTypes.includes('audio'))
|
|
81
85
|
return _jsx(Music, { className: "mr-2 icon-xs" });
|
|
82
86
|
return _jsx(Upload, { className: "mr-2 icon-xs" });
|
|
83
87
|
};
|
|
84
88
|
const clearNative = () => {
|
|
85
89
|
if (fileInputRef.current)
|
|
86
|
-
fileInputRef.current.value =
|
|
90
|
+
fileInputRef.current.value = '';
|
|
87
91
|
};
|
|
88
|
-
const emitError = useCallback((e, type =
|
|
92
|
+
const emitError = useCallback((e, type = 'other') => {
|
|
89
93
|
setLocalError(e);
|
|
90
94
|
onError?.(e);
|
|
91
95
|
onChange({ type, error: e });
|
|
@@ -96,7 +100,7 @@ export function FileInput(props) {
|
|
|
96
100
|
setLocalError(null);
|
|
97
101
|
setInternalSelected(null);
|
|
98
102
|
clearNative();
|
|
99
|
-
onChange({ type:
|
|
103
|
+
onChange({ type: 'other' });
|
|
100
104
|
};
|
|
101
105
|
const handleFileChange = useCallback(async (event) => {
|
|
102
106
|
const file = event.target.files?.[0];
|
|
@@ -104,42 +108,45 @@ export function FileInput(props) {
|
|
|
104
108
|
setLocalError(null);
|
|
105
109
|
if (!file) {
|
|
106
110
|
setInternalSelected(null);
|
|
107
|
-
onChange({ type:
|
|
111
|
+
onChange({ type: 'other' });
|
|
108
112
|
return;
|
|
109
113
|
}
|
|
110
114
|
const fileType = getSimplifiedMediaType(file);
|
|
111
115
|
if (acceptTypes.length && !acceptTypes.includes(fileType)) {
|
|
112
|
-
emitError(err(
|
|
116
|
+
emitError(err('invalid_type', `Invalid file type. Allowed: ${acceptTypes.join(', ')}.`), fileType);
|
|
113
117
|
return;
|
|
114
118
|
}
|
|
115
119
|
const maxBytes = (maxSizeMB[fileType] ?? Infinity) * 1024 * 1024;
|
|
116
120
|
if (file.size > maxBytes) {
|
|
117
|
-
emitError(err(
|
|
121
|
+
emitError(err('too_large', `File too large. Max ${maxSizeMB[fileType]}MB for ${fileType}.`, {
|
|
122
|
+
size: file.size,
|
|
123
|
+
maxBytes,
|
|
124
|
+
}), fileType);
|
|
118
125
|
return;
|
|
119
126
|
}
|
|
120
|
-
if (fileType ===
|
|
127
|
+
if (fileType === 'video' && videoMaxDurationSec) {
|
|
121
128
|
const ok = await validateMediaDuration(file, videoMaxDurationSec);
|
|
122
129
|
if (!ok) {
|
|
123
|
-
emitError(err(
|
|
130
|
+
emitError(err('too_long', `Video too long. Max ${videoMaxDurationSec} seconds.`), fileType);
|
|
124
131
|
return;
|
|
125
132
|
}
|
|
126
133
|
}
|
|
127
|
-
if (fileType ===
|
|
134
|
+
if (fileType === 'audio' && audioMaxDurationSec) {
|
|
128
135
|
const ok = await validateMediaDuration(file, audioMaxDurationSec);
|
|
129
136
|
if (!ok) {
|
|
130
|
-
emitError(err(
|
|
137
|
+
emitError(err('too_long', `Audio too long. Max ${audioMaxDurationSec} seconds.`), fileType);
|
|
131
138
|
return;
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
// cropping path
|
|
135
|
-
if (cropConfig && fileType ===
|
|
142
|
+
if (cropConfig && fileType === 'image') {
|
|
136
143
|
const reader = new FileReader();
|
|
137
144
|
reader.onload = () => {
|
|
138
145
|
setImageToCrop(reader.result);
|
|
139
146
|
setIsCropperOpen(true);
|
|
140
147
|
};
|
|
141
148
|
reader.onerror = () => {
|
|
142
|
-
emitError(err(
|
|
149
|
+
emitError(err('read_failed', 'Failed to read image for cropping.'));
|
|
143
150
|
};
|
|
144
151
|
reader.readAsDataURL(file);
|
|
145
152
|
setInternalSelected(file);
|
|
@@ -149,27 +156,37 @@ export function FileInput(props) {
|
|
|
149
156
|
setInternalSelected(file);
|
|
150
157
|
const previewUrl = makePreviewUrl(file);
|
|
151
158
|
onChange({ type: fileType, file, previewUrl, revokePreviewUrl: revokeLastPreviewUrl });
|
|
152
|
-
}, [
|
|
159
|
+
}, [
|
|
160
|
+
acceptTypes,
|
|
161
|
+
maxSizeMB,
|
|
162
|
+
cropConfig,
|
|
163
|
+
videoMaxDurationSec,
|
|
164
|
+
audioMaxDurationSec,
|
|
165
|
+
emitError,
|
|
166
|
+
makePreviewUrl,
|
|
167
|
+
onChange,
|
|
168
|
+
revokeLastPreviewUrl,
|
|
169
|
+
]);
|
|
153
170
|
const handleCropComplete = (blob) => {
|
|
154
171
|
setIsCropperOpen(false);
|
|
155
172
|
const original = internalSelected;
|
|
156
173
|
setImageToCrop(null);
|
|
157
174
|
if (!blob) {
|
|
158
|
-
emitError(err(
|
|
175
|
+
emitError(err('crop_failed', 'Cropping failed.'), 'image');
|
|
159
176
|
return;
|
|
160
177
|
}
|
|
161
178
|
onChange({
|
|
162
|
-
type:
|
|
179
|
+
type: 'image',
|
|
163
180
|
file: original ?? undefined,
|
|
164
181
|
blob,
|
|
165
182
|
previewUrl: makePreviewUrl(blob),
|
|
166
183
|
revokePreviewUrl: revokeLastPreviewUrl,
|
|
167
184
|
});
|
|
168
185
|
};
|
|
169
|
-
const isUploading = typeof uploadProgress ===
|
|
170
|
-
return (_jsxs("div", { className: cn(
|
|
171
|
-
|
|
172
|
-
|
|
186
|
+
const isUploading = typeof uploadProgress === 'number' && isLoading;
|
|
187
|
+
return (_jsxs("div", { className: cn('relative w-full', className), children: [localError && (_jsxs(Alert, { variant: "destructive", className: "mb-2", children: [_jsx(AlertTriangle, { className: "icon-xs" }), _jsx(AlertDescription, { children: localError.message })] })), _jsx(Input, { id: inputId, type: "file", accept: acceptString, onChange: handleFileChange, className: "hidden", disabled: disabled || isLoading, ref: fileInputRef }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { htmlFor: inputId, className: "w-full flex-grow", children: _jsx(Button, { asChild: true, disabled: disabled || isLoading, variant: variant, size: size, className: cn('w-full relative overflow-hidden cursor-pointer', {
|
|
188
|
+
'icon-xl p-0': size === 'icon',
|
|
189
|
+
}), children: _jsxs("span", { className: "relative w-full h-full center-row", children: [isUploading && (_jsx(Progress, { value: uploadProgress ?? 0, className: "absolute left-0 top-0 h-full w-full z-0 bg-accent" })), _jsx("span", { className: "z-10 center-row w-full", children: isLoading ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { size: "xs", className: "mr-2" }), ' ', isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : 'Processing...'] })) : fileValue ? (_jsxs("span", { className: "flex items-center justify-between w-full", children: [_jsx("span", { className: "truncate pr-2", children: getSelectedFileLabel(fileValue) }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "icon-sm shrink-0", onClick: handleClear, "aria-label": clearLabel, children: _jsx(X, { className: "icon-xs" }) })] })) : (_jsx(_Fragment, { children: buttonLabel === 'Attach' ? (_jsxs(_Fragment, { children: [_jsx(Paperclip, { className: "mr-2 icon-xs" }), " ", buttonLabel] })) : (_jsxs(_Fragment, { children: [getUploadIcon(), " ", buttonLabel] })) })) })] }) }) }), detailsDescriptionParts.length > 0 && size !== 'icon' && (_jsx(Button, { type: "button", variant: "default", size: "icon", onClick: () => setShowDetails((p) => !p), "aria-label": "Show file processing details", className: "flex-shrink-0 icon-xl", children: _jsx(Info, { className: "icon-sm" }) }))] }), showDetails && detailsDescriptionParts.length > 0 && size !== 'icon' && (_jsx(Card, { className: "page-card w-full mt-2", children: _jsx("div", { className: "text-center p-2 space-y-0.5", children: detailsDescriptionParts.map((part, i) => (_jsx("p", { className: "text-small font-bold", children: part }, i))) }) })), cropConfig && imageToCrop && (_jsx(ImageCropperModal, { isOpen: isCropperOpen, onClose: () => {
|
|
173
190
|
setIsCropperOpen(false);
|
|
174
191
|
setImageToCrop(null);
|
|
175
192
|
}, imageSrc: imageToCrop, aspectRatio: cropConfig.aspectRatio, shape: cropConfig.shape, outputWidth: cropConfig.outputWidth, outputHeight: cropConfig.outputHeight, onCropComplete: handleCropComplete }))] }));
|
|
@@ -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 { Alert, AlertDescription, Button, Card, Input, Progress, Spinner } 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(code: FileInputError[\"code\"], message: string, details?: Record<string, unknown>): FileInputError {\n return { code, message, details };\n}\n\nfunction getSelectedFileLabel(file: File): string {\n switch (getSimplifiedMediaType(file)) {\n case \"image\": return \"Image selected\";\n case \"video\": return \"Video selected\";\n case \"audio\": return \"Audio selected\";\n default: 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((blob: Blob) => {\n revokeLastPreviewUrl();\n const url = URL.createObjectURL(blob);\n lastPreviewUrlRef.current = url;\n return url;\n }, [revokeLastPreviewUrl]);\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 = cropConfig.aspectRatioDisplay || `${cropConfig.aspectRatio.toFixed(2)}:1`;\n parts.push(`Image will be cropped to a ${ratioDisplay} rect (${cropConfig.outputWidth}x${cropConfig.outputHeight}px).`);\n }\n\n const sizeParts = acceptTypes\n .map((t) => (maxSizeMB[t] ? `${t.charAt(0).toUpperCase() + t.slice(1)} (${maxSizeMB[t]}MB)` : null))\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((e: FileInputError, type: SimplifiedMediaType = \"other\") => {\n setLocalError(e);\n onError?.(e);\n onChange({ type, error: e });\n }, [onError, onChange]);\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(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(err(\"invalid_type\", `Invalid file type. Allowed: ${acceptTypes.join(\", \")}.`), fileType);\n return;\n }\n\n const maxBytes = (maxSizeMB[fileType] ?? Infinity) * 1024 * 1024;\n if (file.size > maxBytes) {\n emitError(err(\"too_large\", `File too large. Max ${maxSizeMB[fileType]}MB for ${fileType}.`, { size: file.size, maxBytes }), fileType);\n return;\n }\n\n if (fileType === \"video\" && videoMaxDurationSec) {\n const ok = await validateMediaDuration(file, videoMaxDurationSec);\n if (!ok) {\n emitError(err(\"too_long\", `Video too long. Max ${videoMaxDurationSec} seconds.`), fileType);\n return;\n }\n }\n\n if (fileType === \"audio\" && audioMaxDurationSec) {\n const ok = await validateMediaDuration(file, audioMaxDurationSec);\n if (!ok) {\n emitError(err(\"too_long\", `Audio too long. Max ${audioMaxDurationSec} seconds.`), fileType);\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 }, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec, emitError, makePreviewUrl, onChange, revokeLastPreviewUrl]);\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\", { \"icon-xl p-0\": size === \"icon\" })}\n >\n <span className=\"relative w-full h-full center-row\">\n {isUploading && (\n <Progress value={uploadProgress ?? 0} className=\"absolute left-0 top-0 h-full w-full z-0 bg-accent\" />\n )}\n\n <span className=\"z-10 center-row w-full\">\n {isLoading ? (\n <><Spinner size=\"xs\" className=\"mr-2\" /> {isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : \"Processing...\"}</>\n ) : fileValue ? (\n <span className=\"flex items-center justify-between w-full\">\n <span className=\"truncate pr-2\">{getSelectedFileLabel(fileValue)}</span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" className=\"icon-sm shrink-0\" onClick={handleClear} aria-label={clearLabel}>\n <X className=\"icon-xs\" />\n </Button>\n </span>\n ) : (\n <>\n {buttonLabel === \"Attach\"\n ? <><Paperclip className=\"mr-2 icon-xs\" /> {buttonLabel}</>\n : <>{getUploadIcon()} {buttonLabel}</>}\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\">{part}</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} 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,12 +1,14 @@
|
|
|
1
|
-
import React from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type CropEncoding } from '../../lib/image-utils.js';
|
|
2
3
|
export interface ImageCropperModalProps {
|
|
3
4
|
isOpen: boolean;
|
|
4
5
|
onClose: () => void;
|
|
5
6
|
imageSrc: string | null;
|
|
6
7
|
aspectRatio: number;
|
|
7
|
-
shape:
|
|
8
|
+
shape: 'rect' | 'round';
|
|
8
9
|
outputWidth: number;
|
|
9
10
|
outputHeight: number;
|
|
11
|
+
encoding?: CropEncoding;
|
|
10
12
|
onCropComplete: (croppedBlob: Blob | null) => void;
|
|
11
13
|
}
|
|
12
14
|
export declare function ImageCropperModal(props: ImageCropperModalProps): React.JSX.Element | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-cropper-modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAgC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"image-cropper-modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAerD,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,4BAiG9D"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useCallback } from
|
|
4
|
-
import Cropper from
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
import Cropper from 'react-easy-crop';
|
|
5
5
|
const CropperComponent = Cropper;
|
|
6
|
-
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, Slider, useAsyncAction } from
|
|
7
|
-
import { getCroppedImg } from
|
|
6
|
+
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, Slider, useAsyncAction, } from '@ttt-productions/ui-core/react';
|
|
7
|
+
import { getCroppedImg } from '../../lib/image-utils.js';
|
|
8
8
|
export function ImageCropperModal(props) {
|
|
9
|
-
const { isOpen, onClose, imageSrc, aspectRatio, shape, outputWidth, outputHeight, onCropComplete } = props;
|
|
9
|
+
const { isOpen, onClose, imageSrc, aspectRatio, shape, outputWidth, outputHeight, encoding, onCropComplete, } = props;
|
|
10
10
|
const [crop, setCrop] = useState({ x: 0, y: 0 });
|
|
11
11
|
const [zoom, setZoom] = useState(1);
|
|
12
12
|
const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
|
|
@@ -18,7 +18,7 @@ export function ImageCropperModal(props) {
|
|
|
18
18
|
const confirm = useAsyncAction(async () => {
|
|
19
19
|
if (!imageSrc || !croppedAreaPixels)
|
|
20
20
|
return;
|
|
21
|
-
const blob = await getCroppedImg(imageSrc, croppedAreaPixels, 0, { horizontal: false, vertical: false }, outputWidth, outputHeight);
|
|
21
|
+
const blob = await getCroppedImg(imageSrc, croppedAreaPixels, 0, { horizontal: false, vertical: false }, outputWidth, outputHeight, encoding);
|
|
22
22
|
onCropComplete(blob);
|
|
23
23
|
onClose();
|
|
24
24
|
}, { onError: () => onCropComplete(null) });
|
|
@@ -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,OAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,OAAsB,MAAM,iBAAiB,CAAC;AAErD,MAAM,gBAAgB,GAAG,OAA8C,CAAC;AACxE,OAAO,
|
|
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 React, { useState, useCallback } from 'react';\nimport Cropper, { type Area } from 'react-easy-crop';\n\nconst CropperComponent = Cropper as unknown as React.ComponentType<any>;\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 // Confirm renders the crop, then emits it. It is pending for that whole window and\n // ignores a repeat click, so a double click can never emit (and upload) twice.\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 <CropperComponent\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,3 +1,3 @@
|
|
|
1
|
-
import type { FileInputProps } from
|
|
2
|
-
export declare function ImageInput(props: Omit<FileInputProps,
|
|
1
|
+
import type { FileInputProps } from '../../types.js';
|
|
2
|
+
export declare function ImageInput(props: Omit<FileInputProps, 'acceptTypes'>): import("react").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=image-input.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { FileInput } from
|
|
2
|
+
import { FileInput } from './file-input.js';
|
|
3
3
|
export function ImageInput(props) {
|
|
4
|
-
return _jsx(FileInput, { ...props, acceptTypes: [
|
|
4
|
+
return _jsx(FileInput, { ...props, acceptTypes: ['image'] });
|
|
5
5
|
}
|
|
6
6
|
//# sourceMappingURL=image-input.js.map
|
|
@@ -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 '../../types.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;AA0BD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,
|
|
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;AA0BD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,sCA4EpE"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Badge, Card } from
|
|
4
|
-
import { cn } from
|
|
3
|
+
import { Badge, Card } from '@ttt-productions/ui-core/react';
|
|
4
|
+
import { cn } from '@ttt-productions/ui-core';
|
|
5
5
|
function fmtBytes(bytes) {
|
|
6
6
|
if (!bytes || bytes <= 0)
|
|
7
7
|
return null;
|
|
@@ -16,11 +16,11 @@ function fmtAspect(x) {
|
|
|
16
16
|
return null;
|
|
17
17
|
// show common approx ratios if close
|
|
18
18
|
const candidates = [
|
|
19
|
-
[
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
19
|
+
['1:1', 1],
|
|
20
|
+
['4:5', 4 / 5],
|
|
21
|
+
['3:4', 3 / 4],
|
|
22
|
+
['9:16', 9 / 16],
|
|
23
|
+
['16:9', 16 / 9],
|
|
24
24
|
];
|
|
25
25
|
for (const [label, v] of candidates) {
|
|
26
26
|
if (Math.abs(x - v) <= 0.02)
|
|
@@ -29,9 +29,9 @@ function fmtAspect(x) {
|
|
|
29
29
|
return `${x.toFixed(3)}`;
|
|
30
30
|
}
|
|
31
31
|
export function MediaConstraintsHint(props) {
|
|
32
|
-
const { spec, className, title =
|
|
33
|
-
const acceptKinds = spec.accept?.kinds?.length ? spec.accept.kinds.join(
|
|
34
|
-
const acceptMimes = spec.accept?.mimes?.length ? spec.accept.mimes.join(
|
|
32
|
+
const { spec, className, title = 'Upload requirements' } = props;
|
|
33
|
+
const acceptKinds = spec.accept?.kinds?.length ? spec.accept.kinds.join(', ') : 'any';
|
|
34
|
+
const acceptMimes = spec.accept?.mimes?.length ? spec.accept.mimes.join(', ') : 'any';
|
|
35
35
|
const maxBytes = fmtBytes(spec.maxBytes);
|
|
36
36
|
const maxDur = spec.maxDurationSec ??
|
|
37
37
|
spec.processing?.video?.video?.maxDurationSec ??
|
|
@@ -44,22 +44,24 @@ export function MediaConstraintsHint(props) {
|
|
|
44
44
|
const h = procVid?.requiredHeight;
|
|
45
45
|
return w && h ? `${w}×${h}` : null;
|
|
46
46
|
})();
|
|
47
|
-
const videoOri = spec.videoOrientation && spec.videoOrientation !==
|
|
48
|
-
const cropDims = spec.imageCrop
|
|
49
|
-
|
|
47
|
+
const videoOri = spec.videoOrientation && spec.videoOrientation !== 'any' ? spec.videoOrientation : null;
|
|
48
|
+
const cropDims = spec.imageCrop
|
|
49
|
+
? `${spec.imageCrop.outputWidth}×${spec.imageCrop.outputHeight}`
|
|
50
|
+
: null;
|
|
51
|
+
const willAutoFormat = !!spec.processing?.video?.allowAutoFormat;
|
|
50
52
|
const items = [
|
|
51
|
-
{ k:
|
|
52
|
-
{ k:
|
|
53
|
-
{ k:
|
|
54
|
-
{ k:
|
|
55
|
-
{ k:
|
|
56
|
-
{ k:
|
|
57
|
-
{ k:
|
|
58
|
-
{ k:
|
|
59
|
-
{ k:
|
|
53
|
+
{ k: 'Kind', v: acceptKinds },
|
|
54
|
+
{ k: 'Types', v: acceptMimes },
|
|
55
|
+
{ k: 'Max size', v: maxBytes },
|
|
56
|
+
{ k: 'Max length', v: maxDur ? `${maxDur}s` : null },
|
|
57
|
+
{ k: 'Orientation', v: videoOri },
|
|
58
|
+
{ k: 'Aspect', v: requiredAspect },
|
|
59
|
+
{ k: 'Exact size', v: requiredDims },
|
|
60
|
+
{ k: 'Crop output', v: cropDims },
|
|
61
|
+
{ k: 'Auto-format', v: willAutoFormat ? 'yes' : null },
|
|
60
62
|
].filter((x) => x.v);
|
|
61
63
|
if (!items.length)
|
|
62
64
|
return null;
|
|
63
|
-
return (_jsxs(Card, { className: cn(
|
|
65
|
+
return (_jsxs(Card, { className: cn('p-3', className), children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("div", { className: "text-sm font-medium", children: title }), willAutoFormat ? _jsx(Badge, { variant: "secondary", children: "Auto-format supported" }) : null] }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: items.map((it) => (_jsxs(Badge, { variant: "outline", className: "gap-1", children: [_jsxs("span", { className: "text-muted-foreground", children: [it.k, ":"] }), _jsx("span", { children: it.v })] }, it.k))) }), spec.imageCrop ? (_jsxs("div", { className: "mt-2 text-xs text-muted-foreground", children: ["Images will be cropped to ", fmtAspect(spec.imageCrop.aspectRatio), " and exported as", ' ', (spec.imageCrop.format ?? 'jpeg').toUpperCase(), spec.imageCrop.quality ? ` @ ${spec.imageCrop.quality}%` : '', "."] })) : null, willAutoFormat ? (_jsx("div", { className: "mt-2 text-xs text-muted-foreground", children: "If your video doesn't match the format, we'll ask before uploading and then auto-format it in processing." })) : null] }));
|
|
64
66
|
}
|
|
65
67
|
//# sourceMappingURL=media-constraints-hint.js.map
|
|
@@ -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,qCAAqC;IACrC,MAAM,UAAU,GAA4B;QAC1C,CAAC,KAAK,EAAE,CAAC,CAAC;QACV,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC;QAChB,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC;KACjB,CAAC;IACF,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,UAAU,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI;YAAE,OAAO,KAAK,CAAC;IAC5C,CAAC;IACD,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAgC;IACnE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,GAAG,qBAAqB,EAAE,GAAG,KAAK,CAAC;IAEjE,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACtF,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAEtF,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,MAAM,GACV,IAAI,CAAC,cAAc;QACnB,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc;QAC7C,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc;QAC7C,SAAS,CAAC;IAEZ,MAAM,cAAc,GAAG,SAAS,CAAC,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAC1F,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC;QACvC,MAAM,CAAC,GAAG,OAAO,EAAE,aAAa,CAAC;QACjC,MAAM,CAAC,GAAG,OAAO,EAAE,cAAc,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACrC,CAAC,CAAC,EAAE,CAAC;IAEL,MAAM,QAAQ,
|
|
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,qCAAqC;IACrC,MAAM,UAAU,GAA4B;QAC1C,CAAC,KAAK,EAAE,CAAC,CAAC;QACV,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC;QAChB,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC;KACjB,CAAC;IACF,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,UAAU,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI;YAAE,OAAO,KAAK,CAAC;IAC5C,CAAC;IACD,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAgC;IACnE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,GAAG,qBAAqB,EAAE,GAAG,KAAK,CAAC;IAEjE,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACtF,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAEtF,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,MAAM,GACV,IAAI,CAAC,cAAc;QACnB,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc;QAC7C,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc;QAC7C,SAAS,CAAC;IAEZ,MAAM,cAAc,GAAG,SAAS,CAAC,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAC1F,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC;QACvC,MAAM,CAAC,GAAG,OAAO,EAAE,aAAa,CAAC;QACjC,MAAM,CAAC,GAAG,OAAO,EAAE,cAAc,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACrC,CAAC,CAAC,EAAE,CAAC;IAEL,MAAM,QAAQ,GACZ,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,gBAAgB,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC;IAE1F,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS;QAC7B,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE;QAChE,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,eAAe,CAAC;IAEjE,MAAM,KAAK,GAA2C;QACpD,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE;QAC7B,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE;QAC9B,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE;QAC9B,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE;QACpD,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE;QACjC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,cAAc,EAAE;QAClC,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE;QACpC,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE;QACjC,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;KACvD,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAErB,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAE/B,OAAO,CACL,MAAC,IAAI,IAAC,SAAS,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,aACnC,eAAK,SAAS,EAAC,yCAAyC,aACtD,cAAK,SAAS,EAAC,qBAAqB,YAAE,KAAK,GAAO,EACjD,cAAc,CAAC,CAAC,CAAC,KAAC,KAAK,IAAC,OAAO,EAAC,WAAW,sCAA8B,CAAC,CAAC,CAAC,IAAI,IAC7E,EAEN,cAAK,SAAS,EAAC,2BAA2B,YACvC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CACjB,MAAC,KAAK,IAAY,OAAO,EAAC,SAAS,EAAC,SAAS,EAAC,OAAO,aACnD,gBAAM,SAAS,EAAC,uBAAuB,aAAE,EAAE,CAAC,CAAC,SAAS,EACtD,yBAAO,EAAE,CAAC,CAAC,GAAQ,KAFT,EAAE,CAAC,CAAC,CAGR,CACT,CAAC,GACE,EAEL,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAChB,eAAK,SAAS,EAAC,oCAAoC,2CACtB,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,sBAAkB,GAAG,EACpF,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,EAC/C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,SAC1D,CACP,CAAC,CAAC,CAAC,IAAI,EAEP,cAAc,CAAC,CAAC,CAAC,CAChB,cAAK,SAAS,EAAC,oCAAoC,0HAG7C,CACP,CAAC,CAAC,CAAC,IAAI,IACH,CACR,CAAC;AACJ,CAAC","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 // show common approx ratios if close\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,4 +1,4 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import type { MediaInputHandle, MediaInputProps } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { MediaInputHandle, MediaInputProps } from '../../types.js';
|
|
3
3
|
export declare const MediaInput: React.ForwardRefExoticComponent<MediaInputProps & React.RefAttributes<MediaInputHandle>>;
|
|
4
4
|
//# sourceMappingURL=media-input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
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;AAgIxB,eAAO,MAAM,UAAU,0FA+rBtB,CAAC"}
|