Files
aklive2d/template/showcase/src/components/settings.jsx

280 lines
9.5 KiB
JavaScript

import { useState, useEffect } from 'preact/hooks'
import '@/components/settings.css'
import { subscribe, unsubscribe, publish } from '@/libs/events'
const getPercentage = (value) => parseInt(value.replace("%", ""))
const defaultBackgroundImage = getComputedStyle(document.body).backgroundImage
export default function Settings({
spinePlayer, setShowSettings, hidden, logoEl
}) {
const defaultFps = 60
const defaultRatio = 61.8
const defaultOpacity = import.meta.env.VITE_OPACITY
const defaultShowLogo = false
const defaultInvertFilter = import.meta.env.VITE_INVERT_FILTER === "true"
const [defaultLogoImage, setDefaultLogoImage] = useState(null)
const defaultPadLeft = getPercentage(`${import.meta.env.VITE_VIEWPORT_LEFT}%`)
const defaultPadRight = getPercentage(`${import.meta.env.VITE_VIEWPORT_RIGHT}%`)
const defaultPadTop = getPercentage(`${import.meta.env.VITE_VIEWPORT_TOP}%`)
const defaultPadBottom = getPercentage(`${import.meta.env.VITE_VIEWPORT_BOTTOM}%`)
const defaultViewport = {
debugRender: false,
padLeft: `${defaultPadLeft}%`,
padRight: `${defaultPadRight}%`,
padTop: `${defaultPadTop}%`,
padBottom: `${defaultPadBottom}%`,
x: 0,
y: 0,
}
const [fps, setFps] = useState(defaultFps)
const [ratio, setRatio] = useState(defaultRatio)
const [opacity, setOpacity] = useState(defaultOpacity)
const [padLeft, setPadLeft] = useState(defaultPadLeft)
const [padRight, setPadRight] = useState(defaultPadRight)
const [padTop, setPadTop] = useState(defaultPadTop)
const [padBottom, setPadBottom] = useState(defaultPadBottom)
const [showLogo, setShowLogo] = useState(defaultShowLogo)
const [hidePositionSettings, setHidePositionSettings] = useState(true)
const [backgroundClearDisabled, setBackgroundClearDisabled] = useState(true)
const [logoClearDisabled, setLogoClearDisabled] = useState(true)
const [isPlaying, setIsPlaying] = useState(true)
const resize = (value) => {
logoEl.width = window.innerWidth / 2 * (value || ratio) / 100
}
const readFile = (e, onload, callback) => {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
reader.readAsDataURL(file);
reader.onload = readerEvent => onload(readerEvent)
callback()
}
const setFPS = (value) => {
setFps(value)
spinePlayer.setFps(value)
}
const setLogoDisplay = (flag) => {
setShowLogo(flag)
logoEl.hidden = flag;
}
const setLogoImage = (e) => {
readFile(
e,
(readerEvent) => {
const content = readerEvent.target.result;
logoEl.src = content
resize()
setLogoInvertFilter(false)
},
() => setLogoClearDisabled(false)
)
}
const resetLogoImage = () => {
logoEl.src = defaultLogoImage
resize()
setLogoInvertFilter(defaultInvertFilter)
setLogoClearDisabled(true)
}
const setLogoRatio = (value) => {
setRatio(value)
resize(value)
}
const setLogoOpacity = (value) => {
setOpacity(value)
logoEl.style.opacity = value / 100
}
const setLogoInvertFilter = (flag) => {
if (!flag) {
logoEl.style.filter = "invert(0)"
} else {
logoEl.style.filter = "invert(1)"
}
}
const setBackground = (e) => {
readFile(
e,
(readerEvent) => {
const content = readerEvent.target.result;
document.body.style.backgroundImage = `url("${content}")`
},
() => setBackgroundClearDisabled(false)
)
}
const resetBackground = () => {
document.body.style.backgroundImage = defaultBackgroundImage
setBackgroundClearDisabled(true)
}
const positionPadding = (key, value) => {
switch (key) {
case "left":
setPadLeft(value)
spinePlayer.updateViewport({
...defaultViewport,
padLeft: `${value}%`,
})
break;
case "right":
setPadRight(value)
spinePlayer.updateViewport({
...defaultViewport,
padRight: `${value}%`,
})
break;
case "top":
setPadTop(value)
spinePlayer.updateViewport({
...defaultViewport,
padTop: `${value}%`,
})
break;
case "bottom":
setPadBottom(value)
spinePlayer.updateViewport({
...defaultViewport,
padBottom: `${value}%`,
})
break;
}
}
const positionReset = () => {
setPadLeft(defaultPadLeft)
setPadRight(defaultPadRight)
setPadTop(defaultPadTop)
setPadBottom(defaultPadBottom)
spinePlayer.updateViewport(defaultViewport)
}
useEffect(() => {
if (logoEl) {
resize()
setLogoInvertFilter(defaultInvertFilter)
setLogoOpacity(defaultOpacity)
setDefaultLogoImage(logoEl.src)
}
}, [logoEl])
useEffect(() => {
window.addEventListener("contextmenu", e => e.preventDefault());
document.addEventListener("gesturestart", e => e.preventDefault());
}, [])
useEffect(() => {
window.addEventListener("resize", resize, true);
return () => {
window.removeEventListener("resize", resize, true);
}
}, [])
return (
<div class="website-settings" hidden={hidden}>
<div>
<label for="fps">FPS</label>
<input type="range" min="1" max="60" value={fps} step="1"
onChange={(e) => setFPS(e.target.value)}
/>
<input type="number" id="fps_input" min="1" max="60" name="fps" value={fps}
onChange={(e) => setFPS(e.target.value)}
/>
</div>
<div>
<label for="operator_logo">Operator Logo</label>
<input type="checkbox" name="operator_logo" checked={!showLogo} onClick={(e) => setLogoDisplay(!e.target.checked)} />
<div hidden={showLogo}>
<div>
<label for="logo_image">Logo Image (Store Locally)</label>
<input type="file" onChange={(e) => setLogoImage(e)} />
<button type="button" disabled={logoClearDisabled} onClick={() => resetLogoImage()}>Clear</button>
</div>
<div>
<label for="logo_ratio">Logo Ratio</label>
<input type="range" min="0" max="100" step="0.1" value={ratio} onChange={(e) => setLogoRatio(e.target.value)} />
<input type="number" name="logo_ratio" value={ratio} onChange={(e) => setLogoRatio(e.target.value)} />
</div>
<div>
<label for="logo_opacity">Logo Opacity</label>
<input type="range" min="0" max="100" step="1" id="logo_opacity_slider" value={opacity} onChange={(e) => setLogoOpacity(e.target.value)} />
<input type="number" id="logo_opacity_input" name="logo_opacity" value={opacity} onOpacity={(e) => setLogoOpacity(e.target.value)} />
</div>
</div>
</div>
<div>
<label for="background_image">Background Image (Store Locally)</label>
<input type="file" onChange={(e) => setBackground(e)} />
<button type="button" disabled={backgroundClearDisabled} onClick={() => resetBackground()}>Clear</button>
</div>
<div>
<label for="position">Position</label>
<input type="checkbox" name="position" checked={!hidePositionSettings} onClick={(e) => setHidePositionSettings(!e.target.checked)} />
<div hidden={hidePositionSettings}>
<div>
<label for="position_padding_left">Padding Left</label>
<input type="range" min="-100" max="100" value={padLeft} onChange={(e) => positionPadding("left", e.target.value)} />
<input type="number" name="position_padding_left" value={padLeft} onChange={(e) => positionPadding("left", e.target.value)} />
</div>
<div>
<label for="position_padding_right">Padding Right</label>
<input type="range" min="-100" max="100" value={padRight} onChange={(e) => positionPadding("right", e.target.value)} />
<input type="number" name="position_padding_right" value={padRight} onChange={(e) => positionPadding("right", e.target.value)} />
</div>
<div>
<label for="position_padding_top">Padding Top</label>
<input type="range" min="-100" max="100" value={padTop} onChange={(e) => positionPadding("top", e.target.value)} />
<input type="number" name="position_padding_top" value={padTop} onChange={(e) => positionPadding("top", e.target.value)} />
</div>
<div>
<label for="position_padding_bottom">Padding Bottom</label>
<input type="range" min="-100" max="100" value={padBottom} onChange={(e) => positionPadding("bottom", e.target.value)} />
<input type="number" name="position_padding_bottom" value={padBottom} onChange={(e) => positionPadding("bottom", e.target.value)} />
</div>
</div>
</div>
<div>
<button type="button" disabled={isPlaying}
onClick={() => {
spinePlayer.play()
setIsPlaying(true)
}}
>Play
</button>
<button type="button" disabled={!isPlaying}
onClick={() => {
spinePlayer.pause()
setIsPlaying(false)
}}
>
Pause
</button>
<button type="button" onClick={() => {
setFPS(defaultFps)
setLogoDisplay(defaultShowLogo)
resetLogoImage()
setLogoRatio(defaultRatio)
setLogoOpacity(defaultOpacity)
resetBackground()
positionReset()
spinePlayer.play()
}}>
Reset
</button>
<button type="button" onClick={() => setShowSettings(false)}>Close</button>
</div>
</div>
)
}