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 { increase } from './libs/version.js';
import Background from './libs/background.js' import Background from './libs/background.js'
import CharwordTable from './libs/charword_table.js'; import CharwordTable from './libs/charword_table.js';
import Music from './libs/music.js';
async function main() { async function main() {
global.__projetRoot = path.dirname(fileURLToPath(import.meta.url)) global.__projectRoot = path.dirname(fileURLToPath(import.meta.url))
global.__config = getConfig() global.__config = getConfig()
const op = process.argv[2] const op = process.argv[2]
@@ -33,17 +34,17 @@ async function main() {
switch (op) { switch (op) {
case 'directory': case 'directory':
assert(OPERATOR_NAMES.length !== 0, 'Please set a mode for 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 return
case 'build-all': case 'build-all':
for (const [key, _] of Object.entries(__config.operators)) { for (const [key,] of Object.entries(__config.operators)) {
OPERATOR_NAMES.push(key) OPERATOR_NAMES.push(key)
} }
__config.version.showcase = increase(__projetRoot) __config.version.showcase = increase(__projectRoot)
break break
case 'preview': case 'preview':
assert(OPERATOR_NAMES.length !== 0, 'Please set the operator name.') 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 return
case 'charword': case 'charword':
await charwordTable.process() await charwordTable.process()
@@ -57,10 +58,11 @@ async function main() {
const background = new Background() const background = new Background()
await background.process() await background.process()
const backgrounds = ['operator_bg.png', ...background.files] const backgrounds = ['operator_bg.png', ...background.files]
const { musicToCopy, musicMapping } = Music()
for (const OPERATOR_NAME of OPERATOR_NAMES) { for (const OPERATOR_NAME of OPERATOR_NAMES) {
const OPERATOR_SOURCE_FOLDER = path.join(__projetRoot, __config.folder.operator) const OPERATOR_SOURCE_FOLDER = path.join(__projectRoot, __config.folder.operator)
const OPERATOR_RELEASE_FOLDER = path.join(__projetRoot, __config.folder.release, OPERATOR_NAME) const OPERATOR_RELEASE_FOLDER = path.join(__projectRoot, __config.folder.release, OPERATOR_NAME)
const SHOWCASE_PUBLIC_ASSSETS_FOLDER = path.join(OPERATOR_RELEASE_FOLDER, "assets") const SHOWCASE_PUBLIC_ASSSETS_FOLDER = path.join(OPERATOR_RELEASE_FOLDER, "assets")
const EXTRACTED_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'extracted') 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 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')) 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') const envPath = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, '.env')
writeSync((new EnvGenerator()).generate([ writeSync((new EnvGenerator()).generate([
{ {
@@ -219,12 +165,82 @@ async function main() {
}, { }, {
key: "voice_folders", key: "voice_folders",
value: JSON.stringify(__config.folder.voice) value: JSON.stringify(__config.folder.voice)
}, {
key: "music_folder",
value: __config.folder.music
}, {
key: "music_mapping",
value: JSON.stringify(musicMapping)
} }
]), envPath) ]), 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(); main();

View File

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

View File

@@ -32,6 +32,11 @@ localization:
ui_privacy_title: <hr><h4>📜 Privacy</h4><hr> 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_text: <span><b>Check out our privacy policy at https://privacy.halyul.dev</b></span>
ui_privacy_do_not_track: Send usage data 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: zh-chs:
ui_notice_title: <hr><h4>📝 通知</h4><hr> ui_notice_title: <hr><h4>📝 通知</h4><hr>
ui_notice_changelog: <span><b>现在支持干员语音! </b></span> ui_notice_changelog: <span><b>现在支持干员语音! </b></span>
@@ -64,6 +69,11 @@ localization:
ui_privacy_title: <hr><h4>📜 隐私</h4><hr> ui_privacy_title: <hr><h4>📜 隐私</h4><hr>
ui_privacy_text: <span><b>在 https://privacy.halyul.dev 查看我们的隐私政策</b></span> ui_privacy_text: <span><b>在 https://privacy.halyul.dev 查看我们的隐私政策</b></span>
ui_privacy_do_not_track: 发送使用数据 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: properties:
- key: notice_title - key: notice_title
value: value:
@@ -106,8 +116,8 @@ properties:
fraction: true fraction: true
max: 100 max: 100
min: 0 min: 0
precision: 2
step: 0.1 step: 0.1
precision: 1
- key: logoopacity - key: logoopacity
value: value:
text: ui_logo_opacity text: ui_logo_opacity
@@ -214,6 +224,42 @@ properties:
condition: voicesubtitle.value == true condition: voicesubtitle.value == true
type: bool type: bool
value: false 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 - key: position
value: value:
text: ui_position_title 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 { .group {
padding: 1rem; padding: 1rem;
display: flex; display: flex;
@@ -61,13 +50,23 @@
border: var(--home-item-outline-color) 1px dashed; border: var(--home-item-outline-color) 1px dashed;
padding: 6px; 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 { &:before {
@extend %outline-share;
top: -3px; top: -3px;
} }
&:after { &:after {
@extend %outline-share;
bottom: -3px; bottom: -3px;
} }
} }

View File

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

View File

@@ -1,18 +1,19 @@
/* eslint-disable no-undef */
import path from 'path' import path from 'path'
import { appendSync, readSync } from './file.js' import { appendSync, readSync } from './file.js'
export function appendReadme(operatorName) { export function appendReadme(operatorName) {
const operatorConfig = __config.operators[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( appendSync(
`\n| ${operatorConfig.codename["en-US"]} | [Link](https://arknights.halyul.dev/${operatorConfig.link}/?settings) | [Link](https://steamcommunity.com/sharedfiles/filedetails/?id=${projectJson.workshopid}) |`, `\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) { export function appendMainConfig(operatorName) {
appendSync( appendSync(
`\n ${operatorName}: !include config/${operatorName}.yaml`, `\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 path from 'path'
import { read, write, readSync } from './file.js' import { read, write, readSync } from './file.js'
import AlphaComposite from './alpha_composite.js' import AlphaComposite from './alpha_composite.js'
@@ -9,7 +10,7 @@ export default class AssetsProcessor {
#shareFolder #shareFolder
constructor(operatorName, 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.#alphaCompositer = new AlphaComposite()
this.#operatorName = operatorName this.#operatorName = operatorName
this.#shareFolder = shareFolder this.#shareFolder = shareFolder

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-undef */
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import sharp from "sharp"; import sharp from "sharp";
@@ -8,7 +9,7 @@ export default class Background {
#files #files
constructor() { 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'); this.#extractFolder = path.join(this.#backgroundFolder, 'extracted');
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-undef */
import path from 'path' import path from 'path'
import { stringify } from 'yaml' import { stringify } from 'yaml'
import { read as readYAML } from './yaml.js' import { read as readYAML } from './yaml.js'
@@ -9,9 +10,9 @@ export default function init(operatorName, extractedDir) {
mkdir(dir) mkdir(dir)
}) })
const date = new Date() 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.link = operatorName
template.date = `${date.getFullYear()}/${(date.getMonth() + 1).toString().padStart(2, '0') }` 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) 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 path from 'path'
import Matcher from './content_processor.js' import Matcher from './content_processor.js'
import { read as readFile, exists } from './file.js' import { read as readFile, exists } from './file.js'
@@ -13,7 +14,7 @@ export default class ProjectJson {
constructor(operatorName, operatorShareFolder, assets) { constructor(operatorName, operatorShareFolder, assets) {
this.#operatorName = operatorName this.#operatorName = operatorName
this.#operatorSourceFolder = path.join(__projetRoot, __config.folder.operator) this.#operatorSourceFolder = path.join(__projectRoot, __config.folder.operator)
this.#operatorShareFolder = operatorShareFolder this.#operatorShareFolder = operatorShareFolder
this.#assets = assets this.#assets = assets
} }
@@ -44,7 +45,7 @@ export default class ProjectJson {
return matcher.result 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() this.#process()
return this.#json 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.voice.success()
window.settings.success() window.settings.success()
window.music.success()
}, },
}) })
} }

View File

@@ -119,7 +119,7 @@ export default class Settings {
this.functionInsights("setLogo", this.isWallpaperEngine) this.functionInsights("setLogo", this.isWallpaperEngine)
} }
#readFile(e, onload, callback) { #readFile(e, onload, callback = () => { }) {
const file = e.target.files[0] const file = e.target.files[0]
if (!file) return if (!file) return
const reader = new FileReader() const reader = new FileReader()
@@ -161,12 +161,18 @@ export default class Settings {
if (!skipInsights) this.functionInsights("setBackgoundImage", this.isWallpaperEngine); if (!skipInsights) this.functionInsights("setBackgoundImage", this.isWallpaperEngine);
} }
setDefaultBackground(e) { get currentBackground() {
const backgroundURL = `url("${import.meta.env.BASE_URL}assets/${import.meta.env.VITE_BACKGROUND_FOLDER}/${e}")` if (!document.getElementById("custom_background_clear").disabled) {
if (document.getElementById("custom_background_clear").disabled && !document.body.style.backgroundImage.startsWith("url(\"file:")) { return null
this.setBackgoundImage(backgroundURL, true) }
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) this.functionInsights("setDefaultBackground", this.isWallpaperEngine)
} }
@@ -182,9 +188,9 @@ export default class Settings {
} }
resetBackground() { resetBackground() {
this.setBackgoundImage(this.#defaultBackgroundImage)
document.getElementById("custom_background").value = "" document.getElementById("custom_background").value = ""
document.getElementById("custom_background_clear").disabled = true document.getElementById("custom_background_clear").disabled = true
this.setBackgoundImage(this.#defaultBackgroundImage)
} }
loadViewport() { loadViewport() {
@@ -401,6 +407,28 @@ export default class Settings {
</div> </div>
</div> </div>
</div> </div>
<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> <div>
<label for="position">Position</label> <label for="position">Position</label>
<input type="checkbox" id="position" name="position" /> <input type="checkbox" id="position" name="position" />
@@ -443,17 +471,19 @@ export default class Settings {
} }
#sync(source, targetID) { #sync(source, targetID) {
if (typeof source === "string") source = document.getElementById(source);
document.getElementById(targetID).value = source.value; document.getElementById(targetID).value = source.value;
} }
#showRelated(e, relatedSettingsID) { #showRelated(e, relatedSettingsID, revert = false) {
const eRelatedSettings = document.getElementById(relatedSettingsID) const eRelatedSettings = document.getElementById(relatedSettingsID)
if (e.checked) { const checked = revert ? !e.checked : e.checked;
if (checked) {
eRelatedSettings.hidden = false; eRelatedSettings.hidden = false;
} else { } else {
eRelatedSettings.hidden = true; eRelatedSettings.hidden = true;
} }
}; }
#updateOptions(id, array) { #updateOptions(id, array) {
const e = document.getElementById(id); 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", 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 => { id: "logo_ratio_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "logo_ratio_input"); _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", 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 => { id: "voice", event: "click", handler: e => {
_this.#showRelated(e.currentTarget, "voice_realted"); _this.#showRelated(e.currentTarget, "voice_realted");
@@ -593,6 +623,34 @@ export default class Settings {
id: "voice_actor", event: "click", handler: e => { id: "voice_actor", event: "click", handler: e => {
window.voice.useVoiceActor = e.currentTarget.checked; 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 => { id: "position", event: "click", handler: e => {
_this.#showRelated(e.currentTarget, "position_realted"); _this.#showRelated(e.currentTarget, "position_realted");
@@ -651,9 +709,9 @@ export default class Settings {
document.getElementById("settings_play").disabled = false; 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 => { id: "animation_selection", event: "change", handler: e => {
this.spinePlayer.animationState.setAnimation(0, e.currentTarget.value, false, 0) 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.#audioEl.addEventListener('ended', audioEndedFunc)
this.#playEntryVoice() this.#playEntryVoice()
this.#initNextVoiceTimer() this.#initNextVoiceTimer()
this.#widgetEl.addEventListener('click', e => { this.#widgetEl.addEventListener('click', () => {
this.#lastClickToNext = true this.#lastClickToNext = true
this.#nextVoice() this.#nextVoice()
}) })
document.addEventListener('mousemove', e => { document.addEventListener('mousemove', () => {
if (this.#idleListener === -1) { if (this.#idleListener === -1) {
this.#initIdleVoiceTimer() this.#initIdleVoiceTimer()
} }

View File

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

View File

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