mirror of
https://github.com/shadoll/sLogos.git
synced 2026-02-04 02:53:22 +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 Home from "./pages/Home.svelte";
|
||||||
import Preview from "./pages/Preview.svelte";
|
import Preview from "./pages/Preview.svelte";
|
||||||
import NotFound from "./pages/NotFound.svelte";
|
import NotFound from "./pages/NotFound.svelte";
|
||||||
|
import Header from "./components/Header.svelte";
|
||||||
|
|
||||||
export const routes = {
|
export const routes = {
|
||||||
"/": Home,
|
"/": Home,
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
// --- Collection support ---
|
// --- Collection support ---
|
||||||
let collection = localStorage.getItem("collection") || "logos";
|
let collection = localStorage.getItem("collection") || "logos";
|
||||||
function setCollection(val) {
|
function setCollection(val) {
|
||||||
|
console.log("setCollection called with", val, "current:", collection);
|
||||||
if (val !== collection) {
|
if (val !== collection) {
|
||||||
collection = val;
|
collection = val;
|
||||||
localStorage.setItem("collection", val);
|
localStorage.setItem("collection", val);
|
||||||
@@ -40,12 +42,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadCollectionData() {
|
async function loadCollectionData() {
|
||||||
|
console.log("loadCollectionData: loading collection", collection);
|
||||||
// Load the correct data file for the selected collection
|
// Load the correct data file for the selected collection
|
||||||
try {
|
try {
|
||||||
const timestamp = new Date().getTime();
|
const timestamp = new Date().getTime();
|
||||||
const response = await fetch(`data/${collection}.json?t=${timestamp}`);
|
const response = await fetch(`data/${collection}.json?t=${timestamp}`);
|
||||||
|
console.log("loadCollectionData: fetch status", response.status);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
logos = await response.json();
|
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
|
// Reset filters and state for new collection
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
selectedTags = [];
|
selectedTags = [];
|
||||||
@@ -55,9 +63,11 @@
|
|||||||
compactMode = false;
|
compactMode = false;
|
||||||
} else {
|
} else {
|
||||||
logos = [];
|
logos = [];
|
||||||
|
console.log("loadCollectionData: failed to load data for", collection);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logos = [];
|
logos = [];
|
||||||
|
console.error("loadCollectionData: error loading data for", collection, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +136,7 @@
|
|||||||
// Load logos from JSON file with cache busting
|
// Load logos from JSON file with cache busting
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
console.log("App: onMount start - before loading logos");
|
console.log("App: onMount start - before loading logos");
|
||||||
|
await loadCollectionData();
|
||||||
|
|
||||||
// Set initial empty app data
|
// Set initial empty app data
|
||||||
if (typeof window !== "undefined") {
|
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 params = new URLSearchParams(window.location.search);
|
||||||
const searchParam = params.get("search");
|
const searchParam = params.get("search");
|
||||||
if (searchParam) {
|
if (searchParam) {
|
||||||
@@ -836,7 +760,70 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</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>
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -170,7 +170,7 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if logo.brand}
|
{#if logo.brand}
|
||||||
<p><strong>Brand:</strong> <span>{logo.brand}</span></p>
|
<p><strong>Owner:</strong> <span>{logo.brand}</span></p>
|
||||||
{/if}
|
{/if}
|
||||||
<p><strong>Format:</strong> <span>{logo.format}</span></p>
|
<p><strong>Format:</strong> <span>{logo.format}</span></p>
|
||||||
<p><strong>Path:</strong> {logo.path}</p>
|
<p><strong>Path:</strong> {logo.path}</p>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
export let onDownload;
|
export let onDownload;
|
||||||
export let allLogos = [];
|
export let allLogos = [];
|
||||||
export let addBrand = () => {};
|
export let addBrand = () => {};
|
||||||
export const setTheme = () => {};
|
|
||||||
|
|
||||||
function openPreview(logo) {
|
function openPreview(logo) {
|
||||||
// Navigate to preview page using router
|
// Navigate to preview page using router
|
||||||
|
|||||||
@@ -33,6 +33,19 @@
|
|||||||
export let setCompactMode = () => {};
|
export let setCompactMode = () => {};
|
||||||
export let collection = "logos";
|
export let collection = "logos";
|
||||||
export let setCollection = () => {};
|
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>
|
</script>
|
||||||
|
|
||||||
<header class="main-header">
|
<header class="main-header">
|
||||||
@@ -41,12 +54,28 @@
|
|||||||
<div class="header-icon">
|
<div class="header-icon">
|
||||||
<img src="favicon.svg" alt="Logo Gallery icon" />
|
<img src="favicon.svg" alt="Logo Gallery icon" />
|
||||||
</div>
|
</div>
|
||||||
<select class="collection-chooser" bind:value={collection} on:change={(e) => setCollection(e.target.value)} aria-label="Choose collection">
|
<button class="collection-title-btn" on:click={handleTitleClick} aria-haspopup="listbox" aria-expanded={dropdownOpen}>
|
||||||
{#each collections as c}
|
{currentLabel} Gallery <span class="triangle">▼</span>
|
||||||
<option value={c.name}>{c.label}</option>
|
</button>
|
||||||
{/each}
|
{#if dropdownOpen}
|
||||||
</select>
|
<ul class="collection-dropdown" role="listbox">
|
||||||
<h1>Logo Gallery</h1>
|
{#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>
|
</div>
|
||||||
<span class="logo-count">
|
<span class="logo-count">
|
||||||
{#if (searchQuery && searchQuery.trim() !== "") || selectedTags.length > 0 || selectedBrands.length > 0 || selectedVariants.length > 0 || compactMode}
|
{#if (searchQuery && searchQuery.trim() !== "") || selectedTags.length > 0 || selectedBrands.length > 0 || selectedVariants.length > 0 || compactMode}
|
||||||
@@ -127,16 +156,59 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collection-chooser {
|
.collection-title-btn {
|
||||||
font-family: inherit;
|
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;
|
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);
|
background: var(--color-card);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
padding: 0.25rem 0.5rem;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||||
margin-right: 1rem;
|
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;
|
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 {
|
.header-controls {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
let addVariant = () => {};
|
let addVariant = () => {};
|
||||||
let removeVariant = () => {};
|
let removeVariant = () => {};
|
||||||
let setTheme = () => {};
|
let setTheme = () => {};
|
||||||
|
let collection = "logos"; // Default collection
|
||||||
|
let setCollection = () => {};
|
||||||
|
|
||||||
|
|
||||||
$: ({
|
$: ({
|
||||||
@@ -30,6 +32,7 @@
|
|||||||
effectiveTheme = "light",
|
effectiveTheme = "light",
|
||||||
viewMode = "grid",
|
viewMode = "grid",
|
||||||
searchQuery = "",
|
searchQuery = "",
|
||||||
|
collection = "logos",
|
||||||
allTags = [],
|
allTags = [],
|
||||||
selectedTags = [],
|
selectedTags = [],
|
||||||
selectedBrands = [],
|
selectedBrands = [],
|
||||||
@@ -41,6 +44,7 @@
|
|||||||
setListView = () => {},
|
setListView = () => {},
|
||||||
setCompactView = () => {},
|
setCompactView = () => {},
|
||||||
setTheme = () => {},
|
setTheme = () => {},
|
||||||
|
setCollection = () => {},
|
||||||
toggleDropdown = () => {},
|
toggleDropdown = () => {},
|
||||||
addTag = () => {},
|
addTag = () => {},
|
||||||
removeTag = () => {},
|
removeTag = () => {},
|
||||||
@@ -62,6 +66,7 @@
|
|||||||
allLogos = window.appData.logos || [];
|
allLogos = window.appData.logos || [];
|
||||||
viewMode = window.appData.viewMode || "grid";
|
viewMode = window.appData.viewMode || "grid";
|
||||||
theme = window.appData.theme || "system";
|
theme = window.appData.theme || "system";
|
||||||
|
collection = window.appData.collection || "logos";
|
||||||
searchQuery = window.appData.searchQuery || "";
|
searchQuery = window.appData.searchQuery || "";
|
||||||
compactMode = window.appData.compactMode || false;
|
compactMode = window.appData.compactMode || false;
|
||||||
tagDropdownOpen = window.appData.tagDropdownOpen || false;
|
tagDropdownOpen = window.appData.tagDropdownOpen || false;
|
||||||
@@ -233,6 +238,8 @@
|
|||||||
getTagObj={(tag) => (window.appData?.getTagObj ? window.appData.getTagObj(tag) : {text: tag})}
|
getTagObj={(tag) => (window.appData?.getTagObj ? window.appData.getTagObj(tag) : {text: tag})}
|
||||||
{compactMode}
|
{compactMode}
|
||||||
{setCompactMode}
|
{setCompactMode}
|
||||||
|
{collection}
|
||||||
|
{setCollection}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|||||||
Reference in New Issue
Block a user