Restructure layout: search in sidebar, upload in header
Build & Deploy / build-and-deploy (push) Successful in 58s
Build & Deploy / build-and-deploy (push) Successful in 58s
- Removed toolbar (Library title, search, upload button) from library - Moved search box into the sidebar alongside Year/Genre filters - Removed 'Filters' heading from sidebar - Replaced 'Library' nav button in header with '+ Upload' button - Genre filters now sorted by count (desc) instead of alphabetically - Cleaned up unused CSS
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { listGames, uploadGame, searchIGDB } from "../lib/api.js";
|
||||
import GameCard from "../lib/GameCard.svelte";
|
||||
|
||||
let { genreFilter = "", yearFilter = "" } = $props();
|
||||
let { genreFilter = "", yearFilter = "", uploadTriggered = 0 } = $props();
|
||||
|
||||
let games = $state([]);
|
||||
let loading = $state(true);
|
||||
@@ -28,6 +28,11 @@
|
||||
|
||||
let fileInput;
|
||||
|
||||
// When header upload button is clicked, trigger the hidden file input
|
||||
$effect(() => {
|
||||
if (uploadTriggered > 0) fileInput?.click();
|
||||
});
|
||||
|
||||
// Derive filter options from the loaded games
|
||||
let filterOptions = $derived.by(() => {
|
||||
const years = {};
|
||||
@@ -47,7 +52,7 @@
|
||||
.sort((a, b) => Number(b.label) - Number(a.label)),
|
||||
genres: Object.entries(genres)
|
||||
.map(([label, count]) => ({ label, count }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label)),
|
||||
.sort((a, b) => b.count - a.count),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -201,12 +206,21 @@
|
||||
<!-- ─── Filter Sidebar ─────────────────────────── -->
|
||||
<aside class="sidebar" class:sidebar-active={hasActiveFilters()}>
|
||||
<div class="sidebar-header">
|
||||
<h2>Filters</h2>
|
||||
{#if hasActiveFilters()}
|
||||
<button class="btn btn-sm btn-reset" onclick={resetFilters}>✕ Reset</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Search box -->
|
||||
<div class="sidebar-search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search games..."
|
||||
bind:value={search}
|
||||
oninput={() => loadGames()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Year filter group -->
|
||||
<div class="filter-group">
|
||||
<h3 class="filter-label">Year</h3>
|
||||
@@ -260,30 +274,6 @@
|
||||
|
||||
<!-- ─── Main Content ──────────────────────────── -->
|
||||
<div class="library-main">
|
||||
<div class="toolbar">
|
||||
<h1>Library</h1>
|
||||
<div class="toolbar-actions">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search games..."
|
||||
bind:value={search}
|
||||
oninput={() => loadGames()}
|
||||
/>
|
||||
<div class="upload-group">
|
||||
<label class="btn btn-primary upload-label">
|
||||
{uploading ? "Uploading..." : "+ Upload"}
|
||||
<input
|
||||
bind:this={fileInput}
|
||||
type="file"
|
||||
accept=".zip"
|
||||
class="hidden-input"
|
||||
onchange={handleFileSelected}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active filter chips -->
|
||||
{#if hasActiveFilters()}
|
||||
@@ -426,6 +416,16 @@
|
||||
</div>
|
||||
<div class="count">{filteredGames.length} game{filteredGames.length !== 1 ? "s" : ""}</div>
|
||||
{/if}
|
||||
|
||||
<!-- Hidden file input triggered by header upload button -->
|
||||
<input
|
||||
bind:this={fileInput}
|
||||
type="file"
|
||||
accept=".zip"
|
||||
class="hidden-input"
|
||||
onchange={handleFileSelected}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -463,9 +463,9 @@
|
||||
}
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.sidebar-header h2 {
|
||||
color: var(--phosphor);
|
||||
@@ -474,6 +474,12 @@
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.sidebar-search {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.sidebar-search input {
|
||||
width: 100%;
|
||||
}
|
||||
.filter-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -578,45 +584,23 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
.btn-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--phosphor-dim);
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
padding: 4px 0;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
.toolbar h1 {
|
||||
.btn-link:hover {
|
||||
color: var(--phosphor);
|
||||
font-family: var(--font-mono);
|
||||
animation: phosphor-pulse 3s ease-in-out infinite;
|
||||
}
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.toolbar-actions { width: 100%; flex-direction: column; }
|
||||
.toolbar-actions input[type="text"] { width: 100%; min-width: 0; }
|
||||
.upload-group { width: 100%; }
|
||||
}
|
||||
.toolbar-actions input {
|
||||
min-width: 200px;
|
||||
}
|
||||
.upload-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hidden-input {
|
||||
display: none;
|
||||
}
|
||||
.upload-label {
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
@@ -625,6 +609,7 @@
|
||||
@media (max-width: 480px) {
|
||||
.grid { grid-template-columns: repeat(2, 1fr); gap: 12px; }
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #331111;
|
||||
border: 1px solid #cc3333;
|
||||
@@ -633,12 +618,14 @@
|
||||
border-radius: var(--radius-sm);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: var(--text-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
@@ -655,6 +642,7 @@
|
||||
.empty-state p {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.count {
|
||||
margin-top: 24px;
|
||||
text-align: center;
|
||||
|
||||
Reference in New Issue
Block a user