- Added fonts:sync-google command (uses GOOGLE_FONTS_API_KEY, generates /public/fonts/google files, manifest, CSS, cache flush) and
exposed manifest via new GET /api/v1/tenant/fonts endpoint with fallbacks for existing local fonts.
- Imported generated fonts CSS, added API client + font loader hook, and wired branding page font fields to searchable selects (with
custom override) that auto-load selected fonts.
- Invites layout editor now offers font selection per element with runtime font loading for previews/export alignment.
- New tests cover font sync command and font manifest API.
Tests run: php artisan test --filter=Fonts --testsuite=Feature.
Note: repository already has other modified files (e.g., EventPublicController, SettingsStoreRequest, guest components, etc.); left
untouched. Run php artisan fonts:sync-google after setting the API key to populate /public/fonts/google.
78 lines
2.1 KiB
TypeScript
78 lines
2.1 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
|
|
import { getTenantFonts, type TenantFont, type TenantFontVariant } from '../api';
|
|
|
|
const fontLoaders = new Map<string, Promise<void>>();
|
|
|
|
export function useTenantFonts() {
|
|
const { data, isLoading, isFetching, refetch } = useQuery({
|
|
queryKey: ['tenant', 'fonts'],
|
|
queryFn: getTenantFonts,
|
|
staleTime: 6 * 60 * 60 * 1000,
|
|
});
|
|
|
|
return {
|
|
fonts: data ?? [],
|
|
isLoading: isLoading || isFetching,
|
|
refetch,
|
|
};
|
|
}
|
|
|
|
export function ensureFontLoaded(font: TenantFont, preferred?: { weight?: number; style?: string }): Promise<void> {
|
|
if (typeof document === 'undefined' || typeof window === 'undefined') {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
const variant = pickVariant(font, preferred);
|
|
if (!variant) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
const key = `${font.family}-${variant.weight}-${variant.style}`;
|
|
|
|
if (document.fonts?.check(`1rem "${font.family}"`)) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
if (! fontLoaders.has(key)) {
|
|
const loader = new FontFace(font.family, `url(${variant.url})`, {
|
|
weight: String(variant.weight ?? ''),
|
|
style: variant.style ?? 'normal',
|
|
display: 'swap',
|
|
})
|
|
.load()
|
|
.then((fontFace) => {
|
|
document.fonts.add(fontFace);
|
|
})
|
|
.catch((error) => {
|
|
console.warn('[fonts] failed to load font', font.family, variant, error);
|
|
fontLoaders.delete(key);
|
|
});
|
|
|
|
fontLoaders.set(key, loader);
|
|
}
|
|
|
|
return fontLoaders.get(key) ?? Promise.resolve();
|
|
}
|
|
|
|
function pickVariant(font: TenantFont, preferred?: { weight?: number; style?: string }): TenantFontVariant | null {
|
|
const variants = font.variants ?? [];
|
|
if (! variants.length) {
|
|
return null;
|
|
}
|
|
|
|
if (preferred?.weight || preferred?.style) {
|
|
const found = variants.find((variant) => {
|
|
const matchesWeight = preferred.weight ? Number(variant.weight) === Number(preferred.weight) : true;
|
|
const matchesStyle = preferred.style ? variant.style === preferred.style : true;
|
|
return matchesWeight && matchesStyle;
|
|
});
|
|
if (found) {
|
|
return found;
|
|
}
|
|
}
|
|
|
|
return variants[0];
|
|
}
|
|
|