@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
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type {
|
|
3
|
+
UploadItem,
|
|
4
|
+
UseMultiUploadReturn,
|
|
5
|
+
} from "../hooks/use-multi-upload";
|
|
6
|
+
import type { UploadStatus } from "../hooks/use-upload";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Render props passed to the UploadList children function.
|
|
10
|
+
* Provides organized access to upload items, status groupings, and actions.
|
|
11
|
+
*
|
|
12
|
+
* @property items - All upload items (filtered and sorted if configured)
|
|
13
|
+
* @property itemsByStatus - Upload items grouped by their current status
|
|
14
|
+
* @property multiUpload - Complete multi-upload hook instance
|
|
15
|
+
* @property actions - Helper functions for common item operations
|
|
16
|
+
* @property actions.removeItem - Remove an item from the list
|
|
17
|
+
* @property actions.retryItem - Retry a failed upload
|
|
18
|
+
* @property actions.abortItem - Cancel an active upload
|
|
19
|
+
* @property actions.startItem - Begin uploading an idle item
|
|
20
|
+
*/
|
|
21
|
+
export interface UploadListRenderProps {
|
|
22
|
+
/**
|
|
23
|
+
* All upload items
|
|
24
|
+
*/
|
|
25
|
+
items: UploadItem[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Items filtered by status
|
|
29
|
+
*/
|
|
30
|
+
itemsByStatus: {
|
|
31
|
+
idle: UploadItem[];
|
|
32
|
+
uploading: UploadItem[];
|
|
33
|
+
success: UploadItem[];
|
|
34
|
+
error: UploadItem[];
|
|
35
|
+
aborted: UploadItem[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Multi-upload state and controls
|
|
40
|
+
*/
|
|
41
|
+
multiUpload: UseMultiUploadReturn;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Helper functions for item management
|
|
45
|
+
*/
|
|
46
|
+
actions: {
|
|
47
|
+
removeItem: (id: string) => void;
|
|
48
|
+
retryItem: (item: UploadItem) => void;
|
|
49
|
+
abortItem: (item: UploadItem) => void;
|
|
50
|
+
startItem: (item: UploadItem) => void;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Props for the UploadList component.
|
|
56
|
+
*
|
|
57
|
+
* @property multiUpload - Multi-upload hook instance to display
|
|
58
|
+
* @property filter - Optional function to filter which items to show
|
|
59
|
+
* @property sortBy - Optional comparator function to sort items
|
|
60
|
+
* @property children - Render function receiving list state and actions
|
|
61
|
+
*/
|
|
62
|
+
export interface UploadListProps {
|
|
63
|
+
/**
|
|
64
|
+
* Multi-upload instance from useMultiUpload hook
|
|
65
|
+
*/
|
|
66
|
+
multiUpload: UseMultiUploadReturn;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Optional filter for which items to display
|
|
70
|
+
*/
|
|
71
|
+
filter?: (item: UploadItem) => boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Optional sorting function for items
|
|
75
|
+
*/
|
|
76
|
+
sortBy?: (a: UploadItem, b: UploadItem) => number;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render prop that receives upload list state and actions
|
|
80
|
+
*/
|
|
81
|
+
children: (props: UploadListRenderProps) => React.ReactNode;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Headless upload list component that provides flexible rendering for upload items.
|
|
86
|
+
* Uses render props pattern to give full control over how upload items are displayed.
|
|
87
|
+
*
|
|
88
|
+
* @param props - Upload list configuration and render prop
|
|
89
|
+
* @returns Rendered upload list using the provided render prop
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```tsx
|
|
93
|
+
* // Basic upload list with progress bars
|
|
94
|
+
* <UploadList multiUpload={multiUpload}>
|
|
95
|
+
* {({ items, actions }) => (
|
|
96
|
+
* <div>
|
|
97
|
+
* <h3>Upload Queue ({items.length} files)</h3>
|
|
98
|
+
* {items.map((item) => (
|
|
99
|
+
* <div key={item.id} style={{
|
|
100
|
+
* padding: '1rem',
|
|
101
|
+
* border: '1px solid #ccc',
|
|
102
|
+
* marginBottom: '0.5rem',
|
|
103
|
+
* borderRadius: '4px'
|
|
104
|
+
* }}>
|
|
105
|
+
* <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
106
|
+
* <span>{item.file.name}</span>
|
|
107
|
+
* <span>{item.state.status}</span>
|
|
108
|
+
* </div>
|
|
109
|
+
*
|
|
110
|
+
* {item.state.status === 'uploading' && (
|
|
111
|
+
* <div>
|
|
112
|
+
* <progress value={item.state.progress} max={100} />
|
|
113
|
+
* <span>{item.state.progress}%</span>
|
|
114
|
+
* <button onClick={() => actions.abortItem(item)}>Cancel</button>
|
|
115
|
+
* </div>
|
|
116
|
+
* )}
|
|
117
|
+
*
|
|
118
|
+
* {item.state.status === 'error' && (
|
|
119
|
+
* <div>
|
|
120
|
+
* <p style={{ color: 'red' }}>Error: {item.state.error?.message}</p>
|
|
121
|
+
* <button onClick={() => actions.retryItem(item)}>Retry</button>
|
|
122
|
+
* <button onClick={() => actions.removeItem(item.id)}>Remove</button>
|
|
123
|
+
* </div>
|
|
124
|
+
* )}
|
|
125
|
+
*
|
|
126
|
+
* {item.state.status === 'success' && (
|
|
127
|
+
* <div>
|
|
128
|
+
* <p style={{ color: 'green' }}>✓ Uploaded successfully</p>
|
|
129
|
+
* <button onClick={() => actions.removeItem(item.id)}>Remove</button>
|
|
130
|
+
* </div>
|
|
131
|
+
* )}
|
|
132
|
+
*
|
|
133
|
+
* {item.state.status === 'idle' && (
|
|
134
|
+
* <div>
|
|
135
|
+
* <button onClick={() => actions.startItem(item)}>Start Upload</button>
|
|
136
|
+
* <button onClick={() => actions.removeItem(item.id)}>Remove</button>
|
|
137
|
+
* </div>
|
|
138
|
+
* )}
|
|
139
|
+
* </div>
|
|
140
|
+
* ))}
|
|
141
|
+
* </div>
|
|
142
|
+
* )}
|
|
143
|
+
* </UploadList>
|
|
144
|
+
*
|
|
145
|
+
* // Upload list with status filtering and sorting
|
|
146
|
+
* <UploadList
|
|
147
|
+
* multiUpload={multiUpload}
|
|
148
|
+
* filter={(item) => item.state.status !== 'success'} // Hide successful uploads
|
|
149
|
+
* sortBy={(a, b) => {
|
|
150
|
+
* // Sort by status priority, then by filename
|
|
151
|
+
* const statusPriority = { error: 0, uploading: 1, idle: 2, success: 3, aborted: 4 };
|
|
152
|
+
* const aPriority = statusPriority[a.state.status];
|
|
153
|
+
* const bPriority = statusPriority[b.state.status];
|
|
154
|
+
*
|
|
155
|
+
* if (aPriority !== bPriority) {
|
|
156
|
+
* return aPriority - bPriority;
|
|
157
|
+
* }
|
|
158
|
+
*
|
|
159
|
+
* return a.file.name.localeCompare(b.file.name);
|
|
160
|
+
* }}
|
|
161
|
+
* >
|
|
162
|
+
* {({ items, itemsByStatus, multiUpload, actions }) => (
|
|
163
|
+
* <div>
|
|
164
|
+
* {itemsByStatus.error.length > 0 && (
|
|
165
|
+
* <div>
|
|
166
|
+
* <h4 style={{ color: 'red' }}>Failed Uploads ({itemsByStatus.error.length})</h4>
|
|
167
|
+
* {itemsByStatus.error.map((item) => (
|
|
168
|
+
* <UploadListItem key={item.id} item={item} actions={actions} />
|
|
169
|
+
* ))}
|
|
170
|
+
* </div>
|
|
171
|
+
* )}
|
|
172
|
+
*
|
|
173
|
+
* {itemsByStatus.uploading.length > 0 && (
|
|
174
|
+
* <div>
|
|
175
|
+
* <h4>Uploading ({itemsByStatus.uploading.length})</h4>
|
|
176
|
+
* {itemsByStatus.uploading.map((item) => (
|
|
177
|
+
* <UploadListItem key={item.id} item={item} actions={actions} />
|
|
178
|
+
* ))}
|
|
179
|
+
* </div>
|
|
180
|
+
* )}
|
|
181
|
+
*
|
|
182
|
+
* {itemsByStatus.idle.length > 0 && (
|
|
183
|
+
* <div>
|
|
184
|
+
* <h4>Pending ({itemsByStatus.idle.length})</h4>
|
|
185
|
+
* {itemsByStatus.idle.map((item) => (
|
|
186
|
+
* <UploadListItem key={item.id} item={item} actions={actions} />
|
|
187
|
+
* ))}
|
|
188
|
+
* </div>
|
|
189
|
+
* )}
|
|
190
|
+
* </div>
|
|
191
|
+
* )}
|
|
192
|
+
* </UploadList>
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
export function UploadList({
|
|
196
|
+
multiUpload,
|
|
197
|
+
filter,
|
|
198
|
+
sortBy,
|
|
199
|
+
children,
|
|
200
|
+
}: UploadListProps) {
|
|
201
|
+
// Apply filtering
|
|
202
|
+
let items = multiUpload.items;
|
|
203
|
+
if (filter) {
|
|
204
|
+
items = items.filter(filter);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Apply sorting
|
|
208
|
+
if (sortBy) {
|
|
209
|
+
items = [...items].sort(sortBy);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Group items by status
|
|
213
|
+
const itemsByStatus = {
|
|
214
|
+
idle: items.filter((item) => item.state.status === "idle"),
|
|
215
|
+
uploading: items.filter((item) => item.state.status === "uploading"),
|
|
216
|
+
success: items.filter((item) => item.state.status === "success"),
|
|
217
|
+
error: items.filter((item) => item.state.status === "error"),
|
|
218
|
+
aborted: items.filter((item) => item.state.status === "aborted"),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Create action helpers
|
|
222
|
+
const actions = {
|
|
223
|
+
removeItem: (id: string) => {
|
|
224
|
+
multiUpload.removeItem(id);
|
|
225
|
+
},
|
|
226
|
+
retryItem: (_item: UploadItem) => {
|
|
227
|
+
// Retry failed uploads using multiUpload method
|
|
228
|
+
multiUpload.retryFailed();
|
|
229
|
+
},
|
|
230
|
+
abortItem: (item: UploadItem) => {
|
|
231
|
+
// Remove the item to effectively abort it
|
|
232
|
+
multiUpload.removeItem(item.id);
|
|
233
|
+
},
|
|
234
|
+
startItem: (_item: UploadItem) => {
|
|
235
|
+
// Start all pending uploads
|
|
236
|
+
multiUpload.startAll();
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Create render props object
|
|
241
|
+
const renderProps: UploadListRenderProps = {
|
|
242
|
+
items,
|
|
243
|
+
itemsByStatus,
|
|
244
|
+
multiUpload,
|
|
245
|
+
actions,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return <>{children(renderProps)}</>;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Props for the SimpleUploadListItem component.
|
|
253
|
+
*
|
|
254
|
+
* @property item - The upload item to display
|
|
255
|
+
* @property actions - Action functions from UploadList render props
|
|
256
|
+
* @property className - Additional CSS class name
|
|
257
|
+
* @property style - Inline styles for the item container
|
|
258
|
+
* @property showDetails - Whether to display file size and upload details
|
|
259
|
+
*/
|
|
260
|
+
export interface SimpleUploadListItemProps {
|
|
261
|
+
/**
|
|
262
|
+
* The upload item to render
|
|
263
|
+
*/
|
|
264
|
+
item: UploadItem;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Actions from UploadList render props
|
|
268
|
+
*/
|
|
269
|
+
actions: UploadListRenderProps["actions"];
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Additional CSS class name
|
|
273
|
+
*/
|
|
274
|
+
className?: string;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Inline styles
|
|
278
|
+
*/
|
|
279
|
+
style?: React.CSSProperties;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Whether to show detailed information (file size, speed, etc.)
|
|
283
|
+
*/
|
|
284
|
+
showDetails?: boolean;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Pre-styled upload list item component with status indicators and action buttons.
|
|
289
|
+
* Displays file info, progress, errors, and contextual actions based on upload status.
|
|
290
|
+
*
|
|
291
|
+
* Features:
|
|
292
|
+
* - Status-specific color coding and icons
|
|
293
|
+
* - Progress bar for active uploads
|
|
294
|
+
* - Error message display
|
|
295
|
+
* - File size formatting
|
|
296
|
+
* - Contextual action buttons (start, cancel, retry, remove)
|
|
297
|
+
*
|
|
298
|
+
* @param props - Upload item and configuration
|
|
299
|
+
* @returns Styled upload list item component
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```tsx
|
|
303
|
+
* // Use with UploadList
|
|
304
|
+
* <UploadList multiUpload={multiUpload}>
|
|
305
|
+
* {({ items, actions }) => (
|
|
306
|
+
* <div>
|
|
307
|
+
* {items.map((item) => (
|
|
308
|
+
* <SimpleUploadListItem
|
|
309
|
+
* key={item.id}
|
|
310
|
+
* item={item}
|
|
311
|
+
* actions={actions}
|
|
312
|
+
* showDetails={true}
|
|
313
|
+
* />
|
|
314
|
+
* ))}
|
|
315
|
+
* </div>
|
|
316
|
+
* )}
|
|
317
|
+
* </UploadList>
|
|
318
|
+
*
|
|
319
|
+
* // Custom styling
|
|
320
|
+
* <SimpleUploadListItem
|
|
321
|
+
* item={uploadItem}
|
|
322
|
+
* actions={actions}
|
|
323
|
+
* className="my-upload-item"
|
|
324
|
+
* style={{ borderRadius: '12px', margin: '1rem' }}
|
|
325
|
+
* showDetails={true}
|
|
326
|
+
* />
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
export function SimpleUploadListItem({
|
|
330
|
+
item,
|
|
331
|
+
actions,
|
|
332
|
+
className = "",
|
|
333
|
+
style = {},
|
|
334
|
+
showDetails = true,
|
|
335
|
+
}: SimpleUploadListItemProps) {
|
|
336
|
+
const getStatusColor = (status: UploadStatus) => {
|
|
337
|
+
switch (status) {
|
|
338
|
+
case "idle":
|
|
339
|
+
return "#6c757d";
|
|
340
|
+
case "uploading":
|
|
341
|
+
return "#007bff";
|
|
342
|
+
case "success":
|
|
343
|
+
return "#28a745";
|
|
344
|
+
case "error":
|
|
345
|
+
return "#dc3545";
|
|
346
|
+
case "aborted":
|
|
347
|
+
return "#6c757d";
|
|
348
|
+
default:
|
|
349
|
+
return "#6c757d";
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const getStatusIcon = (status: UploadStatus) => {
|
|
354
|
+
switch (status) {
|
|
355
|
+
case "idle":
|
|
356
|
+
return "⏳";
|
|
357
|
+
case "uploading":
|
|
358
|
+
return "📤";
|
|
359
|
+
case "success":
|
|
360
|
+
return "✅";
|
|
361
|
+
case "error":
|
|
362
|
+
return "❌";
|
|
363
|
+
case "aborted":
|
|
364
|
+
return "⏹️";
|
|
365
|
+
default:
|
|
366
|
+
return "❓";
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const formatFileSize = (bytes: number) => {
|
|
371
|
+
if (bytes === 0) return "0 Bytes";
|
|
372
|
+
const k = 1024;
|
|
373
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
374
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
375
|
+
return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<div
|
|
380
|
+
className={`upload-list-item upload-list-item--${item.state.status} ${className}`}
|
|
381
|
+
style={{
|
|
382
|
+
padding: "12px",
|
|
383
|
+
border: "1px solid #e0e0e0",
|
|
384
|
+
borderRadius: "6px",
|
|
385
|
+
marginBottom: "8px",
|
|
386
|
+
backgroundColor: "#fff",
|
|
387
|
+
transition: "all 0.2s ease",
|
|
388
|
+
...style,
|
|
389
|
+
}}
|
|
390
|
+
>
|
|
391
|
+
{/* Header with filename and status */}
|
|
392
|
+
<div
|
|
393
|
+
style={{
|
|
394
|
+
display: "flex",
|
|
395
|
+
justifyContent: "space-between",
|
|
396
|
+
alignItems: "center",
|
|
397
|
+
marginBottom: "8px",
|
|
398
|
+
}}
|
|
399
|
+
>
|
|
400
|
+
<div
|
|
401
|
+
style={{ display: "flex", alignItems: "center", gap: "8px", flex: 1 }}
|
|
402
|
+
>
|
|
403
|
+
<span style={{ fontSize: "16px" }}>
|
|
404
|
+
{getStatusIcon(item.state.status)}
|
|
405
|
+
</span>
|
|
406
|
+
<span style={{ fontWeight: "500", flex: 1 }}>
|
|
407
|
+
{item.file instanceof File ? item.file.name : "File"}
|
|
408
|
+
</span>
|
|
409
|
+
</div>
|
|
410
|
+
<span
|
|
411
|
+
style={{
|
|
412
|
+
fontSize: "12px",
|
|
413
|
+
color: getStatusColor(item.state.status),
|
|
414
|
+
fontWeight: "500",
|
|
415
|
+
textTransform: "uppercase",
|
|
416
|
+
}}
|
|
417
|
+
>
|
|
418
|
+
{item.state.status}
|
|
419
|
+
</span>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
{/* Progress bar for uploading items */}
|
|
423
|
+
{item.state.status === "uploading" && (
|
|
424
|
+
<div style={{ marginBottom: "8px" }}>
|
|
425
|
+
<div
|
|
426
|
+
style={{
|
|
427
|
+
display: "flex",
|
|
428
|
+
justifyContent: "space-between",
|
|
429
|
+
alignItems: "center",
|
|
430
|
+
marginBottom: "4px",
|
|
431
|
+
}}
|
|
432
|
+
>
|
|
433
|
+
<span style={{ fontSize: "12px", color: "#666" }}>
|
|
434
|
+
{item.state.progress}%
|
|
435
|
+
</span>
|
|
436
|
+
{showDetails && item.state.totalBytes && (
|
|
437
|
+
<span style={{ fontSize: "12px", color: "#666" }}>
|
|
438
|
+
{formatFileSize(item.state.bytesUploaded)} /{" "}
|
|
439
|
+
{formatFileSize(item.state.totalBytes)}
|
|
440
|
+
</span>
|
|
441
|
+
)}
|
|
442
|
+
</div>
|
|
443
|
+
<div
|
|
444
|
+
style={{
|
|
445
|
+
width: "100%",
|
|
446
|
+
height: "6px",
|
|
447
|
+
backgroundColor: "#e0e0e0",
|
|
448
|
+
borderRadius: "3px",
|
|
449
|
+
overflow: "hidden",
|
|
450
|
+
}}
|
|
451
|
+
>
|
|
452
|
+
<div
|
|
453
|
+
style={{
|
|
454
|
+
width: `${item.state.progress}%`,
|
|
455
|
+
height: "100%",
|
|
456
|
+
backgroundColor: "#007bff",
|
|
457
|
+
transition: "width 0.2s ease",
|
|
458
|
+
}}
|
|
459
|
+
/>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
)}
|
|
463
|
+
|
|
464
|
+
{/* Details section */}
|
|
465
|
+
{showDetails && (
|
|
466
|
+
<div style={{ fontSize: "12px", color: "#666", marginBottom: "8px" }}>
|
|
467
|
+
{item.state.totalBytes && (
|
|
468
|
+
<span>{formatFileSize(item.state.totalBytes)}</span>
|
|
469
|
+
)}
|
|
470
|
+
{item.state.status === "uploading" && item.state.progress > 0 && (
|
|
471
|
+
<span> • Progress: {item.state.progress}%</span>
|
|
472
|
+
)}
|
|
473
|
+
{item.state.status === "error" && item.state.error && (
|
|
474
|
+
<div style={{ color: "#dc3545", marginTop: "4px" }}>
|
|
475
|
+
{item.state.error.message}
|
|
476
|
+
</div>
|
|
477
|
+
)}
|
|
478
|
+
</div>
|
|
479
|
+
)}
|
|
480
|
+
|
|
481
|
+
{/* Action buttons */}
|
|
482
|
+
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
|
|
483
|
+
{item.state.status === "idle" && (
|
|
484
|
+
<>
|
|
485
|
+
<button
|
|
486
|
+
type="button"
|
|
487
|
+
onClick={() => actions.startItem(item)}
|
|
488
|
+
style={{
|
|
489
|
+
padding: "4px 8px",
|
|
490
|
+
fontSize: "12px",
|
|
491
|
+
border: "1px solid #007bff",
|
|
492
|
+
backgroundColor: "#007bff",
|
|
493
|
+
color: "white",
|
|
494
|
+
borderRadius: "4px",
|
|
495
|
+
cursor: "pointer",
|
|
496
|
+
}}
|
|
497
|
+
>
|
|
498
|
+
Start
|
|
499
|
+
</button>
|
|
500
|
+
<button
|
|
501
|
+
type="button"
|
|
502
|
+
onClick={() => actions.removeItem(item.id)}
|
|
503
|
+
style={{
|
|
504
|
+
padding: "4px 8px",
|
|
505
|
+
fontSize: "12px",
|
|
506
|
+
border: "1px solid #6c757d",
|
|
507
|
+
backgroundColor: "transparent",
|
|
508
|
+
color: "#6c757d",
|
|
509
|
+
borderRadius: "4px",
|
|
510
|
+
cursor: "pointer",
|
|
511
|
+
}}
|
|
512
|
+
>
|
|
513
|
+
Remove
|
|
514
|
+
</button>
|
|
515
|
+
</>
|
|
516
|
+
)}
|
|
517
|
+
|
|
518
|
+
{item.state.status === "uploading" && (
|
|
519
|
+
<button
|
|
520
|
+
type="button"
|
|
521
|
+
onClick={() => actions.abortItem(item)}
|
|
522
|
+
style={{
|
|
523
|
+
padding: "4px 8px",
|
|
524
|
+
fontSize: "12px",
|
|
525
|
+
border: "1px solid #dc3545",
|
|
526
|
+
backgroundColor: "transparent",
|
|
527
|
+
color: "#dc3545",
|
|
528
|
+
borderRadius: "4px",
|
|
529
|
+
cursor: "pointer",
|
|
530
|
+
}}
|
|
531
|
+
>
|
|
532
|
+
Cancel
|
|
533
|
+
</button>
|
|
534
|
+
)}
|
|
535
|
+
|
|
536
|
+
{item.state.status === "error" && (
|
|
537
|
+
<>
|
|
538
|
+
<button
|
|
539
|
+
type="button"
|
|
540
|
+
onClick={() => actions.retryItem(item)}
|
|
541
|
+
style={{
|
|
542
|
+
padding: "4px 8px",
|
|
543
|
+
fontSize: "12px",
|
|
544
|
+
border: "1px solid #28a745",
|
|
545
|
+
backgroundColor: "#28a745",
|
|
546
|
+
color: "white",
|
|
547
|
+
borderRadius: "4px",
|
|
548
|
+
cursor: "pointer",
|
|
549
|
+
}}
|
|
550
|
+
>
|
|
551
|
+
Retry
|
|
552
|
+
</button>
|
|
553
|
+
<button
|
|
554
|
+
type="button"
|
|
555
|
+
onClick={() => actions.removeItem(item.id)}
|
|
556
|
+
style={{
|
|
557
|
+
padding: "4px 8px",
|
|
558
|
+
fontSize: "12px",
|
|
559
|
+
border: "1px solid #6c757d",
|
|
560
|
+
backgroundColor: "transparent",
|
|
561
|
+
color: "#6c757d",
|
|
562
|
+
borderRadius: "4px",
|
|
563
|
+
cursor: "pointer",
|
|
564
|
+
}}
|
|
565
|
+
>
|
|
566
|
+
Remove
|
|
567
|
+
</button>
|
|
568
|
+
</>
|
|
569
|
+
)}
|
|
570
|
+
|
|
571
|
+
{item.state.status === "success" && (
|
|
572
|
+
<button
|
|
573
|
+
type="button"
|
|
574
|
+
onClick={() => actions.removeItem(item.id)}
|
|
575
|
+
style={{
|
|
576
|
+
padding: "4px 8px",
|
|
577
|
+
fontSize: "12px",
|
|
578
|
+
border: "1px solid #6c757d",
|
|
579
|
+
backgroundColor: "transparent",
|
|
580
|
+
color: "#6c757d",
|
|
581
|
+
borderRadius: "4px",
|
|
582
|
+
cursor: "pointer",
|
|
583
|
+
}}
|
|
584
|
+
>
|
|
585
|
+
Remove
|
|
586
|
+
</button>
|
|
587
|
+
)}
|
|
588
|
+
|
|
589
|
+
{item.state.status === "aborted" && (
|
|
590
|
+
<>
|
|
591
|
+
<button
|
|
592
|
+
type="button"
|
|
593
|
+
onClick={() => actions.retryItem(item)}
|
|
594
|
+
style={{
|
|
595
|
+
padding: "4px 8px",
|
|
596
|
+
fontSize: "12px",
|
|
597
|
+
border: "1px solid #007bff",
|
|
598
|
+
backgroundColor: "#007bff",
|
|
599
|
+
color: "white",
|
|
600
|
+
borderRadius: "4px",
|
|
601
|
+
cursor: "pointer",
|
|
602
|
+
}}
|
|
603
|
+
>
|
|
604
|
+
Retry
|
|
605
|
+
</button>
|
|
606
|
+
<button
|
|
607
|
+
type="button"
|
|
608
|
+
onClick={() => actions.removeItem(item.id)}
|
|
609
|
+
style={{
|
|
610
|
+
padding: "4px 8px",
|
|
611
|
+
fontSize: "12px",
|
|
612
|
+
border: "1px solid #6c757d",
|
|
613
|
+
backgroundColor: "transparent",
|
|
614
|
+
color: "#6c757d",
|
|
615
|
+
borderRadius: "4px",
|
|
616
|
+
cursor: "pointer",
|
|
617
|
+
}}
|
|
618
|
+
>
|
|
619
|
+
Remove
|
|
620
|
+
</button>
|
|
621
|
+
</>
|
|
622
|
+
)}
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
);
|
|
626
|
+
}
|