@page-speed/forms 0.1.4 → 0.1.6
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/README.md +1 -1
- package/dist/core.cjs +376 -21
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +356 -1
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +376 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +356 -1
- package/dist/index.js.map +1 -1
- package/dist/inputs.cjs +253 -0
- package/dist/inputs.cjs.map +1 -1
- package/dist/inputs.d.cts +77 -1
- package/dist/inputs.d.ts +77 -1
- package/dist/inputs.js +253 -1
- package/dist/inputs.js.map +1 -1
- package/dist/integration.cjs +243 -0
- package/dist/integration.cjs.map +1 -0
- package/dist/integration.d.cts +381 -0
- package/dist/integration.d.ts +381 -0
- package/dist/integration.js +217 -0
- package/dist/integration.js.map +1 -0
- package/dist/upload.cjs +348 -0
- package/dist/upload.cjs.map +1 -0
- package/dist/upload.d.cts +174 -0
- package/dist/upload.d.ts +174 -0
- package/dist/upload.js +326 -0
- package/dist/upload.js.map +1 -0
- package/dist/validation-rules.cjs +231 -75
- package/dist/validation-rules.cjs.map +1 -1
- package/dist/validation-rules.js +215 -1
- package/dist/validation-rules.js.map +1 -1
- package/dist/validation-utils.cjs +133 -43
- package/dist/validation-utils.cjs.map +1 -1
- package/dist/validation-utils.js +125 -1
- package/dist/validation-utils.js.map +1 -1
- package/dist/validation.cjs +364 -115
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.js +339 -2
- package/dist/validation.js.map +1 -1
- package/package.json +14 -4
- package/dist/chunk-2FXAQT7S.cjs +0 -236
- package/dist/chunk-2FXAQT7S.cjs.map +0 -1
- package/dist/chunk-A3UV7BIN.js +0 -357
- package/dist/chunk-A3UV7BIN.js.map +0 -1
- package/dist/chunk-P37YLBFA.cjs +0 -138
- package/dist/chunk-P37YLBFA.cjs.map +0 -1
- package/dist/chunk-WHQMBQNI.js +0 -127
- package/dist/chunk-WHQMBQNI.js.map +0 -1
- package/dist/chunk-YTTOWHBZ.js +0 -217
- package/dist/chunk-YTTOWHBZ.js.map +0 -1
- package/dist/chunk-ZQCPEOB6.cjs +0 -382
- package/dist/chunk-ZQCPEOB6.cjs.map +0 -1
package/dist/upload.d.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload progress information
|
|
3
|
+
*/
|
|
4
|
+
interface UploadProgress {
|
|
5
|
+
/**
|
|
6
|
+
* Bytes loaded
|
|
7
|
+
*/
|
|
8
|
+
loaded: number;
|
|
9
|
+
/**
|
|
10
|
+
* Total bytes
|
|
11
|
+
*/
|
|
12
|
+
total: number;
|
|
13
|
+
/**
|
|
14
|
+
* Progress percentage (0-100)
|
|
15
|
+
*/
|
|
16
|
+
percent: number;
|
|
17
|
+
/**
|
|
18
|
+
* Upload speed in bytes per second
|
|
19
|
+
*/
|
|
20
|
+
speed: number;
|
|
21
|
+
/**
|
|
22
|
+
* Estimated remaining time in seconds
|
|
23
|
+
*/
|
|
24
|
+
remaining: number;
|
|
25
|
+
/**
|
|
26
|
+
* Current chunk being uploaded (for chunked uploads)
|
|
27
|
+
*/
|
|
28
|
+
currentChunk?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Total chunks (for chunked uploads)
|
|
31
|
+
*/
|
|
32
|
+
totalChunks?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Upload error information
|
|
36
|
+
*/
|
|
37
|
+
interface UploadError {
|
|
38
|
+
/**
|
|
39
|
+
* Error message
|
|
40
|
+
*/
|
|
41
|
+
message: string;
|
|
42
|
+
/**
|
|
43
|
+
* HTTP status code
|
|
44
|
+
*/
|
|
45
|
+
status?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Error code
|
|
48
|
+
*/
|
|
49
|
+
code?: string;
|
|
50
|
+
/**
|
|
51
|
+
* File that failed to upload
|
|
52
|
+
*/
|
|
53
|
+
file: File;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Upload state
|
|
57
|
+
*/
|
|
58
|
+
type UploadState = "idle" | "uploading" | "paused" | "completed" | "error";
|
|
59
|
+
/**
|
|
60
|
+
* Upload format for legacy compatibility
|
|
61
|
+
*/
|
|
62
|
+
type UploadFormat = "legacy" | "chunked" | "standard";
|
|
63
|
+
/**
|
|
64
|
+
* Hook options
|
|
65
|
+
*/
|
|
66
|
+
interface UseFileUploadOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Upload endpoint URL
|
|
69
|
+
*/
|
|
70
|
+
endpoint: string;
|
|
71
|
+
/**
|
|
72
|
+
* Upload format for API compatibility
|
|
73
|
+
* - legacy: Rails DTFormBuilder format (FormData with contact_form_upload[])
|
|
74
|
+
* - chunked: Multi-part chunked upload with resume capability
|
|
75
|
+
* - standard: Simple FormData upload
|
|
76
|
+
* @default "standard"
|
|
77
|
+
*/
|
|
78
|
+
format?: UploadFormat;
|
|
79
|
+
/**
|
|
80
|
+
* Chunk size in bytes (for chunked uploads)
|
|
81
|
+
* @default 1MB (1024 * 1024)
|
|
82
|
+
*/
|
|
83
|
+
chunkSize?: number;
|
|
84
|
+
/**
|
|
85
|
+
* Maximum parallel chunk uploads
|
|
86
|
+
* @default 3
|
|
87
|
+
*/
|
|
88
|
+
maxParallel?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Enable resumable uploads (requires chunked format)
|
|
91
|
+
* @default false
|
|
92
|
+
*/
|
|
93
|
+
resumable?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Request headers
|
|
96
|
+
*/
|
|
97
|
+
headers?: Record<string, string>;
|
|
98
|
+
/**
|
|
99
|
+
* Progress update callback
|
|
100
|
+
*/
|
|
101
|
+
onProgress?: (progress: UploadProgress) => void;
|
|
102
|
+
/**
|
|
103
|
+
* Upload complete callback
|
|
104
|
+
* @param token - Upload token(s) returned by server
|
|
105
|
+
*/
|
|
106
|
+
onComplete?: (token: string | string[]) => void;
|
|
107
|
+
/**
|
|
108
|
+
* Upload error callback
|
|
109
|
+
*/
|
|
110
|
+
onError?: (error: UploadError) => void;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Upload result
|
|
114
|
+
*/
|
|
115
|
+
interface UploadResult {
|
|
116
|
+
/**
|
|
117
|
+
* Upload state
|
|
118
|
+
*/
|
|
119
|
+
state: UploadState;
|
|
120
|
+
/**
|
|
121
|
+
* Upload progress
|
|
122
|
+
*/
|
|
123
|
+
progress: UploadProgress;
|
|
124
|
+
/**
|
|
125
|
+
* Upload error
|
|
126
|
+
*/
|
|
127
|
+
error: UploadError | null;
|
|
128
|
+
/**
|
|
129
|
+
* Upload token(s)
|
|
130
|
+
*/
|
|
131
|
+
token: string | string[] | null;
|
|
132
|
+
/**
|
|
133
|
+
* Start upload
|
|
134
|
+
*/
|
|
135
|
+
upload: (files: File | File[]) => Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Pause upload (chunked only)
|
|
138
|
+
*/
|
|
139
|
+
pause: () => void;
|
|
140
|
+
/**
|
|
141
|
+
* Resume upload (chunked only)
|
|
142
|
+
*/
|
|
143
|
+
resume: () => void;
|
|
144
|
+
/**
|
|
145
|
+
* Cancel upload
|
|
146
|
+
*/
|
|
147
|
+
cancel: () => void;
|
|
148
|
+
/**
|
|
149
|
+
* Reset state
|
|
150
|
+
*/
|
|
151
|
+
reset: () => void;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* File upload hook with progress tracking and chunked upload support
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```tsx
|
|
158
|
+
* const { upload, progress, state } = useFileUpload({
|
|
159
|
+
* endpoint: "/api/uploads",
|
|
160
|
+
* format: "legacy",
|
|
161
|
+
* onComplete: (token) => {
|
|
162
|
+
* form.setFieldValue("resumeToken", token);
|
|
163
|
+
* },
|
|
164
|
+
* });
|
|
165
|
+
*
|
|
166
|
+
* const handleUpload = async () => {
|
|
167
|
+
* await upload(files);
|
|
168
|
+
* };
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
declare function useFileUpload({ endpoint, format, chunkSize, // 1MB
|
|
172
|
+
maxParallel, resumable, headers, onProgress, onComplete, onError, }: UseFileUploadOptions): UploadResult;
|
|
173
|
+
|
|
174
|
+
export { type UploadError, type UploadFormat, type UploadProgress, type UploadState, type UseFileUploadOptions, useFileUpload };
|
package/dist/upload.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
// src/upload/useFileUpload.ts
|
|
4
|
+
function useFileUpload({
|
|
5
|
+
endpoint,
|
|
6
|
+
format = "standard",
|
|
7
|
+
chunkSize = 1024 * 1024,
|
|
8
|
+
// 1MB
|
|
9
|
+
maxParallel = 3,
|
|
10
|
+
resumable = false,
|
|
11
|
+
headers = {},
|
|
12
|
+
onProgress,
|
|
13
|
+
onComplete,
|
|
14
|
+
onError
|
|
15
|
+
}) {
|
|
16
|
+
const [state, setState] = React.useState("idle");
|
|
17
|
+
const [progress, setProgress] = React.useState({
|
|
18
|
+
loaded: 0,
|
|
19
|
+
total: 0,
|
|
20
|
+
percent: 0,
|
|
21
|
+
speed: 0,
|
|
22
|
+
remaining: 0
|
|
23
|
+
});
|
|
24
|
+
const [error, setError] = React.useState(null);
|
|
25
|
+
const [token, setToken] = React.useState(null);
|
|
26
|
+
const abortControllerRef = React.useRef(null);
|
|
27
|
+
const pausedRef = React.useRef(false);
|
|
28
|
+
const startTimeRef = React.useRef(0);
|
|
29
|
+
const loadedAtStartRef = React.useRef(0);
|
|
30
|
+
const calculateProgress = React.useCallback(
|
|
31
|
+
(loaded, total) => {
|
|
32
|
+
const percent = total > 0 ? Math.round(loaded / total * 100) : 0;
|
|
33
|
+
const elapsedTime = (Date.now() - startTimeRef.current) / 1e3;
|
|
34
|
+
const loadedSinceStart = loaded - loadedAtStartRef.current;
|
|
35
|
+
const speed = elapsedTime > 0 ? loadedSinceStart / elapsedTime : 0;
|
|
36
|
+
const remaining = speed > 0 ? Math.ceil((total - loaded) / speed) : 0;
|
|
37
|
+
return {
|
|
38
|
+
loaded,
|
|
39
|
+
total,
|
|
40
|
+
percent,
|
|
41
|
+
speed,
|
|
42
|
+
remaining
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
[]
|
|
46
|
+
);
|
|
47
|
+
const updateProgress = React.useCallback(
|
|
48
|
+
(loaded, total, chunkInfo) => {
|
|
49
|
+
const progressData = {
|
|
50
|
+
...calculateProgress(loaded, total),
|
|
51
|
+
...chunkInfo && {
|
|
52
|
+
currentChunk: chunkInfo.current,
|
|
53
|
+
totalChunks: chunkInfo.total
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
setProgress(progressData);
|
|
57
|
+
if (onProgress) {
|
|
58
|
+
onProgress(progressData);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
[calculateProgress, onProgress]
|
|
62
|
+
);
|
|
63
|
+
const uploadLegacy = React.useCallback(
|
|
64
|
+
async (file) => {
|
|
65
|
+
const formData = new FormData();
|
|
66
|
+
formData.append("contact_form_upload[file]", file);
|
|
67
|
+
formData.append("contact_form_upload[file_type]", file.type);
|
|
68
|
+
formData.append("contact_form_upload[upload_name]", file.name);
|
|
69
|
+
const xhr = new XMLHttpRequest();
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
72
|
+
if (e.lengthComputable) {
|
|
73
|
+
updateProgress(e.loaded, e.total);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
xhr.addEventListener("load", () => {
|
|
77
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
78
|
+
try {
|
|
79
|
+
const response = JSON.parse(xhr.responseText);
|
|
80
|
+
const uploadToken = response.contact_form_upload?.token;
|
|
81
|
+
if (uploadToken) {
|
|
82
|
+
resolve(uploadToken);
|
|
83
|
+
} else {
|
|
84
|
+
reject(new Error("No token in response"));
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
reject(new Error("Invalid JSON response"));
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
xhr.addEventListener("error", () => {
|
|
94
|
+
reject(new Error("Network error"));
|
|
95
|
+
});
|
|
96
|
+
xhr.addEventListener("abort", () => {
|
|
97
|
+
reject(new Error("Upload cancelled"));
|
|
98
|
+
});
|
|
99
|
+
xhr.open("POST", endpoint);
|
|
100
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
101
|
+
xhr.setRequestHeader(key, value);
|
|
102
|
+
});
|
|
103
|
+
xhr.send(formData);
|
|
104
|
+
abortControllerRef.current = {
|
|
105
|
+
abort: () => xhr.abort()
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
[endpoint, headers, updateProgress]
|
|
110
|
+
);
|
|
111
|
+
const uploadStandard = React.useCallback(
|
|
112
|
+
async (file) => {
|
|
113
|
+
const formData = new FormData();
|
|
114
|
+
formData.append("file", file);
|
|
115
|
+
const xhr = new XMLHttpRequest();
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
118
|
+
if (e.lengthComputable) {
|
|
119
|
+
updateProgress(e.loaded, e.total);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
xhr.addEventListener("load", () => {
|
|
123
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
124
|
+
try {
|
|
125
|
+
const response = JSON.parse(xhr.responseText);
|
|
126
|
+
const uploadToken = response.token || response.id;
|
|
127
|
+
if (uploadToken) {
|
|
128
|
+
resolve(uploadToken);
|
|
129
|
+
} else {
|
|
130
|
+
reject(new Error("No token in response"));
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
reject(new Error("Invalid JSON response"));
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
xhr.addEventListener("error", () => {
|
|
140
|
+
reject(new Error("Network error"));
|
|
141
|
+
});
|
|
142
|
+
xhr.addEventListener("abort", () => {
|
|
143
|
+
reject(new Error("Upload cancelled"));
|
|
144
|
+
});
|
|
145
|
+
xhr.open("POST", endpoint);
|
|
146
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
147
|
+
xhr.setRequestHeader(key, value);
|
|
148
|
+
});
|
|
149
|
+
xhr.send(formData);
|
|
150
|
+
abortControllerRef.current = {
|
|
151
|
+
abort: () => xhr.abort()
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
[endpoint, headers, updateProgress]
|
|
156
|
+
);
|
|
157
|
+
const uploadChunked = React.useCallback(
|
|
158
|
+
async (file) => {
|
|
159
|
+
const totalChunks = Math.ceil(file.size / chunkSize);
|
|
160
|
+
let uploadedChunks = 0;
|
|
161
|
+
let totalLoaded = 0;
|
|
162
|
+
const uploadChunk = async (chunk, chunkIndex) => {
|
|
163
|
+
if (pausedRef.current) {
|
|
164
|
+
throw new Error("Upload paused");
|
|
165
|
+
}
|
|
166
|
+
const formData = new FormData();
|
|
167
|
+
formData.append("chunk", chunk);
|
|
168
|
+
formData.append("chunkIndex", chunkIndex.toString());
|
|
169
|
+
formData.append("totalChunks", totalChunks.toString());
|
|
170
|
+
formData.append("fileName", file.name);
|
|
171
|
+
formData.append("fileSize", file.size.toString());
|
|
172
|
+
const response = await fetch(endpoint, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers,
|
|
175
|
+
body: formData,
|
|
176
|
+
signal: abortControllerRef.current?.signal
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
throw new Error(`Chunk ${chunkIndex} upload failed: ${response.status}`);
|
|
180
|
+
}
|
|
181
|
+
uploadedChunks++;
|
|
182
|
+
totalLoaded += chunk.size;
|
|
183
|
+
updateProgress(totalLoaded, file.size, {
|
|
184
|
+
current: uploadedChunks,
|
|
185
|
+
total: totalChunks
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
const chunks = [];
|
|
189
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
190
|
+
const start = i * chunkSize;
|
|
191
|
+
const end = Math.min(start + chunkSize, file.size);
|
|
192
|
+
const chunk = file.slice(start, end);
|
|
193
|
+
chunks.push({ blob: chunk, index: i });
|
|
194
|
+
}
|
|
195
|
+
for (let i = 0; i < chunks.length; i += maxParallel) {
|
|
196
|
+
const batch = chunks.slice(i, i + maxParallel);
|
|
197
|
+
const batchPromises = batch.map(
|
|
198
|
+
(chunk) => uploadChunk(chunk.blob, chunk.index)
|
|
199
|
+
);
|
|
200
|
+
await Promise.all(batchPromises);
|
|
201
|
+
}
|
|
202
|
+
const finalizeResponse = await fetch(`${endpoint}/finalize`, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: {
|
|
205
|
+
"Content-Type": "application/json",
|
|
206
|
+
...headers
|
|
207
|
+
},
|
|
208
|
+
body: JSON.stringify({
|
|
209
|
+
fileName: file.name,
|
|
210
|
+
totalChunks
|
|
211
|
+
}),
|
|
212
|
+
signal: abortControllerRef.current?.signal
|
|
213
|
+
});
|
|
214
|
+
if (!finalizeResponse.ok) {
|
|
215
|
+
throw new Error("Failed to finalize upload");
|
|
216
|
+
}
|
|
217
|
+
const result = await finalizeResponse.json();
|
|
218
|
+
return result.token || result.id;
|
|
219
|
+
},
|
|
220
|
+
[endpoint, chunkSize, maxParallel, headers, updateProgress]
|
|
221
|
+
);
|
|
222
|
+
const uploadFile = React.useCallback(
|
|
223
|
+
async (file) => {
|
|
224
|
+
if (format === "legacy") {
|
|
225
|
+
return uploadLegacy(file);
|
|
226
|
+
} else if (format === "chunked") {
|
|
227
|
+
return uploadChunked(file);
|
|
228
|
+
} else {
|
|
229
|
+
return uploadStandard(file);
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
[format, uploadLegacy, uploadChunked, uploadStandard]
|
|
233
|
+
);
|
|
234
|
+
const upload = React.useCallback(
|
|
235
|
+
async (files) => {
|
|
236
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
237
|
+
if (fileArray.length === 0) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
setState("uploading");
|
|
241
|
+
setError(null);
|
|
242
|
+
setToken(null);
|
|
243
|
+
pausedRef.current = false;
|
|
244
|
+
startTimeRef.current = Date.now();
|
|
245
|
+
loadedAtStartRef.current = 0;
|
|
246
|
+
abortControllerRef.current = new AbortController();
|
|
247
|
+
try {
|
|
248
|
+
const tokens = [];
|
|
249
|
+
for (const file of fileArray) {
|
|
250
|
+
const uploadToken = await uploadFile(file);
|
|
251
|
+
tokens.push(uploadToken);
|
|
252
|
+
}
|
|
253
|
+
setToken(tokens.length === 1 ? tokens[0] : tokens);
|
|
254
|
+
setState("completed");
|
|
255
|
+
if (onComplete) {
|
|
256
|
+
onComplete(tokens.length === 1 ? tokens[0] : tokens);
|
|
257
|
+
}
|
|
258
|
+
} catch (err) {
|
|
259
|
+
const uploadError = {
|
|
260
|
+
message: err instanceof Error ? err.message : "Upload failed",
|
|
261
|
+
file: fileArray[0]
|
|
262
|
+
};
|
|
263
|
+
setError(uploadError);
|
|
264
|
+
setState("error");
|
|
265
|
+
if (onError) {
|
|
266
|
+
onError(uploadError);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
[uploadFile, onComplete, onError]
|
|
271
|
+
);
|
|
272
|
+
const pause = React.useCallback(() => {
|
|
273
|
+
if (state === "uploading" && format === "chunked" && resumable) {
|
|
274
|
+
pausedRef.current = true;
|
|
275
|
+
setState("paused");
|
|
276
|
+
}
|
|
277
|
+
}, [state, format, resumable]);
|
|
278
|
+
const resume = React.useCallback(() => {
|
|
279
|
+
if (state === "paused") {
|
|
280
|
+
pausedRef.current = false;
|
|
281
|
+
setState("uploading");
|
|
282
|
+
}
|
|
283
|
+
}, [state]);
|
|
284
|
+
const cancel = React.useCallback(() => {
|
|
285
|
+
if (abortControllerRef.current) {
|
|
286
|
+
abortControllerRef.current.abort();
|
|
287
|
+
abortControllerRef.current = null;
|
|
288
|
+
}
|
|
289
|
+
setState("idle");
|
|
290
|
+
setProgress({
|
|
291
|
+
loaded: 0,
|
|
292
|
+
total: 0,
|
|
293
|
+
percent: 0,
|
|
294
|
+
speed: 0,
|
|
295
|
+
remaining: 0
|
|
296
|
+
});
|
|
297
|
+
}, []);
|
|
298
|
+
const reset = React.useCallback(() => {
|
|
299
|
+
setState("idle");
|
|
300
|
+
setProgress({
|
|
301
|
+
loaded: 0,
|
|
302
|
+
total: 0,
|
|
303
|
+
percent: 0,
|
|
304
|
+
speed: 0,
|
|
305
|
+
remaining: 0
|
|
306
|
+
});
|
|
307
|
+
setError(null);
|
|
308
|
+
setToken(null);
|
|
309
|
+
pausedRef.current = false;
|
|
310
|
+
}, []);
|
|
311
|
+
return {
|
|
312
|
+
state,
|
|
313
|
+
progress,
|
|
314
|
+
error,
|
|
315
|
+
token,
|
|
316
|
+
upload,
|
|
317
|
+
pause,
|
|
318
|
+
resume,
|
|
319
|
+
cancel,
|
|
320
|
+
reset
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export { useFileUpload };
|
|
325
|
+
//# sourceMappingURL=upload.js.map
|
|
326
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/upload/useFileUpload.ts"],"names":[],"mappings":"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","file":"upload.js","sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Upload progress information\n */\nexport interface UploadProgress {\n /**\n * Bytes loaded\n */\n loaded: number;\n\n /**\n * Total bytes\n */\n total: number;\n\n /**\n * Progress percentage (0-100)\n */\n percent: number;\n\n /**\n * Upload speed in bytes per second\n */\n speed: number;\n\n /**\n * Estimated remaining time in seconds\n */\n remaining: number;\n\n /**\n * Current chunk being uploaded (for chunked uploads)\n */\n currentChunk?: number;\n\n /**\n * Total chunks (for chunked uploads)\n */\n totalChunks?: number;\n}\n\n/**\n * Upload error information\n */\nexport interface UploadError {\n /**\n * Error message\n */\n message: string;\n\n /**\n * HTTP status code\n */\n status?: number;\n\n /**\n * Error code\n */\n code?: string;\n\n /**\n * File that failed to upload\n */\n file: File;\n}\n\n/**\n * Upload state\n */\nexport type UploadState = \"idle\" | \"uploading\" | \"paused\" | \"completed\" | \"error\";\n\n/**\n * Upload format for legacy compatibility\n */\nexport type UploadFormat = \"legacy\" | \"chunked\" | \"standard\";\n\n/**\n * Hook options\n */\nexport interface UseFileUploadOptions {\n /**\n * Upload endpoint URL\n */\n endpoint: string;\n\n /**\n * Upload format for API compatibility\n * - legacy: Rails DTFormBuilder format (FormData with contact_form_upload[])\n * - chunked: Multi-part chunked upload with resume capability\n * - standard: Simple FormData upload\n * @default \"standard\"\n */\n format?: UploadFormat;\n\n /**\n * Chunk size in bytes (for chunked uploads)\n * @default 1MB (1024 * 1024)\n */\n chunkSize?: number;\n\n /**\n * Maximum parallel chunk uploads\n * @default 3\n */\n maxParallel?: number;\n\n /**\n * Enable resumable uploads (requires chunked format)\n * @default false\n */\n resumable?: boolean;\n\n /**\n * Request headers\n */\n headers?: Record<string, string>;\n\n /**\n * Progress update callback\n */\n onProgress?: (progress: UploadProgress) => void;\n\n /**\n * Upload complete callback\n * @param token - Upload token(s) returned by server\n */\n onComplete?: (token: string | string[]) => void;\n\n /**\n * Upload error callback\n */\n onError?: (error: UploadError) => void;\n}\n\n/**\n * Upload result\n */\ninterface UploadResult {\n /**\n * Upload state\n */\n state: UploadState;\n\n /**\n * Upload progress\n */\n progress: UploadProgress;\n\n /**\n * Upload error\n */\n error: UploadError | null;\n\n /**\n * Upload token(s)\n */\n token: string | string[] | null;\n\n /**\n * Start upload\n */\n upload: (files: File | File[]) => Promise<void>;\n\n /**\n * Pause upload (chunked only)\n */\n pause: () => void;\n\n /**\n * Resume upload (chunked only)\n */\n resume: () => void;\n\n /**\n * Cancel upload\n */\n cancel: () => void;\n\n /**\n * Reset state\n */\n reset: () => void;\n}\n\n/**\n * File upload hook with progress tracking and chunked upload support\n *\n * @example\n * ```tsx\n * const { upload, progress, state } = useFileUpload({\n * endpoint: \"/api/uploads\",\n * format: \"legacy\",\n * onComplete: (token) => {\n * form.setFieldValue(\"resumeToken\", token);\n * },\n * });\n *\n * const handleUpload = async () => {\n * await upload(files);\n * };\n * ```\n */\nexport function useFileUpload({\n endpoint,\n format = \"standard\",\n chunkSize = 1024 * 1024, // 1MB\n maxParallel = 3,\n resumable = false,\n headers = {},\n onProgress,\n onComplete,\n onError,\n}: UseFileUploadOptions): UploadResult {\n const [state, setState] = React.useState<UploadState>(\"idle\");\n const [progress, setProgress] = React.useState<UploadProgress>({\n loaded: 0,\n total: 0,\n percent: 0,\n speed: 0,\n remaining: 0,\n });\n const [error, setError] = React.useState<UploadError | null>(null);\n const [token, setToken] = React.useState<string | string[] | null>(null);\n\n const abortControllerRef = React.useRef<AbortController | null>(null);\n const pausedRef = React.useRef(false);\n const startTimeRef = React.useRef<number>(0);\n const loadedAtStartRef = React.useRef<number>(0);\n\n /**\n * Calculate upload speed and remaining time\n */\n const calculateProgress = React.useCallback(\n (loaded: number, total: number): UploadProgress => {\n const percent = total > 0 ? Math.round((loaded / total) * 100) : 0;\n const elapsedTime = (Date.now() - startTimeRef.current) / 1000; // seconds\n const loadedSinceStart = loaded - loadedAtStartRef.current;\n const speed = elapsedTime > 0 ? loadedSinceStart / elapsedTime : 0;\n const remaining =\n speed > 0 ? Math.ceil((total - loaded) / speed) : 0;\n\n return {\n loaded,\n total,\n percent,\n speed,\n remaining,\n };\n },\n []\n );\n\n /**\n * Update progress and notify callback\n */\n const updateProgress = React.useCallback(\n (loaded: number, total: number, chunkInfo?: { current: number; total: number }) => {\n const progressData = {\n ...calculateProgress(loaded, total),\n ...(chunkInfo && {\n currentChunk: chunkInfo.current,\n totalChunks: chunkInfo.total,\n }),\n };\n\n setProgress(progressData);\n\n if (onProgress) {\n onProgress(progressData);\n }\n },\n [calculateProgress, onProgress]\n );\n\n /**\n * Upload file in legacy Rails format\n */\n const uploadLegacy = React.useCallback(\n async (file: File): Promise<string> => {\n const formData = new FormData();\n formData.append(\"contact_form_upload[file]\", file);\n formData.append(\"contact_form_upload[file_type]\", file.type);\n formData.append(\"contact_form_upload[upload_name]\", file.name);\n\n const xhr = new XMLHttpRequest();\n\n return new Promise((resolve, reject) => {\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable) {\n updateProgress(e.loaded, e.total);\n }\n });\n\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n const response = JSON.parse(xhr.responseText);\n const uploadToken = response.contact_form_upload?.token;\n\n if (uploadToken) {\n resolve(uploadToken);\n } else {\n reject(new Error(\"No token in response\"));\n }\n } catch (err) {\n reject(new Error(\"Invalid JSON response\"));\n }\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n reject(new Error(\"Network error\"));\n });\n\n xhr.addEventListener(\"abort\", () => {\n reject(new Error(\"Upload cancelled\"));\n });\n\n xhr.open(\"POST\", endpoint);\n\n // Add custom headers\n Object.entries(headers).forEach(([key, value]) => {\n xhr.setRequestHeader(key, value);\n });\n\n xhr.send(formData);\n\n // Store XHR for cancellation\n abortControllerRef.current = {\n abort: () => xhr.abort(),\n } as AbortController;\n });\n },\n [endpoint, headers, updateProgress]\n );\n\n /**\n * Upload file in standard format\n */\n const uploadStandard = React.useCallback(\n async (file: File): Promise<string> => {\n const formData = new FormData();\n formData.append(\"file\", file);\n\n const xhr = new XMLHttpRequest();\n\n return new Promise((resolve, reject) => {\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable) {\n updateProgress(e.loaded, e.total);\n }\n });\n\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n const response = JSON.parse(xhr.responseText);\n const uploadToken = response.token || response.id;\n\n if (uploadToken) {\n resolve(uploadToken);\n } else {\n reject(new Error(\"No token in response\"));\n }\n } catch (err) {\n reject(new Error(\"Invalid JSON response\"));\n }\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n reject(new Error(\"Network error\"));\n });\n\n xhr.addEventListener(\"abort\", () => {\n reject(new Error(\"Upload cancelled\"));\n });\n\n xhr.open(\"POST\", endpoint);\n\n // Add custom headers\n Object.entries(headers).forEach(([key, value]) => {\n xhr.setRequestHeader(key, value);\n });\n\n xhr.send(formData);\n\n // Store XHR for cancellation\n abortControllerRef.current = {\n abort: () => xhr.abort(),\n } as AbortController;\n });\n },\n [endpoint, headers, updateProgress]\n );\n\n /**\n * Upload file in chunks\n */\n const uploadChunked = React.useCallback(\n async (file: File): Promise<string> => {\n const totalChunks = Math.ceil(file.size / chunkSize);\n let uploadedChunks = 0;\n let totalLoaded = 0;\n\n // Upload chunk\n const uploadChunk = async (\n chunk: Blob,\n chunkIndex: number\n ): Promise<void> => {\n if (pausedRef.current) {\n throw new Error(\"Upload paused\");\n }\n\n const formData = new FormData();\n formData.append(\"chunk\", chunk);\n formData.append(\"chunkIndex\", chunkIndex.toString());\n formData.append(\"totalChunks\", totalChunks.toString());\n formData.append(\"fileName\", file.name);\n formData.append(\"fileSize\", file.size.toString());\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers,\n body: formData,\n signal: abortControllerRef.current?.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Chunk ${chunkIndex} upload failed: ${response.status}`);\n }\n\n uploadedChunks++;\n totalLoaded += chunk.size;\n\n updateProgress(totalLoaded, file.size, {\n current: uploadedChunks,\n total: totalChunks,\n });\n };\n\n // Parallel chunk upload\n const chunks: Array<{ blob: Blob; index: number }> = [];\n for (let i = 0; i < totalChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, file.size);\n const chunk = file.slice(start, end);\n chunks.push({ blob: chunk, index: i });\n }\n\n // Upload chunks with concurrency limit\n const uploadPromises: Promise<void>[] = [];\n for (let i = 0; i < chunks.length; i += maxParallel) {\n const batch = chunks.slice(i, i + maxParallel);\n const batchPromises = batch.map((chunk) =>\n uploadChunk(chunk.blob, chunk.index)\n );\n await Promise.all(batchPromises);\n uploadPromises.push(...batchPromises);\n }\n\n // Finalize upload\n const finalizeResponse = await fetch(`${endpoint}/finalize`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify({\n fileName: file.name,\n totalChunks,\n }),\n signal: abortControllerRef.current?.signal,\n });\n\n if (!finalizeResponse.ok) {\n throw new Error(\"Failed to finalize upload\");\n }\n\n const result = await finalizeResponse.json();\n return result.token || result.id;\n },\n [endpoint, chunkSize, maxParallel, headers, updateProgress]\n );\n\n /**\n * Upload single file\n */\n const uploadFile = React.useCallback(\n async (file: File): Promise<string> => {\n // Choose upload method based on format\n if (format === \"legacy\") {\n return uploadLegacy(file);\n } else if (format === \"chunked\") {\n return uploadChunked(file);\n } else {\n return uploadStandard(file);\n }\n },\n [format, uploadLegacy, uploadChunked, uploadStandard]\n );\n\n /**\n * Upload files\n */\n const upload = React.useCallback(\n async (files: File | File[]) => {\n const fileArray = Array.isArray(files) ? files : [files];\n\n if (fileArray.length === 0) {\n return;\n }\n\n // Reset state\n setState(\"uploading\");\n setError(null);\n setToken(null);\n pausedRef.current = false;\n startTimeRef.current = Date.now();\n loadedAtStartRef.current = 0;\n abortControllerRef.current = new AbortController();\n\n try {\n const tokens: string[] = [];\n\n for (const file of fileArray) {\n const uploadToken = await uploadFile(file);\n tokens.push(uploadToken);\n }\n\n setToken(tokens.length === 1 ? tokens[0] : tokens);\n setState(\"completed\");\n\n if (onComplete) {\n onComplete(tokens.length === 1 ? tokens[0] : tokens);\n }\n } catch (err) {\n const uploadError: UploadError = {\n message: err instanceof Error ? err.message : \"Upload failed\",\n file: fileArray[0],\n };\n\n setError(uploadError);\n setState(\"error\");\n\n if (onError) {\n onError(uploadError);\n }\n }\n },\n [uploadFile, onComplete, onError]\n );\n\n /**\n * Pause upload\n */\n const pause = React.useCallback(() => {\n if (state === \"uploading\" && format === \"chunked\" && resumable) {\n pausedRef.current = true;\n setState(\"paused\");\n }\n }, [state, format, resumable]);\n\n /**\n * Resume upload\n */\n const resume = React.useCallback(() => {\n if (state === \"paused\") {\n pausedRef.current = false;\n setState(\"uploading\");\n // Resume logic would need chunk tracking implementation\n }\n }, [state]);\n\n /**\n * Cancel upload\n */\n const cancel = React.useCallback(() => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = null;\n }\n setState(\"idle\");\n setProgress({\n loaded: 0,\n total: 0,\n percent: 0,\n speed: 0,\n remaining: 0,\n });\n }, []);\n\n /**\n * Reset state\n */\n const reset = React.useCallback(() => {\n setState(\"idle\");\n setProgress({\n loaded: 0,\n total: 0,\n percent: 0,\n speed: 0,\n remaining: 0,\n });\n setError(null);\n setToken(null);\n pausedRef.current = false;\n }, []);\n\n return {\n state,\n progress,\n error,\n token,\n upload,\n pause,\n resume,\n cancel,\n reset,\n };\n}\n"]}
|