diff --git a/src/App.svelte b/src/App.svelte index 17a5322..b26b78c 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -21,6 +21,7 @@ let mq; let allTags = []; let selectedTags = []; + let selectedBrands = []; let tagDropdownOpen = false; let showModal = false; let selectedLogo = null; @@ -44,7 +45,10 @@ logo.tags.some((tag) => selectedTags.includes(typeof tag === "string" ? tag : tag.text), )); - return matchesSearch && matchesTags; + const matchesBrands = + !selectedBrands.length || + (logo.brand && selectedBrands.includes(logo.brand)); + return matchesSearch && matchesTags && matchesBrands; }); window.appData.displayLogos = @@ -95,6 +99,7 @@ searchQuery, allTags: [], selectedTags: [], + selectedBrands: [], tagDropdownOpen, compactMode, setSearchQuery, @@ -111,6 +116,8 @@ setCompactMode, onCopy: copyUrl, onDownload: downloadLogo, + addBrand, + removeBrand, }; } @@ -149,6 +156,7 @@ searchQuery, allTags, selectedTags, + selectedBrands, tagDropdownOpen, compactMode, setSearchQuery, @@ -165,6 +173,8 @@ setCompactMode, onCopy: copyUrl, onDownload: downloadLogo, + addBrand, + removeBrand, }; console.log( "App: Updated window.appData after loading with", @@ -223,6 +233,21 @@ localStorage.removeItem("selectedTags"); } } + + // Restore selected brands from localStorage + const savedBrands = localStorage.getItem("selectedBrands"); + if (savedBrands) { + try { + const parsedBrands = JSON.parse(savedBrands); + if (Array.isArray(parsedBrands)) { + selectedBrands = parsedBrands; + console.log("App: Restored selectedBrands from localStorage:", selectedBrands); + } + } catch (error) { + console.error("App: Error parsing saved brands:", error); + localStorage.removeItem("selectedBrands"); + } + } }); // Make sure to apply theme whenever it changes @@ -243,6 +268,7 @@ ).values(), ).sort((a, b) => a.text.localeCompare(b.text)); + // Update the filtering logic to include brand filtering in the reactive statement $: filteredLogos = logos.filter((logo) => { const matchesSearch = logo.name.toLowerCase().includes(searchQuery.toLowerCase()) || @@ -254,7 +280,10 @@ logo.tags.some((tag) => selectedTags.includes(typeof tag === "string" ? tag : tag.text), )); - return matchesSearch && matchesTags; + const matchesBrands = + !selectedBrands.length || + (logo.brand && selectedBrands.includes(logo.brand)); + return matchesSearch && matchesTags && matchesBrands; }); $: displayLogos = @@ -287,6 +316,7 @@ searchQuery, allTags, selectedTags, + selectedBrands, tagDropdownOpen, compactMode, setSearchQuery, @@ -301,6 +331,8 @@ addTag, removeTag, toggleTag, + addBrand, + removeBrand, getTagObj, closeDropdown, setCompactMode, @@ -490,6 +522,48 @@ } } + function addBrand(brand) { + console.log("App: Adding brand:", brand); + if (!selectedBrands.includes(brand)) { + selectedBrands = [...selectedBrands, brand]; + localStorage.setItem("selectedBrands", JSON.stringify(selectedBrands)); + console.log("App: Updated selectedBrands:", selectedBrands); + + // Update window.appData immediately + if (typeof window !== "undefined" && window.appData) { + window.appData.selectedBrands = [...selectedBrands]; + console.log("App: Updated selectedBrands in window.appData"); + + // Update filtered logos immediately + updateFilteredLogosImmediate(); + } + } + + // Close dropdown after adding brand + tagDropdownOpen = false; + + // Also update the dropdown state in window.appData + if (typeof window !== "undefined" && window.appData) { + window.appData.tagDropdownOpen = false; + } + } + + function removeBrand(brand) { + console.log("App: Removing brand:", brand); + selectedBrands = selectedBrands.filter((b) => b !== brand); + localStorage.setItem("selectedBrands", JSON.stringify(selectedBrands)); + console.log("App: Updated selectedBrands:", selectedBrands); + + // Update window.appData immediately + if (typeof window !== "undefined" && window.appData) { + window.appData.selectedBrands = [...selectedBrands]; + console.log("App: Updated selectedBrands in window.appData"); + + // Update filtered logos immediately + updateFilteredLogosImmediate(); + } + } + // Helper function to immediately update filtered/display logos in window.appData function updateFilteredLogosImmediate() { if (typeof window !== "undefined" && window.appData) { @@ -504,7 +578,10 @@ logo.tags.some((tag) => selectedTags.includes(typeof tag === "string" ? tag : tag.text), )); - return matchesSearch && matchesTags; + const matchesBrands = + !selectedBrands.length || + (logo.brand && selectedBrands.includes(logo.brand)); + return matchesSearch && matchesTags && matchesBrands; }); window.appData.displayLogos = diff --git a/src/components/Header.svelte b/src/components/Header.svelte index 0cb74c2..da47b76 100644 --- a/src/components/Header.svelte +++ b/src/components/Header.svelte @@ -13,16 +13,20 @@ export let setSearchQuery; export let allTags = []; export let selectedTags = []; + export let selectedBrands = []; export let tagDropdownOpen = false; export let toggleDropdown = () => console.log("toggleDropdown not provided"); export let addTag = () => console.log("addTag not provided"); export let removeTag = () => console.log("removeTag not provided"); + export let addBrand = () => console.log("addBrand not provided"); + export let removeBrand = () => console.log("removeBrand not provided"); export let getTagObj = (tag) => ({ text: tag }); export let compactMode = false; export let setCompactMode = () => {}; let searchInput; // Reference to the search input element let tagSearchQuery = ""; // Search query for filtering tags + let activeTab = "categories"; // "categories" or "brands" // Filter available tags based on search query $: filteredAvailableTags = allTags.filter( @@ -36,6 +40,20 @@ t.text.toLowerCase().includes(tagSearchQuery.toLowerCase()), ); + // Compute all unique brands + $: allBrands = Array.from( + new Set( + logos + .map((logo) => logo.brand) + .filter((brand) => brand && brand.trim() !== "") + ) + ).sort(); + + // Filter brands based on search query + $: filteredAllBrands = allBrands.filter((brand) => + brand.toLowerCase().includes(tagSearchQuery.toLowerCase()) + ); + onMount(() => { // Add global keydown listener for the / hotkey function handleKeydown(event) { @@ -74,6 +92,43 @@ console.log("Header: Search query set to:", searchQuery); } + + function toggleBrand(brand) { + if (selectedBrands.includes(brand)) { + selectedBrands = selectedBrands.filter(b => b !== brand); + } else { + selectedBrands = [...selectedBrands, brand]; + } + + // Save to localStorage + localStorage.setItem("selectedBrands", JSON.stringify(selectedBrands)); + + // Update URL parameters + updateFilterParams(); + } + + function updateFilterParams() { + const params = new URLSearchParams(window.location.search); + + // Update tags + if (selectedTags.length > 0) { + params.set("tags", selectedTags.join(",")); + } else { + params.delete("tags"); + } + + // Update brands + if (selectedBrands.length > 0) { + params.set("brands", selectedBrands.join(",")); + } else { + params.delete("brands"); + } + + const newUrl = window.location.pathname + (params.toString() ? "?" + params.toString() : ""); + history.replaceState(null, "", newUrl); + + + }
@@ -229,9 +284,9 @@ fill="currentColor" /> - {#if selectedTags.length + (compactMode ? 1 : 0) > 0} + {#if selectedTags.length + selectedBrands.length + (compactMode ? 1 : 0) > 0} {selectedTags.length + (compactMode ? 1 : 0)}{selectedTags.length + selectedBrands.length + (compactMode ? 1 : 0)} {/if} @@ -265,15 +320,30 @@ - {#if filteredAvailableTags.length > 0 || tagSearchQuery} + {#if filteredAvailableTags.length > 0 || tagSearchQuery || allBrands.length > 0}
-
-
Categories
+
+
+ + +
+ + + {#if isSelected} + ✔︎ + {/if} + + + {#if isSelected} + ✕ + {:else} + ✔︎ + {/if} + + + {tagObj.text} + + {/each} +
+ {:else} +
+ {tagSearchQuery + ? "No categories match your search" + : "No available categories"} +
+ {/if} {:else} -
- {tagSearchQuery - ? "No tags match your search" - : "No available tags"} -
+ {#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 || compactMode} + {#if selectedTags.length > 0 || selectedBrands.length > 0 || compactMode}
{/each} + {#each selectedBrands as brand} + + {/each} + {#if compactMode}
@@ -773,17 +895,32 @@ margin: 0.5rem 0; } - .filter-tags-section { + .filter-tabs { display: flex; - flex-direction: column; - gap: 0.5rem; + margin-bottom: 0.5rem; } - .filter-section-title { + .filter-tab { + flex: 1; + background: none; + border: none; + padding: 0.4rem 0.8rem; font-size: 0.85em; - font-weight: 600; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; color: var(--color-text); - opacity: 0.8; + opacity: 0.7; + border-radius: 0; + } + + .filter-tab.active { + background: var(--color-card); + opacity: 1; + } + + .filter-tab:hover:not(.active) { + opacity: 0.9; } .tags-search-bar { @@ -860,6 +997,15 @@ 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; @@ -898,6 +1044,11 @@ font-weight: 500; } + .brand-text { + font-size: 0.85em; + font-weight: 500; + } + .no-tags { color: #888; font-size: 0.85em; @@ -999,4 +1150,71 @@ font-size: 0.85em; font-weight: 500; } + + .filter-tabs { + display: flex; + border-bottom: 1px solid var(--color-border); + margin-bottom: 0.75rem; + } + + .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; + box-shadow: none; + } + + .selected-brand { + background: #2196F3; + color: #fff; + border: none; + border-radius: 8px; + padding: 0.2em 0.8em 0.2em 0.8em; + font-size: 0.85em; + font-weight: 500; + letter-spacing: 0.02em; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.3em; + opacity: 1; + transition: + background 0.2s, + color 0.2s; + } + + .selected-brand:hover { + background: #1976D2; + } + + .selected-brand .close { + margin-left: 0.4em; + font-size: 1.1em; + font-weight: bold; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.2s; + } + + .selected-brand .close:hover { + opacity: 1; + } diff --git a/src/pages/Home.svelte b/src/pages/Home.svelte index 8ecadb1..0fb3d62 100644 --- a/src/pages/Home.svelte +++ b/src/pages/Home.svelte @@ -14,8 +14,43 @@ let tagDropdownOpen = false; let selectedTags = []; let allTags = []; + let selectedBrands = []; + let addBrand = () => {}; + let removeBrand = () => {}; let setTheme = () => {}; + + $: ({ + logos = [], + filteredLogos = [], + displayLogos = [], + theme = "system", + effectiveTheme = "light", + viewMode = "grid", + searchQuery = "", + allTags = [], + selectedTags = [], + selectedBrands = [], + tagDropdownOpen = false, + compactMode = false, + setSearchQuery = () => {}, + setGridView = () => {}, + setListView = () => {}, + setCompactView = () => {}, + setTheme = () => {}, + toggleDropdown = () => {}, + addTag = () => {}, + removeTag = () => {}, + addBrand = () => {}, + removeBrand = () => {}, + toggleTag = () => {}, + getTagObj = () => ({ text: "" }), + closeDropdown = () => {}, + setCompactMode = () => {}, + onCopy = () => {}, + onDownload = () => {}, + } = appData || {}); + onMount(() => { if (typeof window !== 'undefined' && window.appData) { logos = window.appData.displayLogos || []; @@ -27,6 +62,7 @@ tagDropdownOpen = window.appData.tagDropdownOpen || false; selectedTags = window.appData.selectedTags || []; allTags = window.appData.allTags || []; + selectedBrands = window.appData.selectedBrands || []; if (window.appData.setTheme && typeof window.appData.setTheme === 'function') { console.log("Home: Found window.appData.setTheme function"); @@ -35,6 +71,18 @@ console.warn("Home: window.appData.setTheme not found or not a function"); } + if (window.appData.addBrand && typeof window.appData.addBrand === 'function') { + addBrand = window.appData.addBrand; + } + + if (window.appData.removeBrand && typeof window.appData.removeBrand === 'function') { + removeBrand = window.appData.removeBrand; + } + + if (window.appData.selectedBrands) { + selectedBrands = window.appData.selectedBrands; + } + // Set up reactivity with window.appData const interval = setInterval(() => { if (window.appData) { @@ -56,6 +104,10 @@ if (window.appData.allTags) { allTags = [...window.appData.allTags]; } + + if (window.appData.selectedBrands) { + selectedBrands = [...window.appData.selectedBrands]; + } } }, 100); @@ -134,25 +186,28 @@
(window.appData?.getTagObj ? window.appData.getTagObj(tag) : {text: tag})} {compactMode} {setCompactMode} - getTagObj={(tag) => (window.appData?.getTagObj ? window.appData.getTagObj(tag) : {text: tag})} />