feat(showcase): add music

This commit is contained in:
Haoyu Xu
2023-03-15 21:27:34 -04:00
parent cf933d6a56
commit f3aa867e00
24 changed files with 449 additions and 166 deletions

View File

@@ -1 +1 @@
3.3.63
3.4.1

View File

@@ -15,9 +15,10 @@ import { appendReadme } from './libs/append.js'
import { increase } from './libs/version.js';
import Background from './libs/background.js'
import CharwordTable from './libs/charword_table.js';
import Music from './libs/music.js';
async function main() {
global.__projetRoot = path.dirname(fileURLToPath(import.meta.url))
global.__projectRoot = path.dirname(fileURLToPath(import.meta.url))
global.__config = getConfig()
const op = process.argv[2]
@@ -33,17 +34,17 @@ async function main() {
switch (op) {
case 'directory':
assert(OPERATOR_NAMES.length !== 0, 'Please set a mode for Directory.')
fork(path.join(__projetRoot, 'vite.config.js'), [op, OPERATOR_NAMES])
fork(path.join(__projectRoot, 'vite.config.js'), [op, OPERATOR_NAMES])
return
case 'build-all':
for (const [key, _] of Object.entries(__config.operators)) {
for (const [key,] of Object.entries(__config.operators)) {
OPERATOR_NAMES.push(key)
}
__config.version.showcase = increase(__projetRoot)
__config.version.showcase = increase(__projectRoot)
break
case 'preview':
assert(OPERATOR_NAMES.length !== 0, 'Please set the operator name.')
fork(path.join(__projetRoot, 'vite.config.js'), [op, OPERATOR_NAMES])
fork(path.join(__projectRoot, 'vite.config.js'), [op, OPERATOR_NAMES])
return
case 'charword':
await charwordTable.process()
@@ -57,10 +58,11 @@ async function main() {
const background = new Background()
await background.process()
const backgrounds = ['operator_bg.png', ...background.files]
const { musicToCopy, musicMapping } = Music()
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)
const OPERATOR_SOURCE_FOLDER = path.join(__projectRoot, __config.folder.operator)
const OPERATOR_RELEASE_FOLDER = path.join(__projectRoot, __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))
@@ -116,62 +118,6 @@ async function main() {
writeSync(JSON.stringify(voiceJson), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'charword_table.json'))
const projectJson = new ProjectJson(OPERATOR_NAME, OPERATOR_SHARE_FOLDER, {
backgrounds,
voiceLangs,
subtitleLangs
})
projectJson.load().then((content) => {
write(JSON.stringify(content, null, 2), path.join(OPERATOR_RELEASE_FOLDER, 'project.json'))
})
const assetsProcessor = new AssetsProcessor(OPERATOR_NAME, OPERATOR_SHARE_FOLDER)
assetsProcessor.process(EXTRACTED_FOLDER).then((content) => {
write(JSON.stringify(content.assetsJson, null), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, `assets.json`))
})
const filesToCopy = [
...background.getFilesToCopy(SHOWCASE_PUBLIC_ASSSETS_FOLDER),
{
filename: 'preview.jpg',
source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME),
target: path.join(OPERATOR_RELEASE_FOLDER)
},
{
filename: 'operator_bg.png',
source: path.join(OPERATOR_SHARE_FOLDER, __config.folder.background),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER, __config.folder.background)
},
{
filename: `${__config.operators[OPERATOR_NAME].logo}.png`,
source: path.join(OPERATOR_SHARE_FOLDER, 'logo'),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER)
},
{
filename: `${__config.operators[OPERATOR_NAME].fallback_name}.png`,
source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER)
},
{
filename: `${__config.operators[OPERATOR_NAME].fallback_name}_portrait.png`,
source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER)
}
]
filesToCopy.forEach((file) => {
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()).generate([
{
@@ -219,12 +165,82 @@ async function main() {
}, {
key: "voice_folders",
value: JSON.stringify(__config.folder.voice)
}, {
key: "music_folder",
value: __config.folder.music
}, {
key: "music_mapping",
value: JSON.stringify(musicMapping)
}
]), envPath)
fork(path.join(__projetRoot, 'vite.config.js'), [op, OPERATOR_NAME])
const projectJson = new ProjectJson(OPERATOR_NAME, OPERATOR_SHARE_FOLDER, {
backgrounds,
voiceLangs,
subtitleLangs,
music: Object.keys(musicMapping)
})
projectJson.load().then((content) => {
write(JSON.stringify(content, null, 2), path.join(OPERATOR_RELEASE_FOLDER, 'project.json'))
})
const assetsProcessor = new AssetsProcessor(OPERATOR_NAME, OPERATOR_SHARE_FOLDER)
assetsProcessor.process(EXTRACTED_FOLDER).then((content) => {
write(JSON.stringify(content.assetsJson, null), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, `assets.json`))
})
const filesToCopy = [
...background.getFilesToCopy(SHOWCASE_PUBLIC_ASSSETS_FOLDER),
...musicToCopy.map(entry => {
return {
...entry,
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER, __config.folder.music)
}
}),
{
filename: 'preview.jpg',
source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME),
target: path.join(OPERATOR_RELEASE_FOLDER)
},
{
filename: 'operator_bg.png',
source: path.join(OPERATOR_SHARE_FOLDER, __config.folder.background),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER, __config.folder.background)
},
{
filename: `${__config.operators[OPERATOR_NAME].logo}.png`,
source: path.join(OPERATOR_SHARE_FOLDER, 'logo'),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER)
},
{
filename: `${__config.operators[OPERATOR_NAME].fallback_name}.png`,
source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER)
},
{
filename: `${__config.operators[OPERATOR_NAME].fallback_name}_portrait.png`,
source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER)
}
]
filesToCopy.forEach((file) => {
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)
})
fork(path.join(__projectRoot, 'vite.config.js'), [op, OPERATOR_NAME])
}
directory({ backgrounds, charwordTable })
directory({ backgrounds, musicMapping })
}
main();

