diff --git a/src/entities/Font/lib/helpers/createFontCollection.svelte.ts b/src/entities/Font/lib/helpers/createFontCollection.svelte.ts new file mode 100644 index 0000000..3af4769 --- /dev/null +++ b/src/entities/Font/lib/helpers/createFontCollection.svelte.ts @@ -0,0 +1,224 @@ +/** + * Font collection store + * + * Main font collection cache using Svelte stores. + * Integrates with TanStack Query for advanced caching and deduplication. + * + * Provides derived stores for filtered/sorted fonts. + */ + +import type { + FontCollectionFilters, + FontCollectionSort, + FontCollectionState, + UnifiedFont, +} from '$entities/Font/model/types'; +import { createCollectionCache } from '$shared/lib/fetch/collectionCache'; + +export const DEFAULT_SORT: FontCollectionSort = { field: 'name', direction: 'asc' }; + +/** + * Create font collection store + * + * @param initialState - Initial state for collection + * @returns Font collection store instance + * + * @example + * ```ts + * const fontCollection = createFontCollectionStore({ + * fonts: {}, + * filters: {}, + * sort: { field: 'name', direction: 'asc' } + * }); + * + * // Add fonts to collection + * fontCollection.addFonts([font1, font2]); + * + * // Use in component + * $fontCollection.filteredFonts + * ``` + */ +export function createFontCollection( + initialState?: Partial, +) { + const cache = createCollectionCache({ + defaultTTL: 5 * 60 * 1000, // 5 minutes + maxSize: 1000, + }); + + let fonts = $state(initialState?.fonts ?? {}); + let filters = $state(initialState?.filters ?? { searchQuery: '' }); + let sort = $state(initialState?.sort ?? DEFAULT_SORT); + + // const state: Writable = writable({ + // ...defaultState, + // ...initialState, + // }); + + // const isLoading = writable(false); + let isLoading = $state(false); + // const error = writable(); + let error = $state(); + + // Derived store for fonts as array + // const fonts = derived(state, $state => { + // return Object.values($state.fonts); + // }); + // + + const filtrationArray = $derived([ + (font: UnifiedFont) => filters.providers?.includes(font.provider), + (font: UnifiedFont) => filters.categories?.includes(font.category), + (font: UnifiedFont) => filters.subsets?.some(subset => font.subsets.includes(subset)), + (font: UnifiedFont) => + filters.searchQuery + ? font.name.toLowerCase().includes(filters.searchQuery.toLowerCase()) + : true, + ]); + + const filteredFonts = $derived( + Object.values(fonts).filter(font => { + return filtrationArray.every(filter => filter(font)); + }), + ); + + // Derived store for filtered fonts + // const filteredFonts = derived([state, fonts], ([$state, $fonts]) => { + // let filtered = [...$fonts]; + + // // Apply search filter + // if ($state.filters.searchQuery) { + // const query = $state.filters.searchQuery.toLowerCase(); + // filtered = filtered.filter(font => font.name.toLowerCase().includes(query)); + // } + + // // Apply provider filter + // if ($state.filters.provider) { + // filtered = filtered.filter( + // font => font.provider === $state.filters.provider, + // ); + // } + + // // Apply category filter + // if ($state.filters.category) { + // filtered = filtered.filter( + // font => font.category === $state.filters.category, + // ); + // } + + // // Apply subset filter + // if ($state.filters.subsets?.length) { + // filtered = filtered.filter(font => + // $state.filters.subsets!.some(subset => font.subsets.includes(subset as FontSubset)) + // ); + // } + + // // Apply sort + // const { field, direction } = $state.sort; + // const multiplier = direction === 'asc' ? 1 : -1; + + // filtered.sort((a, b) => { + // let comparison = 0; + + // if (field === 'name') { + // comparison = a.name.localeCompare(b.name); + // } else if (field === 'popularity') { + // const aPop = a.metadata.popularity ?? 0; + // const bPop = b.metadata.popularity ?? 0; + // comparison = aPop - bPop; + // } else if (field === 'category') { + // comparison = a.category.localeCompare(b.category); + // } + + // return comparison * multiplier; + // }); + + // return filtered; + // }); + + // Derived store for count + // const count = derived(fonts, $fonts => $fonts.length); + + const count = $derived(fonts.size); + + return { + get fonts() { + return fonts; + }, + set fonts(newFonts) { + fonts = newFonts; + }, + get filteredFonts() { + return filteredFonts; + }, + get filters() { + return filters; + }, + set filters(newFilters) { + filters = newFilters; + }, + get searchQuery() { + return filters.searchQuery; + }, + set searchQuery(newSearchQuery) { + filters = { + ...filters, + searchQuery: newSearchQuery, + }; + }, + get sort() { + return sort; + }, + set sort(newSort) { + sort = newSort; + }, + get count() { + return count; + }, + get isLoading() { + return isLoading; + }, + set isLoading(value) { + isLoading = value; + }, + get error() { + return error; + }, + set error(value) { + error = value; + }, + addFonts(newFonts: UnifiedFont[]) { + fonts = newFonts.reduce((acc, font) => { + cache.set(font.id, font); + return { ...acc, [font.id]: font }; + }, fonts); + }, + addFont(font: UnifiedFont) { + cache.set(font.id, font); + fonts = Object.fromEntries(Object.entries(fonts).concat([[font.id, font]])); + }, + removeFont(fontId: string) { + cache.remove(fontId); + fonts = Object.fromEntries(Object.entries(fonts).filter(([id]) => id !== fontId)); + }, + clearFonts() { + fonts = {}; + cache.clear(); + }, + clearFilters() { + filters = { + searchQuery: '', + providers: [], + categories: [], + }; + }, + clearSort() { + sort = DEFAULT_SORT; + }, + getFontById(fontId: string) { + return fonts[fontId]; + }, + }; +} + +export type FontCollectionStore = ReturnType; diff --git a/src/entities/Font/model/store/fontCollection.svelte.ts b/src/entities/Font/model/store/fontCollection.svelte.ts new file mode 100644 index 0000000..026f021 --- /dev/null +++ b/src/entities/Font/model/store/fontCollection.svelte.ts @@ -0,0 +1,3 @@ +import { createFontCollection } from '../../lib'; + +export const fontCollection = createFontCollection();