2023-03-15 08:56:17 +09:00

241 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as React from "react";
import { createRoot } from "react-dom/client";
import "./css/App.css"
import { ErrorInfo, useEffect, useMemo, useState, } from "react";
import { useMicrophoneOptions } from "./100_options_microphone";
import { AppStateProvider, useAppState } from "./001_provider/001_AppStateProvider";
import { library } from "@fortawesome/fontawesome-svg-core";
import { fas } from "@fortawesome/free-solid-svg-icons";
import { far } from "@fortawesome/free-regular-svg-icons";
import { fab } from "@fortawesome/free-brands-svg-icons";
import { AppRootProvider } from "./001_provider/001_AppRootProvider";
import ErrorBoundary from "./001_provider/900_ErrorBoundary";
import { INDEXEDDB_KEY_CLIENT, INDEXEDDB_KEY_MODEL_DATA, INDEXEDDB_KEY_SERVER, INDEXEDDB_KEY_WORKLET, INDEXEDDB_KEY_WORKLETNODE, useIndexedDB } from "@dannadori/voice-changer-client-js";
import { CLIENT_TYPE, INDEXEDDB_KEY_AUDIO_OUTPUT, isDesktopApp, TSUKUYOMI_CANVAS } from "./const";
import { Dialog } from "./components/201_Dialog";
library.add(fas, far, fab);
const container = document.getElementById("app")!;
const root = createRoot(container);
const App = () => {
const appState = useAppState()
const { removeItem } = useIndexedDB({ clientType: CLIENT_TYPE })
const { voiceChangerSetting } = useMicrophoneOptions()
const titleRow = useMemo(() => {
const githubLink = isDesktopApp() ?
(
// @ts-ignore
<span className="link" onClick={() => { window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer") }}>
<img src="./assets/icons/github.svg" />
<span>github</span>
</span>
)
:
(
<a className="link" href="https://github.com/w-okada/voice-changer" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/github.svg" />
<span>github</span>
</a>
)
const manualLink = isDesktopApp() ?
(
// @ts-ignore
<span className="link" onClick={() => { window.electronAPI.openBrowser("https://zenn.dev/wok/books/0003_vc-helper-v_1_5") }}>
<img src="./assets/icons/help-circle.svg" />
<span>manual</span>
</span>
)
:
(
<a className="link" href="https://zenn.dev/wok/books/0003_vc-helper-v_1_5" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/help-circle.svg" />
<span>manual</span>
</a>
)
const coffeeLink = isDesktopApp() ?
(
// @ts-ignore
<span className="link" onClick={() => { window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad") }}>
<img className="donate-img" src="./assets/buymeacoffee.png" />
<span>donate()</span>
</span>
)
:
(
<a className="link" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" />
<span>donate()</span>
</a>
)
const licenseButton = (
<span className="link" onClick={() => {
document.getElementById("dialog")?.classList.add("dialog-container-show")
appState.frontendManagerState.stateControls.showLicenseCheckbox.updateState(true)
}}>
<span>License</span>
</span>
)
return (
<>
<div className="top-title">
<span className="title"></span>
</div>
<div className="top-title">
<span className="top-title-version"> ver.0.0α</span>
<span className="belongings">
{githubLink}
{manualLink}
{coffeeLink}
{licenseButton}
</span>
</div>
</>
)
}, [])
const clearRow = useMemo(() => {
const onClearSettingClicked = async () => {
await appState.clearSetting()
await removeItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
location.reload()
}
return (
<>
<div className="body-row split-3-3-4 left-padding-1">
<div className="body-button-container">
<div className="body-button" onClick={onClearSettingClicked}>clear setting</div>
</div>
<div className="body-item-text"></div>
<div className="body-item-text"></div>
</div>
</>
)
}, [])
const mainSetting = useMemo(() => {
return (
<>
<div className="main-body">
<Dialog />
{titleRow}
<div>
<canvas id={TSUKUYOMI_CANVAS}></canvas>
</div>
{clearRow}
{voiceChangerSetting}
</div>
</>
)
}, [voiceChangerSetting])
useEffect(() => {
const startPsdAnimation = async () => {
await appState.psdAnimationState.loadPsd()
await appState.psdAnimationState.setMotion()
await appState.psdAnimationState.switchNormalMotion()
await appState.psdAnimationState.start()
}
startPsdAnimation()
}, [])
useEffect(() => {
if (!appState.initializedRef.current) {
return
}
if (appState.volume > 0.0001) {
appState.psdAnimationState.switchTalkingMotion()
} else {
appState.psdAnimationState.switchNormalMotion()
}
}, [appState.volume, appState.initializedRef.current])
return (
<>
{mainSetting}
</>
)
}
const AppStateWrapper = () => {
// エラーバウンダリー設定
const [error, setError] = useState<{ error: Error, errorInfo: ErrorInfo }>()
const { removeItem } = useIndexedDB({ clientType: CLIENT_TYPE })
const errorComponent = useMemo(() => {
const errorName = error?.error.name || "no error name"
const errorMessage = error?.error.message || "no error message"
const errorInfos = (error?.errorInfo.componentStack || "no error stack").split("\n")
const onClearCacheClicked = async () => {
const indexedDBKeys = [
INDEXEDDB_KEY_CLIENT,
INDEXEDDB_KEY_SERVER,
INDEXEDDB_KEY_WORKLETNODE,
INDEXEDDB_KEY_MODEL_DATA,
INDEXEDDB_KEY_WORKLET,
INDEXEDDB_KEY_AUDIO_OUTPUT
]
for (const k of indexedDBKeys) {
await removeItem(k)
}
location.reload();
}
return (
<div className="error-container">
<div className="top-error-message">
</div>
<div className="top-error-description">
<p></p>
<p></p>
<p><button onClick={onClearCacheClicked}></button></p>
</div>
<div className="error-detail">
<div className="error-name">
{errorName}
</div>
<div className="error-message">
{errorMessage}
</div>
<div className="error-info-container">
{errorInfos.map(x => {
return <div className="error-info-line" key={x}>{x}</div>
})}
</div>
</div>
</div>
)
}, [error])
const updateError = (error: Error, errorInfo: React.ErrorInfo) => {
console.log("error compo", error, errorInfo)
setError({ error, errorInfo })
}
return (
<ErrorBoundary fallback={errorComponent} onError={updateError}>
<AppStateProvider>
<App></App>
</AppStateProvider>
</ErrorBoundary>
)
}
root.render(
<AppRootProvider>
<AppStateWrapper></AppStateWrapper>
</AppRootProvider>
);