import { info, themeColor, ThemeMode } from "../hooks/usePref.js"; import { convertTimeToTag, formatText } from "../lrc-parser.js"; import { unregister } from "../utils/sw.unregister.js"; import { appContext, ChangBits } from "./app.context.js"; import { AkariHideWall } from "./svg.img.js"; const { useCallback, useContext, useEffect, useMemo, useRef } = React; const numberInputProps = { type: "number", step: 1 } as const; type OnChange = (event: React.ChangeEvent) => void; interface IUseNumberInput { (defaultValue: number, onChange: OnChange): typeof numberInputProps & { ref: React.RefObject; onChange: OnChange; defaultValue: number; }; } const useNumberInput: IUseNumberInput = (defaultValue: number, onChange) => { const ref = useRef(null); useEffect(() => { const target = ref.current; if (target) { const onChange = (): void => { target.value = defaultValue.toString(); }; target.addEventListener("change", onChange); return (): void => target.removeEventListener("change", onChange); } }, [defaultValue]); const $onChange = useCallback( (ev: React.ChangeEvent) => { if (ev.target.validity.valid) { onChange(ev); } }, [onChange], ); return { ...numberInputProps, ref, onChange: $onChange, defaultValue }; }; export const Preferences: React.FC = () => { const { prefState, prefDispatch, lang } = useContext(appContext, ChangBits.lang || ChangBits.prefState); const onColorPick = useCallback( (ev: React.ChangeEvent) => { prefDispatch({ type: "themeColor", payload: ev.target.value, }); }, [prefDispatch], ); const userColorInputText = useRef(null); const onUserInput = useCallback( (input: EventTarget & HTMLInputElement) => { let value = input.value; if (!input.validity.valid) { input.value = input.defaultValue; return; } if (value.length === 3) { value = [].map.call(value, (v: string) => v + v).join(""); } if (value.length < 6) { value = value.padEnd(6, "0"); } prefDispatch({ type: "themeColor", payload: "#" + value, }); }, [prefDispatch], ); const onUserColorInputBlur = useCallback( (ev: React.FocusEvent) => onUserInput(ev.target), [onUserInput], ); const onColorSubmit = useCallback( (ev: React.FormEvent) => { ev.preventDefault(); const form = ev.target as HTMLFormElement; const input = form.elements.namedItem("user-color-input-text") as HTMLInputElement; return onUserInput(input); }, [onUserInput], ); useEffect(() => { userColorInputText.current!.value = prefState.themeColor.slice(1); }, [prefState.themeColor]); const onSpaceChange = useCallback( (ev: React.ChangeEvent) => { prefDispatch({ type: ev.target.name as "spaceStart" & "spaceEnd", payload: ev.target.value, }); }, [prefDispatch], ); const onCacheClear = useCallback(() => { unregister(); }, []); const updateTime = useMemo(() => { const date = new Date(MetaData.updateTime); const options = { year: "numeric" as const, month: "short" as const, day: "numeric" as const, hour: "numeric" as const, minute: "numeric" as const, second: "numeric" as const, timeZoneName: "short" as const, hour12: false as const, }; return new Intl.DateTimeFormat(prefState.lang, options).format(date); }, [prefState.lang]); const onLangChanged = useCallback( (ev: React.ChangeEvent) => { prefDispatch({ type: "lang", payload: ev.target.value, }); }, [prefDispatch], ); const onBuiltInAudioToggle = useCallback( () => prefDispatch({ type: "builtInAudio", payload: (prefState) => !prefState.builtInAudio, }), [prefDispatch], ); const onScreenButtonToggle = useCallback( () => prefDispatch({ type: "screenButton", payload: (prefState) => !prefState.screenButton, }), [prefDispatch], ); const onThemeModeChange = useCallback( (ev: React.ChangeEvent) => { prefDispatch({ type: "themeMode", payload: Number.parseInt(ev.target.value, 10) as ThemeMode, }); }, [prefDispatch], ); const onFixedChanged = useCallback( (ev: React.ChangeEvent) => { prefDispatch({ type: "fixed", payload: Number.parseInt(ev.target.value, 10) as Fixed, }); }, [prefDispatch], ); const LangOptionList = useMemo(() => { return Object.entries(info.languages).map(([langCode, langName]) => { return ( ); }); }, []); const ColorPickerWall = useMemo(() => { return Object.values(themeColor).map((color) => { const checked = color === prefState.themeColor; const classNames = ["color-picker", "ripple"]; if (checked) { classNames.push("checked"); } return ( ); }); }, [onColorPick, prefState.themeColor]); const currentThemeColorStyle = useMemo(() => { return { backgroundColor: prefState.themeColor, }; }, [prefState.themeColor]); const formatedText = useMemo(() => { return formatText(" hello 世界~ ", prefState.spaceStart, prefState.spaceEnd); }, [prefState.spaceStart, prefState.spaceEnd]); const userColorLabel = useRef(null); const userColorInput = useRef(null); useEffect(() => { if (userColorInput.current!.type === "color") { userColorLabel.current!.removeAttribute("for"); } }, []); return (
  • {lang.preferences.version} {MetaData.version}
  • {lang.preferences.commitHash} {MetaData.hash}
  • {lang.preferences.updateTime} {updateTime}
  • {lang.preferences.repo} Github
  • {lang.preferences.help} Github Wiki
  • {lang.preferences.language}
  • {lang.preferences.themeMode.label}
  • {lang.preferences.themeColor}
    {"#"} {prefState.themeColor.slice(1)}
    {ColorPickerWall}
  • {lang.preferences.lrcFormat} {formatedText}
  • {lang.preferences.fixed}
  • {lang.preferences.clearCache}
); };