feat(fonts): implement Phase 2 - Unified Font Store
- Implemented UnifiedFontStore extending BaseFontStore - Added pagination support with derived metadata - Added provider-specific shortcuts (setProvider, setCategory, etc.) - Added pagination methods (nextPage, prevPage, goToPage) - Added category getter shortcuts (sansSerifFonts, serifFonts, etc.) - Updated store exports to include unified store - Fixed typo in googleFontsStore.svelte.ts (createGoogleFontsStore) Phase 2/7: Proxy API Integration for GlyphDiff
This commit is contained in:
@@ -20,7 +20,7 @@ export class GoogleFontsStore extends BaseFontStore<GoogleFontsParams> {
|
||||
}
|
||||
}
|
||||
|
||||
export function createFontshareStore(params: GoogleFontsParams = {}) {
|
||||
export function createGoogleFontsStore(params: GoogleFontsParams = {}) {
|
||||
return new GoogleFontsStore(params);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,29 @@
|
||||
* Single export point for the unified font store infrastructure.
|
||||
*/
|
||||
|
||||
// export {
|
||||
// createUnifiedFontStore,
|
||||
// UNIFIED_FONT_STORE_KEY,
|
||||
// type UnifiedFontStore,
|
||||
// } from './unifiedFontStore.svelte';
|
||||
// Primary store (unified)
|
||||
export {
|
||||
createUnifiedFontStore,
|
||||
type UnifiedFontStore,
|
||||
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 {
|
||||
createFontshareStore,
|
||||
type FontshareStore,
|
||||
fontshareStore,
|
||||
} from './fontshareStore.svelte';
|
||||
|
||||
export { appliedFontsManager } from './appliedFontsStore/appliedFontsStore.svelte';
|
||||
|
||||
export { selectedFontsStore } from './selectedFontsStore/selectedFontsStore.svelte';
|
||||
// DEPRECATED: Google Fonts store (will be removed in Phase 6)
|
||||
export {
|
||||
createGoogleFontsStore,
|
||||
type GoogleFontsStore,
|
||||
googleFontsStore,
|
||||
} from './googleFontsStore.svelte';
|
||||
|
||||
@@ -1,25 +1,275 @@
|
||||
import { type Filter } 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';
|
||||
/**
|
||||
* Unified font store
|
||||
*
|
||||
* Single source of truth for font data, powered by the proxy API.
|
||||
* Extends BaseFontStore for TanStack Query integration and reactivity.
|
||||
*
|
||||
* 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 {
|
||||
private sources: Partial<Record<FontProvider, BaseFontStore<ProviderParams>>>;
|
||||
import type { ProxyFontsParams } from '../../api';
|
||||
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 = {
|
||||
fontshare: createFontshareStore(initialConfig?.fontshare),
|
||||
/**
|
||||
* Pagination metadata (derived from proxy API response)
|
||||
*/
|
||||
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