feature/comparison-slider #19
@@ -20,7 +20,7 @@ export class GoogleFontsStore extends BaseFontStore<GoogleFontsParams> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFontshareStore(params: GoogleFontsParams = {}) {
|
export function createGoogleFontsStore(params: GoogleFontsParams = {}) {
|
||||||
return new GoogleFontsStore(params);
|
return new GoogleFontsStore(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,18 +6,29 @@
|
|||||||
* Single export point for the unified font store infrastructure.
|
* Single export point for the unified font store infrastructure.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// export {
|
// Primary store (unified)
|
||||||
// createUnifiedFontStore,
|
export {
|
||||||
// UNIFIED_FONT_STORE_KEY,
|
createUnifiedFontStore,
|
||||||
// type UnifiedFontStore,
|
type UnifiedFontStore,
|
||||||
// } from './unifiedFontStore.svelte';
|
unifiedFontStore,
|
||||||
|
} from './unifiedFontStore.svelte';
|
||||||
|
|
||||||
|
// Applied fonts manager (CSS loading - unchanged)
|
||||||
|
export { appliedFontsManager } from './appliedFontsStore/appliedFontsStore.svelte';
|
||||||
|
|
||||||
|
// Selected fonts store (user selection - unchanged)
|
||||||
|
export { selectedFontsStore } from './selectedFontsStore/selectedFontsStore.svelte';
|
||||||
|
|
||||||
|
// DEPRECATED: Fontshare store (will be removed in Phase 6)
|
||||||
export {
|
export {
|
||||||
createFontshareStore,
|
createFontshareStore,
|
||||||
type FontshareStore,
|
type FontshareStore,
|
||||||
fontshareStore,
|
fontshareStore,
|
||||||
} from './fontshareStore.svelte';
|
} from './fontshareStore.svelte';
|
||||||
|
|
||||||
export { appliedFontsManager } from './appliedFontsStore/appliedFontsStore.svelte';
|
// DEPRECATED: Google Fonts store (will be removed in Phase 6)
|
||||||
|
export {
|
||||||
export { selectedFontsStore } from './selectedFontsStore/selectedFontsStore.svelte';
|
createGoogleFontsStore,
|
||||||
|
type GoogleFontsStore,
|
||||||
|
googleFontsStore,
|
||||||
|
} from './googleFontsStore.svelte';
|
||||||
|
|||||||
@@ -1,25 +1,275 @@
|
|||||||
import { type Filter } from '$shared/lib';
|
/**
|
||||||
import { SvelteMap } from 'svelte/reactivity';
|
* Unified font store
|
||||||
import type { FontProvider } from '../types';
|
*
|
||||||
import type { CheckboxFilter } from '../types/common';
|
* Single source of truth for font data, powered by the proxy API.
|
||||||
import type { BaseFontStore } from './baseFontStore.svelte';
|
* Extends BaseFontStore for TanStack Query integration and reactivity.
|
||||||
import { createFontshareStore } from './fontshareStore.svelte';
|
*
|
||||||
import type { ProviderParams } from './types';
|
* Key features:
|
||||||
|
* - Provider-agnostic (proxy API handles provider logic)
|
||||||
|
* - Reactive to filter changes
|
||||||
|
* - Optimistic updates via TanStack Query
|
||||||
|
* - Pagination support
|
||||||
|
* - Provider-specific shortcuts for common operations
|
||||||
|
*/
|
||||||
|
|
||||||
export class UnitedFontStore {
|
import type { ProxyFontsParams } from '../../api';
|
||||||
private sources: Partial<Record<FontProvider, BaseFontStore<ProviderParams>>>;
|
import { fetchProxyFonts } from '../../api';
|
||||||
|
import type { UnifiedFont } from '../types';
|
||||||
|
import type { FontCategory } from '../types';
|
||||||
|
import { BaseFontStore } from './baseFontStore.svelte';
|
||||||
|
|
||||||
filters: SvelteMap<CheckboxFilter, Filter>;
|
/**
|
||||||
queryValue = $state('');
|
* Unified font store wrapping TanStack Query with Svelte 5 runes
|
||||||
|
*
|
||||||
|
* Extends BaseFontStore to provide:
|
||||||
|
* - Reactive state management
|
||||||
|
* - TanStack Query integration for caching
|
||||||
|
* - Dynamic parameter binding for filters
|
||||||
|
* - Pagination support
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const store = new UnifiedFontStore({
|
||||||
|
* provider: 'google',
|
||||||
|
* category: 'sans-serif',
|
||||||
|
* limit: 50
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Access reactive state
|
||||||
|
* $effect(() => {
|
||||||
|
* console.log(store.fonts);
|
||||||
|
* console.log(store.isLoading);
|
||||||
|
* console.log(store.pagination);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Update parameters
|
||||||
|
* store.setCategory('serif');
|
||||||
|
* store.nextPage();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||||
|
/**
|
||||||
|
* Store pagination metadata separately from fonts
|
||||||
|
* This is a workaround for TanStack Query's type system
|
||||||
|
*/
|
||||||
|
#paginationMetadata = $state<
|
||||||
|
{
|
||||||
|
total: number;
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
} | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
constructor(initialConfig: Partial<Record<FontProvider, ProviderParams>> = {}) {
|
/**
|
||||||
this.sources = {
|
* Pagination metadata (derived from proxy API response)
|
||||||
fontshare: createFontshareStore(initialConfig?.fontshare),
|
*/
|
||||||
|
readonly pagination = $derived.by(() => {
|
||||||
|
if (this.#paginationMetadata) {
|
||||||
|
const { total, limit, offset } = this.#paginationMetadata;
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
hasMore: offset + limit < total,
|
||||||
|
page: Math.floor(offset / limit) + 1,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
limit: this.params.limit || 50,
|
||||||
|
offset: this.params.offset || 0,
|
||||||
|
hasMore: false,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 0,
|
||||||
};
|
};
|
||||||
this.filters = new SvelteMap();
|
});
|
||||||
|
|
||||||
|
constructor(initialParams: ProxyFontsParams = {}) {
|
||||||
|
super(initialParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
get fonts() {
|
/**
|
||||||
return Object.values(this.sources).map(store => store.fonts).flat();
|
* Query key for TanStack Query caching
|
||||||
|
* Normalizes params to treat empty arrays/strings as undefined
|
||||||
|
*/
|
||||||
|
protected getQueryKey(params: ProxyFontsParams) {
|
||||||
|
// Normalize params to treat empty arrays/strings as undefined
|
||||||
|
const normalized = Object.entries(params).reduce((acc, [key, value]) => {
|
||||||
|
if (value === '' || (Array.isArray(value) && value.length === 0)) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
return { ...acc, [key]: value };
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return ['unifiedFonts', normalized] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch function that calls the proxy API
|
||||||
|
* Returns the full response including pagination metadata
|
||||||
|
*/
|
||||||
|
protected async fetchFn(params: ProxyFontsParams): Promise<UnifiedFont[]> {
|
||||||
|
const response = await fetchProxyFonts(params);
|
||||||
|
|
||||||
|
// Store pagination metadata separately for derived values
|
||||||
|
this.#paginationMetadata = {
|
||||||
|
total: response.total,
|
||||||
|
limit: response.limit,
|
||||||
|
offset: response.offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
return response.fonts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Getters (proxied from BaseFontStore) ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all fonts from current query result
|
||||||
|
*/
|
||||||
|
get fonts(): UnifiedFont[] {
|
||||||
|
// The result.data is UnifiedFont[] (from TanStack Query)
|
||||||
|
return (this.result.data as UnifiedFont[] | undefined) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if loading initial data
|
||||||
|
*/
|
||||||
|
get isLoading(): boolean {
|
||||||
|
return this.result.isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if fetching (including background refetches)
|
||||||
|
*/
|
||||||
|
get isFetching(): boolean {
|
||||||
|
return this.result.isFetching;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if error occurred
|
||||||
|
*/
|
||||||
|
get isError(): boolean {
|
||||||
|
return this.result.isError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if result is empty (not loading and no fonts)
|
||||||
|
*/
|
||||||
|
get isEmpty(): boolean {
|
||||||
|
return !this.isLoading && this.fonts.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Provider-specific shortcuts ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set provider filter
|
||||||
|
*/
|
||||||
|
setProvider(provider: 'google' | 'fontshare' | undefined) {
|
||||||
|
this.setParams({ provider });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set category filter
|
||||||
|
*/
|
||||||
|
setCategory(category: ProxyFontsParams['category']) {
|
||||||
|
this.setParams({ category });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set subset filter
|
||||||
|
*/
|
||||||
|
setSubset(subset: ProxyFontsParams['subset']) {
|
||||||
|
this.setParams({ subset });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set search query
|
||||||
|
*/
|
||||||
|
setSearch(search: string) {
|
||||||
|
this.setParams({ q: search || undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sort order
|
||||||
|
*/
|
||||||
|
setSort(sort: ProxyFontsParams['sort']) {
|
||||||
|
this.setParams({ sort });
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Pagination methods ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to next page
|
||||||
|
*/
|
||||||
|
nextPage() {
|
||||||
|
if (this.pagination.hasMore) {
|
||||||
|
this.setParams({
|
||||||
|
offset: this.pagination.offset + this.pagination.limit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to previous page
|
||||||
|
*/
|
||||||
|
prevPage() {
|
||||||
|
if (this.pagination.page > 1) {
|
||||||
|
this.setParams({
|
||||||
|
offset: this.pagination.offset - this.pagination.limit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to specific page
|
||||||
|
*/
|
||||||
|
goToPage(page: number) {
|
||||||
|
if (page >= 1 && page <= this.pagination.totalPages) {
|
||||||
|
this.setParams({
|
||||||
|
offset: (page - 1) * this.pagination.limit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set limit (items per page)
|
||||||
|
*/
|
||||||
|
setLimit(limit: number) {
|
||||||
|
this.setParams({ limit });
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Category shortcuts (for convenience) ---
|
||||||
|
|
||||||
|
get sansSerifFonts() {
|
||||||
|
return this.fonts.filter(f => f.category === 'sans-serif');
|
||||||
|
}
|
||||||
|
|
||||||
|
get serifFonts() {
|
||||||
|
return this.fonts.filter(f => f.category === 'serif');
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayFonts() {
|
||||||
|
return this.fonts.filter(f => f.category === 'display');
|
||||||
|
}
|
||||||
|
|
||||||
|
get handwritingFonts() {
|
||||||
|
return this.fonts.filter(f => f.category === 'handwriting');
|
||||||
|
}
|
||||||
|
|
||||||
|
get monospaceFonts() {
|
||||||
|
return this.fonts.filter(f => f.category === 'monospace');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function to create unified font store
|
||||||
|
*/
|
||||||
|
export function createUnifiedFontStore(params: ProxyFontsParams = {}) {
|
||||||
|
return new UnifiedFontStore(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance for global use
|
||||||
|
*/
|
||||||
|
export const unifiedFontStore = new UnifiedFontStore();
|
||||||
|
|||||||
Reference in New Issue
Block a user