feat(directory): init operator page
This commit is contained in:
@@ -37,7 +37,7 @@ async function main() {
|
||||
for (const [key, _] of Object.entries(__config.operators)) {
|
||||
OPERATOR_NAMES.push(key)
|
||||
}
|
||||
increase(__projetRoot)
|
||||
__config.version.showcase = increase(__projetRoot)
|
||||
break
|
||||
case 'preview':
|
||||
assert(OPERATOR_NAMES.length !== 0, 'Please set the operator name.')
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
VITE_APP_TITLE=AKLive2D
|
||||
VITE_VERSION=0.5.7
|
||||
VITE_VERSION=0.5.9
|
||||
VITE_APP_VOICE_URL=jp/CN_037.ogg
|
||||
VITE_SHOWCASE_VERSION=3.3.13
|
||||
@@ -1 +1 @@
|
||||
0.5.8
|
||||
0.5.9
|
||||
@@ -32,6 +32,7 @@ const router = createBrowserRouter(
|
||||
index={route.index}
|
||||
path={route.path}
|
||||
element={route.element}
|
||||
loader={route.loader}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
flex-wrap: nowrap;
|
||||
width: 480px;
|
||||
max-width: 480px;
|
||||
height: fit-content;
|
||||
margin: 0 auto;
|
||||
background-color: var(--root-background-color);
|
||||
@@ -55,6 +55,20 @@
|
||||
padding: 1rem 1rem 0 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.popup .title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.popup .content {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.popup .return-button {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.popup .overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
import { createContext, useState, useEffect } from "react"
|
||||
import db from "@/db"
|
||||
|
||||
export const ConfigContext = createContext()
|
||||
|
||||
export function ConfigProvider(props) {
|
||||
const [config, setConfig] = useState([])
|
||||
const [operators, setOperators] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/_assets/directory.json").then(res => res.json()).then(data => {
|
||||
setConfig(data)
|
||||
db.config.put({ key: "config", value: data })
|
||||
let operatorsList = []
|
||||
data.operators.forEach((item) => {
|
||||
operatorsList = [...operatorsList, ...item]
|
||||
})
|
||||
setOperators(operatorsList)
|
||||
})
|
||||
fetch("/_assets/version.json").then(res => res.json()).then(data => {
|
||||
db.config.put({ key: "version", value: data })
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider value={{ config }}>
|
||||
<ConfigContext.Provider value={{ config, operators }}>
|
||||
{props.children}
|
||||
</ConfigContext.Provider>
|
||||
)
|
||||
|
||||
@@ -15,15 +15,12 @@ export function HeaderProvider(props) {
|
||||
language,
|
||||
i18n
|
||||
} = useContext(LanguageContext)
|
||||
const [tabs, setTabs] = useState(null)
|
||||
const [tabs, setTabs] = useState([])
|
||||
const [currentTab, setCurrentTab] = useState([])
|
||||
const [appbarExtraArea, setAppbarExtraArea] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
let newTitle = key
|
||||
if (i18n.key[newTitle]) {
|
||||
newTitle = i18n.key[newTitle][language]
|
||||
}
|
||||
const newTitle = i18n(key)
|
||||
document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`;
|
||||
setRealTitle(newTitle)
|
||||
}, [key, language])
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import { createContext, useState, useEffect } from "react"
|
||||
import i18n from '@/i18n'
|
||||
import { createContext, useState, useEffect, useCallback } from "react"
|
||||
import i18nValues from '@/i18n'
|
||||
|
||||
export const LanguageContext = createContext()
|
||||
|
||||
export function LanguageProvider(props) {
|
||||
const textDefaultLang = "en-US"
|
||||
const [language, setLanguage] = useState(i18n.available.includes(navigator.language) ? navigator.language : "en-US") // language will be default to en-US if not available
|
||||
const [language, setLanguage] = useState(i18nValues.available.includes(navigator.language) ? navigator.language : "en-US") // language will be default to en-US if not available
|
||||
const [alternateLang, setAlternateLang] = useState(null) // drawerAlternateLang will be default to zh-CN if language is en-*
|
||||
|
||||
useEffect(() => {
|
||||
setAlternateLang(language.startsWith("en") ? "zh-CN" : language)
|
||||
}, [language])
|
||||
|
||||
const i18n = useCallback((key, preferredLanguage = language) => {
|
||||
if (i18nValues.key[key]) {
|
||||
return i18nValues.key[key][preferredLanguage]
|
||||
}
|
||||
return key
|
||||
}, [language])
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider
|
||||
value={{
|
||||
language, setLanguage,
|
||||
textDefaultLang,
|
||||
alternateLang,
|
||||
i18n
|
||||
i18n, i18nValues
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -4,6 +4,7 @@ const db = new Dexie('aklive2dDatabase');
|
||||
db.version(2).stores({
|
||||
image: '++key, blob',
|
||||
voice: '++key, blob',
|
||||
config: '++key, value',
|
||||
});
|
||||
|
||||
export default db;
|
||||
@@ -39,12 +39,12 @@
|
||||
"zh-CN": "综合",
|
||||
"en-US": "All"
|
||||
},
|
||||
"elite2": {
|
||||
"zh-CN": "干员晋升",
|
||||
"operator": {
|
||||
"zh-CN": "精英2",
|
||||
"en-US": "Elite 2"
|
||||
},
|
||||
"skin": {
|
||||
"zh-CN": "干员时装",
|
||||
"zh-CN": "时装",
|
||||
"en-US": "Skin"
|
||||
},
|
||||
"voice": {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
useCallback
|
||||
} from 'react'
|
||||
import {
|
||||
Link,
|
||||
NavLink,
|
||||
} from "react-router-dom";
|
||||
import './home.css'
|
||||
import { ConfigContext } from '@/context/useConfigContext';
|
||||
@@ -24,7 +24,7 @@ export default function Home() {
|
||||
const {
|
||||
setTitle,
|
||||
setTabs,
|
||||
currentTab, setCurrentTab,
|
||||
currentTab,
|
||||
setAppbarExtraArea
|
||||
} = useContext(HeaderContext)
|
||||
const { config } = useContext(ConfigContext)
|
||||
@@ -39,19 +39,7 @@ export default function Home() {
|
||||
|
||||
useEffect(() => {
|
||||
setTitle('dynamic_compile')
|
||||
setTabs([
|
||||
{
|
||||
key: 'all',
|
||||
text: i18n.key.all
|
||||
}, {
|
||||
key: 'operator',
|
||||
text: i18n.key.elite2
|
||||
}, {
|
||||
key: 'skin',
|
||||
text: i18n.key.skin
|
||||
}
|
||||
])
|
||||
setCurrentTab('all')
|
||||
setTabs(['all', 'operator', 'skin'])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -67,7 +55,7 @@ export default function Home() {
|
||||
(
|
||||
<Switch
|
||||
key="voice"
|
||||
text={i18n.key.voice[language]}
|
||||
text={i18n('voice')}
|
||||
on={voiceOn}
|
||||
handleOnClick={() => toggleVoice()}
|
||||
/>
|
||||
@@ -122,9 +110,8 @@ export default function Home() {
|
||||
<section className="item-group">
|
||||
{v.map(item => {
|
||||
return (
|
||||
<Link
|
||||
reloadDocument
|
||||
to={`/${item.link}`}
|
||||
<NavLink
|
||||
to={`/operator/${item.link}`}
|
||||
className="item"
|
||||
key={item.link}
|
||||
hidden={!isShown(item.type)}
|
||||
@@ -156,7 +143,7 @@ export default function Home() {
|
||||
color: item.color
|
||||
}} />
|
||||
</section>
|
||||
</Link>
|
||||
</NavLink>
|
||||
)
|
||||
})}
|
||||
<section className='item-group-date'>{v[0].date}</section>
|
||||
|
||||
@@ -3,18 +3,52 @@ import {
|
||||
useEffect,
|
||||
useContext
|
||||
} from 'react'
|
||||
import {
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
import './operator.css'
|
||||
import { ConfigContext } from '@/context/useConfigContext';
|
||||
import { LanguageContext } from '@/context/useLanguageContext';
|
||||
import { HeaderContext } from '@/context/useHeaderContext';
|
||||
import useUmami from '@parcellab/react-use-umami'
|
||||
|
||||
export default function Operator(props) {
|
||||
const _trackEvt = useUmami('/operator/:key')
|
||||
const { setTitle } = useContext(HeaderContext)
|
||||
const { operators } = useContext(ConfigContext)
|
||||
const {
|
||||
language,
|
||||
i18n
|
||||
} = useContext(LanguageContext)
|
||||
const { key } = useParams();
|
||||
const {
|
||||
setTitle,
|
||||
setTabs,
|
||||
setAppbarExtraArea
|
||||
} = useContext(HeaderContext)
|
||||
const [config, setConfig] = useState(null)
|
||||
const [spineData, setSpineData] = useState({})
|
||||
|
||||
useEffect(() => {
|
||||
setTitle('chen')
|
||||
setTabs([])
|
||||
setAppbarExtraArea([])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const config = operators.find((item) => item.link === key)
|
||||
if (config) {
|
||||
setConfig(config)
|
||||
fetch(`/_assets/${config.filename.replace("#", "%23")}.json`).then(res => res.json()).then(data => {
|
||||
setSpineData(data)
|
||||
})
|
||||
}
|
||||
}, [operators])
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setTitle(config.codename[language])
|
||||
}
|
||||
}, [config, language])
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section>
|
||||
|
||||
@@ -191,7 +191,6 @@
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 1rem 0;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
@@ -200,12 +199,22 @@
|
||||
|
||||
.footer .links {
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.footer .links .separator {
|
||||
height: 1rem;
|
||||
.footer .links .item {
|
||||
padding: 0 1rem;
|
||||
border-left: 2px solid var(--border-color);
|
||||
height: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.footer .links .item:first-of-type {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.footer .copyright {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Link,
|
||||
NavLink,
|
||||
useNavigate,
|
||||
ScrollRestoration
|
||||
} from "react-router-dom";
|
||||
import './root.css'
|
||||
import routes from '@/routes'
|
||||
@@ -25,7 +26,7 @@ export default function Root(props) {
|
||||
language, setLanguage,
|
||||
textDefaultLang,
|
||||
alternateLang,
|
||||
i18n
|
||||
i18n, i18nValues
|
||||
} = useContext(LanguageContext)
|
||||
const {
|
||||
title,
|
||||
@@ -41,21 +42,21 @@ export default function Root(props) {
|
||||
setHeaderTabs(tabs?.map((item) => {
|
||||
return (
|
||||
<section
|
||||
key={item.key}
|
||||
className={`main-tab-item ${currentTab === item.key ? 'active' : ''}`}
|
||||
key={item}
|
||||
className={`main-tab-item ${currentTab === item ? 'active' : ''}`}
|
||||
onClick={(e) => {
|
||||
setCurrentTab(item.key)
|
||||
item.onClick && item.onClick(e, item.key)
|
||||
setCurrentTab(item)
|
||||
item.onClick && item.onClick(e, item)
|
||||
}}
|
||||
>
|
||||
{item.text[language]}
|
||||
{i18n(item)}
|
||||
</section>
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
const toggleDrawer = () => {
|
||||
setDrawerHidden(!drawerHidden)
|
||||
const toggleDrawer = (value) => {
|
||||
setDrawerHidden(value || !drawerHidden)
|
||||
}
|
||||
|
||||
const renderDrawerDestinations = () => {
|
||||
@@ -67,13 +68,13 @@ export default function Root(props) {
|
||||
to={item.path}
|
||||
target="_blank"
|
||||
className="link"
|
||||
onClick={() => toggleDrawer()}
|
||||
onClick={() => toggleDrawer(false)}
|
||||
>
|
||||
<section>
|
||||
{i18n.key[item.name][textDefaultLang]}
|
||||
{i18n(item.name, textDefaultLang)}
|
||||
</section>
|
||||
<section>
|
||||
{i18n.key[item.name][alternateLang]}
|
||||
{i18n(item.name, alternateLang)}
|
||||
</section>
|
||||
</Link>
|
||||
)
|
||||
@@ -83,13 +84,13 @@ export default function Root(props) {
|
||||
to={item.path}
|
||||
key={item.name}
|
||||
className="link"
|
||||
onClick={() => toggleDrawer()}
|
||||
onClick={() => toggleDrawer(false)}
|
||||
>
|
||||
<section>
|
||||
{i18n.key[item.name][textDefaultLang]}
|
||||
{i18n(item.name, textDefaultLang)}
|
||||
</section>
|
||||
<section>
|
||||
{i18n.key[item.name][alternateLang]}
|
||||
{i18n(item.name, alternateLang)}
|
||||
</section>
|
||||
</NavLink>
|
||||
)
|
||||
@@ -105,6 +106,14 @@ export default function Root(props) {
|
||||
renderHeaderTabs(tabs)
|
||||
}, [tabs, currentTab, language])
|
||||
|
||||
useEffect(() => {
|
||||
if (tabs.length > 0) {
|
||||
setCurrentTab(tabs[0])
|
||||
} else {
|
||||
setCurrentTab(null)
|
||||
}
|
||||
}, [tabs])
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className='header'>
|
||||
@@ -119,10 +128,10 @@ export default function Root(props) {
|
||||
<section className='spacer' />
|
||||
{appbarExtraArea}
|
||||
<Dropdown
|
||||
text={i18n.key[language][language]}
|
||||
menu={i18n.available.map((item) => {
|
||||
text={i18n(language)}
|
||||
menu={i18nValues.available.map((item) => {
|
||||
return {
|
||||
name: i18n.key[item][language],
|
||||
name: i18n(item),
|
||||
value: item
|
||||
}
|
||||
})}
|
||||
@@ -161,24 +170,29 @@ export default function Root(props) {
|
||||
</main>
|
||||
<footer className='footer'>
|
||||
<section className='links section'>
|
||||
<section className="item">
|
||||
<Popup
|
||||
className='item'
|
||||
title={i18n.key.disclaimer[language]}
|
||||
className='link'
|
||||
title={i18n('disclaimer')}
|
||||
>
|
||||
{i18n.key.disclaimer_content[language]}
|
||||
{i18n('disclaimer_content')}
|
||||
</Popup>
|
||||
<span className='separator' />
|
||||
<Link reloadDocument to="https://privacy.halyul.dev" target="_blank" className='item'>{i18n.key.privacy_policy[language]}</Link>
|
||||
<span className='separator' />
|
||||
<Link reloadDocument to="https://github.com/Halyul/aklive2d" target="_blank" className='item'>Github</Link>
|
||||
<span className='separator'/>
|
||||
</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='item'
|
||||
title={i18n.key.contact_us[language]}
|
||||
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>
|
||||
@@ -186,6 +200,12 @@ export default function Root(props) {
|
||||
<span>Version: {import.meta.env.VITE_VERSION}</span>
|
||||
</section>
|
||||
</footer>
|
||||
<ScrollRestoration
|
||||
getKey={(location, matches) => {
|
||||
// default behavior
|
||||
return location.pathname;
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ export default function () {
|
||||
}, {}))
|
||||
.sort((a, b) => Date.parse(b[0].date) - Date.parse(a[0].date)),
|
||||
}
|
||||
const versionJson = __config.version
|
||||
writeSync(JSON.stringify(directoryJson, null), path.join(targetFolder, "directory.json"))
|
||||
writeSync(JSON.stringify(versionJson, null), path.join(targetFolder, "version.json"))
|
||||
filesToCopy.forEach((key) => {
|
||||
copy(path.join(sourceFolder, key, 'assets.json'), path.join(targetFolder, `${__config.operators[key].filename}.json`))
|
||||
})
|
||||
|
||||
@@ -9,5 +9,7 @@ export function increase(dir) {
|
||||
// release version will be lagged by 0.0.1
|
||||
const version = read(dir)
|
||||
const [major, minor, patch] = version.split('.')
|
||||
writeSync(`${major}.${minor}.${+patch + 1}`, path.join(dir, 'Version'))
|
||||
const newVersion = `${major}.${minor}.${+patch + 1}`
|
||||
writeSync(newVersion, path.join(dir, 'Version'))
|
||||
return newVersion
|
||||
}
|
||||
@@ -63,7 +63,7 @@ class ViteRunner {
|
||||
this.#dev(viteConfig)
|
||||
break
|
||||
case 'build':
|
||||
increase(configObj.versionDir)
|
||||
this.#globalConfig.version.showcase = increase(configObj.versionDir)
|
||||
case 'build-all':
|
||||
this.#build(viteConfig)
|
||||
break
|
||||
@@ -133,7 +133,7 @@ class ViteRunner {
|
||||
|
||||
get #directoryConfig() {
|
||||
if (process.env.npm_lifecycle_event === 'vite:directory:build') {
|
||||
increase(path.join(__projetRoot, "directory"))
|
||||
this.#globalConfig.version.directory = increase(path.join(__projetRoot, "directory"))
|
||||
}
|
||||
const directoryDir = path.resolve(__projetRoot, 'directory')
|
||||
writeSync((new EnvGenerator()).generate([
|
||||
@@ -146,6 +146,9 @@ class ViteRunner {
|
||||
}, {
|
||||
key: "app_voice_url",
|
||||
value: this.#globalConfig.directory.voice
|
||||
}, {
|
||||
key: "showcase_version",
|
||||
value: this.#globalConfig.version.showcase
|
||||
}
|
||||
]), path.join(directoryDir, '.env'))
|
||||
this.#mode = process.argv[3]
|
||||
|
||||
Reference in New Issue
Block a user