snice 3.8.0 → 3.9.0
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/bin/snice.js +8 -0
- package/dist/components/file-gallery/snice-file-gallery.d.ts +87 -0
- package/dist/components/file-gallery/snice-file-gallery.js +892 -0
- package/dist/components/file-gallery/snice-file-gallery.js.map +1 -0
- package/dist/components/file-gallery/snice-file-gallery.types.d.ts +72 -0
- package/dist/components/qr-reader/qr-decoder.d.ts +20 -0
- package/dist/components/qr-reader/qr-decoder.js +49 -0
- package/dist/components/qr-reader/qr-decoder.js.map +1 -0
- package/dist/components/qr-reader/qr-worker.d.ts +6 -0
- package/dist/components/qr-reader/qr-worker.js +64 -0
- package/dist/components/qr-reader/qr-worker.js.map +1 -0
- package/dist/components/qr-reader/snice-qr-reader.d.ts +39 -0
- package/dist/components/qr-reader/snice-qr-reader.js +436 -0
- package/dist/components/qr-reader/snice-qr-reader.js.map +1 -0
- package/dist/components/qr-reader/snice-qr-reader.types.d.ts +17 -0
- package/dist/components/qr-reader/zxing-reader.mjs +1582 -0
- package/dist/components/qr-reader/zxing-share.mjs +305 -0
- package/dist/components/qr-reader/zxing_reader.wasm +0 -0
- package/dist/components/zxing-reader-B3Rfebg9.js +1771 -0
- package/dist/components/zxing-reader-B3Rfebg9.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.iife.js +1 -1
- package/dist/symbols.cjs +1 -1
- package/dist/symbols.esm.js +1 -1
- package/dist/transitions.cjs +1 -1
- package/dist/transitions.esm.js +1 -1
- package/docs/ai/README.md +1 -1
- package/docs/ai/components/file-gallery.md +206 -0
- package/docs/ai/components/qr-reader.md +80 -0
- package/docs/components/file-gallery.md +692 -0
- package/docs/components/qr-reader.md +327 -0
- package/package.json +1 -1
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
# File Gallery Component
|
|
2
|
+
|
|
3
|
+
The `<snice-file-gallery>` component provides a file upload gallery with drag-and-drop support, image previews, pausable/resumable uploads, and progress tracking.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Basic Usage](#basic-usage)
|
|
7
|
+
- [Properties](#properties)
|
|
8
|
+
- [Methods](#methods)
|
|
9
|
+
- [Events](#events)
|
|
10
|
+
- [Upload Handler](#upload-handler)
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Examples](#examples)
|
|
13
|
+
|
|
14
|
+
## Basic Usage
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<snice-file-gallery></snice-file-gallery>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import 'snice/components/file-gallery/snice-file-gallery';
|
|
22
|
+
import { respond } from 'snice';
|
|
23
|
+
|
|
24
|
+
// Create upload handler
|
|
25
|
+
class UploadController {
|
|
26
|
+
@respond('file-gallery-upload')
|
|
27
|
+
async handleUpload(request) {
|
|
28
|
+
const { file, fileId, onProgress, signal } = request;
|
|
29
|
+
|
|
30
|
+
// Implement your upload logic here
|
|
31
|
+
return {
|
|
32
|
+
success: true,
|
|
33
|
+
fileId,
|
|
34
|
+
url: 'https://example.com/uploaded-file.jpg'
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const controller = new UploadController();
|
|
40
|
+
controller.attach?.(document.body);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Properties
|
|
44
|
+
|
|
45
|
+
| Property | Type | Default | Description |
|
|
46
|
+
|----------|------|---------|-------------|
|
|
47
|
+
| `accept` | `string` | `''` | Allowed file types (same as input accept) |
|
|
48
|
+
| `multiple` | `boolean` | `true` | Allow multiple file selection |
|
|
49
|
+
| `disabled` | `boolean` | `false` | Whether gallery is disabled |
|
|
50
|
+
| `maxSize` | `number` | `-1` | Maximum file size in bytes (-1 = no limit) |
|
|
51
|
+
| `maxFiles` | `number` | `-1` | Maximum number of files (-1 = no limit) |
|
|
52
|
+
| `view` | `'grid' \| 'list'` | `'grid'` | Display layout mode |
|
|
53
|
+
| `showProgress` | `boolean` | `true` | Show upload progress |
|
|
54
|
+
| `allowPause` | `boolean` | `true` | Allow pause/resume of uploads |
|
|
55
|
+
| `allowDelete` | `boolean` | `true` | Allow file deletion |
|
|
56
|
+
| `autoUpload` | `boolean` | `true` | Start upload immediately on file add |
|
|
57
|
+
| `showAddButton` | `boolean` | `false` | Show add button tile instead of drop zone |
|
|
58
|
+
| `hideAddButton` | `boolean` | `false` | Hide default add button (only show custom actions) |
|
|
59
|
+
| `files` | `GalleryFile[]` | `[]` | Current files (read-only) |
|
|
60
|
+
|
|
61
|
+
## Methods
|
|
62
|
+
|
|
63
|
+
### Getters
|
|
64
|
+
|
|
65
|
+
#### `files: GalleryFile[]`
|
|
66
|
+
Get all files in the gallery (read-only).
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const allFiles = gallery.files;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### `customActions: CustomAction[]`
|
|
73
|
+
Get all custom action buttons (read-only).
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const actions = gallery.customActions;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### `getFile(fileId: string): GalleryFile | undefined`
|
|
80
|
+
Get a specific file by ID.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const file = gallery.getFile('file-id-123');
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### `getCustomAction(actionId: string): CustomAction | undefined`
|
|
87
|
+
Get a specific custom action by ID.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const action = gallery.getCustomAction('action-id-456');
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### `isPending(fileId: string): boolean`
|
|
94
|
+
Check if a file upload is pending.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
if (gallery.isPending('file-id-123')) {
|
|
98
|
+
console.log('File is waiting to upload');
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### `isUploading(fileId: string): boolean`
|
|
103
|
+
Check if a file is currently uploading.
|
|
104
|
+
|
|
105
|
+
#### `isPaused(fileId: string): boolean`
|
|
106
|
+
Check if a file upload is paused.
|
|
107
|
+
|
|
108
|
+
#### `isCompleted(fileId: string): boolean`
|
|
109
|
+
Check if a file upload is completed.
|
|
110
|
+
|
|
111
|
+
#### `hasError(fileId: string): boolean`
|
|
112
|
+
Check if a file upload has an error.
|
|
113
|
+
|
|
114
|
+
#### `canAddFiles(): boolean`
|
|
115
|
+
Check if more files can be added (respects maxFiles limit).
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
if (gallery.canAddFiles()) {
|
|
119
|
+
gallery.openFilePicker();
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### File Management
|
|
124
|
+
|
|
125
|
+
#### `addFiles(files: FileList | File[]): void`
|
|
126
|
+
Add files to the gallery.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const fileInput = document.querySelector('input[type="file"]');
|
|
130
|
+
gallery.addFiles(fileInput.files);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `addFileWithPreview(file: File, previewDataUrl: string): void`
|
|
134
|
+
Add a file with a custom preview (e.g., from camera/canvas).
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const canvas = document.createElement('canvas');
|
|
138
|
+
// ... draw on canvas
|
|
139
|
+
canvas.toBlob((blob) => {
|
|
140
|
+
const file = new File([blob], 'photo.png', { type: 'image/png' });
|
|
141
|
+
const preview = canvas.toDataURL('image/png');
|
|
142
|
+
gallery.addFileWithPreview(file, preview);
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### `removeFile(fileId: string): void`
|
|
147
|
+
Remove a file from the gallery.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
gallery.removeFile('file-id-123');
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### `clear(): void`
|
|
154
|
+
Remove all files from the gallery.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
gallery.clear();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### `clearCompleted(): void`
|
|
161
|
+
Remove all completed files from the gallery.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
gallery.clearCompleted();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### `clearErrors(): void`
|
|
168
|
+
Remove all files with errors from the gallery.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
gallery.clearErrors();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Upload Control
|
|
175
|
+
|
|
176
|
+
#### `pauseUpload(fileId: string): void`
|
|
177
|
+
Pause an ongoing upload.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
gallery.pauseUpload('file-id-123');
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### `resumeUpload(fileId: string): Promise<void>`
|
|
184
|
+
Resume a paused upload.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
await gallery.resumeUpload('file-id-123');
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### `retryUpload(fileId: string): Promise<void>`
|
|
191
|
+
Retry a failed upload.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
await gallery.retryUpload('file-id-123');
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### `cancelUpload(fileId: string): void`
|
|
198
|
+
Cancel an upload and remove the file.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
gallery.cancelUpload('file-id-123');
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### `pauseAll(): void`
|
|
205
|
+
Pause all currently uploading files.
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
gallery.pauseAll();
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### `resumeAll(): void`
|
|
212
|
+
Resume all paused uploads.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
gallery.resumeAll();
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### `retryAll(): void`
|
|
219
|
+
Retry all failed uploads.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
gallery.retryAll();
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### `cancelAll(): void`
|
|
226
|
+
Cancel all active uploads and remove them.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
gallery.cancelAll();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Custom Actions
|
|
233
|
+
|
|
234
|
+
#### `addCustomAction(icon: string, text: string): string`
|
|
235
|
+
Add a custom action button to the gallery. Returns the action ID.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const cameraIcon = '<svg viewBox="0 0 24 24">...</svg>';
|
|
239
|
+
const actionId = gallery.addCustomAction(cameraIcon, 'Camera');
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### `removeCustomAction(actionId: string): void`
|
|
243
|
+
Remove a custom action button.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
gallery.removeCustomAction(actionId);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### `clearCustomActions(): void`
|
|
250
|
+
Remove all custom action buttons.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
gallery.clearCustomActions();
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Utility
|
|
257
|
+
|
|
258
|
+
#### `openFilePicker(): void`
|
|
259
|
+
Programmatically open the file picker dialog.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
gallery.openFilePicker();
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### `setFileBadge(fileId: string, badge: string, position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'): void`
|
|
266
|
+
Add a custom badge overlay to a file's preview thumbnail. Supports HTML content for avatars, icons, or custom elements.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// Add user avatar badge
|
|
270
|
+
const avatarHTML = `<div style="
|
|
271
|
+
width: 40px;
|
|
272
|
+
height: 40px;
|
|
273
|
+
border-radius: 50%;
|
|
274
|
+
background: #3b82f6;
|
|
275
|
+
color: white;
|
|
276
|
+
display: flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
justify-content: center;
|
|
279
|
+
font-weight: bold;
|
|
280
|
+
border: 2px solid white;
|
|
281
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
|
282
|
+
">JD</div>`;
|
|
283
|
+
|
|
284
|
+
gallery.setFileBadge('file-id-123', avatarHTML, 'top-right');
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### `removeFileBadge(fileId: string): void`
|
|
288
|
+
Remove a badge from a file.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
gallery.removeFileBadge('file-id-123');
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Events
|
|
295
|
+
|
|
296
|
+
### `@snice/files-change`
|
|
297
|
+
Fired when files are added or removed.
|
|
298
|
+
|
|
299
|
+
**Detail**: `{ files: GalleryFile[] }`
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
gallery.addEventListener('@snice/files-change', (e) => {
|
|
303
|
+
console.log('Files:', e.detail.files);
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### `@snice/upload-progress`
|
|
308
|
+
Fired during upload progress.
|
|
309
|
+
|
|
310
|
+
**Detail**: `{ file: GalleryFile, progress: number }`
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
gallery.addEventListener('@snice/upload-progress', (e) => {
|
|
314
|
+
console.log(`${e.detail.file.file.name}: ${e.detail.progress}%`);
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### `@snice/upload-complete`
|
|
319
|
+
Fired when an upload completes successfully.
|
|
320
|
+
|
|
321
|
+
**Detail**: `{ file: GalleryFile, url?: string }`
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
gallery.addEventListener('@snice/upload-complete', (e) => {
|
|
325
|
+
console.log('Upload complete:', e.detail.url);
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### `@snice/upload-error`
|
|
330
|
+
Fired when an upload fails.
|
|
331
|
+
|
|
332
|
+
**Detail**: `{ file: GalleryFile, error: string }`
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
gallery.addEventListener('@snice/upload-error', (e) => {
|
|
336
|
+
console.error('Upload error:', e.detail.error);
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### `@snice/custom-action-click`
|
|
341
|
+
Fired when a custom action button is clicked.
|
|
342
|
+
|
|
343
|
+
**Detail**: `{ actionId: string }`
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
gallery.addEventListener('@snice/custom-action-click', (e) => {
|
|
347
|
+
console.log('Custom action clicked:', e.detail.actionId);
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### `@snice/error`
|
|
352
|
+
Fired when a validation or general error occurs.
|
|
353
|
+
|
|
354
|
+
**Detail**: `{ message: string }`
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
gallery.addEventListener('@snice/error', (e) => {
|
|
358
|
+
console.error('Error:', e.detail.message);
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Upload Handler
|
|
363
|
+
|
|
364
|
+
The file gallery uses the `@request/@respond` pattern for uploads. You must implement an upload handler:
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { respond } from 'snice';
|
|
368
|
+
|
|
369
|
+
class UploadController {
|
|
370
|
+
@respond('file-gallery-upload')
|
|
371
|
+
async handleUpload(request) {
|
|
372
|
+
const { file, fileId, onProgress, signal } = request;
|
|
373
|
+
|
|
374
|
+
// Create FormData
|
|
375
|
+
const formData = new FormData();
|
|
376
|
+
formData.append('file', file);
|
|
377
|
+
|
|
378
|
+
// Upload with progress tracking
|
|
379
|
+
return new Promise((resolve, reject) => {
|
|
380
|
+
const xhr = new XMLHttpRequest();
|
|
381
|
+
|
|
382
|
+
// Track upload progress
|
|
383
|
+
xhr.upload.addEventListener('progress', (e) => {
|
|
384
|
+
if (e.lengthComputable && onProgress) {
|
|
385
|
+
onProgress(e.loaded / e.total);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Handle completion
|
|
390
|
+
xhr.addEventListener('load', () => {
|
|
391
|
+
if (xhr.status === 200) {
|
|
392
|
+
const response = JSON.parse(xhr.responseText);
|
|
393
|
+
resolve({
|
|
394
|
+
success: true,
|
|
395
|
+
fileId,
|
|
396
|
+
url: response.url
|
|
397
|
+
});
|
|
398
|
+
} else {
|
|
399
|
+
reject(new Error('Upload failed'));
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Handle errors
|
|
404
|
+
xhr.addEventListener('error', () => {
|
|
405
|
+
reject(new Error('Network error'));
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Handle cancellation
|
|
409
|
+
if (signal) {
|
|
410
|
+
signal.addEventListener('abort', () => {
|
|
411
|
+
xhr.abort();
|
|
412
|
+
reject(new Error('Upload cancelled'));
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Send request
|
|
417
|
+
xhr.open('POST', '/api/upload');
|
|
418
|
+
xhr.send(formData);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Attach controller to document
|
|
424
|
+
const controller = new UploadController();
|
|
425
|
+
controller.attach?.(document.body);
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## Features
|
|
429
|
+
|
|
430
|
+
- **Drag and Drop**: Native drag-and-drop support with visual feedback
|
|
431
|
+
- **Image Preview**: Automatic thumbnail generation for image files
|
|
432
|
+
- **Pausable Uploads**: Pause and resume uploads using AbortController
|
|
433
|
+
- **Progress Tracking**: Real-time upload progress for each file
|
|
434
|
+
- **File Management**: Add, remove, pause, resume, and retry uploads
|
|
435
|
+
- **View Modes**: Toggle between grid and list layouts
|
|
436
|
+
- **Validation**: File size and type validation with error messaging
|
|
437
|
+
- **Auto Upload**: Optional automatic upload on file add
|
|
438
|
+
- **Accessibility**: Full keyboard support and ARIA attributes
|
|
439
|
+
|
|
440
|
+
## Examples
|
|
441
|
+
|
|
442
|
+
### Basic Gallery
|
|
443
|
+
|
|
444
|
+
```html
|
|
445
|
+
<snice-file-gallery></snice-file-gallery>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Image Gallery
|
|
449
|
+
|
|
450
|
+
```html
|
|
451
|
+
<snice-file-gallery accept="image/*"></snice-file-gallery>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Manual Upload Mode
|
|
455
|
+
|
|
456
|
+
```html
|
|
457
|
+
<snice-file-gallery id="manual-gallery" auto-upload="false"></snice-file-gallery>
|
|
458
|
+
|
|
459
|
+
<script>
|
|
460
|
+
const gallery = document.getElementById('manual-gallery');
|
|
461
|
+
|
|
462
|
+
// Upload files manually
|
|
463
|
+
async function uploadAll() {
|
|
464
|
+
const files = gallery.files;
|
|
465
|
+
for (const file of files) {
|
|
466
|
+
if (file.uploadStatus === 'pending') {
|
|
467
|
+
await gallery.resumeUpload(file.id);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
</script>
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### File Limits
|
|
475
|
+
|
|
476
|
+
```html
|
|
477
|
+
<snice-file-gallery
|
|
478
|
+
max-files="3"
|
|
479
|
+
max-size="2097152"
|
|
480
|
+
></snice-file-gallery>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### List View
|
|
484
|
+
|
|
485
|
+
```html
|
|
486
|
+
<snice-file-gallery view="list"></snice-file-gallery>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Custom File Types
|
|
490
|
+
|
|
491
|
+
```html
|
|
492
|
+
<snice-file-gallery
|
|
493
|
+
accept=".pdf,.doc,.docx,.txt"
|
|
494
|
+
allow-pause="false"
|
|
495
|
+
></snice-file-gallery>
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Add Button Mode
|
|
499
|
+
|
|
500
|
+
```html
|
|
501
|
+
<!-- Instead of drop zone, shows a plus tile in the gallery -->
|
|
502
|
+
<snice-file-gallery
|
|
503
|
+
show-add-button="true"
|
|
504
|
+
max-files="6"
|
|
505
|
+
></snice-file-gallery>
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Tracking Upload Events
|
|
509
|
+
|
|
510
|
+
```html
|
|
511
|
+
<snice-file-gallery id="gallery"></snice-file-gallery>
|
|
512
|
+
|
|
513
|
+
<script>
|
|
514
|
+
const gallery = document.getElementById('gallery');
|
|
515
|
+
|
|
516
|
+
gallery.addEventListener('@snice/files-change', (e) => {
|
|
517
|
+
console.log('Files changed:', e.detail.files);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
gallery.addEventListener('@snice/upload-progress', (e) => {
|
|
521
|
+
console.log(`${e.detail.file.file.name}: ${e.detail.progress}%`);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
gallery.addEventListener('@snice/upload-complete', (e) => {
|
|
525
|
+
console.log('Upload complete:', e.detail.file.file.name);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
gallery.addEventListener('@snice/upload-error', (e) => {
|
|
529
|
+
console.error('Upload failed:', e.detail.error);
|
|
530
|
+
});
|
|
531
|
+
</script>
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Programmatic File Management
|
|
535
|
+
|
|
536
|
+
```html
|
|
537
|
+
<snice-file-gallery id="gallery"></snice-file-gallery>
|
|
538
|
+
<button onclick="pauseAll()">Pause All</button>
|
|
539
|
+
<button onclick="resumeAll()">Resume All</button>
|
|
540
|
+
<button onclick="retryAll()">Retry All</button>
|
|
541
|
+
<button onclick="clearCompleted()">Clear Completed</button>
|
|
542
|
+
<button onclick="clearAll()">Clear All</button>
|
|
543
|
+
|
|
544
|
+
<script>
|
|
545
|
+
const gallery = document.getElementById('gallery');
|
|
546
|
+
|
|
547
|
+
function pauseAll() {
|
|
548
|
+
gallery.pauseAll();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function resumeAll() {
|
|
552
|
+
gallery.resumeAll();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function retryAll() {
|
|
556
|
+
gallery.retryAll();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function clearCompleted() {
|
|
560
|
+
gallery.clearCompleted();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function clearAll() {
|
|
564
|
+
if (confirm('Clear all files?')) {
|
|
565
|
+
gallery.clear();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
</script>
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Custom Badges for Collaboration
|
|
572
|
+
|
|
573
|
+
Use badges to show user avatars on files in collaborative scenarios:
|
|
574
|
+
|
|
575
|
+
```html
|
|
576
|
+
<snice-file-gallery id="collab-gallery" show-add-button="true"></snice-file-gallery>
|
|
577
|
+
|
|
578
|
+
<script>
|
|
579
|
+
const gallery = document.getElementById('collab-gallery');
|
|
580
|
+
|
|
581
|
+
// Simulate collaborative file uploads with user badges
|
|
582
|
+
const users = [
|
|
583
|
+
{ name: 'John Doe', initials: 'JD', color: '#3b82f6', position: 'top-right' },
|
|
584
|
+
{ name: 'Jane Smith', initials: 'JS', color: '#ef4444', position: 'top-left' },
|
|
585
|
+
{ name: 'Bob Wilson', initials: 'BW', color: '#10b981', position: 'bottom-right' },
|
|
586
|
+
];
|
|
587
|
+
|
|
588
|
+
gallery.addEventListener('@snice/files-change', (e) => {
|
|
589
|
+
const newFiles = e.detail.files.filter(f => !f.badge);
|
|
590
|
+
|
|
591
|
+
newFiles.forEach((file, index) => {
|
|
592
|
+
const user = users[index % users.length];
|
|
593
|
+
|
|
594
|
+
const avatarHTML = `<div style="
|
|
595
|
+
width: 40px;
|
|
596
|
+
height: 40px;
|
|
597
|
+
border-radius: 50%;
|
|
598
|
+
background: ${user.color};
|
|
599
|
+
color: white;
|
|
600
|
+
display: flex;
|
|
601
|
+
align-items: center;
|
|
602
|
+
justify-content: center;
|
|
603
|
+
font-weight: bold;
|
|
604
|
+
font-size: 14px;
|
|
605
|
+
border: 2px solid white;
|
|
606
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
|
607
|
+
">${user.initials}</div>`;
|
|
608
|
+
|
|
609
|
+
gallery.setFileBadge(file.id, avatarHTML, user.position);
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
</script>
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Custom Action Buttons
|
|
616
|
+
|
|
617
|
+
```html
|
|
618
|
+
<snice-file-gallery id="gallery" show-add-button="true"></snice-file-gallery>
|
|
619
|
+
|
|
620
|
+
<script>
|
|
621
|
+
const gallery = document.getElementById('gallery');
|
|
622
|
+
|
|
623
|
+
// Add camera action
|
|
624
|
+
const cameraIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
625
|
+
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" stroke-width="2"/>
|
|
626
|
+
<circle cx="12" cy="13" r="4" stroke-width="2"/>
|
|
627
|
+
</svg>`;
|
|
628
|
+
|
|
629
|
+
const cameraActionId = gallery.addCustomAction(cameraIcon, 'Camera');
|
|
630
|
+
|
|
631
|
+
// Handle camera action
|
|
632
|
+
gallery.addEventListener('@snice/custom-action-click', (e) => {
|
|
633
|
+
if (e.detail.actionId === cameraActionId) {
|
|
634
|
+
// Open camera interface
|
|
635
|
+
openCamera().then((imageBlob) => {
|
|
636
|
+
const file = new File([imageBlob], `photo-${Date.now()}.jpg`, { type: 'image/jpeg' });
|
|
637
|
+
const preview = URL.createObjectURL(imageBlob);
|
|
638
|
+
gallery.addFileWithPreview(file, preview);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
</script>
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Advanced Upload Handler with Retry
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
import { respond } from 'snice';
|
|
649
|
+
|
|
650
|
+
class UploadController {
|
|
651
|
+
@respond('file-gallery-upload')
|
|
652
|
+
async handleUpload(request) {
|
|
653
|
+
const { file, fileId, onProgress, signal } = request;
|
|
654
|
+
|
|
655
|
+
const uploadToServer = async (retries = 3) => {
|
|
656
|
+
try {
|
|
657
|
+
const formData = new FormData();
|
|
658
|
+
formData.append('file', file);
|
|
659
|
+
|
|
660
|
+
const response = await fetch('/api/upload', {
|
|
661
|
+
method: 'POST',
|
|
662
|
+
body: formData,
|
|
663
|
+
signal,
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
if (!response.ok) {
|
|
667
|
+
throw new Error(`Upload failed: ${response.status}`);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const data = await response.json();
|
|
671
|
+
return {
|
|
672
|
+
success: true,
|
|
673
|
+
fileId,
|
|
674
|
+
url: data.url
|
|
675
|
+
};
|
|
676
|
+
} catch (error) {
|
|
677
|
+
if (retries > 0 && error.name !== 'AbortError') {
|
|
678
|
+
console.log(`Retrying upload (${retries} attempts left)...`);
|
|
679
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
680
|
+
return uploadToServer(retries - 1);
|
|
681
|
+
}
|
|
682
|
+
throw error;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
return uploadToServer();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const controller = new UploadController();
|
|
691
|
+
controller.attach?.(document.body);
|
|
692
|
+
```
|