refactor(aklive2d): update directory settings
This commit is contained in:
8
showcase/src/components/fallback.css
Normal file
8
showcase/src/components/fallback.css
Normal file
@@ -0,0 +1,8 @@
|
||||
#fallback {
|
||||
margin: auto;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
16
showcase/src/components/fallback.js
Normal file
16
showcase/src/components/fallback.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import '@/components/fallback.css'
|
||||
|
||||
export default function (el) {
|
||||
el.hidden = false;
|
||||
alert('WebGL is unavailable. Fallback image will be used.');
|
||||
const calculateScale = (width, height) => {
|
||||
return { x: window.innerWidth / width, y: window.innerHeight / height };
|
||||
}
|
||||
const fallback = () => {
|
||||
const scale = calculateScale(import.meta.env.VITE_IMAGE_WIDTH, import.meta.env.VITE_IMAGE_HEIGHT);
|
||||
el.style.width = import.meta.env.VITE_IMAGE_WIDTH * (scale.x > scale.y ? scale.y : scale.x) + "px";
|
||||
el.style.height = import.meta.env.VITE_IMAGE_HEIGHT * (scale.x > scale.y ? scale.y : scale.x) + "px";
|
||||
}
|
||||
fallback();
|
||||
window.addEventListener('resize', fallback, true);
|
||||
}
|
||||
142
showcase/src/components/music.js
Normal file
142
showcase/src/components/music.js
Normal file
@@ -0,0 +1,142 @@
|
||||
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
|
||||
#isUsingCustomMusic = false
|
||||
|
||||
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() {
|
||||
if (this.#currentMusic === null) this.changeMusic(window.settings.currentBackground)
|
||||
}
|
||||
|
||||
changeMusic(name) {
|
||||
if (name !== null && name !== this.#currentMusic && !this.#isUsingCustomMusic) {
|
||||
this.#currentMusic = name
|
||||
if (this.#useMusic) {
|
||||
this.#audioLoopEl.pause()
|
||||
this.#audioIntroEl.pause()
|
||||
this.#playMusic()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setMusic(data, type) {
|
||||
this.#audioLoopEl.src = data
|
||||
this.#audioLoopEl.querySelector('source').type = type
|
||||
this.#isUsingCustomMusic = true
|
||||
this.#playMusic()
|
||||
}
|
||||
|
||||
resetMusic() {
|
||||
this.#isUsingCustomMusic = false
|
||||
if (this.#useMusic) {
|
||||
this.#playMusic()
|
||||
}
|
||||
}
|
||||
|
||||
#playMusic() {
|
||||
if (!this.#isUsingCustomMusic) {
|
||||
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
|
||||
this.#audioLoopEl.querySelector('source').type = 'audio/ogg'
|
||||
if (introOgg) {
|
||||
this.#audioIntroEl.src = intro || loop
|
||||
this.#audioIntroEl.querySelector('source').type = 'audio/ogg'
|
||||
} else {
|
||||
this.#audioLoopEl.play()
|
||||
}
|
||||
} else {
|
||||
this.#audioIntroEl.pause()
|
||||
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>
|
||||
`
|
||||
}
|
||||
}
|
||||
4
showcase/src/components/player.css
Normal file
4
showcase/src/components/player.css
Normal file
@@ -0,0 +1,4 @@
|
||||
#player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
66
showcase/src/components/player.js
Normal file
66
showcase/src/components/player.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import '@/libs/spine-player.css'
|
||||
import spine from '@/libs/spine-player'
|
||||
import assets from '!/assets.json'
|
||||
import '@/components/player.css'
|
||||
|
||||
const showControls = (new URLSearchParams(window.location.search)).has("controls")
|
||||
let resetTime = window.performance.now();
|
||||
let isPlayingInteract = false;
|
||||
|
||||
export default function spinePlayer(el) {
|
||||
el.hidden = false
|
||||
return new spine.SpinePlayer(el, {
|
||||
skelUrl: `./assets/${import.meta.env.VITE_FILENAME}.skel`,
|
||||
atlasUrl: `./assets/${import.meta.env.VITE_FILENAME}.atlas`,
|
||||
rawDataURIs: assets,
|
||||
premultipliedAlpha: true,
|
||||
alpha: true,
|
||||
backgroundColor: "#00000000",
|
||||
viewport: {
|
||||
debugRender: false,
|
||||
padLeft: `${import.meta.env.VITE_VIEWPORT_LEFT}%`,
|
||||
padRight: `${import.meta.env.VITE_VIEWPORT_RIGHT}%`,
|
||||
padTop: `${import.meta.env.VITE_VIEWPORT_TOP}%`,
|
||||
padBottom: `${import.meta.env.VITE_VIEWPORT_BOTTOM}%`,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
showControls: showControls,
|
||||
touch: showControls,
|
||||
fps: 60,
|
||||
defaultMix: 0,
|
||||
success: function (widget) {
|
||||
if (widget.skeleton.data.animations.map(e => e.name).includes("Start") && window.settings.useStartAnimation) {
|
||||
widget.animationState.setAnimation(0, "Start", false, 0)
|
||||
}
|
||||
widget.animationState.addAnimation(0, "Idle", true, 0);
|
||||
widget.animationState.addListener({
|
||||
end: (e) => {
|
||||
if (e.animation.name == "Interact") {
|
||||
isPlayingInteract = false;
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
if (window.performance.now() - resetTime >= 8 * 1000 && Math.random() < 0.3) {
|
||||
resetTime = window.performance.now();
|
||||
let entry = widget.animationState.setAnimation(0, "Special", false, 0);
|
||||
entry.mixDuration = 0.3;
|
||||
widget.animationState.addAnimation(0, "Idle", true, 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
widget.canvas.onclick = function () {
|
||||
if (isPlayingInteract) {
|
||||
return;
|
||||
}
|
||||
isPlayingInteract = true;
|
||||
let entry = widget.animationState.setAnimation(0, "Interact", false, 0);
|
||||
entry.mixDuration = 0.3;
|
||||
widget.animationState.addAnimation(0, "Idle", true, 0);
|
||||
}
|
||||
window.voice.success()
|
||||
window.settings.success()
|
||||
window.music.success()
|
||||
},
|
||||
})
|
||||
}
|
||||
8
showcase/src/components/settings.css
Normal file
8
showcase/src/components/settings.css
Normal file
@@ -0,0 +1,8 @@
|
||||
#settings {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: white;
|
||||
user-select: auto;
|
||||
z-index: 999;
|
||||
}
|
||||
837
showcase/src/components/settings.js
Normal file
837
showcase/src/components/settings.js
Normal file
@@ -0,0 +1,837 @@
|
||||
import '@/components/settings.css'
|
||||
|
||||
const getPercentage = (value) => parseInt(value.replace("%", ""))
|
||||
|
||||
export default class Settings {
|
||||
#el
|
||||
#logoEl
|
||||
#defaultLogoImage
|
||||
#defaultFps = 60
|
||||
#defaultRatio = 61.8
|
||||
#defaultOpacity = 30
|
||||
#defaulthideLogo = false
|
||||
#defaultBackgroundImage = getComputedStyle(document.body).backgroundImage
|
||||
#defaultInvertFilter = import.meta.env.VITE_INVERT_FILTER === "true"
|
||||
#defaultPadLeft = getPercentage(`${import.meta.env.VITE_VIEWPORT_LEFT}%`)
|
||||
#defaultPadRight = getPercentage(`${import.meta.env.VITE_VIEWPORT_RIGHT}%`)
|
||||
#defaultPadTop = getPercentage(`${import.meta.env.VITE_VIEWPORT_TOP}%`)
|
||||
#defaultPadBottom = getPercentage(`${import.meta.env.VITE_VIEWPORT_BOTTOM}%`)
|
||||
#defaultScale = 1
|
||||
#defaultLogoX = 0
|
||||
#defaultLogoY = 0
|
||||
#defaultViewport = {
|
||||
debugRender: false,
|
||||
padLeft: `${this.#defaultPadLeft}%`,
|
||||
padRight: `${this.#defaultPadRight}%`,
|
||||
padTop: `${this.#defaultPadTop}%`,
|
||||
padBottom: `${this.#defaultPadBottom}%`,
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
#fps = this.#defaultFps
|
||||
#ratio = this.#defaultRatio
|
||||
#opacity = this.#defaultOpacity
|
||||
#padLeft = this.#defaultPadLeft
|
||||
#padRight = this.#defaultPadRight
|
||||
#padTop = this.#defaultPadTop
|
||||
#padBottom = this.#defaultPadBottom
|
||||
#scale = this.#defaultScale
|
||||
#logoX = this.#defaultLogoX
|
||||
#logoY = this.#defaultLogoY
|
||||
#isInsightsInited = false
|
||||
#doNotTrack = false
|
||||
#lastFunctionInsights = null
|
||||
#useStartAnimation = true
|
||||
|
||||
constructor(el, logoEl) {
|
||||
this.isWallpaperEngine = false
|
||||
this.#el = el
|
||||
this.#logoEl = logoEl
|
||||
this.spinePlayer = null
|
||||
this.#defaultLogoImage = this.#logoEl.src
|
||||
this.#init()
|
||||
}
|
||||
|
||||
#init() {
|
||||
const _this = this
|
||||
window.addEventListener("contextmenu", e => e.preventDefault());
|
||||
document.addEventListener("gesturestart", e => e.preventDefault());
|
||||
|
||||
const resize = () => {
|
||||
_this.#resize(_this)
|
||||
}
|
||||
window.addEventListener("resize", resize, true);
|
||||
resize()
|
||||
this.#setLogoInvertFilter(this.#defaultInvertFilter)
|
||||
this.setLogoOpacity(this.#defaultOpacity)
|
||||
|
||||
this.#insertHTML()
|
||||
}
|
||||
|
||||
success() {
|
||||
this.loadViewport()
|
||||
this.insights(false, false)
|
||||
this.#updateOptions("animation_selection", this.spinePlayer.skeleton.data.animations.map(e => e.name))
|
||||
if ((new URLSearchParams(window.location.search)).has("settings") || import.meta.env.MODE === 'development') {
|
||||
this.open()
|
||||
}
|
||||
}
|
||||
|
||||
insights(isWallpaperEngine, doNotTrack) {
|
||||
this.isWallpaperEngine = isWallpaperEngine
|
||||
if (this.#isInsightsInited || import.meta.env.MODE === 'development') return
|
||||
this.#isInsightsInited = true
|
||||
this.#doNotTrack = doNotTrack
|
||||
if (this.#doNotTrack) return
|
||||
try {
|
||||
window.umami?.track(props => ({
|
||||
...props,
|
||||
url: `/${import.meta.env.VITE_LINK}${isWallpaperEngine ? "?steam" : ""}`
|
||||
}));
|
||||
} catch(e) {
|
||||
console.warn && console.warn(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
functionInsights(functionName, toSkip = false) {
|
||||
if (!this.#isInsightsInited || this.#doNotTrack || import.meta.env.MODE === 'development' || functionName === this.#lastFunctionInsights || toSkip) return
|
||||
try {
|
||||
window.umami?.track(props => ({
|
||||
...props,
|
||||
name: `${functionName}`,
|
||||
url: `/${import.meta.env.VITE_LINK}${this.isWallpaperEngine ? "?steam" : ""}`
|
||||
}));
|
||||
} catch (e) {
|
||||
console.warn && console.warn(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
setFPS(value) {
|
||||
this.#fps = value
|
||||
this.spinePlayer.setFps(value)
|
||||
this.functionInsights("setFPS", this.isWallpaperEngine)
|
||||
}
|
||||
|
||||
setLogoDisplay(flag) {
|
||||
this.#logoEl.hidden = flag;
|
||||
this.functionInsights("setLogoDisplay", this.isWallpaperEngine)
|
||||
}
|
||||
|
||||
#resize(_this, value) {
|
||||
_this = _this || this
|
||||
_this.#logoEl.width = window.innerWidth / 2 * (value || _this.#ratio) / 100
|
||||
_this.elementPosition(this.#logoEl, this.#logoX, this.#logoY)
|
||||
}
|
||||
|
||||
#setLogoInvertFilter(flag) {
|
||||
if (!flag) {
|
||||
this.#logoEl.style.filter = "invert(0)"
|
||||
} else {
|
||||
this.#logoEl.style.filter = "invert(1)"
|
||||
}
|
||||
}
|
||||
|
||||
setLogo(src, invert_filter) {
|
||||
this.#logoEl.src = src
|
||||
this.#resize()
|
||||
this.#setLogoInvertFilter(invert_filter)
|
||||
this.functionInsights("setLogo", this.isWallpaperEngine)
|
||||
}
|
||||
|
||||
#readFile(e, callback = () => { }) {
|
||||
const file = e.target.files[0]
|
||||
if (!file) return
|
||||
callback(URL.createObjectURL(file.slice()), file.type)
|
||||
}
|
||||
|
||||
setLogoImage(e) {
|
||||
this.#readFile(
|
||||
e,
|
||||
(blobURL, type) => {
|
||||
this.setLogo(blobURL, false)
|
||||
document.getElementById("logo_image_clear").disabled = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resetLogoImage() {
|
||||
this.setLogo(this.#defaultLogoImage, this.#defaultInvertFilter)
|
||||
document.getElementById("logo_image_clear").disabled = true
|
||||
}
|
||||
|
||||
setLogoRatio(value) {
|
||||
this.#ratio = value
|
||||
this.#resize(this, value)
|
||||
this.functionInsights("setLogoRatio", this.isWallpaperEngine)
|
||||
}
|
||||
|
||||
setLogoOpacity(value) {
|
||||
this.#logoEl.style.opacity = value / 100
|
||||
this.#opacity = value
|
||||
this.functionInsights("setLogoOpacity", this.isWallpaperEngine)
|
||||
}
|
||||
|
||||
setBackgoundImage(v, skipInsights = false) {
|
||||
document.body.style.backgroundImage = v
|
||||
if (!skipInsights) this.functionInsights("setBackgoundImage", this.isWallpaperEngine);
|
||||
}
|
||||
|
||||
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.functionInsights("setDefaultBackground", this.isWallpaperEngine)
|
||||
}
|
||||
|
||||
setBackground(e) {
|
||||
this.#readFile(
|
||||
e,
|
||||
(blobURL, type) => {
|
||||
this.setBackgoundImage(`url("${blobURL}")`)
|
||||
document.getElementById("custom_background_clear").disabled = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resetBackground() {
|
||||
document.getElementById("custom_background").value = ""
|
||||
document.getElementById("custom_background_clear").disabled = true
|
||||
this.setBackgoundImage(this.#defaultBackgroundImage)
|
||||
}
|
||||
|
||||
loadViewport() {
|
||||
this.spinePlayer.updateViewport({
|
||||
padLeft: `${this.#padLeft}%`,
|
||||
padRight: `${this.#padRight}%`,
|
||||
padTop: `${this.#padTop}%`,
|
||||
padBottom: `${this.#padBottom}%`,
|
||||
})
|
||||
}
|
||||
|
||||
setScale(value) {
|
||||
this.#scale = value
|
||||
}
|
||||
|
||||
get scale() {
|
||||
return 1 / this.#scale
|
||||
}
|
||||
|
||||
positionPadding(key, value) {
|
||||
switch (key) {
|
||||
case "left":
|
||||
this.#padLeft = value
|
||||
break;
|
||||
case "right":
|
||||
this.#padRight = value
|
||||
break;
|
||||
case "top":
|
||||
this.#padTop = value
|
||||
break;
|
||||
case "bottom":
|
||||
this.#padBottom = value
|
||||
break;
|
||||
default:
|
||||
this.#padLeft = value.left
|
||||
this.#padRight = value.right
|
||||
this.#padTop = value.top
|
||||
this.#padBottom = value.bottom
|
||||
break;
|
||||
}
|
||||
this.loadViewport()
|
||||
this.functionInsights("positionPadding", this.isWallpaperEngine)
|
||||
}
|
||||
|
||||
positionReset() {
|
||||
this.#padLeft = this.#defaultPadLeft
|
||||
this.#padRight = this.#defaultPadRight
|
||||
this.#padTop = this.#defaultPadTop
|
||||
this.#padBottom = this.#defaultPadBottom
|
||||
this.spinePlayer.updateViewport(this.#defaultViewport)
|
||||
document.getElementById("position_padding_left_slider").value = this.#defaultPadLeft
|
||||
document.getElementById("position_padding_left_input").value = this.#defaultPadLeft
|
||||
document.getElementById("position_padding_right_slider").value = this.#defaultPadRight
|
||||
document.getElementById("position_padding_right_input").value = this.#defaultPadRight
|
||||
document.getElementById("position_padding_top_slider").value = this.#defaultPadTop
|
||||
document.getElementById("position_padding_top_input").value = this.#defaultPadTop
|
||||
document.getElementById("position_padding_bottom_slider").value = this.#defaultPadBottom
|
||||
document.getElementById("position_padding_bottom_input").value = this.#defaultPadBottom
|
||||
}
|
||||
|
||||
scaleReset() {
|
||||
this.#scale = this.#defaultScale
|
||||
}
|
||||
|
||||
elementPosition(el, x, y) {
|
||||
const computedStyle = getComputedStyle(el)
|
||||
const elWidth = computedStyle.width
|
||||
const elHeight = computedStyle.height
|
||||
const windowWidth = window.innerWidth
|
||||
const windowHeight = window.innerHeight
|
||||
const xRange = windowWidth - parseInt(elWidth)
|
||||
const yRange = windowHeight - parseInt(elHeight)
|
||||
const xpx = x * xRange / 100
|
||||
const ypx = y * yRange / 100
|
||||
el.style.transform = `translate(${xpx}px, ${ypx}px)`
|
||||
}
|
||||
|
||||
logoPadding(key, value) {
|
||||
switch (key) {
|
||||
case "x":
|
||||
this.#logoX = value
|
||||
break;
|
||||
case "y":
|
||||
this.#logoY = value
|
||||
break;
|
||||
default:
|
||||
this.#logoX = value.x
|
||||
this.#logoY = value.y
|
||||
break;
|
||||
}
|
||||
this.elementPosition(this.#logoEl, this.#logoX, this.#logoY)
|
||||
this.functionInsights("logoPadding", this.isWallpaperEngine)
|
||||
}
|
||||
|
||||
logoReset() {
|
||||
this.#logoX = this.#defaultLogoX
|
||||
this.#logoY = this.#defaultLogoY
|
||||
this.elementPosition(this.#logoEl, this.#logoX, this.#logoY)
|
||||
document.getElementById("logo_padding_x_slider").value = this.#defaultLogoX
|
||||
document.getElementById("logo_padding_x_input").value = this.#defaultLogoX
|
||||
document.getElementById("logo_padding_y_slider").value = this.#defaultLogoY
|
||||
document.getElementById("logo_padding_y_input").value = this.#defaultLogoY
|
||||
}
|
||||
|
||||
set useStartAnimation(v) {
|
||||
this.#useStartAnimation = v
|
||||
}
|
||||
|
||||
get useStartAnimation() {
|
||||
return this.#useStartAnimation
|
||||
}
|
||||
|
||||
open() {
|
||||
this.#el.hidden = false;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.#el.hidden = true;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.positionReset()
|
||||
this.scaleReset()
|
||||
this.setLogoRatio(this.#defaultRatio)
|
||||
document.getElementById("logo_ratio_slider").value = this.#defaultRatio
|
||||
document.getElementById("logo_ratio_input").value = this.#defaultRatio
|
||||
this.setLogoOpacity(this.#defaultOpacity)
|
||||
document.getElementById("logo_opacity_slider").value = this.#defaultOpacity
|
||||
document.getElementById("logo_opacity_input").value = this.#defaultOpacity
|
||||
this.resetLogoImage()
|
||||
this.logoReset()
|
||||
this.resetBackground()
|
||||
this.resetMusic()
|
||||
this.setLogoDisplay(this.#defaulthideLogo)
|
||||
this.setFPS(this.#defaultFps)
|
||||
document.getElementById("fps_slider").value = this.#defaultFps
|
||||
document.getElementById("fps_input").value = this.#defaultFps
|
||||
this.spinePlayer.play()
|
||||
}
|
||||
|
||||
setMusicFromWE(url) {
|
||||
const type = url.split(".").pop()
|
||||
window.music.setMusic(url, type)
|
||||
document.getElementById("custom_music_clear").disabled = false
|
||||
}
|
||||
|
||||
setMusic(e) {
|
||||
this.#readFile(
|
||||
e,
|
||||
(blobURL, type) => {
|
||||
window.music.setMusic(blobURL, type)
|
||||
document.getElementById("custom_music_clear").disabled = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resetMusic() {
|
||||
document.getElementById("custom_music").value = ""
|
||||
document.getElementById("custom_music_clear").disabled = true
|
||||
window.music.resetMusic()
|
||||
}
|
||||
|
||||
setVideoFromWE(url) {
|
||||
const type = url.split(".").pop()
|
||||
document.getElementById("custom_video_background_clear").disabled = false
|
||||
}
|
||||
|
||||
resetVideo() {
|
||||
document.getElementById("custom_video_background").value = ""
|
||||
document.getElementById("custom_video_background_clear").disabled = true
|
||||
}
|
||||
|
||||
#insertHTML() {
|
||||
this.#el.innerHTML = `
|
||||
<div>
|
||||
<div>
|
||||
<label for="fps">FPS</label>
|
||||
<input type="range" min="1" max="60" value="${this.#fps}" step="1" id="fps_slider"/>
|
||||
<input type="number" id="fps_input" min="1" max="60" name="fps" value="${this.#fps}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="operator_logo">Operator Logo</label>
|
||||
<input type="checkbox" id="operator_logo" name="operator_logo" checked/>
|
||||
<div id="operator_logo_realted">
|
||||
<div>
|
||||
<label for="logo_image">Logo Image (Store Locally)</label>
|
||||
<input type="file" id="logo_image" accept="image/*"/>
|
||||
<button type="button" id="logo_image_clear" disabled>Clear</button>
|
||||
</div>
|
||||
<div>
|
||||
<label for="logo_ratio">Logo Ratio</label>
|
||||
<input type="range" min="0" max="100" step="0.1" id="logo_ratio_slider" value="${this.#ratio}" />
|
||||
<input type="number" id="logo_ratio_input" name="logo_ratio" value="${this.#ratio}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="logo_opacity">Logo Opacity</label>
|
||||
<input type="range" min="0" max="100" data-css-class="logo" step="1" id="logo_opacity_slider" value="${this.#opacity}" />
|
||||
<input type="number" id="logo_opacity_input" name="logo_opacity" value="${this.#opacity}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="logo_padding_x">Logo X Position</label>
|
||||
<input type="range" min="0" max="100" id="logo_padding_x_slider" value="${this.#logoX}" />
|
||||
<input type="number" id="logo_padding_x_input" name="logo_padding_x" value="${this.#logoX}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="logo_padding_y">Logo Y Position</label>
|
||||
<input type="range" min="0" max="100" id="logo_padding_y_slider" value="${this.#logoY}" />
|
||||
<input type="number" id="logo_padding_y_input" name="logo_padding_y" value="${this.#logoY}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label for="default_background_select">Choose a default background:</label>
|
||||
<select name="default_backgrounds" id="default_background_select">
|
||||
${this.#updateOptions(null, JSON.parse(import.meta.env.VITE_BACKGROUND_FILES))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="custom_background"> Custom Background (Store Locally)</label>
|
||||
<input type="file" id="custom_background" accept="image/*"/>
|
||||
<button type="button" disabled id="custom_background_clear" disabled>Clear</button>
|
||||
</div>
|
||||
<div>
|
||||
<label for="custom_video_background"> Custom Video Background (Store Locally)</label>
|
||||
<input type="file" id="custom_video_background" accept="video/*"/>
|
||||
<button type="button" disabled id="custom_video_background_clear" disabled>Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="voice">Voice</label>
|
||||
<input type="checkbox" id="voice" name="voice"/>
|
||||
<div id="voice_realted" hidden>
|
||||
<div>
|
||||
<label for="voice_lang_select">Choose the language of voice:</label>
|
||||
<select name="voice_lang" id="voice_lang_select">
|
||||
${this.#updateOptions("voice_lang_select", window.voice.languages)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="voice_idle_duration">Idle Duration (min)</label>
|
||||
<input type="number" id="voice_idle_duration_input" min="0" name="voice_idle_duration" value="${window.voice.idleDuration}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="voice_next_duration">Next Duration (min)</label>
|
||||
<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"/>
|
||||
<div id="subtitle_realted" hidden>
|
||||
<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>
|
||||
<label for="voice_actor">Voice Actor</label>
|
||||
<input type="checkbox" id="voice_actor" name="voice_actor"/>
|
||||
</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="custom_music"> Custom Music (Store Locally)</label>
|
||||
<input type="file" id="custom_music" accept="audio/*"/>
|
||||
<button type="button" disabled id="custom_music_clear" disabled>Clear</button>
|
||||
</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>
|
||||
<label for="scale">Scale</label>
|
||||
<input type="range" min="0" max="10" step="0.1" id="scale_slider" value="${this.#scale}" />
|
||||
<input type="number" id="scale_input" name="scale" value="${this.#scale}" step="0.1"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="position">Position</label>
|
||||
<input type="checkbox" id="position" name="position" />
|
||||
<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}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="position_padding_right">Padding Right</label>
|
||||
<input type="range" min="-100" max="100" id="position_padding_right_slider" value="${this.padRight}" />
|
||||
<input type="number" id="position_padding_right_input" name="position_padding_right" value="${this.#padRight}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="position_padding_top">Padding Top</label>
|
||||
<input type="range" min="-100" max="100" id="position_padding_top_slider" value="${this.#padTop}" />
|
||||
<input type="number" id="position_padding_top_input" name="position_padding_top" value="${this.#padTop}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="position_padding_bottom">Padding Bottom</label>
|
||||
<input type="range" min="-100" max="100" id="position_padding_bottom_slider" value="${this.#padBottom}" />
|
||||
<input type="number" id="position_padding_bottom_input" name="position_padding_bottom" value="${this.#padBottom}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="animation_select">Animation:</label>
|
||||
<select name="animation_select" id="animation_selection"></select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="use_start_animation">Use Start Animation</label>
|
||||
<input type="checkbox" id="use_start_animation" name="use_start_animation" checked/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" id="settings_play" disabled>Play</button>
|
||||
<button type="button" id="settings_pause">Pause</button>
|
||||
<button type="button" id="settings_reset">Reset</button>
|
||||
<button type="button" id="settings_close">Close</button>
|
||||
<button type="button" id="settings_to_directory">Back to Directory</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
this.#addEventListeners()
|
||||
}
|
||||
|
||||
#sync(source, targetID) {
|
||||
if (typeof source === "string") source = document.getElementById(source);
|
||||
document.getElementById(targetID).value = source.value;
|
||||
}
|
||||
|
||||
#showRelated(e, relatedSettingsID, revert = false) {
|
||||
const eRelatedSettings = document.getElementById(relatedSettingsID)
|
||||
const checked = revert ? !e.checked : e.checked;
|
||||
if (checked) {
|
||||
eRelatedSettings.hidden = false;
|
||||
} else {
|
||||
eRelatedSettings.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
#updateOptions(id, array) {
|
||||
const e = document.getElementById(id);
|
||||
const value = array.map(item => `<option value="${item}">${item}</option>`)
|
||||
if (e) {
|
||||
e.innerHTML = value.join("");
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
#getCurrentOptions(id, value) {
|
||||
const e = document.getElementById(id);
|
||||
const options = [...e]
|
||||
const toSelecteIndex = options.findIndex(i => options.find(o => o.value === value) === i)
|
||||
e.selectedIndex = toSelecteIndex;
|
||||
}
|
||||
|
||||
#addEventListeners() {
|
||||
const _this = this;
|
||||
const listeners = [
|
||||
{
|
||||
id: "fps_slider", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "fps_input");
|
||||
_this.setFPS(e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "fps_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "fps_input");
|
||||
}
|
||||
}, {
|
||||
id: "fps_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "fps_slider");
|
||||
_this.setFPS(e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "operator_logo", event: "click", handler: e => {
|
||||
_this.#showRelated(e.currentTarget, "operator_logo_realted");
|
||||
_this.setLogoDisplay(!e.currentTarget.checked)
|
||||
}
|
||||
}, {
|
||||
id: "logo_image", event: "change", handler: e => _this.setLogoImage(e)
|
||||
}, {
|
||||
id: "logo_image_clear", event: "click", handler: () => _this.resetLogoImage()
|
||||
}, {
|
||||
id: "logo_ratio_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "logo_ratio_input");
|
||||
_this.setLogoRatio(e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "logo_ratio_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "logo_ratio_slider");
|
||||
_this.setLogoRatio(e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "logo_opacity_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "logo_opacity_input");
|
||||
_this.setLogoOpacity(e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "logo_opacity_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "logo_opacity_slider");
|
||||
_this.setLogoOpacity(e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "logo_padding_x_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "logo_padding_x_input");
|
||||
_this.logoPadding("x", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "logo_padding_x_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "logo_padding_x_slider");
|
||||
_this.logoPadding("x", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "logo_padding_y_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "logo_padding_y_input");
|
||||
_this.logoPadding("y", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "logo_padding_y_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "logo_padding_y_slider");
|
||||
_this.logoPadding("y", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "default_background_select", event: "change", handler: e => _this.setDefaultBackground(e.currentTarget.value)
|
||||
}, {
|
||||
id: "custom_background", event: "change", handler: e => _this.setBackground(e)
|
||||
}, {
|
||||
id: "custom_background_clear", event: "click", handler: () => _this.resetBackground()
|
||||
}, {
|
||||
id: "voice", event: "click", handler: e => {
|
||||
_this.#showRelated(e.currentTarget, "voice_realted");
|
||||
window.voice.useVoice = e.currentTarget.checked;
|
||||
}
|
||||
}, {
|
||||
id: "voice_lang_select", event: "change", handler: e => {
|
||||
window.voice.language = e.currentTarget.value
|
||||
_this.#updateOptions("subtitle_lang_select", window.voice.subtitleLanguages)
|
||||
_this.#getCurrentOptions("subtitle_lang_select", window.voice.subtitleLanguage)
|
||||
}
|
||||
}, {
|
||||
id: "voice_idle_duration_input", event: "change", handler: e => {
|
||||
window.voice.idleDuration = parseInt(e.currentTarget.value)
|
||||
}
|
||||
}, {
|
||||
id: "voice_next_duration_input", event: "change", handler: e => {
|
||||
window.voice.nextDuration = parseInt(e.currentTarget.value)
|
||||
}
|
||||
}, {
|
||||
id: "subtitle", event: "click", handler: e => {
|
||||
_this.#showRelated(e.currentTarget, "subtitle_realted");
|
||||
window.voice.useSubtitle = e.currentTarget.checked;
|
||||
}
|
||||
}, {
|
||||
id: "subtitle_lang_select", event: "change", handler: e => window.voice.subtitleLanguage = e.currentTarget.value
|
||||
}, {
|
||||
id: "subtitle_padding_x_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "subtitle_padding_x_input");
|
||||
window.voice.subtitleX = e.currentTarget.value
|
||||
}
|
||||
}, {
|
||||
id: "subtitle_padding_x_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "subtitle_padding_x_slider");
|
||||
window.voice.subtitleX = e.currentTarget.value
|
||||
}
|
||||
}, {
|
||||
id: "subtitle_padding_y_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "subtitle_padding_y_input");
|
||||
window.voice.subtitleY = e.currentTarget.value
|
||||
}
|
||||
}, {
|
||||
id: "subtitle_padding_y_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "subtitle_padding_y_slider");
|
||||
window.voice.subtitleY = e.currentTarget.value
|
||||
}
|
||||
}, {
|
||||
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: "custom_music", event: "change", handler: e => _this.setMusic(e)
|
||||
}, {
|
||||
id: "custom_music_clear", event: "click", handler: () => _this.resetMusic()
|
||||
}, {
|
||||
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");
|
||||
if (!e.currentTarget.checked) _this.positionReset();
|
||||
}
|
||||
}, {
|
||||
id: "scale_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "scale_input");
|
||||
_this.setScale(e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "scale_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "scale_slider");
|
||||
_this.setScale(e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "position_padding_left_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "position_padding_left_input");
|
||||
_this.positionPadding("left", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "position_padding_left_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "position_padding_left_slider");
|
||||
_this.positionPadding("left", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "position_padding_right_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "position_padding_right_input");
|
||||
_this.positionPadding("right", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "position_padding_right_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "position_padding_right_slider");
|
||||
_this.positionPadding("right", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "position_padding_top_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "position_padding_top_input");
|
||||
_this.positionPadding("top", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "position_padding_top_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "position_padding_top_slider");
|
||||
_this.positionPadding("top", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "position_padding_bottom_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "position_padding_bottom_input");
|
||||
_this.positionPadding("bottom", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "position_padding_bottom_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "position_padding_bottom_slider");
|
||||
_this.positionPadding("bottom", e.currentTarget.value);
|
||||
}
|
||||
}, {
|
||||
id: "settings_play", event: "click", handler: e => {
|
||||
this.spinePlayer.play();
|
||||
e.currentTarget.disabled = true;
|
||||
document.getElementById("settings_pause").disabled = false;
|
||||
}
|
||||
}, {
|
||||
id: "settings_pause", event: "click", handler: e => {
|
||||
this.spinePlayer.pause();
|
||||
e.currentTarget.disabled = true;
|
||||
document.getElementById("settings_play").disabled = false;
|
||||
}
|
||||
}, {
|
||||
id: "settings_reset", event: "click", handler: () => _this.reset()
|
||||
}, {
|
||||
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)
|
||||
this.spinePlayer.animationState.addAnimation(0, "Idle", true, 0);
|
||||
}
|
||||
}, {
|
||||
id: "use_start_animation", event: "click", handler: e => {
|
||||
this.#useStartAnimation = e.currentTarget.checked;
|
||||
}
|
||||
}, {
|
||||
id: "settings_to_directory", event: "click", handler: () => {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
listeners.forEach(listener => {
|
||||
document.getElementById(listener.id).addEventListener(listener.event, e => listener.handler(e))
|
||||
})
|
||||
}
|
||||
}
|
||||
75
showcase/src/components/voice.css
Normal file
75
showcase/src/components/voice.css
Normal file
@@ -0,0 +1,75 @@
|
||||
@import "https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans:wght@400;500;700&display=swap";
|
||||
|
||||
#voice_box {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 480px;
|
||||
opacity: 0;
|
||||
margin: 16px;
|
||||
font-family: 'Noto Sans SC', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans', sans-serif;
|
||||
transition: opacity 0.5s cubic-bezier(0.65, 0.05, 0.36, 1);
|
||||
}
|
||||
|
||||
.voice-title {
|
||||
background-color: #9e9e9e;
|
||||
color: black;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: -8px;
|
||||
padding: 2px 8px;
|
||||
font-size: 14px;
|
||||
min-width: 120px;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.voice-subtitle {
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
color: white;
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.5);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.voice-triangle {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 8px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 8px 8px 8px 8px;
|
||||
border-color: white transparent transparent transparent;
|
||||
}
|
||||
|
||||
.voice-actor {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.voice-actor-icon {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAXCAYAAAAGAx/kAAACGElEQVQ4jY2UO4sUQRSFv+6e3hkf6yqi62LiD1DRwGRFZDUTRTAVMTHzHxhosBgr+IhMzIXFRzKJYiQoCLqroYGCgiDK6K6z8+hj0Kd2yrZn8EJRza06X91761Ynkphgh4ETwBB4BrwZu1NS3WhJui1pTSNblXTLa/9oxoEWNd4W/xc0K6kzAfRd0lxV16jJdg6YBtaAB8AvoPA4D+wEZoEvsSgGJYAiXwf4EEFyYOC1qYqmFhQ2bQEO+MYKILMv1tWCikqKm4CPwKoF00DTa6pqkqiP9gIHgR3AWaDvdJIoip4jewh8BVaAz1XQOeAKcNcp9O0fAqlTLgzsApeBa8BSNbUUaAFvgVdMtnlHmsXiEHrHpw+iaOps4BRlDUASQDnw3uA9QHsCqA3sMmjFmhRJmaSmO/SmpCeSjktarunqd5IWJLUl3bCmKSlLJGWuVQbMAHcoi3oPWAAOOerXwFPgErAVuEjZGkNgkEhKDco9bwOuG/Ac+OS67ANOGngV+OYDewEUqt+gvLWmxxHgFHDapz52fV4C64Z3PW+AUsPyCJYCP4FHPuQMsNlRrHv0DRo2XP3Ccw/4Tdl8U2GTb6trYYimYNQ6Gw0pYDuwn1E7hJR3W3TMvp5hKbAM/IC/n8hR4L6hqSPJGT3QjPIfFX4nDeAC8CIGhTrBqNsz16plWN/R9KJShGzUiCBBnFS+FYmCP41gBaA/cImMsTl24NIAAAAASUVORK5CYII=');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.voice-actor-name {
|
||||
height: 16px;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: white;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
337
showcase/src/components/voice.js
Normal file
337
showcase/src/components/voice.js
Normal file
@@ -0,0 +1,337 @@
|
||||
import charword_table from '!/charword_table.json'
|
||||
import '@/components/voice.css'
|
||||
|
||||
export default class Voice {
|
||||
#el
|
||||
#widgetEl
|
||||
#audioEl = new Audio()
|
||||
#audioElId = 'voice-audio'
|
||||
#defaultRegion = charword_table.config.default_region
|
||||
#defaultIdleDuration = 10 * 60 * 1000
|
||||
#defaultNextDuration = 3 * 60 * 1000
|
||||
#voiceLanguages = Object.keys(charword_table.voiceLangs["zh-CN"])
|
||||
#defaultVoiceLang = this.#voiceLanguages[0]
|
||||
#voiceLang = this.#defaultVoiceLang
|
||||
#subtitleLang = this.#defaultRegion
|
||||
#useSubtitle = false
|
||||
#useVoice = false
|
||||
#useVoiceActor = false
|
||||
#isPlaying = false
|
||||
#currentVoiceId = null
|
||||
#lastVoiceId = null
|
||||
#idleListener = -1
|
||||
#idleDuration = this.#defaultIdleDuration
|
||||
#nextListener = -1
|
||||
#nextDuration = this.#defaultNextDuration
|
||||
#lastClickToNext = false
|
||||
#voiceFolderObject = this.#getVoiceFolderObject()
|
||||
#voiceList = Object.keys(this.#getVoices())
|
||||
#defaultSubtitleX = 0
|
||||
#defaultSubtitleY = 0
|
||||
#subtitleX = this.#defaultSubtitleX
|
||||
#subtitleY = this.#defaultSubtitleY
|
||||
|
||||
constructor(el, widgetEl) {
|
||||
this.#el = el
|
||||
this.#widgetEl = widgetEl
|
||||
}
|
||||
|
||||
init() {
|
||||
this.#insertHTML()
|
||||
this.#audioEl = document.getElementById(this.#audioElId)
|
||||
}
|
||||
|
||||
success() {
|
||||
const audioEndedFunc = () => {
|
||||
this.#isPlaying = false
|
||||
this.#setCurrentSubtitle(null)
|
||||
this.#lastClickToNext = false
|
||||
}
|
||||
this.#audioEl.addEventListener('ended', audioEndedFunc)
|
||||
this.#playEntryVoice()
|
||||
this.#initNextVoiceTimer()
|
||||
this.#widgetEl.addEventListener('click', () => {
|
||||
this.#lastClickToNext = true
|
||||
this.#nextVoice()
|
||||
})
|
||||
document.addEventListener('mousemove', () => {
|
||||
if (this.#idleListener === -1) {
|
||||
this.#initIdleVoiceTimer()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
*/
|
||||
set useSubtitle(show) {
|
||||
this.#useSubtitle = show
|
||||
this.#el.hidden = !show
|
||||
window.settings.functionInsights("useSubtitle", window.settings.isWallpaperEngine)
|
||||
}
|
||||
|
||||
get useSubtitle() {
|
||||
return this.#useSubtitle
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
*/
|
||||
set useVoice(show) {
|
||||
window.settings.functionInsights("useVoice", window.settings.isWallpaperEngine)
|
||||
this.#useVoice = show
|
||||
this.#playEntryVoice()
|
||||
if (!show && this.#isPlaying) {
|
||||
this.#audioEl.pause()
|
||||
}
|
||||
}
|
||||
|
||||
get useVoice() {
|
||||
return this.#useVoice
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
*/
|
||||
set useVoiceActor(show) {
|
||||
this.#useVoiceActor = show
|
||||
document.getElementById('voice_actor_box').hidden = !show
|
||||
window.settings.functionInsights("useVoiceActor", window.settings.isWallpaperEngine)
|
||||
}
|
||||
|
||||
get useVoiceActor() {
|
||||
return this.#useVoiceActor
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} lang
|
||||
*/
|
||||
set subtitleLanguage(lang) {
|
||||
if (this.#getSubtitleLanguages().includes(lang)) {
|
||||
this.#subtitleLang = lang
|
||||
} else {
|
||||
this.#subtitleLang = this.#defaultRegion
|
||||
}
|
||||
window.settings.functionInsights("subtitleLanguage", window.settings.isWallpaperEngine)
|
||||
}
|
||||
|
||||
get subtitleLanguage() {
|
||||
return this.#subtitleLang
|
||||
}
|
||||
|
||||
get subtitleLanguages() {
|
||||
return this.#getSubtitleLanguages()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} x
|
||||
*/
|
||||
set subtitleX(x) {
|
||||
this.#subtitleX = x
|
||||
this.#updateSubtitlePosition()
|
||||
}
|
||||
|
||||
get subtitleX() {
|
||||
return this.#subtitleX
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} y
|
||||
*/
|
||||
set subtitleY(y) {
|
||||
this.#subtitleY = y - 100
|
||||
this.#updateSubtitlePosition()
|
||||
}
|
||||
|
||||
get subtitleY() {
|
||||
return this.#subtitleY + 100
|
||||
}
|
||||
|
||||
#updateSubtitlePosition() {
|
||||
window.settings.elementPosition(this.#el, this.#subtitleX, this.#subtitleY)
|
||||
window.settings.functionInsights("subtitlePosition", window.settings.isWallpaperEngine)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} lang
|
||||
*/
|
||||
set language(lang) {
|
||||
if (this.#voiceLanguages.includes(lang)) {
|
||||
this.#voiceLang = lang
|
||||
} else {
|
||||
this.#voiceLang = this.#defaultVoiceLang
|
||||
}
|
||||
window.settings.functionInsights("language", window.settings.isWallpaperEngine)
|
||||
const availableSubtitleLang = this.#getSubtitleLanguages()
|
||||
if (!availableSubtitleLang.includes(this.#subtitleLang)) {
|
||||
this.#subtitleLang = availableSubtitleLang[0]
|
||||
}
|
||||
}
|
||||
|
||||
get language() {
|
||||
return this.#voiceLang
|
||||
}
|
||||
|
||||
get languages() {
|
||||
return this.#voiceLanguages
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} duration
|
||||
*/
|
||||
set idleDuration(duration) {
|
||||
clearInterval(this.#idleListener)
|
||||
if (duration !== 0) {
|
||||
this.#idleDuration = duration * 60 * 1000
|
||||
this.#initIdleVoiceTimer()
|
||||
}
|
||||
window.settings.functionInsights("idleDuration", window.settings.isWallpaperEngine)
|
||||
}
|
||||
|
||||
get idleDuration() {
|
||||
return this.#idleDuration / 60 / 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} duration
|
||||
*/
|
||||
set nextDuration(duration) {
|
||||
clearInterval(this.#nextListener)
|
||||
if (duration !== 0) {
|
||||
this.#nextDuration = duration * 60 * 1000
|
||||
this.#initNextVoiceTimer()
|
||||
}
|
||||
window.settings.functionInsights("nextDuration", window.settings.isWallpaperEngine)
|
||||
}
|
||||
|
||||
get nextDuration() {
|
||||
return this.#nextDuration / 60 / 1000
|
||||
}
|
||||
|
||||
#initIdleVoiceTimer() {
|
||||
this.#idleListener = setInterval(() => {
|
||||
this.#playSpecialVoice("闲置")
|
||||
clearInterval(this.#idleListener)
|
||||
this.#idleListener = -1
|
||||
}, this.#idleDuration)
|
||||
}
|
||||
|
||||
#initNextVoiceTimer() {
|
||||
this.#nextListener = setInterval(() => {
|
||||
if (!this.#lastClickToNext) {
|
||||
this.#nextVoice()
|
||||
}
|
||||
}, this.#nextDuration)
|
||||
}
|
||||
|
||||
#nextVoice() {
|
||||
const voiceId = () => {
|
||||
const id = this.#voiceList[Math.floor((Math.random() * this.#voiceList.length))]
|
||||
return id === this.#lastVoiceId ? voiceId() : id
|
||||
}
|
||||
this.#playVoice(voiceId())
|
||||
}
|
||||
|
||||
#playEntryVoice() {
|
||||
this.#playSpecialVoice("问候")
|
||||
}
|
||||
|
||||
#setCurrentSubtitle(id) {
|
||||
if (id === null) {
|
||||
setTimeout(() => {
|
||||
if (this.#isPlaying) return
|
||||
this.#el.style.opacity = 0
|
||||
}, 5 * 1000);
|
||||
return
|
||||
}
|
||||
const subtitle = this.#getSubtitleById(id)
|
||||
const title = subtitle.title
|
||||
const content = subtitle.text
|
||||
const cvInfo = charword_table.voiceLangs[this.subtitleLanguage][this.#voiceLang]
|
||||
document.getElementById('voice_title').innerText = title
|
||||
document.getElementById('voice_subtitle').innerText = content
|
||||
this.#el.style.opacity = 1
|
||||
document.getElementById('voice_actor_name').innerText = cvInfo.join('')
|
||||
}
|
||||
|
||||
#playVoice(id) {
|
||||
if (!this.useVoice) return
|
||||
this.#lastVoiceId = this.#currentVoiceId
|
||||
this.#currentVoiceId = id
|
||||
this.#audioEl.src = `./assets/${this.#getVoiceFoler()
|
||||
}/${id}.ogg`
|
||||
let startPlayPromise = this.#audioEl.play()
|
||||
if (startPlayPromise !== undefined) {
|
||||
startPlayPromise
|
||||
.then(() => {
|
||||
this.#isPlaying = true
|
||||
this.#setCurrentSubtitle(id)
|
||||
})
|
||||
.catch(() => {
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#playSpecialVoice(matcher) {
|
||||
const voices = this.#getVoices()
|
||||
const voiceId = Object.keys(voices).find(e => voices[e].title === matcher)
|
||||
this.#playVoice(voiceId)
|
||||
}
|
||||
|
||||
#getVoiceFoler() {
|
||||
const folderObject = this.#voiceFolderObject
|
||||
return `${folderObject.main}/${folderObject.sub.find(e => e.lang === this.#voiceLang).name}`
|
||||
}
|
||||
|
||||
#getVoices() {
|
||||
return charword_table.subtitleLangs[this.#subtitleLang].default
|
||||
}
|
||||
|
||||
#getSubtitleById(id) {
|
||||
const obj = charword_table.subtitleLangs[this.#subtitleLang]
|
||||
let key = 'default'
|
||||
if (obj[this.#voiceLang]) {
|
||||
key = this.#voiceLang
|
||||
}
|
||||
return obj[key][id]
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
#getSubtitleLanguages() {
|
||||
return Object.keys(charword_table.subtitleLangs)
|
||||
}
|
||||
|
||||
#insertHTML() {
|
||||
this.#el.innerHTML = `
|
||||
<audio id="${this.#audioElId}" autoplay>
|
||||
<source type="audio/ogg" />
|
||||
</audio>
|
||||
<div class="voice-wrapper" id="voice_wrapper">
|
||||
<div class="voice-title" id="voice_title"></div>
|
||||
<div class="voice-subtitle">
|
||||
<div id="voice_subtitle"></div>
|
||||
<div class="voice-triangle"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="voice_actor_box" hidden>
|
||||
<div class="voice-actor">
|
||||
<span class="voice-actor-icon"></span>
|
||||
<span id="voice_actor_name" class="voice-actor-name"></span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user