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) {
|
export default function CharIcon(props) {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox={props.viewBox}>
|
<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" />
|
<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>
|
</svg>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CharIcon.propTypes = {
|
||||||
|
viewBox: PropTypes.string,
|
||||||
|
type: PropTypes.string,
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import React, {
|
||||||
useState
|
useState
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import './dropdown.css'
|
import './dropdown.css'
|
||||||
|
|
||||||
export default function Dropdown(props) {
|
export default function Dropdown(props) {
|
||||||
@@ -47,3 +48,11 @@ export default function Dropdown(props) {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
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';
|
import './main_border.css';
|
||||||
|
|
||||||
export default function MainBorder(props) {
|
export default function MainBorder(props) {
|
||||||
@@ -7,3 +9,6 @@ export default function MainBorder(props) {
|
|||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
MainBorder.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
@@ -1,40 +1,42 @@
|
|||||||
import {
|
import React, {
|
||||||
useState,
|
useState,
|
||||||
useCallback
|
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import './popup.css'
|
import './popup.css'
|
||||||
import ReturnButton from '@/component/return_button';
|
import ReturnButton from '@/component/return_button';
|
||||||
import MainBorder from '@/component/main_border';
|
import MainBorder from '@/component/main_border';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export default function Popup(props) {
|
export default function Popup(props) {
|
||||||
const [hidden, setHidden] = useState(true)
|
const [hidden, setHidden] = useState(true)
|
||||||
|
|
||||||
const toggle = useCallback(() => {
|
const toggle = () => {
|
||||||
setHidden(!hidden)
|
setHidden(!hidden)
|
||||||
}, [hidden])
|
}
|
||||||
|
|
||||||
return (
|
return (<>
|
||||||
<>
|
<section className={`popup ${hidden ? '' : 'active'}`}>
|
||||||
<section className={`popup ${hidden ? '' : 'active'}`}>
|
<section className='wrapper'>
|
||||||
<section className='wrapper'>
|
<section className='title'>
|
||||||
<section className='title'>
|
<section className="text">{props.title}</section>
|
||||||
<section className="text">{props.title}</section>
|
<ReturnButton onClick={toggle} className="return-button" />
|
||||||
<ReturnButton onClick={toggle} className="return-button"/>
|
</section>
|
||||||
</section>
|
<MainBorder />
|
||||||
<MainBorder/>
|
<section className='content'>
|
||||||
<section className='content'>
|
{props.children}
|
||||||
{props.children}
|
|
||||||
</section>
|
|
||||||
</section>
|
</section>
|
||||||
<section className={`overlay ${hidden ? '' : 'active'}`}
|
|
||||||
onClick={() => toggle()} />
|
|
||||||
</section>
|
</section>
|
||||||
<span
|
<section className={`overlay ${hidden ? '' : 'active'}`}
|
||||||
className="popup-text"
|
onClick={() => toggle()} />
|
||||||
onClick={toggle}
|
</section>
|
||||||
>
|
<span
|
||||||
{props.title}
|
className="popup-text"
|
||||||
</span>
|
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'
|
import './return_button.css'
|
||||||
|
|
||||||
export default function ReturnButton(props) {
|
export default function ReturnButton(props) {
|
||||||
@@ -31,3 +33,6 @@ export default function ReturnButton(props) {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
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 './switch.css';
|
||||||
|
import {
|
||||||
|
useI18n
|
||||||
|
} from '@/state/language'
|
||||||
|
|
||||||
export default function Switch(props) {
|
export default function Switch(props) {
|
||||||
const [on, setOn] = useState(props.on)
|
const [on, setOn] = useState(props.on)
|
||||||
|
const { i18n } = useI18n()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOn(props.on)
|
setOn(props.on)
|
||||||
@@ -13,7 +18,7 @@ export default function Switch(props) {
|
|||||||
className={`switch ${on ? 'active' : ''}`}
|
className={`switch ${on ? 'active' : ''}`}
|
||||||
onClick={() => props.handleOnClick()}
|
onClick={() => props.handleOnClick()}
|
||||||
>
|
>
|
||||||
<span className='text'>{props.text}</span>
|
<span className='text'>{i18n(props.text)}</span>
|
||||||
<section className='icon-wrapper'>
|
<section className='icon-wrapper'>
|
||||||
<span className='icon-line'></span>
|
<span className='icon-line'></span>
|
||||||
<span className='icon'></span>
|
<span className='icon'></span>
|
||||||
@@ -21,3 +26,8 @@ export default function Switch(props) {
|
|||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Switch.propTypes = {
|
||||||
|
on: PropTypes.bool,
|
||||||
|
text: PropTypes.string,
|
||||||
|
handleOnClick: PropTypes.func,
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
useNavigate,
|
useNavigate,
|
||||||
useRouteError
|
useRouteError
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import Home from "@/routes/path/home";
|
import Home from "@/routes/path/home";
|
||||||
import Operator from "@/routes/path/operator";
|
import Operator from "@/routes/path/operator";
|
||||||
import Changelogs from "@/routes/path/changelogs";
|
import Changelogs from "@/routes/path/changelogs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {
|
import React, {
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo
|
useMemo
|
||||||
@@ -9,7 +9,8 @@ import { useAppbar } from '@/state/appbar';
|
|||||||
import useUmami from '@parcellab/react-use-umami'
|
import useUmami from '@parcellab/react-use-umami'
|
||||||
import MainBorder from '@/component/main_border';
|
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 _trackEvt = useUmami('/changelogs')
|
||||||
const {
|
const {
|
||||||
setTitle,
|
setTitle,
|
||||||
@@ -29,7 +30,7 @@ export default function Changelogs(props) {
|
|||||||
fetch('/_assets/changelogs.json').then(res => res.json()).then(data => {
|
fetch('/_assets/changelogs.json').then(res => res.json()).then(data => {
|
||||||
setChangelogs(data)
|
setChangelogs(data)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [setExtraArea, setHeaderIcon, setTitle])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabs(changelogs.map((item) => {
|
setTabs(changelogs.map((item) => {
|
||||||
@@ -37,7 +38,7 @@ export default function Changelogs(props) {
|
|||||||
key: item[0].key
|
key: item[0].key
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}, [changelogs])
|
}, [changelogs, setTabs])
|
||||||
|
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import {
|
import React, {
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo
|
useMemo
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
NavLink,
|
NavLink,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import './home.css'
|
import './home.css'
|
||||||
import { useConfig } from '@/state/config';
|
import { useConfig } from '@/state/config';
|
||||||
import {
|
import {
|
||||||
useLanguage,
|
useLanguage
|
||||||
useI18n
|
|
||||||
} from '@/state/language'
|
} from '@/state/language'
|
||||||
import { useHeader } from '@/state/header';
|
import { useHeader } from '@/state/header';
|
||||||
import { useAppbar } from '@/state/appbar';
|
import { useAppbar } from '@/state/appbar';
|
||||||
@@ -58,6 +58,7 @@ const playVoice = (link) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const _trackEvt = useUmami('/')
|
const _trackEvt = useUmami('/')
|
||||||
const {
|
const {
|
||||||
setTitle,
|
setTitle,
|
||||||
@@ -78,7 +79,7 @@ export default function Home() {
|
|||||||
key: 'skin'
|
key: 'skin'
|
||||||
}])
|
}])
|
||||||
setHeaderIcon(null)
|
setHeaderIcon(null)
|
||||||
}, [])
|
}, [setHeaderIcon, setTabs, setTitle])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContent(config?.operators || [])
|
setContent(config?.operators || [])
|
||||||
@@ -157,7 +158,7 @@ function OperatorElement({ item, hidden }) {
|
|||||||
</section>
|
</section>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)
|
)
|
||||||
}, [language, alternateLang, hidden])
|
}, [item, hidden, language, alternateLang, textDefaultLang])
|
||||||
}
|
}
|
||||||
|
|
||||||
function VoiceSwitchElement() {
|
function VoiceSwitchElement() {
|
||||||
@@ -165,28 +166,19 @@ function VoiceSwitchElement() {
|
|||||||
const {
|
const {
|
||||||
setExtraArea,
|
setExtraArea,
|
||||||
} = useAppbar()
|
} = useAppbar()
|
||||||
const { i18n } = useI18n()
|
|
||||||
|
|
||||||
const toggleVoice = useCallback(() => {
|
useEffect(() => {
|
||||||
setVoiceOn(!voiceOn)
|
setExtraArea([
|
||||||
}, [voiceOn])
|
|
||||||
|
|
||||||
const appbarSwitch = useMemo(() => {
|
|
||||||
return [
|
|
||||||
(
|
(
|
||||||
<Switch
|
<Switch
|
||||||
key="voice"
|
key="voice"
|
||||||
text={i18n('voice')}
|
text='voice'
|
||||||
on={voiceOn}
|
on={voiceOn}
|
||||||
handleOnClick={() => toggleVoice()}
|
handleOnClick={() => setVoiceOn(!voiceOn)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
]
|
])
|
||||||
}, [voiceOn])
|
}, [voiceOn, setExtraArea, setVoiceOn])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setExtraArea(appbarSwitch)
|
|
||||||
}, [voiceOn])
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -195,3 +187,8 @@ function ImageElement({ item }) {
|
|||||||
const { language } = useLanguage()
|
const { language } = useLanguage()
|
||||||
return <img src={`/${item.link}/assets/${item.fallback_name.replace("#", "%23")}_portrait.png`} alt={item.codename[language]} />
|
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,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useCallback
|
useCallback,
|
||||||
|
useMemo
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
useParams,
|
useParams,
|
||||||
@@ -33,7 +34,17 @@ const spinePlayerAtom = atom(null);
|
|||||||
const spineAnimationAtom = atom("Idle");
|
const spineAnimationAtom = atom("Idle");
|
||||||
const audioEl = new Audio()
|
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 navigate = useNavigate()
|
||||||
const { operators } = useConfig()
|
const { operators } = useConfig()
|
||||||
const { language } = useLanguage()
|
const { language } = useLanguage()
|
||||||
@@ -46,13 +57,15 @@ export default function Operator(props) {
|
|||||||
} = useHeader()
|
} = useHeader()
|
||||||
const [config, setConfig] = useAtom(configAtom)
|
const [config, setConfig] = useAtom(configAtom)
|
||||||
const [spineData, setSpineData] = useState(null)
|
const [spineData, setSpineData] = useState(null)
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const _trackEvt = useUmami(`/${key}`)
|
const _trackEvt = useUmami(`/${key}`)
|
||||||
const spineRef = useRef(null)
|
const spineRef = useRef(null)
|
||||||
const [spineAnimation, setSpineAnimation] = useAtom(spineAnimationAtom)
|
const [spineAnimation, setSpineAnimation] = useAtom(spineAnimationAtom)
|
||||||
const { i18n } = useI18n()
|
const { i18n } = useI18n()
|
||||||
const [spinePlayer, setSpinePlayer] = useAtom(spinePlayerAtom)
|
const [spinePlayer, setSpinePlayer] = useAtom(spinePlayerAtom)
|
||||||
const [voiceLang, setVoiceLang] = useState(null)
|
const [voiceLang, setVoiceLang] = useState(null)
|
||||||
const { backgrounds, currentBackground, setCurrentBackground } = useBackgrounds()
|
const { backgrounds } = useBackgrounds()
|
||||||
|
const [currentBackground, setCurrentBackground] = useState(null)
|
||||||
const [voiceConfig, setVoiceConfig] = useState(null)
|
const [voiceConfig, setVoiceConfig] = useState(null)
|
||||||
const [subtitleLang, setSubtitleLang] = useState(null)
|
const [subtitleLang, setSubtitleLang] = useState(null)
|
||||||
const [subtitle, setSubtitle] = useState(null)
|
const [subtitle, setSubtitle] = useState(null)
|
||||||
@@ -63,7 +76,11 @@ export default function Operator(props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAppbarExtraArea([])
|
setAppbarExtraArea([])
|
||||||
}, [])
|
}, [setAppbarExtraArea])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (backgrounds) setCurrentBackground(backgrounds[0])
|
||||||
|
}, [backgrounds])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSpineData(null)
|
setSpineData(null)
|
||||||
@@ -81,18 +98,10 @@ export default function Operator(props) {
|
|||||||
setVoiceConfig(data)
|
setVoiceConfig(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [operators, key])
|
}, [operators, key, setHeaderIcon, setConfig])
|
||||||
|
|
||||||
const getTabName = (item) => {
|
const coverToTab = useCallback((item, language) => {
|
||||||
if (item.type === 'operator') {
|
const key = getTabName(item, language)
|
||||||
return 'operator'
|
|
||||||
} else {
|
|
||||||
return item.codename[language].replace(/^(.+)( )(·|\/)()(.+)$/, '$1')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const coverToTab = (item) => {
|
|
||||||
const key = getTabName(item)
|
|
||||||
return {
|
return {
|
||||||
key: key,
|
key: key,
|
||||||
style: {
|
style: {
|
||||||
@@ -103,30 +112,31 @@ export default function Operator(props) {
|
|||||||
navigate(`/${item.link}`)
|
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 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(() => {
|
useEffect(() => {
|
||||||
if (config) {
|
if (config) {
|
||||||
setTabs(
|
setTabs(
|
||||||
[
|
[
|
||||||
coverToTab(config),
|
coverToTab(config, language),
|
||||||
...getOtherEntries()
|
...otherEntries
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [config, language, key])
|
}, [config, key, coverToTab, setTabs, otherEntries, language])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config) {
|
if (config) {
|
||||||
setTitle(config.codename[language])
|
setTitle(config.codename[language])
|
||||||
}
|
}
|
||||||
}, [config, language, key])
|
}, [config, language, key, setTitle])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (spineRef.current?.children.length === 0 && spineData && config) {
|
if (spineRef.current?.children.length === 0 && spineData && config) {
|
||||||
@@ -153,68 +163,68 @@ export default function Operator(props) {
|
|||||||
defaultMix: 0,
|
defaultMix: 0,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}, [spineData]);
|
}, [spineData, setSpinePlayer, spineAnimation, config]);
|
||||||
|
|
||||||
useEffect(() => {
|
const subtitleObj = useMemo(() => {
|
||||||
if (voiceConfig && voiceLang) {
|
if (voiceConfig && voiceLang) {
|
||||||
let subtitleObj = voiceConfig.subtitleLangs[subtitleLang || 'zh-CN']
|
let subtitleObj = voiceConfig.subtitleLangs[subtitleLang || 'zh-CN']
|
||||||
let subtitleKey = 'default'
|
let subtitleKey = 'default'
|
||||||
if (subtitleObj[voiceLang]) {
|
if (subtitleObj[voiceLang]) {
|
||||||
subtitleKey = voiceLang
|
subtitleKey = voiceLang
|
||||||
}
|
}
|
||||||
subtitleObj = subtitleObj[subtitleKey]
|
return 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [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(() => {
|
useEffect(() => {
|
||||||
if (!isVoicePlaying && !hideSubtitle) {
|
if (subtitleLang) {
|
||||||
const hideSubtitle = () => {
|
if (isVoicePlaying) {
|
||||||
setHideSubtitle(true)
|
setHideSubtitle(false)
|
||||||
}
|
setSubtitle(subtitleObj[currentVoiceId])
|
||||||
setTimeout(hideSubtitle, 5 * 1000)
|
} else {
|
||||||
return () => {
|
const autoHide = () => {
|
||||||
clearTimeout(hideSubtitle)
|
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 = [
|
const spineSettings = [
|
||||||
{
|
{
|
||||||
@@ -377,7 +387,7 @@ export default function Operator(props) {
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</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})`
|
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'/>
|
<img src={`/${config.link}/assets/${config.logo}.png`} alt={config?.codename[language]} className='operator-logo'/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<section ref={spineRef} />
|
<section ref={spineRef} onClick={handleClickPlay} />
|
||||||
{
|
{
|
||||||
subtitle && (
|
subtitle && (
|
||||||
<section className={`voice-wrapper${hideSubtitle ? '' : ' active'}`}>
|
<section className={`voice-wrapper${hideSubtitle ? '' : ' active'}`}>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import {
|
import React, {
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useCallback
|
useCallback
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Outlet,
|
Outlet,
|
||||||
Link,
|
Link,
|
||||||
@@ -26,22 +27,20 @@ import ReturnButton from '@/component/return_button';
|
|||||||
import MainBorder from '@/component/main_border';
|
import MainBorder from '@/component/main_border';
|
||||||
import CharIcon from '@/component/char_icon';
|
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 [drawerHidden, setDrawerHidden] = useState(true)
|
||||||
const { textDefaultLang, language, alternateLang } = useLanguage()
|
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
tabs,
|
tabs,
|
||||||
currentTab, setCurrentTab,
|
setCurrentTab,
|
||||||
headerIcon
|
headerIcon
|
||||||
} = useHeader()
|
} = useHeader()
|
||||||
const {
|
const {
|
||||||
extraArea,
|
extraArea,
|
||||||
} = useAppbar()
|
} = useAppbar()
|
||||||
const { version, fetchConfig, fetchVersion } = useConfig()
|
const { fetchConfig, fetchVersion } = useConfig()
|
||||||
const [drawerDestinations, setDrawerDestinations] = useState(null)
|
|
||||||
const currentYear = useMemo(() => new Date().getFullYear(), [])
|
|
||||||
const { i18n } = useI18n()
|
|
||||||
const { fetchBackgrounds } = useBackgrounds()
|
const { fetchBackgrounds } = useBackgrounds()
|
||||||
|
|
||||||
const headerTabs = useMemo(() => {
|
const headerTabs = useMemo(() => {
|
||||||
@@ -61,8 +60,124 @@ export default function Root(props) {
|
|||||||
setDrawerHidden(value || !drawerHidden)
|
setDrawerHidden(value || !drawerHidden)
|
||||||
}, [drawerHidden])
|
}, [drawerHidden])
|
||||||
|
|
||||||
const renderDrawerDestinations = () => {
|
useEffect(() => {
|
||||||
return routes.filter((item) => item.inDrawer).map((item) => {
|
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') {
|
if (typeof item.element.type === 'string') {
|
||||||
return (
|
return (
|
||||||
<Link reloadDocument
|
<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 { language, setLanguage } = useLanguage()
|
||||||
const { i18n, i18nValues } = useI18n()
|
const { i18n, i18nValues } = useI18n()
|
||||||
|
|
||||||
return (
|
return useMemo(() => {
|
||||||
<Dropdown
|
return (
|
||||||
text={i18n(language)}
|
<Dropdown
|
||||||
menu={i18nValues.available.map((item) => {
|
text={i18n(language)}
|
||||||
return {
|
menu={i18nValues.available.map((item) => {
|
||||||
name: i18n(item),
|
return {
|
||||||
value: item
|
name: i18n(item),
|
||||||
}
|
value: item
|
||||||
})}
|
}
|
||||||
onClick={(item) => {
|
})}
|
||||||
setLanguage(item.value)
|
onClick={(item) => {
|
||||||
}}
|
setLanguage(item.value)
|
||||||
/>
|
}}
|
||||||
)
|
/>
|
||||||
|
)
|
||||||
|
}, [i18n, i18nValues.available, language, setLanguage])
|
||||||
}
|
}
|
||||||
|
|
||||||
function HeaderTabsElement({ item }) {
|
function HeaderTabsElement({ item }) {
|
||||||
@@ -241,30 +256,24 @@ function HeaderTabsElement({ item }) {
|
|||||||
<section className='main-tab-text-wrapper'>
|
<section className='main-tab-text-wrapper'>
|
||||||
<span className='text'>{i18n(item.key)}</span>
|
<span className='text'>{i18n(item.key)}</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
HeaderTabsElement.propTypes = {
|
||||||
|
item: PropTypes.object.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
function HeaderReturnButton() {
|
function HeaderReturnButton() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
return useMemo(() => {
|
||||||
navigate("/")
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const children = useMemo(() => {
|
|
||||||
return (
|
return (
|
||||||
<ReturnButton
|
<MainBorder>
|
||||||
className='return-button'
|
<ReturnButton
|
||||||
onClick={onClick}
|
className='return-button'
|
||||||
/>
|
onClick={() => navigate("/")}
|
||||||
|
/>
|
||||||
|
</MainBorder>
|
||||||
)
|
)
|
||||||
}, [])
|
}, [navigate])
|
||||||
|
|
||||||
return (
|
|
||||||
<MainBorder>
|
|
||||||
{children}
|
|
||||||
</MainBorder>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,19 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { atom, useAtom } from 'jotai';
|
import { atom, useAtom } from 'jotai';
|
||||||
|
|
||||||
const fetcher = (...args) => fetch(...args).then(res => res.json())
|
|
||||||
const backgroundsAtom = atom([]);
|
const backgroundsAtom = atom([]);
|
||||||
const currentBackgroundAtom = atom(null);
|
|
||||||
|
|
||||||
export function useBackgrounds() {
|
export function useBackgrounds() {
|
||||||
const [backgrounds, setBackgrounds] = useAtom(backgroundsAtom);
|
const [backgrounds, setBackgrounds] = useAtom(backgroundsAtom);
|
||||||
const [currentBackground, setCurrentBackground] = useAtom(currentBackgroundAtom)
|
|
||||||
|
|
||||||
const fetchBackgrounds = useCallback(async () => {
|
const fetchBackgrounds = useCallback(async () => {
|
||||||
const res = await fetch('/_assets/backgrounds.json')
|
const res = await fetch('/_assets/backgrounds.json')
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
setBackgrounds(data)
|
setBackgrounds(data)
|
||||||
setCurrentBackground(data[0])
|
}, [setBackgrounds])
|
||||||
}, [])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
backgrounds,
|
backgrounds,
|
||||||
currentBackground,
|
|
||||||
setCurrentBackground,
|
|
||||||
fetchBackgrounds
|
fetchBackgrounds
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -19,13 +19,13 @@ export function useConfig() {
|
|||||||
operatorsList = [...operatorsList, ...item]
|
operatorsList = [...operatorsList, ...item]
|
||||||
})
|
})
|
||||||
setOperators(operatorsList)
|
setOperators(operatorsList)
|
||||||
}, [])
|
}, [setConfig, setOperators])
|
||||||
|
|
||||||
const fetchVersion = useCallback(async () => {
|
const fetchVersion = useCallback(async () => {
|
||||||
const res = await fetch('/_assets/version.json')
|
const res = await fetch('/_assets/version.json')
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
setVersion(data);
|
setVersion(data);
|
||||||
}, [])
|
}, [setVersion])
|
||||||
|
|
||||||
return { config, version, operators, fetchConfig, fetchVersion };
|
return { config, version, operators, fetchConfig, fetchVersion };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { atom, useAtom } from 'jotai';
|
import { atom, useAtom } from 'jotai';
|
||||||
import { useLanguage, useI18n } from "@/state/language"
|
import { useI18n } from "@/state/language"
|
||||||
|
|
||||||
const keyAtom = atom('');
|
const keyAtom = atom('');
|
||||||
const titleAtom = atom('');
|
const titleAtom = atom('');
|
||||||
@@ -17,13 +17,12 @@ export function useHeader() {
|
|||||||
const [appbarExtraArea, setAppbarExtraArea] = useAtom(appbarExtraAreaAtom);
|
const [appbarExtraArea, setAppbarExtraArea] = useAtom(appbarExtraAreaAtom);
|
||||||
const [headerIcon, setHeaderIcon] = useAtom(headerIconAtom);
|
const [headerIcon, setHeaderIcon] = useAtom(headerIconAtom);
|
||||||
const { i18n } = useI18n()
|
const { i18n } = useI18n()
|
||||||
const { language } = useLanguage()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newTitle = i18n(key)
|
const newTitle = i18n(key)
|
||||||
document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`;
|
document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`;
|
||||||
setRealTitle(newTitle)
|
setRealTitle(newTitle)
|
||||||
}, [key, language])
|
}, [i18n, key, setRealTitle])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title, setTitle,
|
title, setTitle,
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
"@types/react": "^18.0.28",
|
"@types/react": "^18.0.28",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
"@vitejs/plugin-react-swc": "^3.2.0",
|
"@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",
|
"rollup": "^3.17.3",
|
||||||
"vite": "^4.1.4"
|
"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