forked from OctoWoW/OctoLauncher
269 lines
7.3 KiB
TypeScript
269 lines
7.3 KiB
TypeScript
import { useForm } from 'react-hook-form';
|
|
import { useEffect, useState } from 'react';
|
|
import {
|
|
FilePen,
|
|
FolderOpen,
|
|
RefreshCw,
|
|
ScrollText,
|
|
ShieldAlert,
|
|
ShieldCheck
|
|
} from 'lucide-react';
|
|
|
|
import { PreferencesSchema } from '~common/schemas';
|
|
import { api } from '~renderer/utils/api';
|
|
import zodResolver from '~renderer/utils/zodResolver';
|
|
import { useT } from '~renderer/i18n';
|
|
|
|
import TextButton from './styled/TextButton';
|
|
import CheckboxInput from './form/CheckboxInput';
|
|
import DialogButton from './styled/DialogButton';
|
|
import ClientDirDialog from './ClientDirDialog';
|
|
import CloseButton from './styled/CloseButton';
|
|
|
|
const MirrorStatus = () => {
|
|
const t = useT();
|
|
const [state, setState] = useState<string>('verifying');
|
|
api.updater.observe.useSubscription(undefined, {
|
|
onData: ({ state }) => setState(state)
|
|
});
|
|
|
|
if (state === 'serverUnreachable')
|
|
return <span className="s1 text-red">{t('prefs.mirrorOffline')}</span>;
|
|
if (state === 'verifying' || state === 'updating')
|
|
return (
|
|
<span className="s1 text-blueGray">{t('prefs.mirrorChecking')}</span>
|
|
);
|
|
return <span className="s1 text-warmGreen">{t('prefs.mirrorOnline')}</span>;
|
|
};
|
|
|
|
type Props = { close: () => void };
|
|
|
|
const isLinux =
|
|
typeof window !== 'undefined' &&
|
|
window.electron?.process?.platform === 'linux';
|
|
|
|
const PreferencesDialog = ({ close }: Props) => {
|
|
const t = useT();
|
|
const { data: pref } = api.preferences.get.useQuery();
|
|
const setPref = api.preferences.set.useMutation();
|
|
const { data: protonVersions = [] } = api.general.protonVersions.useQuery(
|
|
undefined,
|
|
{ enabled: isLinux }
|
|
);
|
|
|
|
const verify = api.updater.verify.useMutation();
|
|
const repair = api.mods.repair.useMutation();
|
|
const openInstallFolder = api.general.openInstallFolder.useMutation();
|
|
const openLogFile = api.general.openLogFile.useMutation();
|
|
const addExclusion = api.general.addDefenderExclusion.useMutation();
|
|
|
|
const { handleSubmit, watch, setValue, reset } = useForm({
|
|
defaultValues: pref ?? {},
|
|
resolver: zodResolver(PreferencesSchema)
|
|
});
|
|
|
|
useEffect(() => {
|
|
pref && reset(pref);
|
|
}, [reset, pref]);
|
|
|
|
const setBool = (key: keyof PreferencesSchema) => (v: boolean) =>
|
|
setValue(key, v, {
|
|
shouldTouch: true,
|
|
shouldDirty: true,
|
|
shouldValidate: true
|
|
});
|
|
|
|
const useProton = !!watch('useProton');
|
|
const protonPath = watch('protonPath');
|
|
|
|
useEffect(() => {
|
|
if (!useProton || !protonVersions.length) return;
|
|
const stillValid = protonVersions.some(v => v.path === protonPath);
|
|
if (!stillValid)
|
|
setValue('protonPath', protonVersions[0].path, {
|
|
shouldDirty: true,
|
|
shouldValidate: true
|
|
});
|
|
}, [useProton, protonVersions, protonPath, setValue]);
|
|
|
|
return (
|
|
<form
|
|
className="tw-dialog !w-fit min-w-[480px] max-w-[640px] !gap-1"
|
|
onSubmit={handleSubmit(async v => {
|
|
await setPref.mutateAsync({
|
|
cleanWdb: v.cleanWdb,
|
|
minimizeToTrayOnPlay: v.minimizeToTrayOnPlay,
|
|
useProton: v.useProton,
|
|
protonPath: v.protonPath
|
|
});
|
|
close();
|
|
})}
|
|
>
|
|
<CloseButton
|
|
close={() => {
|
|
reset();
|
|
close();
|
|
}}
|
|
/>
|
|
<h3 className="tw-color">{t('prefs.title')}</h3>
|
|
<hr className="mb-1" />
|
|
|
|
<div className="flex items-center gap-3">
|
|
<h4 className="tw-color">{t('prefs.installLocation')}</h4>
|
|
<TextButton
|
|
icon={FolderOpen}
|
|
size={14}
|
|
onClick={() => openInstallFolder.mutateAsync()}
|
|
className="!p-1 text-blueGray"
|
|
>
|
|
{t('prefs.openFolder')}
|
|
</TextButton>
|
|
</div>
|
|
<div className="flex items-center gap-2 border border-blueGray/20 bg-darkGray/40 px-3 py-1">
|
|
<span
|
|
title={pref?.clientDir}
|
|
className="min-w-0 shrink grow overflow-hidden text-ellipsis whitespace-nowrap"
|
|
>
|
|
{pref?.clientDir ?? t('prefs.notSelected')}
|
|
</span>
|
|
<DialogButton
|
|
dialog={closeInner => (
|
|
<ClientDirDialog
|
|
close={() => {
|
|
closeInner();
|
|
close();
|
|
}}
|
|
/>
|
|
)}
|
|
clickAway={pref?.isPortable}
|
|
>
|
|
{open => (
|
|
<TextButton
|
|
icon={FilePen}
|
|
size={14}
|
|
onClick={open}
|
|
className="!p-1"
|
|
>
|
|
{t('prefs.change')}
|
|
</TextButton>
|
|
)}
|
|
</DialogButton>
|
|
</div>
|
|
|
|
<div className="mt-1 flex items-center gap-3">
|
|
<h4 className="tw-color">{t('prefs.downloadMirror')}</h4>
|
|
</div>
|
|
<div className="flex items-center gap-2 pl-2">
|
|
<input type="radio" checked readOnly className="accent-warmGreen" />
|
|
<span>Iceland</span>
|
|
<MirrorStatus />
|
|
<TextButton
|
|
icon={RefreshCw}
|
|
size={12}
|
|
onClick={() => verify.mutateAsync()}
|
|
title={t('prefs.recheck')}
|
|
className="!p-0 text-blueGray"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex min-w-0 flex-col">
|
|
<h4 className="tw-color">{t('prefs.troubleshooting')}</h4>
|
|
<TextButton
|
|
icon={ShieldCheck}
|
|
onClick={() => repair.mutateAsync().then(close)}
|
|
className="!items-start text-left text-warmGreen"
|
|
>
|
|
{t('prefs.verifyGameFiles')}
|
|
</TextButton>
|
|
<TextButton
|
|
icon={ScrollText}
|
|
onClick={() => openLogFile.mutateAsync()}
|
|
className="!items-start text-left text-pink"
|
|
>
|
|
{t('prefs.openLogFile')}
|
|
</TextButton>
|
|
<TextButton
|
|
icon={ShieldAlert}
|
|
onClick={() => addExclusion.mutateAsync()}
|
|
loading={addExclusion.isLoading}
|
|
className="!items-start text-left text-orange"
|
|
>
|
|
{t('prefs.allowThroughAntivirus')}
|
|
</TextButton>
|
|
{addExclusion.data?.ok === true && (
|
|
<span className="s1 text-warmGreen">
|
|
{t('prefs.exclusionAdded')}
|
|
</span>
|
|
)}
|
|
{addExclusion.data?.ok === false && (
|
|
<span className="s1 text-orange">{addExclusion.data.error}</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex min-w-0 flex-col">
|
|
<h4 className="tw-color">{t('prefs.generalSettings')}</h4>
|
|
<CheckboxInput
|
|
value={!!watch('cleanWdb')}
|
|
setValue={setBool('cleanWdb')}
|
|
label={t('prefs.cleanWdb')}
|
|
/>
|
|
<CheckboxInput
|
|
value={!!watch('minimizeToTrayOnPlay')}
|
|
setValue={setBool('minimizeToTrayOnPlay')}
|
|
label={t('prefs.minimizeToTray')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{isLinux && (
|
|
<div className="mt-1 flex flex-col gap-1">
|
|
<h4 className="tw-color">{t('prefs.proton')}</h4>
|
|
<CheckboxInput
|
|
value={useProton}
|
|
setValue={setBool('useProton')}
|
|
label={t('prefs.useProton')}
|
|
/>
|
|
{useProton && (
|
|
<>
|
|
<label className="s1 flex flex-col gap-0.5 pl-2 text-blueGray">
|
|
{t('prefs.protonVersion')}
|
|
<select
|
|
className="border border-blueGray/30 bg-darkGray px-2 py-1 text-white outline-none focus:border-warmGreen"
|
|
value={protonPath ?? ''}
|
|
onChange={e =>
|
|
setValue('protonPath', e.target.value || undefined, {
|
|
shouldDirty: true,
|
|
shouldValidate: true
|
|
})
|
|
}
|
|
>
|
|
{protonVersions.length === 0 && (
|
|
<option value="">{t('prefs.protonNone')}</option>
|
|
)}
|
|
{protonVersions.map(v => (
|
|
<option key={v.path} value={v.path}>
|
|
{v.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
{protonVersions.length === 0 && (
|
|
<span className="s1 pl-2 text-orange">
|
|
{t('prefs.protonNoneHint')}
|
|
</span>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<TextButton type="submit" className="mt-1 self-end text-green">
|
|
{t('prefs.save')}
|
|
</TextButton>
|
|
</form>
|
|
);
|
|
};
|
|
|
|
export default PreferencesDialog;
|