chore: rename FetchFonts to GetFonts

This commit is contained in:
Ilia Mashkov
2026-01-13 19:59:07 +03:00
parent d9d45bf9fb
commit 99d4b4e29a
16 changed files with 140 additions and 537 deletions

View File

@@ -0,0 +1,31 @@
import {
type FontshareParams,
fetchFontshareFonts,
} from '../../api';
import { normalizeFontshareFonts } from '../../lib';
import type { UnifiedFont } from '../types';
/**
* Query function for fetching fonts from Fontshare.
*
* @param params - The parameters for fetching fonts from Fontshare (E.g. search query, page number, etc.).
* @returns A promise that resolves with an array of UnifiedFont objects representing the fonts found in Fontshare.
*/
export async function fetchFontshareFontsQuery(params: FontshareParams): Promise<UnifiedFont[]> {
try {
const response = await fetchFontshareFonts(params);
return normalizeFontshareFonts(response.fonts);
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('Failed to fetch')) {
throw new Error(
'Unable to connect to Fontshare. Please check your internet connection.',
);
}
if (error.message.includes('404')) {
throw new Error('Font not found in Fontshare catalog.');
}
}
throw new Error('Failed to load fonts from Fontshare.');
}
}

View File

@@ -1,30 +1,21 @@
/**
* Service for fetching Google Fonts with Svelte 5 runes + TanStack Query
*/
import { fetchGoogleFonts } from '$entities/Font';
import { normalizeGoogleFonts } from '$entities/Font';
import type {
FontCategory,
FontSubset,
GoogleFontsParams,
} from '$entities/Font';
import type { UnifiedFont } from '$entities/Font';
import {
type CreateQueryResult,
createQuery,
useQueryClient,
} from '@tanstack/svelte-query';
/**
* Google Fonts query parameters
*/
// export interface GoogleFontsParams {
// category?: FontCategory;
// subset?: FontSubset;
// sort?: 'popularity' | 'alpha' | 'date';
// search?: string;
// forceRefetch?: boolean;
// }
import {
type GoogleFontsParams,
fetchGoogleFonts,
} from '../../api';
import { normalizeGoogleFonts } from '../../lib';
import type {
FontCategory,
FontSubset,
} from '../types';
import type { UnifiedFont } from '../types/normalize';
/**
* Query key factory
@@ -36,7 +27,7 @@ function getGoogleFontsQueryKey(params: GoogleFontsParams) {
/**
* Query function
*/
async function fetchGoogleFontsQuery(params: GoogleFontsParams): Promise<UnifiedFont[]> {
export async function fetchGoogleFontsQuery(params: GoogleFontsParams): Promise<UnifiedFont[]> {
try {
const response = await fetchGoogleFonts({
category: params.category,
@@ -281,109 +272,3 @@ export class GoogleFontsStore {
export function createGoogleFontsStore(params: GoogleFontsParams = {}) {
return new GoogleFontsStore(params);
}
/**
* Manager for multiple Google Fonts stores
*/
// export class GoogleFontsManager {
// stores = $state<Map<string, GoogleFontsStore>>(new Map());
// private queryClient = useQueryClient();
// /**
// * Get or create a store with specific parameters
// */
// getStore(params: GoogleFontsParams = {}): GoogleFontsStore {
// const key = JSON.stringify(params);
// if (!this.stores.has(key)) {
// this.stores.set(key, new GoogleFontsStore(params));
// }
// return this.stores.get(key)!;
// }
// /**
// * Get store by category (convenience method)
// */
// getStoreByCategory(category: FontCategory): GoogleFontsStore {
// return this.getStore({ category });
// }
// /**
// * Invalidate ALL Google Fonts queries across all stores
// */
// invalidateAll() {
// this.queryClient.invalidateQueries({
// queryKey: ['googleFonts'],
// });
// }
// /**
// * Prefetch fonts in background
// */
// async prefetch(params: GoogleFontsParams) {
// await this.queryClient.prefetchQuery({
// queryKey: getGoogleFontsQueryKey(params),
// queryFn: () => fetchGoogleFontsQuery(params),
// staleTime: 5 * 60 * 1000,
// });
// }
// /**
// * Prefetch all categories (useful on app init)
// */
// async prefetchAllCategories() {
// const categories: FontCategory[] = ['sans-serif', 'serif', 'display', 'handwriting', 'monospace'];
// await Promise.all(
// categories.map(category => this.prefetch({ category }))
// );
// }
// /**
// * Cancel all Google Fonts queries
// */
// cancelAll() {
// this.queryClient.cancelQueries({
// queryKey: ['googleFonts'],
// });
// }
// /**
// * Clear all Google Fonts cache
// */
// clearAllCache() {
// this.queryClient.removeQueries({
// queryKey: ['googleFonts'],
// });
// }
// /**
// * Get total fonts count across all stores
// */
// get totalFontsCount() {
// return Array.from(this.stores.values()).reduce(
// (sum, store) => sum + store.fontCount,
// 0
// );
// }
// /**
// * Check if any store is loading
// */
// get isAnyLoading() {
// return Array.from(this.stores.values()).some(store => store.isLoading);
// }
// /**
// * Get all errors from all stores
// */
// get allErrors() {
// return Array.from(this.stores.values())
// .map(store => store.error)
// .filter((error): error is Error => error !== null);
// }
// }
// export function createGoogleFontsManager() {
// return new GoogleFontsManager();
// }

View File

@@ -1,11 +0,0 @@
/**
* Fetch fonts feature exports
*
* Exports service functions for fetching fonts from Google Fonts and Fontshare
*/
export { createGoogleFontsStore } from './model/services/fetchGoogleFonts';
export { createFontshareStore } from './model/services/fetchFontshareFonts';
export { FontSearch } from './ui';

View File

@@ -1,269 +0,0 @@
import {
type FontshareParams,
type UnifiedFont,
fetchFontshareFonts,
normalizeFontshareFonts,
} from '$entities/Font';
import {
type CreateQueryResult,
createQuery,
useQueryClient,
} from '@tanstack/svelte-query';
/**
* Query key factory
*/
function getFontshareQueryKey(params: FontshareParams) {
return ['fontshare', params] as const;
}
/**
* Query function
*/
async function fetchFontshareFontsQuery(params: FontshareParams): Promise<UnifiedFont[]> {
try {
const response = await fetchFontshareFonts(params);
return normalizeFontshareFonts(response.items);
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('Failed to fetch')) {
throw new Error(
'Unable to connect to Fontshare. Please check your internet connection.',
);
}
if (error.message.includes('404')) {
throw new Error('Font not found in Fontshare catalog.');
}
}
throw new Error('Failed to load fonts from Fontshare.');
}
}
/**
* Fontshare store wrapping TanStack Query with runes
*/
export class FontshareStore {
params = $state<FontshareParams>({});
private query: CreateQueryResult<UnifiedFont[], Error>;
private queryClient = useQueryClient();
constructor(initialParams: FontshareParams = {}) {
this.params = initialParams;
// Create the query - it's already reactive
this.query = createQuery(() => ({
queryKey: getFontshareQueryKey(this.params),
queryFn: () => fetchFontshareFontsQuery(this.params),
staleTime: 5 * 60 * 1000,
gcTime: 10 * 60 * 1000,
}));
}
// Proxy TanStack Query's reactive state
get fonts() {
return this.query.data ?? [];
}
get isLoading() {
return this.query.isLoading;
}
get isFetching() {
return this.query.isFetching;
}
get error() {
return this.query.error;
}
get isError() {
return this.query.isError;
}
get isSuccess() {
return this.query.isSuccess;
}
// Derived helpers
get hasData() {
return this.fonts.length > 0;
}
get isEmpty() {
return !this.isLoading && this.fonts.length === 0;
}
/**
* Update parameters - TanStack Query will automatically refetch
*/
setParams(newParams: Partial<FontshareParams>) {
this.params = { ...this.params, ...newParams };
}
setCategories(categories: string[]) {
this.setParams({ categories });
}
setTags(tags: string[]) {
this.setParams({ tags });
}
setSearch(search: string) {
this.setParams({ search });
}
setPage(page: number) {
this.setParams({ page });
}
setLimit(limit: number) {
this.setParams({ limit });
}
/**
* Manually refetch
*/
async refetch() {
await this.query.refetch();
}
/**
* Invalidate cache and refetch
*/
invalidate() {
this.queryClient.invalidateQueries({
queryKey: getFontshareQueryKey(this.params),
});
}
/**
* Invalidate all Fontshare queries
*/
invalidateAll() {
this.queryClient.invalidateQueries({
queryKey: ['fontshare'],
});
}
/**
* Prefetch with different params (for hover states, pagination, etc.)
*/
async prefetch(params: FontshareParams) {
await this.queryClient.prefetchQuery({
queryKey: getFontshareQueryKey(params),
queryFn: () => fetchFontshareFontsQuery(params),
staleTime: 5 * 60 * 1000,
});
}
/**
* Cancel ongoing queries
*/
cancel() {
this.queryClient.cancelQueries({
queryKey: getFontshareQueryKey(this.params),
});
}
/**
* Clear cache for current params
*/
clearCache() {
this.queryClient.removeQueries({
queryKey: getFontshareQueryKey(this.params),
});
}
/**
* Get cached data without triggering fetch
*/
getCachedData() {
return this.queryClient.getQueryData<UnifiedFont[]>(
getFontshareQueryKey(this.params),
);
}
/**
* Set data manually (optimistic updates)
*/
setQueryData(updater: (old: UnifiedFont[] | undefined) => UnifiedFont[]) {
this.queryClient.setQueryData(
getFontshareQueryKey(this.params),
updater,
);
}
}
export function createFontshareStore(params: FontshareParams = {}) {
return new FontshareStore(params);
}
/**
* Manager for multiple Fontshare stores
*/
// export class FontshareManager {
// stores = $state<Map<string, FontshareStore>>(new Map());
// private queryClient = useQueryClient();
// getStore(params: FontshareParams = {}): FontshareStore {
// const key = JSON.stringify(params);
// if (!this.stores.has(key)) {
// this.stores.set(key, new FontshareStore(params));
// }
// return this.stores.get(key)!;
// }
// /**
// * Invalidate ALL Fontshare queries across all stores
// */
// invalidateAll() {
// this.queryClient.invalidateQueries({
// queryKey: ['fontshare'],
// });
// }
// /**
// * Prefetch fonts in background
// */
// async prefetch(params: FontshareParams) {
// await this.queryClient.prefetchQuery({
// queryKey: getFontshareQueryKey(params),
// queryFn: () => fetchFontshareFontsQuery(params),
// staleTime: 5 * 60 * 1000,
// });
// }
// /**
// * Cancel all Fontshare queries
// */
// cancelAll() {
// this.queryClient.cancelQueries({
// queryKey: ['fontshare'],
// });
// }
// /**
// * Clear all Fontshare cache
// */
// clearAllCache() {
// this.queryClient.removeQueries({
// queryKey: ['fontshare'],
// });
// }
// /**
// * Get query state for debugging
// */
// getQueryState(params: FontshareParams) {
// return this.queryClient.getQueryState(
// getFontshareQueryKey(params),
// );
// }
// }
// export function createFontshareManager() {
// return new FontshareManager();
// }
//

View File

@@ -1,76 +0,0 @@
/**
* Fetch Fonts feature types
*
* Type definitions for font fetching feature
*/
import type {
FontCategory,
FontProvider,
FontSubset,
} from '$entities/Font/model/types';
import type { UnifiedFont } from '$entities/Font/model/types/normalize';
/**
* Combined query parameters for fetching from any provider
*/
export interface FetchFontsParams {
/** Font provider to fetch from */
provider?: FontProvider;
/** Category filter */
category?: FontCategory;
/** Subset filter */
subset?: FontSubset;
/** Search query */
search?: string;
/** Page number (for Fontshare) */
page?: number;
/** Limit (for Fontshare) */
limit?: number;
/** Force refetch even if cached */
forceRefetch?: boolean;
}
/**
* Font fetching result
*/
export interface FetchFontsResult {
/** Fetched fonts */
fonts: UnifiedFont[];
/** Total count (for pagination) */
total?: number;
/** Whether more fonts are available */
hasMore?: boolean;
/** Page number (for pagination) */
page?: number;
}
/**
* Font fetching error
*/
export interface FetchFontsError {
/** Error message */
message: string;
/** Provider that failed */
provider: FontProvider | 'all';
/** HTTP status code (if applicable) */
status?: number;
/** Original error */
originalError?: unknown;
}
/**
* Font fetching state
*/
export interface FetchFontsState {
/** Currently fetching */
isFetching: boolean;
/** Currently loading initial data */
isLoading: boolean;
/** Error state */
error: FetchFontsError | null;
/** Cached fonts */
fonts: UnifiedFont[];
/** Last fetch timestamp */
lastFetchedAt: number | null;
}

View File

@@ -1,17 +0,0 @@
<script lang="ts">
import { fontCollection } from '$entities/Font';
import FontList from '$entities/Font/ui/FontList/FontList.svelte';
import SearchBar from '$shared/ui/SearchBar/SearchBar.svelte';
interface Props {
id?: string;
}
const {
id = 'font-search',
}: Props = $props();
</script>
<SearchBar class="w-full" bind:value={fontCollection.searchQuery} id={id}>
<FontList />
</SearchBar>

View File

@@ -1,3 +0,0 @@
import FontSearch from './FontSearch/FontSearch.svelte';
export { FontSearch };

View File

@@ -1,7 +0,0 @@
import type { Property } from '$shared/lib';
export interface FilterGroupConfig<TValue extends string> {
id: string;
label: string;
properties: Property<TValue>[];
}

View File

@@ -1,26 +0,0 @@
import { createFilterManager } from '../../lib/filterManager/filterManager.svelte';
import {
FONT_CATEGORIES,
FONT_PROVIDERS,
FONT_SUBSETS,
} from '../const/const';
const filtersData = [
{
id: 'providers',
label: 'Font provider',
properties: FONT_PROVIDERS,
},
{
id: 'subsets',
label: 'Font subset',
properties: FONT_SUBSETS,
},
{
id: 'categories',
label: 'Font category',
properties: FONT_CATEGORIES,
},
];
export const filterManager = createFilterManager(filtersData);

View File

@@ -1,11 +1,19 @@
export {
createFilterManager,
type FilterManager,
} from './lib/filterManager/filterManager.svelte';
mapManagerToParams,
} from './lib';
export {
FONT_CATEGORIES,
FONT_PROVIDERS,
FONT_SUBSETS,
} from './model/const/const';
export type { FilterGroupConfig } from './model/const/types/common';
export { filterManager } from './model/state/manager.svelte';
export {
FilterControls,
Filters,
FontSearch,
} from './ui';

View File

@@ -0,0 +1,6 @@
export {
createFilterManager,
type FilterManager,
} from './filterManager/filterManager.svelte';
export { mapManagerToParams } from './mapper/mapManagerToParams';

View File

@@ -0,0 +1,6 @@
export type {
FilterConfig,
FilterGroupConfig,
} from './types/filter';
export { filterManager } from './state/manager.svelte';

View File

@@ -0,0 +1,29 @@
import { createFilterManager } from '../../lib/filterManager/filterManager.svelte';
import {
FONT_CATEGORIES,
FONT_PROVIDERS,
FONT_SUBSETS,
} from '../const/const';
const initialConfig = {
queryValue: '',
groups: [
{
id: 'providers',
label: 'Font provider',
properties: FONT_PROVIDERS,
},
{
id: 'subsets',
label: 'Font subset',
properties: FONT_SUBSETS,
},
{
id: 'categories',
label: 'Font category',
properties: FONT_CATEGORIES,
},
],
};
export const filterManager = createFilterManager(initialConfig);

View File

@@ -0,0 +1,38 @@
<script lang="ts">
import {
FontList,
fontshareStore,
} from '$entities/Font';
import { SearchBar } from '$shared/ui';
import { onMount } from 'svelte';
import { mapManagerToParams } from '../../lib';
import { filterManager } from '../../model';
/**
* FontSearch
*
* Font search component with search input and font list display.
* Uses unifiedFontStore for all font operations and search state.
*/
onMount(() => {
/**
* The Pairing:
* We "plug" this manager into the global store.
* addBinding returns a function that removes this binding when the component unmounts.
*/
const unbind = fontshareStore.addBinding(() => mapManagerToParams(filterManager));
return unbind;
});
$inspect(filterManager.queryValue, filterManager.debouncedQueryValue);
</script>
<SearchBar
id="font-search"
class="w-full"
placeholder="Search fonts by name..."
bind:value={filterManager.queryValue}
>
<FontList />
</SearchBar>

View File

@@ -0,0 +1,9 @@
import Filters from './Filters/Filters.svelte';
import FilterControls from './FiltersControl/FilterControls.svelte';
import FontSearch from './FontSearch/FontSearch.svelte';
export {
FilterControls,
Filters,
FontSearch,
};