diff --git a/Version b/Version
index 654ec68..f82bbc7 100644
--- a/Version
+++ b/Version
@@ -1 +1 @@
-3.3.47
\ No newline at end of file
+3.3.59
\ No newline at end of file
diff --git a/aklive2d.js b/aklive2d.js
index 923f859..309594f 100644
--- a/aklive2d.js
+++ b/aklive2d.js
@@ -1,3 +1,5 @@
+/* eslint-disable no-fallthrough */
+/* eslint-disable no-undef */
import assert from 'assert'
import path from 'path'
import { fileURLToPath } from 'url'
@@ -56,8 +58,6 @@ async function main() {
await background.process()
const backgrounds = ['operator_bg.png', ...background.files]
- directory({backgrounds, charwordTable})
-
for (const OPERATOR_NAME of OPERATOR_NAMES) {
const OPERATOR_SOURCE_FOLDER = path.join(__projetRoot, __config.folder.operator)
const OPERATOR_RELEASE_FOLDER = path.join(__projetRoot, __config.folder.release, OPERATOR_NAME)
@@ -85,29 +85,36 @@ async function main() {
rmdir(OPERATOR_RELEASE_FOLDER)
const charwordTableLookup = charwordTable.lookup(OPERATOR_NAME)
- const voiceLangs = (() => {
- const infoArray = Object.values(charwordTableLookup.operator.info[charwordTableLookup.config.default_region])
- // combine the infoArray
- let output = {}
- for (const info of infoArray) {
- output = {
- ...output,
- ...info
+ const voiceJson = {}
+ voiceJson.config = {
+ default_region: charwordTableLookup.config.default_region.replace("_", "-"),
+ regions: charwordTableLookup.config.regions.map((item) => item.replace("_", "-")),
+ }
+ voiceJson.voiceLangs = {}
+ voiceJson.subtitleLangs = {}
+ 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 subtitleLangs = (() => {
- const output = []
- for (const [key, value] of Object.entries(charwordTableLookup.operator.info)) {
- if (Object.keys(value).length !== 0) {
- output.push(key)
- }
- }
- return output
- })()
+ })
+ const voiceLangs = Object.keys(voiceJson.voiceLangs["zh-CN"])
+ const subtitleLangs = Object.keys(voiceJson.subtitleLangs)
- 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, {
backgrounds,
@@ -219,6 +226,8 @@ async function main() {
]), envPath)
fork(path.join(__projetRoot, 'vite.config.js'), [op, OPERATOR_NAME])
}
+
+ directory({ backgrounds, charwordTable })
}
main();
\ No newline at end of file
diff --git a/changelogs.yaml b/changelogs.yaml
index 9ee50b5..a75e4e2 100644
--- a/changelogs.yaml
+++ b/changelogs.yaml
@@ -1,4 +1,6 @@
showcase:
+ 2023/03/03:
+ - Performance optimization
2023/02/26:
- Rename w_fugue to w_wonder
2023/02/14:
diff --git a/config.yaml b/config.yaml
index 7952649..8193f91 100644
--- a/config.yaml
+++ b/config.yaml
@@ -24,6 +24,9 @@ share:
directory:
title: AKLive2D
voice: jp/CN_037.ogg
+ error:
+ files: !include config/_directory.yaml
+ voice: CN_034.ogg
operators:
chen: !include config/chen.yaml
dusk: !include config/dusk.yaml
@@ -49,4 +52,4 @@ operators:
mizuki_summer_feast: !include config/mizuki_summer_feast.yaml
chongyue: !include config/chongyue.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
\ No newline at end of file
diff --git a/config/_directory.yaml b/config/_directory.yaml
new file mode 100644
index 0000000..f27aaa4
--- /dev/null
+++ b/config/_directory.yaml
@@ -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
\ No newline at end of file
diff --git a/config/_project_json.yaml b/config/_project_json.yaml
index 379aa12..3fcecfc 100644
--- a/config/_project_json.yaml
+++ b/config/_project_json.yaml
@@ -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:
en-us:
ui_notice_title:
📝 Notes
diff --git a/directory/.env b/directory/.env
index 016f182..ede81d2 100644
--- a/directory/.env
+++ b/directory/.env
@@ -2,4 +2,6 @@ VITE_APP_TITLE=AKLive2D
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_DIRECTORY_FOLDER="_assets"
-VITE_BACKGROUND_FOLDER="background"
\ No newline at end of file
+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"}
\ No newline at end of file
diff --git a/directory/Version b/directory/Version
index 5767123..a6a3a43 100644
--- a/directory/Version
+++ b/directory/Version
@@ -1 +1 @@
-0.5.30
\ No newline at end of file
+1.0.4
\ No newline at end of file
diff --git a/directory/src/libs/voice.jsx b/directory/src/libs/voice.jsx
index e6cbf42..a7e2a5b 100644
--- a/directory/src/libs/voice.jsx
+++ b/directory/src/libs/voice.jsx
@@ -5,7 +5,7 @@ import {
} from "react"
import { useCallback } from "react"
const audioEl = new Audio()
-
+let lastSrc = ''
export default function useAudio() {
const [isPlaying, _setIsPlaying] = useState(false)
const isPlayingRef = useRef(isPlaying)
@@ -29,17 +29,19 @@ export default function useAudio() {
},
callback = () => { }
) => {
- if (!options.overwrite && audioEl.src === (window.location.href.replace(/\/$/g, '') + link)) return
+ if (!options.overwrite && link === lastSrc) return
audioEl.src = link
let startPlayPromise = audioEl.play()
if (startPlayPromise !== undefined) {
setIsPlaying(true)
startPlayPromise
.then(() => {
+ lastSrc = link
callback()
return
})
- .catch(() => {
+ .catch((e) => {
+ console.log(e)
return
})
}
diff --git a/directory/src/routes/error-page.css b/directory/src/routes/error-page.css
new file mode 100644
index 0000000..e7ef39f
--- /dev/null
+++ b/directory/src/routes/error-page.css
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/directory/src/routes/error-page.jsx b/directory/src/routes/error-page.jsx
index e0810a0..7070af2 100644
--- a/directory/src/routes/error-page.jsx
+++ b/directory/src/routes/error-page.jsx
@@ -1,17 +1,170 @@
-import React from "react";
+import React, {
+ useState,
+ useEffect,
+ useMemo,
+ useRef,
+ useCallback
+} from "react";
import {
useNavigate,
useRouteError
} 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() {
const error = useRouteError();
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 (
-
- error
-
+
+
+ navigate(-1, { replace: true })}
+ />
+ setVoiceOn(!voiceOn)}
+ />
+
+
+ {
+ content.map((item, index) => {
+ return (
+
+ )
+ })
+ }
+
+
);
}
\ No newline at end of file
diff --git a/directory/src/routes/path/changelogs.css b/directory/src/routes/path/changelogs.css
index 6a67f58..c2ea891 100644
--- a/directory/src/routes/path/changelogs.css
+++ b/directory/src/routes/path/changelogs.css
@@ -9,6 +9,7 @@
align-items: flex-start;
gap: 0.5rem;
padding-left: 1rem;
+ word-break: break-word;
}
.changelogs .item-info-content {
diff --git a/directory/src/routes/path/home.css b/directory/src/routes/path/home.css
index 43d0cc9..d10b81c 100644
--- a/directory/src/routes/path/home.css
+++ b/directory/src/routes/path/home.css
@@ -3,6 +3,7 @@
display: flex;
align-items: flex-end;
flex-wrap: wrap;
+ user-select: none;
}
.item-group-date {
diff --git a/directory/src/routes/path/operator.css b/directory/src/routes/path/operator.css
index cf9f237..13bc416 100644
--- a/directory/src/routes/path/operator.css
+++ b/directory/src/routes/path/operator.css
@@ -8,13 +8,12 @@
background-position: center;
background-repeat: no-repeat;
background-size: cover;
-}
-
-.operator .spine-container {
width: 100%;
position: relative;
margin-bottom: 2rem;
+ user-select: none;
}
+
.operator .spine-container:before {
content: "";
display: block;
@@ -28,9 +27,9 @@
right: 0;
}
-.operator .spine-settings,
-.operator .steam-workshop-wrapper {
+.operator .spine-settings {
margin-right: 1.5rem;
+ user-select: none;
}
.operator .text {
diff --git a/directory/src/routes/path/operator.jsx b/directory/src/routes/path/operator.jsx
index a33f879..ad15268 100644
--- a/directory/src/routes/path/operator.jsx
+++ b/directory/src/routes/path/operator.jsx
@@ -33,8 +33,6 @@ const getVoiceFoler = (lang) => {
}
const spinePlayerAtom = atom(null);
const spineAnimationAtom = atom("Idle");
-const voiceLangAtom = atom(null);
-const subtitleLangAtom = atom(null);
const getTabName = (item, language) => {
if (item.type === 'operator') {
@@ -63,11 +61,11 @@ export default function Operator() {
const [spineAnimation, setSpineAnimation] = useAtom(spineAnimationAtom)
const { i18n } = useI18n()
const [spinePlayer, setSpinePlayer] = useAtom(spinePlayerAtom)
- const [voiceLang, _setVoiceLang] = useAtom(voiceLangAtom)
+ const [voiceLang, _setVoiceLang] = useState(null)
const { backgrounds } = useBackgrounds()
const [currentBackground, setCurrentBackground] = useState(null)
const [voiceConfig, setVoiceConfig] = useState(null)
- const [subtitleLang, setSubtitleLang] = useAtom(subtitleLangAtom)
+ const [subtitleLang, setSubtitleLang] = useState(null)
const [hideSubtitle, setHideSubtitle] = useState(true)
const { play, stop, getSrc, isPlaying, isPlayingRef } = useAudio()
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 (
diff --git a/directory/src/routes/root.css b/directory/src/routes/root.css
index 5da2231..25f852d 100644
--- a/directory/src/routes/root.css
+++ b/directory/src/routes/root.css
@@ -86,6 +86,7 @@
display: flex;
flex-direction: row;
align-items: flex-start;
+ user-select: none;
}
.drawer .links {
@@ -237,6 +238,10 @@
color: var(--text-color);
}
+.footer {
+ user-select: none;
+}
+
.footer .section {
border-top: 1px solid var(--border-color);
padding: 1rem 0;
diff --git a/directory/src/routes/root.jsx b/directory/src/routes/root.jsx
index 50b6040..df17dc9 100644
--- a/directory/src/routes/root.jsx
+++ b/directory/src/routes/root.jsx
@@ -132,6 +132,7 @@ export default function Root() {
function FooterElement() {
const { i18n } = useI18n()
const { version } = useConfig()
+ const navigate = useNavigate()
return useMemo(() => {
return (
@@ -160,7 +161,9 @@ function FooterElement() {
-
+ {
+ navigate('/error')
+ }}>
Spine Runtimes © 2013 - 2019 Esoteric Software LLC
Assets © 2017 - {currentYear} Arknights/Hypergryph Co., Ltd
Source Code © 2021 - {currentYear} Halyul
@@ -169,7 +172,7 @@ function FooterElement() {
)
- }, [i18n, version.directory, version.showcase])
+ }, [i18n, navigate, version.directory, version.showcase])
}
function DrawerDestinations({ toggleDrawer }) {
diff --git a/libs/assets_processor.js b/libs/assets_processor.js
index 40d30db..5a9af8e 100644
--- a/libs/assets_processor.js
+++ b/libs/assets_processor.js
@@ -36,10 +36,10 @@ export default class AssetsProcessor {
const croppedBuffer = await this.#alphaCompositer.crop(portraitBuffer, rect)
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_PNG_PREFIX = 'data:image/png;base64,'
const assetsJson = {}
diff --git a/libs/charword_table.js b/libs/charword_table.js
index c9e4f60..9f6f0e3 100644
--- a/libs/charword_table.js
+++ b/libs/charword_table.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-undef */
import fetch from "node-fetch"
import path from "path"
import dotenv from "dotenv"
diff --git a/libs/directory.js b/libs/directory.js
index 8b63855..571b0d6 100644
--- a/libs/directory.js
+++ b/libs/directory.js
@@ -1,16 +1,13 @@
+/* eslint-disable no-undef */
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';
-
-/**
- * TODO:
- * 1. add voice config -> look up charword table
- */
+import AssetsProcessor from './assets_processor.js'
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 sourceFolder = path.join(__projetRoot, __config.folder.operator);
- rmdir(targetFolder);
const filesToCopy = Object.keys(__config.operators)
const directoryJson = {
operators: Object.values(
@@ -36,35 +33,6 @@ export default function ({ backgrounds, charwordTable }) {
}
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 changelogsArray = Object.keys(changelogs).reduce((acc, cur) => {
const array = []
@@ -79,11 +47,20 @@ export default function ({ backgrounds, charwordTable }) {
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(versionJson, null), path.join(targetFolder, "version.json"))
writeSync(JSON.stringify(changelogsArray, null), path.join(targetFolder, "changelogs.json"))
writeSync(JSON.stringify(backgrounds, null), path.join(targetFolder, "backgrounds.json"))
filesToCopy.forEach((key) => {
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`))
}
diff --git a/package.json b/package.json
index af2bf5a..8bb7640 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
"react-dom": "^18.2.0",
"react-refresh": "^0.14.0",
"react-router-dom": "^6.8.1",
+ "react-simple-typewriter": "^5.0.1",
"reset-css": "^5.0.1",
"sharp": "^0.31.3",
"yaml": "^2.2.1"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f6b93db..07c63fd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,7 @@ specifiers:
react-dom: ^18.2.0
react-refresh: ^0.14.0
react-router-dom: ^6.8.1
+ react-simple-typewriter: ^5.0.1
reset-css: ^5.0.1
rollup: ^3.17.3
sharp: ^0.31.3
@@ -32,6 +33,7 @@ dependencies:
react-dom: 18.2.0_react@18.2.0
react-refresh: 0.14.0
react-router-dom: 6.8.1_biqbaboplfbrettd7655fr4n2y
+ react-simple-typewriter: 5.0.1_biqbaboplfbrettd7655fr4n2y
reset-css: 5.0.1
sharp: 0.31.3
yaml: 2.2.1
@@ -1996,6 +1998,17 @@ packages:
react: 18.2.0
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:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
diff --git a/src/components/voice.js b/src/components/voice.js
index 6049fa0..b0bd673 100644
--- a/src/components/voice.js
+++ b/src/components/voice.js
@@ -9,7 +9,7 @@ export default class Voice {
#defaultRegion = charword_table.config.default_region
#defaultIdleDuration = 10 * 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]
#voiceLang = this.#defaultVoiceLang
#subtitleLang = this.#defaultRegion
@@ -42,6 +42,12 @@ export default class Voice {
}
success() {
+ const audioEndedFunc = () => {
+ this.#isPlaying = false
+ this.#setCurrentSubtitle(null)
+ this.#lastClickToNext = false
+ }
+ this.#audioEl.addEventListener('ended', audioEndedFunc)
this.#playEntryVoice()
this.#initNextVoiceTimer()
this.#widgetEl.addEventListener('click', e => {
@@ -242,7 +248,7 @@ export default class Voice {
const subtitle = this.#getSubtitleById(id)
const title = subtitle.title
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_subtitle').innerText = content
this.#el.style.opacity = 1
@@ -260,15 +266,6 @@ export default class Voice {
startPlayPromise
.then(() => {
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)
})
.catch(() => {
@@ -278,7 +275,8 @@ export default class Voice {
}
#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)
}
@@ -287,18 +285,17 @@ export default class Voice {
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() {
- return charword_table.operator.voice[this.#defaultRegion][this.#getWordKeyByVoiceLang()[this.#defaultVoiceLang]]
+ return charword_table.subtitleLangs[this.#subtitleLang].default
}
#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() {
@@ -314,51 +311,8 @@ export default class Voice {
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() {
- return Object.keys(this.#getCVInfoByVoiceLang()[this.#voiceLang])
+ return Object.keys(charword_table.subtitleLangs)
}
#insertHTML() {
diff --git a/vite.config.js b/vite.config.js
index d2d232d..248c404 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-undef */
import path from 'path'
import { fileURLToPath } from 'url'
import { defineConfig } from 'vite'
@@ -156,12 +157,20 @@ class ViteRunner {
, {
key: "background_folder",
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'))
this.#mode = process.argv[3]
+ const publicDir = path.resolve(__projetRoot, this.#globalConfig.folder.release)
return {
...this.#baseViteConfig,
envDir: directoryDir,
+ base: "/",
plugins: [
react(),
// PerfseePlugin({
@@ -178,6 +187,7 @@ class ViteRunner {
alias: {
'@': path.resolve(directoryDir, './src'),
'!': path.resolve(__projetRoot, './src'),
+ '#': path.resolve(publicDir, this.#globalConfig.folder.directory),
},
},
build: {
@@ -187,6 +197,7 @@ class ViteRunner {
rollupOptions: {
output: {
manualChunks: {
+ 'react': ['react', 'react-dom', 'react-router-dom'],
},
}
}