feature/fetch-fonts #14

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

View File

@@ -43,11 +43,9 @@ export interface FontshareParams extends QueryParams {
/** /**
* Fontshare API response wrapper * Fontshare API response wrapper
* Extends collection model with additional metadata * Re-exported from model/types for backward compatibility
*/ */
export interface FontshareResponse extends FontshareApiModel { export type FontshareResponse = FontshareApiModel;
// Response structure matches FontshareApiModel
}
/** /**
* Fetch fonts from Fontshare API * Fetch fonts from Fontshare API

View File

@@ -7,6 +7,10 @@
* @see https://developers.google.com/fonts/docs/developer_api * @see https://developers.google.com/fonts/docs/developer_api
*/ */
import type {
FontItem,
GoogleFontsApiModel,
} from '$entities/Font';
import { api } from '$shared/api/api'; import { api } from '$shared/api/api';
import { buildQueryString } from '$shared/utils'; import { buildQueryString } from '$shared/utils';
import type { QueryParams } from '$shared/utils'; import type { QueryParams } from '$shared/utils';
@@ -43,25 +47,15 @@ export interface GoogleFontsParams extends QueryParams {
/** /**
* Google Fonts API response wrapper * Google Fonts API response wrapper
* Re-exported from model/types for backward compatibility
*/ */
export interface GoogleFontsResponse { export type GoogleFontsResponse = GoogleFontsApiModel;
kind: string;
items: GoogleFontItem[];
}
/** /**
* Simplified font item from Google Fonts API * Simplified font item from Google Fonts API
* Re-exported from model/types for backward compatibility
*/ */
export interface GoogleFontItem { export type GoogleFontItem = FontItem;
family: string;
category: string;
variants: string[];
subsets: string[];
version: string;
lastModified: string;
files: Record<string, string>;
menu: string;
}
/** /**
* Google Fonts API base URL * Google Fonts API base URL

View File

@@ -1,11 +1,13 @@
import type { FontshareFont } from '$entities/Font'; import type {
FontshareFont,
GoogleFontItem,
UnifiedFont,
} from '$entities/Font';
import { import {
describe, describe,
expect, expect,
it, it,
} from 'vitest'; } from 'vitest';
import type { GoogleFontItem } from '../google/googleFonts';
import type { UnifiedFont } from './normalize';
import { import {
normalizeFontshareFont, normalizeFontshareFont,
normalizeFontshareFonts, normalizeFontshareFonts,

View File

@@ -7,89 +7,16 @@
import type { import type {
FontCategory, FontCategory,
FontFeatures,
FontMetadata,
FontProvider, FontProvider,
FontStyleUrls,
FontSubset, FontSubset,
} from '$entities/Font'; FontshareFont,
import type { FontshareFont } from '$entities/Font'; GoogleFontItem,
import type { GoogleFontItem } from './googleFonts'; UnifiedFont,
UnifiedFontVariant,
/** } from '../../model/types';
* Font variant types (standardized)
*/
export type UnifiedFontVariant = string;
/**
* Font style URLs
*/
export interface FontStyleUrls {
/** Regular weight URL */
regular?: string;
/** Italic URL */
italic?: string;
/** Bold weight URL */
bold?: string;
/** Bold italic URL */
boldItalic?: string;
}
/**
* Font metadata
*/
export interface FontMetadata {
/** Timestamp when font was cached */
cachedAt: number;
/** Font version from provider */
version?: string;
/** Last modified date from provider */
lastModified?: string;
/** Popularity rank (if available from provider) */
popularity?: number;
}
/**
* Font features (variable fonts, axes, tags)
*/
export interface FontFeatures {
/** Whether this is a variable font */
isVariable?: boolean;
/** Variable font axes (for Fontshare) */
axes?: Array<{
name: string;
property: string;
default: number;
min: number;
max: number;
}>;
/** Usage tags (for Fontshare) */
tags?: string[];
}
/**
* Unified font model
*
* Combines Google Fonts and Fontshare data into a common interface
* for consistent font handling across the application.
*/
export interface UnifiedFont {
/** Unique identifier (Google: family name, Fontshare: slug) */
id: string;
/** Font display name */
name: string;
/** Font provider (google | fontshare) */
provider: FontProvider;
/** Font category classification */
category: FontCategory;
/** Supported character subsets */
subsets: FontSubset[];
/** Available font variants (weights, styles) */
variants: UnifiedFontVariant[];
/** URL mapping for font file downloads */
styles: FontStyleUrls;
/** Additional metadata */
metadata: FontMetadata;
/** Advanced font features */
features: FontFeatures;
}
/** /**
* Map Google Fonts category to unified FontCategory * Map Google Fonts category to unified FontCategory

View File

@@ -23,31 +23,38 @@ export {
normalizeGoogleFonts, normalizeGoogleFonts,
} from './api/normalize'; } from './api/normalize';
export type { export type {
FontFeatures, // Domain types
FontMetadata,
FontStyleUrls,
UnifiedFont,
UnifiedFontVariant,
} from './api/normalize';
export type {
FontCategory, FontCategory,
FontCollectionFilters,
FontCollectionSort,
// Store types
FontCollectionState,
FontCollectionStore,
FontFeatures,
FontFiles,
FontItem,
FontMetadata,
FontProvider, FontProvider,
FontSubset, // Fontshare API types
} from './model/types/font';
export type {
FontshareApiModel, FontshareApiModel,
FontshareAxis,
FontshareDesigner, FontshareDesigner,
FontshareFeature, FontshareFeature,
FontshareFont, FontshareFont,
FontshareLink,
FontsharePublisher, FontsharePublisher,
FontshareStyle, FontshareStyle,
FontshareStyleProperties, FontshareStyleProperties,
FontshareTag, FontshareTag,
FontshareWeight, FontshareWeight,
} from './model/types/fontshare_fonts'; FontStyleUrls,
export type { FontSubset,
FontFiles,
FontItem,
FontVariant, FontVariant,
FontWeight,
FontWeightItalic,
// Google Fonts API types
GoogleFontsApiModel, GoogleFontsApiModel,
} from './model/types/google_fonts'; // Normalization types
UnifiedFont,
UnifiedFontVariant,
} from './model/types';

View File

@@ -7,7 +7,16 @@
* Provides derived stores for filtered/sorted fonts. * Provides derived stores for filtered/sorted fonts.
*/ */
import type { UnifiedFont } from '$entities/Font/api/normalize'; import type {
FontCategory,
FontCollectionFilters,
FontCollectionSort,
FontCollectionState,
FontCollectionStore,
FontProvider,
FontSubset,
UnifiedFont,
} from '$entities/Font';
import { createCollectionCache } from '$shared/fetch/collectionCache'; import { createCollectionCache } from '$shared/fetch/collectionCache';
import type { import type {
Readable, Readable,
@@ -18,84 +27,6 @@ import {
get, get,
writable, writable,
} from 'svelte/store'; } from 'svelte/store';
import type {
FontCategory,
FontProvider,
} from '../types/font';
/**
* Font collection state
*/
export interface FontCollectionState {
/** All cached fonts */
fonts: Record<string, UnifiedFont>;
/** Active filters */
filters: FontCollectionFilters;
/** Sort configuration */
sort: FontCollectionSort;
}
/**
* Font collection filters
*/
export interface FontCollectionFilters {
/** Search query */
searchQuery?: string;
/** Filter by provider */
provider?: FontProvider;
/** Filter by category */
category?: FontCategory;
/** Filter by subsets */
subsets?: string[];
}
/**
* Font collection sort configuration
*/
export interface FontCollectionSort {
/** Sort field */
field: 'name' | 'popularity' | 'category';
/** Sort direction */
direction: 'asc' | 'desc';
}
/**
* Font collection store interface
*/
export interface FontCollectionStore {
/** Main state store */
state: Writable<FontCollectionState>;
/** All fonts as array */
fonts: Readable<UnifiedFont[]>;
/** Filtered fonts as array */
filteredFonts: Readable<UnifiedFont[]>;
/** Number of fonts in collection */
count: Readable<number>;
/** Loading state */
isLoading: Readable<boolean>;
/** Error state */
error: Readable<string | undefined>;
/** Add fonts to collection */
addFonts: (fonts: UnifiedFont[]) => void;
/** Add single font to collection */
addFont: (font: UnifiedFont) => void;
/** Remove font from collection */
removeFont: (fontId: string) => void;
/** Clear all fonts */
clear: () => void;
/** Update filters */
setFilters: (filters: Partial<FontCollectionFilters>) => void;
/** Clear filters */
clearFilters: () => void;
/** Update sort configuration */
setSort: (sort: FontCollectionSort) => void;
/** Get font by ID */
getFont: (fontId: string) => UnifiedFont | undefined;
/** Get fonts by provider */
getFontsByProvider: (provider: FontProvider) => UnifiedFont[];
/** Get fonts by category */
getFontsByCategory: (category: FontCategory) => UnifiedFont[];
}
/** /**
* Create font collection store * Create font collection store
@@ -172,7 +103,7 @@ export function createFontCollectionStore(
// Apply subset filter // Apply subset filter
if ($state.filters.subsets?.length) { if ($state.filters.subsets?.length) {
filtered = filtered.filter(font => filtered = filtered.filter(font =>
$state.filters.subsets!.some(subset => font.subsets.includes(subset as any)) $state.filters.subsets!.some(subset => font.subsets.includes(subset as FontSubset))
); );
} }

View File

@@ -1,3 +1,144 @@
/**
* ============================================================================
* DOMAIN TYPES
* ============================================================================
*/
/**
* Font category
*/
export type FontCategory = 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace';
/**
* Font provider
*/
export type FontProvider = 'google' | 'fontshare';
/**
* Font subset
*/
export type FontSubset = 'latin' | 'latin-ext' | 'cyrillic' | 'greek' | 'arabic' | 'devanagari';
/**
* ============================================================================
* GOOGLE FONTS API TYPES
* ============================================================================
*/
/**
* Model of google fonts api response
*/
export interface GoogleFontsApiModel {
/**
* Array of font items returned by the Google Fonts API
* Contains all font families matching the requested query parameters
*/
items: FontItem[];
}
/**
* Individual font from Google Fonts API
*/
export interface FontItem {
/**
* Font family name (e.g., "Roboto", "Open Sans", "Lato")
* This is the name used in CSS font-family declarations
*/
family: string;
/**
* Font category classification (e.g., "sans-serif", "serif", "display", "handwriting", "monospace")
* Useful for grouping and filtering fonts by style
*/
category: string;
/**
* Available font variants for this font family
* Array of strings representing available weights and styles
* Examples: ["regular", "italic", "100", "200", "300", "400", "500", "600", "700", "800", "900", "100italic", "900italic"]
* The keys in the `files` object correspond to these variant values
*/
variants: FontVariant[];
/**
* Supported character subsets for this font
* Examples: ["latin", "latin-ext", "cyrillic", "greek", "arabic", "devanagari", "vietnamese", "hebrew", "thai", etc.]
* Determines which character sets are included in the font files
*/
subsets: string[];
/**
* Font version identifier
* Format: "v" followed by version number (e.g., "v31", "v20", "v1")
* Used to track font updates and cache busting
*/
version: string;
/**
* Last modification date of the font
* Format: ISO 8601 date string (e.g., "2024-01-15", "2023-12-01")
* Indicates when the font was last updated by the font foundry
*/
lastModified: string;
/**
* Mapping of font variants to their downloadable URLs
* Keys correspond to values in the `variants` array
* Examples:
* - "regular" "https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Me4W..."
* - "700" "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlf..."
* - "700italic" "https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TzA..."
*/
files: FontFiles;
/**
* URL to the font menu preview image
* Typically a PNG showing the font family name in the font
* Example: "https://fonts.gstatic.com/l/font?kit=KFOmCnqEu92Fr1Me4W...&s=i2"
*/
menu: string;
}
/**
* Standard font weights that can appear in Google Fonts API
*/
export type FontWeight = '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
/**
* Italic variant format: e.g., "100italic", "400italic", "700italic"
*/
export type FontWeightItalic = `${FontWeight}italic`;
/**
* All possible font variants in Google Fonts API
* - Numeric weights: "400", "700", etc.
* - Italic variants: "400italic", "700italic", etc.
* - Legacy names: "regular", "italic", "bold", "bolditalic"
*/
export type FontVariant =
| FontWeight
| FontWeightItalic
| 'regular'
| 'italic'
| 'bold'
| 'bolditalic';
/**
* Google Fonts API file mapping
* Dynamic keys that match the variants array
*
* Examples:
* - { "regular": "...", "italic": "...", "700": "...", "700italic": "..." }
* - { "400": "...", "400italic": "...", "900": "..." }
*/
export type FontFiles = Partial<Record<FontVariant, string>>;
/**
* ============================================================================
* FONTHARE API TYPES
* ============================================================================
*/
import type { CollectionApiModel } from '$shared/types/collection'; import type { CollectionApiModel } from '$shared/types/collection';
export const FONTSHARE_API_URL = 'https://api.fontshare.com/v2' as const; export const FONTSHARE_API_URL = 'https://api.fontshare.com/v2' as const;
@@ -439,3 +580,167 @@ export interface FontshareWeight {
*/ */
weight: number; weight: number;
} }
/**
* ============================================================================
* NORMALIZATION TYPES
* ============================================================================
*/
/**
* Font variant types (standardized)
*/
export type UnifiedFontVariant = string;
/**
* Font style URLs
*/
export interface FontStyleUrls {
/** Regular weight URL */
regular?: string;
/** Italic URL */
italic?: string;
/** Bold weight URL */
bold?: string;
/** Bold italic URL */
boldItalic?: string;
}
/**
* Font metadata
*/
export interface FontMetadata {
/** Timestamp when font was cached */
cachedAt: number;
/** Font version from provider */
version?: string;
/** Last modified date from provider */
lastModified?: string;
/** Popularity rank (if available from provider) */
popularity?: number;
}
/**
* Font features (variable fonts, axes, tags)
*/
export interface FontFeatures {
/** Whether this is a variable font */
isVariable?: boolean;
/** Variable font axes (for Fontshare) */
axes?: Array<{
name: string;
property: string;
default: number;
min: number;
max: number;
}>;
/** Usage tags (for Fontshare) */
tags?: string[];
}
/**
* Unified font model
*
* Combines Google Fonts and Fontshare data into a common interface
* for consistent font handling across the application.
*/
export interface UnifiedFont {
/** Unique identifier (Google: family name, Fontshare: slug) */
id: string;
/** Font display name */
name: string;
/** Font provider (google | fontshare) */
provider: FontProvider;
/** Font category classification */
category: FontCategory;
/** Supported character subsets */
subsets: FontSubset[];
/** Available font variants (weights, styles) */
variants: UnifiedFontVariant[];
/** URL mapping for font file downloads */
styles: FontStyleUrls;
/** Additional metadata */
metadata: FontMetadata;
/** Advanced font features */
features: FontFeatures;
}
/**
* ============================================================================
* STORE TYPES
* ============================================================================
*/
/**
* Font collection state
*/
export interface FontCollectionState {
/** All cached fonts */
fonts: Record<string, UnifiedFont>;
/** Active filters */
filters: FontCollectionFilters;
/** Sort configuration */
sort: FontCollectionSort;
}
/**
* Font collection filters
*/
export interface FontCollectionFilters {
/** Search query */
searchQuery?: string;
/** Filter by provider */
provider?: FontProvider;
/** Filter by category */
category?: FontCategory;
/** Filter by subsets */
subsets?: string[];
}
/**
* Font collection sort configuration
*/
export interface FontCollectionSort {
/** Sort field */
field: 'name' | 'popularity' | 'category';
/** Sort direction */
direction: 'asc' | 'desc';
}
/**
* Font collection store interface
*/
export interface FontCollectionStore {
/** Main state store */
state: import('svelte/store').Writable<FontCollectionState>;
/** All fonts as array */
fonts: import('svelte/store').Readable<UnifiedFont[]>;
/** Filtered fonts as array */
filteredFonts: import('svelte/store').Readable<UnifiedFont[]>;
/** Number of fonts in collection */
count: import('svelte/store').Readable<number>;
/** Loading state */
isLoading: import('svelte/store').Readable<boolean>;
/** Error state */
error: import('svelte/store').Readable<string | undefined>;
/** Add fonts to collection */
addFonts: (fonts: UnifiedFont[]) => void;
/** Add single font to collection */
addFont: (font: UnifiedFont) => void;
/** Remove font from collection */
removeFont: (fontId: string) => void;
/** Clear all fonts */
clear: () => void;
/** Update filters */
setFilters: (filters: Partial<FontCollectionFilters>) => void;
/** Clear filters */
clearFilters: () => void;
/** Update sort configuration */
setSort: (sort: FontCollectionSort) => void;
/** Get font by ID */
getFont: (fontId: string) => UnifiedFont | undefined;
/** Get fonts by provider */
getFontsByProvider: (provider: FontProvider) => UnifiedFont[];
/** Get fonts by category */
getFontsByCategory: (category: FontCategory) => UnifiedFont[];
}

View File

@@ -1,14 +0,0 @@
/**
* Font category
*/
export type FontCategory = 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace';
/**
* Font provider
*/
export type FontProvider = 'google' | 'fontshare';
/**
* Font subset
*/
export type FontSubset = 'latin' | 'latin-ext' | 'cyrillic' | 'greek' | 'arabic' | 'devanagari';

View File

@@ -1,104 +0,0 @@
/**
* Model of google fonts api response
*/
export interface GoogleFontsApiModel {
/**
* Array of font items returned by the Google Fonts API
* Contains all font families matching the requested query parameters
*/
items: FontItem[];
}
export interface FontItem {
/**
* Font family name (e.g., "Roboto", "Open Sans", "Lato")
* This is the name used in CSS font-family declarations
*/
family: string;
/**
* Font category classification (e.g., "sans-serif", "serif", "display", "handwriting", "monospace")
* Useful for grouping and filtering fonts by style
*/
category: string;
/**
* Available font variants for this font family
* Array of strings representing available weights and styles
* Examples: ["regular", "italic", "100", "200", "300", "400", "500", "600", "700", "800", "900", "100italic", "900italic"]
* The keys in the `files` object correspond to these variant values
*/
variants: FontVariant[];
/**
* Supported character subsets for this font
* Examples: ["latin", "latin-ext", "cyrillic", "greek", "arabic", "devanagari", "vietnamese", "hebrew", "thai", etc.]
* Determines which character sets are included in the font files
*/
subsets: string[];
/**
* Font version identifier
* Format: "v" followed by version number (e.g., "v31", "v20", "v1")
* Used to track font updates and cache busting
*/
version: string;
/**
* Last modification date of the font
* Format: ISO 8601 date string (e.g., "2024-01-15", "2023-12-01")
* Indicates when the font was last updated by the font foundry
*/
lastModified: string;
/**
* Mapping of font variants to their downloadable URLs
* Keys correspond to values in the `variants` array
* Examples:
* - "regular" → "https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Me4W..."
* - "700" → "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlf..."
* - "700italic" → "https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TzA..."
*/
files: FontFiles;
/**
* URL to the font menu preview image
* Typically a PNG showing the font family name in the font
* Example: "https://fonts.gstatic.com/l/font?kit=KFOmCnqEu92Fr1Me4W...&s=i2"
*/
menu: string;
}
/**
* Standard font weights that can appear in Google Fonts API
*/
export type FontWeight = '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
/**
* Italic variant format: e.g., "100italic", "400italic", "700italic"
*/
export type FontWeightItalic = `${FontWeight}italic`;
/**
* All possible font variants in Google Fonts API
* - Numeric weights: "400", "700", etc.
* - Italic variants: "400italic", "700italic", etc.
* - Legacy names: "regular", "italic", "bold", "bolditalic"
*/
export type FontVariant =
| FontWeight
| FontWeightItalic
| 'regular'
| 'italic'
| 'bold'
| 'bolditalic';
/**
* Google Fonts API file mapping
* Dynamic keys that match the variants array
*
* Examples:
* - { "regular": "...", "italic": "...", "700": "...", "700italic": "..." }
* - { "400": "...", "400italic": "...", "900": "..." }
*/
export type FontFiles = Partial<Record<FontVariant, string>>;