feat(aklive2d): performance optimization for showcase and error page for directory
This commit is contained in:
53
aklive2d.js
53
aklive2d.js
@@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable no-fallthrough */
|
||||||
|
/* eslint-disable no-undef */
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@@ -56,8 +58,6 @@ async function main() {
|
|||||||
await background.process()
|
await background.process()
|
||||||
const backgrounds = ['operator_bg.png', ...background.files]
|
const backgrounds = ['operator_bg.png', ...background.files]
|
||||||
|
|
||||||
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)
|
||||||
const OPERATOR_RELEASE_FOLDER = path.join(__projetRoot, __config.folder.release, OPERATOR_NAME)
|
const OPERATOR_RELEASE_FOLDER = path.join(__projetRoot, __config.folder.release, OPERATOR_NAME)
|
||||||
@@ -85,29 +85,36 @@ async function main() {
|
|||||||
rmdir(OPERATOR_RELEASE_FOLDER)
|
rmdir(OPERATOR_RELEASE_FOLDER)
|
||||||
|
|
||||||
const charwordTableLookup = charwordTable.lookup(OPERATOR_NAME)
|
const charwordTableLookup = charwordTable.lookup(OPERATOR_NAME)
|
||||||
const voiceLangs = (() => {
|
const voiceJson = {}
|
||||||
const infoArray = Object.values(charwordTableLookup.operator.info[charwordTableLookup.config.default_region])
|
voiceJson.config = {
|
||||||
// combine the infoArray
|
default_region: charwordTableLookup.config.default_region.replace("_", "-"),
|
||||||
let output = {}
|
regions: charwordTableLookup.config.regions.map((item) => item.replace("_", "-")),
|
||||||
for (const info of infoArray) {
|
}
|
||||||
output = {
|
voiceJson.voiceLangs = {}
|
||||||
...output,
|
voiceJson.subtitleLangs = {}
|
||||||
...info
|
const subtitleInfo = Object.keys(charwordTableLookup.operator.info)
|
||||||
|
subtitleInfo.forEach((item) => {
|
||||||
|
if (Object.keys(charwordTableLookup.operator.info[item]).length > 0) {
|
||||||
|
const key = item.replace("_", "-")
|
||||||
|
voiceJson.subtitleLangs[key] = {}
|
||||||
|
for (const [id, subtitles] of Object.entries(charwordTableLookup.operator.voice[item])) {
|
||||||
|
const match = id.replace(/(.+?)([A-Z]\w+)/, '$2')
|
||||||
|
if (match === id) {
|
||||||
|
voiceJson.subtitleLangs[key].default = subtitles
|
||||||
|
} else {
|
||||||
|
voiceJson.subtitleLangs[key][match] = subtitles
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
voiceJson.voiceLangs[key] = {}
|
||||||
|
Object.values(charwordTableLookup.operator.info[item]).forEach((item) => {
|
||||||
|
voiceJson.voiceLangs[key] = { ...voiceJson.voiceLangs[key], ...item }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return Object.keys(output)
|
})
|
||||||
})()
|
const voiceLangs = Object.keys(voiceJson.voiceLangs["zh-CN"])
|
||||||
const subtitleLangs = (() => {
|
const subtitleLangs = Object.keys(voiceJson.subtitleLangs)
|
||||||
const output = []
|
|
||||||
for (const [key, value] of Object.entries(charwordTableLookup.operator.info)) {
|
|
||||||
if (Object.keys(value).length !== 0) {
|
|
||||||
output.push(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
})()
|
|
||||||
|
|
||||||
writeSync(JSON.stringify(charwordTableLookup), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'charword_table.json'))
|
writeSync(JSON.stringify(voiceJson), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'charword_table.json'))
|
||||||
|
|
||||||
const projectJson = new ProjectJson(OPERATOR_NAME, OPERATOR_SHARE_FOLDER, {
|
const projectJson = new ProjectJson(OPERATOR_NAME, OPERATOR_SHARE_FOLDER, {
|
||||||
backgrounds,
|
backgrounds,
|
||||||
@@ -219,6 +226,8 @@ async function main() {
|
|||||||
]), envPath)
|
]), envPath)
|
||||||
fork(path.join(__projetRoot, 'vite.config.js'), [op, OPERATOR_NAME])
|
fork(path.join(__projetRoot, 'vite.config.js'), [op, OPERATOR_NAME])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
directory({ backgrounds, charwordTable })
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
showcase:
|
showcase:
|
||||||
|
2023/03/03:
|
||||||
|
- Performance optimization
|
||||||
2023/02/26:
|
2023/02/26:
|
||||||
- Rename w_fugue to w_wonder
|
- Rename w_fugue to w_wonder
|
||||||
2023/02/14:
|
2023/02/14:
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ share:
|
|||||||
directory:
|
directory:
|
||||||
title: AKLive2D
|
title: AKLive2D
|
||||||
voice: jp/CN_037.ogg
|
voice: jp/CN_037.ogg
|
||||||
|
error:
|
||||||
|
files: !include config/_directory.yaml
|
||||||
|
voice: CN_034.ogg
|
||||||
operators:
|
operators:
|
||||||
chen: !include config/chen.yaml
|
chen: !include config/chen.yaml
|
||||||
dusk: !include config/dusk.yaml
|
dusk: !include config/dusk.yaml
|
||||||
@@ -49,4 +52,4 @@ operators:
|
|||||||
mizuki_summer_feast: !include config/mizuki_summer_feast.yaml
|
mizuki_summer_feast: !include config/mizuki_summer_feast.yaml
|
||||||
chongyue: !include config/chongyue.yaml
|
chongyue: !include config/chongyue.yaml
|
||||||
ling_it_does_wash_the_strings: !include config/ling_it_does_wash_the_strings.yaml
|
ling_it_does_wash_the_strings: !include config/ling_it_does_wash_the_strings.yaml
|
||||||
pozemka_snowy_plains_in_words: !include config/pozemka_snowy_plains_in_words.yaml
|
pozemka_snowy_plains_in_words: !include config/pozemka_snowy_plains_in_words.yaml
|
||||||
12
config/_directory.yaml
Normal file
12
config/_directory.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
- key: build_char_128_plosis_epoque#3
|
||||||
|
paddings:
|
||||||
|
left: -120
|
||||||
|
right: 150
|
||||||
|
top: 10
|
||||||
|
bottom: 0
|
||||||
|
- key: build_char_128_plosis
|
||||||
|
paddings:
|
||||||
|
left: -90
|
||||||
|
right: 100
|
||||||
|
top: 10
|
||||||
|
bottom: 0
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
description: !match "~{split('config', 'title' ,' - ')[0]} Live 2D\n~{split('config', 'title' ,' - ')[1]} Live 2D\nThe model is extracted from game with Spine support.\n模型来自游戏内提取,支持Spine\nPlease set your FPS target in Wallpaper Engine > Settings > Performance > FPS\n\nLive preview on: https://arknights.halyul.dev/~{var('config', 'link')}?settings\nGithub: https://github.com/Halyul/aklive2d\nCheck out our privacy policy at https://privacy.halyul.dev"
|
description: !match "~{split('config', 'title' ,' - ')[0]} Live 2D\n~{split('config', 'title' ,' - ')[1]} Live 2D\nThe model is extracted from game with Spine support.\n模型来自游戏内提取,支持Spine\nPlease set your FPS target in Wallpaper Engine > Settings > Performance > FPS\n\nLive preview on: https://arknights.halyul.dev/~{var('config', 'link')}\nGithub: https://github.com/Halyul/aklive2d\nCheck out our privacy policy at https://privacy.halyul.dev"
|
||||||
localization:
|
localization:
|
||||||
en-us:
|
en-us:
|
||||||
ui_notice_title: <hr><h4>📝 Notes</h4><hr>
|
ui_notice_title: <hr><h4>📝 Notes</h4><hr>
|
||||||
|
|||||||
@@ -2,4 +2,6 @@ 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"}]}
|
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"}]}
|
||||||
VITE_DIRECTORY_FOLDER="_assets"
|
VITE_DIRECTORY_FOLDER="_assets"
|
||||||
VITE_BACKGROUND_FOLDER="background"
|
VITE_BACKGROUND_FOLDER="background"
|
||||||
|
VITE_AVAILABLE_OPERATORS=["chen","dusk","dusk_everything_is_a_miracle","ling","nearl","nian","nian_unfettered_freedom","phatom_focus","rosmontis","skadi","skadi_sublimation","w","w_wonder","specter","gavial","surtr_colorful_wonderland","lee_trust_your_eyes","texas_the_omertosa","nearl_relight","rosmontis_become_anew","passager_dream_in_a_moment","mizuki_summer_feast","chongyue","ling_it_does_wash_the_strings","pozemka_snowy_plains_in_words"]
|
||||||
|
VITE_ERROR_FILES={"files":[{"key":"build_char_128_plosis_epoque%233","paddings":{"left":-120,"right":150,"top":10,"bottom":0}},{"key":"build_char_128_plosis","paddings":{"left":-90,"right":100,"top":10,"bottom":0}}],"voice":"CN_034.ogg"}
|
||||||
@@ -1 +1 @@
|
|||||||
0.5.30
|
1.0.4
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
} from "react"
|
} from "react"
|
||||||
import { useCallback } from "react"
|
import { useCallback } from "react"
|
||||||
const audioEl = new Audio()
|
const audioEl = new Audio()
|
||||||
|
let lastSrc = ''
|
||||||
export default function useAudio() {
|
export default function useAudio() {
|
||||||
const [isPlaying, _setIsPlaying] = useState(false)
|
const [isPlaying, _setIsPlaying] = useState(false)
|
||||||
const isPlayingRef = useRef(isPlaying)
|
const isPlayingRef = useRef(isPlaying)
|
||||||
@@ -29,17 +29,19 @@ export default function useAudio() {
|
|||||||
},
|
},
|
||||||
callback = () => { }
|
callback = () => { }
|
||||||
) => {
|
) => {
|
||||||
if (!options.overwrite && audioEl.src === (window.location.href.replace(/\/$/g, '') + link)) return
|
if (!options.overwrite && link === lastSrc) return
|
||||||
audioEl.src = link
|
audioEl.src = link
|
||||||
let startPlayPromise = audioEl.play()
|
let startPlayPromise = audioEl.play()
|
||||||
if (startPlayPromise !== undefined) {
|
if (startPlayPromise !== undefined) {
|
||||||
setIsPlaying(true)
|
setIsPlaying(true)
|
||||||
startPlayPromise
|
startPlayPromise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
lastSrc = link
|
||||||
callback()
|
callback()
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
53
directory/src/routes/error-page.css
Normal file
53
directory/src/routes/error-page.css
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
.error-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 16px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-page .header {
|
||||||
|
padding: 1rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-page .main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-top: 10rem;
|
||||||
|
font-size: 3rem;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-page .spine {
|
||||||
|
max-width: 600px;
|
||||||
|
flex: 1;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-page .spine.active {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.error-page .main {
|
||||||
|
padding-top: 6rem;
|
||||||
|
}
|
||||||
|
.error-page .content {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.error-page .main {
|
||||||
|
padding-top: 4rem;
|
||||||
|
}
|
||||||
|
.error-page .content {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,170 @@
|
|||||||
import React from "react";
|
import React, {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useCallback
|
||||||
|
} from "react";
|
||||||
import {
|
import {
|
||||||
useNavigate,
|
useNavigate,
|
||||||
useRouteError
|
useRouteError
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
|
import './error-page.css'
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
import { atomWithStorage } from 'jotai/utils';
|
||||||
|
import Switch from '@/component/switch';
|
||||||
|
import ReturnButton from "@/component/return_button";
|
||||||
|
import { Typewriter } from 'react-simple-typewriter'
|
||||||
|
import { useHeader } from '@/state/header';
|
||||||
|
import useAudio from '@/libs/voice';
|
||||||
|
import spine from '!/libs/spine-player'
|
||||||
|
import '!/libs/spine-player.css'
|
||||||
|
|
||||||
|
const voiceOnAtom = atomWithStorage('voiceOn', false)
|
||||||
|
const config = JSON.parse(import.meta.env.VITE_ERROR_FILES)
|
||||||
|
const obj = config.files[Math.floor((Math.random() * config.files.length))]
|
||||||
|
const filename = obj.key.replace("#", "%23")
|
||||||
|
const padding = obj.paddings
|
||||||
|
|
||||||
export default function ErrorPage() {
|
export default function ErrorPage() {
|
||||||
const error = useRouteError();
|
const error = useRouteError();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
console.log(error)
|
const {
|
||||||
|
setTitle,
|
||||||
|
} = useHeader()
|
||||||
|
const [voiceOn, setVoiceOn] = useAtom(voiceOnAtom)
|
||||||
|
const [spineDone, _setSpineDone] = useState(false)
|
||||||
|
const spineRef = useRef(null)
|
||||||
|
const [spineData, setSpineData] = useState(null)
|
||||||
|
const { play, stop } = useAudio()
|
||||||
|
const spineDoneRef = useRef(spineDone)
|
||||||
|
const voiceOnRef = useRef(voiceOn)
|
||||||
|
|
||||||
|
const setSpineDone = (data) => {
|
||||||
|
spineDoneRef.current = data
|
||||||
|
_setSpineDone(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = useMemo(() => ['エラー発生。', '发生错误。', 'Error occured.', '에러 발생.', '發生錯誤。'], [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(error)
|
||||||
|
fetch(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/${filename}.json`).then(res => res.json()).then(data => {
|
||||||
|
setSpineData(data)
|
||||||
|
})
|
||||||
|
}, [error])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle(content[0])
|
||||||
|
stop()
|
||||||
|
}, [content, setTitle, stop])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!voiceOn) {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}, [voiceOn, stop])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
voiceOnRef.current = voiceOn
|
||||||
|
}, [voiceOn])
|
||||||
|
|
||||||
|
const playVoice = useCallback(() => {
|
||||||
|
play(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/error.ogg`, { overwrite: true })
|
||||||
|
}, [play])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (voiceOn) playVoice()
|
||||||
|
}, [playVoice, voiceOn])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (spineRef.current?.children.length === 0 && spineData) {
|
||||||
|
new spine.SpinePlayer(spineRef.current, {
|
||||||
|
skelUrl: `./assets/${filename}.skel`,
|
||||||
|
atlasUrl: `./assets/${filename}.atlas`,
|
||||||
|
rawDataURIs: spineData,
|
||||||
|
animation: 'Relax',
|
||||||
|
premultipliedAlpha: true,
|
||||||
|
alpha: true,
|
||||||
|
backgroundColor: "#00000000",
|
||||||
|
viewport: {
|
||||||
|
debugRender: false,
|
||||||
|
padLeft: `${padding.left}%`,
|
||||||
|
padRight: `${padding.right}%`,
|
||||||
|
padTop: `${padding.top}%`,
|
||||||
|
padBottom: `${padding.bottom}%`,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
showControls: false,
|
||||||
|
touch: false,
|
||||||
|
fps: 60,
|
||||||
|
defaultMix: 0,
|
||||||
|
success: (player) => {
|
||||||
|
let isPlayingInteract = false
|
||||||
|
player.animationState.addListener({
|
||||||
|
end: (e) => {
|
||||||
|
if (e.animation.name == "Interact") {
|
||||||
|
isPlayingInteract = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSpineDone(true)
|
||||||
|
const ani = () => {
|
||||||
|
if (isPlayingInteract) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isPlayingInteract = true;
|
||||||
|
player.animationState.setAnimation(0, "Interact", false, 0);
|
||||||
|
player.animationState.addAnimation(0, "Relax", true, 0);
|
||||||
|
if (voiceOnRef.current) playVoice()
|
||||||
|
}
|
||||||
|
ani()
|
||||||
|
player.canvas.onclick = () => {
|
||||||
|
ani()
|
||||||
|
}
|
||||||
|
player.canvas.onmouseenter = () => {
|
||||||
|
ani()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [playVoice, spineData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className='error-page'>
|
||||||
error
|
<header className='header'>
|
||||||
<button onClick={() => navigate(-1, { replace: true })}>Go Home</button>
|
<ReturnButton
|
||||||
|
className='return-button'
|
||||||
|
onClick={() => navigate(-1, { replace: true })}
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
key="voice"
|
||||||
|
text='voice'
|
||||||
|
on={voiceOn}
|
||||||
|
handleOnClick={() => setVoiceOn(!voiceOn)}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<main className='main'>
|
||||||
|
{
|
||||||
|
content.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<section key={index} className='content'>
|
||||||
|
<Typewriter
|
||||||
|
words={[item]}
|
||||||
|
cursor
|
||||||
|
cursorStyle='|'
|
||||||
|
typeSpeed={100}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<section
|
||||||
|
className={`spine ${spineDone ? 'active' : ''}`}
|
||||||
|
ref={spineRef}
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.changelogs .item-info-content {
|
.changelogs .item-info-content {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-group-date {
|
.item-group-date {
|
||||||
|
|||||||
@@ -8,13 +8,12 @@
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
|
||||||
|
|
||||||
.operator .spine-container {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.operator .spine-container:before {
|
.operator .spine-container:before {
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
@@ -28,9 +27,9 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.operator .spine-settings,
|
.operator .spine-settings {
|
||||||
.operator .steam-workshop-wrapper {
|
|
||||||
margin-right: 1.5rem;
|
margin-right: 1.5rem;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.operator .text {
|
.operator .text {
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ const getVoiceFoler = (lang) => {
|
|||||||
}
|
}
|
||||||
const spinePlayerAtom = atom(null);
|
const spinePlayerAtom = atom(null);
|
||||||
const spineAnimationAtom = atom("Idle");
|
const spineAnimationAtom = atom("Idle");
|
||||||
const voiceLangAtom = atom(null);
|
|
||||||
const subtitleLangAtom = atom(null);
|
|
||||||
|
|
||||||
const getTabName = (item, language) => {
|
const getTabName = (item, language) => {
|
||||||
if (item.type === 'operator') {
|
if (item.type === 'operator') {
|
||||||
@@ -63,11 +61,11 @@ export default function Operator() {
|
|||||||
const [spineAnimation, setSpineAnimation] = useAtom(spineAnimationAtom)
|
const [spineAnimation, setSpineAnimation] = useAtom(spineAnimationAtom)
|
||||||
const { i18n } = useI18n()
|
const { i18n } = useI18n()
|
||||||
const [spinePlayer, setSpinePlayer] = useAtom(spinePlayerAtom)
|
const [spinePlayer, setSpinePlayer] = useAtom(spinePlayerAtom)
|
||||||
const [voiceLang, _setVoiceLang] = useAtom(voiceLangAtom)
|
const [voiceLang, _setVoiceLang] = useState(null)
|
||||||
const { backgrounds } = useBackgrounds()
|
const { backgrounds } = useBackgrounds()
|
||||||
const [currentBackground, setCurrentBackground] = useState(null)
|
const [currentBackground, setCurrentBackground] = useState(null)
|
||||||
const [voiceConfig, setVoiceConfig] = useState(null)
|
const [voiceConfig, setVoiceConfig] = useState(null)
|
||||||
const [subtitleLang, setSubtitleLang] = useAtom(subtitleLangAtom)
|
const [subtitleLang, setSubtitleLang] = useState(null)
|
||||||
const [hideSubtitle, setHideSubtitle] = useState(true)
|
const [hideSubtitle, setHideSubtitle] = useState(true)
|
||||||
const { play, stop, getSrc, isPlaying, isPlayingRef } = useAudio()
|
const { play, stop, getSrc, isPlaying, isPlayingRef } = useAudio()
|
||||||
const [subtitleObj, _setSubtitleObj] = useState(null)
|
const [subtitleObj, _setSubtitleObj] = useState(null)
|
||||||
@@ -339,6 +337,10 @@ export default function Operator() {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (!JSON.parse(import.meta.env.VITE_AVAILABLE_OPERATORS).includes(key)) {
|
||||||
|
throw new Error('Operator not found')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="operator">
|
<section className="operator">
|
||||||
<section className="spine-player-wrapper">
|
<section className="spine-player-wrapper">
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer .links {
|
.drawer .links {
|
||||||
@@ -237,6 +238,10 @@
|
|||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
.footer .section {
|
.footer .section {
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export default function Root() {
|
|||||||
function FooterElement() {
|
function FooterElement() {
|
||||||
const { i18n } = useI18n()
|
const { i18n } = useI18n()
|
||||||
const { version } = useConfig()
|
const { version } = useConfig()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@@ -160,7 +161,9 @@ function FooterElement() {
|
|||||||
</Popup>
|
</Popup>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<section className='copyright section'>
|
<section className='copyright section' onDoubleClick={() => {
|
||||||
|
navigate('/error')
|
||||||
|
}}>
|
||||||
<span>Spine Runtimes © 2013 - 2019 Esoteric Software LLC</span>
|
<span>Spine Runtimes © 2013 - 2019 Esoteric Software LLC</span>
|
||||||
<span>Assets © 2017 - {currentYear} Arknights/Hypergryph Co., Ltd</span>
|
<span>Assets © 2017 - {currentYear} Arknights/Hypergryph Co., Ltd</span>
|
||||||
<span>Source Code © 2021 - {currentYear} Halyul</span>
|
<span>Source Code © 2021 - {currentYear} Halyul</span>
|
||||||
@@ -169,7 +172,7 @@ function FooterElement() {
|
|||||||
</section>
|
</section>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
}, [i18n, version.directory, version.showcase])
|
}, [i18n, navigate, version.directory, version.showcase])
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerDestinations({ toggleDrawer }) {
|
function DrawerDestinations({ toggleDrawer }) {
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ export default class AssetsProcessor {
|
|||||||
const croppedBuffer = await this.#alphaCompositer.crop(portraitBuffer, rect)
|
const croppedBuffer = await this.#alphaCompositer.crop(portraitBuffer, rect)
|
||||||
await write(croppedBuffer, path.join(this.#operatorSourceFolder, this.#operatorName, `${fallback_name}_portrait.png`))
|
await write(croppedBuffer, path.join(this.#operatorSourceFolder, this.#operatorName, `${fallback_name}_portrait.png`))
|
||||||
|
|
||||||
return await this.#generateAssets(__config.operators[this.#operatorName].filename, extractedDir)
|
return await this.generateAssets(__config.operators[this.#operatorName].filename, extractedDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
async #generateAssets(filename, extractedDir) {
|
async generateAssets(filename, extractedDir) {
|
||||||
const BASE64_BINARY_PREFIX = 'data:application/octet-stream;base64,'
|
const BASE64_BINARY_PREFIX = 'data:application/octet-stream;base64,'
|
||||||
const BASE64_PNG_PREFIX = 'data:image/png;base64,'
|
const BASE64_PNG_PREFIX = 'data:image/png;base64,'
|
||||||
const assetsJson = {}
|
const assetsJson = {}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-undef */
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import dotenv from "dotenv"
|
import dotenv from "dotenv"
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
|
/* eslint-disable no-undef */
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { writeSync, copy, rmdir, readSync as readFile } from './file.js'
|
import { writeSync, copy, readSync as readFile } from './file.js'
|
||||||
import { read } from './yaml.js';
|
import { read } from './yaml.js';
|
||||||
|
import AssetsProcessor from './assets_processor.js'
|
||||||
/**
|
|
||||||
* TODO:
|
|
||||||
* 1. add voice config -> look up charword table
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function ({ backgrounds, charwordTable }) {
|
export default function ({ backgrounds, charwordTable }) {
|
||||||
|
const extractedFolder = path.join(__projetRoot, __config.folder.operator, '_directory')
|
||||||
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);
|
|
||||||
const filesToCopy = Object.keys(__config.operators)
|
const filesToCopy = Object.keys(__config.operators)
|
||||||
const directoryJson = {
|
const directoryJson = {
|
||||||
operators: Object.values(
|
operators: Object.values(
|
||||||
@@ -36,35 +33,6 @@ export default function ({ backgrounds, charwordTable }) {
|
|||||||
}
|
}
|
||||||
const versionJson = __config.version
|
const versionJson = __config.version
|
||||||
|
|
||||||
filesToCopy.forEach((operator) => {
|
|
||||||
const voiceJson = {}
|
|
||||||
|
|
||||||
voiceJson.voiceLangs = {}
|
|
||||||
voiceJson.subtitleLangs = {}
|
|
||||||
const charwordTableObj = charwordTable.lookup(operator)
|
|
||||||
const subtitleInfo = Object.keys(charwordTableObj.operator.info)
|
|
||||||
subtitleInfo.forEach((item) => {
|
|
||||||
if (Object.keys(charwordTableObj.operator.info[item]).length > 0) {
|
|
||||||
const key = item.replace("_", "-")
|
|
||||||
voiceJson.subtitleLangs[key] = {}
|
|
||||||
for (const [id, subtitles] of Object.entries(charwordTableObj.operator.voice[item])) {
|
|
||||||
const match = id.replace(/(.+?)([A-Z]\w+)/, '$2')
|
|
||||||
if (match === id) {
|
|
||||||
voiceJson.subtitleLangs[key].default = subtitles
|
|
||||||
} else {
|
|
||||||
voiceJson.subtitleLangs[key][match] = subtitles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
voiceJson.voiceLangs[key] = {}
|
|
||||||
Object.values(charwordTableObj.operator.info[item]).forEach((item) => {
|
|
||||||
voiceJson.voiceLangs[key] = { ...voiceJson.voiceLangs[key], ...item }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
writeSync(JSON.stringify(voiceJson, null), path.join(targetFolder, `voice_${operator}.json`))
|
|
||||||
})
|
|
||||||
|
|
||||||
const changelogs = read(path.join(__projetRoot, 'changelogs.yaml'))
|
const changelogs = read(path.join(__projetRoot, 'changelogs.yaml'))
|
||||||
const changelogsArray = Object.keys(changelogs).reduce((acc, cur) => {
|
const changelogsArray = Object.keys(changelogs).reduce((acc, cur) => {
|
||||||
const array = []
|
const array = []
|
||||||
@@ -79,11 +47,20 @@ export default function ({ backgrounds, charwordTable }) {
|
|||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
__config.directory.error.files.forEach((key) => {
|
||||||
|
const assetsProcessor = new AssetsProcessor()
|
||||||
|
assetsProcessor.generateAssets(key.key, extractedFolder).then((content) => {
|
||||||
|
writeSync(JSON.stringify(content.assetsJson, null), path.join(targetFolder, `${key.key}.json`))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
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"))
|
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`))
|
||||||
|
copy(path.join(sourceFolder, key, 'charword_table.json'), path.join(targetFolder, `voice_${key}.json`))
|
||||||
})
|
})
|
||||||
|
copy(path.join(extractedFolder, __config.directory.error.voice), path.join(targetFolder, `error.ogg`))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"react-router-dom": "^6.8.1",
|
"react-router-dom": "^6.8.1",
|
||||||
|
"react-simple-typewriter": "^5.0.1",
|
||||||
"reset-css": "^5.0.1",
|
"reset-css": "^5.0.1",
|
||||||
"sharp": "^0.31.3",
|
"sharp": "^0.31.3",
|
||||||
"yaml": "^2.2.1"
|
"yaml": "^2.2.1"
|
||||||
|
|||||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -17,6 +17,7 @@ specifiers:
|
|||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
react-refresh: ^0.14.0
|
react-refresh: ^0.14.0
|
||||||
react-router-dom: ^6.8.1
|
react-router-dom: ^6.8.1
|
||||||
|
react-simple-typewriter: ^5.0.1
|
||||||
reset-css: ^5.0.1
|
reset-css: ^5.0.1
|
||||||
rollup: ^3.17.3
|
rollup: ^3.17.3
|
||||||
sharp: ^0.31.3
|
sharp: ^0.31.3
|
||||||
@@ -32,6 +33,7 @@ dependencies:
|
|||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
react-refresh: 0.14.0
|
react-refresh: 0.14.0
|
||||||
react-router-dom: 6.8.1_biqbaboplfbrettd7655fr4n2y
|
react-router-dom: 6.8.1_biqbaboplfbrettd7655fr4n2y
|
||||||
|
react-simple-typewriter: 5.0.1_biqbaboplfbrettd7655fr4n2y
|
||||||
reset-css: 5.0.1
|
reset-css: 5.0.1
|
||||||
sharp: 0.31.3
|
sharp: 0.31.3
|
||||||
yaml: 2.2.1
|
yaml: 2.2.1
|
||||||
@@ -1996,6 +1998,17 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-simple-typewriter/5.0.1_biqbaboplfbrettd7655fr4n2y:
|
||||||
|
resolution: {integrity: sha512-vA5HkABwJKL/DJ4RshSlY/igdr+FiVY4MLsSQYJX6FZG/f1/VwN4y1i3mPXRyfaswrvI8xii1kOVe1dYtO2Row==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18.0.0'
|
||||||
|
react-dom: '>=18.0.0'
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react/18.2.0:
|
/react/18.2.0:
|
||||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default class Voice {
|
|||||||
#defaultRegion = charword_table.config.default_region
|
#defaultRegion = charword_table.config.default_region
|
||||||
#defaultIdleDuration = 10 * 60 * 1000
|
#defaultIdleDuration = 10 * 60 * 1000
|
||||||
#defaultNextDuration = 3 * 60 * 1000
|
#defaultNextDuration = 3 * 60 * 1000
|
||||||
#voiceLanguages = Object.keys(this.#getCVInfo(this.#defaultRegion))
|
#voiceLanguages = Object.keys(charword_table.voiceLangs["zh-CN"])
|
||||||
#defaultVoiceLang = this.#voiceLanguages[0]
|
#defaultVoiceLang = this.#voiceLanguages[0]
|
||||||
#voiceLang = this.#defaultVoiceLang
|
#voiceLang = this.#defaultVoiceLang
|
||||||
#subtitleLang = this.#defaultRegion
|
#subtitleLang = this.#defaultRegion
|
||||||
@@ -42,6 +42,12 @@ export default class Voice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
success() {
|
success() {
|
||||||
|
const audioEndedFunc = () => {
|
||||||
|
this.#isPlaying = false
|
||||||
|
this.#setCurrentSubtitle(null)
|
||||||
|
this.#lastClickToNext = false
|
||||||
|
}
|
||||||
|
this.#audioEl.addEventListener('ended', audioEndedFunc)
|
||||||
this.#playEntryVoice()
|
this.#playEntryVoice()
|
||||||
this.#initNextVoiceTimer()
|
this.#initNextVoiceTimer()
|
||||||
this.#widgetEl.addEventListener('click', e => {
|
this.#widgetEl.addEventListener('click', e => {
|
||||||
@@ -242,7 +248,7 @@ export default class Voice {
|
|||||||
const subtitle = this.#getSubtitleById(id)
|
const subtitle = this.#getSubtitleById(id)
|
||||||
const title = subtitle.title
|
const title = subtitle.title
|
||||||
const content = subtitle.text
|
const content = subtitle.text
|
||||||
const cvInfo = this.#getCVInfoByVoiceLang()[this.#voiceLang][this.subtitleLanguage]
|
const cvInfo = charword_table.voiceLangs[this.subtitleLanguage][this.#voiceLang]
|
||||||
document.getElementById('voice_title').innerText = title
|
document.getElementById('voice_title').innerText = title
|
||||||
document.getElementById('voice_subtitle').innerText = content
|
document.getElementById('voice_subtitle').innerText = content
|
||||||
this.#el.style.opacity = 1
|
this.#el.style.opacity = 1
|
||||||
@@ -260,15 +266,6 @@ export default class Voice {
|
|||||||
startPlayPromise
|
startPlayPromise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.#isPlaying = true
|
this.#isPlaying = true
|
||||||
const audioEndedFunc = () => {
|
|
||||||
this.#isPlaying = false
|
|
||||||
this.#audioEl.removeEventListener('ended', audioEndedFunc)
|
|
||||||
if (this.#currentVoiceId !== id) return
|
|
||||||
this.#setCurrentSubtitle(null)
|
|
||||||
this.#lastClickToNext = false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#audioEl.addEventListener('ended', audioEndedFunc)
|
|
||||||
this.#setCurrentSubtitle(id)
|
this.#setCurrentSubtitle(id)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -278,7 +275,8 @@ export default class Voice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#playSpecialVoice(matcher) {
|
#playSpecialVoice(matcher) {
|
||||||
const voiceId = this.#getSpecialVoiceId(matcher)
|
const voices = this.#getVoices()
|
||||||
|
const voiceId = Object.keys(voices).find(e => voices[e].title === matcher)
|
||||||
this.#playVoice(voiceId)
|
this.#playVoice(voiceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,18 +285,17 @@ export default class Voice {
|
|||||||
return `${folderObject.main}/${folderObject.sub.find(e => e.lang === this.#voiceLang).name}`
|
return `${folderObject.main}/${folderObject.sub.find(e => e.lang === this.#voiceLang).name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
#getSpecialVoiceId(matcher) {
|
|
||||||
const voices = this.#getVoices()
|
|
||||||
const voiceId = Object.keys(voices).find(e => voices[e].title === matcher)
|
|
||||||
return voiceId
|
|
||||||
}
|
|
||||||
|
|
||||||
#getVoices() {
|
#getVoices() {
|
||||||
return charword_table.operator.voice[this.#defaultRegion][this.#getWordKeyByVoiceLang()[this.#defaultVoiceLang]]
|
return charword_table.subtitleLangs[this.#subtitleLang].default
|
||||||
}
|
}
|
||||||
|
|
||||||
#getSubtitleById(id) {
|
#getSubtitleById(id) {
|
||||||
return charword_table.operator.voice[this.#subtitleLang][this.#getWordKeyByVoiceLang()[this.#voiceLang]][id]
|
const obj = charword_table.subtitleLangs[this.#subtitleLang]
|
||||||
|
let key = 'default'
|
||||||
|
if (obj[this.#voiceLang]) {
|
||||||
|
key = this.#voiceLang
|
||||||
|
}
|
||||||
|
return obj[key][id]
|
||||||
}
|
}
|
||||||
|
|
||||||
#getVoiceFolderObject() {
|
#getVoiceFolderObject() {
|
||||||
@@ -314,51 +311,8 @@ export default class Voice {
|
|||||||
return folderObject
|
return folderObject
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns the cvInfo in the region's language
|
|
||||||
*/
|
|
||||||
#getCVInfo(region) {
|
|
||||||
const infoArray = Object.values(charword_table.operator.info[region])
|
|
||||||
// combine the infoArray
|
|
||||||
let output = {}
|
|
||||||
for (const info of infoArray) {
|
|
||||||
output = {
|
|
||||||
...output,
|
|
||||||
...info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns the cvInfo corresponsing to the voice language
|
|
||||||
*/
|
|
||||||
#getCVInfoByVoiceLang() {
|
|
||||||
const languages = {}
|
|
||||||
for (const lang of Object.keys(charword_table.operator.info)) {
|
|
||||||
const cvInfo = this.#getCVInfo(lang)
|
|
||||||
for (const [voiceLanguage, cvArray] of Object.entries(cvInfo)) {
|
|
||||||
if (languages[voiceLanguage] === undefined) {
|
|
||||||
languages[voiceLanguage] = {}
|
|
||||||
}
|
|
||||||
languages[voiceLanguage][lang] = cvArray
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return languages
|
|
||||||
}
|
|
||||||
|
|
||||||
#getWordKeyByVoiceLang() {
|
|
||||||
const output = {}
|
|
||||||
for (const [wordKey, wordKeyDict] of Object.entries(charword_table.operator.info[this.#defaultRegion])) {
|
|
||||||
for (const lang of Object.keys(wordKeyDict)) {
|
|
||||||
output[lang] = wordKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
#getSubtitleLanguages() {
|
#getSubtitleLanguages() {
|
||||||
return Object.keys(this.#getCVInfoByVoiceLang()[this.#voiceLang])
|
return Object.keys(charword_table.subtitleLangs)
|
||||||
}
|
}
|
||||||
|
|
||||||
#insertHTML() {
|
#insertHTML() {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-undef */
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
@@ -156,12 +157,20 @@ class ViteRunner {
|
|||||||
, {
|
, {
|
||||||
key: "background_folder",
|
key: "background_folder",
|
||||||
value: JSON.stringify(this.#globalConfig.folder.background)
|
value: JSON.stringify(this.#globalConfig.folder.background)
|
||||||
|
}, {
|
||||||
|
key: "available_operators",
|
||||||
|
value: JSON.stringify(Object.keys(this.#globalConfig.operators))
|
||||||
|
}, {
|
||||||
|
key: "error_files",
|
||||||
|
value: JSON.stringify(this.#globalConfig.directory.error).replace('#', '%23')
|
||||||
}
|
}
|
||||||
]), path.join(directoryDir, '.env'))
|
]), path.join(directoryDir, '.env'))
|
||||||
this.#mode = process.argv[3]
|
this.#mode = process.argv[3]
|
||||||
|
const publicDir = path.resolve(__projetRoot, this.#globalConfig.folder.release)
|
||||||
return {
|
return {
|
||||||
...this.#baseViteConfig,
|
...this.#baseViteConfig,
|
||||||
envDir: directoryDir,
|
envDir: directoryDir,
|
||||||
|
base: "/",
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
// PerfseePlugin({
|
// PerfseePlugin({
|
||||||
@@ -178,6 +187,7 @@ class ViteRunner {
|
|||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(directoryDir, './src'),
|
'@': path.resolve(directoryDir, './src'),
|
||||||
'!': path.resolve(__projetRoot, './src'),
|
'!': path.resolve(__projetRoot, './src'),
|
||||||
|
'#': path.resolve(publicDir, this.#globalConfig.folder.directory),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
@@ -187,6 +197,7 @@ class ViteRunner {
|
|||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
manualChunks: {
|
manualChunks: {
|
||||||
|
'react': ['react', 'react-dom', 'react-router-dom'],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user