import type { IpcRendererEvent } from 'electron'; import isElectron from 'is-electron'; import { FileInput, NumberInput, Select, toast } from '/@/renderer/components'; import { SettingsSection, SettingOption, } from '/@/renderer/features/settings/components/settings-section'; import { useFontSettings, useGeneralSettings, useSettingsStoreActions, } from '/@/renderer/store/settings.store'; import { useTranslation } from 'react-i18next'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { FontType } from '/@/renderer/types'; import i18n, { languages } from '/@/i18n/i18n'; const localSettings = isElectron() ? window.electron.localSettings : null; const ipc = isElectron() ? window.electron.ipc : null; type Font = { label: string; value: string; }; const FONT_OPTIONS: Font[] = [ { label: 'Archivo', value: 'Archivo' }, { label: 'Fredoka', value: 'Fredoka' }, { label: 'Inter', value: 'Inter' }, { label: 'League Spartan', value: 'League Spartan' }, { label: 'Lexend', value: 'Lexend' }, { label: 'Poppins', value: 'Poppins' }, { label: 'Raleway', value: 'Raleway' }, { label: 'Sora', value: 'Sora' }, { label: 'Work Sans', value: 'Work Sans' }, ]; const FONT_TYPES: Font[] = [ { label: i18n.t('setting.fontType', { context: 'optionBuiltIn', postProcess: 'sentenceCase', }), value: FontType.BUILT_IN, }, ]; if (window.queryLocalFonts) { FONT_TYPES.push({ label: i18n.t('setting.fontType', { context: 'optionSystem', postProcess: 'sentenceCase' }), value: FontType.SYSTEM, }); } if (isElectron()) { FONT_TYPES.push({ label: i18n.t('setting.fontType', { context: 'optionCustom', postProcess: 'sentenceCase' }), value: FontType.CUSTOM, }); } export const ApplicationSettings = () => { const { t } = useTranslation(); const settings = useGeneralSettings(); const fontSettings = useFontSettings(); const { setSettings } = useSettingsStoreActions(); const [localFonts, setLocalFonts] = useState([]); const fontList = useMemo(() => { if (fontSettings.custom) { return fontSettings.custom.split(/(\\|\/)/g).pop()!; } return ''; }, [fontSettings.custom]); const onFontError = useCallback( (_: IpcRendererEvent, file: string) => { toast.error({ message: `${file} is not a valid font file`, }); setSettings({ font: { ...fontSettings, custom: null, }, }); }, [fontSettings, setSettings], ); useEffect(() => { if (localSettings) { localSettings.fontError(onFontError); return () => { ipc!.removeAllListeners('custom-font-error'); }; } return () => {}; }, [onFontError]); useEffect(() => { const getFonts = async () => { if ( fontSettings.type === FontType.SYSTEM && localFonts.length === 0 && window.queryLocalFonts ) { try { // WARNING (Oct 17 2023): while this query is valid for chromium-based // browsers, it is still experimental, and so Typescript will complain // @ts-ignore const status = await navigator.permissions.query({ name: 'local-fonts' }); if (status.state === 'denied') { throw new Error( t('error.localFontAccessDenied', { postProcess: 'sentenceCase' }), ); } const data = await window.queryLocalFonts(); setLocalFonts( data.map((font) => ({ label: font.fullName, value: font.postscriptName, })), ); } catch (error) { toast.error({ message: t('error.systemFontError', { postProcess: 'sentenceCase' }), }); setSettings({ font: { ...fontSettings, type: FontType.BUILT_IN, }, }); } } }; getFonts(); }, [fontSettings, localFonts, setSettings, t]); const handleChangeLanguage = (e: string) => { setSettings({ general: { ...settings, language: e, }, }); }; const options: SettingOption[] = [ { control: ( { if (!e) return; setSettings({ font: { ...fontSettings, type: e as FontType, }, }); }} /> ), description: t('setting.fontType', { context: 'description', postProcess: 'sentenceCase', }), isHidden: FONT_TYPES.length === 1, title: t('setting.fontType', { postProcess: 'sentenceCase' }), }, { control: ( { if (!e) return; setSettings({ font: { ...fontSettings, system: e, }, }); }} /> ), description: t('setting.font', { context: 'description', postProcess: 'sentenceCase' }), isHidden: !localFonts || fontSettings.type !== FontType.SYSTEM, title: t('setting.font', { postProcess: 'sentenceCase' }), }, { control: ( setSettings({ font: { ...fontSettings, custom: e?.path ?? null, }, }) } /> ), description: t('setting.customFontPath', { context: 'description', postProcess: 'sentenceCase', }), isHidden: fontSettings.type !== FontType.CUSTOM, title: t('setting.customFontPath', { postProcess: 'sentenceCase' }), }, { control: ( { if (!e) return; const newVal = e.currentTarget.value ? Math.min(Math.max(Number(e.currentTarget.value), 50), 300) : settings.zoomFactor; setSettings({ general: { ...settings, zoomFactor: newVal, }, }); localSettings!.setZoomFactor(newVal); }} /> ), description: t('setting.zoom', { context: 'description', postProcess: 'sentenceCase', }), isHidden: !isElectron(), title: t('setting.zoom', { postProcess: 'sentenceCase', }), }, ]; return ; };