View File

@@ -2,6 +2,7 @@ folder:
operator: ./operator/
release: ./release/
background: background
music: music
directory: _assets
share: _share
voice:

View File

@@ -32,6 +32,11 @@ localization:
ui_privacy_title: <hr><h4>📜 Privacy</h4><hr>
ui_privacy_text: <span><b>Check out our privacy policy at https://privacy.halyul.dev</b></span>
ui_privacy_do_not_track: Send usage data
ui_music_title: <hr><h4>📝 Music</h4><hr>
ui_music_notice: <span><b>Please adjust the 'Offset' value if you notice audio cutoff</b></span>
ui_music_selection: Music
ui_music_volume: Volume
ui_music_offset: Offset
zh-chs:
ui_notice_title: <hr><h4>📝 通知</h4><hr>
ui_notice_changelog: <span><b>现在支持干员语音! </b></span>
@@ -64,6 +69,11 @@ localization:
ui_privacy_title: <hr><h4>📜 隐私</h4><hr>
ui_privacy_text: <span><b>在 https://privacy.halyul.dev 查看我们的隐私政策</b></span>
ui_privacy_do_not_track: 发送使用数据
ui_music_title: <hr><h4>🎵 音乐</h4><hr>
ui_music_notice: <span><b>如若发现音频截止,请调节 '弥补' 数值</b></span>
ui_music_selection: 音乐
ui_music_volume: 音量
ui_music_offset: 弥补
properties:
- key: notice_title
value:
@@ -106,8 +116,8 @@ properties:
fraction: true
max: 100
min: 0
precision: 2
step: 0.1
precision: 1
- key: logoopacity
value:
text: ui_logo_opacity
@@ -214,6 +224,42 @@ properties:
condition: voicesubtitle.value == true
type: bool
value: false
- key: music_title
value:
text: ui_music_title
type: bool
value: false
- key: music_notice
value:
text: ui_music_notice
condition: music_title.value == true
- key: music_selection
value:
text: ui_music_selection
condition: music_title.value == true
type: combo
value: !match ~{var('assets', "music")[0]}
options: !match ~{var('assets', "musicOptions")}
- key: music_volume
value:
text: ui_music_volume
type: slider
value: 50
condition: music_title.value == true
fraction: false
max: 100
min: 0
- key: music_offset
value:
text: ui_music_offset
type: slider
value: 0.3
condition: music_title.value == true
fraction: true
precision: 2
step: 0.01
max: 1
min: 0
- key: position
value:
text: ui_position_title

View File

@@ -1,14 +1,3 @@
%outline-share {
content: "";
display: block;
position: absolute;
left: -3px;
height: 3px;
width: 100%;
border-left: var(--text-color) solid 3px;
border-right: var(--text-color) solid 3px;
}
.group {
padding: 1rem;
display: flex;
@@ -61,13 +50,23 @@
border: var(--home-item-outline-color) 1px dashed;
padding: 6px;
&:before,
&:after {
content: "";
display: block;
position: absolute;
left: -3px;
height: 3px;
width: 100%;
border-left: var(--text-color) solid 3px;
border-right: var(--text-color) solid 3px;
}
&:before {
@extend %outline-share;
top: -3px;
}
&:after {
@extend %outline-share;
bottom: -3px;
}
}

View File

@@ -12,5 +12,6 @@
<body style="background-image: url('./assets/background/operator_bg.png');">
<div id="app"></div>
<script type="module" src="/src/index.js"></script>
<script src="/wallpaper_engine.js"></script>
</body>
</html>

View File

