- Drop zone: drag and drop files - Paste: Ctrl+V pastes clipboard screenshots directly - Browse: file picker button - Saves to platform/screenshots/ with timestamp filename - Mounted as volume in frontend container - Accessible from any device at dash.quadjourney.com/upload Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
118 lines
3.8 KiB
Svelte
118 lines
3.8 KiB
Svelte
<script lang="ts">
|
|
let uploading = $state(false);
|
|
let lastFile = $state('');
|
|
let dragOver = $state(false);
|
|
|
|
async function upload(file: File) {
|
|
uploading = true;
|
|
const fd = new FormData();
|
|
fd.append('file', file);
|
|
try {
|
|
const res = await fetch('/upload', { method: 'POST', body: fd, credentials: 'include' });
|
|
const data = await res.json();
|
|
lastFile = data.filename || '';
|
|
} catch { lastFile = 'Error'; }
|
|
uploading = false;
|
|
}
|
|
|
|
function handleDrop(e: DragEvent) {
|
|
e.preventDefault();
|
|
dragOver = false;
|
|
const file = e.dataTransfer?.files[0];
|
|
if (file) upload(file);
|
|
}
|
|
|
|
function handlePaste(e: ClipboardEvent) {
|
|
const items = e.clipboardData?.items;
|
|
if (!items) return;
|
|
for (const item of items) {
|
|
if (item.type.startsWith('image/')) {
|
|
const file = item.getAsFile();
|
|
if (file) upload(file);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleFileInput(e: Event) {
|
|
const input = e.target as HTMLInputElement;
|
|
if (input.files?.[0]) upload(input.files[0]);
|
|
}
|
|
</script>
|
|
|
|
<svelte:window onpaste={handlePaste} />
|
|
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div class="upload-page"
|
|
ondragover={(e) => { e.preventDefault(); dragOver = true; }}
|
|
ondragleave={() => dragOver = false}
|
|
ondrop={handleDrop}
|
|
>
|
|
<div class="upload-zone" class:dragover={dragOver}>
|
|
{#if uploading}
|
|
<div class="upload-status">Uploading...</div>
|
|
{:else if lastFile}
|
|
<div class="upload-done">
|
|
<div class="upload-check">Saved</div>
|
|
<div class="upload-filename">{lastFile}</div>
|
|
<button class="upload-another" onclick={() => lastFile = ''}>Upload another</button>
|
|
</div>
|
|
{:else}
|
|
<div class="upload-prompt">
|
|
<div class="upload-icon">
|
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
|
</div>
|
|
<div class="upload-text">Drop a file, paste a screenshot, or click to upload</div>
|
|
<label class="upload-btn">
|
|
Browse files
|
|
<input type="file" accept="image/*,.pdf,.txt,.md" onchange={handleFileInput} hidden />
|
|
</label>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.upload-page {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 80vh;
|
|
padding: 24px;
|
|
}
|
|
.upload-zone {
|
|
width: 100%;
|
|
max-width: 480px;
|
|
min-height: 280px;
|
|
border: 2px dashed rgba(35,26,17,0.15);
|
|
border-radius: 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 40px;
|
|
transition: all 200ms;
|
|
background: rgba(255,252,248,0.5);
|
|
}
|
|
.upload-zone.dragover {
|
|
border-color: rgba(179,92,50,0.4);
|
|
background: rgba(255,248,242,0.8);
|
|
}
|
|
.upload-prompt { text-align: center; }
|
|
.upload-icon { color: #8c7b69; margin-bottom: 16px; }
|
|
.upload-text { color: #5c5046; font-size: 1rem; margin-bottom: 16px; line-height: 1.5; }
|
|
.upload-btn {
|
|
display: inline-block; padding: 10px 24px; border-radius: 999px;
|
|
background: #1e1812; color: white; font-size: 0.88rem; font-weight: 600;
|
|
cursor: pointer; transition: opacity 160ms;
|
|
}
|
|
.upload-btn:hover { opacity: 0.9; }
|
|
.upload-status { font-size: 1.1rem; color: #5c5046; }
|
|
.upload-done { text-align: center; }
|
|
.upload-check { font-size: 1.2rem; font-weight: 700; color: #059669; margin-bottom: 8px; }
|
|
.upload-filename { font-size: 0.88rem; color: #5c5046; font-family: var(--mono); margin-bottom: 16px; }
|
|
.upload-another {
|
|
padding: 8px 18px; border-radius: 999px; border: 1px solid rgba(35,26,17,0.12);
|
|
background: none; color: #5c5046; font-size: 0.85rem; cursor: pointer;
|
|
}
|
|
</style>
|