From b4bd726dadbdf31386893c1f00518ba1c5b4e436 Mon Sep 17 00:00:00 2001 From: sHa Date: Thu, 12 Jun 2025 16:14:17 +0300 Subject: [PATCH] feat: Add Filter, ListViewSwitcher, SearchBar, and ThemeSwitcher components - Implemented Filter component for managing tags and brands with localStorage and URL parameter updates. - Created ListViewSwitcher component to toggle between compact, grid, and list views. - Developed SearchBar component with global hotkey support for quick access and URL search parameter management. - Added ThemeSwitcher component to allow users to switch between system, light, and dark themes. --- public/data/logos.json | 9 +- src/App.svelte | 68 +- src/components/Filter.svelte | 806 +++++++++++++++++ src/components/Header.svelte | 1151 +----------------------- src/components/ListViewSwitcher.svelte | 143 +++ src/components/SearchBar.svelte | 156 ++++ src/components/ThemeSwitcher.svelte | 84 ++ 7 files changed, 1289 insertions(+), 1128 deletions(-) create mode 100644 src/components/Filter.svelte create mode 100644 src/components/ListViewSwitcher.svelte create mode 100644 src/components/SearchBar.svelte create mode 100644 src/components/ThemeSwitcher.svelte diff --git a/public/data/logos.json b/public/data/logos.json index e0f1324..60ac57d 100644 --- a/public/data/logos.json +++ b/public/data/logos.json @@ -70,7 +70,8 @@ "tags": [ "Design", "Software" - ] + ], + "variants": ["logo_only", "icon"] }, { "name": "Affinity Photo", @@ -80,7 +81,8 @@ "brand": "Affinity", "tags": [ "Software" - ] + ], + "variants": ["logo_only", "icon"] }, { "name": "Affinity Publisher", @@ -91,7 +93,8 @@ "tags": [ "Design", "Software" - ] + ], + "variants": ["logo_only", "icon"] }, { "name": "Amazon", diff --git a/src/App.svelte b/src/App.svelte index b26b78c..bc2db30 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -22,6 +22,7 @@ let allTags = []; let selectedTags = []; let selectedBrands = []; + let selectedVariants = []; let tagDropdownOpen = false; let showModal = false; let selectedLogo = null; @@ -48,7 +49,14 @@ const matchesBrands = !selectedBrands.length || (logo.brand && selectedBrands.includes(logo.brand)); - return matchesSearch && matchesTags && matchesBrands; + const matchesVariants = + !selectedVariants.length || + (logo.variant && ( + Array.isArray(logo.variant) + ? logo.variant.some((v) => selectedVariants.includes(v)) + : selectedVariants.includes(logo.variant) + )); + return matchesSearch && matchesTags && matchesBrands && matchesVariants; }); window.appData.displayLogos = @@ -100,6 +108,7 @@ allTags: [], selectedTags: [], selectedBrands: [], + selectedVariants: [], tagDropdownOpen, compactMode, setSearchQuery, @@ -118,6 +127,8 @@ onDownload: downloadLogo, addBrand, removeBrand, + addVariant, + removeVariant, }; } @@ -157,6 +168,7 @@ allTags, selectedTags, selectedBrands, + selectedVariants, tagDropdownOpen, compactMode, setSearchQuery, @@ -175,6 +187,8 @@ onDownload: downloadLogo, addBrand, removeBrand, + addVariant, + removeVariant, }; console.log( "App: Updated window.appData after loading with", @@ -248,6 +262,21 @@ localStorage.removeItem("selectedBrands"); } } + + // Restore selected variants from localStorage + const savedVariants = localStorage.getItem("selectedVariants"); + if (savedVariants) { + try { + const parsedVariants = JSON.parse(savedVariants); + if (Array.isArray(parsedVariants)) { + selectedVariants = parsedVariants; + console.log("App: Restored selectedVariants from localStorage:", selectedVariants); + } + } catch (error) { + console.error("App: Error parsing saved variants:", error); + localStorage.removeItem("selectedVariants"); + } + } }); // Make sure to apply theme whenever it changes @@ -268,7 +297,7 @@ ).values(), ).sort((a, b) => a.text.localeCompare(b.text)); - // Update the filtering logic to include brand filtering in the reactive statement + // Update the filtering logic to include brand and variant filtering in the reactive statement $: filteredLogos = logos.filter((logo) => { const matchesSearch = logo.name.toLowerCase().includes(searchQuery.toLowerCase()) || @@ -283,7 +312,14 @@ const matchesBrands = !selectedBrands.length || (logo.brand && selectedBrands.includes(logo.brand)); - return matchesSearch && matchesTags && matchesBrands; + const matchesVariants = + !selectedVariants.length || + (logo.variant && ( + Array.isArray(logo.variant) + ? logo.variant.some((v) => selectedVariants.includes(v)) + : selectedVariants.includes(logo.variant) + )); + return matchesSearch && matchesTags && matchesBrands && matchesVariants; }); $: displayLogos = @@ -317,6 +353,7 @@ allTags, selectedTags, selectedBrands, + selectedVariants, tagDropdownOpen, compactMode, setSearchQuery, @@ -333,6 +370,8 @@ toggleTag, addBrand, removeBrand, + addVariant, + removeVariant, getTagObj, closeDropdown, setCompactMode, @@ -564,6 +603,20 @@ } } + function addVariant(variant) { + if (!selectedVariants.includes(variant)) { + selectedVariants = [...selectedVariants, variant]; + localStorage.setItem("selectedVariants", JSON.stringify(selectedVariants)); + updateFilteredLogosImmediate(); + } + } + + function removeVariant(variant) { + selectedVariants = selectedVariants.filter((v) => v !== variant); + localStorage.setItem("selectedVariants", JSON.stringify(selectedVariants)); + updateFilteredLogosImmediate(); + } + // Helper function to immediately update filtered/display logos in window.appData function updateFilteredLogosImmediate() { if (typeof window !== "undefined" && window.appData) { @@ -581,7 +634,14 @@ const matchesBrands = !selectedBrands.length || (logo.brand && selectedBrands.includes(logo.brand)); - return matchesSearch && matchesTags && matchesBrands; + const matchesVariants = + !selectedVariants.length || + (logo.variant && ( + Array.isArray(logo.variant) + ? logo.variant.some((v) => selectedVariants.includes(v)) + : selectedVariants.includes(logo.variant) + )); + return matchesSearch && matchesTags && matchesBrands && matchesVariants; }); window.appData.displayLogos = diff --git a/src/components/Filter.svelte b/src/components/Filter.svelte new file mode 100644 index 0000000..52e7d89 --- /dev/null +++ b/src/components/Filter.svelte @@ -0,0 +1,806 @@ + + +
+
+ + {#if tagDropdownOpen} + +
+
+ +
+ + {#if filteredAvailableTags.length > 0 || tagSearchQuery || allBrands.length > 0} +
+
+
+ + +
+ + + + {#if activeTab === "categories"} + {#if filteredAllTags.length > 0} +
+ {#each filteredAllTags as tagObj} + {@const isSelected = selectedTags.includes(tagObj.text)} + + {/each} +
+ {:else} +
+ {tagSearchQuery + ? "No categories match your search" + : "No available categories"} +
+ {/if} + {:else} + {#if filteredAllBrands.length > 0} +
+ {#each filteredAllBrands as brand} + {@const isSelected = selectedBrands.includes(brand)} + + {/each} +
+ {:else} +
+ {tagSearchQuery + ? "No brands match your search" + : "No available brands"} +
+ {/if} + {/if} +
+ {/if} + + {#if selectedTags.length > 0 || selectedBrands.length > 0 || compactMode} +
+
+ +
+ {/if} +
+ {/if} +
+ +
+ {#each selectedTags as tagText} + + {/each} + + {#each selectedBrands as brand} + + {/each} + + {#if compactMode} + + {/if} +
+
+ + diff --git a/src/components/Header.svelte b/src/components/Header.svelte index 9d99484..3266422 100644 --- a/src/components/Header.svelte +++ b/src/components/Header.svelte @@ -1,5 +1,9 @@
@@ -147,502 +45,32 @@ {allLogos ? allLogos.length : 0} images in gallery {/if} -
-
- - - -
-
+
- -
-
- - {#if tagDropdownOpen} - -
-
- -
- - {#if filteredAvailableTags.length > 0 || tagSearchQuery || allBrands.length > 0} -
-
-
- - -
- - - - {#if activeTab === "categories"} - {#if filteredAllTags.length > 0} -
- {#each filteredAllTags as tagObj} - {@const isSelected = selectedTags.includes(tagObj.text)} - - {/each} -
- {:else} -
- {tagSearchQuery - ? "No categories match your search" - : "No available categories"} -
- {/if} - {:else} - {#if filteredAllBrands.length > 0} -
- {#each filteredAllBrands as brand} - {@const isSelected = selectedBrands.includes(brand)} - - {/each} -
- {:else} -
- {tagSearchQuery - ? "No brands match your search" - : "No available brands"} -
- {/if} - {/if} -
- {/if} - - {#if selectedTags.length > 0 || selectedBrands.length > 0 || compactMode} -
-
- -
- {/if} -
- {/if} -
- -
- {#each selectedTags as tagText} - - {/each} - - {#each selectedBrands as brand} - - {/each} - - {#if compactMode} - - {/if} -
-
-
-
- - - -
-
+ + +
@@ -692,520 +120,6 @@ margin: 1rem 0; } - .search-bar { - margin-bottom: 0; - width: 100%; - max-width: 500px; - position: relative; - display: flex; - align-items: center; - } - - .search-bar input { - width: 100%; - padding: 0.75rem; - border: 1px solid var(--color-border); - border-radius: 4px; - font-size: 1rem; - background: var(--color-card); - color: var(--color-text); - padding-right: 4em; - } - - .clear-btn { - position: absolute; - right: 2.5em; /* Positioned to leave space for hotkey hint */ - background: none; - border: none; - padding: 0; - margin: 0; - cursor: pointer; - color: #888; - display: flex; - align-items: center; - justify-content: center; - height: 100%; - } - - .clear-btn:hover { - color: #f44336; - } - - .hotkey-hint { - position: absolute; - right: 0.5em; - display: flex; - align-items: center; - pointer-events: none; - } - - .hotkey-hint kbd { - background: var(--color-border); - color: var(--color-text); - border: 1px solid var(--color-border); - border-radius: 3px; - padding: 0.2em 0.4em; - font-size: 0.75em; - font-family: monospace; - font-weight: bold; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); - } - - .theme-switcher { - display: flex; - align-items: center; - gap: 0.2rem; - margin-left: auto; - } - - .filter-section { - display: grid; - grid-template-columns: auto 1fr; - gap: 0.5rem; - align-items: flex-start; - position: relative; - } - - /* Base styles for all selected filter buttons */ - .selected-filter-btn { - border: none; - border-radius: 4px; - padding: 0.1em 0.7em; - font-size: 0.75em; - font-weight: 300; - letter-spacing: 0.02em; - cursor: pointer; - display: flex; - align-items: center; - gap: 0.3em; - opacity: 1; - transition: background 0.2s, color 0.2s, transform 0.1s; - position: relative; - color: #fff; - } - - .selected-filter-btn:hover { - filter: brightness(1.6); - } - - .selected-filter-btn .close { - margin-left: 0.4em; - font-size: 1.1em; - font-weight: bold; - cursor: pointer; - opacity: 0.7; - transition: opacity 0.2s; - } - - .selected-filter-btn .close:hover { - opacity: 1; - } - - /* Type-specific styles */ - .selected-tag { - background: var(--color-accent); - color: #fff; - } - - .selected-tag:hover { - background: var(--color-accent); - } - - .selected-brand { - background: #27ae60; - color: #fff; - } - - .selected-brand:hover { - background: var(--additional-color); - } - - .compact-indicator { - background: var(--color-border); - color: var(--color-text); - opacity: 0.8; - } - - .compact-indicator:hover { - opacity: 1; - background: var(--color-text); - color: var(--color-card); - } - - .selected-tag { - background: var(--color-accent); - align-items: center; - } - - .compact-indicator { - background: var(--color-border); - color: var(--color-text); - } - - .compact-indicator:hover { - opacity: 1; - background: var(--color-text); - color: var(--color-card); - } - - .filter-dropdown { - grid-column: 1; - position: relative; - display: inline-block; - } - - .filter-toggle { - background: var(--color-card); - color: var(--color-text); - border: 1px solid var(--color-border); - border-radius: 6px; - padding: 0.6em 0.8em; - font-size: 0.9em; - cursor: pointer; - transition: - background 0.2s, - color 0.2s, - border-color 0.2s; - display: flex; - align-items: center; - justify-content: center; - position: relative; - gap: 0.3rem; - height: 40px; - } - - .filter-toggle:hover, - .filter-toggle.active { - background: var(--color-accent); - color: #fff; - border-color: var(--color-accent); - } - - .filter-count { - background: var(--color-accent); - color: #fff; - border-radius: 50%; - min-width: 16px; - height: 16px; - font-size: 0.7em; - font-weight: 600; - display: flex; - align-items: center; - justify-content: center; - position: absolute; - bottom: -6px; - right: -6px; - padding: 0 2px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); - } - - .filter-toggle.active .filter-count { - background: #fff; - color: var(--color-accent); - } - - .filter-dropdown-panel { - position: absolute; - left: 0; - top: 100%; - margin-top: 0.5rem; - min-width: 250px; - background: var(--color-card); - color: var(--color-text); - border: 1px solid var(--color-border); - border-radius: 8px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); - z-index: 1000; - padding: 1rem; - display: flex; - flex-direction: column; - gap: 0.5rem; - } - - .filter-options { - display: flex; - flex-direction: column; - gap: 0.5rem; - } - - .filter-separator { - height: 1px; - background: var(--color-border); - margin: 0.5rem 0; - } - - .filter-tabs { - display: flex; - margin-bottom: 0.5rem; - } - - .filter-tab { - background: none; - border: none; - color: var(--color-text); - cursor: pointer; - padding: 0.5rem 0.75rem; - border-bottom: 2px solid transparent; - transition: color 0.2s, border-color 0.2s; - font-size: 0.9rem; - opacity: 0.7; - border-radius: 0; - } - - .filter-tab:hover { - opacity: 1; - } - - .filter-tab.active { - color: var(--color-accent); - border-bottom-color: var(--color-accent); - opacity: 1; - font-weight: 500; - } - - .tags-search-bar { - position: relative; - margin-bottom: 0.5rem; - } - - .tags-search-input { - width: 100%; - padding: 0.5rem 2.5rem 0.5rem 0.5rem; - border: 1px solid var(--color-border); - border-radius: 4px; - font-size: 0.85em; - background: var(--color-card); - color: var(--color-text); - } - - .tags-search-clear { - position: absolute; - right: 0.5rem; - top: 50%; - transform: translateY(-50%); - background: none; - border: none; - padding: 0.2rem; - cursor: pointer; - color: #888; - display: flex; - align-items: center; - justify-content: center; - border-radius: 2px; - transition: - color 0.2s, - background 0.2s; - } - - .tags-search-clear:hover { - color: #f44336; - } - - .filter-tags-list { - display: flex; - flex-direction: column; - gap: 0.2rem; - max-height: 200px; - overflow-y: auto; - } - - .filter-tag-item { - background: none; - border: none; - padding: 0.4rem 0.5rem; - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - border-radius: 4px; - transition: background 0.2s; - text-align: left; - color: var(--color-text); - } - - .filter-tag-item:hover { - background: var(--color-border); - } - - .filter-tag-item.selected { - background: none; - color: var(--color-text); - } - - .filter-tag-item.selected:hover { - background: var(--color-border); - opacity: 1; - } - - .filter-brand-item.selected { - background: none; - color: var(--color-text); - } - - .filter-brand-item.selected:hover { - background: var(--color-border); - } - - .tag-icon { - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - position: relative; - font-size: 14px; - } - - .filter-tag-item .tag-icon .hover-icon { - opacity: 0; - transition: opacity 0.2s; - position: absolute; - } - - .filter-tag-item:hover .tag-icon .hover-icon { - opacity: 1; - } - - .filter-tag-item:hover .tag-icon .permanent-icon { - opacity: 0; - } - - .filter-option-item:hover .option-icon .hover-icon { - opacity: 1; - } - - .filter-option-item:hover .option-icon .permanent-icon { - opacity: 0; - } - - .tag-text { - font-size: 0.85em; - font-weight: 500; - } - - .brand-text { - font-size: 0.85em; - font-weight: 500; - } - - .no-tags { - color: #888; - font-size: 0.85em; - padding: 1rem; - text-align: center; - font-style: italic; - } - - .filter-option-item { - background: none; - border: none; - padding: 0.4rem 0.5rem; - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - border-radius: 4px; - transition: background 0.2s; - text-align: left; - color: var(--color-text); - width: 100%; - } - - .filter-option-item:hover { - background: var(--color-border); - } - - .option-icon { - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - position: relative; - font-size: 14px; - } - - .filter-option-item .option-icon .hover-icon { - opacity: 0; - transition: opacity 0.2s; - position: absolute; - } - - .filter-option-item:hover .option-icon .hover-icon { - opacity: 1; - } - - .filter-option-item:hover .option-icon > *:not(.hover-icon) { - opacity: 0; - } - - .permanent-icon { - position: absolute; - transition: opacity 0.2s; - } - - .hover-icon { - opacity: 0; - transition: opacity 0.2s; - position: absolute; - } - - .clear-all-section { - display: flex; - flex-direction: column; - } - - .clear-all-button { - background: none; - border: none; - padding: 0.4rem 0.5rem; - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - border-radius: 4px; - transition: background 0.2s; - text-align: left; - color: var(--color-text); - width: 100%; - } - - .clear-all-button:hover { - background: var(--color-border); - } - - .clear-all-icon { - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - font-size: 14px; - } - - .clear-all-text { - font-size: 0.85em; - font-weight: 500; - } - - - - - - .selected-filters { - grid-column: 2; - display: flex; - flex-wrap: wrap; - gap: 0.3rem; - align-items: flex-end; - min-height: 2.5rem; - max-height: 5rem; - overflow: hidden; - } - @media (max-width: 700px) { .header-row { display: grid; @@ -1220,11 +134,6 @@ grid-row: 1; } - .theme-switcher { - grid-column: 2; - grid-row: 1; - } - .logo-count { grid-column: 1 / -1; grid-row: 2; @@ -1241,17 +150,17 @@ margin: 1rem 0; } - .search-bar { + .header-controls :global(.search-bar) { grid-column: 1; grid-row: 1; } - .view-toggle { + .header-controls :global(.view-toggle) { grid-column: 2; grid-row: 1; } - .filter-section { + .header-controls :global(.filter-section) { grid-column: 1 / -1; grid-row: 2; } diff --git a/src/components/ListViewSwitcher.svelte b/src/components/ListViewSwitcher.svelte new file mode 100644 index 0000000..dfaa743 --- /dev/null +++ b/src/components/ListViewSwitcher.svelte @@ -0,0 +1,143 @@ + + +
+
+ + + +
+
+ + diff --git a/src/components/SearchBar.svelte b/src/components/SearchBar.svelte new file mode 100644 index 0000000..b022d0e --- /dev/null +++ b/src/components/SearchBar.svelte @@ -0,0 +1,156 @@ + + + + + diff --git a/src/components/ThemeSwitcher.svelte b/src/components/ThemeSwitcher.svelte new file mode 100644 index 0000000..859747d --- /dev/null +++ b/src/components/ThemeSwitcher.svelte @@ -0,0 +1,84 @@ + + +
+
+ + + +
+
+ +