Redesign file uploads: drag-and-drop zones for images and documents
Deploy to LXC / deploy (push) Successful in 20s
Deploy to LXC / deploy (push) Successful in 20s
New reusable components: - ImageUpload.svelte: drag-and-drop / click / paste zone with file icon, preview of selected filename, caption field, 50MB limit - DocumentUpload.svelte: same pattern for documents with description field instead of caption Applied to both device and component detail pages, replacing the old inline file input forms. Cleaner look matching modern upload UIs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
import { enhance } from '$app/forms';
|
||||
import { COMPONENT_CONDITIONS } from '$lib/constants.js';
|
||||
import { formatDate, timeAgo } from '$lib/utils/date.js';
|
||||
import ImageUpload from '$lib/components/ui/ImageUpload.svelte';
|
||||
import DocumentUpload from '$lib/components/ui/DocumentUpload.svelte';
|
||||
|
||||
let { data, form } = $props();
|
||||
const c = $derived(data.component);
|
||||
@@ -88,30 +90,9 @@
|
||||
<div class="space-y-6 lg:col-span-2">
|
||||
<!-- Images -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Images</h2>
|
||||
<button type="button" onclick={() => (showUploadForm = !showUploadForm)}
|
||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
|
||||
{showUploadForm ? 'Cancel' : 'Upload'}
|
||||
</button>
|
||||
</div>
|
||||
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Images</h2>
|
||||
|
||||
{#if showUploadForm}
|
||||
<form method="POST" action="?/uploadImage" enctype="multipart/form-data" use:enhance class="mb-4 flex flex-wrap items-end gap-3">
|
||||
<input type="file" name="image" accept="image/*" required
|
||||
onchange={(e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (file && file.size > 50 * 1024 * 1024) {
|
||||
alert('File too large. Maximum size is 50MB.');
|
||||
(e.target as HTMLInputElement).value = '';
|
||||
}
|
||||
}}
|
||||
class="text-sm text-gray-600 dark:text-gray-400" />
|
||||
<input type="text" name="caption" placeholder="Caption (optional)"
|
||||
class="rounded-md border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
||||
<button type="submit" class="rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700">Upload</button>
|
||||
</form>
|
||||
{/if}
|
||||
<ImageUpload action="?/uploadImage" />
|
||||
|
||||
{#if data.images.length === 0}
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">No images yet.</p>
|
||||
@@ -330,21 +311,9 @@
|
||||
|
||||
<!-- Documents -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Documents</h2>
|
||||
<button type="button" onclick={() => (showDocForm = !showDocForm)}
|
||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
|
||||
{showDocForm ? 'Cancel' : 'Upload'}
|
||||
</button>
|
||||
</div>
|
||||
{#if showDocForm}
|
||||
<form method="POST" action="?/uploadDocument" enctype="multipart/form-data" use:enhance class="mb-3 space-y-2">
|
||||
<input type="file" name="document" required class="text-sm text-gray-600 dark:text-gray-400" />
|
||||
<input type="text" name="description" placeholder="Description"
|
||||
class="w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
||||
<button type="submit" class="rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700">Upload</button>
|
||||
</form>
|
||||
{/if}
|
||||
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Documents</h2>
|
||||
|
||||
<DocumentUpload action="?/uploadDocument" />
|
||||
{#if data.documents.length === 0}
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">No documents.</p>
|
||||
{:else}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { DEVICE_CONDITIONS, DEVICE_LOG_TYPES } from '$lib/constants.js';
|
||||
import ImageUpload from '$lib/components/ui/ImageUpload.svelte';
|
||||
import DocumentUpload from '$lib/components/ui/DocumentUpload.svelte';
|
||||
import { formatDate, timeAgo } from '$lib/utils/date.js';
|
||||
|
||||
let { data, form } = $props();
|
||||
@@ -124,34 +126,13 @@
|
||||
<div class="space-y-6 lg:col-span-2">
|
||||
<!-- Images -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Images</h2>
|
||||
<button onclick={() => (showUploadForm = !showUploadForm)}
|
||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
|
||||
{showUploadForm ? 'Cancel' : 'Upload'}
|
||||
</button>
|
||||
</div>
|
||||
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Images</h2>
|
||||
|
||||
{#if form?.error}
|
||||
<div class="mb-3 rounded-md bg-red-50 p-3 text-sm text-red-700 dark:bg-red-900/30 dark:text-red-300">{form.error}</div>
|
||||
{/if}
|
||||
|
||||
{#if showUploadForm}
|
||||
<form method="POST" action="?/uploadImage" enctype="multipart/form-data" use:enhance class="mb-4 flex flex-wrap items-end gap-3">
|
||||
<input type="file" name="image" accept="image/*" required
|
||||
onchange={(e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (file && file.size > 50 * 1024 * 1024) {
|
||||
alert('File too large. Maximum size is 50MB.');
|
||||
(e.target as HTMLInputElement).value = '';
|
||||
}
|
||||
}}
|
||||
class="text-sm text-gray-600 dark:text-gray-400" />
|
||||
<input type="text" name="caption" placeholder="Caption (optional)"
|
||||
class="rounded-md border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
||||
<button type="submit" class="rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700">Upload</button>
|
||||
</form>
|
||||
{/if}
|
||||
<ImageUpload action="?/uploadImage" />
|
||||
|
||||
{#if data.images.length === 0}
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">No images yet.</p>
|
||||
@@ -657,22 +638,9 @@
|
||||
|
||||
<!-- Documents -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Documents</h2>
|
||||
<button onclick={() => (showDocForm = !showDocForm)}
|
||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
|
||||
{showDocForm ? 'Cancel' : 'Upload'}
|
||||
</button>
|
||||
</div>
|
||||
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Documents</h2>
|
||||
|
||||
{#if showDocForm}
|
||||
<form method="POST" action="?/uploadDocument" enctype="multipart/form-data" use:enhance class="mb-3 space-y-2">
|
||||
<input type="file" name="document" required class="text-sm text-gray-600 dark:text-gray-400" />
|
||||
<input type="text" name="description" placeholder="Description"
|
||||
class="w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
||||
<button type="submit" class="rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700">Upload</button>
|
||||
</form>
|
||||
{/if}
|
||||
<DocumentUpload action="?/uploadDocument" />
|
||||
|
||||
{#if data.documents.length === 0}
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">No documents.</p>
|
||||
|
||||
Reference in New Issue
Block a user