web/src/routes/upload/+page.svelte (view raw)
1<script lang="ts">
2 import { uploadImage, suggestTags } from '$lib/api';
3
4 let file = $state<File | null>(null);
5 let tags = $state('');
6 let error = $state('');
7 let success = $state('');
8 let loading = $state(false);
9 let suggestions = $state<string[]>([]);
10 let showSuggestions = $state(false);
11
12 async function onTagInput() {
13 const q = tags.split(',').pop()?.trim() || '';
14 if (q.length < 1) {
15 suggestions = [];
16 showSuggestions = false;
17 return;
18 }
19 try {
20 const result = await suggestTags(q);
21 suggestions = result.map((t) => t.tag);
22 showSuggestions = suggestions.length > 0;
23 } catch {
24 suggestions = [];
25 showSuggestions = false;
26 }
27 }
28
29 function selectSuggestion(s: string) {
30 const parts = tags.split(',').slice(0, -1);
31 parts.push(s);
32 tags = parts.join(', ') + ', ';
33 showSuggestions = false;
34 }
35
36 async function handleSubmit(e: Event) {
37 e.preventDefault();
38 if (!file) return;
39 error = '';
40 success = '';
41 loading = true;
42 try {
43 const tagList = tags.split(',').map((t) => t.trim()).filter(Boolean);
44 const img = await uploadImage(file, tagList);
45 success = `Uploaded "${img.filename}" (ID: ${img.id})`;
46 file = null;
47 tags = '';
48 } catch (err) {
49 error = err instanceof Error ? err.message : 'Upload failed';
50 } finally {
51 loading = false;
52 }
53 }
54</script>
55
56<h1>Upload</h1>
57
58<form onsubmit={handleSubmit}>
59 {#if error}
60 <p class="error">{error}</p>
61 {/if}
62 {#if success}
63 <p class="success">{success} <a href="/browse">Browse images</a></p>
64 {/if}
65
66 <label>
67 Image / Video
68 <input type="file" accept="image/*,video/mp4,video/webm" required
69 onchange={(e) => { file = (e.target as HTMLInputElement).files?.[0] ?? null; }}
70 disabled={loading}
71 />
72 </label>
73
74 <label>
75 Tags (comma-separated)
76 <div class="tag-wrapper">
77 <input type="text" placeholder="funny, meme, cat" bind:value={tags}
78 oninput={onTagInput}
79 onblur={() => setTimeout(() => { showSuggestions = false; }, 200)}
80 disabled={loading}
81 />
82 {#if showSuggestions}
83 <ul class="suggestions">
84 {#each suggestions as s}
85 <li role="button" tabindex="0" onmousedown={() => selectSuggestion(s)}>{s}</li>
86 {/each}
87 </ul>
88 {/if}
89 </div>
90 </label>
91
92 <button type="submit" disabled={loading || !file}>
93 {loading ? 'Uploading...' : 'Upload'}
94 </button>
95</form>
96
97<style>
98 form {
99 display: flex;
100 flex-direction: column;
101 gap: 1rem;
102 max-width: 480px;
103 }
104 label {
105 display: flex;
106 flex-direction: column;
107 gap: 0.25rem;
108 }
109 input[type="file"], input[type="text"] {
110 padding: 0.5rem;
111 border: 1px solid #ccc;
112 border-radius: 4px;
113 }
114 .tag-wrapper {
115 position: relative;
116 }
117 .suggestions {
118 position: absolute;
119 top: 100%;
120 left: 0;
121 right: 0;
122 background: #fff;
123 border: 1px solid #ccc;
124 border-top: none;
125 list-style: none;
126 margin: 0;
127 padding: 0;
128 z-index: 10;
129 max-height: 150px;
130 overflow-y: auto;
131 }
132 .suggestions li {
133 padding: 0.4rem 0.5rem;
134 cursor: pointer;
135 font-size: 0.9rem;
136 }
137 .suggestions li:hover {
138 background: #f0f0f0;
139 }
140 button[type="submit"] {
141 padding: 0.5rem 1.5rem;
142 background: #1a1a2e;
143 color: #eee;
144 border: none;
145 border-radius: 4px;
146 cursor: pointer;
147 align-self: flex-start;
148 }
149 button[type="submit"]:disabled {
150 opacity: 0.6;
151 }
152 .error {
153 color: #c0392b;
154 }
155 .success {
156 color: #27ae60;
157 }
158</style>