Enhance Header and List components with compact mode functionality

This commit is contained in:
sHa
2025-05-04 12:48:15 +03:00
parent 2d9de12630
commit f017d480e8
5 changed files with 163 additions and 36 deletions

View File

@@ -663,28 +663,6 @@
], ],
"brand": "PostgreSQL" "brand": "PostgreSQL"
}, },
{
"name": "Privat24",
"path": "logos/privat24.svg",
"format": "SVG",
"disable": false,
"brand": "PrivatBank",
"tags": [
"bank",
"finance"
]
},
{
"name": "Privat24 Bussiness",
"path": "logos/privat24bussiness.svg",
"format": "SVG",
"disable": false,
"brand": "PrivatBank",
"tags": [
"bank",
"finance"
]
},
{ {
"name": "Privatbank", "name": "Privatbank",
"path": "logos/privatbank.svg", "path": "logos/privatbank.svg",
@@ -707,6 +685,28 @@
"finance" "finance"
] ]
}, },
{
"name": "Privat24",
"path": "logos/privat24.svg",
"format": "SVG",
"disable": false,
"brand": "PrivatBank",
"tags": [
"bank",
"finance"
]
},
{
"name": "Privat24 Bussiness",
"path": "logos/privat24bussiness.svg",
"format": "SVG",
"disable": false,
"brand": "PrivatBank",
"tags": [
"bank",
"finance"
]
},
{ {
"name": "Puma", "name": "Puma",
"path": "logos/puma.svg", "path": "logos/puma.svg",

View File

@@ -516,6 +516,7 @@ div.logo-image img {
margin-left: 1rem; margin-left: 1rem;
align-self: center; align-self: center;
opacity: 0.7; opacity: 0.7;
padding-top: 0.9rem;
} }
.theme-switcher { .theme-switcher {

View File

@@ -9,6 +9,7 @@
let searchQuery = ""; let searchQuery = "";
let logos = []; let logos = [];
let filteredLogos = []; let filteredLogos = [];
let displayLogos = [];
let theme = "system"; let theme = "system";
let mq; let mq;
let allTags = []; let allTags = [];
@@ -16,11 +17,17 @@
let tagDropdownOpen = false; let tagDropdownOpen = false;
let showModal = false; let showModal = false;
let selectedLogo = null; let selectedLogo = null;
let compactMode = false;
function setSearchQuery(val) { function setSearchQuery(val) {
searchQuery = val; searchQuery = val;
} }
function setCompactMode(val) {
compactMode = val;
localStorage.setItem("compactMode", String(val));
}
// Load logos from JSON file with cache busting // Load logos from JSON file with cache busting
onMount(async () => { onMount(async () => {
try { try {
@@ -72,6 +79,16 @@
if (searchParam) { if (searchParam) {
searchQuery = searchParam; searchQuery = searchParam;
} }
// Restore view mode and compact mode from localStorage
const savedViewMode = localStorage.getItem("viewMode");
if (savedViewMode === "grid" || savedViewMode === "list") {
viewMode = savedViewMode;
}
const savedCompact = localStorage.getItem("compactMode");
if (savedCompact === "true" || savedCompact === "false") {
setCompactMode(savedCompact === "true");
}
}); });
// Make sure to apply theme whenever it changes // Make sure to apply theme whenever it changes
@@ -104,6 +121,11 @@
return matchesSearch && matchesTags; return matchesSearch && matchesTags;
}); });
$: displayLogos = (!searchQuery && compactMode)
? filteredLogos.filter((logo, idx, arr) =>
arr.findIndex(l => (l.brand || l.name) === (logo.brand || logo.name)) === idx)
: filteredLogos;
// Compute the effective theme for children // Compute the effective theme for children
$: effectiveTheme = $: effectiveTheme =
theme === "system" theme === "system"
@@ -115,11 +137,13 @@
function setGridView() { function setGridView() {
console.log("Setting view mode to: grid"); console.log("Setting view mode to: grid");
viewMode = "grid"; viewMode = "grid";
localStorage.setItem("viewMode", "grid");
} }
function setListView() { function setListView() {
console.log("Setting view mode to: list"); console.log("Setting view mode to: list");
viewMode = "list"; viewMode = "list";
localStorage.setItem("viewMode", "list");
} }
function copyUrl(logoPath) { function copyUrl(logoPath) {
@@ -257,7 +281,8 @@
<main class="container app-flex"> <main class="container app-flex">
<Header <Header
{logos} logos={logos}
displayLogos={displayLogos}
{theme} {theme}
{setTheme} {setTheme}
{viewMode} {viewMode}
@@ -275,6 +300,8 @@
{getTagObj} {getTagObj}
{closeDropdown} {closeDropdown}
{filteredLogos} {filteredLogos}
{compactMode}
setCompactMode={setCompactMode}
/> />
<Preview <Preview
@@ -288,20 +315,22 @@
<div class="logos-container main-content"> <div class="logos-container main-content">
{#if viewMode === "grid"} {#if viewMode === "grid"}
<Grid <Grid
logos={filteredLogos} logos={displayLogos}
onCopy={copyUrl} onCopy={copyUrl}
onDownload={downloadLogo} onDownload={downloadLogo}
theme={effectiveTheme} theme={effectiveTheme}
setSearchQuery={setSearchQuery} setSearchQuery={setSearchQuery}
on:openPreview={(e) => openPreview(e.detail)} on:openPreview={(e) => openPreview(e.detail)}
{compactMode}
/> />
{:else} {:else}
<List <List
logos={filteredLogos} logos={displayLogos}
onCopy={copyUrl} onCopy={copyUrl}
onDownload={downloadLogo} onDownload={downloadLogo}
setSearchQuery={setSearchQuery} setSearchQuery={setSearchQuery}
on:openPreview={(e) => openPreview(e.detail)} on:openPreview={(e) => openPreview(e.detail)}
{compactMode}
/> />
{/if} {/if}
</div> </div>

View File

@@ -1,7 +1,8 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from "svelte";
export let logos = []; export let logos = [];
export let displayLogos = [];
export let theme; export let theme;
export let setTheme; export let setTheme;
export let viewMode; export let viewMode;
@@ -16,6 +17,8 @@
export let addTag; export let addTag;
export let removeTag; export let removeTag;
export let getTagObj; export let getTagObj;
export let compactMode = false;
export let setCompactMode = () => {};
function onInput(event) { function onInput(event) {
searchQuery = event.target.value; searchQuery = event.target.value;
@@ -23,19 +26,27 @@
// Update URL with search param // Update URL with search param
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
if (searchQuery) { if (searchQuery) {
params.set('search', searchQuery); params.set("search", searchQuery);
} else { } else {
params.delete('search'); params.delete("search");
} }
const newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : ''); const newUrl =
history.replaceState(null, '', newUrl); window.location.pathname +
(params.toString() ? "?" + params.toString() : "");
history.replaceState(null, "", newUrl);
} }
</script> </script>
<header class="main-header"> <header class="main-header">
<div class="header-row"> <div class="header-row">
<h1>Logo Gallery</h1> <h1>Logo Gallery</h1>
<span class="logo-count">{logos.length} images in gallery</span> <span class="logo-count">
{#if displayLogos && logos && displayLogos.length === logos.length}
{logos.length} images in gallery
{:else}
{displayLogos ? displayLogos.length : 0} of {logos ? logos.length : 0} images displayed
{/if}
</span>
<div class="theme-switcher"> <div class="theme-switcher">
<div class="theme-switch-group"> <div class="theme-switch-group">
<button <button
@@ -117,9 +128,33 @@
aria-label="Search logos" aria-label="Search logos"
/> />
{#if searchQuery} {#if searchQuery}
<button class="clear-btn" on:click={() => { searchQuery = ''; setSearchQuery(''); const params = new URLSearchParams(window.location.search); params.delete('search'); const newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : ''); history.replaceState(null, '', newUrl); }} aria-label="Clear search"> <button
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> class="clear-btn"
<path d="M4 4L12 12M12 4L4 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> on:click={() => {
searchQuery = "";
setSearchQuery("");
const params = new URLSearchParams(window.location.search);
params.delete("search");
const newUrl =
window.location.pathname +
(params.toString() ? "?" + params.toString() : "");
history.replaceState(null, "", newUrl);
}}
aria-label="Clear search"
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4 4L12 12M12 4L4 12"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg> </svg>
</button> </button>
{/if} {/if}
@@ -163,6 +198,47 @@
</div> </div>
</div> </div>
<div class="view-toggle"> <div class="view-toggle">
<button
class="compact-switch-btn"
aria-label="Toggle compact mode"
class:active={compactMode}
on:click={() => setCompactMode(!compactMode)}
title="Show only one logo per brand (compact mode)"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 11L3 11"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
<path
d="M10 16H3"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
<path
d="M14 13.5L16.1 16L20 11"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M3 6L13.5 6M20 6L17.75 6"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
</svg>
</button>
<button <button
class:active={viewMode === "grid"} class:active={viewMode === "grid"}
on:click={setGridView} on:click={setGridView}
@@ -184,7 +260,8 @@
x="11" x="11"
y="11" y="11"
width="6" width="6"
height="6" fill="currentColor" height="6"
fill="currentColor"
/></svg /></svg
> >
</button> </button>
@@ -238,4 +315,24 @@
.clear-btn:hover { .clear-btn:hover {
color: #f44336; color: #f44336;
} }
.compact-switch-btn {
background: none;
border: none;
color: var(--color-text);
cursor: pointer;
padding: 0.3em 0.6em;
border-radius: 6px;
margin-right: 0.5em;
font-size: 1.1em;
display: inline-flex;
align-items: center;
transition:
background 0.2s,
color 0.2s;
}
.compact-switch-btn.active,
.compact-switch-btn:hover {
background: var(--color-accent, #4f8cff);
color: #fff;
}
</style> </style>

View File

@@ -226,7 +226,7 @@
margin-left: 0.5em; margin-left: 0.5em;
} }
.brand-filter-btn:hover { .brand-filter-btn:hover {
background: var(--color-accent, #4f8cff); background: var(--color-accent-light, #e0f0ff);
color: #fff; color: #fff;
} }
.brand-filter-btn.common-btn { .brand-filter-btn.common-btn {