waybetter/packages/shell/scripts/google-fonts-catalog.mjs
CRT ad2a3ad375 shell: the panel, createPanel(site) (moved from wayBetter for X)
packages/shell/src/panel (waybetter/shell/panel): an app's own tab. An app describes itself
(PanelSite: brand and the home page's words, its own sections and their order, its theme store,
catalog and the preview component a theme document is drawn in, Make's examples and docs, the
install controller, its languages, what its native side serves) and createPanel returns the
component for ShellSite.panel. It is in the shell package because it is drawn in the frame and
shares its stores, native bridge and language.

- Panel.tsx (header, settings search over every settings section, navigation, toasts; Accounts
  and Updates), nav.ts (which built-in sections an app gets: each shows only when the app serves
  what it needs; the app's sections in its order), Home.tsx (hero, highlights, About from the
  app's data), controls.tsx + panel.css.
- Appearance.tsx, ThemeBuilder.tsx + builderModel.ts + builder.css, fonts/ (FontBrowser, FontRoles,
  catalog, the offline Google Fonts list; scripts/google-fonts-catalog.mjs regenerates it):
  the theme documents are the runtime's, the store, catalog, accents, draft palette, file suffix
  and preview are the app's (PanelSite.themes).
- Plugins.tsx (the plugins list and custom CSS over the host's plugins_*/custom_css_*),
  StoreSection.tsx, MakePanel.tsx + makeModel.ts + MarkdownDoc.tsx + markdown.ts + make.css (the
  examples, the theme file and the docs are the app's), Settings.tsx (links and session over
  settings_get/set with `siteLinks`, keyword alerts, the media panel), FeatureToggles.tsx
  (useFeatures + FeatureGroups over features_get/set: the app names its flags and groups),
  PowerSettings.tsx (deck defaults from site.deck: its flags and presets; DeckPreset gets `name`
  and `hint`), BackupPanel.tsx + backup.css, DeveloperPanel.tsx, LanguageSettings.tsx, compare.ts.
- frame: language.ts (createLanguage: follow the site / the system / one language, i18n_set;
  moved from the app), appI18n.ts (createAppI18n: the app's typed i18n instance, useT, dates and
  numbers; the app passes React's useSyncExternalStore), formatDate in i18n.ts (ShellI18n.date).
- tests/panel.test.ts: every panel key has English and is used, sections per app, the builder
  model, the font catalog, plugin header rules, the UI kit demos, Markdown, compare.
- npm package: exports ./shell/panel, ./shell/panel/*, ./shell/panel/*.css; the build is
  scripts/build.mjs (the shell is compiled with the runtime sources it imports under one root and
  its imports are pointed at dist/runtime; stylesheets and the font list are copied).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:03:40 +07:00

34 lines
2 KiB
JavaScript

// Regenerates src/panel/fonts/googleFontsCatalog.json, the font browser's offline list of Google
// Fonts families (name, category, weight axis or static weights, popularity rank). Development-time
// only: an app never fetches this list; it downloads font files natively when the owner installs
// one (fonts.googleapis.com / fonts.gstatic.com, see waybetter-host gfonts.rs and fonts.rs).
//
// node packages/shell/scripts/google-fonts-catalog.mjs [metadata.json]
//
// Without an argument it reads https://fonts.google.com/metadata/fonts. Only families with a Latin
// subset and a name the native side accepts (letters, digits, single spaces) are kept.
import { readFileSync, writeFileSync } from 'node:fs';
const CATEGORY = { 'Sans Serif': 'sans', Serif: 'serif', Display: 'display', Handwriting: 'handwriting', Monospace: 'mono' };
const NAME = /^[A-Za-z0-9]+( [A-Za-z0-9]+)*$/;
const text = process.argv[2]
? readFileSync(process.argv[2], 'utf8')
: await (await fetch('https://fonts.google.com/metadata/fonts')).text();
const metadata = JSON.parse(text.replace(/^\)\]\}'\s*/, ''));
const families = metadata.familyMetadataList
.filter(font => font.subsets?.includes('latin') && NAME.test(font.family) && font.family.length <= 48 && CATEGORY[font.category])
.sort((a, b) => a.popularity - b.popularity)
.map((font, rank) => {
const axis = font.axes?.find(item => item.tag === 'wght');
const weights = Object.keys(font.fonts ?? {}).filter(key => /^\d+$/.test(key)).map(Number).sort((a, b) => a - b);
return [font.family, CATEGORY[font.category], axis ? [axis.min, axis.max] : null, weights, rank + 1];
});
const out = {
source: 'https://fonts.google.com/metadata/fonts',
generated: new Date().toISOString().slice(0, 10),
fields: ['family', 'category', 'wghtAxis', 'weights', 'rank'],
families,
};
writeFileSync(new URL('../src/panel/fonts/googleFontsCatalog.json', import.meta.url), `${JSON.stringify(out)}\n`);
console.log(`${families.length} families`);