feat(directory): add voice
This commit is contained in:
@@ -1 +1,3 @@
|
||||
VITE_APP_TITLE="AKLive2D"
|
||||
VITE_APP_TITLE=AKLive2D
|
||||
VITE_VERSION=0.5.5
|
||||
VITE_APP_VOICE_URL=jp/CN_037.ogg
|
||||
1
directory/Version
Normal file
1
directory/Version
Normal file
@@ -0,0 +1 @@
|
||||
0.5.6
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
:root {
|
||||
--text-color: rgba(255, 255, 255, 0.87);
|
||||
--text-color-full: #fff;
|
||||
--secondary-text-color: #686a72;
|
||||
--date-color: rgba(255, 255, 255, 0.2);
|
||||
--border-color: #707070;
|
||||
@@ -17,7 +18,7 @@
|
||||
|
||||
font-family: "Geometos", "Noto Sans SC", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.1em;
|
||||
line-height: 1.2em;
|
||||
font-weight: 400;
|
||||
|
||||
color: var(--text-color);
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
user-select: none;
|
||||
z-index: 2;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown .text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown .content {
|
||||
@@ -18,6 +18,9 @@
|
||||
}
|
||||
|
||||
.dropdown .icon {
|
||||
position: absolute;
|
||||
bottom: 0.5rem;
|
||||
right: -0.1rem;
|
||||
width: 0.5em;
|
||||
height: 0.5em;
|
||||
display: inline-block;
|
||||
@@ -27,10 +30,10 @@
|
||||
border-right: 0.15em solid var(--text-color);
|
||||
border-top: 0.15em solid var(--text-color);
|
||||
transform: translate(0, -0.15em) rotate(-45deg);
|
||||
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.dropdown .icon.active {
|
||||
.dropdown.active .icon,
|
||||
.dropdown:hover .icon {
|
||||
animation: icon-flash 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
|
||||
}
|
||||
|
||||
@@ -55,7 +58,7 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.dropdown .menu.active {
|
||||
.dropdown.active .menu {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
z-index: 2;
|
||||
@@ -76,7 +79,9 @@
|
||||
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.dropdown .menu .item:hover, .dropdown .menu .item:focus, .dropdown .menu .item.active {
|
||||
.dropdown .menu .item:hover,
|
||||
.dropdown .menu .item:focus,
|
||||
.dropdown .menu .item.active {
|
||||
color: var(--link-highlight-color);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
useState,
|
||||
useEffect
|
||||
useState
|
||||
} from 'react'
|
||||
import './dropdown.css'
|
||||
|
||||
@@ -13,15 +12,15 @@ export default function Dropdown(props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className='dropdown'>
|
||||
<section className={`dropdown ${hidden ? '' : 'active'}`} >
|
||||
<section
|
||||
className='text'
|
||||
onClick={() => toggleDropdown()}
|
||||
>
|
||||
<span className='content'>{props.text}</span>
|
||||
<span className={`icon ${hidden ? '' : 'active'}`}></span>
|
||||
<span className='icon'></span>
|
||||
</section>
|
||||
<ul className={`menu ${hidden ? '' : 'active'}`}>
|
||||
<ul className='menu'>
|
||||
{
|
||||
props.menu.map((item) => {
|
||||
return (
|
||||
|
||||
62
directory/src/component/switch.css
Normal file
62
directory/src/component/switch.css
Normal file
@@ -0,0 +1,62 @@
|
||||
.switch {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
z-index: 2;
|
||||
padding: 8px 36px 8px 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
color: var(--secondary-text-color);
|
||||
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.switch.active {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.switch .content {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.switch .icon-wrapper {
|
||||
color: var(--secondary-text-color);
|
||||
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.switch.active .icon-wrapper {
|
||||
color: var(--text-color-full);
|
||||
}
|
||||
|
||||
.switch .icon {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 18px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
border-left: 2px solid currentColor;
|
||||
border-bottom: 2px solid currentColor;
|
||||
border-right: 2px solid currentColor;
|
||||
border-top: 2px solid currentColor;
|
||||
transform: translate(0, -2px) rotate(-45deg);
|
||||
transition: right cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s, background-color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.switch.active .icon {
|
||||
background-color: currentColor;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.switch .icon-line {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
right: 6px;
|
||||
width: 18px;
|
||||
height: 2px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
background-color: currentColor;
|
||||
z-index: -1;
|
||||
}
|
||||
23
directory/src/component/switch.jsx
Normal file
23
directory/src/component/switch.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import './switch.css';
|
||||
|
||||
export default function Switch(props) {
|
||||
const [on, setOn] = useState(props.on)
|
||||
|
||||
useEffect(() => {
|
||||
setOn(props.on)
|
||||
}, [props.on])
|
||||
|
||||
return (
|
||||
<section
|
||||
className={`switch ${on ? 'active' : ''}`}
|
||||
onClick={() => props.handleOnClick()}
|
||||
>
|
||||
<span className='text'>{props.text}</span>
|
||||
<section className='icon-wrapper'>
|
||||
<span className='icon-line'></span>
|
||||
<span className='icon'></span>
|
||||
</section>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export function HeaderProvider(props) {
|
||||
<HeaderContext.Provider value={{
|
||||
title, setTitle,
|
||||
tabs, setTabs,
|
||||
currentTab, setCurrentTab,
|
||||
currentTab, setCurrentTab
|
||||
}}>
|
||||
{props.children}
|
||||
</HeaderContext.Provider>
|
||||
|
||||
@@ -3,57 +3,65 @@
|
||||
"zh-CN", "en-US"
|
||||
],
|
||||
"key": {
|
||||
"dynamic_compile": {
|
||||
"zh-CN": "干员动态集录",
|
||||
"en-US": "Dynamic Compile"
|
||||
},
|
||||
"home": {
|
||||
"zh-CN": "首页",
|
||||
"en-US": "Home"
|
||||
},
|
||||
"changelogs": {
|
||||
"zh-CN": "更新日志",
|
||||
"en-US": "Changelogs"
|
||||
},
|
||||
"offical_page": {
|
||||
"zh-CN": "官方页面",
|
||||
"en-US": "Offical Page"
|
||||
},
|
||||
"disclaimer": {
|
||||
"zh-CN": "免责声明",
|
||||
"en-US": "Disclaimer"
|
||||
},
|
||||
"disclaimer_content": {
|
||||
"zh-CN": "本网站由 Halyul 设立并为明日方舟社区服务,Halyul 声明本网站完全独立运营,与 上海鹰角网络科技有限公司, Esoteric Software LLC 或其任何关联实体并无任何联系。",
|
||||
"en-US": "This website is set up and operated by Halyul for the benefit of the Arknights Community. Halyul hereby states that this website is dedicated, but not related to Hypergryph Co., Ltd, Esoteric Software LLC or any of its affiliated entity."
|
||||
},
|
||||
"privacy_policy": {
|
||||
"zh-CN": "隐私政策",
|
||||
"en-US": "Privacy Policy"
|
||||
},
|
||||
"contact_us": {
|
||||
"zh-CN": "联系我们",
|
||||
"en-US": "Contact Us"
|
||||
},
|
||||
"all": {
|
||||
"zh-CN": "综合",
|
||||
"en-US": "All"
|
||||
},
|
||||
"elite2": {
|
||||
"zh-CN": "干员晋升",
|
||||
"en-US": "Elite 2"
|
||||
},
|
||||
"skin": {
|
||||
"zh-CN": "干员时装",
|
||||
"en-US": "Skin"
|
||||
},
|
||||
"zh-CN": {
|
||||
"zh-CN": "简体中文",
|
||||
"en-US": "Chinese (Simplified)"
|
||||
},
|
||||
"en-US": {
|
||||
"zh-CN": "英语",
|
||||
"en-US": "English"
|
||||
}
|
||||
"dynamic_compile": {
|
||||
"zh-CN": "动态集录",
|
||||
"en-US": "Dynamic Compile"
|
||||
},
|
||||
"home": {
|
||||
"zh-CN": "首页",
|
||||
"en-US": "Home"
|
||||
},
|
||||
"changelogs": {
|
||||
"zh-CN": "更新日志",
|
||||
"en-US": "Changelogs"
|
||||
},
|
||||
"offical_page": {
|
||||
"zh-CN": "官方页面",
|
||||
"en-US": "Offical Page"
|
||||
},
|
||||
"disclaimer": {
|
||||
"zh-CN": "免责声明",
|
||||
"en-US": "Disclaimer"
|
||||
},
|
||||
"disclaimer_content": {
|
||||
"zh-CN": "本网站由 Halyul 设立并为明日方舟社区服务,Halyul 声明本网站完全独立运营,与 上海鹰角网络科技有限公司, Esoteric Software LLC 或其任何关联实体并无任何联系。",
|
||||
"en-US": "This website is set up and operated by Halyul for the benefit of the Arknights Community. Halyul hereby states that this website is dedicated, but not related to Hypergryph Co., Ltd, Esoteric Software LLC or any of its affiliated entity."
|
||||
},
|
||||
"privacy_policy": {
|
||||
"zh-CN": "隐私政策",
|
||||
"en-US": "Privacy Policy"
|
||||
},
|
||||
"contact_us": {
|
||||
"zh-CN": "联系我们",
|
||||
"en-US": "Contact Us"
|
||||
},
|
||||
"all": {
|
||||
"zh-CN": "综合",
|
||||
"en-US": "All"
|
||||
},
|
||||
"elite2": {
|
||||
"zh-CN": "干员晋升",
|
||||
"en-US": "Elite 2"
|
||||
},
|
||||
"skin": {
|
||||
"zh-CN": "干员时装",
|
||||
"en-US": "Skin"
|
||||
},
|
||||
"voice": {
|
||||
"zh-CN": "语音",
|
||||
"en-US": "Voice"
|
||||
},
|
||||
"live2d": {
|
||||
"zh-CN": "Live2D",
|
||||
"en-US": "Live2D"
|
||||
},
|
||||
"zh-CN": {
|
||||
"zh-CN": "简体中文",
|
||||
"en-US": "Chinese (Simplified)"
|
||||
},
|
||||
"en-US": {
|
||||
"zh-CN": "英语",
|
||||
"en-US": "English"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: 0.1rem;
|
||||
flex: auto;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.home .item-group .item {
|
||||
@@ -41,7 +42,7 @@
|
||||
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-outline{
|
||||
.home .item-group .item .item-outline {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: -6px;
|
||||
@@ -86,11 +87,17 @@
|
||||
background-color: var(--home-item-hover-background-color);
|
||||
}
|
||||
|
||||
.home .item-group .item .item-img .live2d {
|
||||
width: 100%;
|
||||
height: 360px;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
padding: 0.8rem 0.4rem;
|
||||
line-height: 1;
|
||||
line-height: 1.2em;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info .item-title-container {
|
||||
@@ -109,11 +116,15 @@
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info .item-title-container .item-title {
|
||||
line-height: 1.1em;
|
||||
height: 1.25rem;
|
||||
line-height: 1.3em;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info .item-title-container .item-type {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
text-align: center;
|
||||
width: 1.5rem;
|
||||
fill: var(--text-color)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ import {
|
||||
useState,
|
||||
useEffect,
|
||||
useContext,
|
||||
useMemo
|
||||
useRef
|
||||
} from 'react'
|
||||
import {
|
||||
Link,
|
||||
useOutletContext,
|
||||
} from "react-router-dom";
|
||||
import './home.css'
|
||||
import { ConfigContext } from '@/context/useConfigContext';
|
||||
@@ -13,14 +14,19 @@ import { LanguageContext } from '@/context/useLanguageContext';
|
||||
import { HeaderContext } from '@/context/useHeaderContext';
|
||||
import CharIcon from '@/component/char_icon';
|
||||
import MainBorder from '@/component/main_border';
|
||||
import useUmami from '@parcellab/react-use-umami'
|
||||
import useUmami from '@parcellab/react-use-umami';
|
||||
import Switch from '@/component/switch';
|
||||
import spine from '!/libs/spine-player';
|
||||
import '!/libs/spine-player.css';
|
||||
|
||||
export default function Home() {
|
||||
const _trackEvt = useUmami('/')
|
||||
const { setAppbarExtraArea } = useOutletContext()
|
||||
const {
|
||||
setTitle,
|
||||
tabs, setTabs,
|
||||
currentTab, setCurrentTab } = useContext(HeaderContext)
|
||||
setTabs,
|
||||
currentTab, setCurrentTab
|
||||
} = useContext(HeaderContext)
|
||||
const { config } = useContext(ConfigContext)
|
||||
const {
|
||||
language,
|
||||
@@ -29,6 +35,12 @@ export default function Home() {
|
||||
i18n
|
||||
} = useContext(LanguageContext)
|
||||
const [content, setContent] = useState([])
|
||||
const [voiceOn, setVoiceOn] = useState(false)
|
||||
const [live2dOn, setLive2dOn] = useState(false)
|
||||
const [audioUrl, setAudioUrl] = useState('')
|
||||
const audioEl = new Audio(audioUrl)
|
||||
const live2dRefObject = useRef({})
|
||||
const live2dSpineObject = useRef({})
|
||||
|
||||
useEffect(() => {
|
||||
setTitle('dynamic_compile')
|
||||
@@ -48,20 +60,100 @@ export default function Home() {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const value = config.reduce((acc, cur) => {
|
||||
const date = cur.date
|
||||
if (acc[date]) {
|
||||
acc[date].push(cur)
|
||||
} else {
|
||||
acc[date] = [cur]
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
setContent(Object.values(value).sort((a, b) => new Date(b[0].date) - new Date(a[0].date)))
|
||||
setContent(config?.operators || [])
|
||||
}, [config])
|
||||
|
||||
const toggleVoice = () => {
|
||||
setVoiceOn(!voiceOn)
|
||||
setAudioUrl('')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setAppbarExtraArea([
|
||||
(
|
||||
<Switch
|
||||
key="voice"
|
||||
text={i18n.key.voice[language]}
|
||||
on={voiceOn}
|
||||
handleOnClick={() => toggleVoice()}
|
||||
/>
|
||||
// ), (
|
||||
// <Switch
|
||||
// key="live2d"
|
||||
// text={i18n.key.live2d[language]}
|
||||
// on={live2dOn}
|
||||
// handleOnClick={() => setLive2dOn(!live2dOn)}
|
||||
// />
|
||||
)
|
||||
])
|
||||
}, [voiceOn, live2dOn])
|
||||
|
||||
const isShown = (type) => currentTab === 'all' || currentTab === type
|
||||
|
||||
const playVoice = (link) => {
|
||||
if (!voiceOn) return
|
||||
audioEl.src = `/${link}/assets/voice/${import.meta.env.VITE_APP_VOICE_URL}`
|
||||
let startPlayPromise = audioEl.play()
|
||||
if (startPlayPromise !== undefined) {
|
||||
startPlayPromise
|
||||
.then(() => {
|
||||
return
|
||||
})
|
||||
.catch(() => {
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const getLive2d = (link) => {
|
||||
const reactEl = <section className="live2d" ref={ref => {
|
||||
live2dRefObject.current[link] = ref
|
||||
}}></section>
|
||||
return reactEl
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (live2dOn) {
|
||||
Object.keys(live2dRefObject.current).forEach((link) => {
|
||||
const ref = live2dRefObject.current[link]
|
||||
if (ref) {
|
||||
if (live2dSpineObject.current[link]) {
|
||||
return
|
||||
}
|
||||
fetch(`/_assets/dyn_portrait_char_2014_nian_nian%234.json`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
live2dSpineObject.current[link] = new spine.SpinePlayer(ref, {
|
||||
skelUrl: `./assets/dyn_portrait_char_2014_nian_nian%234.skel`,
|
||||
atlasUrl: `./assets/dyn_portrait_char_2014_nian_nian%234.atlas`,
|
||||
rawDataURIs: data,
|
||||
animation: "Idle",
|
||||
premultipliedAlpha: true,
|
||||
alpha: true,
|
||||
backgroundColor: "#00000000",
|
||||
viewport: {
|
||||
debugRender: false,
|
||||
padLeft: `0%`,
|
||||
padRight: `0%`,
|
||||
padTop: `0%`,
|
||||
padBottom: `0%`,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
showControls: false,
|
||||
touch: false,
|
||||
fps: 60,
|
||||
defaultMix: 0,
|
||||
success: function (widget) {
|
||||
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [live2dOn])
|
||||
|
||||
return (
|
||||
<section className="home">
|
||||
{
|
||||
@@ -71,31 +163,44 @@ export default function Home() {
|
||||
<section className="item-group-wrapper" key={v[0].date} hidden={length === 0}>
|
||||
<section className="item-group">
|
||||
{v.map(item => {
|
||||
return (<Link reloadDocument to={`/${item.link}`} className="item" key={item.link} hidden={!isShown(item.type)}>
|
||||
<section className="item-background-filler" />
|
||||
<section className="item-outline" />
|
||||
<section className="item-img">
|
||||
<img src={`/${item.link}/assets/${item.fallback_name.replace("#", "%23")}_portrait.png`} alt={item.codename[language]} />
|
||||
</section>
|
||||
<section className="item-info">
|
||||
<section className="item-title-container">
|
||||
<section className="item-title">{item.codename[language]}</section>
|
||||
<section className="item-type">
|
||||
<CharIcon
|
||||
type={item.type}
|
||||
viewBox={
|
||||
item.type === 'operator' ? '0 0 88.969 71.469' : '0 0 94.563 67.437'
|
||||
} />
|
||||
return (
|
||||
<Link
|
||||
reloadDocument
|
||||
to={`/${item.link}`}
|
||||
className="item"
|
||||
key={item.link}
|
||||
hidden={!isShown(item.type)}
|
||||
onMouseEnter={(e) => playVoice(item.link)}
|
||||
>
|
||||
<section className="item-background-filler" />
|
||||
<section className="item-outline" />
|
||||
<section className="item-img">
|
||||
{live2dOn && item.portrait !== null ? (
|
||||
getLive2d(item.link)
|
||||
) : (
|
||||
<img src={`/${item.link}/assets/${item.fallback_name.replace("#", "%23")}_portrait.png`} alt={item.codename[language]} />
|
||||
)}
|
||||
</section>
|
||||
<section className="item-info">
|
||||
<section className="item-title-container">
|
||||
<section className="item-title">{item.codename[language]}</section>
|
||||
<section className="item-type">
|
||||
<CharIcon
|
||||
type={item.type}
|
||||
viewBox={
|
||||
item.type === 'operator' ? '0 0 88.969 71.469' : '0 0 94.563 67.437'
|
||||
} />
|
||||
</section>
|
||||
</section>
|
||||
<section className="item-text-wrapper">
|
||||
<span className='item-text'>{item.codename[language.startsWith("en") ? alternateLang : textDefaultLang]}</span>
|
||||
</section>
|
||||
<section className="item-info-background" style={{
|
||||
color: item.color
|
||||
}} />
|
||||
</section>
|
||||
<section className="item-text-wrapper">
|
||||
<span className='item-text'>{item.codename[language.startsWith("en") ? alternateLang : textDefaultLang]}</span>
|
||||
</section>
|
||||
<section className="item-info-background" style={{
|
||||
color: item.color
|
||||
}} />
|
||||
</section>
|
||||
</Link>)
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
<section className='item-group-date'>{v[0].date}</section>
|
||||
</section>
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.header .spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.header .dropdown {
|
||||
margin-left: auto;
|
||||
}
|
||||
@@ -140,7 +144,7 @@
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.1em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
.main .main-header .main-tab {
|
||||
@@ -174,7 +178,7 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main .main-header .return-button {
|
||||
.main .return-button {
|
||||
right: -3.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,15 @@ export default function Root(props) {
|
||||
alternateLang,
|
||||
i18n
|
||||
} = useContext(LanguageContext)
|
||||
const { title, tabs, currentTab, setCurrentTab } = useContext(HeaderContext)
|
||||
const {
|
||||
title,
|
||||
tabs,
|
||||
currentTab, setCurrentTab
|
||||
} = useContext(HeaderContext)
|
||||
const [drawerDestinations, setDrawerDestinations] = useState(null)
|
||||
const currentYear = new Date().getFullYear()
|
||||
const [headerTabs, setHeaderTabs] = useState(null)
|
||||
const [appbarExtraArea, setAppbarExtraArea] = useState(null)
|
||||
|
||||
const renderHeaderTabs = (tabs) => {
|
||||
setHeaderTabs(tabs?.map((item) => {
|
||||
@@ -111,6 +116,8 @@ export default function Root(props) {
|
||||
<section className='bar'></section>
|
||||
<section className='bar'></section>
|
||||
</section>
|
||||
<section className='spacer' />
|
||||
{appbarExtraArea}
|
||||
<Dropdown
|
||||
text={i18n.key[language][language]}
|
||||
menu={i18n.available.map((item) => {
|
||||
@@ -150,7 +157,7 @@ export default function Root(props) {
|
||||
}}
|
||||
/>
|
||||
</MainBorder>
|
||||
<Outlet />
|
||||
<Outlet context={{setAppbarExtraArea}}/>
|
||||
</main>
|
||||
<footer className='footer'>
|
||||
<section className='links section'>
|
||||
@@ -176,6 +183,7 @@ export default function Root(props) {
|
||||
<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>Version: {import.meta.env.VITE_APP_VERSION}</span>
|
||||
</section>
|
||||
</footer>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user