feat: add fuse.js to store search

This commit is contained in:
sethburkart123
2024-10-31 16:54:02 +11:00
parent bfb253341e
commit c1e1741b71
4 changed files with 77 additions and 21 deletions
+1
View File
@@ -72,6 +72,7 @@
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"color": "^4.2.3", "color": "^4.2.3",
"dompurify": "^3.1.6", "dompurify": "^3.1.6",
"fuse.js": "^7.0.0",
"idb": "^8.0.0", "idb": "^8.0.0",
"kolorist": "^1.8.0", "kolorist": "^1.8.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",
@@ -3,6 +3,8 @@
import { setTheme } from '@/seqta/ui/themes/setTheme'; import { setTheme } from '@/seqta/ui/themes/setTheme';
import Spinner from '../Spinner.svelte'; import Spinner from '../Spinner.svelte';
import { settingsState } from '@/seqta/utils/listeners/SettingsState' import { settingsState } from '@/seqta/utils/listeners/SettingsState'
import Fuse from 'fuse.js';
import { backgroundUpdates } from '@/svelte-interface/hooks/BackgroundUpdates'
type Background = { id: string; category: string; type: string; lowResUrl: string; highResUrl: string; name: string; description: string; featured?: boolean }; type Background = { id: string; category: string; type: string; lowResUrl: string; highResUrl: string; name: string; description: string; featured?: boolean };
let { searchTerm } = $props<{ searchTerm: string }>(); let { searchTerm } = $props<{ searchTerm: string }>();
@@ -21,6 +23,14 @@
let activeTab = $state<'all' | 'installed' | 'photos' | 'videos'>('all'); let activeTab = $state<'all' | 'installed' | 'photos' | 'videos'>('all');
let sortBy = $state<'newest' | 'popular' | 'name'>('newest'); let sortBy = $state<'newest' | 'popular' | 'name'>('newest');
// Add Fuse.js options
const fuseOptions = {
keys: ['name', 'description'],
threshold: 0.4,
ignoreLocation: true
};
let fuse: Fuse<Background>;
// Existing functions // Existing functions
const loadStore = async () => { const loadStore = async () => {
try { try {
@@ -31,7 +41,7 @@
} }
const data = await response.json(); const data = await response.json();
backgrounds = data.backgrounds; backgrounds = data.backgrounds;
console.log(data.backgrounds); fuse = new Fuse(backgrounds, fuseOptions);
debugInfo = `Loaded ${backgrounds.length} backgrounds`; debugInfo = `Loaded ${backgrounds.length} backgrounds`;
await loadSavedBackgrounds(); await loadSavedBackgrounds();
} catch (e) { } catch (e) {
@@ -60,15 +70,20 @@
// Derived states // Derived states
let filteredBackgrounds = $derived((() => { let filteredBackgrounds = $derived((() => {
let filtered = backgrounds.filter((bg: Background) => { let filtered = backgrounds;
const matchesCategory = selectedCategory === 'All'
// Use Fuse.js search if there's a search term
if (searchTerm.trim()) {
filtered = fuse?.search(searchTerm).map((result: any) => result.item) ?? [];
}
// Apply category filtering
filtered = filtered.filter((bg: Background) => {
return selectedCategory === 'All'
? true ? true
: selectedCategory === 'Featured' : selectedCategory === 'Featured'
? bg.featured ? bg.featured
: bg.category === selectedCategory; : bg.category === selectedCategory;
const matchesSearch = bg.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
bg.description.toLowerCase().includes(searchTerm.toLowerCase());
return matchesCategory && matchesSearch;
}); });
// Apply sorting // Apply sorting
@@ -133,6 +148,7 @@
installingBackgrounds = new Set(installingBackgrounds).add(background.id); installingBackgrounds = new Set(installingBackgrounds).add(background.id);
try { try {
await saveBackgroundFromUrl(background.highResUrl, background.id, background.type); await saveBackgroundFromUrl(background.highResUrl, background.id, background.type);
backgroundUpdates.triggerUpdate();
} finally { } finally {
installingBackgrounds = new Set(installingBackgrounds); installingBackgrounds = new Set(installingBackgrounds);
installingBackgrounds.delete(background.id); installingBackgrounds.delete(background.id);
@@ -191,7 +207,7 @@
<!-- Header --> <!-- Header -->
<div class="sticky top-0 z-10 p-4 bg-white border-b dark:bg-zinc-900 dark:border-zinc-700"> <div class="sticky top-0 z-10 p-4 bg-white border-b dark:bg-zinc-900 dark:border-zinc-700">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<h1 class="text-2xl font-bold">Explore Backgrounds</h1> <h1 class="text-2xl font-bold">Explore Backgrounds {searchTerm ? `- "${searchTerm}"` : ''}</h1>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<select <select
bind:value={sortBy} bind:value={sortBy}
@@ -281,9 +297,6 @@
</span> </span>
{/if} {/if}
</div> </div>
<div class="absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black to-transparent">
<h3 class="text-sm font-semibold text-white">{background.name}</h3>
</div>
</div> </div>
{/each} {/each}
</div> </div>
@@ -5,6 +5,7 @@
import { onMount, onDestroy } from 'svelte' import { onMount, onDestroy } from 'svelte'
import { loadBackground } from '@/seqta/ui/ImageBackgrounds' import { loadBackground } from '@/seqta/ui/ImageBackgrounds'
import { delay } from 'lodash' import { delay } from 'lodash'
import { backgroundUpdates } from '@/svelte-interface/hooks/BackgroundUpdates'
let { isEditMode, selectNoBackground = $bindable(), selectedBackground = $bindable() } = $props<{ isEditMode: boolean, selectNoBackground: () => void, selectedBackground: string | null }>(); let { isEditMode, selectNoBackground = $bindable(), selectedBackground = $bindable() } = $props<{ isEditMode: boolean, selectNoBackground: () => void, selectedBackground: string | null }>();
let backgrounds = $state<{ id: string; type: string; blob: Blob | null; url?: string }[]>([]); let backgrounds = $state<{ id: string; type: string; blob: Blob | null; url?: string }[]>([]);
@@ -77,7 +78,7 @@
} }
} }
async function loadFullBackgrounds(): Promise<void> { async function syncBackgrounds(): Promise<void> {
try { try {
error = null; error = null;
@@ -85,8 +86,25 @@
throw new Error("Your browser doesn't support IndexedDB. Unable to load backgrounds."); throw new Error("Your browser doesn't support IndexedDB. Unable to load backgrounds.");
} }
const data = await readAllData(); const dbData = await readAllData();
backgrounds = await preloadBackgrounds(data);
// Release existing object URLs to prevent memory leaks
backgrounds.forEach(bg => {
if (bg.url) URL.revokeObjectURL(bg.url);
});
// Create fresh background objects with new object URLs
backgrounds = dbData.map(bg => ({
id: bg.id,
type: bg.type,
blob: bg.blob,
url: URL.createObjectURL(bg.blob)
}));
// Check if selected background still exists
if (selectedBackground && !backgrounds.some(bg => bg.id === selectedBackground)) {
selectNoBackground();
}
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
error = e.message; error = e.message;
@@ -96,13 +114,6 @@
} }
} }
async function preloadBackgrounds(data: { id: string; type: string; blob: Blob }[]): Promise<{ id: string; type: string; blob: Blob; url: string }[]> {
return data.map(bg => ({
...bg,
url: URL.createObjectURL(bg.blob)
}));
}
function selectBackground(fileId: string): void { function selectBackground(fileId: string): void {
if (selectedBackground === fileId) { if (selectedBackground === fileId) {
selectNoBackground(); selectNoBackground();
@@ -150,13 +161,14 @@
if (parentElement?.classList.contains('active')) { if (parentElement?.classList.contains('active')) {
delay(() => { delay(() => {
isVisible = true; isVisible = true;
loadFullBackgrounds(); syncBackgrounds();
}, 600); }, 600);
} }
} }
onMount(() => { onMount(() => {
loadBackgroundMetadata(); loadBackgroundMetadata();
backgroundUpdates.addListener(syncBackgrounds);
parentElement = element.closest('.tab'); parentElement = element.closest('.tab');
if (parentElement) { if (parentElement) {
@@ -165,6 +177,7 @@
return () => { return () => {
observer.disconnect(); observer.disconnect();
backgroundUpdates.removeListener(syncBackgrounds);
}; };
} }
}); });
@@ -0,0 +1,29 @@
type BackgroundUpdateCallback = () => void;
class BackgroundUpdates {
private static instance: BackgroundUpdates;
private listeners: Set<BackgroundUpdateCallback> = new Set();
private constructor() {}
public static getInstance(): BackgroundUpdates {
if (!BackgroundUpdates.instance) {
BackgroundUpdates.instance = new BackgroundUpdates();
}
return BackgroundUpdates.instance;
}
public addListener(callback: BackgroundUpdateCallback): void {
this.listeners.add(callback);
}
public removeListener(callback: BackgroundUpdateCallback): void {
this.listeners.delete(callback);
}
public triggerUpdate(): void {
this.listeners.forEach(callback => callback());
}
}
export const backgroundUpdates = BackgroundUpdates.getInstance();