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
@@ -3,6 +3,8 @@
import { setTheme } from '@/seqta/ui/themes/setTheme';
import Spinner from '../Spinner.svelte';
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 };
let { searchTerm } = $props<{ searchTerm: string }>();
@@ -21,6 +23,14 @@
let activeTab = $state<'all' | 'installed' | 'photos' | 'videos'>('all');
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
const loadStore = async () => {
try {
@@ -31,7 +41,7 @@
}
const data = await response.json();
backgrounds = data.backgrounds;
console.log(data.backgrounds);
fuse = new Fuse(backgrounds, fuseOptions);
debugInfo = `Loaded ${backgrounds.length} backgrounds`;
await loadSavedBackgrounds();
} catch (e) {
@@ -60,15 +70,20 @@
// Derived states
let filteredBackgrounds = $derived((() => {
let filtered = backgrounds.filter((bg: Background) => {
const matchesCategory = selectedCategory === 'All'
let filtered = backgrounds;
// 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
: selectedCategory === 'Featured'
? bg.featured
: bg.category === selectedCategory;
const matchesSearch = bg.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
bg.description.toLowerCase().includes(searchTerm.toLowerCase());
return matchesCategory && matchesSearch;
});
// Apply sorting
@@ -133,6 +148,7 @@
installingBackgrounds = new Set(installingBackgrounds).add(background.id);
try {
await saveBackgroundFromUrl(background.highResUrl, background.id, background.type);
backgroundUpdates.triggerUpdate();
} finally {
installingBackgrounds = new Set(installingBackgrounds);
installingBackgrounds.delete(background.id);
@@ -191,7 +207,7 @@
<!-- Header -->
<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">
<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">
<select
bind:value={sortBy}
@@ -281,9 +297,6 @@
</span>
{/if}
</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>
{/each}
</div>
@@ -5,6 +5,7 @@
import { onMount, onDestroy } from 'svelte'
import { loadBackground } from '@/seqta/ui/ImageBackgrounds'
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 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 {
error = null;
@@ -85,8 +86,25 @@
throw new Error("Your browser doesn't support IndexedDB. Unable to load backgrounds.");
}
const data = await readAllData();
backgrounds = await preloadBackgrounds(data);
const dbData = await readAllData();
// 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) {
if (e instanceof Error) {
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 {
if (selectedBackground === fileId) {
selectNoBackground();
@@ -150,13 +161,14 @@
if (parentElement?.classList.contains('active')) {
delay(() => {
isVisible = true;
loadFullBackgrounds();
syncBackgrounds();
}, 600);
}
}
onMount(() => {
loadBackgroundMetadata();
backgroundUpdates.addListener(syncBackgrounds);
parentElement = element.closest('.tab');
if (parentElement) {
@@ -165,6 +177,7 @@
return () => {
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();