mirror of
https://github.com/shadoll/sLogos.git
synced 2025-12-20 11:32:01 +00:00
feat: add brand filtering functionality and integrate with localStorage
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
let mq;
|
let mq;
|
||||||
let allTags = [];
|
let allTags = [];
|
||||||
let selectedTags = [];
|
let selectedTags = [];
|
||||||
|
let selectedBrands = [];
|
||||||
let tagDropdownOpen = false;
|
let tagDropdownOpen = false;
|
||||||
let showModal = false;
|
let showModal = false;
|
||||||
let selectedLogo = null;
|
let selectedLogo = null;
|
||||||
@@ -44,7 +45,10 @@
|
|||||||
logo.tags.some((tag) =>
|
logo.tags.some((tag) =>
|
||||||
selectedTags.includes(typeof tag === "string" ? tag : tag.text),
|
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 =
|
window.appData.displayLogos =
|
||||||
@@ -95,6 +99,7 @@
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
allTags: [],
|
allTags: [],
|
||||||
selectedTags: [],
|
selectedTags: [],
|
||||||
|
selectedBrands: [],
|
||||||
tagDropdownOpen,
|
tagDropdownOpen,
|
||||||
compactMode,
|
compactMode,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
@@ -111,6 +116,8 @@
|
|||||||
setCompactMode,
|
setCompactMode,
|
||||||
onCopy: copyUrl,
|
onCopy: copyUrl,
|
||||||
onDownload: downloadLogo,
|
onDownload: downloadLogo,
|
||||||
|
addBrand,
|
||||||
|
removeBrand,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +156,7 @@
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
allTags,
|
allTags,
|
||||||
selectedTags,
|
selectedTags,
|
||||||
|
selectedBrands,
|
||||||
tagDropdownOpen,
|
tagDropdownOpen,
|
||||||
compactMode,
|
compactMode,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
@@ -165,6 +173,8 @@
|
|||||||
setCompactMode,
|
setCompactMode,
|
||||||
onCopy: copyUrl,
|
onCopy: copyUrl,
|
||||||
onDownload: downloadLogo,
|
onDownload: downloadLogo,
|
||||||
|
addBrand,
|
||||||
|
removeBrand,
|
||||||
};
|
};
|
||||||
console.log(
|
console.log(
|
||||||
"App: Updated window.appData after loading with",
|
"App: Updated window.appData after loading with",
|
||||||
@@ -223,6 +233,21 @@
|
|||||||
localStorage.removeItem("selectedTags");
|
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
|
// Make sure to apply theme whenever it changes
|
||||||
@@ -243,6 +268,7 @@
|
|||||||
).values(),
|
).values(),
|
||||||
).sort((a, b) => a.text.localeCompare(b.text));
|
).sort((a, b) => a.text.localeCompare(b.text));
|
||||||
|
|
||||||
|
// Update the filtering logic to include brand filtering in the reactive statement
|
||||||
$: filteredLogos = logos.filter((logo) => {
|
$: filteredLogos = logos.filter((logo) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
logo.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
logo.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
@@ -254,7 +280,10 @@
|
|||||||
logo.tags.some((tag) =>
|
logo.tags.some((tag) =>
|
||||||
selectedTags.includes(typeof tag === "string" ? tag : tag.text),
|
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 =
|
$: displayLogos =
|
||||||
@@ -287,6 +316,7 @@
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
allTags,
|
allTags,
|
||||||
selectedTags,
|
selectedTags,
|
||||||
|
selectedBrands,
|
||||||
tagDropdownOpen,
|
tagDropdownOpen,
|
||||||
compactMode,
|
compactMode,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
@@ -301,6 +331,8 @@
|
|||||||
addTag,
|
addTag,
|
||||||
removeTag,
|
removeTag,
|
||||||
toggleTag,
|
toggleTag,
|
||||||
|
addBrand,
|
||||||
|
removeBrand,
|
||||||
getTagObj,
|
getTagObj,
|
||||||
closeDropdown,
|
closeDropdown,
|
||||||
setCompactMode,
|
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
|
// Helper function to immediately update filtered/display logos in window.appData
|
||||||
function updateFilteredLogosImmediate() {
|
function updateFilteredLogosImmediate() {
|
||||||
if (typeof window !== "undefined" && window.appData) {
|
if (typeof window !== "undefined" && window.appData) {
|
||||||
@@ -504,7 +578,10 @@
|
|||||||
logo.tags.some((tag) =>
|
logo.tags.some((tag) =>
|
||||||
selectedTags.includes(typeof tag === "string" ? tag : tag.text),
|
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 =
|
window.appData.displayLogos =
|
||||||
|
|||||||
@@ -13,16 +13,20 @@
|
|||||||
export let setSearchQuery;
|
export let setSearchQuery;
|
||||||
export let allTags = [];
|
export let allTags = [];
|
||||||
export let selectedTags = [];
|
export let selectedTags = [];
|
||||||
|
export let selectedBrands = [];
|
||||||
export let tagDropdownOpen = false;
|
export let tagDropdownOpen = false;
|
||||||
export let toggleDropdown = () => console.log("toggleDropdown not provided");
|
export let toggleDropdown = () => console.log("toggleDropdown not provided");
|
||||||
export let addTag = () => console.log("addTag not provided");
|
export let addTag = () => console.log("addTag not provided");
|
||||||
export let removeTag = () => console.log("removeTag 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 getTagObj = (tag) => ({ text: tag });
|
||||||
export let compactMode = false;
|
export let compactMode = false;
|
||||||
export let setCompactMode = () => {};
|
export let setCompactMode = () => {};
|
||||||
|
|
||||||
let searchInput; // Reference to the search input element
|
let searchInput; // Reference to the search input element
|
||||||
let tagSearchQuery = ""; // Search query for filtering tags
|
let tagSearchQuery = ""; // Search query for filtering tags
|
||||||
|
let activeTab = "categories"; // "categories" or "brands"
|
||||||
|
|
||||||
// Filter available tags based on search query
|
// Filter available tags based on search query
|
||||||
$: filteredAvailableTags = allTags.filter(
|
$: filteredAvailableTags = allTags.filter(
|
||||||
@@ -36,6 +40,20 @@
|
|||||||
t.text.toLowerCase().includes(tagSearchQuery.toLowerCase()),
|
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(() => {
|
onMount(() => {
|
||||||
// Add global keydown listener for the / hotkey
|
// Add global keydown listener for the / hotkey
|
||||||
function handleKeydown(event) {
|
function handleKeydown(event) {
|
||||||
@@ -74,6 +92,43 @@
|
|||||||
|
|
||||||
console.log("Header: Search query set to:", searchQuery);
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="main-header">
|
<header class="main-header">
|
||||||
@@ -229,9 +284,9 @@
|
|||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{#if selectedTags.length + (compactMode ? 1 : 0) > 0}
|
{#if selectedTags.length + selectedBrands.length + (compactMode ? 1 : 0) > 0}
|
||||||
<span class="filter-count"
|
<span class="filter-count"
|
||||||
>{selectedTags.length + (compactMode ? 1 : 0)}</span
|
>{selectedTags.length + selectedBrands.length + (compactMode ? 1 : 0)}</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
@@ -265,15 +320,30 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if filteredAvailableTags.length > 0 || tagSearchQuery}
|
{#if filteredAvailableTags.length > 0 || tagSearchQuery || allBrands.length > 0}
|
||||||
<div class="filter-separator"></div>
|
<div class="filter-separator"></div>
|
||||||
<div class="filter-tags-section">
|
<div class="filter-tabs-section">
|
||||||
<div class="filter-section-title">Categories</div>
|
<div class="filter-tabs">
|
||||||
|
<button
|
||||||
|
class="filter-tab"
|
||||||
|
class:active={activeTab === "categories"}
|
||||||
|
on:click={() => activeTab = "categories"}
|
||||||
|
>
|
||||||
|
Categories
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="filter-tab"
|
||||||
|
class:active={activeTab === "brands"}
|
||||||
|
on:click={() => activeTab = "brands"}
|
||||||
|
>
|
||||||
|
Brands
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="tags-search-bar">
|
<div class="tags-search-bar">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search categories..."
|
placeholder={activeTab === "categories" ? "Search categories..." : "Search brands..."}
|
||||||
bind:value={tagSearchQuery}
|
bind:value={tagSearchQuery}
|
||||||
class="tags-search-input"
|
class="tags-search-input"
|
||||||
/>
|
/>
|
||||||
@@ -281,7 +351,7 @@
|
|||||||
<button
|
<button
|
||||||
class="tags-search-clear"
|
class="tags-search-clear"
|
||||||
on:click|stopPropagation={() => (tagSearchQuery = "")}
|
on:click|stopPropagation={() => (tagSearchQuery = "")}
|
||||||
aria-label="Clear tag search"
|
aria-label="Clear search"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="14"
|
width="14"
|
||||||
@@ -301,60 +371,104 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if filteredAllTags.length > 0}
|
{#if activeTab === "categories"}
|
||||||
<div class="filter-tags-list">
|
{#if filteredAllTags.length > 0}
|
||||||
{#each filteredAllTags as tagObj}
|
<div class="filter-tags-list">
|
||||||
{@const isSelected = selectedTags.includes(tagObj.text)}
|
{#each filteredAllTags as tagObj}
|
||||||
<button
|
{@const isSelected = selectedTags.includes(tagObj.text)}
|
||||||
class="filter-tag-item"
|
<button
|
||||||
class:selected={isSelected}
|
class="filter-tag-item"
|
||||||
on:click={() =>
|
class:selected={isSelected}
|
||||||
isSelected
|
on:click={() =>
|
||||||
? removeTag(tagObj.text)
|
isSelected
|
||||||
: addTag(tagObj.text)}
|
? removeTag(tagObj.text)
|
||||||
aria-label={isSelected
|
: addTag(tagObj.text)}
|
||||||
? `Remove tag: ${tagObj.text}`
|
aria-label={isSelected
|
||||||
: `Add tag: ${tagObj.text}`}
|
? `Remove tag: ${tagObj.text}`
|
||||||
>
|
: `Add tag: ${tagObj.text}`}
|
||||||
<span class="tag-icon">
|
|
||||||
<span class="permanent-icon">
|
|
||||||
{#if isSelected}
|
|
||||||
✔︎
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<span class="hover-icon">
|
|
||||||
{#if isSelected}
|
|
||||||
✕
|
|
||||||
{:else}
|
|
||||||
✔︎
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="tag-text"
|
|
||||||
style={tagObj.color ? `color: ${tagObj.color};` : ""}
|
|
||||||
>{tagObj.text}</span
|
|
||||||
>
|
>
|
||||||
</button>
|
<span class="tag-icon">
|
||||||
{/each}
|
<span class="permanent-icon">
|
||||||
</div>
|
{#if isSelected}
|
||||||
|
✔︎
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<span class="hover-icon">
|
||||||
|
{#if isSelected}
|
||||||
|
✕
|
||||||
|
{:else}
|
||||||
|
✔︎
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="tag-text"
|
||||||
|
style={tagObj.color ? `color: ${tagObj.color};` : ""}
|
||||||
|
>{tagObj.text}</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="no-tags">
|
||||||
|
{tagSearchQuery
|
||||||
|
? "No categories match your search"
|
||||||
|
: "No available categories"}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="no-tags">
|
{#if filteredAllBrands.length > 0}
|
||||||
{tagSearchQuery
|
<div class="filter-tags-list">
|
||||||
? "No tags match your search"
|
{#each filteredAllBrands as brand}
|
||||||
: "No available tags"}
|
{@const isSelected = selectedBrands.includes(brand)}
|
||||||
</div>
|
<button
|
||||||
|
class="filter-tag-item filter-brand-item"
|
||||||
|
class:selected={isSelected}
|
||||||
|
on:click={() =>
|
||||||
|
isSelected
|
||||||
|
? removeBrand(brand)
|
||||||
|
: addBrand(brand)}
|
||||||
|
aria-label={isSelected
|
||||||
|
? `Remove brand: ${brand}`
|
||||||
|
: `Add brand: ${brand}`}
|
||||||
|
>
|
||||||
|
<span class="tag-icon">
|
||||||
|
<span class="permanent-icon">
|
||||||
|
{#if isSelected}
|
||||||
|
✔︎
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<span class="hover-icon">
|
||||||
|
{#if isSelected}
|
||||||
|
✕
|
||||||
|
{:else}
|
||||||
|
✔︎
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="brand-text">{brand}</span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="no-tags">
|
||||||
|
{tagSearchQuery
|
||||||
|
? "No brands match your search"
|
||||||
|
: "No available brands"}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if selectedTags.length > 0 || compactMode}
|
{#if selectedTags.length > 0 || selectedBrands.length > 0 || compactMode}
|
||||||
<div class="filter-separator"></div>
|
<div class="filter-separator"></div>
|
||||||
<div class="clear-all-section">
|
<div class="clear-all-section">
|
||||||
<button
|
<button
|
||||||
class="clear-all-button"
|
class="clear-all-button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
selectedTags.forEach(tag => removeTag(tag));
|
selectedTags.forEach(tag => removeTag(tag));
|
||||||
|
selectedBrands.forEach(brand => removeBrand(brand));
|
||||||
if (compactMode) setCompactMode(false);
|
if (compactMode) setCompactMode(false);
|
||||||
}}
|
}}
|
||||||
aria-label="Clear all filters"
|
aria-label="Clear all filters"
|
||||||
@@ -379,6 +493,17 @@
|
|||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#each selectedBrands as brand}
|
||||||
|
<button
|
||||||
|
class="selected-brand"
|
||||||
|
aria-label={`Remove brand: ${brand}`}
|
||||||
|
on:click={() => removeBrand(brand)}
|
||||||
|
>
|
||||||
|
{brand}
|
||||||
|
<span class="close">×</span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
|
||||||
{#if compactMode}
|
{#if compactMode}
|
||||||
<button
|
<button
|
||||||
class="compact-indicator"
|
class="compact-indicator"
|
||||||
@@ -500,10 +625,7 @@
|
|||||||
/><rect
|
/><rect
|
||||||
x="4"
|
x="4"
|
||||||
y="13"
|
y="13"
|
||||||
width="12"
|
width="12" height="2" fill="currentColor" /></svg
|
||||||
height="2"
|
|
||||||
fill="currentColor"
|
|
||||||
/></svg
|
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -773,17 +895,32 @@
|
|||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-tags-section {
|
.filter-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
margin-bottom: 0.5rem;
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-section-title {
|
.filter-tab {
|
||||||
|
flex: 1;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
color: var(--color-text);
|
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 {
|
.tags-search-bar {
|
||||||
@@ -860,6 +997,15 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-brand-item.selected {
|
||||||
|
background: none;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-brand-item.selected:hover {
|
||||||
|
background: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
.tag-icon {
|
.tag-icon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@@ -898,6 +1044,11 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.brand-text {
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.no-tags {
|
.no-tags {
|
||||||
color: #888;
|
color: #888;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
@@ -999,4 +1150,71 @@
|
|||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
font-weight: 500;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,8 +14,43 @@
|
|||||||
let tagDropdownOpen = false;
|
let tagDropdownOpen = false;
|
||||||
let selectedTags = [];
|
let selectedTags = [];
|
||||||
let allTags = [];
|
let allTags = [];
|
||||||
|
let selectedBrands = [];
|
||||||
|
let addBrand = () => {};
|
||||||
|
let removeBrand = () => {};
|
||||||
let setTheme = () => {};
|
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(() => {
|
onMount(() => {
|
||||||
if (typeof window !== 'undefined' && window.appData) {
|
if (typeof window !== 'undefined' && window.appData) {
|
||||||
logos = window.appData.displayLogos || [];
|
logos = window.appData.displayLogos || [];
|
||||||
@@ -27,6 +62,7 @@
|
|||||||
tagDropdownOpen = window.appData.tagDropdownOpen || false;
|
tagDropdownOpen = window.appData.tagDropdownOpen || false;
|
||||||
selectedTags = window.appData.selectedTags || [];
|
selectedTags = window.appData.selectedTags || [];
|
||||||
allTags = window.appData.allTags || [];
|
allTags = window.appData.allTags || [];
|
||||||
|
selectedBrands = window.appData.selectedBrands || [];
|
||||||
|
|
||||||
if (window.appData.setTheme && typeof window.appData.setTheme === 'function') {
|
if (window.appData.setTheme && typeof window.appData.setTheme === 'function') {
|
||||||
console.log("Home: Found 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");
|
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
|
// Set up reactivity with window.appData
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (window.appData) {
|
if (window.appData) {
|
||||||
@@ -56,6 +104,10 @@
|
|||||||
if (window.appData.allTags) {
|
if (window.appData.allTags) {
|
||||||
allTags = [...window.appData.allTags];
|
allTags = [...window.appData.allTags];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window.appData.selectedBrands) {
|
||||||
|
selectedBrands = [...window.appData.selectedBrands];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
@@ -134,25 +186,28 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Header
|
<Header
|
||||||
{searchQuery}
|
{logos}
|
||||||
{setSearchQuery}
|
displayLogos={logos}
|
||||||
{viewMode}
|
|
||||||
{theme}
|
{theme}
|
||||||
{setTheme}
|
{setTheme}
|
||||||
|
{viewMode}
|
||||||
{setGridView}
|
{setGridView}
|
||||||
{setListView}
|
{setListView}
|
||||||
{setCompactView}
|
{setCompactView}
|
||||||
logos={allLogos}
|
{searchQuery}
|
||||||
displayLogos={logos}
|
{setSearchQuery}
|
||||||
|
allTags={allTags}
|
||||||
|
selectedTags={selectedTags}
|
||||||
|
selectedBrands={selectedBrands}
|
||||||
|
{tagDropdownOpen}
|
||||||
{toggleDropdown}
|
{toggleDropdown}
|
||||||
{addTag}
|
{addTag}
|
||||||
{removeTag}
|
{removeTag}
|
||||||
allTags={allTags}
|
addBrand={addBrand}
|
||||||
selectedTags={selectedTags}
|
removeBrand={removeBrand}
|
||||||
tagDropdownOpen={tagDropdownOpen}
|
getTagObj={(tag) => (window.appData?.getTagObj ? window.appData.getTagObj(tag) : {text: tag})}
|
||||||
{compactMode}
|
{compactMode}
|
||||||
{setCompactMode}
|
{setCompactMode}
|
||||||
getTagObj={(tag) => (window.appData?.getTagObj ? window.appData.getTagObj(tag) : {text: tag})}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|||||||
Reference in New Issue
Block a user