refactor(directory): add eslint
This commit is contained in:
15
.eslintrc.cjs
Normal file
15
.eslintrc.cjs
Normal file
@@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
'eslint:recommended',
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function CharIcon(props) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox={props.viewBox}>
|
||||
@@ -8,6 +11,10 @@ export default function CharIcon(props) {
|
||||
<path d="M90.4 50.6l-39.8-23.5v-4c0-4.5-5-6.5-5-6.5a5.4 5.4 0 012.2-10.1c2.7 0 5.3 1.5 5.5 4.8.4 5.3 6.4 3.9 6.4-.3a11.7 11.7 0 00-12-11c-9 0-11.6 8.8-11.6 11.6a11.5 11.5 0 001.6 6.2c2.2 3.8 6.6 4.3 6.6 6.8v2.5L4.2 50.7c-4 2.3-4.7 7.3-3.8 10.3a9.1 9.1 0 009.1 6.4h75.2c5.9 0 8.6-3.4 9.5-6.3C95 58.1 95 53.4 90.4 50.6Zm-5.6 10.3h-75.2c-2.4.1-4-3.3-1.5-4.8l39.2-22.9 39 22.8A2.7 2.7 0 0184.7 60.8Z" />
|
||||
}
|
||||
</svg>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
CharIcon.propTypes = {
|
||||
viewBox: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
import React, {
|
||||
useState
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
import './dropdown.css'
|
||||
|
||||
export default function Dropdown(props) {
|
||||
@@ -46,4 +47,12 @@ export default function Dropdown(props) {
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
Dropdown.propTypes = {
|
||||
className: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
menu: PropTypes.array,
|
||||
onClick: PropTypes.func,
|
||||
activeColor: PropTypes.object,
|
||||
activeRule: PropTypes.func,
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './main_border.css';
|
||||
|
||||
export default function MainBorder(props) {
|
||||
@@ -6,4 +8,7 @@ export default function MainBorder(props) {
|
||||
{props.children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
MainBorder.propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
@@ -1,40 +1,42 @@
|
||||
import {
|
||||
import React, {
|
||||
useState,
|
||||
useCallback
|
||||
} from 'react'
|
||||
import './popup.css'
|
||||
import ReturnButton from '@/component/return_button';
|
||||
import MainBorder from '@/component/main_border';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function Popup(props) {
|
||||
const [hidden, setHidden] = useState(true)
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
const toggle = () => {
|
||||
setHidden(!hidden)
|
||||
}, [hidden])
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={`popup ${hidden ? '' : 'active'}`}>
|
||||
<section className='wrapper'>
|
||||
<section className='title'>
|
||||
<section className="text">{props.title}</section>
|
||||
<ReturnButton onClick={toggle} className="return-button"/>
|
||||
</section>
|
||||
<MainBorder/>
|
||||
<section className='content'>
|
||||
{props.children}
|
||||
</section>
|
||||
return (<>
|
||||
<section className={`popup ${hidden ? '' : 'active'}`}>
|
||||
<section className='wrapper'>
|
||||
<section className='title'>
|
||||
<section className="text">{props.title}</section>
|
||||
<ReturnButton onClick={toggle} className="return-button" />
|
||||
</section>
|
||||
<MainBorder />
|
||||
<section className='content'>
|
||||
{props.children}
|
||||
</section>
|
||||
<section className={`overlay ${hidden ? '' : 'active'}`}
|
||||
onClick={() => toggle()} />
|
||||
</section>
|
||||
<span
|
||||
className="popup-text"
|
||||
onClick={toggle}
|
||||
>
|
||||
{props.title}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<section className={`overlay ${hidden ? '' : 'active'}`}
|
||||
onClick={() => toggle()} />
|
||||
</section>
|
||||
<span
|
||||
className="popup-text"
|
||||
onClick={toggle}
|
||||
>
|
||||
{props.title}
|
||||
</span>
|
||||
</>)
|
||||
}
|
||||
Popup.propTypes = {
|
||||
title: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './return_button.css'
|
||||
|
||||
export default function ReturnButton(props) {
|
||||
@@ -30,4 +32,7 @@ export default function ReturnButton(props) {
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
ReturnButton.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
@@ -1,8 +1,13 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './switch.css';
|
||||
import {
|
||||
useI18n
|
||||
} from '@/state/language'
|
||||
|
||||
export default function Switch(props) {
|
||||
const [on, setOn] = useState(props.on)
|
||||
const { i18n } = useI18n()
|
||||
|
||||
useEffect(() => {
|
||||
setOn(props.on)
|
||||
@@ -13,11 +18,16 @@ export default function Switch(props) {
|
||||
className={`switch ${on ? 'active' : ''}`}
|
||||
onClick={() => props.handleOnClick()}
|
||||
>
|
||||
<span className='text'>{props.text}</span>
|
||||
<span className='text'>{i18n(props.text)}</span>
|
||||
<section className='icon-wrapper'>
|
||||
<span className='icon-line'></span>
|
||||
<span className='icon'></span>
|
||||
</section>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
Switch.propTypes = {
|
||||
on: PropTypes.bool,
|
||||
text: PropTypes.string,
|
||||
handleOnClick: PropTypes.func,
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react";
|
||||
import {
|
||||
useNavigate,
|
||||
useRouteError
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react";
|
||||
import Home from "@/routes/path/home";
|
||||
import Operator from "@/routes/path/operator";
|
||||
import Changelogs from "@/routes/path/changelogs";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo
|
||||
@@ -9,7 +9,8 @@ import { useAppbar } from '@/state/appbar';
|
||||
import useUmami from '@parcellab/react-use-umami'
|
||||
import MainBorder from '@/component/main_border';
|
||||
|
||||
export default function Changelogs(props) {
|
||||
export default function Changelogs() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const _trackEvt = useUmami('/changelogs')
|
||||
const {
|
||||
setTitle,
|
||||
@@ -29,7 +30,7 @@ export default function Changelogs(props) {
|
||||
fetch('/_assets/changelogs.json').then(res => res.json()).then(data => {
|
||||
setChangelogs(data)
|
||||
})
|
||||
}, [])
|
||||
}, [setExtraArea, setHeaderIcon, setTitle])
|
||||
|
||||
useEffect(() => {
|
||||
setTabs(changelogs.map((item) => {
|
||||
@@ -37,7 +38,7 @@ export default function Changelogs(props) {
|
||||
key: item[0].key
|
||||
}
|
||||
}))
|
||||
}, [changelogs])
|
||||
}, [changelogs, setTabs])
|
||||
|
||||
const content = useMemo(() => {
|
||||
return (
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import {
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
NavLink,
|
||||
} from "react-router-dom";
|
||||
import './home.css'
|
||||
import { useConfig } from '@/state/config';
|
||||
import {
|
||||
useLanguage,
|
||||
useI18n
|
||||
useLanguage
|
||||
} from '@/state/language'
|
||||
import { useHeader } from '@/state/header';
|
||||
import { useAppbar } from '@/state/appbar';
|
||||
@@ -58,6 +58,7 @@ const playVoice = (link) => {
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const _trackEvt = useUmami('/')
|
||||
const {
|
||||
setTitle,
|
||||
@@ -78,7 +79,7 @@ export default function Home() {
|
||||
key: 'skin'
|
||||
}])
|
||||
setHeaderIcon(null)
|
||||
}, [])
|
||||
}, [setHeaderIcon, setTabs, setTitle])
|
||||
|
||||
useEffect(() => {
|
||||
setContent(config?.operators || [])
|
||||
@@ -157,7 +158,7 @@ function OperatorElement({ item, hidden }) {
|
||||
</section>
|
||||
</NavLink>
|
||||
)
|
||||
}, [language, alternateLang, hidden])
|
||||
}, [item, hidden, language, alternateLang, textDefaultLang])
|
||||
}
|
||||
|
||||
function VoiceSwitchElement() {
|
||||
@@ -165,28 +166,19 @@ function VoiceSwitchElement() {
|
||||
const {
|
||||
setExtraArea,
|
||||
} = useAppbar()
|
||||
const { i18n } = useI18n()
|
||||
|
||||
const toggleVoice = useCallback(() => {
|
||||
setVoiceOn(!voiceOn)
|
||||
}, [voiceOn])
|
||||
|
||||
const appbarSwitch = useMemo(() => {
|
||||
return [
|
||||
useEffect(() => {
|
||||
setExtraArea([
|
||||
(
|
||||
<Switch
|
||||
key="voice"
|
||||
text={i18n('voice')}
|
||||
text='voice'
|
||||
on={voiceOn}
|
||||
handleOnClick={() => toggleVoice()}
|
||||
handleOnClick={() => setVoiceOn(!voiceOn)}
|
||||
/>
|
||||
)
|
||||
]
|
||||
}, [voiceOn])
|
||||
|
||||
useEffect(() => {
|
||||
setExtraArea(appbarSwitch)
|
||||
}, [voiceOn])
|
||||
])
|
||||
}, [voiceOn, setExtraArea, setVoiceOn])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -194,4 +186,9 @@ function VoiceSwitchElement() {
|
||||
function ImageElement({ item }) {
|
||||
const { language } = useLanguage()
|
||||
return <img src={`/${item.link}/assets/${item.fallback_name.replace("#", "%23")}_portrait.png`} alt={item.codename[language]} />
|
||||
}
|
||||
ImageElement.propTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
fallback_name: PropTypes.string,
|
||||
codename: PropTypes.object,
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import {
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useCallback
|
||||
useCallback,
|
||||
useMemo
|
||||
} from 'react'
|
||||
import {
|
||||
useParams,
|
||||
@@ -33,7 +34,17 @@ const spinePlayerAtom = atom(null);
|
||||
const spineAnimationAtom = atom("Idle");
|
||||
const audioEl = new Audio()
|
||||
|
||||
export default function Operator(props) {
|
||||
const getTabName = (item, language) => {
|
||||
if (item.type === 'operator') {
|
||||
return 'operator'
|
||||
} else {
|
||||
return item.codename[language].replace(/^(.+)( )(·|\/)()(.+)$/, '$1')
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fix subtitle show/hide, fix voice play/pause when change route
|
||||
|
||||
export default function Operator() {
|
||||
const navigate = useNavigate()
|
||||
const { operators } = useConfig()
|
||||
const { language } = useLanguage()
|
||||
@@ -46,13 +57,15 @@ export default function Operator(props) {
|
||||
} = useHeader()
|
||||
const [config, setConfig] = useAtom(configAtom)
|
||||
const [spineData, setSpineData] = useState(null)
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const _trackEvt = useUmami(`/${key}`)
|
||||
const spineRef = useRef(null)
|
||||
const [spineAnimation, setSpineAnimation] = useAtom(spineAnimationAtom)
|
||||
const { i18n } = useI18n()
|
||||
const [spinePlayer, setSpinePlayer] = useAtom(spinePlayerAtom)
|
||||
const [voiceLang, setVoiceLang] = useState(null)
|
||||
const { backgrounds, currentBackground, setCurrentBackground } = useBackgrounds()
|
||||
const { backgrounds } = useBackgrounds()
|
||||
const [currentBackground, setCurrentBackground] = useState(null)
|
||||
const [voiceConfig, setVoiceConfig] = useState(null)
|
||||
const [subtitleLang, setSubtitleLang] = useState(null)
|
||||
const [subtitle, setSubtitle] = useState(null)
|
||||
@@ -63,7 +76,11 @@ export default function Operator(props) {
|
||||
|
||||
useEffect(() => {
|
||||
setAppbarExtraArea([])
|
||||
}, [])
|
||||
}, [setAppbarExtraArea])
|
||||
|
||||
useEffect(() => {
|
||||
if (backgrounds) setCurrentBackground(backgrounds[0])
|
||||
}, [backgrounds])
|
||||
|
||||
useEffect(() => {
|
||||
setSpineData(null)
|
||||
@@ -81,18 +98,10 @@ export default function Operator(props) {
|
||||
setVoiceConfig(data)
|
||||
})
|
||||
}
|
||||
}, [operators, key])
|
||||
}, [operators, key, setHeaderIcon, setConfig])
|
||||
|
||||
const getTabName = (item) => {
|
||||
if (item.type === 'operator') {
|
||||
return 'operator'
|
||||
} else {
|
||||
return item.codename[language].replace(/^(.+)( )(·|\/)()(.+)$/, '$1')
|
||||
}
|
||||
}
|
||||
|
||||
const coverToTab = (item) => {
|
||||
const key = getTabName(item)
|
||||
const coverToTab = useCallback((item, language) => {
|
||||
const key = getTabName(item, language)
|
||||
return {
|
||||
key: key,
|
||||
style: {
|
||||
@@ -103,30 +112,31 @@ export default function Operator(props) {
|
||||
navigate(`/${item.link}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [navigate])
|
||||
|
||||
const getOtherEntries = () => {
|
||||
const otherEntries = useMemo(() => {
|
||||
if (!config || !language) return null
|
||||
return operators.filter((item) => item.id === config.id && item.link !== config.link).map((item) => {
|
||||
return coverToTab(item)
|
||||
return coverToTab(item, language)
|
||||
})
|
||||
}
|
||||
}, [config, language, operators, coverToTab])
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setTabs(
|
||||
[
|
||||
coverToTab(config),
|
||||
...getOtherEntries()
|
||||
coverToTab(config, language),
|
||||
...otherEntries
|
||||
]
|
||||
)
|
||||
}
|
||||
}, [config, language, key])
|
||||
}, [config, key, coverToTab, setTabs, otherEntries, language])
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setTitle(config.codename[language])
|
||||
}
|
||||
}, [config, language, key])
|
||||
}, [config, language, key, setTitle])
|
||||
|
||||
useEffect(() => {
|
||||
if (spineRef.current?.children.length === 0 && spineData && config) {
|
||||
@@ -153,68 +163,68 @@ export default function Operator(props) {
|
||||
defaultMix: 0,
|
||||
}))
|
||||
}
|
||||
}, [spineData]);
|
||||
}, [spineData, setSpinePlayer, spineAnimation, config]);
|
||||
|
||||
useEffect(() => {
|
||||
const subtitleObj = useMemo(() => {
|
||||
if (voiceConfig && voiceLang) {
|
||||
let subtitleObj = voiceConfig.subtitleLangs[subtitleLang || 'zh-CN']
|
||||
let subtitleKey = 'default'
|
||||
if (subtitleObj[voiceLang]) {
|
||||
subtitleKey = voiceLang
|
||||
}
|
||||
subtitleObj = subtitleObj[subtitleKey]
|
||||
const playVoice = () => {
|
||||
const voiceId = () => {
|
||||
const keys = Object.keys(subtitleObj)
|
||||
const id = keys[Math.floor((Math.random() * keys.length))]
|
||||
return id === lastVoiceId ? voiceId() : id
|
||||
}
|
||||
const id = voiceId()
|
||||
setLastVoiceId(id)
|
||||
setCurrentVoiceId(id)
|
||||
audioEl.src = `/${config.link}/assets/${getVoiceFoler(voiceLang)}/${id}.ogg`
|
||||
let startPlayPromise = audioEl.play()
|
||||
if (startPlayPromise !== undefined) {
|
||||
startPlayPromise
|
||||
.then(() => {
|
||||
setIsVoicePlaying(true)
|
||||
const audioEndedFunc = () => {
|
||||
setIsVoicePlaying(false)
|
||||
audioEl.removeEventListener('ended', audioEndedFunc)
|
||||
if (currentVoiceId !== id) return
|
||||
setSubtitle(null)
|
||||
}
|
||||
if (subtitleLang) showSubtitle(id)
|
||||
audioEl.addEventListener('ended', audioEndedFunc)
|
||||
})
|
||||
.catch(() => {
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
const showSubtitle = (id) => {
|
||||
setSubtitle(subtitleObj[id])
|
||||
setHideSubtitle(false)
|
||||
}
|
||||
spineRef.current?.addEventListener('click', playVoice)
|
||||
return () => {
|
||||
spineRef.current?.removeEventListener('click', playVoice)
|
||||
}
|
||||
return subtitleObj[subtitleKey]
|
||||
}
|
||||
}, [voiceLang, spineRef, voiceConfig, subtitleLang, lastVoiceId])
|
||||
}, [subtitleLang, voiceConfig, voiceLang])
|
||||
|
||||
const handleClickPlay = useCallback(() => {
|
||||
if (!voiceLang) return
|
||||
const voiceId = () => {
|
||||
const keys = Object.keys(subtitleObj)
|
||||
const id = keys[Math.floor((Math.random() * keys.length))]
|
||||
return id === lastVoiceId ? voiceId() : id
|
||||
}
|
||||
const id = voiceId()
|
||||
setLastVoiceId(currentVoiceId)
|
||||
setCurrentVoiceId(id)
|
||||
audioEl.src = `/${config.link}/assets/${getVoiceFoler(voiceLang)}/${id}.ogg`
|
||||
let startPlayPromise = audioEl.play()
|
||||
setIsVoicePlaying(true)
|
||||
if (startPlayPromise !== undefined) {
|
||||
startPlayPromise
|
||||
.then(() => {
|
||||
const audioEndedFunc = () => {
|
||||
audioEl.removeEventListener('ended', audioEndedFunc)
|
||||
if (currentVoiceId !== id) return
|
||||
setIsVoicePlaying(false)
|
||||
}
|
||||
audioEl.addEventListener('ended', audioEndedFunc)
|
||||
})
|
||||
.catch(() => {
|
||||
return
|
||||
})
|
||||
}
|
||||
}, [voiceLang, lastVoiceId, config, currentVoiceId, subtitleObj])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVoicePlaying && !hideSubtitle) {
|
||||
const hideSubtitle = () => {
|
||||
setHideSubtitle(true)
|
||||
}
|
||||
setTimeout(hideSubtitle, 5 * 1000)
|
||||
return () => {
|
||||
clearTimeout(hideSubtitle)
|
||||
if (subtitleLang) {
|
||||
if (isVoicePlaying) {
|
||||
setHideSubtitle(false)
|
||||
setSubtitle(subtitleObj[currentVoiceId])
|
||||
} else {
|
||||
const autoHide = () => {
|
||||
if (isVoicePlaying) return
|
||||
setHideSubtitle(true)
|
||||
}
|
||||
setTimeout(autoHide, 5 * 1000)
|
||||
return () => {
|
||||
clearTimeout(autoHide)
|
||||
}
|
||||
// setHideSubtitle(true)
|
||||
}
|
||||
} else {
|
||||
setHideSubtitle(true)
|
||||
}
|
||||
}, [isVoicePlaying, hideSubtitle])
|
||||
}, [subtitleLang, currentVoiceId, isVoicePlaying, subtitleObj])
|
||||
|
||||
const spineSettings = [
|
||||
{
|
||||
@@ -377,7 +387,7 @@ export default function Operator(props) {
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
<section className="spine-container" style={{
|
||||
<section className="spine-container" style={currentBackground && {
|
||||
backgroundImage: `url(/${key}/assets/${import.meta.env.VITE_BACKGROUND_FOLDER}/${currentBackground})`
|
||||
}} >
|
||||
{
|
||||
@@ -385,7 +395,7 @@ export default function Operator(props) {
|
||||
<img src={`/${config.link}/assets/${config.logo}.png`} alt={config?.codename[language]} className='operator-logo'/>
|
||||
)
|
||||
}
|
||||
<section ref={spineRef} />
|
||||
<section ref={spineRef} onClick={handleClickPlay} />
|
||||
{
|
||||
subtitle && (
|
||||
<section className={`voice-wrapper${hideSubtitle ? '' : ' active'}`}>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useCallback
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Outlet,
|
||||
Link,
|
||||
@@ -26,22 +27,20 @@ import ReturnButton from '@/component/return_button';
|
||||
import MainBorder from '@/component/main_border';
|
||||
import CharIcon from '@/component/char_icon';
|
||||
|
||||
export default function Root(props) {
|
||||
const currentYear = new Date().getFullYear()
|
||||
|
||||
export default function Root() {
|
||||
const [drawerHidden, setDrawerHidden] = useState(true)
|
||||
const { textDefaultLang, language, alternateLang } = useLanguage()
|
||||
const {
|
||||
title,
|
||||
tabs,
|
||||
currentTab, setCurrentTab,
|
||||
setCurrentTab,
|
||||
headerIcon
|
||||
} = useHeader()
|
||||
const {
|
||||
extraArea,
|
||||
} = useAppbar()
|
||||
const { version, fetchConfig, fetchVersion } = useConfig()
|
||||
const [drawerDestinations, setDrawerDestinations] = useState(null)
|
||||
const currentYear = useMemo(() => new Date().getFullYear(), [])
|
||||
const { i18n } = useI18n()
|
||||
const { fetchConfig, fetchVersion } = useConfig()
|
||||
const { fetchBackgrounds } = useBackgrounds()
|
||||
|
||||
const headerTabs = useMemo(() => {
|
||||
@@ -61,8 +60,124 @@ export default function Root(props) {
|
||||
setDrawerHidden(value || !drawerHidden)
|
||||
}, [drawerHidden])
|
||||
|
||||
const renderDrawerDestinations = () => {
|
||||
return routes.filter((item) => item.inDrawer).map((item) => {
|
||||
useEffect(() => {
|
||||
if (tabs.length > 0) {
|
||||
setCurrentTab(tabs[0].key)
|
||||
} else {
|
||||
setCurrentTab(null)
|
||||
}
|
||||
}, [setCurrentTab, tabs])
|
||||
|
||||
useEffect(() => {
|
||||
fetchConfig()
|
||||
fetchVersion()
|
||||
fetchBackgrounds()
|
||||
}, [fetchBackgrounds, fetchConfig, fetchVersion])
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className='header'>
|
||||
<section
|
||||
className={`navButton ${drawerHidden ? '' : 'active'}`}
|
||||
onClick={() => toggleDrawer()}
|
||||
>
|
||||
<section className='bar'></section>
|
||||
<section className='bar'></section>
|
||||
<section className='bar'></section>
|
||||
</section>
|
||||
<section className='spacer' />
|
||||
{extraArea}
|
||||
<LanguageDropdown />
|
||||
</header>
|
||||
<nav className={`drawer ${drawerHidden ? '' : 'active'}`}>
|
||||
<section
|
||||
className='links'
|
||||
>
|
||||
<DrawerDestinations
|
||||
toggleDrawer={toggleDrawer}
|
||||
/>
|
||||
</section>
|
||||
<section
|
||||
className={`overlay ${drawerHidden ? '' : 'active'}`}
|
||||
onClick={() => toggleDrawer()}
|
||||
/>
|
||||
</nav>
|
||||
<main className='main'>
|
||||
<section className='main-header'>
|
||||
<section className='main-title'>
|
||||
{headerIcon && (
|
||||
<section className='main-icon'>
|
||||
<CharIcon
|
||||
type={headerIcon}
|
||||
viewBox={
|
||||
headerIcon === 'operator' ? '0 0 88.969 71.469' : '0 0 94.563 67.437'
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
{title}
|
||||
</section>
|
||||
<section className='main-tab'>
|
||||
{headerTabs}
|
||||
</section>
|
||||
</section>
|
||||
<HeaderReturnButton />
|
||||
<Outlet />
|
||||
</main>
|
||||
<FooterElement />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function FooterElement() {
|
||||
const { i18n } = useI18n()
|
||||
const { version } = useConfig()
|
||||
|
||||
return useMemo(() => {
|
||||
return (
|
||||
<footer className='footer'>
|
||||
<section className='links section'>
|
||||
<section className="item">
|
||||
<Popup
|
||||
className='link'
|
||||
title={i18n('disclaimer')}
|
||||
>
|
||||
{i18n('disclaimer_content')}
|
||||
</Popup>
|
||||
</section>
|
||||
<section className="item">
|
||||
<Link reloadDocument to="https://privacy.halyul.dev" target="_blank" className='link'>{i18n('privacy_policy')}</Link>
|
||||
</section>
|
||||
<section className="item">
|
||||
<Link reloadDocument to="https://github.com/Halyul/aklive2d" target="_blank" className='link'>GitHub</Link>
|
||||
</section>
|
||||
<section className="item">
|
||||
<Popup
|
||||
className='link'
|
||||
title={i18n('contact_us')}
|
||||
>
|
||||
ak#halyul.dev
|
||||
</Popup>
|
||||
</section>
|
||||
</section>
|
||||
<section className='copyright section'>
|
||||
<span>Spine Runtimes © 2013 - 2019 Esoteric Software LLC</span>
|
||||
<span>Assets © 2017 - {currentYear} Arknights/Hypergryph Co., Ltd</span>
|
||||
<span>Source Code © 2021 - {currentYear} Halyul</span>
|
||||
<span>Directory @ {version.directory}</span>
|
||||
<span>Showcase @ {version.showcase}</span>
|
||||
</section>
|
||||
</footer>
|
||||
)
|
||||
}, [i18n, version.directory, version.showcase])
|
||||
}
|
||||
|
||||
function DrawerDestinations({ toggleDrawer }) {
|
||||
const { i18n } = useI18n()
|
||||
const { textDefaultLang, alternateLang } = useLanguage()
|
||||
|
||||
return (
|
||||
routes.filter((item) => item.inDrawer).map((item) => {
|
||||
if (typeof item.element.type === 'string') {
|
||||
return (
|
||||
<Link reloadDocument
|
||||
@@ -98,108 +213,6 @@ export default function Root(props) {
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setDrawerDestinations(renderDrawerDestinations())
|
||||
}, [alternateLang])
|
||||
|
||||
useEffect(() => {
|
||||
if (tabs.length > 0) {
|
||||
setCurrentTab(tabs[0].key)
|
||||
} else {
|
||||
setCurrentTab(null)
|
||||
}
|
||||
}, [tabs])
|
||||
|
||||
useEffect(() => {
|
||||
fetchConfig()
|
||||
fetchVersion()
|
||||
fetchBackgrounds()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className='header'>
|
||||
<section
|
||||
className={`navButton ${drawerHidden ? '' : 'active'}`}
|
||||
onClick={() => toggleDrawer()}
|
||||
>
|
||||
<section className='bar'></section>
|
||||
<section className='bar'></section>
|
||||
<section className='bar'></section>
|
||||
</section>
|
||||
<section className='spacer' />
|
||||
{extraArea}
|
||||
<LanguageDropdown />
|
||||
</header>
|
||||
<nav className={`drawer ${drawerHidden ? '' : 'active'}`}>
|
||||
<section
|
||||
className='links'
|
||||
>
|
||||
{drawerDestinations}
|
||||
</section>
|
||||
<section
|
||||
className={`overlay ${drawerHidden ? '' : 'active'}`}
|
||||
onClick={() => toggleDrawer()}
|
||||
/>
|
||||
</nav>
|
||||
<main className='main'>
|
||||
<section className='main-header'>
|
||||
<section className='main-title'>
|
||||
{headerIcon && (
|
||||
<section className='main-icon'>
|
||||
<CharIcon
|
||||
type={headerIcon}
|
||||
viewBox={
|
||||
headerIcon === 'operator' ? '0 0 88.969 71.469' : '0 0 94.563 67.437'
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
{title}
|
||||
</section>
|
||||
<section className='main-tab'>
|
||||
{headerTabs}
|
||||
</section>
|
||||
</section>
|
||||
<HeaderReturnButton />
|
||||
<Outlet />
|
||||
</main>
|
||||
<footer className='footer'>
|
||||
<section className='links section'>
|
||||
<section className="item">
|
||||
<Popup
|
||||
className='link'
|
||||
title={i18n('disclaimer')}
|
||||
>
|
||||
{i18n('disclaimer_content')}
|
||||
</Popup>
|
||||
</section>
|
||||
<section className="item">
|
||||
<Link reloadDocument to="https://privacy.halyul.dev" target="_blank" className='link'>{i18n('privacy_policy')}</Link>
|
||||
</section>
|
||||
<section className="item">
|
||||
<Link reloadDocument to="https://github.com/Halyul/aklive2d" target="_blank" className='link'>GitHub</Link>
|
||||
</section>
|
||||
<section className="item">
|
||||
<Popup
|
||||
className='link'
|
||||
title={i18n('contact_us')}
|
||||
>
|
||||
ak#halyul.dev
|
||||
</Popup>
|
||||
</section>
|
||||
</section>
|
||||
<section className='copyright section'>
|
||||
<span>Spine Runtimes © 2013 - 2019 Esoteric Software LLC</span>
|
||||
<span>Assets © 2017 - {currentYear} Arknights/Hypergryph Co., Ltd</span>
|
||||
<span>Source Code © 2021 - {currentYear} Halyul</span>
|
||||
<span>Directory @ {version.directory}</span>
|
||||
<span>Showcase @ {version.showcase}</span>
|
||||
</section>
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -207,20 +220,22 @@ function LanguageDropdown() {
|
||||
const { language, setLanguage } = useLanguage()
|
||||
const { i18n, i18nValues } = useI18n()
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
text={i18n(language)}
|
||||
menu={i18nValues.available.map((item) => {
|
||||
return {
|
||||
name: i18n(item),
|
||||
value: item
|
||||
}
|
||||
})}
|
||||
onClick={(item) => {
|
||||
setLanguage(item.value)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return useMemo(() => {
|
||||
return (
|
||||
<Dropdown
|
||||
text={i18n(language)}
|
||||
menu={i18nValues.available.map((item) => {
|
||||
return {
|
||||
name: i18n(item),
|
||||
value: item
|
||||
}
|
||||
})}
|
||||
onClick={(item) => {
|
||||
setLanguage(item.value)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}, [i18n, i18nValues.available, language, setLanguage])
|
||||
}
|
||||
|
||||
function HeaderTabsElement({ item }) {
|
||||
@@ -241,30 +256,24 @@ function HeaderTabsElement({ item }) {
|
||||
<section className='main-tab-text-wrapper'>
|
||||
<span className='text'>{i18n(item.key)}</span>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
)
|
||||
}
|
||||
HeaderTabsElement.propTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
function HeaderReturnButton() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
navigate("/")
|
||||
}, [])
|
||||
|
||||
const children = useMemo(() => {
|
||||
return useMemo(() => {
|
||||
return (
|
||||
<ReturnButton
|
||||
className='return-button'
|
||||
onClick={onClick}
|
||||
/>
|
||||
<MainBorder>
|
||||
<ReturnButton
|
||||
className='return-button'
|
||||
onClick={() => navigate("/")}
|
||||
/>
|
||||
</MainBorder>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<MainBorder>
|
||||
{children}
|
||||
</MainBorder>
|
||||
)
|
||||
}, [navigate])
|
||||
}
|
||||
@@ -1,25 +1,19 @@
|
||||
import { useCallback } from 'react';
|
||||
import { atom, useAtom } from 'jotai';
|
||||
|
||||
const fetcher = (...args) => fetch(...args).then(res => res.json())
|
||||
const backgroundsAtom = atom([]);
|
||||
const currentBackgroundAtom = atom(null);
|
||||
|
||||
export function useBackgrounds() {
|
||||
const [backgrounds, setBackgrounds] = useAtom(backgroundsAtom);
|
||||
const [currentBackground, setCurrentBackground] = useAtom(currentBackgroundAtom)
|
||||
|
||||
const fetchBackgrounds = useCallback(async () => {
|
||||
const res = await fetch('/_assets/backgrounds.json')
|
||||
const data = await res.json()
|
||||
setBackgrounds(data)
|
||||
setCurrentBackground(data[0])
|
||||
}, [])
|
||||
}, [setBackgrounds])
|
||||
|
||||
return {
|
||||
backgrounds,
|
||||
currentBackground,
|
||||
setCurrentBackground,
|
||||
fetchBackgrounds
|
||||
};
|
||||
}
|
||||
@@ -19,13 +19,13 @@ export function useConfig() {
|
||||
operatorsList = [...operatorsList, ...item]
|
||||
})
|
||||
setOperators(operatorsList)
|
||||
}, [])
|
||||
}, [setConfig, setOperators])
|
||||
|
||||
const fetchVersion = useCallback(async () => {
|
||||
const res = await fetch('/_assets/version.json')
|
||||
const data = await res.json()
|
||||
setVersion(data);
|
||||
}, [])
|
||||
}, [setVersion])
|
||||
|
||||
return { config, version, operators, fetchConfig, fetchVersion };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { atom, useAtom } from 'jotai';
|
||||
import { useLanguage, useI18n } from "@/state/language"
|
||||
import { useI18n } from "@/state/language"
|
||||
|
||||
const keyAtom = atom('');
|
||||
const titleAtom = atom('');
|
||||
@@ -17,13 +17,12 @@ export function useHeader() {
|
||||
const [appbarExtraArea, setAppbarExtraArea] = useAtom(appbarExtraAreaAtom);
|
||||
const [headerIcon, setHeaderIcon] = useAtom(headerIconAtom);
|
||||
const { i18n } = useI18n()
|
||||
const { language } = useLanguage()
|
||||
|
||||
useEffect(() => {
|
||||
const newTitle = i18n(key)
|
||||
document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`;
|
||||
setRealTitle(newTitle)
|
||||
}, [key, language])
|
||||
}, [i18n, key, setRealTitle])
|
||||
|
||||
return {
|
||||
title, setTitle,
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@vitejs/plugin-react-swc": "^3.2.0",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"rollup": "^3.17.3",
|
||||
"vite": "^4.1.4"
|
||||
},
|
||||
|
||||
1032
pnpm-lock.yaml
generated
1032
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user