refactor(directory): add eslint

This commit is contained in:
Haoyu Xu
2023-03-02 23:53:46 -05:00
parent 1cbf3357d2
commit 5fc6f60b16
18 changed files with 1387 additions and 294 deletions

15
.eslintrc.cjs Normal file
View 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"
},
}

View File

@@ -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,
};

View File

@@ -1,6 +1,7 @@
import {
import React, {
useState
} from 'react'
import PropTypes from 'prop-types';
import './dropdown.css'
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,
};

View File

@@ -1,3 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import './main_border.css';
export default function MainBorder(props) {
@@ -7,3 +9,6 @@ export default function MainBorder(props) {
</section>
)
}
MainBorder.propTypes = {
children: PropTypes.node,
};

View File

@@ -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,
};

View File

@@ -1,3 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import './return_button.css'
export default function ReturnButton(props) {
@@ -31,3 +33,6 @@ export default function ReturnButton(props) {
</>
)
}
ReturnButton.propTypes = {
onClick: PropTypes.func,
};

View File

@@ -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,7 +18,7 @@ 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>
@@ -21,3 +26,8 @@ export default function Switch(props) {
</section>
)
}
Switch.propTypes = {
on: PropTypes.bool,
text: PropTypes.string,
handleOnClick: PropTypes.func,
};

View File

@@ -1,3 +1,4 @@
import React from "react";
import {
useNavigate,
useRouteError

View File

@@ -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";

View File

@@ -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 (

View File

@@ -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
}
@@ -195,3 +187,8 @@ 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,
}

View File

@@ -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'}`}>

View File

@@ -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])
}

View File

@@ -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
};
}

View File

@@ -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 };
}

View File

@@ -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,

View File

@@ -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

File diff suppressed because it is too large Load Diff