From 1c8356bbbb8cbd360323ae348654fc5ccb4e03ac Mon Sep 17 00:00:00 2001 From: Haoyu Xu Date: Wed, 1 Mar 2023 16:22:35 -0500 Subject: [PATCH] feat(directory): add changelogs page --- Version | 2 +- changelogs.yaml | 6 + directory/Version | 2 +- directory/src/App.jsx | 11 +- directory/src/component/char_icon.jsx | 2 +- directory/src/component/popup.css | 5 + directory/src/component/popup.jsx | 8 +- directory/src/context/useConfigContext.jsx | 92 --- directory/src/context/useHeaderContext.jsx | 38 -- directory/src/context/useLanguageContext.jsx | 34 -- directory/src/db/index.js | 16 - directory/src/i18n.json | 8 + directory/src/libs/stable_navigate.jsx | 30 + directory/src/routes/path/changelogs.css | 17 + directory/src/routes/path/changelogs.jsx | 55 +- directory/src/routes/path/home.css | 44 +- directory/src/routes/path/home.jsx | 97 ++- directory/src/routes/path/operator.jsx | 73 ++- directory/src/routes/root.css | 51 +- directory/src/routes/root.jsx | 132 ++-- directory/src/state/config.js | 30 + directory/src/state/header.js | 35 ++ directory/src/state/language.js | 35 ++ libs/charword_table.js | 12 +- libs/config.js | 4 + libs/directory.js | 17 + package.json | 5 +- pnpm-lock.yaml | 604 ++++++++++++++++++- vite.config.js | 9 +- 29 files changed, 1077 insertions(+), 397 deletions(-) create mode 100644 changelogs.yaml delete mode 100644 directory/src/context/useConfigContext.jsx delete mode 100644 directory/src/context/useHeaderContext.jsx delete mode 100644 directory/src/context/useLanguageContext.jsx delete mode 100644 directory/src/db/index.js create mode 100644 directory/src/libs/stable_navigate.jsx create mode 100644 directory/src/state/config.js create mode 100644 directory/src/state/header.js create mode 100644 directory/src/state/language.js diff --git a/Version b/Version index 050ffa7..d9cb810 100644 --- a/Version +++ b/Version @@ -1 +1 @@ -3.3.13 \ No newline at end of file +3.3.25 \ No newline at end of file diff --git a/changelogs.yaml b/changelogs.yaml new file mode 100644 index 0000000..daffd4b --- /dev/null +++ b/changelogs.yaml @@ -0,0 +1,6 @@ +showcase: + 2023/03/01: + - Under Construction :) +directory: + 2023/03/01: + - Under Construction :) \ No newline at end of file diff --git a/directory/Version b/directory/Version index 12697f7..dc2fb79 100644 --- a/directory/Version +++ b/directory/Version @@ -1 +1 @@ -0.5.12 \ No newline at end of file +0.5.21 \ No newline at end of file diff --git a/directory/src/App.jsx b/directory/src/App.jsx index 0444c94..88d7860 100644 --- a/directory/src/App.jsx +++ b/directory/src/App.jsx @@ -11,9 +11,6 @@ import ErrorPage from "@/routes/error-page"; import routes from "@/routes"; import '@/App.css'; import 'reset-css'; -import { LanguageProvider } from '@/context/useLanguageContext'; -import { ConfigProvider } from '@/context/useConfigContext'; -import { HeaderProvider } from '@/context/useHeaderContext'; const router = createBrowserRouter( createRoutesFromElements( @@ -42,12 +39,6 @@ const router = createBrowserRouter( ReactDOM.createRoot(document.getElementById('root')).render( - - - - - - - + ) \ No newline at end of file diff --git a/directory/src/component/char_icon.jsx b/directory/src/component/char_icon.jsx index 6ba5529..950cd03 100644 --- a/directory/src/component/char_icon.jsx +++ b/directory/src/component/char_icon.jsx @@ -5,7 +5,7 @@ export default function CharIcon(props) { props.type === 'operator' ? : - + } diff --git a/directory/src/component/popup.css b/directory/src/component/popup.css index d58c447..91b27f4 100644 --- a/directory/src/component/popup.css +++ b/directory/src/component/popup.css @@ -50,6 +50,11 @@ font-family: "Geometos", "Noto Sans SC", sans-serif; } +.popup .text { + flex-grow: 1; + margin-right: 3rem; +} + .popup .content { line-height: 1.3em; padding: 1rem 1rem 0 1rem; diff --git a/directory/src/component/popup.jsx b/directory/src/component/popup.jsx index 06afe17..7154864 100644 --- a/directory/src/component/popup.jsx +++ b/directory/src/component/popup.jsx @@ -1,6 +1,6 @@ import { useState, - useEffect + useCallback } from 'react' import './popup.css' import ReturnButton from '@/component/return_button'; @@ -9,16 +9,16 @@ import MainBorder from '@/component/main_border'; export default function Popup(props) { const [hidden, setHidden] = useState(true) - const toggle = () => { + const toggle = useCallback(() => { setHidden(!hidden) - } + }, [hidden]) return ( <>
- {props.title} +
{props.title}
diff --git a/directory/src/context/useConfigContext.jsx b/directory/src/context/useConfigContext.jsx deleted file mode 100644 index 542870b..0000000 --- a/directory/src/context/useConfigContext.jsx +++ /dev/null @@ -1,92 +0,0 @@ -import { createContext, useState, useEffect, useCallback } from "react" -import db, { invalidateCache } from "@/db" - -const versionCompare = (v1, v2) => { - const v1Arr = v1.split(".") - const v2Arr = v2.split(".") - for (let i = 0; i < v1Arr.length; i++) { - if (v1Arr[i] > v2Arr[i]) { - return 1 - } else if (v1Arr[i] < v2Arr[i]) { - return -1 - } - } - return 0 -} - -const invalidateRules = (local, version) => { - if (local === undefined) { - // no local version - return true - } - if (version === undefined) { - // no remote version - return false - } - return versionCompare(local, version) < 0 -} - -export const ConfigContext = createContext() - -export function ConfigProvider(props) { - const [config, setConfig] = useState([]) - const [operators, setOperators] = useState([]) - const [version, setVersion] = useState({}) - - const fetchConfig = (version) => { - fetch("/_assets/directory.json").then(res => res.json()).then(data => { - setConfig(data) - db.config.put({ key: "config", value: data }) - resolveOperators(data) - db.config.put({ key: "version", value: version }) - }) - } - - const resolveOperators = useCallback((data) => { - let operatorsList = [] - data.operators.forEach((item) => { - operatorsList = [...operatorsList, ...item] - }) - setOperators(operatorsList) - }, []) - - useEffect(() => { - fetch("/_assets/version.json").then(res => res.json()).then(data => { - db.config.get({ key: "version" }).then((local) => { - if (local === undefined || invalidateRules(local.value.directory, data.directory) || invalidateRules(local.value.showcase, data.showcase)) { - invalidateCache() - fetchConfig(data) - } else { - db.config.get({ key: "config" }).then((local) => { - if (local) { - setConfig(local.value) - resolveOperators(local.value) - } else { - fetchConfig(data) - } - }) - } - }) - setVersion(data) - }) - /* - local.directory | version.directory | action - --------------- | ----------------- | ------ - 1.0.0 | 1.0.0 | use cache - 1.0.0 | 1.0.1 | invalidate cache - 1.0.1 | 1.0.0 | impossible - - local.showcase | version.showcase | action - -------------- | ---------------- | ------ - 1.0.0 | 1.0.0 | use cache - 1.0.0 | 1.0.1 | invalidate cache - 1.0.1 | 1.0.0 | use cache - */ - }, []) - - return ( - - {props.children} - - ) -} \ No newline at end of file diff --git a/directory/src/context/useHeaderContext.jsx b/directory/src/context/useHeaderContext.jsx deleted file mode 100644 index 1fb7d1c..0000000 --- a/directory/src/context/useHeaderContext.jsx +++ /dev/null @@ -1,38 +0,0 @@ -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([]) - const [currentTab, setCurrentTab] = useState([]) - const [appbarExtraArea, setAppbarExtraArea] = useState([]) - - useEffect(() => { - const newTitle = i18n(key) - document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`; - setRealTitle(newTitle) - }, [key, language]) - - return ( - - {props.children} - - ) -} \ No newline at end of file diff --git a/directory/src/context/useLanguageContext.jsx b/directory/src/context/useLanguageContext.jsx deleted file mode 100644 index afe46b7..0000000 --- a/directory/src/context/useLanguageContext.jsx +++ /dev/null @@ -1,34 +0,0 @@ -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(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 ( - - {props.children} - - ) -} \ No newline at end of file diff --git a/directory/src/db/index.js b/directory/src/db/index.js deleted file mode 100644 index 6edf713..0000000 --- a/directory/src/db/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import Dexie from 'dexie'; - -const db = new Dexie('aklive2dDatabase'); -db.version(2).stores({ - image: '++key, blob', - voice: '++key, blob', - config: '++key, value', -}); - -export function invalidateCache() { - db.image.clear(); - db.voice.clear(); - db.config.clear(); -} - -export default db; \ No newline at end of file diff --git a/directory/src/i18n.json b/directory/src/i18n.json index 2511700..819b75c 100644 --- a/directory/src/i18n.json +++ b/directory/src/i18n.json @@ -51,6 +51,14 @@ "zh-CN": "语音", "en-US": "Voice" }, + "showcase": { + "zh-CN": "壁纸", + "en-US": "Wallpaper" + }, + "directory": { + "zh-CN": "目录页", + "en-US": "Directory Page" + }, "zh-CN": { "zh-CN": "简体中文", "en-US": "Chinese (Simplified)" diff --git a/directory/src/libs/stable_navigate.jsx b/directory/src/libs/stable_navigate.jsx new file mode 100644 index 0000000..2c92737 --- /dev/null +++ b/directory/src/libs/stable_navigate.jsx @@ -0,0 +1,30 @@ +// taken from: https://shallowdepth.online/posts/2022/04/why-usenavigate-hook-in-react-router-v6-triggers-waste-re-renders-and-how-to-solve-it/ +import { createContext, useContext, useRef } from 'react' +import { useNavigate } from 'react-router-dom' + +const StableNavigateContext = createContext(null) + +const StableNavigateContextProvider = ({ children }) => { + const navigate = useNavigate() + const navigateRef = useRef(navigate) + + return ( + + {children} + + ) +} + +const useStableNavigate = () => { + const navigateRef = useContext(StableNavigateContext) + if (navigateRef.current === null) + throw new Error('StableNavigate context is not initialized') + + return navigateRef.current +} + +export { + StableNavigateContext, + StableNavigateContextProvider, + useStableNavigate, +} \ No newline at end of file diff --git a/directory/src/routes/path/changelogs.css b/directory/src/routes/path/changelogs.css index e69de29..6a67f58 100644 --- a/directory/src/routes/path/changelogs.css +++ b/directory/src/routes/path/changelogs.css @@ -0,0 +1,17 @@ +.changelogs .item-group { + flex-direction: column; + align-items: stretch; +} + +.changelogs .item-info { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + padding-left: 1rem; +} + +.changelogs .item-info-content { + font-size: 1.5rem; + display: list-item; +} \ No newline at end of file diff --git a/directory/src/routes/path/changelogs.jsx b/directory/src/routes/path/changelogs.jsx index 7c0d62f..e32ff08 100644 --- a/directory/src/routes/path/changelogs.jsx +++ b/directory/src/routes/path/changelogs.jsx @@ -1,32 +1,65 @@ import { useState, useEffect, - useContext } from 'react' import './changelogs.css' -import { HeaderContext } from '@/context/useHeaderContext'; -import { LanguageContext } from '@/context/useLanguageContext'; +import { useHeader } from '@/state/header'; import useUmami from '@parcellab/react-use-umami' +import MainBorder from '@/component/main_border'; export default function Changelogs(props) { const _trackEvt = useUmami('/changelogs') const { setTitle, setTabs, - currentTab, setCurrentTab - } = useContext(HeaderContext) - const { language, i18n } = useContext(LanguageContext) + currentTab, + setHeaderIcon, + } = useHeader() + const [changelogs, setChangelogs] = useState([]) useEffect(() => { setTitle('changelogs') - setTabs([]) + setHeaderIcon(null) + fetch('/_assets/changelogs.json').then(res => res.json()).then(data => { + setChangelogs(data) + }) }, []) + useEffect(() => { + setTabs(changelogs.map((item) => { + return { + key: item[0].key + } + })) + }, [changelogs]) + return ( -
-
- Under Construction :( -
+
+ { + changelogs.map((v) => { + return ( + v.map((item) => { + return ( + + ) + }) + ) + }) + }
) } \ No newline at end of file diff --git a/directory/src/routes/path/home.css b/directory/src/routes/path/home.css index 75ab331..021ba3c 100644 --- a/directory/src/routes/path/home.css +++ b/directory/src/routes/path/home.css @@ -1,11 +1,11 @@ -.home .item-group { +.item-group { padding: 1rem; display: flex; align-items: flex-end; flex-wrap: wrap; } -.home .item-group-date { +.item-group-date { margin: 1.5rem; font-family: "Bender"; font-weight: bold; @@ -17,7 +17,7 @@ user-select: none; } -.home .item-group .item { +.item-group .item { position: relative; cursor: pointer; width: 180px; @@ -25,7 +25,7 @@ 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-background-filler { +.item-group .item .item-background-filler { border-right: 1px solid var(--home-item-background-linear-gradient-color); position: absolute; top: 0; @@ -34,7 +34,7 @@ bottom: 0; } -.home .item-group .item .item-outline { +.item-group .item .item-outline { display: block; position: absolute; opacity: 0; @@ -42,7 +42,7 @@ transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; } -.home .item-group .item .item-outline { +.item-group .item .item-outline { width: 100%; height: 100%; left: -6px; @@ -51,8 +51,8 @@ padding: 6px; } -.home .item-group .item .item-outline::before, -.home .item-group .item .item-outline::after { +.item-group .item .item-outline::before, +.item-group .item .item-outline::after { content: ""; display: block; position: absolute; @@ -63,31 +63,31 @@ border-right: var(--text-color) solid 3px; } -.home .item-group .item .item-outline::before { +.item-group .item .item-outline::before { top: -3px; } -.home .item-group .item .item-outline::after { +.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 { +.item-group .item:hover .item-outline, +.item-group .item:hover .item-info .item-info-background { opacity: 1; visibility: visible; } -.home .item-group .item .item-img { +.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 { +.item-group .item:hover .item-img { background-color: var(--home-item-hover-background-color); } -.home .item-group .item .item-info { +.item-group .item .item-info { white-space: nowrap; position: relative; padding: 0.8rem 0.4rem; @@ -95,7 +95,7 @@ height: 36px; } -.home .item-group .item .item-info .item-title-container { +.item-group .item .item-info .item-title-container { color: white; display: flex; justify-content: space-between; @@ -104,18 +104,18 @@ font-weight: bold; } -.home .item-group .item .item-info .item-title-container .item-title { +.item-group .item .item-info .item-title-container .item-title { flex: 1; overflow: hidden; text-overflow: ellipsis; } -.home .item-group .item .item-info .item-title-container .item-title { +.item-group .item .item-info .item-title-container .item-title { line-height: 1.3em; height: auto; } -.home .item-group .item .item-info .item-title-container .item-type { +.item-group .item .item-info .item-title-container .item-type { display: flex; flex-direction: row; align-items: baseline; @@ -124,13 +124,13 @@ fill: var(--text-color) } -.home .item-group .item .item-info .item-text { +.item-group .item .item-info .item-text { font-size: 0.75rem; font-family: "Geometos"; margin-top: 1rem; } -.home .item-group .item .item-info .item-info-background { +.item-group .item .item-info .item-info-background { position: absolute; top: 0; left: 0; @@ -142,7 +142,7 @@ background-image: linear-gradient(70deg, transparent 40%, currentColor 150%); } -.home .item-group .item .item-info .item-text-wrapper { +.item-group .item .item-info .item-text-wrapper { overflow: hidden; text-overflow: ellipsis; color: var(--secondary-text-color); diff --git a/directory/src/routes/path/home.jsx b/directory/src/routes/path/home.jsx index 70fd5e2..7a3dbec 100644 --- a/directory/src/routes/path/home.jsx +++ b/directory/src/routes/path/home.jsx @@ -1,23 +1,28 @@ import { useState, useEffect, - useContext, useCallback } from 'react' import { NavLink, } from "react-router-dom"; import './home.css' -import { ConfigContext } from '@/context/useConfigContext'; -import { LanguageContext } from '@/context/useLanguageContext'; -import { HeaderContext } from '@/context/useHeaderContext'; +import { useConfig } from '@/state/config'; +import { + useLanguage, + useI18n +} from '@/state/language' +import { useHeader } from '@/state/header'; +import { useAtom } from 'jotai' +import { atomWithStorage } from 'jotai/utils'; import CharIcon from '@/component/char_icon'; import MainBorder from '@/component/main_border'; import useUmami from '@parcellab/react-use-umami'; import Switch from '@/component/switch'; -import db from '@/db'; const audioEl = new Audio() +let isPlaying = false +const voiceOnAtom = atomWithStorage('voiceOn', false) export default function Home() { const _trackEvt = useUmami('/') @@ -25,30 +30,34 @@ export default function Home() { setTitle, setTabs, currentTab, - setAppbarExtraArea - } = useContext(HeaderContext) - const { config } = useContext(ConfigContext) - const { - language, - textDefaultLang, - alternateLang, - i18n - } = useContext(LanguageContext) + setAppbarExtraArea, + setHeaderIcon + } = useHeader() + const { config } = useConfig() + const { textDefaultLang, language, alternateLang } = useLanguage() const [content, setContent] = useState([]) - const [voiceOn, setVoiceOn] = useState(false) + const [voiceOn, setVoiceOn] = useAtom(voiceOnAtom) + const { i18n } = useI18n() useEffect(() => { setTitle('dynamic_compile') - setTabs(['all', 'operator', 'skin']) + setTabs([{ + key: 'all' + }, { + key: 'operator' + }, { + key: 'skin' + }]) + setHeaderIcon(null) }, []) useEffect(() => { setContent(config?.operators || []) }, [config]) - const toggleVoice = () => { + const toggleVoice = useCallback(() => { setVoiceOn(!voiceOn) - } + }, [voiceOn]) useEffect(() => { setAppbarExtraArea([ @@ -63,42 +72,29 @@ export default function Home() { ]) }, [voiceOn, language]) - const isShown = (type) => currentTab === 'all' || currentTab === type + const isShown = useCallback((type) => currentTab === 'all' || currentTab === type, [currentTab]) - const playVoice = useCallback((blob) => { - const audioUrl = URL.createObjectURL(blob) + const playVoice = useCallback((link) => { + const audioUrl = `/${link}/assets/voice/${import.meta.env.VITE_APP_VOICE_URL}` + if (!voiceOn || (audioEl.src === (window.location.href.replace(/\/$/g, '') + audioUrl) && isPlaying)) return audioEl.src = audioUrl let startPlayPromise = audioEl.play() if (startPlayPromise !== undefined) { startPlayPromise .then(() => { + isPlaying = true const audioEndedFunc = () => { audioEl.removeEventListener('ended', audioEndedFunc) - URL.revokeObjectURL(audioUrl) + isPlaying = false } audioEl.addEventListener('ended', audioEndedFunc) }) - .catch(() => { + .catch((e) => { + console.log(e) return }) } - }, []) - - const loadVoice = (link) => { - if (!voiceOn) return - db.voice.get({ key: link }).then((v) => { - if (v) { - playVoice(v.blob) - } else { - fetch(`/${link}/assets/voice/${import.meta.env.VITE_APP_VOICE_URL}`) - .then(res => res.blob()) - .then(blob => { - db.voice.put({ key: link, blob: blob }) - playVoice(blob) - }) - } - }) - } + }, [voiceOn]) return (
@@ -115,7 +111,7 @@ export default function Home() { className="item" key={item.link} hidden={!isShown(item.type)} - onMouseEnter={() => loadVoice(item.link)} + onMouseEnter={() => playVoice(item.link)} >
@@ -158,22 +154,5 @@ export default function Home() { } function ImageElement({ item, language }) { - const [blobUrl, setBlobUrl] = useState(null) - - useEffect(() => { - db.image.get({ key: item.link }).then((v) => { - if (v) { - setBlobUrl(URL.createObjectURL(v.blob)) - } else { - fetch(`/${item.link}/assets/${item.fallback_name.replace("#", "%23")}_portrait.png`) - .then(res => res.blob()) - .then(blob => { - db.image.put({ key: item.link, blob: blob }) - setBlobUrl(URL.createObjectURL(blob)) - }) - } - }) - }, [item.link]) - - return blobUrl ? {item.codename[language]} : null + return {item.codename[language]} } \ No newline at end of file diff --git a/directory/src/routes/path/operator.jsx b/directory/src/routes/path/operator.jsx index 1c83808..000b772 100644 --- a/directory/src/routes/path/operator.jsx +++ b/directory/src/routes/path/operator.jsx @@ -1,35 +1,38 @@ import { useState, useEffect, - useContext + useContext, + useCallback, + useMemo } from 'react' import { useParams, + useNavigate } from "react-router-dom"; import './operator.css' -import { ConfigContext } from '@/context/useConfigContext'; -import { LanguageContext } from '@/context/useLanguageContext'; -import { HeaderContext } from '@/context/useHeaderContext'; +import { useConfig } from '@/state/config'; +import { + useLanguage, +} from '@/state/language' +import { useHeader } from '@/state/header'; import useUmami from '@parcellab/react-use-umami' export default function Operator(props) { - const _trackEvt = useUmami('/operator/:key') - const { operators } = useContext(ConfigContext) - const { - language, - i18n - } = useContext(LanguageContext) + const navigate = useNavigate() + const { operators } = useConfig() + const { language } = useLanguage() const { key } = useParams(); const { setTitle, setTabs, - setAppbarExtraArea - } = useContext(HeaderContext) + setAppbarExtraArea, + setHeaderIcon + } = useHeader() const [config, setConfig] = useState(null) const [spineData, setSpineData] = useState({}) + const _trackEvt = useUmami(`/operator/${key}`) useEffect(() => { - setTabs([]) setAppbarExtraArea([]) }, []) @@ -40,14 +43,54 @@ export default function Operator(props) { fetch(`/_assets/${config.filename.replace("#", "%23")}.json`).then(res => res.json()).then(data => { setSpineData(data) }) + setHeaderIcon(config.type) } - }, [operators]) + }, [operators, key]) + + const getTabName = (item) => { + if (item.type === 'operator') { + return 'operator' + } else { + return item.codename[language].replace(/^(.+)( )(·|\/)()(.+)$/, '$1') + } + } + + const coverToTab = (item) => { + const key = getTabName(item) + return { + key: key, + style: { + color: item.color + }, + onClick: (e, tab) => { + if (tab === key) return + navigate(`/operator/${item.link}`) + } + } + } + + const getOtherEntries = () => { + return operators.filter((item) => item.id === config.id && item.link !== config.link).map((item) => { + return coverToTab(item) + }) + } + + useEffect(() => { + if (config) { + setTabs( + [ + coverToTab(config), + ...getOtherEntries() + ] + ) + } + }, [config, language, key]) useEffect(() => { if (config) { setTitle(config.codename[language]) } - }, [config, language]) + }, [config, language, key]) return (
diff --git a/directory/src/routes/root.css b/directory/src/routes/root.css index 211fcea..34317d2 100644 --- a/directory/src/routes/root.css +++ b/directory/src/routes/root.css @@ -147,26 +147,63 @@ line-height: 1.2em; } +@media (max-width: 600px) { + .main .main-header .main-title { + font-size: 2.5rem; + } +} + +@media (max-width: 480px) { + .main .main-header .main-title { + font-size: 2rem; + } +} + .main .main-header .main-tab { - display: flex; - flex-direction: row; - align-items: center; + flex: auto; + text-align: right; + white-space: nowrap; + user-select: none; } .main .main-header .main-tab .main-tab-item { - font-size: 1em; + font-size: 1.25rem; line-height: 3em; 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; + display: inline-block; + cursor: pointer; + text-decoration: none; +} + +.main .main-header .main-tab .main-tab-item.active .text, +.main .main-header .main-tab .main-tab-item:hover .text { + color: currentColor; +} + +.main .main-header .main-tab .main-tab-item.active, +.main .main-header .main-tab .main-tab-item:hover { + color: var(--link-highlight-color); } .main .main-header .main-tab .main-tab-item.active { - color: var(--link-highlight-color); - border-bottom-color: var(--link-highlight-color); + border-bottom-color: currentColor; +} + +.main .main-header .main-tab .main-tab-item .text { + color: var(--text-color); + transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; +} + +.main .main-icon { + width: 3.88rem; + margin-right: 1.88rem; + fill: var(--text-color); + display: inline-block; + vertical-align: middle; } .main .return-button { diff --git a/directory/src/routes/root.jsx b/directory/src/routes/root.jsx index cea5dc7..8813ddf 100644 --- a/directory/src/routes/root.jsx +++ b/directory/src/routes/root.jsx @@ -1,65 +1,67 @@ import { useState, useEffect, - useContext + useContext, + useMemo, + useCallback } from 'react' import { Outlet, Link, NavLink, useNavigate, - ScrollRestoration } from "react-router-dom"; import './root.css' import routes from '@/routes' -import { ConfigContext } from '@/context/useConfigContext'; -import { HeaderContext } from '@/context/useHeaderContext'; -import { LanguageContext } from '@/context/useLanguageContext'; +import { useConfig } from '@/state/config'; +import { useHeader } from '@/state/header'; +import { + useI18n, + useLanguage, +} from '@/state/language' import Dropdown from '@/component/dropdown'; import Popup from '@/component/popup'; import ReturnButton from '@/component/return_button'; import MainBorder from '@/component/main_border'; +import CharIcon from '@/component/char_icon'; export default function Root(props) { - const navigate = useNavigate() const [drawerHidden, setDrawerHidden] = useState(true) - const { - language, setLanguage, - textDefaultLang, - alternateLang, - i18n, i18nValues - } = useContext(LanguageContext) + const { textDefaultLang, language, alternateLang } = useLanguage() const { title, tabs, currentTab, setCurrentTab, - appbarExtraArea - } = useContext(HeaderContext) - const { version } = useContext(ConfigContext) + appbarExtraArea, + headerIcon + } = useHeader() + const { version } = useConfig() const [drawerDestinations, setDrawerDestinations] = useState(null) - const currentYear = new Date().getFullYear() + const currentYear = useMemo(() => new Date().getFullYear(), []) const [headerTabs, setHeaderTabs] = useState(null) + const { i18n } = useI18n() const renderHeaderTabs = (tabs) => { setHeaderTabs(tabs?.map((item) => { return (
{ - setCurrentTab(item) - item.onClick && item.onClick(e, item) + setCurrentTab(item.key) + item.onClick && item.onClick(e, currentTab) }} + style={item.style} > - {i18n(item)} + {i18n(item.key)}
) })) } - const toggleDrawer = (value) => { + const toggleDrawer = useCallback((value) => { setDrawerHidden(value || !drawerHidden) - } + }, [drawerHidden]) const renderDrawerDestinations = () => { return routes.filter((item) => item.inDrawer).map((item) => { @@ -110,7 +112,7 @@ export default function Root(props) { useEffect(() => { if (tabs.length > 0) { - setCurrentTab(tabs[0]) + setCurrentTab(tabs[0].key) } else { setCurrentTab(null) } @@ -129,18 +131,7 @@ export default function Root(props) {
{appbarExtraArea} - { - return { - name: i18n(item), - value: item - } - })} - onClick={(item) => { - setLanguage(item.value) - }} - /> +