From feccf4178f385a21e6d02e58b2ab183b67f48372 Mon Sep 17 00:00:00 2001 From: David Alvarez Date: Tue, 26 May 2026 21:31:50 +0200 Subject: [PATCH] feat: move meta/dev under cover, upload dialog on file select MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Game Detail: - Year/genre badges and developer/publisher info moved from the info column to the cover column (below the cover image) - Edit mode unaffected — fields remain in the form on the right - Added CSS spacing for meta/dev in cover section Library upload: - Removed the always-visible title input field - After selecting a .zip, a modal dialog appears with two options: 1. 'Use filename as title' — uploads immediately with filename 2. Custom title input + 'Upload' button Plus a Cancel button - Modal is dismissible by clicking outside or Cancel --- frontend/src/pages/GameDetail.svelte | 24 +++--- frontend/src/pages/Library.svelte | 114 ++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 28 deletions(-) diff --git a/frontend/src/pages/GameDetail.svelte b/frontend/src/pages/GameDetail.svelte index 8984e07..e82b8af 100644 --- a/frontend/src/pages/GameDetail.svelte +++ b/frontend/src/pages/GameDetail.svelte @@ -156,13 +156,24 @@ ← Back to Library
- +
{#if game.has_cover} {game.title} {:else}
💾
{/if} + {#if !editing} + {#if game.year || game.genre} +
+ {#if game.year}{game.year}{/if} + {#if game.genre}{game.genre}{/if} +
+ {/if} + {#if game.developer} +

by {game.developer}{game.publisher ? ` · ${game.publisher}` : ""}

+ {/if} + {/if}
@@ -186,15 +197,6 @@
{:else}

{game.title}

- {#if game.year || game.genre} -
- {#if game.year}{game.year}{/if} - {#if game.genre}{game.genre}{/if} -
- {/if} - {#if game.developer} -

by {game.developer}{game.publisher ? ` · ${game.publisher}` : ""}

- {/if} {#if game.description}

{game.description}

{/if} @@ -333,6 +335,8 @@ .detail-layout > * { min-width: 0; } /* prevent grid overflow from wide content */ @media (max-width: 640px) { .detail-layout { grid-template-columns: 1fr; } } .cover-section { border-radius: var(--radius); overflow: hidden; } + .cover-section .meta { margin-top: 12px; } + .cover-section .dev { margin-top: 8px; } .cover-img { width: 100%; border-radius: var(--radius); } .cover-placeholder { aspect-ratio: 3/4; background: var(--surface); display: flex; align-items: center; justify-content: center; font-size: 4rem; border-radius: var(--radius); } .info-section h1 { color: var(--phosphor); font-family: var(--font-mono); margin-bottom: 8px; word-break: break-word; } diff --git a/frontend/src/pages/Library.svelte b/frontend/src/pages/Library.svelte index 7d25eb0..0d1ce6f 100644 --- a/frontend/src/pages/Library.svelte +++ b/frontend/src/pages/Library.svelte @@ -7,7 +7,12 @@ let search = $state(""); let uploading = $state(false); let uploadError = $state(""); - let uploadTitle = $state(""); + + // Post-selection upload dialog + let pendingFile = $state(null); + let pendingFileName = $state(""); + let pendingTitle = $state(""); + let fileInput; async function loadGames() { @@ -21,16 +26,31 @@ loading = false; } - async function handleUpload(e) { + function handleFileSelected(e) { const file = e.target.files?.[0]; if (!file) return; + const name = file.name.replace(/\.zip$/i, ""); + pendingFile = file; + pendingFileName = name; + pendingTitle = ""; + // Reset input so the same file can be re-selected + fileInput.value = ""; + } + + function cancelUpload() { + pendingFile = null; + pendingFileName = ""; + pendingTitle = ""; + } + + async function doUpload(title) { + if (!pendingFile) return; uploading = true; uploadError = ""; try { - await uploadGame(file, uploadTitle.trim() || undefined); - uploadTitle = ""; + await uploadGame(pendingFile, title || undefined); + cancelUpload(); await loadGames(); - fileInput.value = ""; } catch (err) { uploadError = err.message; } @@ -53,13 +73,6 @@ oninput={() => loadGames()} />
- @@ -75,6 +88,32 @@
+ {#if pendingFile} + + {/if} + {#if uploadError}
{uploadError}
{/if} @@ -131,7 +170,6 @@ .toolbar-actions { width: 100%; flex-direction: column; } .toolbar-actions input[type="text"] { width: 100%; min-width: 0; } .upload-group { width: 100%; } - .title-input { width: 100% !important; min-width: 0 !important; } } .toolbar-actions input { min-width: 200px; @@ -141,10 +179,6 @@ gap: 8px; align-items: center; } - .title-input { - min-width: 160px !important; - width: 160px; - } .hidden-input { display: none; } @@ -196,4 +230,48 @@ color: var(--text-dim); font-size: 0.85rem; } + + /* Upload dialog overlay */ + .upload-overlay { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 200; + padding: 16px; + } + .upload-dialog { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 24px; + max-width: 420px; + width: 100%; + } + .upload-dialog h3 { + margin-bottom: 4px; + color: var(--phosphor); + font-family: var(--font-mono); + } + .upload-file-name { + color: var(--text-dim); + font-size: 0.85rem; + margin-bottom: 20px; + word-break: break-all; + } + .upload-dialog-actions { + display: flex; + flex-direction: column; + gap: 10px; + } + .upload-custom-row { + display: flex; + gap: 8px; + } + .upload-custom-row input { + flex: 1; + } +