@@ -1,18 +1,19 @@
/* eslint-disable no-undef */
import path from 'path'
import { appendSync, readSync } from './file.js'
export function appendReadme(operatorName) {
const operatorConfig = __config.operators[operatorName]
const projectJson = JSON.parse(readSync(path.join(__projetRoot, __config.folder.operator, operatorName, 'project.json')))
const projectJson = JSON.parse(readSync(path.join(__projectRoot, __config.folder.operator, operatorName, 'project.json')))
appendSync(
`\n| ${operatorConfig.codename["en-US"]} | [Link](https://arknights.halyul.dev/${operatorConfig.link}/?settings) | [Link](https://steamcommunity.com/sharedfiles/filedetails/?id=${projectJson.workshopid}) |`,
path.join(__projetRoot, 'README.md')
path.join(__projectRoot, 'README.md')
)
}
export function appendMainConfig(operatorName) {
appendSync(
`\n ${operatorName}: !include config/${operatorName}.yaml`,
path.join(__projetRoot, 'config.yaml')
path.join(__projectRoot, 'config.yaml')
)
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-undef */
import path from 'path'
import { read, write, readSync } from './file.js'
import AlphaComposite from './alpha_composite.js'
@@ -9,7 +10,7 @@ export default class AssetsProcessor {
#shareFolder
constructor(operatorName, shareFolder) {
this.#operatorSourceFolder = path.join(__projetRoot, __config.folder.operator)
this.#operatorSourceFolder = path.join(__projectRoot, __config.folder.operator)
this.#alphaCompositer = new AlphaComposite()
this.#operatorName = operatorName
this.#shareFolder = shareFolder

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-undef */
import path from 'path';
import fs from 'fs';
import sharp from "sharp";
@@ -8,7 +9,7 @@ export default class Background {
#files
constructor() {
this.#backgroundFolder = path.join(__projetRoot, __config.folder.operator, __config.folder.share, __config.folder.background);
this.#backgroundFolder = path.join(__projectRoot, __config.folder.operator, __config.folder.share, __config.folder.background);
this.#extractFolder = path.join(this.#backgroundFolder, 'extracted');
}

View File

@@ -23,7 +23,7 @@ export function getOperatorId(operatorConfig) {
export default class CharwordTable {
#operatorIDs = Object.values(__config.operators).map(operator => { return getOperatorId(operator) })
#charwordTablePath = path.join(__projetRoot, __config.folder.operator, __config.folder.share)
#charwordTablePath = path.join(__projectRoot, __config.folder.operator, __config.folder.share)
#charwordTableFile = path.join(this.#charwordTablePath, 'charword_table.json')
#charwordTable = JSON.parse(readSync(this.#charwordTableFile)) || {
config: {
@@ -135,10 +135,10 @@ export default class CharwordTable {
console.log(`charword_table_${region}.json is updated.`)
// remove old file
const files = readdirSync(path.join(__projetRoot, __config.folder.operator, __config.folder.share))
const files = readdirSync(path.join(__projectRoot, __config.folder.operator, __config.folder.share))
for (const file of files) {
if (file.startsWith(`charword_table_${region}`) && file !== path.basename(filepath)) {
rm(path.join(__projetRoot, __config.folder.operator, __config.folder.share, file))
rm(path.join(__projectRoot, __config.folder.operator, __config.folder.share, file))
}
}
return data
@@ -215,10 +215,10 @@ export default class CharwordTable {
console.log(`charword_table_${region}.json is updated.`)
// remove old file
const files = readdirSync(path.join(__projetRoot, __config.folder.operator, __config.folder.share))
const files = readdirSync(path.join(__projectRoot, __config.folder.operator, __config.folder.share))
for (const file of files) {
if (file.startsWith(`charword_table_${region}`) && file !== path.basename(filepath)) {
rm(path.join(__projetRoot, __config.folder.operator, __config.folder.share, file))
rm(path.join(__projectRoot, __config.folder.operator, __config.folder.share, file))
}
}
output.data = data
@@ -233,10 +233,10 @@ export default class CharwordTable {
console.log(`handbook_info_table_${region}.json is updated.`)
// remove old file
const files = readdirSync(path.join(__projetRoot, __config.folder.operator, __config.folder.share))
const files = readdirSync(path.join(__projectRoot, __config.folder.operator, __config.folder.share))
for (const file of files) {
if (file.startsWith(`handbook_info_table_${region}`) && file !== path.basename(handbookFilepath)) {
rm(path.join(__projetRoot, __config.folder.operator, __config.folder.share, file))
rm(path.join(__projectRoot, __config.folder.operator, __config.folder.share, file))
}
}
output.handbook = data

View File

@@ -1,10 +1,11 @@
/* eslint-disable no-undef */
import path from 'path'
import { read } from './yaml.js'
import { read as readVersion } from './version.js'
import { getOperatorId } from './charword_table.js'
export default function () {
return process(read(path.join(__projetRoot, 'config.yaml')))
return process(read(path.join(__projectRoot, 'config.yaml')))
}
function process(config) {
@@ -23,8 +24,8 @@ function process(config) {
// version
config.version = {
showcase: readVersion(path.join(__projetRoot)),
directory: readVersion(path.join(__projetRoot, 'directory')),
showcase: readVersion(path.join(__projectRoot)),
directory: readVersion(path.join(__projectRoot, 'directory')),
}
return config

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-undef */
export default class Matcher {
#start
#end

View File

@@ -3,11 +3,12 @@ import path from 'path'
import { writeSync, copy, readSync as readFile } from './file.js'
import { read } from './yaml.js';
import AssetsProcessor from './assets_processor.js'
import EnvGenerator from './env_generator.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);
export default function ({ backgrounds }) {
const extractedFolder = path.join(__projectRoot, __config.folder.operator, '_directory')
const targetFolder = path.join(__projectRoot, __config.folder.release, __config.folder.directory);
const sourceFolder = path.join(__projectRoot, __config.folder.operator);
const filesToCopy = Object.keys(__config.operators)
const directoryJson = {
operators: Object.values(
@@ -22,7 +23,7 @@ export default function ({ backgrounds, charwordTable }) {
cur.workshopId = null
try {
cur.workshopId = JSON.parse(readFile(path.join(__projetRoot, __config.folder.operator, cur.link, 'project.json'))).workshopid
cur.workshopId = JSON.parse(readFile(path.join(__projectRoot, __config.folder.operator, cur.link, 'project.json'))).workshopid
} catch (e) {
console.log(`No workshop id for ${cur.link}!`, e)
}
@@ -33,7 +34,7 @@ export default function ({ backgrounds, charwordTable }) {
}
const versionJson = __config.version
const changelogs = read(path.join(__projetRoot, 'changelogs.yaml'))
const changelogs = read(path.join(__projectRoot, 'changelogs.yaml'))
const changelogsArray = Object.keys(changelogs).reduce((acc, cur) => {
const array = []
Object.keys(changelogs[cur]).map((item) => {
@@ -78,7 +79,7 @@ export default function ({ backgrounds, charwordTable }) {
key: "error_files",
value: JSON.stringify(__config.directory.error).replace('#', '%23')
}
]), path.join(__projetRoot, 'directory', '.env'))
]), path.join(__projectRoot, 'directory', '.env'))
writeSync(JSON.stringify(directoryJson, null), path.join(targetFolder, "directory.json"))
writeSync(JSON.stringify(versionJson, null), path.join(targetFolder, "version.json"))

View File

@@ -4,5 +4,4 @@ export default class EnvGenerator {
return `VITE_${value.key.toUpperCase()}=${value.value}`
}).join('\n')
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-undef */
import path from 'path'
import { stringify } from 'yaml'
import { read as readYAML } from './yaml.js'
@@ -9,9 +10,9 @@ export default function init(operatorName, extractedDir) {
mkdir(dir)
})
const date = new Date()
const template = readYAML(path.join(__projetRoot, 'config', '_template.yaml'))
const template = readYAML(path.join(__projectRoot, 'config', '_template.yaml'))
template.link = operatorName
template.date = `${date.getFullYear()}/${(date.getMonth() + 1).toString().padStart(2, '0') }`
writeSync(stringify(template), path.join(__projetRoot, 'config', `${operatorName}.yaml`))
writeSync(stringify(template), path.join(__projectRoot, 'config', `${operatorName}.yaml`))
appendMainConfig(operatorName)
}

19
libs/music.js Normal file
View File

@@ -0,0 +1,19 @@
/* eslint-disable no-undef */
import path from 'path';
import { read } from './yaml.js';
export default function () {
const musicFolder = path.join(__projectRoot, __config.folder.operator, __config.folder.share, __config.folder.music);
const musicMapping = read(path.join(musicFolder, 'mapping.yaml'));
const musicToCopy = Object.values(musicMapping).map(entry => Object.values(entry)).flat(1).filter(entry => entry !== null).map(entry => {
return {
filename: entry,
source: musicFolder,
}
})
return {
musicToCopy,
musicMapping,
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-undef */
import path from 'path'
import Matcher from './content_processor.js'
import { read as readFile, exists } from './file.js'
@@ -13,7 +14,7 @@ export default class ProjectJson {
constructor(operatorName, operatorShareFolder, assets) {
this.#operatorName = operatorName
this.#operatorSourceFolder = path.join(__projetRoot, __config.folder.operator)
this.#operatorSourceFolder = path.join(__projectRoot, __config.folder.operator)
this.#operatorShareFolder = operatorShareFolder
this.#assets = assets
}
@@ -44,7 +45,7 @@ export default class ProjectJson {
return matcher.result
}
}
this.#template = readYAML(path.join(__projetRoot, 'config', '_project_json.yaml'), [match])
this.#template = readYAML(path.join(__projectRoot, 'config', '_project_json.yaml'), [match])
this.#process()
return this.#json
}

120
src/components/music.js Normal file
View File

@@ -0,0 +1,120 @@
export default class Music {
#el
#mapping = JSON.parse(import.meta.env.VITE_MUSIC_MAPPING)
#folder = import.meta.env.VITE_MUSIC_FOLDER
#currentMusic = null
#audioIntroEl
#audioLoopEl
#audioIntroElId = 'music-intro'
#audioLoopElId = 'music-loop'
#useMusic = false
#timeOffset = 0.3
#volume = 0.5
constructor(el) {
this.#el = el
this.#insertHTML()
this.#audioIntroEl = document.getElementById(this.#audioIntroElId)
this.#audioLoopEl = document.getElementById(this.#audioLoopElId)
this.#audioIntroEl.volume = this.#volume
this.#audioLoopEl.volume = this.#volume
this.#audioIntroEl.ontimeupdate = () => {
if (this.#audioIntroEl.currentTime >= this.#audioIntroEl.duration - this.#timeOffset) {
this.#audioIntroEl.pause()
this.#audioLoopEl.play()
}
}
this.#audioLoopEl.ontimeupdate = () => {
if (this.#audioLoopEl.currentTime >= this.#audioLoopEl.duration - this.#timeOffset) {
this.#audioLoopEl.currentTime = 0
this.#audioLoopEl.play()
}
}
}
get timeOffset() {
return this.#timeOffset
}
set timeOffset(value) {
this.#timeOffset = value
}
get volume() {
return this.#volume * 100
}
set volume(value) {
value = value / 100
this.#volume = value
this.#audioIntroEl.volume = value
this.#audioLoopEl.volume = value
}
get music() {
return Object.keys(this.#mapping)
}
get useMusic() {
return this.#useMusic
}
get currentMusic() {
return this.#currentMusic
}
/**
* @param {bool} value
*/
set useMusic(value) {
this.#useMusic = value
if (value) {
this.#playMusic()
} else {
this.#stopMusic()
}
}
success() {
this.changeMusic(window.settings.currentBackground)
}
changeMusic(name) {
if (name !== this.#currentMusic) {
this.#currentMusic = name
if (this.#useMusic) {
this.#audioLoopEl.pause()
this.#audioIntroEl.pause()
this.#playMusic()
}
}
}
#playMusic() {
const introOgg = this.#mapping[this.#currentMusic].intro
const intro = `./assets/${this.#folder}/${introOgg}`
const loop = `./assets/${this.#folder}/${this.#mapping[this.#currentMusic].loop}`
this.#audioLoopEl.src = loop
if (introOgg) {
this.#audioIntroEl.src = intro || loop
} else {
this.#audioLoopEl.play()
}
}
#stopMusic() {
this.#audioIntroEl.pause()
this.#audioLoopEl.pause()
}
#insertHTML() {
this.#el.innerHTML = `
<audio id="${this.#audioIntroElId}" preload="auto" autoplay>
<source type="audio/ogg" />
</audio>
<audio id="${this.#audioLoopElId}" preload="auto">
<source type="audio/ogg" />
</audio>
`
}
}

View File

@@ -57,6 +57,7 @@ export default function spinePlayer(el) {
}
window.voice.success()
window.settings.success()
window.music.success()
},
})
}

View File

@@ -119,7 +119,7 @@ export default class Settings {
this.functionInsights("setLogo", this.isWallpaperEngine)
}
#readFile(e, onload, callback) {
#readFile(e, onload, callback = () => { }) {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
@@ -161,12 +161,18 @@ export default class Settings {
if (!skipInsights) this.functionInsights("setBackgoundImage", this.isWallpaperEngine);
}
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, true)
get currentBackground() {
if (!document.getElementById("custom_background_clear").disabled) {
return null
}
return this.#defaultBackgroundImage.replace(/^(url\(('|"))(.+)(\/)(.+.png)(('|")\))$/, '$5')
}
setDefaultBackground(e) {
this.#defaultBackgroundImage = `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(this.#defaultBackgroundImage, true)
}
this.#defaultBackgroundImage = backgroundURL
this.functionInsights("setDefaultBackground", this.isWallpaperEngine)
}
@@ -182,9 +188,9 @@ export default class Settings {
}
resetBackground() {
this.setBackgoundImage(this.#defaultBackgroundImage)
document.getElementById("custom_background").value = ""
document.getElementById("custom_background_clear").disabled = true
this.setBackgoundImage(this.#defaultBackgroundImage)
}
loadViewport() {
@@ -374,31 +380,53 @@ export default class Settings {
<input type="number" id="voice_next_duration_input" name="voice_next_duration" value="${window.voice.nextDuration}" />
</div>
<div>
<label for="subtitle">Subtitle</label>
<input type="checkbox" id="subtitle" name="subtitle" checked/>
<div id="subtitle_realted">
<div>
<label for="subtitle_lang_select">Choose the language of subtitle:</label>
<select name="subtitle_lang" id="subtitle_lang_select">
${this.#updateOptions("subtitle_lang_select", window.voice.subtitleLanguages)}
</select>
<label for="subtitle">Subtitle</label>
<input type="checkbox" id="subtitle" name="subtitle" checked/>
<div id="subtitle_realted">
<div>
<label for="subtitle_lang_select">Choose the language of subtitle:</label>
<select name="subtitle_lang" id="subtitle_lang_select">
${this.#updateOptions("subtitle_lang_select", window.voice.subtitleLanguages)}
</select>
</div>
<div>
<label for="subtitle_padding_x">Subtitle X Position</label>
<input type="range" min="0" max="100" id="subtitle_padding_x_slider" value="${window.voice.subtitleX}" />
<input type="number" id="subtitle_padding_x_input" name="subtitle_padding_x" value="${window.voice.subtitleX}" />
</div>
<div>
<label for="subtitle_padding_y">Subtitle Y Position</label>
<input type="range" min="0" max="100" id="subtitle_padding_y_slider" value="${window.voice.subtitleY}" />
<input type="number" id="subtitle_padding_y_input" name="subtitle_padding_y" value="${window.voice.subtitleY}" />
</div>
</div>
</div>
<div>
<label for="subtitle_padding_x">Subtitle X Position</label>
<input type="range" min="0" max="100" id="subtitle_padding_x_slider" value="${window.voice.subtitleX}" />
<input type="number" id="subtitle_padding_x_input" name="subtitle_padding_x" value="${window.voice.subtitleX}" />
</div>
<div>
<label for="subtitle_padding_y">Subtitle Y Position</label>
<input type="range" min="0" max="100" id="subtitle_padding_y_slider" value="${window.voice.subtitleY}" />
<input type="number" id="subtitle_padding_y_input" name="subtitle_padding_y" value="${window.voice.subtitleY}" />
<label for="voice_actor">Voice Actor</label>
<input type="checkbox" id="voice_actor" name="voice_actor"/>
</div>
</div>
</div>
<div>
<label for="voice_actor">Voice Actor</label>
<input type="checkbox" id="voice_actor" name="voice_actor"/>
</div>
<label for="music">Music</label>
<input type="checkbox" id="music" name="music" />
<div id="music_realted" hidden>
<div>
<label for="music_select">Choose theme music:</label>
<select name="music_select" id="music_select">
${this.#updateOptions("music_select", window.music.music)}
</select>
</div>
<div>
<label for="music_volume">Music Volume</label>
<input type="range" min="0" max="100" step="1" id="music_volume_slider" value="${window.music.volume}" />
<input type="number" id="music_volume_input" min="0" max="100" step="1" name="music_volume" value="${window.music.volume}" />
</div>
<div>
<label for="music_switch_offset">Music Swtich Offset</label>
<input type="range" min="0" max="1" step="0.01" id="music_switch_offset_slider" value="${window.music.timeOffset}" />
<input type="number" id="music_switch_offset_input" min="0" max="1" step="0.01" name="music_switch_offset" value="${window.music.timeOffset}" />
</div>
</div>
</div>
<div>
@@ -407,8 +435,8 @@ export default class Settings {
<div id="position_realted" hidden>
<div>
<label for="position_padding_left">Padding Left</label>
<input type="range" min="-100" max="100" id="position_padding_left_slider" value="${this.#padLeft}" />
<input type="number" id="position_padding_left_input" name="position_padding_left" value="${this.#padLeft}" />
<input type="range" min="-100" max="100" id="position_padding_left_slider" value="${this.#padLeft}" />
<input type="number" id="position_padding_left_input" name="position_padding_left" value="${this.#padLeft}" />
</div>
<div>
<label for="position_padding_right">Padding Right</label>
@@ -428,9 +456,9 @@ export default class Settings {
</div>
</div>
<div>
<label for="animation_select">Animation:</label>
<select name="animation_select" id="animation_selection"></select>
</div>
<label for="animation_select">Animation:</label>
<select name="animation_select" id="animation_selection"></select>
</div>
<div>
<button type="button" id="settings_play" disabled>Play</button>
<button type="button" id="settings_pause">Pause</button>
@@ -443,17 +471,19 @@ export default class Settings {
}
#sync(source, targetID) {
if (typeof source === "string") source = document.getElementById(source);
document.getElementById(targetID).value = source.value;
}
#showRelated(e, relatedSettingsID) {
#showRelated(e, relatedSettingsID, revert = false) {
const eRelatedSettings = document.getElementById(relatedSettingsID)
if (e.checked) {
const checked = revert ? !e.checked : e.checked;
if (checked) {
eRelatedSettings.hidden = false;
} else {
eRelatedSettings.hidden = true;
}
};
}
#updateOptions(id, array) {
const e = document.getElementById(id);
@@ -496,7 +526,7 @@ export default class Settings {
}, {
id: "logo_image", event: "change", handler: e => _this.setLogoImage(e)
}, {
id: "logo_image_clear", event: "click", handler: e => _this.resetLogoImage()
id: "logo_image_clear", event: "click", handler: () => _this.resetLogoImage()
}, {
id: "logo_ratio_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "logo_ratio_input");
@@ -542,7 +572,7 @@ export default class Settings {
}, {
id: "custom_background", event: "change", handler: e => _this.setBackground(e)
}, {
id: "custom_background_clear", event: "click", handler: e => _this.resetBackground()
id: "custom_background_clear", event: "click", handler: () => _this.resetBackground()
}, {
id: "voice", event: "click", handler: e => {
_this.#showRelated(e.currentTarget, "voice_realted");
@@ -593,6 +623,34 @@ export default class Settings {
id: "voice_actor", event: "click", handler: e => {
window.voice.useVoiceActor = e.currentTarget.checked;
}
}, {
id: "music", event: "click", handler: e => {
_this.#showRelated(e.currentTarget, "music_realted");
window.music.useMusic = e.currentTarget.checked;
window.music.changeMusic(this.currentBackground)
}
}, {
id: "music_select", event: "change", handler: e => window.music.changeMusic(e.currentTarget.value)
}, {
id: "music_volume_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "music_volume_input");
window.music.volume = parseInt(e.currentTarget.value)
}
}, {
id: "music_volume_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "music_volume_slider");
window.music.volume = parseInt(e.currentTarget.value)
}
}, {
id: "music_switch_offset_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "music_switch_offset_input");
window.music.timeOffset = parseFloat(e.currentTarget.value)
}
}, {
id: "music_switch_offset_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "music_switch_offset_slider");
window.music.timeOffset = parseFloat(e.currentTarget.value)
}
}, {
id: "position", event: "click", handler: e => {
_this.#showRelated(e.currentTarget, "position_realted");
@@ -651,9 +709,9 @@ export default class Settings {
document.getElementById("settings_play").disabled = false;
}
}, {
id: "settings_reset", event: "click", handler: e => _this.reset()
id: "settings_reset", event: "click", handler: () => _this.reset()
}, {
id: "settings_close", event: "click", handler: e => _this.close()
id: "settings_close", event: "click", handler: () => _this.close()
}, {
id: "animation_selection", event: "change", handler: e => {
this.spinePlayer.animationState.setAnimation(0, e.currentTarget.value, false, 0)

View File

@@ -50,11 +50,11 @@ export default class Voice {
this.#audioEl.addEventListener('ended', audioEndedFunc)
this.#playEntryVoice()
this.#initNextVoiceTimer()
this.#widgetEl.addEventListener('click', e => {
this.#widgetEl.addEventListener('click', () => {
this.#lastClickToNext = true
this.#nextVoice()
})
document.addEventListener('mousemove', e => {
document.addEventListener('mousemove', () => {
if (this.#idleListener === -1) {
this.#initIdleVoiceTimer()
}

View File

@@ -3,11 +3,13 @@ import '@/libs/wallpaper_engine'
import check_web_gl from '@/libs/check_web_gl'
import Settings from '@/components/settings'
import Voice from '@/components/voice'
import Music from '@/components/music'
document.querySelector('#app').innerHTML = `
<img src="./assets/${import.meta.env.VITE_LOGO_FILENAME}.png" class="logo invert-filter" id="logo" alt="operator logo" />
<div id="settings"></div>
<div id="voice_box" hidden></div>
<div id="voice_box" hidden></div>
<div id="music_box" hidden></div>
<div id="widget-wrapper">
<div id="fallback"
style="background-image: url(./assets/${import.meta.env.VITE_FALLBACK_FILENAME}.png)"
@@ -18,6 +20,7 @@ document.querySelector('#app').innerHTML = `
`
window.voice = new Voice(document.querySelector('#voice_box'), document.querySelector('#widget-wrapper'))
window.voice.init()
window.music = new Music(document.querySelector('#music_box'))
window.settings = new Settings(document.querySelector('#settings'), document.querySelector('#logo'))
document.title = import.meta.env.VITE_TITLE
console.log("All resources are extracted from Arknights. Github: https://github.com/Halyul/aklive2d")

View File

@@ -89,9 +89,21 @@ window.wallpaperPropertyListener = {
}
if (properties.voiceactor) {
window.voice.useVoiceActor = properties.voiceactor.value
console.log(properties)
window.settings.functionInsights("useVoiceActor", Object.keys(properties) !== 1)
}
if (properties.music_title) {
window.music.useMusic = properties.music_title.value
window.settings.functionInsights("useMusic", Object.keys(properties) !== 1)
}
if (properties.music_selection) {
// TODO: not working
window.music.changeMusic(properties.music_selection.value)
window.settings.functionInsights("music_selection", Object.keys(properties) !== 1)
}
if (properties.music_volume) {
window.music.volume = properties.music_volume.value
window.settings.functionInsights("music_volume", Object.keys(properties) !== 1)
}
if (properties.position) {
if (!properties.position.value) {
window.settings.positionReset()

View File

@@ -11,7 +11,7 @@ import EnvGenerator from './libs/env_generator.js'
import directory from './libs/directory.js'
import { PerfseePlugin } from '@perfsee/rollup'
global.__projetRoot = path.dirname(fileURLToPath(import.meta.url))
global.__projectRoot = path.dirname(fileURLToPath(import.meta.url))
class ViteRunner {
#globalConfig = getConfig()
@@ -35,12 +35,12 @@ class ViteRunner {
case 'directory':
result = {
data: this.#directoryConfig,
versionDir: path.join(__projetRoot, "directory"),
versionDir: path.join(__projectRoot, "directory"),
}
const op = temp[2] || process.argv[3]
if (op !== 'preview') {
rmdir(path.resolve(__projetRoot, this.#globalConfig.folder.release, "_directory"))
rmdir(path.resolve(__projetRoot, this.#globalConfig.folder.release, "index.html"))
rmdir(path.resolve(__projectRoot, this.#globalConfig.folder.release, "_directory"))
rmdir(path.resolve(__projectRoot, this.#globalConfig.folder.release, "index.html"))
}
break
case 'dev':
@@ -49,7 +49,7 @@ class ViteRunner {
case 'preview':
result = {
data: this.#operatorConfig,
versionDir: path.join(__projetRoot),
versionDir: path.join(__projectRoot),
}
break
default:
@@ -115,22 +115,22 @@ class ViteRunner {
const assetsDir = 'assets'
return {
...this.#baseViteConfig,
envDir: path.join(__projetRoot, this.#globalConfig.folder.operator, operatorName),
publicDir: path.resolve(__projetRoot, this.#globalConfig.folder.release, operatorName),
root: path.resolve(__projetRoot),
envDir: path.join(__projectRoot, this.#globalConfig.folder.operator, operatorName),
publicDir: path.resolve(__projectRoot, this.#globalConfig.folder.release, operatorName),
root: path.resolve(__projectRoot),
server: {
...this.#baseViteConfig.server,
},
resolve: {
alias: {
'@': path.resolve(__projetRoot, './src'),
'!': path.resolve(__projetRoot, this.#globalConfig.folder.operator, operatorName),
'@': path.resolve(__projectRoot, './src'),
'!': path.resolve(__projectRoot, this.#globalConfig.folder.operator, operatorName),
},
},
build: {
...this.#baseViteConfig.build,
chunkSizeWarningLimit: 10000,
outDir: path.resolve(__projetRoot, this.#globalConfig.folder.release, operatorName),
outDir: path.resolve(__projectRoot, this.#globalConfig.folder.release, operatorName),
rollupOptions: {
output: {
entryFileNames: `${assetsDir}/[name].js`,
@@ -144,12 +144,12 @@ class ViteRunner {
get #directoryConfig() {
if (process.env.npm_lifecycle_event === 'vite:directory:build') {
this.#globalConfig.version.directory = increase(path.join(__projetRoot, "directory"))
this.#globalConfig.version.directory = increase(path.join(__projectRoot, "directory"))
global.__config = this.#globalConfig
}
const directoryDir = path.resolve(__projetRoot, 'directory')
const directoryDir = path.resolve(__projectRoot, 'directory')
this.#mode = process.argv[3]
const publicDir = path.resolve(__projetRoot, this.#globalConfig.folder.release)
const publicDir = path.resolve(__projectRoot, this.#globalConfig.folder.release)
const assetsDir = '_directory'
return {
...this.#baseViteConfig,
@@ -162,7 +162,7 @@ class ViteRunner {
// artifactName: 'directory',
// }),
],
publicDir: path.resolve(__projetRoot, this.#globalConfig.folder.release),
publicDir: path.resolve(__projectRoot, this.#globalConfig.folder.release),
root: directoryDir,
server: {
...this.#baseViteConfig.server,
@@ -170,13 +170,13 @@ class ViteRunner {
resolve: {
alias: {
'@': path.resolve(directoryDir, './src'),
'!': path.resolve(__projetRoot, './src'),
'!': path.resolve(__projectRoot, './src'),
'#': path.resolve(publicDir, this.#globalConfig.folder.directory),
},
},
build: {
...this.#baseViteConfig.build,
outDir: path.resolve(__projetRoot, this.#globalConfig.folder.release),
outDir: path.resolve(__projectRoot, this.#globalConfig.folder.release),
rollupOptions: {
output: {
entryFileNames: `${assetsDir}/[name].js`,