diff --git a/src/entities/Font/model/store/unifiedFontStore.svelte.ts b/src/entities/Font/model/store/unifiedFontStore.svelte.ts index b767e04..4a430f0 100644 --- a/src/entities/Font/model/store/unifiedFontStore.svelte.ts +++ b/src/entities/Font/model/store/unifiedFontStore.svelte.ts @@ -1,263 +1,29 @@ -/** - * ============================================================================ - * UNIFIED FONT STORE - * ============================================================================ - * - * Single source of truth for font operations across all providers. - * Combines fetching, filtering, caching, and managing fonts from Google Fonts and Fontshare. - * - * NOW INTEGRATED WITH TANSTACK QUERY via specific stores. - * - * OPTIMIZATIONS (P0/P1): - * - Debounced search (300ms) to reduce re-renders - * - Single-pass filter function for O(n) complexity - * - Two derived values: filteredFonts (filtered) + sortedFilteredFonts (final) - * - TanStack Query cancellation for stale requests - */ - -import { debounce } from '$shared/lib/utils'; import { - filterFonts, - sortFonts, -} from '../../lib/filterUtils'; -import { createFontshareStore } from '../services/fetchFontshareFonts.svelte'; -import { createGoogleFontsStore } from '../services/fetchGoogleFonts.svelte'; -import type { UnifiedFont } from '../types/normalize'; -import type { - FetchFontsParams, - FilterType, - FontFilters, - FontSort, -} from './types'; + type Filter, + type FilterModel, + createFilter, +} from '$shared/lib'; +import { SvelteMap } from 'svelte/reactivity'; +import type { FontProvider } from '../types'; +import type { CheckboxFilter } from '../types/common'; +import type { BaseFontStore } from './baseFontStore.svelte'; +import { createFontshareStore } from './fontshareStore.svelte'; +import type { ProviderParams } from './types'; -/** - * Creates a unified font store instance. - */ -export function createUnifiedFontStore() { - // Instantiate specific stores - const googleStore = createGoogleFontsStore(); - const fontshareStore = createFontshareStore(); +export class UnitedFontStore { + private sources: Partial>>; - // Internal state for local filters (that apply to the combined list) - let _filters = $state({ - providers: [], - categories: [], - subsets: [], - searchQuery: '', - }); + filters: SvelteMap; + queryValue = $state(''); - let _sort = $state({ field: 'name', direction: 'asc' }); - - // === Debounced Search === - // Debounce search query to avoid excessive filtering during typing - let _debouncedSearchQuery = $state(''); - - const setSearchQuery = debounce((query: string) => { - _debouncedSearchQuery = query; - }, 300); - - $effect(() => { - setSearchQuery(_filters.searchQuery); - }); - - // === Computed Values === - - /** - * Combined fonts from all active stores - */ - const allFonts = $derived.by(() => { - let fonts: UnifiedFont[] = []; - - // Google Fonts - if (_filters.providers.length === 0 || _filters.providers.includes('google')) { - fonts = [...fonts, ...googleStore.fonts]; - } - - // Fontshare Fonts - if (_filters.providers.length === 0 || _filters.providers.includes('fontshare')) { - fonts = [...fonts, ...fontshareStore.fonts]; - } - - return fonts; - }); - - /** - * Filtered fonts (before sort) - Derived Value #1 - * Uses optimized single-pass filter function - */ - const filteredFonts = $derived.by(() => { - const filtersForFiltering: FontFilters = { - providers: _filters.providers, - categories: _filters.categories, - subsets: _filters.subsets, - searchQuery: _debouncedSearchQuery, + constructor(initialConfig: Partial> = {}) { + this.sources = { + fontshare: createFontshareStore(initialConfig?.fontshare), }; - return filterFonts(allFonts, filtersForFiltering); - }); - - /** - * Sorted filtered fonts (final result) - Derived Value #2 - * Fast path: skip sorting for default name ascending order - */ - const sortedFilteredFonts = $derived.by(() => { - const filtered = filteredFonts; - - // Fast path: default sort (name ascending) - already sorted by filterFonts - if (_sort.field === 'name' && _sort.direction === 'asc') { - return filtered; - } - - return sortFonts(filtered, _sort); - }); - - // === Status Derivation === - - const isLoading = $derived(googleStore.isLoading || fontshareStore.isLoading); - const isFetching = $derived(googleStore.isFetching || fontshareStore.isFetching); - const error = $derived(googleStore.error?.message || fontshareStore.error?.message || null); - - // === Methods === - - /** - * Fetch fonts from all active providers using TanStack Query - * This now mostly just updates params or triggers refetch if needed. - * - * Includes cancellation of stale requests to improve performance. - */ - async function fetchFonts(params?: FetchFontsParams): Promise { - // Update local filters - if (params) { - if (params.providers) _filters.providers = params.providers; - if (params.categories) _filters.categories = params.categories; - if (params.subsets) _filters.subsets = params.subsets; - if (params.search !== undefined) _filters.searchQuery = params.search; - if (params.sort) _sort = params.sort; - } - - // Cancel existing queries before starting new ones (optimization) - googleStore.cancel(); - fontshareStore.cancel(); - - // Trigger fetches in underlying stores - // We pass the filter params down to the stores so they can optimize server-side fetching if supported - // For Google Fonts: - googleStore.setParams({ - category: _filters.categories.length === 1 ? _filters.categories[0] : undefined, - subset: _filters.subsets.length === 1 ? _filters.subsets[0] : undefined, - sort: (_sort.field === 'popularity' - ? 'popularity' - : _sort.field === 'date' - ? 'date' - : 'alpha') as any, - }); - - // For Fontshare: - // fontshareStore.setCategories(_filters.categories); // If supported + this.filters = new SvelteMap(); } - /** Update specific filter */ - function setFilter(type: FilterType, values: string[]): void { - if (type === 'searchQuery') { - _filters.searchQuery = values[0] ?? ''; - } else { - _filters = { - ..._filters, - [type]: values as any, // Type cast for loose array matching - }; - } + get fonts() { + return Object.values(this.sources).map(store => store.fonts).flat(); } - - /** Clear specific filter */ - function clearFilter(type: FilterType): void { - if (type === 'searchQuery') { - _filters.searchQuery = ''; - } else { - _filters = { - ..._filters, - [type]: [], - }; - } - } - - /** Clear all filters */ - function clearFilters(): void { - _filters = { - providers: [], - categories: [], - subsets: [], - searchQuery: '', - }; - } - - return { - // Getters - get fonts() { - return allFonts; - }, - get filteredFonts() { - return sortedFilteredFonts; - }, - get count() { - return allFonts.length; - }, - get isLoading() { - return isLoading; - }, - get isFetching() { - return isFetching; - }, - get error() { - return error; - }, - get filters() { - return _filters; - }, - get searchQuery() { - return _filters.searchQuery; - }, - get sort() { - return _sort; - }, - get providers() { - // Expose underlying stores for direct access if needed - return { - google: googleStore, - fontshare: fontshareStore, - }; - }, - - // Setters - set filters(value: FontFilters) { - _filters = value; - }, - set searchQuery(value: string) { - _filters.searchQuery = value; - }, - set sort(value: FontSort) { - _sort = value; - }, - - // Methods - fetchFonts, - setFilter, - clearFilter, - clearFilters, - - // Legacy support (no-op or adapted) - addFont: () => {}, // Not supported in reactive query model - addFonts: () => {}, - removeFont: () => {}, - getFontById: (id: string) => allFonts.find(f => f.id === id), - clearCache: () => { - googleStore.clearCache(); - fontshareStore.clearCache(); - }, - }; } - -export type UnifiedFontStore = ReturnType; - -/** - * Context key for dependency injection - */ -export const UNIFIED_FONT_STORE_KEY = Symbol('UNIFIED_FONT_STORE');