This commit is contained in:
@@ -9,30 +9,30 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@mui/icons-material": "^5.11.0",
|
||||
"@mui/material": "^5.11.6",
|
||||
"@react-three/drei": "^9.56.12",
|
||||
"@react-three/fiber": "^8.10.1",
|
||||
"@react-three/postprocessing": "^2.7.0",
|
||||
"axios": "^1.2.6",
|
||||
"notistack": "^2.0.8",
|
||||
"postprocessing": "^6.29.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.43.0",
|
||||
"three": "^0.148.0",
|
||||
"three-stdlib": "^2.21.8"
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@fontsource/roboto": "^5.2.5",
|
||||
"@mui/icons-material": "^7.0.1",
|
||||
"@mui/material": "^7.0.1",
|
||||
"@react-three/drei": "^10.0.5",
|
||||
"@react-three/fiber": "^9.1.0",
|
||||
"@react-three/postprocessing": "^3.0.4",
|
||||
"axios": "^1.8.4",
|
||||
"notistack": "^3.0.2",
|
||||
"postprocessing": "^6.37.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hook-form": "^7.55.0",
|
||||
"three": "^0.175.0",
|
||||
"three-stdlib": "^2.35.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"@types/three": "^0.148.0",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0",
|
||||
"vite-plugin-mock-dev-server": "^0.3.16"
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/three": "^0.175.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.3",
|
||||
"vite-plugin-mock-dev-server": "^1.8.4"
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023. Gardel <sunxinao@hotmail.com> and contributors
|
||||
* Copyright (C) 2023-2025. Gardel <sunxinao@hotmail.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -18,6 +18,7 @@
|
||||
import React from 'react';
|
||||
import './app.css';
|
||||
import Login from './login';
|
||||
import PasswordReset from './reset';
|
||||
import {Container} from '@mui/material';
|
||||
import {AppState} from './types';
|
||||
import User from './user';
|
||||
@@ -29,12 +30,13 @@ function App() {
|
||||
const [appData, setAppData] = React.useState(() => {
|
||||
const saved = localStorage.getItem('appData');
|
||||
return (saved ? JSON.parse(saved) : {
|
||||
login: false,
|
||||
login: 'register',
|
||||
accessToken: '',
|
||||
tokenValid: false,
|
||||
loginTime: 0,
|
||||
profileName: '',
|
||||
uuid: ''
|
||||
uuid: '',
|
||||
passwordReset: false
|
||||
}) as AppState;
|
||||
});
|
||||
|
||||
@@ -95,11 +97,52 @@ function App() {
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Container maxWidth={'lg'}>
|
||||
{appData.tokenValid ? <User appData={appData} setAppData={setAppData}/> : <Login appData={appData} setAppData={setAppData}/>}
|
||||
</Container>
|
||||
);
|
||||
const path = window.location.pathname;
|
||||
const hash = window.location.hash;
|
||||
if (hash && hash.length > 1) {
|
||||
const params = new URLSearchParams(hash.substring(1));
|
||||
if (params.has('emailVerifyToken')) {
|
||||
const token = params.get('emailVerifyToken');
|
||||
axios.get('/authserver/verifyEmail?access_token=' + token)
|
||||
.then(() => {
|
||||
window.location.replace('/profile/')
|
||||
enqueueSnackbar('邮箱验证通过', {variant: 'success'});
|
||||
})
|
||||
.catch(e => {
|
||||
const response = e.response;
|
||||
if (response && response.status >= 400 && response.status < 500) {
|
||||
let errorMessage = response.data.errorMessage ?? response.data;
|
||||
enqueueSnackbar('邮箱验证失败: ' + errorMessage, {variant: 'error'});
|
||||
} else {
|
||||
enqueueSnackbar('网络错误:' + e.message, {variant: 'error'});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (path === '/profile/resetPassword' || path === '/resetPassword') {
|
||||
appData.passwordReset = true;
|
||||
// setAppData(appData);
|
||||
}
|
||||
|
||||
if (appData.tokenValid) {
|
||||
return (
|
||||
<Container maxWidth={'lg'}>
|
||||
<User appData={appData} setAppData={setAppData}/>
|
||||
</Container>
|
||||
);
|
||||
} else if (appData.passwordReset) {
|
||||
return (
|
||||
<Container maxWidth={'lg'}>
|
||||
<PasswordReset appData={appData} setAppData={setAppData}/>
|
||||
</Container>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Container maxWidth={'lg'}>
|
||||
<Login appData={appData} setAppData={setAppData}/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023. Gardel <sunxinao@hotmail.com> and contributors
|
||||
* Copyright (C) 2023-2025. Gardel <sunxinao@hotmail.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -48,9 +48,29 @@ function Login(props: { appData: AppState, setAppData: React.Dispatch<React.SetS
|
||||
const {enqueueSnackbar} = useSnackbar();
|
||||
const {register, handleSubmit, formState: {errors}} = useForm<Inputs>();
|
||||
const [submitting, setSubmitting] = React.useState(false);
|
||||
const [submitText, setSubmitText] = React.useState('提交');
|
||||
React.useEffect(() => {
|
||||
if (submitting) {
|
||||
return
|
||||
}
|
||||
switch (appData.login) {
|
||||
case 'login':
|
||||
setSubmitText('登录');
|
||||
break;
|
||||
case 'register':
|
||||
setSubmitText('注册');
|
||||
break;
|
||||
case 'reset':
|
||||
setSubmitText('重置');
|
||||
break;
|
||||
default:
|
||||
setSubmitText('提交');
|
||||
break;
|
||||
}
|
||||
}, [appData, submitting])
|
||||
const onSubmit: SubmitHandler<Inputs> = data => {
|
||||
setSubmitting(true)
|
||||
if (appData.login) {
|
||||
setSubmitting(true);
|
||||
if (appData.login === 'login') {
|
||||
axios.post('/authserver/authenticate', {
|
||||
username: data.username,
|
||||
password: data.password
|
||||
@@ -80,7 +100,7 @@ function Login(props: { appData: AppState, setAppData: React.Dispatch<React.SetS
|
||||
}
|
||||
})
|
||||
.finally(() => setSubmitting(false))
|
||||
} else {
|
||||
} else if (appData.login === 'register') {
|
||||
axios.post('/authserver/register', {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
@@ -90,7 +110,7 @@ function Login(props: { appData: AppState, setAppData: React.Dispatch<React.SetS
|
||||
let data = response.data
|
||||
if (data && data.id) {
|
||||
enqueueSnackbar("注册成功,uuid:" + data.id, {variant: 'success'});
|
||||
setLogin(true)
|
||||
setLogin('login')
|
||||
} else {
|
||||
enqueueSnackbar(data && data.errorMessage ? "注册失败: " + data.errorMessage: "注册失败", {variant: 'error'});
|
||||
}
|
||||
@@ -98,7 +118,7 @@ function Login(props: { appData: AppState, setAppData: React.Dispatch<React.SetS
|
||||
.catch(e => {
|
||||
const response = e.response;
|
||||
if (response && response.data) {
|
||||
let errorMessage = response.data.errorMessage;
|
||||
let errorMessage = response.data.errorMessage ?? response.data;
|
||||
let message = "注册失败: " + errorMessage;
|
||||
if (errorMessage === "profileName exist") {
|
||||
message = "注册失败: 角色名已存在";
|
||||
@@ -111,6 +131,38 @@ function Login(props: { appData: AppState, setAppData: React.Dispatch<React.SetS
|
||||
}
|
||||
})
|
||||
.finally(() => setSubmitting(false))
|
||||
} else if (appData.login === 'reset') {
|
||||
const countdown = {
|
||||
timeout: 60,
|
||||
}
|
||||
setSubmitText(`${countdown.timeout}`);
|
||||
const timer = setInterval(() => {
|
||||
countdown.timeout = countdown.timeout - 1;
|
||||
if (countdown.timeout <= 0) {
|
||||
clearInterval(timer);
|
||||
setSubmitting(false);
|
||||
return
|
||||
}
|
||||
setSubmitting(true);
|
||||
setSubmitText(`${countdown.timeout}`);
|
||||
}, 1000);
|
||||
axios.post('/authserver/sendEmail', {
|
||||
email: data.username,
|
||||
emailType: "resetPassword"
|
||||
})
|
||||
.then(() => {
|
||||
enqueueSnackbar("重置链接发送成功,请检查垃圾邮箱", {variant: 'success'});
|
||||
})
|
||||
.catch(e => {
|
||||
const response = e.response;
|
||||
if (response && response.data) {
|
||||
let errorMessage = response.data.errorMessage ?? response.data;
|
||||
enqueueSnackbar("发送失败: " + errorMessage, {variant: 'error'});
|
||||
} else {
|
||||
enqueueSnackbar('网络错误:' + e.message, {variant: 'error'});
|
||||
}
|
||||
countdown.timeout = 0;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -122,7 +174,7 @@ function Login(props: { appData: AppState, setAppData: React.Dispatch<React.SetS
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const setLogin = (login: boolean) => setAppData((oldData: AppState) => {
|
||||
const setLogin = (login: string) => setAppData((oldData: AppState) => {
|
||||
return {
|
||||
...oldData,
|
||||
login
|
||||
@@ -146,19 +198,21 @@ function Login(props: { appData: AppState, setAppData: React.Dispatch<React.SetS
|
||||
required
|
||||
error={errors.username && true}
|
||||
type='email'
|
||||
inputProps={{
|
||||
...register('username', {required: true})
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
...register('username', {required: true})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Collapse in={!appData.login} className='profileName'>
|
||||
<FormControl fullWidth variant="filled" required={!appData.login} error={errors.profileName && true}>
|
||||
<Collapse in={appData.login === 'register'} className='profileName'>
|
||||
<FormControl fullWidth variant="filled" required={appData.login === 'register'} error={errors.profileName && true}>
|
||||
<InputLabel htmlFor="profileName-input">角色名</InputLabel>
|
||||
<FilledInput
|
||||
id="profileName-input"
|
||||
name="profileName"
|
||||
required={!appData.login}
|
||||
inputProps={appData.login ? {} : {
|
||||
required={appData.login === 'register'}
|
||||
inputProps={appData.login !== 'register' ? {} : {
|
||||
minLength: '2', maxLength: 16,
|
||||
...register('profileName', {required: true, minLength: 2, pattern: /^[a-zA-Z0-9_]{1,16}$/, maxLength: 16})
|
||||
}}
|
||||
@@ -166,13 +220,13 @@ function Login(props: { appData: AppState, setAppData: React.Dispatch<React.SetS
|
||||
<FocusedShowHelperText id="profileName-input-helper-text">字母,数字或下划线</FocusedShowHelperText>
|
||||
</FormControl>
|
||||
</Collapse>
|
||||
<div className='password'>
|
||||
<FormControl fullWidth variant="filled" required error={errors.password && true}>
|
||||
<Collapse in={appData.login !== 'reset'} className='password'>
|
||||
<FormControl fullWidth variant="filled" required={appData.login !== 'reset'} error={errors.password && true}>
|
||||
<InputLabel htmlFor="password-input">密码</InputLabel>
|
||||
<FilledInput
|
||||
id="password-input"
|
||||
name="password"
|
||||
required
|
||||
required={appData.login !== 'reset'}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
@@ -185,17 +239,19 @@ function Login(props: { appData: AppState, setAppData: React.Dispatch<React.SetS
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
inputProps={{
|
||||
inputProps={appData.login === 'reset' ? {} : {
|
||||
minLength: '6',
|
||||
...register('password', {required: true, minLength: 6})
|
||||
}}
|
||||
/>
|
||||
<FocusedShowHelperText id="password-input-helper-text">警告: 暂无重置密码接口,请妥善保管密码</FocusedShowHelperText>
|
||||
<FocusedShowHelperText id="password-input-helper-text">请妥善保管密码</FocusedShowHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
</Collapse>
|
||||
<div className='button-container'>
|
||||
<Button variant='contained' onClick={() => setLogin(!appData.login)} disabled={submitting}>{appData.login ? '注册' : '已有帐号登录'}</Button>
|
||||
<Button variant='contained' type='submit' disabled={submitting}>{appData.login ? '登录' : '注册'}</Button>
|
||||
{appData.login !== 'reset' && <Button variant='contained' onClick={() => setLogin('reset')}>忘记密码</Button>}
|
||||
{appData.login !== 'login' && <Button variant='contained' onClick={() => setLogin('login')}>已有账号登录</Button>}
|
||||
{appData.login !== 'register' && <Button variant='contained' onClick={() => setLogin('register')}>注册</Button>}
|
||||
<Button variant='contained' type='submit' disabled={submitting}>{submitText}</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
43
frontend/src/reset.css
Normal file
43
frontend/src/reset.css
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2025. Gardel <sunxinao@hotmail.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reset-card {
|
||||
padding: 14px 24px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.username,
|
||||
.password {
|
||||
display: block;
|
||||
width: 87%;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 87%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.button-container button {
|
||||
margin: 3px;
|
||||
}
|
155
frontend/src/reset.tsx
Normal file
155
frontend/src/reset.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2025. Gardel <sunxinao@hotmail.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
FilledInput,
|
||||
FormControl,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
Paper,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import {Visibility, VisibilityOff} from '@mui/icons-material';
|
||||
import {AppState} from './types';
|
||||
import './reset.css';
|
||||
import {SubmitHandler, useForm} from 'react-hook-form';
|
||||
import axios from 'axios';
|
||||
import {useSnackbar} from 'notistack';
|
||||
|
||||
type Inputs = {
|
||||
username: string,
|
||||
profileName: string,
|
||||
password: string
|
||||
};
|
||||
|
||||
function PasswordReset(props: { appData: AppState, setAppData: React.Dispatch<React.SetStateAction<AppState>> }) {
|
||||
const {appData, setAppData} = props;
|
||||
const {enqueueSnackbar} = useSnackbar();
|
||||
const {register, handleSubmit, formState: {errors}} = useForm<Inputs>();
|
||||
const [submitting, setSubmitting] = React.useState(false);
|
||||
const onSubmit: SubmitHandler<Inputs> = data => {
|
||||
setSubmitting(true);
|
||||
const hash = window.location.hash;
|
||||
if (!hash) {
|
||||
setSubmitting(false);
|
||||
enqueueSnackbar('链接失效,请重新打开', {variant: 'error'});
|
||||
return;
|
||||
}
|
||||
const params = new URLSearchParams(hash.substring(1));
|
||||
axios.post('/authserver/resetPassword', {
|
||||
email: data.username,
|
||||
password: data.password,
|
||||
accessToken: params.get('passwordResetToken'),
|
||||
})
|
||||
.then(() => {
|
||||
toLogin();
|
||||
window.location.replace('/profile/')
|
||||
enqueueSnackbar("重置成功", {variant: 'success'});
|
||||
})
|
||||
.catch(e => {
|
||||
const response = e.response;
|
||||
if (response && response.status >= 400 && response.status < 500) {
|
||||
let errorMessage = response.data.errorMessage ?? response.data;
|
||||
enqueueSnackbar('重置失败: ' + errorMessage, {variant: 'error'});
|
||||
} else {
|
||||
enqueueSnackbar('网络错误:' + e.message, {variant: 'error'});
|
||||
}
|
||||
})
|
||||
.finally(() => setSubmitting(false))
|
||||
};
|
||||
|
||||
const [showPassword, setShowPassword] = React.useState(false);
|
||||
|
||||
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||
|
||||
const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const toLogin = () => {
|
||||
setAppData({
|
||||
...appData,
|
||||
passwordReset: false
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Container maxWidth={'sm'}>
|
||||
<Paper className={'reset-card'}>
|
||||
<section className="header">
|
||||
<h1>简陋重置密码页</h1>
|
||||
</section>
|
||||
<Box component="form" autoComplete="off" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className='username'>
|
||||
<TextField
|
||||
id="username-input"
|
||||
name='username'
|
||||
fullWidth
|
||||
label="邮箱"
|
||||
variant="filled"
|
||||
required
|
||||
error={errors.username && true}
|
||||
type='email'
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
...register('username', {required: true})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='password'>
|
||||
<FormControl fullWidth variant="filled" required error={errors.password && true}>
|
||||
<InputLabel htmlFor="password-input">新密码</InputLabel>
|
||||
<FilledInput
|
||||
id="password-input"
|
||||
name="password"
|
||||
required
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="显示密码"
|
||||
onClick={handleClickShowPassword}
|
||||
onMouseDown={handleMouseDownPassword}
|
||||
edge="end">
|
||||
{showPassword ? <VisibilityOff/> : <Visibility/>}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
inputProps={{
|
||||
minLength: '6',
|
||||
...register('password', {required: true, minLength: 6})
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className='button-container'>
|
||||
<Button variant='contained' onClick={toLogin}>登录</Button>
|
||||
<Button variant='contained' type='submit' disabled={submitting}>重置</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default PasswordReset;
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023. Gardel <sunxinao@hotmail.com> and contributors
|
||||
* Copyright (C) 2023-2025. Gardel <sunxinao@hotmail.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -21,7 +21,7 @@ import {Canvas, RootState, useFrame, useLoader} from '@react-three/fiber';
|
||||
import React from 'react';
|
||||
import createPlayerModel from './utils';
|
||||
import {OrbitControls} from '@react-three/drei';
|
||||
import {EffectComposer, Vignette, SMAA, SSAO, SSR} from '@react-three/postprocessing';
|
||||
import {EffectComposer, SSAO} from '@react-three/postprocessing';
|
||||
import {BlendFunction} from 'postprocessing';
|
||||
|
||||
function PlayerModel(props: { skinUrl: string, capeUrl?: string, slim?: boolean }) {
|
||||
@@ -84,7 +84,7 @@ function SkinRender(props: { skinUrl: string, capeUrl?: string, slim?: boolean }
|
||||
rangeFalloff={0.1}
|
||||
luminanceInfluence={0.9}
|
||||
radius={20}
|
||||
scale={0.5}
|
||||
resolutionScale={0.5}
|
||||
bias={0.5}
|
||||
/>
|
||||
</EffectComposer>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023. Gardel <sunxinao@hotmail.com> and contributors
|
||||
* Copyright (C) 2023-2025. Gardel <sunxinao@hotmail.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -99,7 +99,7 @@ function createCube(texture: THREE.Texture, width: number, height: number, depth
|
||||
}
|
||||
|
||||
|
||||
export default function createPlayerModel(skinTexture: THREE.Texture, capeTexture: THREE.Texture | null | undefined, v: number, slim: boolean = false, capeType?: string): THREE.Object3D<THREE.Event> {
|
||||
export default function createPlayerModel(skinTexture: THREE.Texture, capeTexture: THREE.Texture | null | undefined, v: number, slim: boolean = false, capeType?: string): THREE.Object3D<THREE.Object3DEventMap> {
|
||||
let headGroup = new THREE.Object3D();
|
||||
headGroup.name = 'headGroup';
|
||||
headGroup.position.x = 0;
|
||||
|
5
frontend/src/types.d.ts
vendored
5
frontend/src/types.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023. Gardel <sunxinao@hotmail.com> and contributors
|
||||
* Copyright (C) 2023-2025. Gardel <sunxinao@hotmail.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -16,10 +16,11 @@
|
||||
*/
|
||||
|
||||
export type AppState = {
|
||||
login: boolean
|
||||
login: string
|
||||
accessToken: string
|
||||
tokenValid: boolean
|
||||
loginTime: number
|
||||
profileName: string
|
||||
uuid: string
|
||||
passwordReset: boolean
|
||||
}
|
2699
frontend/yarn.lock
2699
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user