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'], }, } }