Skip to content

Commit ff67ea7

Browse files
committed
chore: update playground
1 parent 8cd0b99 commit ff67ea7

File tree

1 file changed

+155
-48
lines changed

1 file changed

+155
-48
lines changed

playground/app/pages/components/uploader.vue

Lines changed: 155 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<script setup lang="ts">
22
import type { UploadFile } from "#ui-elements"
33
import {
4-
PluginAzureStorage,
4+
PluginAzureDataLake,
55
ValidatorMaxFiles,
6-
ValidatorMaxfileSize,
6+
ValidatorMaxFileSize,
77
ValidatorAllowedFileTypes,
88
PluginThumbnailGenerator,
99
PluginImageCompressor,
@@ -15,43 +15,48 @@
1515
// Configuration state
1616
const config = ref({
1717
maxFiles: 5,
18-
maxFileSize: 5 * 1024 * 1024, // 5MB
19-
allowedFileTypes: ["image/jpeg", "image/png", "image/webp"],
18+
maxFileSize: 100 * 1024 * 1024, // 100MB (to support videos)
19+
allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "video/mp4", "video/webm"],
2020
thumbnails: true,
2121
imageCompression: false,
2222
autoProceed: false,
23+
thumbnailMaxWidth: 200,
24+
thumbnailMaxHeight: 200,
25+
videoCaptureTime: 1,
2326
})
2427
25-
// Create uploader with all plugins explicitly defined
28+
// Create uploader with new storage/processing plugin separation
2629
const createUploader = () => {
27-
const plugins = []
30+
const processingPlugins = []
2831
2932
// Validators
3033
if (config.value.maxFiles) {
31-
plugins.push(ValidatorMaxFiles({ maxFiles: config.value.maxFiles }))
34+
processingPlugins.push(ValidatorMaxFiles({ maxFiles: config.value.maxFiles }))
3235
}
3336
3437
if (config.value.maxFileSize) {
35-
plugins.push(ValidatorMaxfileSize({ maxFileSize: config.value.maxFileSize }))
38+
processingPlugins.push(ValidatorMaxFileSize({ maxFileSize: config.value.maxFileSize }))
3639
}
3740
3841
if (config.value.allowedFileTypes.length > 0) {
39-
plugins.push(ValidatorAllowedFileTypes({ allowedFileTypes: config.value.allowedFileTypes }))
42+
processingPlugins.push(ValidatorAllowedFileTypes({ allowedFileTypes: config.value.allowedFileTypes }))
4043
}
4144
42-
// Processors
45+
// Thumbnails (with improved API)
4346
if (config.value.thumbnails) {
44-
plugins.push(
47+
processingPlugins.push(
4548
PluginThumbnailGenerator({
46-
width: 128,
47-
height: 128,
48-
quality: 1,
49+
maxWidth: config.value.thumbnailMaxWidth,
50+
maxHeight: config.value.thumbnailMaxHeight,
51+
quality: 0.7,
52+
videoCaptureTime: config.value.videoCaptureTime,
4953
}),
5054
)
5155
}
5256
57+
// Image Compression
5358
if (config.value.imageCompression) {
54-
plugins.push(
59+
processingPlugins.push(
5560
PluginImageCompressor({
5661
maxWidth: 1920,
5762
maxHeight: 1920,
@@ -63,33 +68,35 @@
6368
)
6469
}
6570
66-
// Storage (optional)
67-
if (useStoragePlugin.value) {
68-
plugins.push(
69-
PluginAzureStorage({
71+
// Storage plugin (NEW: separate from processing plugins)
72+
const storagePlugin = useStoragePlugin.value
73+
? PluginAzureDataLake({
7074
sasURL: "mock://azure-storage",
7175
path: "uploads/playground",
7276
metadata: { source: "playground" },
73-
}),
74-
)
75-
}
77+
retries: 3,
78+
retryDelay: 1000,
79+
})
80+
: undefined
7681
7782
return useUploadManager({
7883
autoProceed: config.value.autoProceed,
79-
plugins,
84+
storage: storagePlugin, // NEW: separate storage option
85+
plugins: processingPlugins,
8086
})
8187
}
8288
8389
// Initialize uploader
8490
let uploader = createUploader()
85-
let { files, totalProgress, addFiles, removeFile, clearFiles, upload, onUpload, on } = uploader
91+
let { files, totalProgress, status, addFiles, removeFile, clearFiles, upload, onUpload, on } = uploader
8692
87-
// Recreate uploader when storage mode changes
88-
watch(useStoragePlugin, () => {
93+
// Recreate uploader when config changes
94+
watch([useStoragePlugin, () => config.value.thumbnails, () => config.value.imageCompression], () => {
8995
uploader = createUploader()
9096
const newUploader = uploader
9197
files = newUploader.files
9298
totalProgress = newUploader.totalProgress
99+
status = newUploader.status
93100
addFiles = newUploader.addFiles
94101
removeFile = newUploader.removeFile
95102
clearFiles = newUploader.clearFiles
@@ -113,7 +120,7 @@
113120
114121
if (progress >= 100) {
115122
clearInterval(interval)
116-
resolve(`https://example.com/uploads/${file.id}`)
123+
resolve({ url: `https://example.com/uploads/${file.id}` })
117124
}
118125
}, 200)
119126
})
@@ -123,27 +130,53 @@
123130
setupEventListeners()
124131
}
125132
133+
// Get toast for notifications
134+
const toast = useToast()
135+
126136
// Setup event listeners
127137
const setupEventListeners = () => {
128138
on("file:added", (file) => {
129-
console.log(`File added: ${file.name} (${formatFileSize(file.size)})`)
139+
console.log(`✅ File added: ${file.name} (${formatFileSize(file.size)})`)
140+
if (file.meta.thumbnail) {
141+
console.log(`📸 Thumbnail generated for ${file.name}`)
142+
}
143+
})
144+
145+
on("file:removed", (file) => {
146+
console.log(`🗑️ File removed: ${file.name}`)
130147
})
131148
132149
on("file:error", ({ file, error }) => {
133-
console.log(`Error uploading ${file.name}: ${error.message}`)
150+
console.error(`❌ Error with ${file.name}:`, error.message)
151+
152+
// Show toast notification for validation errors
153+
toast.add({
154+
title: "File Error",
155+
description: `${file.name}: ${error.message}`,
156+
color: "error",
157+
timeout: 5000,
158+
})
159+
})
160+
161+
on("upload:start", () => {
162+
console.log("🚀 Upload started")
134163
})
135164
136165
on("upload:complete", (files) => {
137-
console.log("Upload complete:", files)
166+
console.log("Upload complete:", files.length, "files")
138167
})
139168
140169
// Image compression events
170+
on("image-compressor:start", (payload: any) => {
171+
console.log("[Compression] Started for", payload.file.name)
172+
})
173+
141174
on("image-compressor:complete", (payload: any) => {
142-
console.log("[Compression] Completed", payload)
175+
console.log("[Compression] Completed", payload.file.name, payload.compressionRatio + "% saved")
143176
})
144177
145178
on("image-compressor:skip", (payload: any) => {
146-
console.log("[Compression] Skipped", payload)
179+
console.log("[Compression] ⏭️ Skipped", payload.file.name, "-", payload.reason)
147180
})
148181
}
149182
@@ -155,8 +188,29 @@
155188
const handleFileSelect = async (event: Event) => {
156189
const input = event.target as HTMLInputElement
157190
if (input.files) {
158-
await addFiles(Array.from(input.files))
191+
const fileCount = input.files.length
192+
const addedFiles = await addFiles(Array.from(input.files))
193+
194+
// Show summary toast
195+
const failedCount = fileCount - addedFiles.length
196+
if (failedCount === 0 && addedFiles.length > 0) {
197+
toast.add({
198+
title: "Files Added",
199+
description: `Successfully added ${addedFiles.length} file${addedFiles.length > 1 ? "s" : ""}`,
200+
color: "success",
201+
timeout: 3000,
202+
})
203+
} else if (failedCount > 0 && addedFiles.length > 0) {
204+
toast.add({
205+
title: "Partial Success",
206+
description: `Added ${addedFiles.length} file${addedFiles.length > 1 ? "s" : ""}, ${failedCount} failed`,
207+
color: "warning",
208+
timeout: 3000,
209+
})
210+
}
159211
}
212+
// Reset input to allow selecting the same file again
213+
if (input) input.value = ""
160214
}
161215
162216
const triggerFileSelect = () => {
@@ -187,9 +241,25 @@
187241
}
188242
}
189243
244+
const getStatusIcon = (status: string) => {
245+
switch (status) {
246+
case "waiting":
247+
return "i-heroicons-clock"
248+
case "uploading":
249+
return "i-heroicons-arrow-up-tray"
250+
case "complete":
251+
return "i-heroicons-check-circle"
252+
case "error":
253+
return "i-heroicons-x-circle"
254+
default:
255+
return "i-heroicons-document"
256+
}
257+
}
258+
190259
const getCompressionInfo = (file: UploadFile) => {
191260
if (file.meta.compressed && typeof file.meta.compressionRatio === "number") {
192-
return `Compressed: ${file.meta.compressionRatio}% saved`
261+
const saved = file.meta.compressionRatio
262+
return `Compressed: ${saved}% smaller`
193263
}
194264
return null
195265
}
@@ -208,14 +278,15 @@
208278
209279
// Available file types for testing
210280
const fileTypePresets = [
211-
{ label: "Images", value: ["image/jpeg", "image/png", "image/webp"] },
281+
{ label: "Images Only", value: ["image/jpeg", "image/png", "image/webp"] },
212282
{ label: "Images + GIF", value: ["image/jpeg", "image/png", "image/webp", "image/gif"] },
213-
{ label: "Documents", value: ["application/pdf", "application/msword"] },
214-
{ label: "Videos", value: ["video/mp4", "video/webm"] },
283+
{ label: "Images + Videos", value: ["image/jpeg", "image/png", "image/webp", "video/mp4", "video/webm"] },
284+
{ label: "Videos Only", value: ["video/mp4", "video/webm", "video/quicktime"] },
285+
{ label: "Documents", value: ["application/pdf", "application/msword", "text/plain"] },
215286
{ label: "All Files", value: [] },
216287
]
217288
218-
const selectedPreset = ref(0)
289+
const selectedPreset = ref(2) // Images + Videos by default
219290
const applyPreset = (index: number) => {
220291
selectedPreset.value = index
221292
const preset = fileTypePresets[index]
@@ -268,11 +339,25 @@
268339
</div>
269340

270341
<!-- Thumbnails -->
271-
<div>
342+
<div class="space-y-2">
272343
<label class="flex items-center gap-2">
273344
<UCheckbox v-model="config.thumbnails" />
274345
<span class="text-sm font-medium">Generate Thumbnails</span>
275346
</label>
347+
<div v-if="config.thumbnails" class="ml-6 space-y-2">
348+
<div>
349+
<label class="block text-xs text-gray-600 dark:text-gray-400 mb-1">Max Width (px)</label>
350+
<UInput v-model.number="config.thumbnailMaxWidth" type="number" min="50" max="500" size="sm" />
351+
</div>
352+
<div>
353+
<label class="block text-xs text-gray-600 dark:text-gray-400 mb-1">Max Height (px)</label>
354+
<UInput v-model.number="config.thumbnailMaxHeight" type="number" min="50" max="500" size="sm" />
355+
</div>
356+
<div>
357+
<label class="block text-xs text-gray-600 dark:text-gray-400 mb-1">Video Capture Time (s)</label>
358+
<UInput v-model.number="config.videoCaptureTime" type="number" min="0" max="10" step="0.5" size="sm" />
359+
</div>
360+
</div>
276361
</div>
277362

278363
<!-- Image Compression -->
@@ -343,12 +428,15 @@
343428

344429
<!-- File List -->
345430
<div class="space-y-3">
346-
<div v-for="file in files" :key="file.id" class="flex items-start gap-4 p-3 border dark:border-gray-700 rounded-lg">
431+
<div v-for="file in files" :key="file.id" class="flex items-start gap-4 p-3 border dark:border-gray-700 rounded-lg hover:border-gray-300 dark:hover:border-gray-600 transition-colors">
347432
<!-- Thumbnail -->
348433
<div class="shrink-0">
349-
<img v-if="file.preview" :src="file.preview" class="w-16 h-16 object-cover rounded" :alt="file.name" />
350-
<div v-else class="w-16 h-16 bg-gray-100 dark:bg-gray-800 rounded flex items-center justify-center">
351-
<UIcon name="i-heroicons-document" class="w-8 h-8 text-gray-400" />
434+
<img v-if="file.meta.thumbnail" :src="file.meta.thumbnail" class="w-20 h-20 object-cover rounded border dark:border-gray-700" :alt="file.name" />
435+
<div v-else-if="file.mimeType.startsWith('image/') || file.mimeType.startsWith('video/')" class="w-20 h-20 bg-gray-100 dark:bg-gray-800 rounded flex items-center justify-center border dark:border-gray-700">
436+
<UIcon :name="file.mimeType.startsWith('video/') ? 'i-heroicons-film' : 'i-heroicons-photo'" class="w-10 h-10 text-gray-400" />
437+
</div>
438+
<div v-else class="w-20 h-20 bg-gray-100 dark:bg-gray-800 rounded flex items-center justify-center border dark:border-gray-700">
439+
<UIcon name="i-heroicons-document" class="w-10 h-10 text-gray-400" />
352440
</div>
353441
</div>
354442

@@ -357,26 +445,45 @@
357445
<div class="flex items-start justify-between gap-2 mb-1">
358446
<div class="flex-1 min-w-0">
359447
<p class="font-medium truncate">{{ file.name }}</p>
360-
<p class="text-sm text-gray-600 dark:text-gray-400">{{ formatFileSize(file.size) }} • {{ file.mimeType }}</p>
448+
<p class="text-sm text-gray-600 dark:text-gray-400">
449+
{{ formatFileSize(file.size) }} • {{ file.mimeType }}
450+
</p>
361451
</div>
362-
<UBadge :color="getStatusColor(file.status)">
452+
<UBadge :color="getStatusColor(file.status)" :icon="getStatusIcon(file.status)">
363453
{{ file.status }}
364454
</UBadge>
365455
</div>
366456

367457
<!-- Progress Bar -->
368-
<div v-if="file.status === 'uploading' || file.status === 'complete'" class="mt-2">
458+
<div v-if="file.status === 'uploading'" class="mt-2">
459+
<div class="flex items-center gap-2 mb-1">
460+
<span class="text-xs text-gray-600 dark:text-gray-400">{{ file.progress.percentage }}%</span>
461+
</div>
369462
<UProgress :model-value="file.progress.percentage" />
370463
</div>
371464

465+
<div v-else-if="file.status === 'complete'" class="mt-2">
466+
<div class="flex items-center gap-1 text-xs text-green-600 dark:text-green-400">
467+
<UIcon name="i-heroicons-check-circle" class="w-4 h-4" />
468+
<span>Upload complete</span>
469+
</div>
470+
</div>
471+
372472
<!-- Error Message -->
373473
<div v-if="file.error" class="mt-2">
374474
<UAlert color="error" variant="soft" :title="file.error.message" />
375475
</div>
376476

377477
<!-- Meta Info -->
378-
<div v-if="getCompressionInfo(file)" class="mt-2 text-xs text-gray-500">
379-
<span>{{ getCompressionInfo(file) }}</span>
478+
<div v-if="file.meta.thumbnail || getCompressionInfo(file)" class="mt-2 flex gap-3 text-xs text-gray-500">
479+
<span v-if="file.meta.thumbnail" class="flex items-center gap-1">
480+
<UIcon name="i-heroicons-photo" class="w-3 h-3" />
481+
Thumbnail generated
482+
</span>
483+
<span v-if="getCompressionInfo(file)" class="flex items-center gap-1">
484+
<UIcon name="i-heroicons-archive-box-arrow-down" class="w-3 h-3" />
485+
{{ getCompressionInfo(file) }}
486+
</span>
380487
</div>
381488
</div>
382489

0 commit comments

Comments
 (0)