feat: Enhance collection management with dropdown in Header and update related components

This commit is contained in:
sHa
2025-06-17 17:50:47 +03:00
parent 91918c5b0f
commit ca97046961
5 changed files with 167 additions and 102 deletions

View File

@@ -6,6 +6,7 @@
import Home from "./pages/Home.svelte";
import Preview from "./pages/Preview.svelte";
import NotFound from "./pages/NotFound.svelte";
import Header from "./components/Header.svelte";
export const routes = {
"/": Home,
@@ -32,6 +33,7 @@
// --- Collection support ---
let collection = localStorage.getItem("collection") || "logos";
function setCollection(val) {
console.log("setCollection called with", val, "current:", collection);
if (val !== collection) {
collection = val;
localStorage.setItem("collection", val);
@@ -40,12 +42,18 @@
}
async function loadCollectionData() {
console.log("loadCollectionData: loading collection", collection);
// Load the correct data file for the selected collection
try {
const timestamp = new Date().getTime();
const response = await fetch(`data/${collection}.json?t=${timestamp}`);
console.log("loadCollectionData: fetch status", response.status);
if (response.ok) {
logos = await response.json();
console.log("loadCollectionData: loaded", logos.length, "items for", collection);
logos = [...logos]; // trigger reactivity
filteredLogos = logos;
displayLogos = logos;
// Reset filters and state for new collection
searchQuery = "";
selectedTags = [];
@@ -55,9 +63,11 @@
compactMode = false;
} else {
logos = [];
console.log("loadCollectionData: failed to load data for", collection);
}
} catch (error) {
logos = [];
console.error("loadCollectionData: error loading data for", collection, error);
}
}
@@ -126,6 +136,7 @@
// Load logos from JSON file with cache busting
onMount(async () => {
console.log("App: onMount start - before loading logos");
await loadCollectionData();
// Set initial empty app data
if (typeof window !== "undefined") {
@@ -164,93 +175,6 @@
};
}
try {
// Add timestamp as cache-busting query parameter
const timestamp = new Date().getTime();
const response = await fetch(`data/logos.json?t=${timestamp}`, {
// Force reload from server, don't use cache
cache: "no-cache",
headers: {
"Cache-Control": "no-cache",
Pragma: "no-cache",
},
});
if (response.ok) {
logos = await response.json();
filteredLogos = logos;
displayLogos = logos;
console.log(
"Loaded logos:",
logos.length,
"at",
new Date().toLocaleTimeString(),
);
// Update app data immediately after logos are loaded
if (typeof window !== "undefined") {
window.appData = {
logos,
filteredLogos,
displayLogos,
theme,
effectiveTheme,
viewMode,
searchQuery,
allTags,
selectedTags,
selectedBrands,
selectedVariants,
tagDropdownOpen,
compactMode,
setSearchQuery,
setGridView,
setListView,
setCompactView,
setTheme,
toggleDropdown,
addTag,
removeTag,
toggleTag,
getTagObj,
closeDropdown,
setCompactMode,
onCopy: copyUrl,
onDownload: downloadLogo,
addBrand,
removeBrand,
addVariant,
removeVariant,
};
console.log(
"App: Updated window.appData after loading with",
logos.length,
"logos",
);
}
} else {
console.error("Failed to load logos data", response.status);
}
} catch (error) {
console.error("Error loading logos:", error);
}
const stored = localStorage.getItem("theme");
if (stored === "light" || stored === "dark" || stored === "system") {
theme = stored;
}
applyTheme();
// Listen for system theme changes if using system
mq = window.matchMedia("(prefers-color-scheme: dark)");
mq.addEventListener("change", () => {
if (theme === "system") applyTheme();
});
// Open preview if URL contains anchor
openLogoByAnchor(window.location.hash);
// On mount, check for search param in URL and set searchQuery
const params = new URLSearchParams(window.location.search);
const searchParam = params.get("search");
if (searchParam) {
@@ -836,7 +760,70 @@
}
</script>
<Router {routes} />
<Router {routes} let:Component>
<svelte:component
this={Component}
displayLogos={displayLogos}
allLogos={logos}
theme={theme}
setTheme={setTheme}
viewMode={viewMode}
setGridView={setGridView}
setListView={setListView}
setCompactView={setCompactView}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
allTags={allTags}
selectedTags={selectedTags}
selectedBrands={selectedBrands}
selectedVariants={selectedVariants}
tagDropdownOpen={tagDropdownOpen}
toggleDropdown={toggleDropdown}
addTag={addTag}
removeTag={removeTag}
addBrand={addBrand}
removeBrand={removeBrand}
addVariant={addVariant}
removeVariant={removeVariant}
getTagObj={getTagObj}
compactMode={compactMode}
setCompactMode={setCompactMode}
collection={collection}
setCollection={setCollection}
collections={collections}
/>
</Router>
<Header
{displayLogos}
allLogos={logos}
{theme}
{setTheme}
{viewMode}
{setGridView}
{setListView}
{setCompactView}
{searchQuery}
{setSearchQuery}
{allTags}
{selectedTags}
{selectedBrands}
{selectedVariants}
{tagDropdownOpen}
{toggleDropdown}
{addTag}
{removeTag}
{addBrand}
{removeBrand}
{addVariant}
{removeVariant}
{getTagObj}
{compactMode}
{setCompactMode}
{collection}
{setCollection}
{collections}
/>
<style>
</style>

View File

@@ -170,7 +170,7 @@
/>
{/if}
{#if logo.brand}
<p><strong>Brand:</strong> <span>{logo.brand}</span></p>
<p><strong>Owner:</strong> <span>{logo.brand}</span></p>
{/if}
<p><strong>Format:</strong> <span>{logo.format}</span></p>
<p><strong>Path:</strong> {logo.path}</p>

View File

@@ -10,7 +10,6 @@
export let onDownload;
export let allLogos = [];
export let addBrand = () => {};
export const setTheme = () => {};
function openPreview(logo) {
// Navigate to preview page using router

View File

@@ -33,6 +33,19 @@
export let setCompactMode = () => {};
export let collection = "logos";
export let setCollection = () => {};
let dropdownOpen = false;
function handleTitleClick() {
dropdownOpen = !dropdownOpen;
}
function handleCollectionSelect(name) {
setCollection(name);
dropdownOpen = false;
}
$: currentLabel = (collections.find(c => c.name === collection)?.label || "Logo Gallery").replace(/s$/, "");
</script>
<header class="main-header">
@@ -41,12 +54,28 @@
<div class="header-icon">
<img src="favicon.svg" alt="Logo Gallery icon" />
</div>
<select class="collection-chooser" bind:value={collection} on:change={(e) => setCollection(e.target.value)} aria-label="Choose collection">
{#each collections as c}
<option value={c.name}>{c.label}</option>
{/each}
</select>
<h1>Logo Gallery</h1>
<button class="collection-title-btn" on:click={handleTitleClick} aria-haspopup="listbox" aria-expanded={dropdownOpen}>
{currentLabel} Gallery <span class="triangle"></span>
</button>
{#if dropdownOpen}
<ul class="collection-dropdown" role="listbox">
{#each collections as c}
<li
class:active={c.name === collection}
role="option"
aria-selected={c.name === collection}
tabindex="0"
on:click={() => handleCollectionSelect(c.name)}
on:keydown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleCollectionSelect(c.name);
}
}}
>{c.label} Gallery</li>
{/each}
</ul>
{/if}
</div>
<span class="logo-count">
{#if (searchQuery && searchQuery.trim() !== "") || selectedTags.length > 0 || selectedBrands.length > 0 || selectedVariants.length > 0 || compactMode}
@@ -127,16 +156,59 @@
border-radius: 4px;
}
.collection-chooser {
font-family: inherit;
.collection-title-btn {
background: none;
border: none;
font: inherit;
color: inherit;
font-size: 1.5rem;
font-weight: bold;
display: flex;
align-items: center;
gap: 0.3em;
cursor: pointer;
padding: 0;
margin: 0 1rem 0 0;
position: relative;
transition: color 0.15s;
}
.collection-title-btn:hover,
.collection-title-btn:focus {
color: var(--color-primary, #0070f3);
}
.triangle {
font-size: 1rem;
color: var(--color-text);
margin-left: 0.2em;
transition: transform 0.2s;
}
.collection-title-btn[aria-expanded="true"] .triangle {
transform: rotate(180deg);
}
.collection-dropdown {
position: absolute;
top: 2.2em;
left: 0;
background: var(--color-card);
border: 1px solid var(--color-border);
border-radius: 4px;
padding: 0.25rem 0.5rem;
margin-right: 1rem;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
z-index: 10;
min-width: 180px;
padding: 0.3em 0;
margin: 0;
list-style: none;
}
.collection-dropdown li {
padding: 0.5em 1.2em;
cursor: pointer;
font-size: 1.1rem;
color: var(--color-text);
transition: background 0.13s, color 0.13s;
}
.collection-dropdown li.active,
.collection-dropdown li:hover {
background: var(--color-primary, #0070f3);
color: #fff;
}
.header-controls {

View File

@@ -20,6 +20,8 @@
let addVariant = () => {};
let removeVariant = () => {};
let setTheme = () => {};
let collection = "logos"; // Default collection
let setCollection = () => {};
$: ({
@@ -30,6 +32,7 @@
effectiveTheme = "light",
viewMode = "grid",
searchQuery = "",
collection = "logos",
allTags = [],
selectedTags = [],
selectedBrands = [],
@@ -41,6 +44,7 @@
setListView = () => {},
setCompactView = () => {},
setTheme = () => {},
setCollection = () => {},
toggleDropdown = () => {},
addTag = () => {},
removeTag = () => {},
@@ -62,6 +66,7 @@
allLogos = window.appData.logos || [];
viewMode = window.appData.viewMode || "grid";
theme = window.appData.theme || "system";
collection = window.appData.collection || "logos";
searchQuery = window.appData.searchQuery || "";
compactMode = window.appData.compactMode || false;
tagDropdownOpen = window.appData.tagDropdownOpen || false;
@@ -233,6 +238,8 @@
getTagObj={(tag) => (window.appData?.getTagObj ? window.appData.getTagObj(tag) : {text: tag})}
{compactMode}
{setCompactMode}
{collection}
{setCollection}
/>
<main>