import React, { useEffect, useMemo, useState } from 'react'; import { RefreshCw } from 'lucide-react'; import { useI18n } from '../../application/i18n/I18nProvider'; import { refreshFonts, useFontsLoading, useInstalledFontFamilies, } from '../../application/state/fontStore'; import { isFontInstalled } from '../../lib/fontAvailability'; import { cn } from '../../lib/utils'; import { Button } from '../ui/button'; import { Combobox, type ComboboxOption } from '../ui/combobox'; import { buildTerminalCjkFontOptions, getTerminalCjkFontSelectionStatus, RECOMMENDED_CJK_FONT_FAMILIES, type TerminalCjkFontOptionKind, } from '../../domain/terminalCjkFonts'; const previewFontFamily = (family: string): string | undefined => { const trimmed = family.trim(); if (!trimmed) return undefined; const escaped = trimmed.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); return `"${escaped}", monospace`; }; interface Props { value: string; onChange: (next: string) => void; className?: string; disabled?: boolean; label?: string; description?: string; } export const TerminalCjkFontSelect: React.FC = ({ value, onChange, className, disabled, label, description, }) => { const { t } = useI18n(); const installedFamilies = useInstalledFontFamilies(); const isLoading = useFontsLoading(); const [previewValue, setPreviewValue] = useState(value); useEffect(() => { setPreviewValue(value); }, [value]); const availableRecommendedFamilies = RECOMMENDED_CJK_FONT_FAMILIES.filter( (family) => isFontInstalled(family), ); const options = useMemo(() => { const built = buildTerminalCjkFontOptions({ installedFamilies, selectedValue: value, availableRecommendedFamilies, }); const kindLabels: Record = { auto: '', recommended: t('settings.terminal.font.cjk.option.recommended'), installed: t('settings.terminal.font.cjk.option.installed'), unverified: t('settings.terminal.font.cjk.option.unverified'), unavailable: t('settings.terminal.font.cjk.option.unavailable'), }; return built.map((option) => { const label = option.kind === 'auto' ? t('settings.terminal.font.cjk.option.auto') : option.value.trim(); return { value: option.value, label, sublabel: kindLabels[option.kind] || undefined, labelStyle: option.value ? { fontFamily: previewFontFamily(option.value) } : undefined, }; }); }, [availableRecommendedFamilies, installedFamilies, t, value]); const previewSelection = previewValue.trim(); const status = getTerminalCjkFontSelectionStatus( previewSelection, installedFamilies, availableRecommendedFamilies, Boolean(previewSelection && isFontInstalled(previewSelection)), ); const selectedFontFamily = previewFontFamily(value); const previewFamily = previewFontFamily(previewValue); const controls = (
); const preview = previewValue.trim() && ( <>
        {'你好 │ ABC  │ 123\n123  │ 测试 │ ABC'}
      
{status === 'alignment-risk' && (

{t('settings.terminal.font.cjk.alignmentWarning')}

)} {status === 'unavailable' && (

{t('settings.terminal.font.cjk.unavailableWarning')}

)} ); if (label) { return (
{label}
{description &&
{description}
}
{controls}
{preview &&
{preview}
}
); } return (
{controls} {preview}
); }; export default TerminalCjkFontSelect;