mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
feat: add fuse.js to store search
This commit is contained in:
@@ -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();
|
||||
Reference in New Issue
Block a user