@uploadista/react 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-check.log +89 -0
- package/FLOW_UPLOAD.md +307 -0
- package/LICENSE +21 -0
- package/README.md +318 -0
- package/package.json +35 -0
- package/src/components/flow-upload-list.tsx +614 -0
- package/src/components/flow-upload-zone.tsx +441 -0
- package/src/components/upload-list.tsx +626 -0
- package/src/components/upload-zone.tsx +545 -0
- package/src/components/uploadista-provider.tsx +190 -0
- package/src/hooks/use-drag-drop.ts +404 -0
- package/src/hooks/use-flow-upload.ts +568 -0
- package/src/hooks/use-multi-flow-upload.ts +477 -0
- package/src/hooks/use-multi-upload.ts +691 -0
- package/src/hooks/use-upload-metrics.ts +585 -0
- package/src/hooks/use-upload.ts +411 -0
- package/src/hooks/use-uploadista-client.ts +145 -0
- package/src/index.ts +87 -0
- package/tsconfig.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# @uploadista/react
|
|
2
|
+
|
|
3
|
+
React hooks and components for the Uploadista unified client. Provides a complete solution for file uploads and flow execution with WebSocket support, progress tracking, and comprehensive state management.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @uploadista/react @uploadista/client @uploadista/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Upload Management**: Single and multi-file upload with progress tracking
|
|
14
|
+
- **Flow Execution**: Execute processing flows with real-time WebSocket updates
|
|
15
|
+
- **Drag & Drop**: Built-in drag-and-drop support with file validation
|
|
16
|
+
- **State Management**: Comprehensive state management for uploads and flows
|
|
17
|
+
- **Performance Metrics**: Track upload performance, speed, and network conditions
|
|
18
|
+
- **TypeScript**: Full TypeScript support with type inference
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Upload Provider Setup
|
|
23
|
+
|
|
24
|
+
Wrap your app with the `UploadProvider` to provide upload client configuration:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { UploadProvider } from '@uploadista/react';
|
|
28
|
+
|
|
29
|
+
function App() {
|
|
30
|
+
return (
|
|
31
|
+
<UploadProvider
|
|
32
|
+
baseUrl="https://api.example.com"
|
|
33
|
+
storageId="my-storage"
|
|
34
|
+
chunkSize={1024 * 1024} // 1MB chunks
|
|
35
|
+
storeFingerprintForResuming={true}
|
|
36
|
+
onEvent={(event) => {
|
|
37
|
+
console.log('Upload event:', event);
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<YourApp />
|
|
41
|
+
</UploadProvider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Single File Upload
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { useUploadContext, useUpload } from '@uploadista/react';
|
|
50
|
+
|
|
51
|
+
function SingleFileUploader() {
|
|
52
|
+
const uploadClient = useUploadContext();
|
|
53
|
+
const upload = useUpload(uploadClient, {
|
|
54
|
+
onSuccess: (result) => console.log('Upload complete:', result),
|
|
55
|
+
onError: (error) => console.error('Upload failed:', error),
|
|
56
|
+
onProgress: (progress) => console.log('Progress:', progress + '%'),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
60
|
+
const file = e.target.files?.[0];
|
|
61
|
+
if (file) upload.upload(file);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div>
|
|
66
|
+
<input type="file" onChange={handleFileSelect} />
|
|
67
|
+
{upload.isUploading && <div>Progress: {upload.state.progress}%</div>}
|
|
68
|
+
{upload.state.error && <div>Error: {upload.state.error.message}</div>}
|
|
69
|
+
{upload.canRetry && <button onClick={upload.retry}>Retry</button>}
|
|
70
|
+
<button onClick={upload.abort} disabled={!upload.isUploading}>Abort</button>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Multi-File Upload
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { useUploadContext, useMultiUpload } from '@uploadista/react';
|
|
80
|
+
|
|
81
|
+
function MultiFileUploader() {
|
|
82
|
+
const uploadClient = useUploadContext();
|
|
83
|
+
const multiUpload = useMultiUpload(uploadClient, {
|
|
84
|
+
maxConcurrent: 3,
|
|
85
|
+
onComplete: (results) => {
|
|
86
|
+
console.log(`${results.successful.length}/${results.total} successful`);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const handleFilesSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
91
|
+
if (e.target.files) {
|
|
92
|
+
multiUpload.addFiles(Array.from(e.target.files));
|
|
93
|
+
multiUpload.startAll();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div>
|
|
99
|
+
<input type="file" multiple onChange={handleFilesSelect} />
|
|
100
|
+
<div>Progress: {multiUpload.state.progress}%</div>
|
|
101
|
+
<div>
|
|
102
|
+
{multiUpload.state.uploading} uploading,
|
|
103
|
+
{multiUpload.state.successful} successful,
|
|
104
|
+
{multiUpload.state.failed} failed
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{multiUpload.items.map((item) => (
|
|
108
|
+
<div key={item.id}>
|
|
109
|
+
{item.file.name}: {item.state.status} ({item.state.progress}%)
|
|
110
|
+
</div>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Drag & Drop Upload
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { useUploadContext, useDragDrop, useMultiUpload } from '@uploadista/react';
|
|
121
|
+
|
|
122
|
+
function DragDropUploader() {
|
|
123
|
+
const uploadClient = useUploadContext();
|
|
124
|
+
const multiUpload = useMultiUpload(uploadClient);
|
|
125
|
+
|
|
126
|
+
const dragDrop = useDragDrop({
|
|
127
|
+
accept: ['image/*', '.pdf'],
|
|
128
|
+
maxFiles: 5,
|
|
129
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
130
|
+
onFilesReceived: (files) => {
|
|
131
|
+
multiUpload.addFiles(files);
|
|
132
|
+
multiUpload.startAll();
|
|
133
|
+
},
|
|
134
|
+
onValidationError: (errors) => {
|
|
135
|
+
console.error('Validation errors:', errors);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
{...dragDrop.dragHandlers}
|
|
142
|
+
style={{
|
|
143
|
+
border: dragDrop.state.isDragging ? '2px dashed #007bff' : '2px dashed #ccc',
|
|
144
|
+
padding: '2rem',
|
|
145
|
+
textAlign: 'center',
|
|
146
|
+
}}
|
|
147
|
+
onClick={dragDrop.openFilePicker}
|
|
148
|
+
>
|
|
149
|
+
{dragDrop.state.isDragging ? (
|
|
150
|
+
<p>Drop files here...</p>
|
|
151
|
+
) : (
|
|
152
|
+
<p>Drag files here or click to select</p>
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
{dragDrop.state.errors.length > 0 && (
|
|
156
|
+
<div>
|
|
157
|
+
{dragDrop.state.errors.map((error, i) => (
|
|
158
|
+
<p key={i} style={{ color: 'red' }}>{error}</p>
|
|
159
|
+
))}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
<input {...dragDrop.inputProps} />
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Upload Zone (Combined Drag & Drop + Upload)
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { SimpleUploadZone } from '@uploadista/react';
|
|
173
|
+
|
|
174
|
+
function SimpleUploader() {
|
|
175
|
+
return (
|
|
176
|
+
<SimpleUploadZone
|
|
177
|
+
multiple={true}
|
|
178
|
+
accept={['image/*']}
|
|
179
|
+
maxFileSize={5 * 1024 * 1024}
|
|
180
|
+
onUploadStart={(files) => console.log('Starting uploads:', files.length)}
|
|
181
|
+
onValidationError={(errors) => console.error('Validation errors:', errors)}
|
|
182
|
+
multiUploadOptions={{
|
|
183
|
+
maxConcurrent: 3,
|
|
184
|
+
onComplete: (results) => console.log('All uploads complete:', results),
|
|
185
|
+
}}
|
|
186
|
+
/>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Flow Execution
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
import { useFlowClient, useFlow } from '@uploadista/react';
|
|
195
|
+
|
|
196
|
+
function FlowExecutor() {
|
|
197
|
+
const flowClient = useFlowClient({
|
|
198
|
+
baseUrl: 'https://api.example.com',
|
|
199
|
+
storageId: 'my-storage',
|
|
200
|
+
chunkSize: 1024 * 1024,
|
|
201
|
+
storeFingerprintForResuming: true,
|
|
202
|
+
onEvent: (event) => console.log('Flow event:', event),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const flow = useFlow(flowClient, {
|
|
206
|
+
flowId: 'image-processing',
|
|
207
|
+
storageId: 'my-storage',
|
|
208
|
+
autoConnectWebSocket: true,
|
|
209
|
+
onSuccess: (result) => console.log('Flow completed:', result),
|
|
210
|
+
onError: (error) => console.error('Flow failed:', error),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const handleExecute = () => {
|
|
214
|
+
flow.executeFlow({
|
|
215
|
+
image: 'photo.jpg',
|
|
216
|
+
quality: 80,
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div>
|
|
222
|
+
<button onClick={handleExecute} disabled={flow.state.status === 'running'}>
|
|
223
|
+
Execute Flow
|
|
224
|
+
</button>
|
|
225
|
+
|
|
226
|
+
{flow.state.status === 'running' && <div>Processing...</div>}
|
|
227
|
+
{flow.state.status === 'success' && <div>Success!</div>}
|
|
228
|
+
{flow.state.error && <div>Error: {flow.state.error.message}</div>}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Upload Metrics
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
import { useUploadMetrics } from '@uploadista/react';
|
|
238
|
+
|
|
239
|
+
function UploadMetricsDisplay() {
|
|
240
|
+
const metrics = useUploadMetrics({
|
|
241
|
+
speedCalculationInterval: 1000,
|
|
242
|
+
onMetricsUpdate: (metrics) => {
|
|
243
|
+
console.log(`Speed: ${metrics.currentSpeed / 1024} KB/s`);
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Track file uploads
|
|
248
|
+
React.useEffect(() => {
|
|
249
|
+
// When starting an upload
|
|
250
|
+
metrics.startFileUpload('file-1', 'photo.jpg', 1024 * 1024);
|
|
251
|
+
|
|
252
|
+
// When progress updates
|
|
253
|
+
metrics.updateFileProgress('file-1', 512 * 1024);
|
|
254
|
+
|
|
255
|
+
// When upload completes
|
|
256
|
+
metrics.completeFileUpload('file-1');
|
|
257
|
+
}, []);
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div>
|
|
261
|
+
<div>Overall Progress: {metrics.metrics.progress}%</div>
|
|
262
|
+
<div>Speed: {(metrics.metrics.currentSpeed / 1024).toFixed(1)} KB/s</div>
|
|
263
|
+
<div>Files: {metrics.metrics.completedFiles}/{metrics.metrics.totalFiles}</div>
|
|
264
|
+
|
|
265
|
+
{metrics.fileMetrics.map((file) => (
|
|
266
|
+
<div key={file.id}>
|
|
267
|
+
{file.filename}: {file.progress}%
|
|
268
|
+
</div>
|
|
269
|
+
))}
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## API Reference
|
|
276
|
+
|
|
277
|
+
### Hooks
|
|
278
|
+
|
|
279
|
+
#### Upload Hooks
|
|
280
|
+
- `useUploadClient(options)` - Create a unified upload/flow client
|
|
281
|
+
- `useUpload(client, options)` - Manage single file upload
|
|
282
|
+
- `useMultiUpload(client, options)` - Manage multiple file uploads
|
|
283
|
+
- `useUploadMetrics(options)` - Track upload performance metrics
|
|
284
|
+
|
|
285
|
+
#### Flow Hooks
|
|
286
|
+
- `useUploadFlow(client, options)` - Execute and manage flows
|
|
287
|
+
|
|
288
|
+
#### UI Hooks
|
|
289
|
+
- `useDragDrop(options)` - Drag and drop functionality
|
|
290
|
+
- `useUploadContext()` - Access upload client from context
|
|
291
|
+
|
|
292
|
+
### Components
|
|
293
|
+
|
|
294
|
+
#### Providers
|
|
295
|
+
- `<UploadProvider>` - Context provider for upload client
|
|
296
|
+
|
|
297
|
+
#### Upload Components
|
|
298
|
+
- `<UploadZone>` - Headless upload zone with render props
|
|
299
|
+
- `<SimpleUploadZone>` - Pre-styled upload zone
|
|
300
|
+
- `<UploadList>` - Headless upload list with render props
|
|
301
|
+
- `<SimpleUploadListItem>` - Pre-styled upload list item
|
|
302
|
+
|
|
303
|
+
### Utilities
|
|
304
|
+
|
|
305
|
+
- `formatFileSize(bytes)` - Format bytes to human-readable size
|
|
306
|
+
- `formatSpeed(bytesPerSecond)` - Format speed to human-readable format
|
|
307
|
+
- `formatDuration(milliseconds)` - Format duration to human-readable format
|
|
308
|
+
- `validateFileType(file, accept)` - Validate file against accepted types
|
|
309
|
+
- `isImageFile(file)` - Check if file is an image
|
|
310
|
+
- `isVideoFile(file)` - Check if file is a video
|
|
311
|
+
- `isAudioFile(file)` - Check if file is audio
|
|
312
|
+
- `isDocumentFile(file)` - Check if file is a document
|
|
313
|
+
- `createFilePreview(file)` - Create preview URL for file
|
|
314
|
+
- `revokeFilePreview(url)` - Clean up preview URL
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uploadista/react",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.3",
|
|
5
|
+
"description": "React client for Uploadista",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Uploadista",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./components/*": "./src/components/*.tsx",
|
|
11
|
+
"./hooks/*": "./src/hooks/*.ts",
|
|
12
|
+
"./utils/*": "./src/utils/*.ts"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"react": "19.2.0",
|
|
16
|
+
"react-dom": "19.2.0",
|
|
17
|
+
"@uploadista/client-core": "0.0.3",
|
|
18
|
+
"@uploadista/core": "0.0.3",
|
|
19
|
+
"@uploadista/client-browser": "0.0.3"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": "19.2.2",
|
|
23
|
+
"@types/react-dom": "19.2.2",
|
|
24
|
+
"vitest": "3.2.4",
|
|
25
|
+
"@uploadista/typescript-config": "0.0.3"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"format": "biome format --write ./src",
|
|
29
|
+
"lint": "biome lint --write ./src",
|
|
30
|
+
"check": "biome check --write ./src",
|
|
31
|
+
"test": "vitest",
|
|
32
|
+
"test:run": "vitest run",
|
|
33
|
+
"test:watch": "vitest --watch"
|
|
34
|
+
}
|
|
35
|
+
}
|