feature/project-redesign #28
@@ -7,8 +7,6 @@
|
|||||||
* Proxy API normalizes font data from Google Fonts and Fontshare into a single
|
* Proxy API normalizes font data from Google Fonts and Fontshare into a single
|
||||||
* unified format, eliminating the need for client-side normalization.
|
* unified format, eliminating the need for client-side normalization.
|
||||||
*
|
*
|
||||||
* Fallback: If proxy API fails, falls back to Fontshare API for development.
|
|
||||||
*
|
|
||||||
* @see https://api.glyphdiff.com/api/v1/fonts
|
* @see https://api.glyphdiff.com/api/v1/fonts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -26,40 +24,37 @@ import type {
|
|||||||
*/
|
*/
|
||||||
const PROXY_API_URL = 'https://api.glyphdiff.com/api/v1/fonts' as const;
|
const PROXY_API_URL = 'https://api.glyphdiff.com/api/v1/fonts' as const;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to use proxy API (true) or fallback (false)
|
|
||||||
*
|
|
||||||
* Set to true when your proxy API is ready:
|
|
||||||
* const USE_PROXY_API = true;
|
|
||||||
*
|
|
||||||
* Set to false to use Fontshare API as fallback during development:
|
|
||||||
* const USE_PROXY_API = false;
|
|
||||||
*
|
|
||||||
* The app will automatically fall back to Fontshare API if the proxy fails.
|
|
||||||
*/
|
|
||||||
const USE_PROXY_API = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proxy API parameters
|
* Proxy API parameters
|
||||||
*
|
*
|
||||||
* Maps directly to the proxy API query parameters
|
* Maps directly to the proxy API query parameters
|
||||||
|
*
|
||||||
|
* UPDATED: Now supports array values for filters
|
||||||
*/
|
*/
|
||||||
export interface ProxyFontsParams extends QueryParams {
|
export interface ProxyFontsParams extends QueryParams {
|
||||||
/**
|
/**
|
||||||
* Font provider filter ("google" or "fontshare")
|
* Font provider filter
|
||||||
* Omit to fetch from both providers
|
*
|
||||||
|
* NEW: Supports array of providers (e.g., ["google", "fontshare"])
|
||||||
|
* Backward compatible: Single value still works
|
||||||
*/
|
*/
|
||||||
provider?: 'google' | 'fontshare';
|
providers?: string[] | string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Font category filter
|
* Font category filter
|
||||||
|
*
|
||||||
|
* NEW: Supports array of categories (e.g., ["serif", "sans-serif"])
|
||||||
|
* Backward compatible: Single value still works
|
||||||
*/
|
*/
|
||||||
category?: FontCategory;
|
categories?: string[] | string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Character subset filter
|
* Character subset filter
|
||||||
|
*
|
||||||
|
* NEW: Supports array of subsets (e.g., ["latin", "cyrillic"])
|
||||||
|
* Backward compatible: Single value still works
|
||||||
*/
|
*/
|
||||||
subset?: FontSubset;
|
subsets?: string[] | string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search query (e.g., "roboto", "satoshi")
|
* Search query (e.g., "roboto", "satoshi")
|
||||||
@@ -108,8 +103,6 @@ export interface ProxyFontsResponse {
|
|||||||
/**
|
/**
|
||||||
* Fetch fonts from proxy API
|
* Fetch fonts from proxy API
|
||||||
*
|
*
|
||||||
* If proxy API fails or is unavailable, falls back to Fontshare API for development.
|
|
||||||
*
|
|
||||||
* @param params - Query parameters for filtering and pagination
|
* @param params - Query parameters for filtering and pagination
|
||||||
* @returns Promise resolving to proxy API response
|
* @returns Promise resolving to proxy API response
|
||||||
* @throws ApiError when request fails
|
* @throws ApiError when request fails
|
||||||
@@ -138,84 +131,16 @@ export interface ProxyFontsResponse {
|
|||||||
export async function fetchProxyFonts(
|
export async function fetchProxyFonts(
|
||||||
params: ProxyFontsParams = {},
|
params: ProxyFontsParams = {},
|
||||||
): Promise<ProxyFontsResponse> {
|
): Promise<ProxyFontsResponse> {
|
||||||
// Try proxy API first if enabled
|
const queryString = buildQueryString(params);
|
||||||
if (USE_PROXY_API) {
|
const url = `${PROXY_API_URL}${queryString}`;
|
||||||
try {
|
|
||||||
const queryString = buildQueryString(params);
|
|
||||||
const url = `${PROXY_API_URL}${queryString}`;
|
|
||||||
|
|
||||||
console.log('[fetchProxyFonts] Fetching from proxy API', { params, url });
|
const response = await api.get<ProxyFontsResponse>(url);
|
||||||
|
|
||||||
const response = await api.get<ProxyFontsResponse>(url);
|
if (!response.data || !Array.isArray(response.data.fonts)) {
|
||||||
|
throw new Error('Proxy API returned invalid response');
|
||||||
// Validate response has fonts array
|
|
||||||
if (!response.data || !Array.isArray(response.data.fonts)) {
|
|
||||||
console.error('[fetchProxyFonts] Invalid response from proxy API', response.data);
|
|
||||||
throw new Error('Proxy API returned invalid response');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[fetchProxyFonts] Proxy API success', {
|
|
||||||
count: response.data.fonts.length,
|
|
||||||
});
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[fetchProxyFonts] Proxy API failed, using fallback', error);
|
|
||||||
|
|
||||||
// Check if it's a network error or proxy not available
|
|
||||||
const isNetworkError = error instanceof Error
|
|
||||||
&& (error.message.includes('Failed to fetch')
|
|
||||||
|| error.message.includes('Network')
|
|
||||||
|| error.message.includes('404')
|
|
||||||
|| error.message.includes('500'));
|
|
||||||
|
|
||||||
if (isNetworkError) {
|
|
||||||
// Fall back to Fontshare API
|
|
||||||
console.log('[fetchProxyFonts] Using Fontshare API as fallback');
|
|
||||||
return await fetchFontshareFallback(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-throw other errors
|
|
||||||
if (error instanceof Error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
throw new Error(`Failed to fetch fonts from proxy API: ${String(error)}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use Fontshare API directly
|
return response.data;
|
||||||
console.log('[fetchProxyFonts] Using Fontshare API (proxy disabled)');
|
|
||||||
return await fetchFontshareFallback(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fallback to Fontshare API when proxy is unavailable
|
|
||||||
*
|
|
||||||
* Maps proxy API params to Fontshare API params and normalizes response
|
|
||||||
*/
|
|
||||||
async function fetchFontshareFallback(
|
|
||||||
params: ProxyFontsParams,
|
|
||||||
): Promise<ProxyFontsResponse> {
|
|
||||||
// Import dynamically to avoid circular dependency
|
|
||||||
const { fetchFontshareFonts } = await import('$entities/Font/api/fontshare/fontshare');
|
|
||||||
const { normalizeFontshareFonts } = await import('$entities/Font/lib/normalize/normalize');
|
|
||||||
|
|
||||||
// Map proxy params to Fontshare params
|
|
||||||
const fontshareParams = {
|
|
||||||
q: params.q,
|
|
||||||
categories: params.category ? [params.category] : undefined,
|
|
||||||
page: params.offset ? Math.floor(params.offset / (params.limit || 50)) + 1 : undefined,
|
|
||||||
limit: params.limit,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetchFontshareFonts(fontshareParams);
|
|
||||||
const normalizedFonts = normalizeFontshareFonts(response.fonts);
|
|
||||||
|
|
||||||
return {
|
|
||||||
fonts: normalizedFonts,
|
|
||||||
total: response.count_total,
|
|
||||||
limit: params.limit || response.count,
|
|
||||||
offset: params.offset || 0,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,24 +181,9 @@ export async function fetchProxyFontById(
|
|||||||
export async function fetchFontsByIds(ids: string[]): Promise<UnifiedFont[]> {
|
export async function fetchFontsByIds(ids: string[]): Promise<UnifiedFont[]> {
|
||||||
if (ids.length === 0) return [];
|
if (ids.length === 0) return [];
|
||||||
|
|
||||||
// Use proxy API if enabled
|
const queryString = ids.join(',');
|
||||||
if (USE_PROXY_API) {
|
const url = `${PROXY_API_URL}/batch?ids=${queryString}`;
|
||||||
const queryString = ids.join(',');
|
|
||||||
const url = `${PROXY_API_URL}/batch?ids=${queryString}`;
|
|
||||||
|
|
||||||
try {
|
const response = await api.get<UnifiedFont[]>(url);
|
||||||
const response = await api.get<UnifiedFont[]>(url);
|
return response.data ?? [];
|
||||||
return response.data ?? [];
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[fetchFontsByIds] Proxy API batch fetch failed, falling back', error);
|
|
||||||
// Fallthrough to fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: Fetch individually (not efficient but functional for fallback)
|
|
||||||
const results = await Promise.all(
|
|
||||||
ids.map(id => fetchProxyFontById(id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return results.filter((f): f is UnifiedFont => !!f);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import type { FilterManager } from '../filterManager/filterManager.svelte';
|
|||||||
/**
|
/**
|
||||||
* Maps filter manager to proxy API parameters.
|
* Maps filter manager to proxy API parameters.
|
||||||
*
|
*
|
||||||
* Transforms UI filter state into proxy API query parameters.
|
* Updated to support multiple filter values (arrays)
|
||||||
* Handles conversion from filter groups to API-specific parameters.
|
|
||||||
*
|
*
|
||||||
* @param manager - Filter manager instance with reactive state
|
* @param manager - Filter manager instance with reactive state
|
||||||
* @returns - Partial proxy API parameters ready for API call
|
* @returns - Partial proxy API parameters ready for API call
|
||||||
@@ -15,13 +14,18 @@ import type { FilterManager } from '../filterManager/filterManager.svelte';
|
|||||||
* // Example filter manager state:
|
* // Example filter manager state:
|
||||||
* // {
|
* // {
|
||||||
* // queryValue: 'roboto',
|
* // queryValue: 'roboto',
|
||||||
* // providers: ['google'],
|
* // providers: ['google', 'fontshare'],
|
||||||
* // categories: ['sans-serif'],
|
* // categories: ['sans-serif', 'serif'],
|
||||||
* // subsets: ['latin']
|
* // subsets: ['latin']
|
||||||
* // }
|
* // }
|
||||||
*
|
*
|
||||||
* const params = mapManagerToParams(manager);
|
* const params = mapManagerToParams(manager);
|
||||||
* // Returns: { provider: 'google', category: 'sans-serif', subset: 'latin', q: 'roboto' }
|
* // Returns: {
|
||||||
|
* // providers: ['google', 'fontshare'],
|
||||||
|
* // categories: ['sans-serif', 'serif'],
|
||||||
|
* // subsets: ['latin'],
|
||||||
|
* // q: 'roboto'
|
||||||
|
* // }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function mapManagerToParams(manager: FilterManager): Partial<ProxyFontsParams> {
|
export function mapManagerToParams(manager: FilterManager): Partial<ProxyFontsParams> {
|
||||||
@@ -33,22 +37,17 @@ export function mapManagerToParams(manager: FilterManager): Partial<ProxyFontsPa
|
|||||||
// Search query (debounced)
|
// Search query (debounced)
|
||||||
q: manager.debouncedQueryValue || undefined,
|
q: manager.debouncedQueryValue || undefined,
|
||||||
|
|
||||||
// Provider filter (single value - proxy API doesn't support array)
|
// NEW: Support arrays - send all selected values
|
||||||
// Use first provider if multiple selected, or undefined if none/all selected
|
providers: providers && providers.length > 0
|
||||||
provider: providers && providers.length === 1
|
? providers as string[]
|
||||||
? (providers[0] as 'google' | 'fontshare')
|
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
||||||
// Category filter (single value - proxy API doesn't support array)
|
categories: categories && categories.length > 0
|
||||||
// Use first category if multiple selected, or undefined if none/all selected
|
? categories as string[]
|
||||||
category: categories && categories.length === 1
|
|
||||||
? (categories[0] as ProxyFontsParams['category'])
|
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
||||||
// Subset filter (single value - proxy API doesn't support array)
|
subsets: subsets && subsets.length > 0
|
||||||
// Use first subset if multiple selected, or undefined if none/all selected
|
? subsets as string[]
|
||||||
subset: subsets && subsets.length === 1
|
|
||||||
? (subsets[0] as ProxyFontsParams['subset'])
|
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,58 @@ import {
|
|||||||
FONT_PROVIDERS,
|
FONT_PROVIDERS,
|
||||||
FONT_SUBSETS,
|
FONT_SUBSETS,
|
||||||
} from '../const/const';
|
} from '../const/const';
|
||||||
|
import { filtersStore } from './filters.svelte';
|
||||||
|
|
||||||
const initialConfig = {
|
/**
|
||||||
queryValue: '',
|
* Creates initial filter config
|
||||||
groups: [
|
*
|
||||||
{
|
* Uses dynamic filters from backend if available,
|
||||||
id: 'providers',
|
* otherwise falls back to hard-coded constants
|
||||||
label: 'Font provider',
|
*/
|
||||||
properties: FONT_PROVIDERS,
|
function createInitialConfig() {
|
||||||
},
|
const dynamicFilters = filtersStore.filters;
|
||||||
{
|
|
||||||
id: 'subsets',
|
// If filters are loaded, use them
|
||||||
label: 'Font subset',
|
if (dynamicFilters.length > 0) {
|
||||||
properties: FONT_SUBSETS,
|
return {
|
||||||
},
|
queryValue: '',
|
||||||
{
|
groups: dynamicFilters.map(filter => ({
|
||||||
id: 'categories',
|
id: filter.id,
|
||||||
label: 'Font category',
|
label: filter.name,
|
||||||
properties: FONT_CATEGORIES,
|
properties: filter.options.map(opt => ({
|
||||||
},
|
id: opt.id,
|
||||||
],
|
name: opt.name,
|
||||||
};
|
value: opt.value,
|
||||||
|
count: opt.count,
|
||||||
|
selected: false,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to hard-coded constants (backward compatibility)
|
||||||
|
return {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialConfig = createInitialConfig();
|
||||||
|
|
||||||
export const filterManager = createFilterManager(initialConfig);
|
export const filterManager = createFilterManager(initialConfig);
|
||||||
|
|||||||
Reference in New Issue
Block a user