2023-03-06 14:36:06 -05:00
|
|
|
import { ErrorContent } from 'components/ErrorContent';
|
2023-03-06 09:01:18 -05:00
|
|
|
import { Loading } from 'components/Loading';
|
2023-03-06 14:36:06 -05:00
|
|
|
import { usePromise, usePromiseLazy } from 'hooks/usePromise';
|
|
|
|
|
import React, { useState } from 'react'
|
2023-03-06 07:21:29 -05:00
|
|
|
import { useTranslation } from 'react-i18next';
|
2023-03-06 14:36:06 -05:00
|
|
|
import { Config, getConfig, getProfile, Profile, setConfig, setProfile } from 'services/config';
|
|
|
|
|
import { composeLoadable } from 'utils/composeLoadable';
|
|
|
|
|
import classNames from 'classnames';
|
2023-03-06 15:04:00 -05:00
|
|
|
import { useLogin } from 'services/s3si';
|
2023-03-06 15:32:09 -05:00
|
|
|
import { STAT_INK } from 'constant';
|
2023-03-08 09:55:23 -05:00
|
|
|
import { Header } from 'components/Header';
|
2023-03-06 07:21:29 -05:00
|
|
|
|
2023-03-06 14:36:06 -05:00
|
|
|
const Page: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
|
|
|
|
const { t } = useTranslation();
|
2023-03-08 09:55:23 -05:00
|
|
|
return <div className='full-card'>
|
|
|
|
|
<Header title={t('配置')} />
|
2023-03-06 14:36:06 -05:00
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type FormData = {
|
|
|
|
|
config: Config,
|
|
|
|
|
profile: Profile,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Form: React.FC<{
|
|
|
|
|
oldValue: FormData,
|
|
|
|
|
onSaved?: () => void,
|
|
|
|
|
}> = ({ oldValue, onSaved }) => {
|
2023-03-06 15:04:00 -05:00
|
|
|
const { login } = useLogin();
|
2023-03-06 07:21:29 -05:00
|
|
|
const { t } = useTranslation();
|
2023-03-06 14:36:06 -05:00
|
|
|
const [value, setValue] = useState(oldValue);
|
2023-03-06 07:21:29 -05:00
|
|
|
|
2023-03-06 14:36:06 -05:00
|
|
|
const changed = JSON.stringify(value) !== JSON.stringify(oldValue);
|
|
|
|
|
|
2023-03-06 15:04:00 -05:00
|
|
|
const setSessionToken = (t: string) => setValue({
|
|
|
|
|
...value,
|
|
|
|
|
profile: {
|
|
|
|
|
...value.profile,
|
|
|
|
|
state: {
|
|
|
|
|
...value.profile.state,
|
|
|
|
|
loginState: {
|
|
|
|
|
...value.profile.state.loginState,
|
|
|
|
|
sessionToken: t,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2023-03-06 14:36:06 -05:00
|
|
|
const [onSave, { loading, error }] = usePromiseLazy(async () => {
|
|
|
|
|
await setProfile(0, value.profile);
|
|
|
|
|
await setConfig(value.config);
|
|
|
|
|
onSaved?.();
|
|
|
|
|
})
|
2023-03-06 15:04:00 -05:00
|
|
|
const [onLogin, loginState] = usePromiseLazy(async () => {
|
|
|
|
|
const result = await login();
|
|
|
|
|
if (!result) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setSessionToken(result.sessionToken);
|
|
|
|
|
})
|
2023-03-06 08:32:00 -05:00
|
|
|
|
2023-03-06 07:21:29 -05:00
|
|
|
return <>
|
2023-03-06 14:36:06 -05:00
|
|
|
<div className='card'>
|
2023-03-06 15:04:00 -05:00
|
|
|
<div className="form-control w-full max-w-md mb-4">
|
|
|
|
|
<label className="label">
|
|
|
|
|
<span className="label-text">{t('Nintendo Account 会话令牌')}</span>
|
|
|
|
|
<span className="label-text-alt"><button
|
|
|
|
|
className={classNames('link', {
|
|
|
|
|
loading: loginState.loading,
|
|
|
|
|
})}
|
|
|
|
|
onClick={onLogin}
|
2023-03-06 15:27:48 -05:00
|
|
|
disabled={loginState.loading}
|
2023-03-06 15:04:00 -05:00
|
|
|
>{t('网页登录')}</button></span>
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
className="input input-bordered w-full"
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder={t('请点击右上角的登录填入') ?? undefined}
|
|
|
|
|
value={value.profile.state.loginState?.sessionToken ?? ''}
|
|
|
|
|
onChange={e => setSessionToken(e.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="form-control w-full max-w-md mb-4">
|
2023-03-06 14:36:06 -05:00
|
|
|
<label className="label">
|
|
|
|
|
<span className="label-text">{t('stat.ink API密钥')}</span>
|
|
|
|
|
<span className="label-text-alt"><a
|
|
|
|
|
className='underline'
|
|
|
|
|
target='_blank'
|
|
|
|
|
rel='noopener noreferrer'
|
2023-03-06 15:32:09 -05:00
|
|
|
href={`${STAT_INK}/profile`}
|
2023-03-06 14:36:06 -05:00
|
|
|
title={t('打开 stat.ink') ?? undefined}
|
|
|
|
|
>{t('stat.ink')}</a></span>
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
2023-03-06 15:04:00 -05:00
|
|
|
className="input input-bordered w-full"
|
2023-03-06 14:36:06 -05:00
|
|
|
type="text"
|
|
|
|
|
placeholder={t('长度为43') ?? undefined}
|
|
|
|
|
value={value.profile.state.statInkApiKey ?? ''}
|
|
|
|
|
onChange={e => setValue({
|
|
|
|
|
...value,
|
|
|
|
|
profile: {
|
|
|
|
|
...value.profile,
|
|
|
|
|
state: {
|
|
|
|
|
...value.profile.state,
|
|
|
|
|
statInkApiKey: e.target.value,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})}
|
|
|
|
|
/>
|
2023-03-06 07:21:29 -05:00
|
|
|
</div>
|
2023-03-06 14:36:06 -05:00
|
|
|
</div>
|
|
|
|
|
<ErrorContent error={error} />
|
2023-03-06 15:04:00 -05:00
|
|
|
<div className='flex gap-4 max-w-md justify-between flex-auto-all'>
|
|
|
|
|
<div className="tooltip" data-tip={changed ? undefined : t('没有更改')}>
|
|
|
|
|
<button className={classNames('btn btn-primary w-full', {
|
|
|
|
|
loading,
|
|
|
|
|
})} onClick={onSave} disabled={!changed}>{t('保存')}</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button className={classNames('btn', {
|
2023-03-06 14:36:06 -05:00
|
|
|
loading,
|
|
|
|
|
})} onClick={() => setValue(oldValue)}>{t('重置')}</button>
|
2023-03-06 07:21:29 -05:00
|
|
|
</div>
|
|
|
|
|
</>
|
2023-03-06 14:36:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const Settings: React.FC = () => {
|
|
|
|
|
let { loading, error, retry, result } = composeLoadable({
|
|
|
|
|
config: usePromise(getConfig),
|
|
|
|
|
profile: usePromise(() => getProfile(0)),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
return <Page>
|
|
|
|
|
<div className='h-full flex items-center justify-center'><Loading /></div>
|
|
|
|
|
</Page>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
return <Page>
|
|
|
|
|
<ErrorContent error={error} retry={retry} />
|
|
|
|
|
</Page>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return <Page>
|
|
|
|
|
{result && <Form oldValue={result} onSaved={retry} />}
|
|
|
|
|
</Page>
|
|
|
|
|
}
|