Files
aklive2d/directory/src/routes/Error.jsx
2023-07-30 03:29:41 -04:00

191 lines
5.5 KiB
JavaScript

import React, {
useState,
useEffect,
useMemo,
useRef,
useCallback
} from "react";
import {
useNavigate,
useRouteError
} from "react-router-dom";
import header from '@/scss/root/header.module.scss'
import classes from '@/scss/error/Error.module.scss'
import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils';
import Switch from '@/component/switch';
import ReturnButton from "@/component/return_button";
import { Typewriter } from 'react-simple-typewriter'
import { useHeader } from '@/state/header';
import VoiceElement from '@/component/voice';
import spine from '!/libs/spine-player'
import '!/libs/spine-player.css'
import useUmami from '@/state/insights';
const voiceOnAtom = atomWithStorage('voiceOn', false)
const config = JSON.parse(import.meta.env.VITE_ERROR_FILES)
const obj = config.files[Math.floor((Math.random() * config.files.length))]
const filename = obj.key.replace("#", "%23")
const padding = obj.paddings
let lastVoiceState = 'ended'
export default function Error() {
// eslint-disable-next-line no-unused-vars
const _trackEvt = useUmami('/error')
const error = useRouteError();
const navigate = useNavigate();
const {
setTitle,
} = useHeader()
const [voiceOn, setVoiceOn] = useAtom(voiceOnAtom)
const [spineDone, _setSpineDone] = useState(false)
const spineRef = useRef(null)
const [spineData, setSpineData] = useState(null)
const spineDoneRef = useRef(spineDone)
const voiceOnRef = useRef(voiceOn)
const [voiceSrc, setVoiceSrc] = useState(null)
const [voiceReplay, setVoiceReplay] = useState(false)
const [spinePlayer, setSpinePlayer] = useState(null)
const setSpineDone = (data) => {
spineDoneRef.current = data
_setSpineDone(data)
}
const content = useMemo(() => ['エラー発生。', '发生错误。', 'Error occured.', '에러 발생.', '發生錯誤。'], [])
useEffect(() => {
console.log(error)
fetch(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/${filename}.json`).then(res => res.json()).then(data => {
setSpineData(data)
})
}, [error])
useEffect(() => {
setTitle(content[0])
}, [content, setTitle])
useEffect(() => {
if (!voiceOn) {
setVoiceSrc(null)
} else {
setVoiceSrc(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/error.ogg`)
if (spinePlayer) {
spinePlayer.animationState.setAnimation(0, "Interact", false, 0);
spinePlayer.animationState.addAnimation(0, "Relax", true, 0);
}
}
}, [voiceOn])
useEffect(() => {
voiceOnRef.current = voiceOn
}, [voiceOn])
const playVoice = useCallback(() => {
if (lastVoiceState === 'ended' && voiceSrc !== null) {
setVoiceReplay(true)
}
}, [voiceSrc])
const handleAduioStateChange = useCallback((e, state) => {
lastVoiceState = state
if (state === 'ended') {
setVoiceReplay(false)
}
}, [])
useEffect(() => {
if (spineRef.current?.children.length === 0 && spineData) {
setSpinePlayer(new spine.SpinePlayer(spineRef.current, {
skelUrl: `./assets/${filename}.skel`,
atlasUrl: `./assets/${filename}.atlas`,
rawDataURIs: spineData,
animation: 'Relax',
premultipliedAlpha: true,
alpha: true,
backgroundColor: "#00000000",
viewport: {
debugRender: false,
padLeft: `${padding.left}%`,
padRight: `${padding.right}%`,
padTop: `${padding.top}%`,
padBottom: `${padding.bottom}%`,
x: 0,
y: 0,
},
showControls: false,
touch: false,
fps: 60,
defaultMix: 0.3,
success: (player) => {
let isPlayingInteract = false
player.animationState.addListener({
end: (e) => {
if (e.animation.name == "Interact") {
isPlayingInteract = false;
}
}
});
setSpineDone(true)
const ani = () => {
if (isPlayingInteract) {
return;
}
isPlayingInteract = true;
player.animationState.setAnimation(0, "Interact", false, 0);
player.animationState.addAnimation(0, "Relax", true, 0);
if (voiceOnRef.current) playVoice()
}
ani()
player.canvas.onclick = () => {
ani()
}
player.canvas.onmouseenter = () => {
ani()
}
}
}))
}
}, [playVoice, spineData]);
return (
<section className={classes.error}>
<header className={`${header.header} ${classes.header}`}>
<ReturnButton
onClick={() => navigate(-1, { replace: true })}
/>
<Switch
key="voice"
text='voice'
on={voiceOn}
handleOnClick={() => setVoiceOn(!voiceOn)}
/>
</header>
<main className={classes.main}>
{
content.map((item, index) => {
return (
<section key={index} className={classes.content}>
<Typewriter
words={[item]}
cursor
cursorStyle='|'
typeSpeed={100}
/>
</section>
)
})
}
<section
className={`${classes.spine} ${spineDone ? classes.active : ''}`}
ref={spineRef}
/>
<VoiceElement
src={voiceSrc}
replay={voiceReplay}
handleAduioStateChange={handleAduioStateChange}
/>
</main>
</section>
);
}