feat(directory): half baked operator page :)
This commit is contained in:
@@ -56,7 +56,7 @@ async function main() {
|
|||||||
await background.process()
|
await background.process()
|
||||||
const backgrounds = ['operator_bg.png', ...background.files]
|
const backgrounds = ['operator_bg.png', ...background.files]
|
||||||
|
|
||||||
directory()
|
directory({backgrounds, charwordTable})
|
||||||
|
|
||||||
for (const OPERATOR_NAME of OPERATOR_NAMES) {
|
for (const OPERATOR_NAME of OPERATOR_NAMES) {
|
||||||
const OPERATOR_SOURCE_FOLDER = path.join(__projetRoot, __config.folder.operator)
|
const OPERATOR_SOURCE_FOLDER = path.join(__projetRoot, __config.folder.operator)
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
VITE_APP_TITLE=AKLive2D
|
VITE_APP_TITLE=AKLive2D
|
||||||
VITE_APP_VOICE_URL=jp/CN_037.ogg
|
VITE_APP_VOICE_URL=jp/CN_037.ogg
|
||||||
|
VITE_VOICE_FOLDERS={"main":"voice","sub":[{"name":"jp","lang":"JP"},{"name":"cn","lang":"CN_MANDARIN"},{"name":"en","lang":"EN"},{"name":"kr","lang":"KR"},{"name":"custom","lang":"CUSTOM"}]}
|
||||||
@@ -1 +1 @@
|
|||||||
0.5.21
|
0.5.24
|
||||||
@@ -41,7 +41,6 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: var(--root-background-color);
|
background-color: var(--root-background-color);
|
||||||
color: var(--text-color);
|
|
||||||
width: max-content;
|
width: max-content;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
top: 2rem;
|
top: 2rem;
|
||||||
@@ -56,6 +55,7 @@
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
color: var(--link-highlight-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown.active .menu {
|
.dropdown.active .menu {
|
||||||
@@ -79,10 +79,14 @@
|
|||||||
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown .menu .item:hover,
|
.dropdown .menu .item:hover .text,
|
||||||
.dropdown .menu .item:focus,
|
.dropdown .menu .item:focus .text,
|
||||||
.dropdown .menu .item.active {
|
.dropdown .menu .item.active .text {
|
||||||
color: var(--link-highlight-color);
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .text {
|
||||||
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown .overlay {
|
.dropdown .overlay {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default function Dropdown(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className={`dropdown ${hidden ? '' : 'active'}`} >
|
<section className={`dropdown ${props.className} ${hidden ? '' : 'active'}`} >
|
||||||
<section
|
<section
|
||||||
className='text'
|
className='text'
|
||||||
onClick={() => toggleDropdown()}
|
onClick={() => toggleDropdown()}
|
||||||
@@ -20,19 +20,19 @@ export default function Dropdown(props) {
|
|||||||
<span className='content'>{props.text}</span>
|
<span className='content'>{props.text}</span>
|
||||||
<span className='icon'></span>
|
<span className='icon'></span>
|
||||||
</section>
|
</section>
|
||||||
<ul className='menu'>
|
<ul className='menu' style={props.activeColor}>
|
||||||
{
|
{
|
||||||
props.menu.map((item) => {
|
props.menu.map((item) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={item.name}
|
key={item.name}
|
||||||
className={`item${item.name === props.text ? ' active' : ''}`}
|
className={`item${item.name === props.text || (props.activeRule && props.activeRule(item)) ? ' active' : ''}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onClick(item)
|
props.onClick(item)
|
||||||
toggleDropdown()
|
toggleDropdown()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.name}
|
<section className="text">{item.name}</section>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -54,11 +54,43 @@
|
|||||||
"showcase": {
|
"showcase": {
|
||||||
"zh-CN": "壁纸",
|
"zh-CN": "壁纸",
|
||||||
"en-US": "Wallpaper"
|
"en-US": "Wallpaper"
|
||||||
},
|
},
|
||||||
"directory": {
|
"directory": {
|
||||||
"zh-CN": "目录页",
|
"zh-CN": "目录页",
|
||||||
"en-US": "Directory Page"
|
"en-US": "Directory Page"
|
||||||
},
|
},
|
||||||
|
"animation": {
|
||||||
|
"zh-CN": "动画",
|
||||||
|
"en-US": "Animation"
|
||||||
|
},
|
||||||
|
"backgrounds": {
|
||||||
|
"zh-CN": "背景",
|
||||||
|
"en-US": "Backgrounds"
|
||||||
|
},
|
||||||
|
"CN_MANDARIN": {
|
||||||
|
"zh-CN": "普通话",
|
||||||
|
"en-US": "Mandarin"
|
||||||
|
},
|
||||||
|
"JP": {
|
||||||
|
"zh-CN": "日语",
|
||||||
|
"en-US": "Japanese"
|
||||||
|
},
|
||||||
|
"KR": {
|
||||||
|
"zh-CN": "韩语",
|
||||||
|
"en-US": "Korean"
|
||||||
|
},
|
||||||
|
"EN": {
|
||||||
|
"zh-CN": "英语",
|
||||||
|
"en-US": "English"
|
||||||
|
},
|
||||||
|
"ITA": {
|
||||||
|
"zh-CN": "意大利语",
|
||||||
|
"en-US": "Italian"
|
||||||
|
},
|
||||||
|
"CN_TOPOLECT": {
|
||||||
|
"zh-CN": "中文方言",
|
||||||
|
"en-US": "Chinese Topolect"
|
||||||
|
},
|
||||||
"zh-CN": {
|
"zh-CN": {
|
||||||
"zh-CN": "简体中文",
|
"zh-CN": "简体中文",
|
||||||
"en-US": "Chinese (Simplified)"
|
"en-US": "Chinese (Simplified)"
|
||||||
|
|||||||
@@ -9,12 +9,6 @@ export default [
|
|||||||
name: "home",
|
name: "home",
|
||||||
element: <Home />,
|
element: <Home />,
|
||||||
inDrawer: true
|
inDrawer: true
|
||||||
}, {
|
|
||||||
path: "operator/:key",
|
|
||||||
index: false,
|
|
||||||
name: "operator",
|
|
||||||
element: <Operator />,
|
|
||||||
inDrawer: false
|
|
||||||
}, {
|
}, {
|
||||||
path: "changelogs",
|
path: "changelogs",
|
||||||
index: false,
|
index: false,
|
||||||
@@ -27,5 +21,11 @@ export default [
|
|||||||
name: "offical_page",
|
name: "offical_page",
|
||||||
element: <a/>,
|
element: <a/>,
|
||||||
inDrawer: true
|
inDrawer: true
|
||||||
}
|
}, {
|
||||||
|
path: ":key",
|
||||||
|
index: false,
|
||||||
|
name: "operator",
|
||||||
|
element: <Operator />,
|
||||||
|
inDrawer: false
|
||||||
|
},
|
||||||
]
|
]
|
||||||
@@ -107,7 +107,7 @@ export default function Home() {
|
|||||||
{v.map(item => {
|
{v.map(item => {
|
||||||
return (
|
return (
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`/operator/${item.link}`}
|
to={`/${item.link}`}
|
||||||
className="item"
|
className="item"
|
||||||
key={item.link}
|
key={item.link}
|
||||||
hidden={!isShown(item.type)}
|
hidden={!isShown(item.type)}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
.operator .spine-player-wrapper {
|
||||||
|
padding: 3rem 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings {
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .text {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-title-wrapper {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
border-left: 3px solid currentColor;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
margin-bottom: 0.81rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper {
|
||||||
|
margin-bottom: 0.81rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content {
|
||||||
|
padding: 0.81rem 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text {
|
||||||
|
pointer-events: none;
|
||||||
|
position: relative;
|
||||||
|
transform: translate3d(0, 0, 1px);
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0.44rem 2.25rem 0.44rem 0.63rem;
|
||||||
|
background-color: rgba(67, 67, 67, 0.3);
|
||||||
|
background-image: repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0, rgba(255, 255, 255, 0.1) 1px, transparent 1px, transparent 4px);
|
||||||
|
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content:hover,
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content.active {
|
||||||
|
transform: translate3d(6px, 0, 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text .outline {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: content-box;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 6px;
|
||||||
|
border: rgba(214, 214, 214, 0.3) 1px dashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text::before,
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text .outline {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.3s, visibility 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text .outline::before,
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text .outline::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: -2px;
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
border-left: var(--text-color) solid 2px;
|
||||||
|
border-right: var(--text-color) solid 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text .outline::before {
|
||||||
|
top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text .outline::after {
|
||||||
|
bottom: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text::before {
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 60%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: linear-gradient(90deg, transparent, currentColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content:hover .content-text::before,
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content.active .content-text::before,
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content:hover .content-text .outline,
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content.active .content-text .outline,
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content.active .content-text .tick-icon {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-settings .settings-content-wrapper .content .content-text .tick-icon {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
right: 0.31rem;
|
||||||
|
top: 50%;
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 1rem;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.3s, visibility 0.3s;
|
||||||
|
border-right: var(--text-color) solid 0.25rem;
|
||||||
|
border-bottom: var(--text-color) solid 0.25rem;
|
||||||
|
transform: translate(-50%, -70%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .backgrounds-dropdown {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .backgrounds-dropdown .content {
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .backgrounds-dropdown .icon {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.operator .backgrounds-dropdown .menu {
|
||||||
|
left: 0;
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator .spine-container {
|
||||||
|
max-height: 720px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
@@ -1,21 +1,35 @@
|
|||||||
import {
|
import {
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
useContext,
|
useRef,
|
||||||
useCallback,
|
|
||||||
useMemo
|
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
useParams,
|
useParams,
|
||||||
useNavigate
|
useNavigate
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
|
import { atom, useAtom } from 'jotai';
|
||||||
import './operator.css'
|
import './operator.css'
|
||||||
import { useConfig } from '@/state/config';
|
import { useConfig } from '@/state/config';
|
||||||
import {
|
import {
|
||||||
useLanguage,
|
useLanguage,
|
||||||
} from '@/state/language'
|
} from '@/state/language'
|
||||||
import { useHeader } from '@/state/header';
|
import { useHeader } from '@/state/header';
|
||||||
|
import { useBackgrounds } from '@/state/background';
|
||||||
import useUmami from '@parcellab/react-use-umami'
|
import useUmami from '@parcellab/react-use-umami'
|
||||||
|
import spine from '!/libs/spine-player'
|
||||||
|
import '!/libs/spine-player.css'
|
||||||
|
import MainBorder from '@/component/main_border';
|
||||||
|
import { useI18n } from '@/state/language';
|
||||||
|
import Dropdown from '@/component/dropdown';
|
||||||
|
|
||||||
|
const getVoiceFoler = (lang) => {
|
||||||
|
const folderObject = JSON.parse(import.meta.env.VITE_VOICE_FOLDERS)
|
||||||
|
return `${folderObject.main}/${folderObject.sub.find(e => e.lang === lang).name}`
|
||||||
|
}
|
||||||
|
const configAtom = atom(null);
|
||||||
|
const spinePlayerAtom = atom(null);
|
||||||
|
const spineAnimationAtom = atom("Idle");
|
||||||
|
const audioEl = new Audio()
|
||||||
|
|
||||||
export default function Operator(props) {
|
export default function Operator(props) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -28,15 +42,20 @@ export default function Operator(props) {
|
|||||||
setAppbarExtraArea,
|
setAppbarExtraArea,
|
||||||
setHeaderIcon
|
setHeaderIcon
|
||||||
} = useHeader()
|
} = useHeader()
|
||||||
const [config, setConfig] = useState(null)
|
const [config, setConfig] = useAtom(configAtom)
|
||||||
const [spineData, setSpineData] = useState({})
|
const [spineData, setSpineData] = useState(null)
|
||||||
const _trackEvt = useUmami(`/operator/${key}`)
|
const _trackEvt = useUmami(`/operator/${key}`)
|
||||||
|
const spineRef = useRef(null)
|
||||||
|
const [, setSpinePlayer] = useAtom(spinePlayerAtom)
|
||||||
|
const { currentBackground } = useBackgrounds()
|
||||||
|
const [spineAnimation,] = useAtom(spineAnimationAtom)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAppbarExtraArea([])
|
setAppbarExtraArea([])
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setSpineData(null)
|
||||||
const config = operators.find((item) => item.link === key)
|
const config = operators.find((item) => item.link === key)
|
||||||
if (config) {
|
if (config) {
|
||||||
setConfig(config)
|
setConfig(config)
|
||||||
@@ -44,6 +63,9 @@ export default function Operator(props) {
|
|||||||
setSpineData(data)
|
setSpineData(data)
|
||||||
})
|
})
|
||||||
setHeaderIcon(config.type)
|
setHeaderIcon(config.type)
|
||||||
|
if (spineRef.current?.children.length > 0) {
|
||||||
|
spineRef.current?.removeChild(spineRef.current?.children[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [operators, key])
|
}, [operators, key])
|
||||||
|
|
||||||
@@ -64,7 +86,7 @@ export default function Operator(props) {
|
|||||||
},
|
},
|
||||||
onClick: (e, tab) => {
|
onClick: (e, tab) => {
|
||||||
if (tab === key) return
|
if (tab === key) return
|
||||||
navigate(`/operator/${item.link}`)
|
navigate(`/${item.link}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,14 +114,162 @@ export default function Operator(props) {
|
|||||||
}
|
}
|
||||||
}, [config, language, key])
|
}, [config, language, key])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (spineRef.current?.children.length === 0 && spineData && config) {
|
||||||
|
setSpinePlayer(new spine.SpinePlayer(spineRef.current, {
|
||||||
|
skelUrl: `./assets/${config.filename.replace('#', '%23')}.skel`,
|
||||||
|
atlasUrl: `./assets/${config.filename.replace('#', '%23')}.atlas`,
|
||||||
|
rawDataURIs: spineData,
|
||||||
|
animation: spineAnimation,
|
||||||
|
premultipliedAlpha: true,
|
||||||
|
alpha: true,
|
||||||
|
backgroundColor: "#00000000",
|
||||||
|
viewport: {
|
||||||
|
debugRender: false,
|
||||||
|
padLeft: `${config.viewport_left}%`,
|
||||||
|
padRight: `${config.viewport_right}%`,
|
||||||
|
padTop: `${config.viewport_top}%`,
|
||||||
|
padBottom: `${config.viewport_bottom}%`,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
showControls: false,
|
||||||
|
touch: false,
|
||||||
|
fps: 60,
|
||||||
|
defaultMix: 0,
|
||||||
|
success: (spinePlayer) => {
|
||||||
|
spinePlayer.setAnimation(spineAnimation, true)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}, [spineData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className="operator">
|
||||||
<section>
|
<section className="spine-player-wrapper">
|
||||||
1
|
<SpineSettingsElement />
|
||||||
<button onClick={() => setTitle('123')}>123</button>
|
<section className="spine-container" ref={spineRef} style={{
|
||||||
|
backgroundImage: `url(/${key}/assets/background/${currentBackground})`
|
||||||
|
}} />
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<MainBorder />
|
||||||
2
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SpineSettingsElement() {
|
||||||
|
const [config,] = useAtom(configAtom)
|
||||||
|
const { i18n } = useI18n()
|
||||||
|
const [spineAnimation, setSpineAnimation] = useAtom(spineAnimationAtom)
|
||||||
|
const [spinePlayer,] = useAtom(spinePlayerAtom)
|
||||||
|
const [voiceLang, setVoiceLang] = useState(null)
|
||||||
|
|
||||||
|
const { backgrounds, currentBackground, setCurrentBackground } = useBackgrounds()
|
||||||
|
|
||||||
|
const spineSettings = [
|
||||||
|
{
|
||||||
|
name: 'animation',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Idle',
|
||||||
|
onClick: () => {
|
||||||
|
spinePlayer.setAnimation("Idle", true)
|
||||||
|
setSpineAnimation('Idle')
|
||||||
|
},
|
||||||
|
activeRule: () => {
|
||||||
|
return spineAnimation === 'Idle'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'Interact',
|
||||||
|
onClick: () => {
|
||||||
|
spinePlayer.setAnimation("Interact", true)
|
||||||
|
setSpineAnimation('Interact')
|
||||||
|
},
|
||||||
|
activeRule: () => {
|
||||||
|
return spineAnimation === 'Interact'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'Special',
|
||||||
|
onClick: () => {
|
||||||
|
spinePlayer.setAnimation("Special", true)
|
||||||
|
setSpineAnimation('Special')
|
||||||
|
},
|
||||||
|
activeRule: () => {
|
||||||
|
return spineAnimation === 'Special'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
name: 'voice',
|
||||||
|
options: config?.voiceLangs.map((item) => {
|
||||||
|
return {
|
||||||
|
name: i18n(item),
|
||||||
|
onClick: () => {
|
||||||
|
if (voiceLang !== item) {
|
||||||
|
setVoiceLang(item)
|
||||||
|
} else {
|
||||||
|
setVoiceLang(null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activeRule: () => {
|
||||||
|
return voiceLang === item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="spine-settings" style={{
|
||||||
|
color: config?.color
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
spineSettings.map((item) => {
|
||||||
|
if (item.options.length === 0) return null
|
||||||
|
return (
|
||||||
|
<section key={item.name}>
|
||||||
|
<section className='settings-title-wrapper'>
|
||||||
|
<section className='text'>{i18n(item.name)}</section>
|
||||||
|
</section>
|
||||||
|
<section className='settings-content-wrapper'>
|
||||||
|
{item.options.map((option) => {
|
||||||
|
return (
|
||||||
|
<section className={`content ${option.activeRule && option.activeRule() ? 'active' : ''}`} onClick={() => option.onClick()} key={option.name}>
|
||||||
|
<section className='content-text'>
|
||||||
|
<section className="outline" />
|
||||||
|
<section className='text'>{option.name}</section>
|
||||||
|
<section className='tick-icon' />
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<section className='settings-title-wrapper'>
|
||||||
|
<section className='text'>
|
||||||
|
<Dropdown
|
||||||
|
text={i18n('backgrounds')}
|
||||||
|
menu={backgrounds.map((item) => {
|
||||||
|
return {
|
||||||
|
name: item,
|
||||||
|
value: item
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
onClick={(item) => {
|
||||||
|
setCurrentBackground(item.name)
|
||||||
|
}}
|
||||||
|
className='backgrounds-dropdown'
|
||||||
|
activeRule={(item) => {
|
||||||
|
return item?.name === currentBackground
|
||||||
|
}}
|
||||||
|
activeColor={{
|
||||||
|
color: config?.color
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -138,6 +138,7 @@
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
padding-right: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main .main-header .main-title {
|
.main .main-header .main-title {
|
||||||
|
|||||||
20
directory/src/state/background.js
Normal file
20
directory/src/state/background.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { atom, useAtom } from 'jotai';
|
||||||
|
|
||||||
|
const fetcher = (...args) => fetch(...args).then(res => res.json())
|
||||||
|
const backgroundsAtom = atom([]);
|
||||||
|
const currentBackgroundAtom = atom(null);
|
||||||
|
|
||||||
|
export function useBackgrounds() {
|
||||||
|
const [backgrounds, setBackgrounds] = useAtom(backgroundsAtom);
|
||||||
|
const [currentBackground, setCurrentBackground] = useAtom(currentBackgroundAtom)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetcher('/_assets/backgrounds.json').then(data => {
|
||||||
|
setBackgrounds(data)
|
||||||
|
setCurrentBackground(data[0])
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { backgrounds, currentBackground, setCurrentBackground };
|
||||||
|
}
|
||||||
@@ -27,4 +27,4 @@ export function useConfig() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { config, version, operators };
|
return { config, version, operators };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { read } from './yaml.js';
|
|||||||
* 1. add voice config -> look up charword table
|
* 1. add voice config -> look up charword table
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default function () {
|
export default function ({ backgrounds, charwordTable }) {
|
||||||
const targetFolder = path.join(__projetRoot, __config.folder.release, __config.folder.directory);
|
const targetFolder = path.join(__projetRoot, __config.folder.release, __config.folder.directory);
|
||||||
const sourceFolder = path.join(__projetRoot, __config.folder.operator);
|
const sourceFolder = path.join(__projetRoot, __config.folder.operator);
|
||||||
rmdir(targetFolder);
|
rmdir(targetFolder);
|
||||||
@@ -22,6 +22,11 @@ export default function () {
|
|||||||
} else {
|
} else {
|
||||||
acc[date] = [cur]
|
acc[date] = [cur]
|
||||||
}
|
}
|
||||||
|
cur.voiceLangs = []
|
||||||
|
const voiceInfo = Object.values(charwordTable.lookup(cur.link).operator.info.zh_CN)
|
||||||
|
voiceInfo.forEach((item) => {
|
||||||
|
cur.voiceLangs = [...cur.voiceLangs, ...Object.keys(item)]
|
||||||
|
})
|
||||||
return acc
|
return acc
|
||||||
}, {}))
|
}, {}))
|
||||||
.sort((a, b) => Date.parse(b[0].date) - Date.parse(a[0].date)),
|
.sort((a, b) => Date.parse(b[0].date) - Date.parse(a[0].date)),
|
||||||
@@ -45,6 +50,7 @@ export default function () {
|
|||||||
writeSync(JSON.stringify(directoryJson, null), path.join(targetFolder, "directory.json"))
|
writeSync(JSON.stringify(directoryJson, null), path.join(targetFolder, "directory.json"))
|
||||||
writeSync(JSON.stringify(versionJson, null), path.join(targetFolder, "version.json"))
|
writeSync(JSON.stringify(versionJson, null), path.join(targetFolder, "version.json"))
|
||||||
writeSync(JSON.stringify(changelogsArray, null), path.join(targetFolder, "changelogs.json"))
|
writeSync(JSON.stringify(changelogsArray, null), path.join(targetFolder, "changelogs.json"))
|
||||||
|
writeSync(JSON.stringify(backgrounds, null), path.join(targetFolder, "backgrounds.json"))
|
||||||
filesToCopy.forEach((key) => {
|
filesToCopy.forEach((key) => {
|
||||||
copy(path.join(sourceFolder, key, 'assets.json'), path.join(targetFolder, `${__config.operators[key].filename}.json`))
|
copy(path.join(sourceFolder, key, 'assets.json'), path.join(targetFolder, `${__config.operators[key].filename}.json`))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -137,16 +137,18 @@ class ViteRunner {
|
|||||||
if (process.env.npm_lifecycle_event === 'vite:directory:build') {
|
if (process.env.npm_lifecycle_event === 'vite:directory:build') {
|
||||||
this.#globalConfig.version.directory = increase(path.join(__projetRoot, "directory"))
|
this.#globalConfig.version.directory = increase(path.join(__projetRoot, "directory"))
|
||||||
global.__config = this.#globalConfig
|
global.__config = this.#globalConfig
|
||||||
directory()
|
|
||||||
}
|
}
|
||||||
const directoryDir = path.resolve(__projetRoot, 'directory')
|
const directoryDir = path.resolve(__projetRoot, 'directory')
|
||||||
writeSync((new EnvGenerator()).generate([
|
writeSync((new EnvGenerator()).generate([
|
||||||
{
|
{
|
||||||
key: "app_title",
|
key: "app_title",
|
||||||
value: this.#globalConfig.directory.title
|
value: this.#globalConfig.directory.title
|
||||||
}, {
|
}, {
|
||||||
key: "app_voice_url",
|
key: "app_voice_url",
|
||||||
value: this.#globalConfig.directory.voice
|
value: this.#globalConfig.directory.voice
|
||||||
|
}, {
|
||||||
|
key: "voice_folders",
|
||||||
|
value: JSON.stringify(this.#globalConfig.folder.voice)
|
||||||
}
|
}
|
||||||
]), path.join(directoryDir, '.env'))
|
]), path.join(directoryDir, '.env'))
|
||||||
this.#mode = process.argv[3]
|
this.#mode = process.argv[3]
|
||||||
@@ -168,6 +170,7 @@ class ViteRunner {
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(directoryDir, './src'),
|
'@': path.resolve(directoryDir, './src'),
|
||||||
|
'!': path.resolve(__projetRoot, './src'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
Reference in New Issue
Block a user