diff --git a/config.yaml b/config.yaml index 0538059..a37f7fb 100644 --- a/config.yaml +++ b/config.yaml @@ -2,9 +2,21 @@ folder: operator: ./operator/ release: ./release/ background: background - voice: voice directory: _assets share: _share + voice: + main: voice + sub: + - name: jp + lang: JP + - name: cn + lang: CN_MANDARIN + - name: en + lang: EN + - name: kr + lang: KR + - name: custom + lang: CUSTOM operators: chen: !include config/chen.yaml dusk: !include config/dusk.yaml diff --git a/libs/env_generator.js b/libs/env_generator.js index d6ee719..a0487f1 100644 --- a/libs/env_generator.js +++ b/libs/env_generator.js @@ -23,6 +23,7 @@ export default class EnvGenerator { `VITE_IMAGE_HEIGHT=2048`, `VITE_BACKGROUND_FILES=${JSON.stringify(this.#assets.backgrounds)}`, `VITE_BACKGROUND_FOLDER=${__config.folder.background}`, + `VITE_VOICE_FOLDERS=${JSON.stringify(__config.folder.voice)}`, ].join('\n') } diff --git a/libs/file.js b/libs/file.js index 6335258..58c7ee8 100644 --- a/libs/file.js +++ b/libs/file.js @@ -53,6 +53,15 @@ export async function copy(sourcePath, targetPath) { return await fsP.copyFile(sourcePath, targetPath) } +export async function copyDir(sourcePath, targetPath) { + if (!exists(sourcePath)) { + console.warn(`Source file ${sourcePath} does not exist.`) + return + } + mkdir(targetPath) + return await fsP.cp(sourcePath, targetPath, { recursive: true }) +} + export function appendSync(content, filePath) { return fs.appendFileSync(filePath, content, 'utf8'); } diff --git a/libs/initializer.js b/libs/initializer.js index 0d7597b..5725165 100644 --- a/libs/initializer.js +++ b/libs/initializer.js @@ -5,7 +5,9 @@ import { mkdir, writeSync } from './file.js' import { appendMainConfig } from './append.js' export default function init(operatorName, extractedDir) { - mkdir(extractedDir) + extractedDir.forEach((dir) => { + mkdir(dir) + }) const date = new Date() const template = readYAML(path.join(__dirname, 'config', '_template.yaml')) template.link = operatorName diff --git a/runner.js b/runner.js index e302c76..b74fd93 100644 --- a/runner.js +++ b/runner.js @@ -5,7 +5,7 @@ import { fork } from 'child_process'; import getConfig from './libs/config.js' import ProjectJson from './libs/project_json.js' import EnvGenerator from './libs/env_generator.js' -import { write, rmdir, copy, writeSync } from './libs/file.js' +import { write, rmdir, copy, writeSync, copyDir } from './libs/file.js' import AssetsProcessor from './libs/assets_processor.js' import init from './libs/initializer.js' import directory from './libs/directory.js' @@ -54,6 +54,7 @@ async function main() { const OPERATOR_RELEASE_FOLDER = path.join(__dirname, __config.folder.release, OPERATOR_NAME) const SHOWCASE_PUBLIC_ASSSETS_FOLDER = path.join(OPERATOR_RELEASE_FOLDER, "assets") const EXTRACTED_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'extracted') + const VOICE_FOLDERS = __config.folder.voice.sub.map((sub) => path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, __config.folder.voice.main, sub.name)) const OPERATOR_SHARE_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, __config.folder.share) /** @@ -63,7 +64,7 @@ async function main() { */ switch (op) { case 'init': - init(OPERATOR_NAME, EXTRACTED_FOLDER) + init(OPERATOR_NAME, [EXTRACTED_FOLDER, ...VOICE_FOLDERS]) process.exit(0) case 'readme': appendReadme(OPERATOR_NAME) @@ -115,6 +116,16 @@ async function main() { copy(path.join(file.source, file.filename), path.join(file.target, file.filename)) }) + const foldersToCopy = [ + { + source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, __config.folder.voice.main), + target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER, __config.folder.voice.main) + } + ] + foldersToCopy.forEach((folder) => { + copyDir(folder.source, folder.target) + }) + const envPath = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, '.env') writeSync((new EnvGenerator(OPERATOR_NAME, { backgrounds diff --git a/src/components/player.js b/src/components/player.js index b0a757d..6dba470 100644 --- a/src/components/player.js +++ b/src/components/player.js @@ -55,6 +55,7 @@ export default function spinePlayer(el) { entry.mixDuration = 0.3; widget.animationState.addAnimation(0, "Idle", true, 0); } + window.voice.success() window.settings.success() }, }) diff --git a/src/components/settings.js b/src/components/settings.js index 796aa68..a9503fa 100644 --- a/src/components/settings.js +++ b/src/components/settings.js @@ -38,6 +38,8 @@ export default class Settings { #logoX = this.#defaultLogoX #logoY = this.#defaultLogoY #isInsightsInited = false + #doNotTrack = false + #lastFunctionInsights = null constructor(el, logoEl) { this.#el = el @@ -65,22 +67,31 @@ export default class Settings { success() { this.loadViewport() - this.insights(false) + this.insights(false, false) } - insights(flag) { - if (this.#isInsightsInited) return - window.umami.trackView(`/${import.meta.env.VITE_LINK}${flag ? "?steam" : ""}`); + insights(isWallpaperEngine, doNotTrack) { this.#isInsightsInited = true + if (this.#isInsightsInited || doNotTrack || import.meta.env.MODE === 'development') return + this.#doNotTrack = doNotTrack + window.umami?.trackView(`/${import.meta.env.VITE_LINK}${isWallpaperEngine ? "?steam" : ""}`); + } + + functionInsights(functionName) { + if (!this.#isInsightsInited || this.#doNotTrack || import.meta.env.MODE === 'development' || functionName === this.#lastFunctionInsights) return + this.#lastFunctionInsights = functionName + window.umami?.trackEvent(`${functionName}`); } setFPS(value) { this.#fps = value this.spinePlayer.setFps(value) + this.functionInsights("setFPS") } setLogoDisplay(flag) { this.#logoEl.hidden = flag; + this.functionInsights("setLogoDisplay") } #resize(_this, value) { @@ -101,6 +112,7 @@ export default class Settings { this.#logoEl.src = src this.#resize() this.#setLogoInvertFilter(invert_filter) + this.functionInsights("setLogo") } #readFile(e, onload, callback) { @@ -131,23 +143,27 @@ export default class Settings { setLogoRatio(value) { this.#ratio = value this.#resize(this, value) + this.functionInsights("setLogoRatio") } setLogoOpacity(value) { this.#logoEl.style.opacity = value / 100 this.#opacity = value + this.functionInsights("setLogoOpacity") } - setBackgoundImage(v) { + setBackgoundImage(v, skipInsights = false) { document.body.style.backgroundImage = v + if (!skipInsights) this.functionInsights("setBackgoundImage"); } setDefaultBackground(e) { const backgroundURL = `url("${import.meta.env.BASE_URL}assets/${import.meta.env.VITE_BACKGROUND_FOLDER}/${e}")` if (document.getElementById("custom_background_clear").disabled && !document.body.style.backgroundImage.startsWith("url(\"file:")) { - this.setBackgoundImage(backgroundURL) + this.setBackgoundImage(backgroundURL, true) } this.#defaultBackgroundImage = backgroundURL + this.functionInsights("setDefaultBackground") } setBackground(e) { @@ -198,6 +214,7 @@ export default class Settings { break; } this.loadViewport() + this.functionInsights("positionPadding") } positionReset() { @@ -242,6 +259,7 @@ export default class Settings { break; } this.elementPosition(this.#logoEl, this.#logoX, this.#logoY) + this.functionInsights("logoPadding") } logoReset() { diff --git a/src/components/voice.js b/src/components/voice.js index 1a0a9a1..6bd8861 100644 --- a/src/components/voice.js +++ b/src/components/voice.js @@ -3,11 +3,15 @@ import '@/components/voice.css' export default class Voice { #el + #widgetEl + #audioEl = new Audio() + #audioElId = 'voice-audio' #defaultVoiceLang = "JP" #defaultRegion = charword_table.config.default_region #defaultIdleDuration = 10 * 60 * 1000 #defaultNextDuration = 3 * 60 * 1000 #voiceLang = this.#defaultVoiceLang + #voiceLanguages = Object.keys(this.#getCVInfo(this.#defaultRegion)) #subtitleLang = this.#defaultRegion #useSubtitle = false #useVoice = false @@ -19,19 +23,22 @@ export default class Voice { #nextListener = -1 #nextDuration = this.#defaultNextDuration #lastClickToNext = false + #voiceFolderObject = this.#getVoiceFolderObject() - constructor(el) { + constructor(el, widgetEl) { this.#el = el + this.#widgetEl = widgetEl } init() { - this.#playTitleVoice() + this.#insertHTML() + this.#audioEl = document.getElementById(this.#audioElId) } success() { this.#playEntryVoice() this.#initNextVoiceTimer() - document.addEventListener('click', e => { + this.#widgetEl.addEventListener('click', e => { this.#lastClickToNext = true this.#nextVoice() }) @@ -86,7 +93,7 @@ export default class Voice { * @param {string} lang */ set language(lang) { - if (this.#getVoiceLanguages().includes(lang)) { + if (this.#voiceLanguages.includes(lang)) { this.#voiceLang = lang } else { this.#voiceLang = this.#defaultVoiceLang @@ -102,7 +109,7 @@ export default class Voice { } get languages() { - return this.#getVoiceLanguages() + return this.#voiceLanguages } /** @@ -135,10 +142,6 @@ export default class Voice { return this.#nextDuration / 60 / 1000 } - #playTitleVoice() { - this.#playSpecialVoice("标题") - } - #initIdleVoiceTimer() { this.#idleListener = setInterval(() => { this.#playSpecialVoice("闲置") @@ -169,16 +172,26 @@ export default class Voice { #playVoice(id) { this.#currentVoiceId = id - const audio = new Audio() - // audio.src = `https://cdn.jsdelivr.net/gh/Arondight/Adachi-BOT@master/src/assets/voice/${id}.mp3` - audio.play() - audio.addEventListener('ended', () => { - this.#lastVoiceId = this.#currentVoiceId - this.#currentVoiceId = null - this.#setCurrentSubtitle(null) - this.#lastClickToNext = false - }) - this.#setCurrentSubtitle(id) + this.#audioEl.src = `./assets/${this.#getVoiceFoler() +}/${id}.wav` + let startPlayPromise = this.#audioEl.play() + if (startPlayPromise !== undefined) { + startPlayPromise + .then(() => { + const audioEndedFunc = () => { + this.#lastVoiceId = this.#currentVoiceId + this.#currentVoiceId = null + this.#setCurrentSubtitle(null) + this.#lastClickToNext = false + this.#audioEl.removeEventListener('ended', audioEndedFunc) + } + this.#audioEl.addEventListener('ended', audioEndedFunc) + this.#setCurrentSubtitle(id) + }) + .catch(() => { + return + }) + } } #playSpecialVoice(matcher) { @@ -186,6 +199,11 @@ export default class Voice { this.#playVoice(voiceId) } + #getVoiceFoler() { + const folderObject = this.#voiceFolderObject + 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) @@ -200,8 +218,17 @@ export default class Voice { return charword_table.operator.voice[this.#subtitleLang][this.#getWordKeyByVoiceLang()[this.#voiceLang]][id] } - #getVoiceLanguages() { - return Object.keys(this.#getCVInfo(this.#defaultRegion)) + #getVoiceFolderObject() { + const folderObject = JSON.parse(import.meta.env.VITE_VOICE_FOLDERS) + const languagesCopy = this.#voiceLanguages.slice() + const customVoiceName = languagesCopy.filter(i => !folderObject.sub.map(e => e.lang).includes(i))[0] + folderObject.sub = folderObject.sub.map(e => { + return { + name: e.name, + lang: e.lang === "CUSTOM" ? customVoiceName : e.lang + } + }) + return folderObject } /** @@ -251,4 +278,12 @@ export default class Voice { return Object.keys(this.#getCVInfoByVoiceLang()[this.#voiceLang]) } + #insertHTML() { + this.#el.innerHTML = ` + + ` + } } \ No newline at end of file diff --git a/src/index.js b/src/index.js index 00a3e4f..e15e92b 100644 --- a/src/index.js +++ b/src/index.js @@ -7,16 +7,16 @@ import Voice from '@/components/voice' document.querySelector('#app').innerHTML = `
+
-
` -window.voice = new Voice(document.querySelector('#voice')) +window.voice = new Voice(document.querySelector('#voice'), document.querySelector('#widget-wrapper')) window.voice.init() window.settings = new Settings(document.querySelector('#settings'), document.querySelector('#logo')) document.title = import.meta.env.VITE_TITLE diff --git a/src/libs/wallpaper_engine.js b/src/libs/wallpaper_engine.js index f724508..723086e 100644 --- a/src/libs/wallpaper_engine.js +++ b/src/libs/wallpaper_engine.js @@ -5,7 +5,7 @@ window.wallpaperPropertyListener = { } }, applyUserProperties: function (properties) { - window.settings.insights(true) + window.settings.insights(true, false) if (properties.logo) { window.settings.setLogoDisplay(!properties.logo.value) }