feat: upload button in AppShell sidebar — click or drag to upload screenshots

- Dashed border button at bottom of sidebar nav
- Click to open file picker
- Drag files onto the bottom rail area to upload
- Shows "Uploading..." then "Saved!" for 2 seconds
- Files save to platform/screenshots/ with timestamp names
- Works from any page in the Atelier shell

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-01 21:36:13 -05:00
parent 07cbec89c4
commit b87a3a583d

View File

@@ -8,7 +8,6 @@
CircleDot, CircleDot,
Compass, Compass,
Dumbbell, Dumbbell,
FolderOpen,
Landmark, Landmark,
LibraryBig, LibraryBig,
Menu, Menu,
@@ -16,7 +15,7 @@
Search, Search,
Settings2, Settings2,
SquareCheckBig, SquareCheckBig,
Tag, Upload,
X X
} from '@lucide/svelte'; } from '@lucide/svelte';
@@ -52,6 +51,33 @@
); );
let mobileNavOpen = $state(false); let mobileNavOpen = $state(false);
let uploadInput: HTMLInputElement;
let uploadStatus = $state<'' | 'uploading' | 'done'>('');
async function handleUpload(file: File) {
uploadStatus = 'uploading';
const fd = new FormData();
fd.append('file', file);
try {
await fetch('/upload', { method: 'POST', body: fd, credentials: 'include' });
uploadStatus = 'done';
setTimeout(() => uploadStatus = '', 2000);
} catch {
uploadStatus = '';
}
}
function onUploadInput(e: Event) {
const input = e.target as HTMLInputElement;
if (input.files?.[0]) handleUpload(input.files[0]);
input.value = '';
}
function onRailDrop(e: DragEvent) {
e.preventDefault();
const file = e.dataTransfer?.files[0];
if (file) handleUpload(file);
}
// Brain sidebar sub-items // Brain sidebar sub-items
interface BrainFolder { id: string; name: string; item_count: number; } interface BrainFolder { id: string; name: string; item_count: number; }
@@ -116,7 +142,13 @@
</nav> </nav>
</div> </div>
<div class="rail-bottom"> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rail-bottom" ondragover={(e) => e.preventDefault()} ondrop={onRailDrop}>
<button class="rail-upload" onclick={() => uploadInput?.click()}>
<Upload size={14} strokeWidth={1.8} />
<span>{uploadStatus === 'uploading' ? 'Uploading...' : uploadStatus === 'done' ? 'Saved!' : 'Upload screenshot'}</span>
</button>
<input bind:this={uploadInput} type="file" accept="image/*,.pdf" onchange={onUploadInput} hidden />
<div class="rail-date"> <div class="rail-date">
<CalendarDays size={14} strokeWidth={1.8} /> <CalendarDays size={14} strokeWidth={1.8} />
<span>{new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' }).format(new Date())}</span> <span>{new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' }).format(new Date())}</span>
@@ -383,6 +415,26 @@
border-top: 1px solid var(--shell-line); border-top: 1px solid var(--shell-line);
} }
.rail-upload {
display: flex;
align-items: center;
gap: 8px;
padding: 9px 12px;
border-radius: 999px;
border: 1px dashed var(--shell-line);
background: none;
color: var(--shell-muted);
font-size: 0.78rem;
font-family: inherit;
cursor: pointer;
transition: all 160ms;
}
.rail-upload:hover {
border-color: var(--shell-ink);
color: var(--shell-ink);
background: rgba(255,255,255,0.3);
}
.rail-date { .rail-date {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;