mirror of
https://github.com/shadoll/sLogos.git
synced 2025-12-20 04:27:59 +00:00
feat: Enhance collection management with dropdown in Header and update related components
This commit is contained in:
163
src/App.svelte
163
src/App.svelte
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user