feature/fetch-fonts #14

Merged
ilia merged 76 commits from feature/fetch-fonts into main 2026-01-14 11:01:44 +00:00
2 changed files with 227 additions and 0 deletions
Showing only changes of commit ea1f46f780 - Show all commits

View File

@@ -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<FontCollectionState>,
) {
const cache = createCollectionCache<UnifiedFont>({
defaultTTL: 5 * 60 * 1000, // 5 minutes
maxSize: 1000,
});
let fonts = $state(initialState?.fonts ?? {});
let filters = $state<FontCollectionFilters>(initialState?.filters ?? { searchQuery: '' });
let sort = $state<FontCollectionSort>(initialState?.sort ?? DEFAULT_SORT);
// const state: Writable<FontCollectionState> = writable({
// ...defaultState,
// ...initialState,
// });
// const isLoading = writable(false);
let isLoading = $state<boolean>(false);
// const error = writable<string | undefined>();
let error = $state<string | undefined>();
// 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<typeof createFontCollection>;

View File

@@ -0,0 +1,3 @@
import { createFontCollection } from '../../lib';
export const fontCollection = createFontCollection();