上传皮肤
This commit is contained in:
parent
6c2080f53a
commit
2363283cc5
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<title>皮肤站</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
@ -18,6 +18,7 @@
|
||||
"ahooks": "^3.7.8",
|
||||
"immer": "^10.0.2",
|
||||
"jotai": "^2.4.2",
|
||||
"mui-file-input": "^3.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
|
32
frontend/pnpm-lock.yaml
generated
32
frontend/pnpm-lock.yaml
generated
@ -29,6 +29,9 @@ dependencies:
|
||||
jotai:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2(@types/react@18.2.21)(react@18.2.0)
|
||||
mui-file-input:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/icons-material@5.14.9)(@mui/material@5.14.10)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0)
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
@ -1821,6 +1824,30 @@ packages:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: true
|
||||
|
||||
/mui-file-input@3.0.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/icons-material@5.14.9)(@mui/material@5.14.10)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-58Jp3+f5MUXjhZjlLfYFOEOBICnLoFC4x0G1+y411JsGBjZQ1lgICv7KQVgP5aF+IRvhJ1vfI6KpbnmqwRKXoA==}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@mui/icons-material': ^5.0.0
|
||||
'@mui/material': ^5.0.0
|
||||
'@types/react': ^18.0.0
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@emotion/react': 11.11.1(@types/react@18.2.21)(react@18.2.0)
|
||||
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.21)(react@18.2.0)
|
||||
'@mui/icons-material': 5.14.9(@mui/material@5.14.10)(@types/react@18.2.21)(react@18.2.0)
|
||||
'@mui/material': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@types/react': 18.2.21
|
||||
pretty-bytes: 6.1.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/nanoid@3.3.6:
|
||||
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@ -1930,6 +1957,11 @@ packages:
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dev: true
|
||||
|
||||
/pretty-bytes@6.1.1:
|
||||
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
|
||||
engines: {node: ^14.13.1 || >=16.0.0}
|
||||
dev: false
|
||||
|
||||
/prop-types@15.8.1:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
dependencies:
|
||||
|
16
frontend/src/hooks/useTitle.ts
Normal file
16
frontend/src/hooks/useTitle.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { serverInfo } from '@/apis/apis'
|
||||
import { useTitle as auseTitle, useRequest } from 'ahooks'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function useTitle(title: string) {
|
||||
const { data, error } = useRequest(serverInfo, {
|
||||
cacheKey: "/api/yggdrasil",
|
||||
staleTime: 60000,
|
||||
})
|
||||
useEffect(() => {
|
||||
error && console.warn(error)
|
||||
}, [error])
|
||||
auseTitle(title + " - " + data?.meta.serverName ?? "", {
|
||||
restoreOnUnmount: true
|
||||
})
|
||||
}
|
@ -19,7 +19,7 @@ import { AccountCircle } from '@mui/icons-material';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { LayoutAlertErr, token, user } from '@/store/store';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import Button from '@mui/material/Button';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useRequest, useMemoizedFn } from 'ahooks';
|
||||
@ -37,6 +37,7 @@ import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import useTilg from 'tilg'
|
||||
|
||||
const drawerWidth = 240;
|
||||
const DrawerOpen = atom(false)
|
||||
|
||||
const DrawerHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -53,51 +54,9 @@ interface ListItem {
|
||||
link: string
|
||||
}
|
||||
|
||||
|
||||
const Layout = memo(function Layout() {
|
||||
const theme = useTheme();
|
||||
const isLg = useMediaQuery(theme.breakpoints.up('lg'))
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const nowToken = useAtomValue(token)
|
||||
const [err, setErr] = useAtom(LayoutAlertErr)
|
||||
const navigate = useNavigate();
|
||||
|
||||
const userinfo = useRequest(() => userInfo(nowToken), {
|
||||
refreshDeps: [nowToken],
|
||||
cacheKey: "/api/v1/user",
|
||||
onError: e => {
|
||||
if (e instanceof ApiErr && e.code == 5) {
|
||||
navigate("/login")
|
||||
}
|
||||
console.warn(e)
|
||||
setErr(String(e))
|
||||
}
|
||||
})
|
||||
|
||||
const userDrawerList = React.useMemo(() => [
|
||||
{
|
||||
icon: <PersonIcon />,
|
||||
title: '个人信息',
|
||||
link: '/profile'
|
||||
},
|
||||
{
|
||||
icon: <SettingsIcon />,
|
||||
title: '皮肤设置',
|
||||
link: '/textures'
|
||||
},
|
||||
{
|
||||
icon: <SecurityIcon />,
|
||||
title: '安全设置',
|
||||
link: '/setting'
|
||||
}
|
||||
] as ListItem[], [])
|
||||
|
||||
const adminDrawerList = React.useMemo(() => [
|
||||
{
|
||||
icon: <PersonIcon />,
|
||||
title: 'test'
|
||||
}
|
||||
] as ListItem[], [])
|
||||
|
||||
useTilg()
|
||||
|
||||
@ -108,37 +67,9 @@ const Layout = memo(function Layout() {
|
||||
zIndex: { lg: theme.zIndex.drawer + 1 }
|
||||
}}
|
||||
>
|
||||
<MyToolbar setOpen={setOpen}></MyToolbar>
|
||||
<MyToolbar />
|
||||
</AppBar>
|
||||
{userinfo.data && (
|
||||
<Drawer
|
||||
sx={{
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: drawerWidth,
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
}}
|
||||
variant={isLg ? "persistent" : "temporary"}
|
||||
anchor="left"
|
||||
open={open || isLg}
|
||||
onClose={() => setOpen(false)}
|
||||
>
|
||||
<DrawerHeader>
|
||||
<IconButton onClick={() => setOpen(false)}>
|
||||
{theme.direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
|
||||
</IconButton>
|
||||
</DrawerHeader>
|
||||
<Divider />
|
||||
<MyList list={userDrawerList} />
|
||||
{userinfo.data?.is_admin && (
|
||||
<>
|
||||
<Divider />
|
||||
<MyList list={adminDrawerList} />
|
||||
</>)}
|
||||
</Drawer>
|
||||
)}
|
||||
<MyDrawer />
|
||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={err != ""} onClose={() => setErr("")} >
|
||||
<Alert onClose={() => setErr("")} severity="error">{err}</Alert>
|
||||
</Snackbar>
|
||||
@ -157,15 +88,17 @@ const Layout = memo(function Layout() {
|
||||
</>)
|
||||
})
|
||||
|
||||
const MyToolbar = memo(function MyToolbar(p: { setOpen: (v: boolean) => void }) {
|
||||
const MyToolbar = memo(function MyToolbar() {
|
||||
const [nowUser, setNowUser] = useAtom(user)
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const navigate = useNavigate();
|
||||
const [, setToken] = useAtom(token)
|
||||
const setErr = useSetAtom(LayoutAlertErr)
|
||||
const setOpen = useSetAtom(DrawerOpen)
|
||||
|
||||
const server = useRequest(serverInfo, {
|
||||
cacheKey: "/api/yggdrasil",
|
||||
staleTime: 60000,
|
||||
onError: e => {
|
||||
console.warn(e)
|
||||
setErr(String(e))
|
||||
@ -180,6 +113,8 @@ const MyToolbar = memo(function MyToolbar(p: { setOpen: (v: boolean) => void })
|
||||
navigate("/")
|
||||
})
|
||||
|
||||
useTilg()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar>
|
||||
@ -190,7 +125,7 @@ const MyToolbar = memo(function MyToolbar(p: { setOpen: (v: boolean) => void })
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
sx={{ mr: 2, display: { lg: 'none' } }}
|
||||
onClick={() => p.setOpen(true)}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton >
|
||||
@ -270,4 +205,85 @@ const MyListItem = function MyListItem(p: ListItem) {
|
||||
)
|
||||
}
|
||||
|
||||
const MyDrawer = function MyDrawer() {
|
||||
const nowToken = useAtomValue(token)
|
||||
const setErr = useSetAtom(LayoutAlertErr)
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const isLg = useMediaQuery(theme.breakpoints.up('lg'))
|
||||
const [open, setOpen] = useAtom(DrawerOpen)
|
||||
|
||||
const userinfo = useRequest(() => userInfo(nowToken), {
|
||||
refreshDeps: [nowToken],
|
||||
cacheKey: "/api/v1/user" + nowToken,
|
||||
staleTime: 60000,
|
||||
onError: e => {
|
||||
if (e instanceof ApiErr && e.code == 5) {
|
||||
navigate("/login")
|
||||
}
|
||||
console.warn(e)
|
||||
setErr(String(e))
|
||||
},
|
||||
})
|
||||
|
||||
const userDrawerList = React.useMemo(() => [
|
||||
{
|
||||
icon: <PersonIcon />,
|
||||
title: '个人信息',
|
||||
link: '/profile'
|
||||
},
|
||||
{
|
||||
icon: <SettingsIcon />,
|
||||
title: '皮肤设置',
|
||||
link: '/textures'
|
||||
},
|
||||
{
|
||||
icon: <SecurityIcon />,
|
||||
title: '安全设置',
|
||||
link: '/setting'
|
||||
}
|
||||
] as ListItem[], [])
|
||||
|
||||
const adminDrawerList = React.useMemo(() => [
|
||||
{
|
||||
icon: <PersonIcon />,
|
||||
title: 'test'
|
||||
}
|
||||
] as ListItem[], [])
|
||||
|
||||
useTilg()
|
||||
|
||||
return (<>
|
||||
{userinfo.data && (
|
||||
<Drawer
|
||||
sx={{
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: drawerWidth,
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
}}
|
||||
variant={isLg ? "persistent" : "temporary"}
|
||||
anchor="left"
|
||||
open={open || isLg}
|
||||
onClose={() => setOpen(false)}
|
||||
>
|
||||
<DrawerHeader>
|
||||
<IconButton onClick={() => setOpen(false)}>
|
||||
{theme.direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
|
||||
</IconButton>
|
||||
</DrawerHeader>
|
||||
<Divider />
|
||||
<MyList list={userDrawerList} />
|
||||
{userinfo.data?.is_admin && (
|
||||
<>
|
||||
<Divider />
|
||||
<MyList list={adminDrawerList} />
|
||||
</>)}
|
||||
</Drawer>
|
||||
)}
|
||||
</>)
|
||||
}
|
||||
|
||||
export default Layout
|
@ -17,6 +17,7 @@ import { login } from '@/apis/apis'
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
import Loading from '@/components/Loading'
|
||||
import CheckInput, { refType } from '@/components/CheckInput'
|
||||
import useTitle from '@/hooks/useTitle';
|
||||
|
||||
|
||||
|
||||
@ -27,6 +28,7 @@ export default function SignIn() {
|
||||
const setUserInfo = useSetAtom(user)
|
||||
const checkList = React.useRef<Map<string, refType>>(new Map<string, refType>())
|
||||
const navigate = useNavigate();
|
||||
useTitle("登录")
|
||||
|
||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
@ -50,7 +52,7 @@ export default function SignIn() {
|
||||
uuid: v.selectedProfile.uuid,
|
||||
name: v.selectedProfile.name,
|
||||
})
|
||||
navigate("/")
|
||||
navigate("/profile")
|
||||
}).
|
||||
catch(v => [setErr(String(v)), console.warn(v)]).
|
||||
finally(() => setLoading(false))
|
||||
|
@ -18,6 +18,7 @@ import Loading from '@/components/Loading'
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import CaptchaWidget from '@/components/CaptchaWidget';
|
||||
import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget'
|
||||
import useTitle from '@/hooks/useTitle';
|
||||
|
||||
export default function SignUp() {
|
||||
const [regErr, setRegErr] = useState("");
|
||||
@ -25,6 +26,7 @@ export default function SignUp() {
|
||||
const [captchaToken, setCaptchaToken] = useState("");
|
||||
const captchaRef = useRef<CaptchaWidgetRef>(null)
|
||||
const [loading, setLoading] = useState(false);
|
||||
useTitle("注册")
|
||||
|
||||
|
||||
const checkList = React.useRef<Map<string, refType>>(new Map<string, refType>())
|
||||
|
@ -4,30 +4,34 @@ import CardContent from '@mui/material/CardContent';
|
||||
import Button from '@mui/material/Button';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CardHeader from '@mui/material/CardHeader';
|
||||
import { useHover, useRequest } from 'ahooks';
|
||||
import { useHover, useRequest, useUnmount } from 'ahooks';
|
||||
import { ApiErr } from '@/apis/error';
|
||||
import { LayoutAlertErr, token } from '@/store/store';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { userInfo, yggProfile } from '@/apis/apis';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Box from '@mui/material/Box';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { decodeSkin } from '@/utils/skin';
|
||||
import ReactSkinview3d from "react-skinview3d"
|
||||
import type { ReactSkinview3dOptions } from "react-skinview3d"
|
||||
import { WalkingAnimation } from "skinview3d"
|
||||
import type { SkinViewer } from "skinview3d"
|
||||
import Skeleton from '@mui/material/Skeleton';
|
||||
import useTilg from 'tilg';
|
||||
import useTitle from '@/hooks/useTitle';
|
||||
|
||||
const Profile = memo(function Profile() {
|
||||
const Profile = function Profile() {
|
||||
const nowToken = useAtomValue(token)
|
||||
const navigate = useNavigate();
|
||||
const setErr = useSetAtom(LayoutAlertErr)
|
||||
const [textures, setTextures] = useState({ skin: "", cape: "", model: "default" })
|
||||
useTitle("个人信息")
|
||||
|
||||
const userinfo = useRequest(() => userInfo(nowToken), {
|
||||
refreshDeps: [nowToken],
|
||||
cacheKey: "/api/v1/user",
|
||||
cacheKey: "/api/v1/user" + nowToken,
|
||||
staleTime: 60000,
|
||||
onError: e => {
|
||||
if (e instanceof ApiErr && e.code == 5) {
|
||||
navigate("/login")
|
||||
@ -53,6 +57,7 @@ const Profile = memo(function Profile() {
|
||||
}, [SkinInfo.data])
|
||||
|
||||
|
||||
useTilg()
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -106,10 +111,10 @@ const Profile = memo(function Profile() {
|
||||
</Box >
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const MySkin = memo(function MySkin(p: ReactSkinview3dOptions) {
|
||||
const MySkin = function MySkin(p: ReactSkinview3dOptions) {
|
||||
const refSkinview3d = useRef(null);
|
||||
const skinisHovering = useHover(refSkinview3d);
|
||||
const skinview3dView = useRef<SkinViewer | null>(null);
|
||||
@ -123,13 +128,17 @@ const MySkin = memo(function MySkin(p: ReactSkinview3dOptions) {
|
||||
}
|
||||
}, [skinisHovering])
|
||||
|
||||
useUnmount(() => {
|
||||
skinview3dView.current?.dispose()
|
||||
})
|
||||
|
||||
return <div ref={refSkinview3d}>
|
||||
<ReactSkinview3d
|
||||
{...p}
|
||||
onReady={v => [v.viewer.animation = new WalkingAnimation(), v.viewer.autoRotate = true, skinview3dView.current = v.viewer]}
|
||||
/>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
|
||||
function getYggRoot() {
|
||||
const u = new URL((import.meta.env.VITE_APIADDR ?? location.origin) + "/api/yggdrasil")
|
||||
|
@ -1,9 +1,121 @@
|
||||
import { memo } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Card from '@mui/material/Card';
|
||||
import CardActions from '@mui/material/CardActions';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import FormLabel from "@mui/material/FormLabel";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Radio from "@mui/material/Radio";
|
||||
import RadioGroup from "@mui/material/RadioGroup";
|
||||
import Button from "@mui/material/Button";
|
||||
import { CardHeader } from "@mui/material";
|
||||
import useTilg from "tilg";
|
||||
import useTitle from "@/hooks/useTitle";
|
||||
import { MuiFileInput } from 'mui-file-input'
|
||||
import Box from "@mui/material/Box";
|
||||
import ReactSkinview3d from "react-skinview3d";
|
||||
import { useUnmount } from "ahooks";
|
||||
import { SkinViewer } from "skinview3d";
|
||||
|
||||
const Textures = function Textures() {
|
||||
const [redioValue, setRedioValue] = useState("skin")
|
||||
useTitle("上传皮肤")
|
||||
const [file, setFile] = useState<File | null>(null)
|
||||
const skin = useRef({
|
||||
skinUrl: "",
|
||||
capeUrl: "",
|
||||
})
|
||||
const skinview3dView = useRef<SkinViewer | null>(null);
|
||||
|
||||
useUnmount(() => {
|
||||
skin.current.skinUrl && URL.revokeObjectURL(skin.current.skinUrl)
|
||||
skin.current.capeUrl && URL.revokeObjectURL(skin.current.capeUrl)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (file) {
|
||||
const nu = URL.createObjectURL(file)
|
||||
skin.current.skinUrl && URL.revokeObjectURL(skin.current.skinUrl)
|
||||
skin.current.capeUrl && URL.revokeObjectURL(skin.current.capeUrl)
|
||||
switch (redioValue) {
|
||||
case "skin":
|
||||
skin.current.skinUrl = nu
|
||||
skinview3dView.current?.loadSkin(nu, { model: "default" }).then(() =>
|
||||
skinview3dView.current?.loadSkin(nu, { model: "default" })
|
||||
)
|
||||
break
|
||||
case "slim":
|
||||
skin.current.skinUrl = nu
|
||||
skinview3dView.current?.loadSkin(nu, { model: "slim" }).then(() =>
|
||||
skinview3dView.current?.loadSkin(nu, { model: "slim" })
|
||||
)
|
||||
break
|
||||
case "cape":
|
||||
skin.current.capeUrl = nu
|
||||
skinview3dView.current?.loadCape(nu).then(() => {
|
||||
skinview3dView.current?.loadCape(nu)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [file, redioValue])
|
||||
|
||||
useEffect(() => {
|
||||
skinview3dView.current?.render()
|
||||
return skinview3dView.current?.dispose
|
||||
}, [])
|
||||
|
||||
const onRadioChange = (_a: React.ChangeEvent<HTMLInputElement>, value: string) => {
|
||||
setRedioValue(value)
|
||||
}
|
||||
const handleChange = (newFile: File | null) => {
|
||||
setFile(newFile)
|
||||
}
|
||||
|
||||
useTilg()
|
||||
|
||||
const Textures = memo(function Textures() {
|
||||
return (<>
|
||||
<p>修改皮肤</p>
|
||||
<Box sx={{
|
||||
display: "grid", gap: "1em", gridTemplateAreas: {
|
||||
lg: '"a b" ". b"',
|
||||
xs: '"a" "b"'
|
||||
}, gridTemplateColumns: { lg: "1fr 1fr" }
|
||||
}}>
|
||||
<Card sx={{ gridArea: "a" }}>
|
||||
<CardHeader title="设置皮肤" />
|
||||
<CardContent>
|
||||
<FormControl>
|
||||
<FormLabel>类型</FormLabel>
|
||||
<RadioGroup
|
||||
row
|
||||
onChange={onRadioChange}
|
||||
value={redioValue}
|
||||
>
|
||||
<FormControlLabel value="skin" control={<Radio />} label="Steve" />
|
||||
<FormControlLabel value="slim" control={<Radio />} label="Alex" />
|
||||
<FormControlLabel value="cape" control={<Radio />} label="披风" />
|
||||
</RadioGroup>
|
||||
<br />
|
||||
<MuiFileInput label="选择文件" value={file} inputProps={{ accept: 'image/png' }} onChange={handleChange} />
|
||||
</FormControl>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button variant="contained" sx={{ maxWidth: "3em" }}>上传</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
<Card sx={{ gridArea: "b" }}>
|
||||
<CardHeader title="预览" />
|
||||
<CardContent>
|
||||
{file && <ReactSkinview3d
|
||||
skinUrl={""}
|
||||
capeUrl={""}
|
||||
height="250"
|
||||
width="250"
|
||||
onReady={v => skinview3dView.current = v.viewer}
|
||||
/>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</>)
|
||||
})
|
||||
}
|
||||
|
||||
export default Textures
|
@ -64,7 +64,7 @@ func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, c
|
||||
ut, err := client.UserToken.Query().Where(usertoken.HasUserWith(user.ID(claims.UID))).First(ctx)
|
||||
if err != nil {
|
||||
var ne *ent.NotFoundError
|
||||
if !errors.As(err, &ne) {
|
||||
if errors.As(err, &ne) {
|
||||
return 0, errors.Join(err, ErrTokenInvalid)
|
||||
}
|
||||
return 0, err
|
||||
|
Loading…
x
Reference in New Issue
Block a user