feat(directory): finish home page
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@400;500&family=Noto+Sans+SC&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.cdnfonts.com/css/bender');
|
||||
@import url('https://fonts.cdnfonts.com/css/geometos');
|
||||
|
||||
:root {
|
||||
--text-color: rgba(255, 255, 255, 0.87);
|
||||
--date-color: rgba(255, 255, 255, 0.2);
|
||||
--border-color: #707070;
|
||||
--link-highlight-color: #33b5e5;
|
||||
--drawer-background-color: rgba(0, 0, 0, 0.5);
|
||||
--drawer-background-color: rgba(0, 0, 0, 0.88);
|
||||
--root-background-color: #131313;
|
||||
--home-item-hover-background-color: rgba(67, 67, 67, 0.3);
|
||||
--home-item-background-linear-gradient-color: rgba(255, 255, 255, 0.1);
|
||||
--home-item-outline-color: rgba(214, 214, 214, 0.3);
|
||||
--button-color: #666;
|
||||
|
||||
font-family: "Josefin Sans", "Noto Sans SC", sans-serif;
|
||||
font-family: "Geometos", "Noto Sans SC", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -11,8 +11,9 @@ import ErrorPage from "@/routes/error-page";
|
||||
import routes from "@/routes";
|
||||
import '@/App.css';
|
||||
import 'reset-css';
|
||||
import { TitleProvider } from '@/context/useTitleContext';
|
||||
import { LanguageProvider } from '@/context/useLanguageContext';
|
||||
import { ConfigProvider } from '@/context/useConfigContext';
|
||||
import { HeaderProvider } from '@/context/useHeaderContext';
|
||||
|
||||
const router = createBrowserRouter(
|
||||
createRoutesFromElements(
|
||||
@@ -40,10 +41,12 @@ const router = createBrowserRouter(
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<LanguageProvider>
|
||||
<TitleProvider>
|
||||
<RouterProvider router={router} />
|
||||
</TitleProvider>
|
||||
</LanguageProvider>
|
||||
<ConfigProvider>
|
||||
<LanguageProvider>
|
||||
<HeaderProvider>
|
||||
<RouterProvider router={router} />
|
||||
</HeaderProvider>
|
||||
</LanguageProvider>
|
||||
</ConfigProvider>
|
||||
</React.StrictMode>
|
||||
)
|
||||
13
directory/src/component/char_icon.jsx
Normal file
13
directory/src/component/char_icon.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
export default function CharIcon(props) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox={props.viewBox}>
|
||||
{
|
||||
props.type === 'operator' ?
|
||||
<g><path d="M89 17.5 30.4 57 24.3 71.4 82.9 32.6Z"></path><path d="M0 17.5 58.6 57 64.7 71.4 6.1 32.7Z"> </path><path d="M89 0 30.4 39.5 24.3 53.9 82.9 15.1Z"> </path><path d="M0 0 58.6 39.5 64.7 53.9 6.1 15.2Z"> </path></g>
|
||||
:
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 94.563 67.437"><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>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
.dropdown .menu {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
background-color: var(--background-color);
|
||||
background-color: var(--root-background-color);
|
||||
color: var(--text-color);
|
||||
width: max-content;
|
||||
z-index: -1;
|
||||
|
||||
@@ -28,7 +28,10 @@ export default function Dropdown(props) {
|
||||
<li
|
||||
key={item.name}
|
||||
className={`item${item.name === props.text ? ' active' : ''}`}
|
||||
onClick={() => props.onClick(item)}
|
||||
onClick={() => {
|
||||
props.onClick(item)
|
||||
toggleDropdown()
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</li>
|
||||
|
||||
23
directory/src/component/main_border.css
Normal file
23
directory/src/component/main_border.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.main-border {
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
border-bottom: 1px solid var(--text-color);
|
||||
}
|
||||
|
||||
.main-border::before, .main-border::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
top: -2px;
|
||||
background-color: var(--text-color);
|
||||
}
|
||||
|
||||
.main-border::before {
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
.main-border::after {
|
||||
left: 100%;
|
||||
}
|
||||
7
directory/src/component/main_border.jsx
Normal file
7
directory/src/component/main_border.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import './main_border.css';
|
||||
|
||||
export default function MainBorder(props) {
|
||||
return (
|
||||
<section className="main-border"/>
|
||||
)
|
||||
}
|
||||
@@ -41,13 +41,18 @@
|
||||
.popup .title {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.popup .content {
|
||||
line-height: 1.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.popup .overlay {
|
||||
@@ -66,3 +71,12 @@
|
||||
opacity: 0.5;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.popup .return-button {
|
||||
color: #666;
|
||||
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.popup .return-button:hover {
|
||||
color: var(--text-color);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export default function Popup(props) {
|
||||
<section className='wrapper'>
|
||||
<section className='title'>
|
||||
<span>{props.title}</span>
|
||||
<ReturnButton onClick={toggle} />
|
||||
<ReturnButton onClick={toggle} className="return-button"/>
|
||||
</section>
|
||||
<section className='content'>
|
||||
{props.children}
|
||||
|
||||
@@ -17,20 +17,20 @@
|
||||
.return-button .bar {
|
||||
width: 1rem;
|
||||
height: 0.4rem;
|
||||
background-color: var(--text-color);
|
||||
background-color: currentColor;
|
||||
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.return-button .bar-arrow-left {
|
||||
border-top: 0.24rem solid transparent;
|
||||
border-bottom: 0.24rem solid transparent;
|
||||
border-right: 0.3rem solid var(--text-color);
|
||||
border-right: 0.3rem solid currentColor;
|
||||
}
|
||||
|
||||
.return-button .bar-arrow-right {
|
||||
border-top: 0.24rem solid transparent;
|
||||
border-bottom: 0.24rem solid transparent;
|
||||
border-left: 0.3rem solid var(--text-color);
|
||||
border-left: 0.3rem solid currentColor;
|
||||
}
|
||||
|
||||
.return-button .bar-wrapper:nth-child(1) {
|
||||
|
||||
19
directory/src/context/useConfigContext.jsx
Normal file
19
directory/src/context/useConfigContext.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createContext, useState, useEffect } from "react"
|
||||
|
||||
export const ConfigContext = createContext()
|
||||
|
||||
export function ConfigProvider(props) {
|
||||
const [config, setConfig] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/_assets/directory.json").then(res => res.json()).then(data => {
|
||||
setConfig(data)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider value={{ config }}>
|
||||
{props.children}
|
||||
</ConfigContext.Provider>
|
||||
)
|
||||
}
|
||||
39
directory/src/context/useHeaderContext.jsx
Normal file
39
directory/src/context/useHeaderContext.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
createContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useContext
|
||||
} from "react"
|
||||
import { LanguageContext } from "@/context/useLanguageContext"
|
||||
|
||||
export const HeaderContext = createContext()
|
||||
|
||||
export function HeaderProvider(props) {
|
||||
const [key, setTitle] = useState('')
|
||||
const [title, setRealTitle] = useState('')
|
||||
const {
|
||||
language,
|
||||
i18n
|
||||
} = useContext(LanguageContext)
|
||||
const [tabs, setTabs] = useState(null)
|
||||
const [currentTab, setCurrentTab] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
let newTitle = key
|
||||
if (i18n.key[newTitle]) {
|
||||
newTitle = i18n.key[newTitle][language]
|
||||
}
|
||||
document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`;
|
||||
setRealTitle(newTitle)
|
||||
}, [key, language])
|
||||
|
||||
return (
|
||||
<HeaderContext.Provider value={{
|
||||
title, setTitle,
|
||||
tabs, setTabs,
|
||||
currentTab, setCurrentTab,
|
||||
}}>
|
||||
{props.children}
|
||||
</HeaderContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -4,20 +4,20 @@ import i18n from '@/i18n'
|
||||
export const LanguageContext = createContext()
|
||||
|
||||
export function LanguageProvider(props) {
|
||||
const drawerTextDefaultLang = "en-US"
|
||||
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 [drawerAlternateLang, setDrawerAlternateLang] = useState(null) // drawerAlternateLang will be default to zh-CN if language is en-*
|
||||
const [alternateLang, setAlternateLang] = useState(null) // drawerAlternateLang will be default to zh-CN if language is en-*
|
||||
|
||||
useEffect(() => {
|
||||
setDrawerAlternateLang(language.startsWith("en") ? "zh-CN" : language)
|
||||
setAlternateLang(language.startsWith("en") ? "zh-CN" : language)
|
||||
}, [language])
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider
|
||||
value={{
|
||||
language, setLanguage,
|
||||
drawerTextDefaultLang,
|
||||
drawerAlternateLang,
|
||||
textDefaultLang,
|
||||
alternateLang,
|
||||
i18n
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { createContext, useState, useEffect, useContext } from "react"
|
||||
import { LanguageContext } from '@/context/useLanguageContext';
|
||||
|
||||
export const TitleContext = createContext()
|
||||
|
||||
export function TitleProvider(props) {
|
||||
const { i18n, language } = useContext(LanguageContext)
|
||||
const [fakeTitle, setTitle] = useState('dynamic_compile')
|
||||
const [title, setActualTitle] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
let newTitle = fakeTitle
|
||||
if (i18n.key[fakeTitle]) {
|
||||
newTitle = i18n.key[fakeTitle][language]
|
||||
}
|
||||
document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`;
|
||||
setActualTitle(newTitle)
|
||||
}, [fakeTitle, language])
|
||||
|
||||
return (
|
||||
<TitleContext.Provider value={{ title, setTitle }}>
|
||||
{props.children}
|
||||
</TitleContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -11,14 +11,22 @@
|
||||
"zh-CN": "首页",
|
||||
"en-US": "Home"
|
||||
},
|
||||
"news": {
|
||||
"zh-CN": "新闻",
|
||||
"en-US": "News"
|
||||
"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"
|
||||
@@ -27,6 +35,18 @@
|
||||
"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)"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Home from "@/routes/path/home";
|
||||
import Operator from "@/routes/path/operator";
|
||||
import News from "@/routes/path/news";
|
||||
import Changelogs from "@/routes/path/changelogs";
|
||||
|
||||
export default [
|
||||
{
|
||||
@@ -16,10 +16,10 @@ export default [
|
||||
element: <Operator />,
|
||||
inDrawer: false
|
||||
}, {
|
||||
path: "news",
|
||||
path: "changelogs",
|
||||
index: false,
|
||||
name: "news",
|
||||
element: <News />,
|
||||
name: "changelogs",
|
||||
element: <Changelogs />,
|
||||
inDrawer: true
|
||||
}, {
|
||||
path: "https://ak.hypergryph.com/archive/dynamicCompile/",
|
||||
|
||||
32
directory/src/routes/path/changelogs.jsx
Normal file
32
directory/src/routes/path/changelogs.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useContext
|
||||
} from 'react'
|
||||
import './changelogs.css'
|
||||
import { HeaderContext } from '@/context/useHeaderContext';
|
||||
import { LanguageContext } from '@/context/useLanguageContext';
|
||||
import useUmami from '@parcellab/react-use-umami'
|
||||
|
||||
export default function Changelogs(props) {
|
||||
const _trackEvt = useUmami('/changelogs')
|
||||
const {
|
||||
setTitle,
|
||||
setTabs,
|
||||
currentTab, setCurrentTab
|
||||
} = useContext(HeaderContext)
|
||||
const { language, i18n } = useContext(LanguageContext)
|
||||
|
||||
useEffect(() => {
|
||||
setTitle('changelogs')
|
||||
setTabs([])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section>
|
||||
Under Construction :(
|
||||
</section>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
.home .item-group {
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.home .item-group-date {
|
||||
margin: 1.5rem;
|
||||
font-family: "Bender";
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
color: var(--date-color);
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: 0.1rem;
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.home .item-group .item {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 180px;
|
||||
margin: 1.25rem;
|
||||
background-image: repeating-linear-gradient(90deg, var(--home-item-background-linear-gradient-color) 0, var(--home-item-background-linear-gradient-color) 1px, transparent 1px, transparent 5px);
|
||||
}
|
||||
|
||||
.home .item-group .item .item-outline {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-outline{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: -6px;
|
||||
top: -6px;
|
||||
border: var(--home-item-outline-color) 1px dashed;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-outline::before,
|
||||
.home .item-group .item .item-outline::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -3px;
|
||||
height: 3px;
|
||||
width: 100%;
|
||||
border-left: var(--text-color) solid 3px;
|
||||
border-right: var(--text-color) solid 3px;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-outline::before {
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-outline::after {
|
||||
bottom: -3px;
|
||||
}
|
||||
|
||||
.home .item-group .item:hover .item-outline,
|
||||
.home .item-group .item:hover .item-info .item-info-background {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-img {
|
||||
height: 360px;
|
||||
width: 100%;
|
||||
transition: background-color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.home .item-group .item:hover .item-img {
|
||||
background-color: var(--home-item-hover-background-color);
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
padding: 0.8rem 0.4rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info .item-title-container {
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info .item-title-container .item-title,
|
||||
.home .item-group .item .item-info .item-text-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info .item-title-container .item-type {
|
||||
width: 1.5rem;
|
||||
fill: var(--text-color)
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info .item-text {
|
||||
color: var(--text-color);
|
||||
font-size: 0.75rem;
|
||||
font-family: "Geometos";
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.home .item-group .item .item-info .item-info-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
background-image: linear-gradient(70deg, transparent 40%, currentColor 150%);
|
||||
}
|
||||
@@ -1,32 +1,115 @@
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useContext
|
||||
useContext,
|
||||
useMemo
|
||||
} from 'react'
|
||||
import {
|
||||
Link,
|
||||
} from "react-router-dom";
|
||||
import './home.css'
|
||||
import { TitleContext } from '@/context/useTitleContext';
|
||||
import { ConfigContext } from '@/context/useConfigContext';
|
||||
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'
|
||||
|
||||
export default function Home(props) {
|
||||
const { title, setTitle } = useContext(TitleContext)
|
||||
useEffect(() => {
|
||||
fetch("/_assets/directory.json").then(res => res.json()).then(data => {
|
||||
console.log(data)
|
||||
})
|
||||
}, [])
|
||||
export default function Home() {
|
||||
const _trackEvt = useUmami('/')
|
||||
const {
|
||||
setTitle,
|
||||
setTabs,
|
||||
currentTab, setCurrentTab } = useContext(HeaderContext)
|
||||
const { config } = useContext(ConfigContext)
|
||||
const {
|
||||
language,
|
||||
textDefaultLang,
|
||||
alternateLang,
|
||||
i18n
|
||||
} = useContext(LanguageContext)
|
||||
|
||||
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')
|
||||
}, [])
|
||||
|
||||
const renderContent = () => {
|
||||
const list = config?.filter((v) => currentTab === 'all' || currentTab === v.type)
|
||||
const content = []
|
||||
if (list) {
|
||||
console.log(list)
|
||||
let items = {}
|
||||
list.forEach(element => {
|
||||
if (items[element.date]) {
|
||||
items[element.date].push(toItemEl(element))
|
||||
} else {
|
||||
items[element.date] = [toItemEl(element)]
|
||||
}
|
||||
});
|
||||
items = Object.keys(items).sort().reverse().reduce((r, k) => {
|
||||
r[k] = items[k]
|
||||
return r
|
||||
}, {})
|
||||
for (const [date, group] of Object.entries(items)) {
|
||||
content.push(
|
||||
<section className="item-group-wrapper" key={date}>
|
||||
<section className="item-group">
|
||||
{group.map((v) => v)}
|
||||
<section className='item-group-date'>{date}</section>
|
||||
</section>
|
||||
<MainBorder />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
return content
|
||||
}
|
||||
const toItemEl = (item) => {
|
||||
return (
|
||||
<Link reloadDocument to={`/${item.link}`} className="item" key={item.link}>
|
||||
<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'
|
||||
} />
|
||||
</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>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
const content = useMemo(() => renderContent(), [currentTab, config, language])
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section>
|
||||
{title}
|
||||
<button onClick={() => setTitle('123')}>123</button>
|
||||
</section>
|
||||
<section>
|
||||
22s
|
||||
</section>
|
||||
<section className="home">
|
||||
{content}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useContext
|
||||
} from 'react'
|
||||
import './news.css'
|
||||
import { TitleContext } from '@/context/useTitleContext';
|
||||
|
||||
export default function News(props) {
|
||||
const { title, setTitle } = useContext(TitleContext)
|
||||
|
||||
useEffect(() => {
|
||||
setTitle('news')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section>
|
||||
1
|
||||
<button onClick={() => setTitle('123')}>123</button>
|
||||
</section>
|
||||
<section>
|
||||
2
|
||||
</section>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -4,13 +4,15 @@ import {
|
||||
useContext
|
||||
} from 'react'
|
||||
import './operator.css'
|
||||
import { TitleContext } from '@/context/useTitleContext';
|
||||
import { HeaderContext } from '@/context/useHeaderContext';
|
||||
import useUmami from '@parcellab/react-use-umami'
|
||||
|
||||
export default function Operator(props) {
|
||||
const { title, setTitle } = useContext(TitleContext)
|
||||
const _trackEvt = useUmami('/operator/:key')
|
||||
const { setTitle } = useContext(HeaderContext)
|
||||
|
||||
useEffect(() => {
|
||||
setTitle('Chen')
|
||||
setTitle('chen')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,149 +1,204 @@
|
||||
.main {
|
||||
flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
padding-bottom: 3rem;
|
||||
margin: 0 auto;
|
||||
width: 70%;
|
||||
max-width: 100rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
width: auto;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
width: auto;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 1rem;
|
||||
z-index: 2;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.header .dropdown {
|
||||
margin-left: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.navButton {
|
||||
padding: 0.5rem;
|
||||
font-size: 2rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
z-index: 2;
|
||||
padding: 0.5rem;
|
||||
font-size: 2rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.navButton .bar {
|
||||
width: 2rem;
|
||||
height: 0.2rem;
|
||||
background-color: var(--text-color);
|
||||
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
width: 2rem;
|
||||
height: 0.2rem;
|
||||
background-color: var(--text-color);
|
||||
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.navButton .bar:nth-child(1) {
|
||||
transform: translate(0, -200%);
|
||||
transform: translate(0, -200%);
|
||||
}
|
||||
|
||||
.navButton .bar:nth-child(3) {
|
||||
transform: translate(0, 200%);
|
||||
transform: translate(0, 200%);
|
||||
}
|
||||
|
||||
.navButton.active .bar:nth-child(1) {
|
||||
transform: translate(0, 100%) rotateZ(45deg) scaleX(0.5) translate(-50%);
|
||||
transform: translate(0, 100%) rotateZ(45deg) scaleX(0.5) translate(-50%);
|
||||
}
|
||||
|
||||
.navButton.active .bar:nth-child(2) {
|
||||
transform: rotateZ(-45deg);
|
||||
transform: rotateZ(-45deg);
|
||||
}
|
||||
|
||||
.navButton.active .bar:nth-child(3) {
|
||||
transform: translate(0, -100%) rotateZ(45deg) scaleX(0.5) translate(50%);
|
||||
transform: translate(0, -100%) rotateZ(45deg) scaleX(0.5) translate(50%);
|
||||
}
|
||||
|
||||
.drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: -15rem;
|
||||
width: 15rem;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
transition: left cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: -15rem;
|
||||
width: 15rem;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
transition: left cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.drawer .links {
|
||||
padding: 8rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
background-color: var(--drawer-background-color);
|
||||
height: 100%;
|
||||
width: 15rem;
|
||||
padding: 8rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
background-color: var(--drawer-background-color);
|
||||
height: 100%;
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
.drawer .overlay {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
z-index: 2;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.drawer.active {
|
||||
pointer-events: all;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
pointer-events: all;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.drawer a {
|
||||
.drawer .link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
color: var(--text-color);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.drawer .link:hover {
|
||||
color: var(--link-highlight-color);
|
||||
}
|
||||
|
||||
.drawer .link.active {
|
||||
color: var(--link-highlight-color);
|
||||
}
|
||||
|
||||
.main .main-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main .main-header .main-title {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.main .main-header .main-tab {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
color: var(--text-color);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.main .main-header .main-tab .main-tab-item {
|
||||
font-size: 1em;
|
||||
line-height: 3rem;
|
||||
font-weight: 700;
|
||||
padding: 0 1rem;
|
||||
text-transform: uppercase;
|
||||
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
cursor: pointer;
|
||||
border-bottom: 0.3rem solid transparent;
|
||||
}
|
||||
|
||||
.drawer a:hover {
|
||||
.main .main-header .main-tab .main-tab-item.active {
|
||||
color: var(--link-highlight-color);
|
||||
border-bottom-color: var(--link-highlight-color);
|
||||
}
|
||||
|
||||
.main {
|
||||
margin: 0 auto;
|
||||
width: 90%;
|
||||
max-width: 100rem;
|
||||
.main .main-header .return-button {
|
||||
position: absolute;
|
||||
right: -4rem;
|
||||
bottom: -1.5rem;
|
||||
color: #666;
|
||||
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||
}
|
||||
|
||||
.main .main-header .return-button:hover {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.footer .section {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 1rem 0;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 1rem 0;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
font-family: "Noto Sans SC", sans-serif;
|
||||
}
|
||||
|
||||
.footer .links {
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.footer .links .separator {
|
||||
height: 1rem;
|
||||
border-left: 2px solid var(--border-color);
|
||||
height: 1rem;
|
||||
border-left: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.footer .copyright {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
font-size: 12px;
|
||||
}
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -7,28 +7,47 @@ import {
|
||||
Outlet,
|
||||
Link,
|
||||
NavLink,
|
||||
useNavigation,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
} from "react-router-dom";
|
||||
import './root.css'
|
||||
import routes from '@/routes'
|
||||
import { TitleContext } from '@/context/useTitleContext';
|
||||
import { HeaderContext } from '@/context/useHeaderContext';
|
||||
import { LanguageContext } from '@/context/useLanguageContext';
|
||||
import Dropdown from '@/component/dropdown';
|
||||
import Popup from '@/component/popup';
|
||||
import ReturnButton from '@/component/return_button';
|
||||
import MainBorder from '@/component/main_border';
|
||||
|
||||
export default function Root(props) {
|
||||
const { title, setTitle } = useContext(TitleContext)
|
||||
const navigate = useNavigate()
|
||||
const [drawerHidden, setDrawerHidden] = useState(true)
|
||||
const {
|
||||
language, setLanguage,
|
||||
drawerTextDefaultLang,
|
||||
drawerAlternateLang,
|
||||
textDefaultLang,
|
||||
alternateLang,
|
||||
i18n
|
||||
} = useContext(LanguageContext)
|
||||
const { title, tabs, currentTab, setCurrentTab } = useContext(HeaderContext)
|
||||
const [drawerDestinations, setDrawerDestinations] = useState(null)
|
||||
const currentYear = new Date().getFullYear()
|
||||
const [headerTabs, setHeaderTabs] = useState(null)
|
||||
|
||||
const renderHeaderTabs = (tabs) => {
|
||||
setHeaderTabs(tabs?.map((item) => {
|
||||
return (
|
||||
<section
|
||||
key={item.key}
|
||||
className={`main-tab-item ${currentTab === item.key ? 'active' : ''}`}
|
||||
onClick={(e) => {
|
||||
setCurrentTab(item.key)
|
||||
item.onClick && item.onClick(e, item.key)
|
||||
}}
|
||||
>
|
||||
{item.text[language]}
|
||||
</section>
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
const toggleDrawer = () => {
|
||||
setDrawerHidden(!drawerHidden)
|
||||
@@ -38,25 +57,34 @@ export default function Root(props) {
|
||||
return routes.filter((item) => item.inDrawer).map((item) => {
|
||||
if (typeof item.element.type === 'string') {
|
||||
return (
|
||||
<a key={item.name}
|
||||
href={item.path}
|
||||
target="_blank">
|
||||
<Link reloadDocument
|
||||
key={item.name}
|
||||
to={item.path}
|
||||
target="_blank"
|
||||
className="link"
|
||||
onClick={() => toggleDrawer()}
|
||||
>
|
||||
<section>
|
||||
{i18n.key[item.name][drawerTextDefaultLang]}
|
||||
{i18n.key[item.name][textDefaultLang]}
|
||||
</section>
|
||||
<section>
|
||||
{i18n.key[item.name][drawerAlternateLang]}
|
||||
{i18n.key[item.name][alternateLang]}
|
||||
</section>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<NavLink to={item.path} key={item.name}>
|
||||
<NavLink
|
||||
to={item.path}
|
||||
key={item.name}
|
||||
className="link"
|
||||
onClick={() => toggleDrawer()}
|
||||
>
|
||||
<section>
|
||||
{i18n.key[item.name][drawerTextDefaultLang]}
|
||||
{i18n.key[item.name][textDefaultLang]}
|
||||
</section>
|
||||
<section>
|
||||
{i18n.key[item.name][drawerAlternateLang]}
|
||||
{i18n.key[item.name][alternateLang]}
|
||||
</section>
|
||||
</NavLink>
|
||||
)
|
||||
@@ -66,7 +94,11 @@ export default function Root(props) {
|
||||
|
||||
useEffect(() => {
|
||||
setDrawerDestinations(renderDrawerDestinations())
|
||||
}, [drawerAlternateLang])
|
||||
}, [alternateLang])
|
||||
|
||||
useEffect(() => {
|
||||
renderHeaderTabs(tabs)
|
||||
}, [tabs, currentTab, language])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -104,21 +136,33 @@ export default function Root(props) {
|
||||
/>
|
||||
</nav>
|
||||
<main className='main'>
|
||||
<section>
|
||||
<section>{title}</section>
|
||||
<section>
|
||||
{/* <NavLink to="/home">All</NavLink>
|
||||
<NavLink to="/about">Operator</NavLink>
|
||||
<NavLink to="/contact">Skill</NavLink> */}
|
||||
<section className='main-header'>
|
||||
<section className='main-title'>{title}</section>
|
||||
<section className='main-tab'>
|
||||
{headerTabs}
|
||||
</section>
|
||||
<ReturnButton
|
||||
className='return-button'
|
||||
onClick={() => {
|
||||
navigate("/")
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
<MainBorder />
|
||||
<Outlet />
|
||||
</main>
|
||||
<footer className='footer'>
|
||||
<section className='links section'>
|
||||
<a href="https://privacy.halyul.dev" target="_blank" className='item'>{i18n.key.privacy_policy[language]}</a>
|
||||
<Popup
|
||||
className='item'
|
||||
title={i18n.key.disclaimer[language]}
|
||||
>
|
||||
{i18n.key.disclaimer_content[language]}
|
||||
</Popup>
|
||||
<span className='separator' />
|
||||
<a href="https://github.com/Halyul/aklive2d" target="_blank" className='item'>Github</a>
|
||||
<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'/>
|
||||
<Popup
|
||||
className='item'
|
||||
@@ -128,9 +172,9 @@ export default function Root(props) {
|
||||
</Popup>
|
||||
</section>
|
||||
<section className='copyright section'>
|
||||
<span>Spine Runtimes © 2013 - 2019, Esoteric Software LLC</span>
|
||||
<span>Assets © 2017 - 2023 上海鹰角网络科技有限公司</span>
|
||||
<span>Source Code © 2021 - 2023 Halyul</span>
|
||||
<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>
|
||||
</section>
|
||||
</footer>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user