WIP: improve gui: add accordion

This commit is contained in:
wataru 2023-02-17 00:09:56 +09:00
parent 27c5d54cc5
commit eaf310c00b
23 changed files with 1278 additions and 574 deletions

View File

@ -10,6 +10,11 @@
"license": "ISC",
"dependencies": {
"@dannadori/voice-changer-client-js": "^1.0.69",
"@fortawesome/fontawesome-svg-core": "^6.3.0",
"@fortawesome/free-brands-svg-icons": "^6.3.0",
"@fortawesome/free-regular-svg-icons": "^6.3.0",
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@ -3272,6 +3277,75 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
"integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz",
"integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.3.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.3.0.tgz",
"integrity": "sha512-xI0c+a8xnKItAXCN8rZgCNCJQiVAd2Y7p9e2ND6zN3J3ekneu96qrePieJ7yA7073C1JxxoM3vH1RU7rYsaj8w==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.3.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz",
"integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.3.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz",
"integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.3.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/react-fontawesome": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
"integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
"react": ">=16.3"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@ -8160,7 +8234,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -8878,7 +8951,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@ -9044,8 +9116,7 @@
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/read-pkg": {
"version": "3.0.0",
@ -13591,6 +13662,51 @@
}
}
},
"@fortawesome/fontawesome-common-types": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
"integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg=="
},
"@fortawesome/fontawesome-svg-core": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz",
"integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.3.0"
}
},
"@fortawesome/free-brands-svg-icons": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.3.0.tgz",
"integrity": "sha512-xI0c+a8xnKItAXCN8rZgCNCJQiVAd2Y7p9e2ND6zN3J3ekneu96qrePieJ7yA7073C1JxxoM3vH1RU7rYsaj8w==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.3.0"
}
},
"@fortawesome/free-regular-svg-icons": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz",
"integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.3.0"
}
},
"@fortawesome/free-solid-svg-icons": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz",
"integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.3.0"
}
},
"@fortawesome/react-fontawesome": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
"integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
"requires": {
"prop-types": "^15.8.1"
}
},
"@humanwhocodes/config-array": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@ -17304,8 +17420,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"object-inspect": {
"version": "1.12.3",
@ -17812,7 +17927,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@ -17933,8 +18047,7 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"read-pkg": {
"version": "3.0.0",

View File

@ -52,6 +52,11 @@
},
"dependencies": {
"@dannadori/voice-changer-client-js": "^1.0.69",
"@fortawesome/fontawesome-svg-core": "^6.3.0",
"@fortawesome/free-brands-svg-icons": "^6.3.0",
"@fortawesome/free-regular-svg-icons": "^6.3.0",
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}

View File

@ -3,20 +3,52 @@ import { createRoot } from "react-dom/client";
import "./css/App.css"
import { useMemo, } 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, useAppRoot } from "./001_provider/001_AppRootProvider";
library.add(fas, far, fab);
const container = document.getElementById("app")!;
const root = createRoot(container);
const App = () => {
const appState = useAppState()
const { voiceChangerSetting } = useMicrophoneOptions()
const { voiceChangerSetting, clearSetting } = useMicrophoneOptions()
const titleRow = useMemo(() => {
return (
<div className="top-title">
<span className="title">Voice Changer Setting</span>
<span className="top-title-version">for v.1.5.x</span>
<span className="belongings">
<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>
<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>
</span>
<span className="belongings">
const onClearSettingClicked = async () => {
clearSetting()
location.reload()
}
</span>
</div>
)
}, [])
const clearRow = useMemo(() => {
const onClearSettingClicked = async () => {
await appState.clearSetting()
location.reload()
}
return (
<>
<div className="body-row split-3-3-4 left-padding-1">
@ -30,39 +62,41 @@ const App = () => {
)
}, [])
const mainSetting = useMemo(() => {
return (
<>
<div className="main-body">
{titleRow}
{clearRow}
{voiceChangerSetting}
</div>
</>
)
}, [voiceChangerSetting])
return (
<>
{mainSetting}
</>
)
}
const AppStateWrapper = () => {
const appRoot = useAppRoot()
if (!appRoot.audioContextState.audioContext) {
return <>please click window</>
}
return (
<div className="main-body">
<div className="body-row split-6-4">
<div className="body-top-title">
Voice Changer Setting
<span className="body-top-title-version">for v.1.5.x</span>
</div>
<div className="body-top-title-belongings">
<div className="belonging-item">
<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>
</div>
<div className="belonging-item">
<a className="link" href="https://zenn.dev/wok/articles/s01_vc001_top" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/help-circle.svg" />
<span>manual</span>
</a>
</div>
</div>
</div>
{clearRow}
{voiceChangerSetting}
<div>
<audio id="audio-output"></audio>
</div>
</div>
<AppStateProvider>
<App></App>
</AppStateProvider>
)
}
root.render(
<App></App>
<AppRootProvider>
<AppStateWrapper></AppStateWrapper>
</AppRootProvider>
);

View File

@ -0,0 +1,37 @@
import { useEffect, useState } from "react"
export type AudioConfigState = {
audioContext: AudioContext | null
}
export const useAudioConfig = (): AudioConfigState => {
const [audioContext, setAudioContext] = useState<AudioContext | null>(null)
useEffect(() => {
try {
const ctx = new AudioContext()
setAudioContext(ctx)
} catch (e) {
console.warn(e)
// If crate ctx failed, wait for user jesture.
const createAudioContext = () => {
console.log("click window")
const ctx = new AudioContext()
document.removeEventListener('touchstart', createAudioContext);
document.removeEventListener('mousedown', createAudioContext);
setAudioContext(ctx)
}
document.addEventListener('touchstart', createAudioContext);
document.addEventListener('mousedown', createAudioContext);
}
}, [])
console.log("AUDIO CONTEXT", audioContext)
const ret: AudioConfigState = {
audioContext
}
return ret
}

View File

@ -0,0 +1,28 @@
import { ClientState, useClient } from "@dannadori/voice-changer-client-js"
import { useEffect, useState } from "react"
import { AUDIO_ELEMENT_FOR_PLAY_RESULT } from "../const"
export type UseVCClientProps = {
audioContext: AudioContext
}
export type VCClientState = {
audioContext: AudioContext
clientState: ClientState
}
export const useVCClient = (props: UseVCClientProps) => {
const clientState = useClient({
audioContext: props.audioContext,
audioOutputElementId: AUDIO_ELEMENT_FOR_PLAY_RESULT
})
const ret: VCClientState = {
audioContext: props.audioContext,
clientState
}
return ret
}

View File

@ -0,0 +1,47 @@
import { useState } from "react"
import { StateControlCheckbox, useStateControlCheckbox } from "../hooks/useStateControlCheckbox";
import { OpenAdvancedSettingCheckbox, OpenConverterSettingCheckbox, OpenDeviceSettingCheckbox, OpenModelSettingCheckbox, OpenQualityControlCheckbox, OpenServerControlCheckbox, OpenSpeakerSettingCheckbox } from "../const"
export type StateControls = {
openServerControlCheckbox: StateControlCheckbox
openModelSettingCheckbox: StateControlCheckbox
openDeviceSettingCheckbox: StateControlCheckbox
openQualityControlCheckbox: StateControlCheckbox
openSpeakerSettingCheckbox: StateControlCheckbox
openConverterSettingCheckbox: StateControlCheckbox
openAdvancedSettingCheckbox: StateControlCheckbox
}
type FrontendManagerState = {
stateControls: StateControls
};
export type FrontendManagerStateAndMethod = FrontendManagerState & {
}
export const useFrontendManager = (): FrontendManagerStateAndMethod => {
// (1) Controller Switch
const openServerControlCheckbox = useStateControlCheckbox(OpenServerControlCheckbox);
const openModelSettingCheckbox = useStateControlCheckbox(OpenModelSettingCheckbox);
const openDeviceSettingCheckbox = useStateControlCheckbox(OpenDeviceSettingCheckbox);
const openQualityControlCheckbox = useStateControlCheckbox(OpenQualityControlCheckbox);
const openSpeakerSettingCheckbox = useStateControlCheckbox(OpenSpeakerSettingCheckbox);
const openConverterSettingCheckbox = useStateControlCheckbox(OpenConverterSettingCheckbox);
const openAdvancedSettingCheckbox = useStateControlCheckbox(OpenAdvancedSettingCheckbox);
const returnValue = {
stateControls: {
openServerControlCheckbox,
openModelSettingCheckbox,
openDeviceSettingCheckbox,
openQualityControlCheckbox,
openSpeakerSettingCheckbox,
openConverterSettingCheckbox,
openAdvancedSettingCheckbox
}
};
return returnValue;
};

View File

@ -0,0 +1,30 @@
import React, { useContext } from "react";
import { ReactNode } from "react";
import { AudioConfigState, useAudioConfig } from "../001_globalHooks/001_useAudioConfig";
type Props = {
children: ReactNode;
};
type AppRootValue = {
audioContextState: AudioConfigState
}
const AppRootContext = React.createContext<AppRootValue | null>(null);
export const useAppRoot = (): AppRootValue => {
const state = useContext(AppRootContext);
if (!state) {
throw new Error("useAppState must be used within AppStateProvider");
}
return state;
};
export const AppRootProvider = ({ children }: Props) => {
const audioContextState = useAudioConfig()
const providerValue: AppRootValue = {
audioContextState
};
return <AppRootContext.Provider value={providerValue}>{children}</AppRootContext.Provider>;
};

View File

@ -0,0 +1,38 @@
import { ClientState } from "@dannadori/voice-changer-client-js";
import React, { useContext, useEffect } from "react";
import { ReactNode } from "react";
import { useVCClient, VCClientState } from "../001_globalHooks/001_useVCClient";
import { FrontendManagerStateAndMethod, useFrontendManager } from "../001_globalHooks/010_useFrontendManager";
import { useAppRoot } from "./001_AppRootProvider";
type Props = {
children: ReactNode;
};
type AppStateValue = ClientState & {
audioContext: AudioContext
frontendManagerState: FrontendManagerStateAndMethod;
}
const AppStateContext = React.createContext<AppStateValue | null>(null);
export const useAppState = (): AppStateValue => {
const state = useContext(AppStateContext);
if (!state) {
throw new Error("useAppState must be used within AppStateProvider");
}
return state;
};
export const AppStateProvider = ({ children }: Props) => {
const appRoot = useAppRoot()
const clientState = useVCClient({ audioContext: appRoot.audioContextState.audioContext! })
const frontendManagerState = useFrontendManager();
const providerValue: AppStateValue = {
audioContext: appRoot.audioContextState.audioContext!,
...clientState.clientState,
frontendManagerState
};
return <AppStateContext.Provider value={providerValue}>{children}</AppStateContext.Provider>;
};

View File

@ -1,54 +1,28 @@
import * as React from "react";
import { useEffect, useMemo, useState } from "react";
import { AUDIO_ELEMENT_FOR_PLAY_RESULT } from "./const";
import { useServerSettingArea } from "./101_server_setting";
import { useDeviceSetting } from "./102_device_setting";
import { useConvertSetting } from "./104_convert_setting";
import { useAdvancedSetting } from "./105_advanced_setting";
import { useSpeakerSetting } from "./103_speaker_setting";
import { useServerControl } from "./106_server_control";
import { useClient } from "@dannadori/voice-changer-client-js";
import { useQualityControl } from "./107_qulity_control";
import { useMemo } from "react";
import { useModelSettingArea } from "./102_model_setting";
import { useDeviceSetting } from "./103_device_setting";
import { useConvertSetting } from "./106_convert_setting";
import { useAdvancedSetting } from "./107_advanced_setting";
import { useSpeakerSetting } from "./105_speaker_setting";
import { useServerControl } from "./101_server_control";
import { useQualityControl } from "./104_qulity_control";
export const useMicrophoneOptions = () => {
const [audioContext, setAudioContext] = useState<AudioContext | null>(null)
const clientState = useClient({
audioContext: audioContext,
audioOutputElementId: AUDIO_ELEMENT_FOR_PLAY_RESULT
})
const serverSetting = useServerSettingArea({ clientState })
const deviceSetting = useDeviceSetting(audioContext, { clientState })
const speakerSetting = useSpeakerSetting({ clientState })
const convertSetting = useConvertSetting({ clientState })
const advancedSetting = useAdvancedSetting({ clientState })
const serverControl = useServerControl({ clientState })
const qualityControl = useQualityControl({ clientState })
const clearSetting = async () => {
await clientState.clearSetting()
}
useEffect(() => {
const createAudioContext = () => {
const ctx = new AudioContext({
sampleRate: 48000,
})
setAudioContext(ctx)
document.removeEventListener('touchstart', createAudioContext);
document.removeEventListener('mousedown', createAudioContext);
}
document.addEventListener('touchstart', createAudioContext);
document.addEventListener('mousedown', createAudioContext);
}, [])
const serverControl = useServerControl()
const modelSetting = useModelSettingArea()
const deviceSetting = useDeviceSetting()
const speakerSetting = useSpeakerSetting()
const convertSetting = useConvertSetting()
const advancedSetting = useAdvancedSetting()
const qualityControl = useQualityControl()
const voiceChangerSetting = useMemo(() => {
return (
<>
{serverControl.serverControl}
{serverSetting.serverSetting}
{modelSetting.modelSetting}
{deviceSetting.deviceSetting}
{qualityControl.qualityControl}
{speakerSetting.speakerSetting}
@ -57,16 +31,16 @@ export const useMicrophoneOptions = () => {
</>
)
}, [serverControl.serverControl,
serverSetting.serverSetting,
modelSetting.modelSetting,
deviceSetting.deviceSetting,
speakerSetting.speakerSetting,
convertSetting.convertSetting,
advancedSetting.advancedSetting,
qualityControl.qualityControl])
return {
voiceChangerSetting,
clearSetting
voiceChangerSetting
}
}

View File

@ -1,22 +1,34 @@
import React, { useMemo, useState } from "react"
import { ClientState } from "@dannadori/voice-changer-client-js";
import { useAppState } from "./001_provider/001_AppStateProvider";
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
export type UseServerControlProps = {
clientState: ClientState
}
export const useServerControl = (props: UseServerControlProps) => {
export const useServerControl = () => {
const appState = useAppState()
const [isStarted, setIsStarted] = useState<boolean>(false)
const accodionButton = useMemo(() => {
const accodionButtonProps: HeaderButtonProps = {
stateControlCheckbox: appState.frontendManagerState.stateControls.openServerControlCheckbox,
tooltip: "Open/Close",
onIcon: ["fas", "caret-up"],
offIcon: ["fas", "caret-up"],
animation: AnimationTypes.spinner,
tooltipClass: "tooltip-right",
};
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
}, []);
const startButtonRow = useMemo(() => {
const onStartClicked = async () => {
setIsStarted(true)
await props.clientState.clientSetting.start()
await appState.clientSetting.start()
}
const onStopClicked = async () => {
setIsStarted(false)
console.log("stop click1")
await props.clientState.clientSetting.stop()
await appState.clientSetting.stop()
console.log("stop click2")
}
const startClassName = isStarted ? "body-button-active" : "body-button-stanby"
@ -34,9 +46,8 @@ export const useServerControl = (props: UseServerControlProps) => {
<div className="body-input-container">
</div>
</div>
)
}, [isStarted, props.clientState.clientSetting.start, props.clientState.clientSetting.stop])
}, [isStarted, appState.clientSetting.start, appState.clientSetting.stop])
const performanceRow = useMemo(() => {
return (
@ -50,20 +61,20 @@ export const useServerControl = (props: UseServerControlProps) => {
</div>
<div className="body-row split-3-1-1-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1"></div>
<div className="body-item-text">{props.clientState.volume.toFixed(4)}</div>
<div className="body-item-text">{props.clientState.bufferingTime}</div>
<div className="body-item-text">{props.clientState.responseTime}</div>
<div className="body-item-text">{appState.volume.toFixed(4)}</div>
<div className="body-item-text">{appState.bufferingTime}</div>
<div className="body-item-text">{appState.responseTime}</div>
<div className="body-item-text"></div>
</div>
</>
)
}, [props.clientState.volume, props.clientState.bufferingTime, props.clientState.responseTime])
}, [appState.volume, appState.bufferingTime, appState.responseTime])
const infoRow = useMemo(() => {
const onReloadClicked = async () => {
const info = await props.clientState.getInfo()
const info = await appState.getInfo()
console.log("info", info)
}
return (
@ -71,9 +82,9 @@ export const useServerControl = (props: UseServerControlProps) => {
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Model Info:</div>
<div className="body-item-text">
<span className="body-item-text-item">{props.clientState.serverSetting.serverInfo?.configFile || ""}</span>
<span className="body-item-text-item">{props.clientState.serverSetting.serverInfo?.pyTorchModelFile || ""}</span>
<span className="body-item-text-item">{props.clientState.serverSetting.serverInfo?.onnxModelFile || ""}</span>
<span className="body-item-text-item">{appState.serverSetting.serverInfo?.configFile || ""}</span>
<span className="body-item-text-item">{appState.serverSetting.serverInfo?.pyTorchModelFile || ""}</span>
<span className="body-item-text-item">{appState.serverSetting.serverInfo?.onnxModelFile || ""}</span>
</div>
@ -83,21 +94,28 @@ export const useServerControl = (props: UseServerControlProps) => {
</div>
</>
)
}, [props.clientState.getInfo, props.clientState.serverSetting.serverInfo])
}, [appState.getInfo, appState.serverSetting.serverInfo])
const serverControl = useMemo(() => {
return (
<>
<div className="body-row split-3-7 left-padding-1">
<div className="body-sub-section-title">Server Control</div>
<div className="body-select-container">
{appState.frontendManagerState.stateControls.openServerControlCheckbox.trigger}
<div className="partition">
<div className="partition-header">
<span className="caret">
{accodionButton}
</span>
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openServerControlCheckbox.updateState(!appState.frontendManagerState.stateControls.openServerControlCheckbox.checked()) }}>
Server Control
</span>
</div>
<div className="partition-content">
{startButtonRow}
{performanceRow}
{infoRow}
</div>
</div>
{startButtonRow}
{performanceRow}
{infoRow}
</>
)
}, [startButtonRow, performanceRow, infoRow])
@ -105,7 +123,6 @@ export const useServerControl = (props: UseServerControlProps) => {
return {
serverControl,
}
}

View File

@ -1,18 +1,56 @@
import { OnnxExecutionProvider, Framework, fileSelector } from "@dannadori/voice-changer-client-js"
import React, { useState } from "react"
import { useMemo } from "react"
import { ClientState } from "@dannadori/voice-changer-client-js";
export type UseServerSettingProps = {
clientState: ClientState
}
import { useAppState } from "./001_provider/001_AppStateProvider";
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
export type ServerSettingState = {
serverSetting: JSX.Element;
modelSetting: JSX.Element;
}
export const useServerSettingArea = (props: UseServerSettingProps): ServerSettingState => {
export const useModelSettingArea = (): ServerSettingState => {
const appState = useAppState()
const [showPyTorch, setShowPyTorch] = useState<boolean>(true)
const [showDetail, setShowDetail] = useState<boolean>(true)
const accodionButton = useMemo(() => {
const accodionButtonProps: HeaderButtonProps = {
stateControlCheckbox: appState.frontendManagerState.stateControls.openModelSettingCheckbox,
tooltip: "Open/Close",
onIcon: ["fas", "caret-up"],
offIcon: ["fas", "caret-up"],
animation: AnimationTypes.spinner,
tooltipClass: "tooltip-right",
};
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
}, []);
// If already model is set, show summarry.
const settingDone = useMemo(() => {
if (
appState.serverSetting.fileUploadSetting.configFile?.filename && // Config file
(
appState.serverSetting.fileUploadSetting.onnxModel?.filename || // Model file
appState.serverSetting.fileUploadSetting.pyTorchModel?.filename
) &&
appState.clientSetting.setting.correspondences.length > 0 // Corresopondence file
) {
return true
} else {
false
}
}, [
appState.serverSetting.fileUploadSetting,
appState.clientSetting.setting.correspondences
])
const uploadeModelSummaryRow = useMemo(() => {
}, [settingDone, showDetail])
const uploadeModelRow = useMemo(() => {
const onPyTorchFileLoadClicked = async () => {
const file = await fileSelector("")
@ -20,16 +58,16 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
alert("モデルファイルの拡張子はpthである必要があります。")
return
}
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
appState.serverSetting.setFileUploadSetting({
...appState.serverSetting.fileUploadSetting,
pyTorchModel: {
file: file
}
})
}
const onPyTorchFileClearClicked = () => {
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
appState.serverSetting.setFileUploadSetting({
...appState.serverSetting.fileUploadSetting,
pyTorchModel: null
})
}
@ -39,16 +77,16 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
alert("モデルファイルの拡張子はjsonである必要があります。")
return
}
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
appState.serverSetting.setFileUploadSetting({
...appState.serverSetting.fileUploadSetting,
configFile: {
file: file
}
})
}
const onConfigFileClearClicked = () => {
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
appState.serverSetting.setFileUploadSetting({
...appState.serverSetting.fileUploadSetting,
configFile: null
})
}
@ -58,39 +96,39 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
alert("モデルファイルの拡張子はonnxである必要があります。")
return
}
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
appState.serverSetting.setFileUploadSetting({
...appState.serverSetting.fileUploadSetting,
onnxModel: {
file: file
}
})
}
const onOnnxFileClearClicked = () => {
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
appState.serverSetting.setFileUploadSetting({
...appState.serverSetting.fileUploadSetting,
onnxModel: null
})
}
const onCorrespondenceFileLoadClicked = async () => {
const file = await fileSelector("")
props.clientState.clientSetting.setCorrespondences(file)
appState.clientSetting.setCorrespondences(file)
}
const onCorrespondenceFileClearClicked = () => {
props.clientState.clientSetting.setCorrespondences(null)
appState.clientSetting.setCorrespondences(null)
}
const onModelUploadClicked = async () => {
props.clientState.serverSetting.loadModel()
appState.serverSetting.loadModel()
}
const uploadButtonClassName = props.clientState.serverSetting.isUploading ? "body-button-disabled" : "body-button"
const uploadButtonAction = props.clientState.serverSetting.isUploading ? () => { } : onModelUploadClicked
const uploadButtonLabel = props.clientState.serverSetting.isUploading ? "wait..." : "upload"
const uploadButtonClassName = appState.serverSetting.isUploading ? "body-button-disabled" : "body-button"
const uploadButtonAction = appState.serverSetting.isUploading ? () => { } : onModelUploadClicked
const uploadButtonLabel = appState.serverSetting.isUploading ? "wait..." : "upload"
const configFilenameText = props.clientState.serverSetting.fileUploadSetting.configFile?.filename || props.clientState.serverSetting.fileUploadSetting.configFile?.file?.name || ""
const onnxModelFilenameText = props.clientState.serverSetting.fileUploadSetting.onnxModel?.filename || props.clientState.serverSetting.fileUploadSetting.onnxModel?.file?.name || ""
const pyTorchFilenameText = props.clientState.serverSetting.fileUploadSetting.pyTorchModel?.filename || props.clientState.serverSetting.fileUploadSetting.pyTorchModel?.file?.name || ""
const correspondenceFileText = props.clientState.clientSetting.setting.correspondences ? JSON.stringify(props.clientState.clientSetting.setting.correspondences.map(x => { return x.dirname })) : ""
const configFilenameText = appState.serverSetting.fileUploadSetting.configFile?.filename || appState.serverSetting.fileUploadSetting.configFile?.file?.name || ""
const onnxModelFilenameText = appState.serverSetting.fileUploadSetting.onnxModel?.filename || appState.serverSetting.fileUploadSetting.onnxModel?.file?.name || ""
const pyTorchFilenameText = appState.serverSetting.fileUploadSetting.pyTorchModel?.filename || appState.serverSetting.fileUploadSetting.pyTorchModel?.file?.name || ""
const correspondenceFileText = appState.clientSetting.setting.correspondences ? JSON.stringify(appState.clientSetting.setting.correspondences.map(x => { return x.dirname })) : ""
return (
<>
@ -161,7 +199,7 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-2"></div>
<div className="body-item-text">
{props.clientState.serverSetting.isUploading ? `uploading.... ${props.clientState.serverSetting.uploadProgress}%` : ""}
{appState.serverSetting.isUploading ? `uploading.... ${appState.serverSetting.uploadProgress}%` : ""}
</div>
<div className="body-button-container">
<div className={uploadButtonClassName} onClick={uploadButtonAction}>{uploadButtonLabel}</div>
@ -170,22 +208,22 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
</>
)
}, [
props.clientState.serverSetting.fileUploadSetting,
props.clientState.serverSetting.loadModel,
props.clientState.serverSetting.isUploading,
props.clientState.serverSetting.uploadProgress,
props.clientState.clientSetting.setting.correspondences,
appState.serverSetting.fileUploadSetting,
appState.serverSetting.loadModel,
appState.serverSetting.isUploading,
appState.serverSetting.uploadProgress,
appState.clientSetting.setting.correspondences,
showPyTorch])
const frameworkRow = useMemo(() => {
const onFrameworkChanged = async (val: Framework) => {
props.clientState.serverSetting.setFramework(val)
appState.serverSetting.setFramework(val)
}
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Framework</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.serverSetting.setting.framework} onChange={(e) => {
<select className="body-select" value={appState.serverSetting.setting.framework} onChange={(e) => {
onFrameworkChanged(e.target.value as
Framework)
}}>
@ -198,20 +236,20 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
</div>
</div>
)
}, [props.clientState.serverSetting.setting.framework, props.clientState.serverSetting.setFramework])
}, [appState.serverSetting.setting.framework, appState.serverSetting.setFramework])
const onnxExecutionProviderRow = useMemo(() => {
if (props.clientState.serverSetting.setting.framework != "ONNX") {
if (appState.serverSetting.setting.framework != "ONNX") {
return
}
const onOnnxExecutionProviderChanged = async (val: OnnxExecutionProvider) => {
props.clientState.serverSetting.setOnnxExecutionProvider(val)
appState.serverSetting.setOnnxExecutionProvider(val)
}
return (
<div className="body-row split-3-7 left-padding-1">
<div className="body-item-title left-padding-2">OnnxExecutionProvider</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.serverSetting.setting.onnxExecutionProvider} onChange={(e) => {
<select className="body-select" value={appState.serverSetting.setting.onnxExecutionProvider} onChange={(e) => {
onOnnxExecutionProviderChanged(e.target.value as
OnnxExecutionProvider)
}}>
@ -224,25 +262,34 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
</div>
</div>
)
}, [props.clientState.serverSetting.setting.framework, props.clientState.serverSetting.setting.onnxExecutionProvider, props.clientState.serverSetting.setOnnxExecutionProvider])
}, [appState.serverSetting.setting.framework, appState.serverSetting.setting.onnxExecutionProvider, appState.serverSetting.setOnnxExecutionProvider])
const serverSetting = useMemo(() => {
const modelSetting = useMemo(() => {
return (
<>
<div className="body-row split-3-7 left-padding-1">
<div className="body-sub-section-title">Server Setting</div>
<div className="body-select-container">
{appState.frontendManagerState.stateControls.openModelSettingCheckbox.trigger}
<div className="partition">
<div className="partition-header">
<span className="caret">
{accodionButton}
</span>
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openModelSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openModelSettingCheckbox.checked()) }}>
Model Setting
</span>
</div>
<div className="partition-content">
{uploadeModelRow}
{frameworkRow}
{onnxExecutionProviderRow}
</div>
</div>
{uploadeModelRow}
{frameworkRow}
{onnxExecutionProviderRow}
</>
)
}, [uploadeModelRow, frameworkRow, onnxExecutionProviderRow])
return {
serverSetting,
modelSetting,
}
}

View File

@ -1,7 +1,8 @@
import { fileSelectorAsDataURL, useIndexedDB } from "@dannadori/voice-changer-client-js"
import React, { useEffect, useMemo, useRef, useState } from "react"
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_OUTPUT } from "./const"
import { ClientState } from "@dannadori/voice-changer-client-js";
import { useAppState } from "./001_provider/001_AppStateProvider";
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
const reloadDevices = async () => {
@ -38,15 +39,25 @@ const reloadDevices = async () => {
// })
return [audioInputs, audioOutputs]
}
export type UseDeviceSettingProps = {
clientState: ClientState
}
export type DeviceSettingState = {
deviceSetting: JSX.Element;
}
export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDeviceSettingProps): DeviceSettingState => {
export const useDeviceSetting = (): DeviceSettingState => {
const appState = useAppState()
const accodionButton = useMemo(() => {
const accodionButtonProps: HeaderButtonProps = {
stateControlCheckbox: appState.frontendManagerState.stateControls.openDeviceSettingCheckbox,
tooltip: "Open/Close",
onIcon: ["fas", "caret-up"],
offIcon: ["fas", "caret-up"],
animation: AnimationTypes.spinner,
tooltipClass: "tooltip-right",
};
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
}, []);
const [inputAudioDeviceInfo, setInputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
@ -69,16 +80,16 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
}, [])
useEffect(() => {
if (typeof props.clientState.clientSetting.setting.audioInput == "string") {
if (typeof appState.clientSetting.setting.audioInput == "string") {
if (inputAudioDeviceInfo.find(x => {
// console.log("COMPARE:", x.deviceId, props.clientState.clientSetting.setting.audioInput)
return x.deviceId == props.clientState.clientSetting.setting.audioInput
// console.log("COMPARE:", x.deviceId, appState.clientSetting.setting.audioInput)
return x.deviceId == appState.clientSetting.setting.audioInput
})) {
setAudioInputForGUI(props.clientState.clientSetting.setting.audioInput)
setAudioInputForGUI(appState.clientSetting.setting.audioInput)
}
}
}, [inputAudioDeviceInfo, props.clientState.clientSetting.setting.audioInput])
}, [inputAudioDeviceInfo, appState.clientSetting.setting.audioInput])
const audioInputRow = useMemo(() => {
return (
@ -97,20 +108,16 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
</div>
</div>
)
}, [inputAudioDeviceInfo, audioInputForGUI, props.clientState.clientSetting.setting.audioInput])
}, [inputAudioDeviceInfo, audioInputForGUI, appState.clientSetting.setting.audioInput])
useEffect(() => {
if (!audioContext) {
return
}
if (audioInputForGUI == "file") {
// file selector (audioMediaInputRow)
} else {
props.clientState.clientSetting.setAudioInput(audioInputForGUI)
appState.clientSetting.setAudioInput(audioInputForGUI)
}
}, [audioContext, audioInputForGUI, props.clientState.clientSetting.setAudioInput])
}, [appState.audioContext, audioInputForGUI, appState.clientSetting.setAudioInput])
const audioMediaInputRow = useMemo(() => {
if (audioInputForGUI != "file") {
@ -127,15 +134,15 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
audio.src = url
await audio.play()
if (!audioSrcNode.current) {
audioSrcNode.current = audioContext!.createMediaElementSource(audio);
audioSrcNode.current = appState.audioContext!.createMediaElementSource(audio);
}
if (audioSrcNode.current.mediaElement != audio) {
audioSrcNode.current = audioContext!.createMediaElementSource(audio);
audioSrcNode.current = appState.audioContext!.createMediaElementSource(audio);
}
const dst = audioContext!.createMediaStreamDestination()
const dst = appState.audioContext.createMediaStreamDestination()
audioSrcNode.current.connect(dst)
props.clientState.clientSetting.setAudioInput(dst.stream)
appState.clientSetting.setAudioInput(dst.stream)
const audio_echo = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) as HTMLAudioElement
audio_echo.srcObject = dst.stream
@ -173,7 +180,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
</div>
</div>
)
}, [audioInputForGUI, props.clientState.clientSetting.setAudioInput, fileInputEchoback])
}, [audioInputForGUI, appState.clientSetting.setAudioInput, fileInputEchoback])
@ -204,11 +211,11 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
// }
const onOutputRecordStartClicked = async () => {
setOutputRecordingStarted(true)
await props.clientState.workletSetting.startOutputRecording()
await appState.workletSetting.startOutputRecording()
}
const onOutputRecordStopClicked = async () => {
setOutputRecordingStarted(false)
await props.clientState.workletSetting.stopOutputRecording()
await appState.workletSetting.stopOutputRecording()
}
const startClassName = outputRecordingStarted ? "body-button-active" : "body-button-stanby"
@ -226,7 +233,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
</div>
)
}, [audioOutputForGUI, outputRecordingStarted, props.clientState.workletSetting.startOutputRecording, props.clientState.workletSetting.stopOutputRecording])
}, [audioOutputForGUI, outputRecordingStarted, appState.workletSetting.startOutputRecording, appState.workletSetting.stopOutputRecording])
useEffect(() => {
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => {
@ -277,15 +284,25 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
const deviceSetting = useMemo(() => {
return (
<>
<div className="body-row split-3-7 left-padding-1">
<div className="body-sub-section-title">Device Setting</div>
<div className="body-select-container">
{appState.frontendManagerState.stateControls.openDeviceSettingCheckbox.trigger}
<div className="partition">
<div className="partition-header">
<span className="caret">
{accodionButton}
</span>
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openDeviceSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openDeviceSettingCheckbox.checked()) }}>
Device Setting
</span>
</div>
<div className="partition-content">
{audioInputRow}
{audioMediaInputRow}
{audioOutputRow}
{audioOutputRecordingRow}
</div>
</div>
{audioInputRow}
{audioMediaInputRow}
{audioOutputRow}
{audioOutputRecordingRow}
</>
)
}, [audioInputRow, audioMediaInputRow, audioOutputRow, audioOutputRecordingRow])
@ -293,10 +310,10 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
// 出力の録音データ(from worklet)がストアされたら実行
useEffect(() => {
if (!props.clientState.outputRecordData || props.clientState.outputRecordData?.length == 0) {
if (!appState.outputRecordData || appState.outputRecordData?.length == 0) {
return
}
const f32Datas = props.clientState.outputRecordData
const f32Datas = appState.outputRecordData
const sampleSize = f32Datas.reduce((prev, cur) => {
return prev + cur.length
}, 0)
@ -353,7 +370,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, [props.clientState.outputRecordData])
}, [appState.outputRecordData])
return {
deviceSetting,

View File

@ -1,177 +0,0 @@
import React, { useEffect, useMemo, useState } from "react"
import { ClientState } from "@dannadori/voice-changer-client-js";
export type UseSpeakerSettingProps = {
clientState: ClientState
}
export const useSpeakerSetting = (props: UseSpeakerSettingProps) => {
const [editSpeakerTargetId, setEditSpeakerTargetId] = useState<number>(0)
const [editSpeakerTargetName, setEditSpeakerTargetName] = useState<string>("")
useEffect(() => {
const src = props.clientState.clientSetting.setting.correspondences?.find(x => {
return x.sid == props.clientState.serverSetting.setting.srcId
})
const dst = props.clientState.clientSetting.setting.correspondences?.find(x => {
return x.sid == props.clientState.serverSetting.setting.dstId
})
const recommendedF0Factor = dst && src ? dst.correspondence / src.correspondence : 0
props.clientState.serverSetting.setF0Factor(recommendedF0Factor)
}, [props.clientState.serverSetting.setting.srcId, props.clientState.serverSetting.setting.dstId])
const srcIdRow = useMemo(() => {
const selected = props.clientState.clientSetting.setting.correspondences?.find(x => {
return x.sid == props.clientState.serverSetting.setting.srcId
})
return (
<div className="body-row split-3-2-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Source Speaker Id</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.serverSetting.setting.srcId} onChange={(e) => {
props.clientState.serverSetting.setSrcId(Number(e.target.value))
}}>
{
// props.clientState.clientSetting.setting.speakers.map(x => {
// return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
// })
props.clientState.clientSetting.setting.correspondences?.map(x => {
return <option key={x.sid} value={x.sid}>{x.dirname}({x.sid})</option>
})
}
</select>
</div>
<div className="body-item-text">
<div>F0: {selected?.correspondence.toFixed(1) || ""}</div>
</div>
<div className="body-item-text"></div>
</div>
)
}, [props.clientState.clientSetting.setting.speakers, props.clientState.serverSetting.setting.srcId, props.clientState.clientSetting.setting.correspondences, props.clientState.serverSetting.setSrcId])
const dstIdRow = useMemo(() => {
const selected = props.clientState.clientSetting.setting.correspondences?.find(x => {
return x.sid == props.clientState.serverSetting.setting.dstId
})
return (
<div className="body-row split-3-2-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Destination Speaker Id</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.serverSetting.setting.dstId} onChange={(e) => {
props.clientState.serverSetting.setDstId(Number(e.target.value))
}}>
{
// props.clientState.clientSetting.setting.speakers.map(x => {
// return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
// })
props.clientState.clientSetting.setting.correspondences?.map(x => {
return <option key={x.sid} value={x.sid}>{x.dirname}({x.sid})</option>
})
}
</select>
</div>
<div className="body-item-text">
<div>F0: {selected?.correspondence.toFixed(1) || ""}</div>
</div>
<div className="body-item-text"></div>
</div>
)
}, [props.clientState.clientSetting.setting.speakers, props.clientState.serverSetting.setting.dstId, props.clientState.clientSetting.setting.correspondences, props.clientState.serverSetting.setDstId])
const editSpeakerIdMappingRow = useMemo(() => {
const onSetSpeakerMappingClicked = async () => {
const targetId = editSpeakerTargetId
const targetName = editSpeakerTargetName
const targetSpeaker = props.clientState.clientSetting.setting.speakers.find(x => { return x.id == targetId })
if (targetSpeaker) {
if (targetName.length == 0) { // Delete
const newSpeakers = props.clientState.clientSetting.setting.speakers.filter(x => { return x.id != targetId })
props.clientState.clientSetting.setSpeakers(newSpeakers)
} else { // Update
targetSpeaker.name = targetName
props.clientState.clientSetting.setSpeakers([...props.clientState.clientSetting.setting.speakers])
}
} else {
if (targetName.length == 0) { // Noop
} else {// add
props.clientState.clientSetting.setting.speakers.push({
id: targetId,
name: targetName
})
props.clientState.clientSetting.setSpeakers([...props.clientState.clientSetting.setting.speakers])
}
}
}
return (
<div className="body-row split-3-1-2-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Edit Speaker Mapping</div>
<div className="body-input-container">
<input type="number" min={1} max={256} step={1} value={editSpeakerTargetId} onChange={(e) => {
const id = Number(e.target.value)
setEditSpeakerTargetId(id)
setEditSpeakerTargetName(props.clientState.clientSetting.setting.speakers.find(x => { return x.id == id })?.name || "")
}} />
</div>
<div className="body-input-container">
<input type="text" value={editSpeakerTargetName} onChange={(e) => {
setEditSpeakerTargetName(e.target.value)
}} />
</div>
<div className="body-button-container">
<div className="body-button" onClick={onSetSpeakerMappingClicked}>set</div>
</div>
</div>
)
}, [props.clientState.clientSetting.setting.speakers, editSpeakerTargetId, editSpeakerTargetName])
const f0FactorRow = useMemo(() => {
const src = props.clientState.clientSetting.setting.correspondences?.find(x => {
return x.sid == props.clientState.serverSetting.setting.srcId
})
const dst = props.clientState.clientSetting.setting.correspondences?.find(x => {
return x.sid == props.clientState.serverSetting.setting.dstId
})
const recommendedF0Factor = dst && src ? dst.correspondence / src.correspondence : 0
return (
<div className="body-row split-3-2-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">F0 Factor</div>
<div className="body-input-container">
<input type="range" className="body-item-input-slider" min="0.1" max="5.0" step="0.1" value={props.clientState.serverSetting.setting.f0Factor} onChange={(e) => {
props.clientState.serverSetting.setF0Factor(Number(e.target.value))
}}></input>
<span className="body-item-input-slider-val">{props.clientState.serverSetting.setting.f0Factor.toFixed(1)}</span>
</div>
<div className="body-item-text"></div>
<div className="body-item-text">recommend: {recommendedF0Factor.toFixed(1)}</div>
</div>
)
}, [props.clientState.serverSetting.setting.f0Factor, props.clientState.serverSetting.setting.srcId, props.clientState.serverSetting.setting.dstId, props.clientState.clientSetting.setting.correspondences, props.clientState.serverSetting.setF0Factor])
const speakerSetting = useMemo(() => {
return (
<>
<div className="body-row split-3-7 left-padding-1">
<div className="body-sub-section-title">Speaker Setting</div>
<div className="body-select-container">
</div>
</div>
{srcIdRow}
{dstIdRow}
{/* {editSpeakerIdMappingRow} */}
{f0FactorRow}
</>
)
}, [srcIdRow, dstIdRow, editSpeakerIdMappingRow, f0FactorRow])
return {
speakerSetting,
}
}

View File

@ -1,66 +0,0 @@
import React, { useMemo } from "react"
import { ClientState } from "@dannadori/voice-changer-client-js";
export type UseConvertSettingProps = {
clientState: ClientState
}
export type ConvertSettingState = {
convertSetting: JSX.Element;
}
export const useConvertSetting = (props: UseConvertSettingProps): ConvertSettingState => {
const inputChunkNumRow = useMemo(() => {
return (
<div className="body-row split-3-2-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Input Chunk Num(128sample/chunk)</div>
<div className="body-input-container">
<input type="number" min={1} max={256} step={1} value={props.clientState.clientSetting.setting.inputChunkNum} onChange={(e) => {
props.clientState.clientSetting.setInputChunkNum(Number(e.target.value))
}} />
</div>
<div className="body-item-text">
<div>buff: {(props.clientState.clientSetting.setting.inputChunkNum * 128 * 1000 / 24000).toFixed(1)}ms</div>
</div>
<div className="body-item-text"></div>
</div>
)
}, [props.clientState.clientSetting.setting.inputChunkNum, props.clientState.clientSetting.setInputChunkNum])
const gpuRow = useMemo(() => {
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">GPU</div>
<div className="body-input-container">
<input type="number" min={-2} max={5} step={1} value={props.clientState.serverSetting.setting.gpu} onChange={(e) => {
props.clientState.serverSetting.setGpu(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.serverSetting.setting.gpu, props.clientState.serverSetting.setGpu])
const convertSetting = useMemo(() => {
return (
<>
<div className="body-row split-3-7 left-padding-1">
<div className="body-sub-section-title">Converter Setting</div>
<div className="body-select-container">
</div>
</div>
{inputChunkNumRow}
{gpuRow}
</>
)
}, [inputChunkNumRow, gpuRow])
return {
convertSetting,
}
}

View File

@ -1,11 +1,9 @@
import { BufferSize, DownSamplingMode, F0Detector, Protocol, SampleRate, VoiceChangerMode } from "@dannadori/voice-changer-client-js"
import { F0Detector } from "@dannadori/voice-changer-client-js"
import React, { useEffect, useMemo, useState } from "react"
import { ClientState } from "@dannadori/voice-changer-client-js";
import { useAppState } from "./001_provider/001_AppStateProvider";
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
export type UseQualityControlProps = {
clientState: ClientState
}
export type QualityControlState = {
qualityControl: JSX.Element;
@ -24,8 +22,20 @@ const reloadDevices = async () => {
}
export const useQualityControl = (props: UseQualityControlProps): QualityControlState => {
const [showQualityControl, setShowQualityControl] = useState<boolean>(false)
export const useQualityControl = (): QualityControlState => {
const appState = useAppState()
const accodionButton = useMemo(() => {
const accodionButtonProps: HeaderButtonProps = {
stateControlCheckbox: appState.frontendManagerState.stateControls.openQualityControlCheckbox,
tooltip: "Open/Close",
onIcon: ["fas", "caret-up"],
offIcon: ["fas", "caret-up"],
animation: AnimationTypes.spinner,
tooltipClass: "tooltip-right",
};
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
}, []);
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none")
useEffect(() => {
@ -42,18 +52,18 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
<div className="body-row split-3-2-2-2-1 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">Noise Suppression</div>
<div>
<input type="checkbox" checked={props.clientState.clientSetting.setting.echoCancel} onChange={(e) => {
props.clientState.clientSetting.setEchoCancel(e.target.checked)
<input type="checkbox" checked={appState.clientSetting.setting.echoCancel} onChange={(e) => {
appState.clientSetting.setEchoCancel(e.target.checked)
}} /> echo cancel
</div>
<div>
<input type="checkbox" checked={props.clientState.clientSetting.setting.noiseSuppression} onChange={(e) => {
props.clientState.clientSetting.setNoiseSuppression(e.target.checked)
<input type="checkbox" checked={appState.clientSetting.setting.noiseSuppression} onChange={(e) => {
appState.clientSetting.setNoiseSuppression(e.target.checked)
}} /> suppression1
</div>
<div>
<input type="checkbox" checked={props.clientState.clientSetting.setting.noiseSuppression2} onChange={(e) => {
props.clientState.clientSetting.setNoiseSuppression2(e.target.checked)
<input type="checkbox" checked={appState.clientSetting.setting.noiseSuppression2} onChange={(e) => {
appState.clientSetting.setNoiseSuppression2(e.target.checked)
}} /> suppression2
</div>
<div className="body-button-container">
@ -61,9 +71,9 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
</div>
)
}, [
props.clientState.clientSetting.setting.echoCancel, props.clientState.clientSetting.setEchoCancel,
props.clientState.clientSetting.setting.noiseSuppression, props.clientState.clientSetting.setNoiseSuppression,
props.clientState.clientSetting.setting.noiseSuppression2, props.clientState.clientSetting.setNoiseSuppression2,
appState.clientSetting.setting.echoCancel, appState.clientSetting.setEchoCancel,
appState.clientSetting.setting.noiseSuppression, appState.clientSetting.setNoiseSuppression,
appState.clientSetting.setting.noiseSuppression2, appState.clientSetting.setNoiseSuppression2,
])
const gainControlRow = useMemo(() => {
@ -72,25 +82,25 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
<div className="body-item-title left-padding-1 ">Gain Control</div>
<div>
<span className="body-item-input-slider-label">in</span>
<input type="range" className="body-item-input-slider" min="0.0" max="1.0" step="0.1" value={props.clientState.clientSetting.setting.inputGain} onChange={(e) => {
props.clientState.clientSetting.setInputGain(Number(e.target.value))
<input type="range" className="body-item-input-slider" min="0.0" max="1.0" step="0.1" value={appState.clientSetting.setting.inputGain} onChange={(e) => {
appState.clientSetting.setInputGain(Number(e.target.value))
}}></input>
<span className="body-item-input-slider-val">{props.clientState.clientSetting.setting.inputGain}</span>
<span className="body-item-input-slider-val">{appState.clientSetting.setting.inputGain}</span>
</div>
<div>
<span className="body-item-input-slider-label">out</span>
<input type="range" className="body-item-input-slider" min="0.0" max="1.0" step="0.1" value={props.clientState.clientSetting.setting.outputGain} onChange={(e) => {
props.clientState.clientSetting.setOutputGain(Number(e.target.value))
<input type="range" className="body-item-input-slider" min="0.0" max="1.0" step="0.1" value={appState.clientSetting.setting.outputGain} onChange={(e) => {
appState.clientSetting.setOutputGain(Number(e.target.value))
}}></input>
<span className="body-item-input-slider-val">{props.clientState.clientSetting.setting.outputGain}</span>
<span className="body-item-input-slider-val">{appState.clientSetting.setting.outputGain}</span>
</div>
<div className="body-button-container">
</div>
</div>
)
}, [
props.clientState.clientSetting.setting.inputGain, props.clientState.clientSetting.setting.inputGain,
props.clientState.clientSetting.setting.outputGain, props.clientState.clientSetting.setOutputGain,
appState.clientSetting.setting.inputGain, appState.clientSetting.setting.inputGain,
appState.clientSetting.setting.outputGain, appState.clientSetting.setOutputGain,
])
const f0DetectorRow = useMemo(() => {
@ -99,8 +109,8 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">F0 Detector</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.serverSetting.setting.f0Detector} onChange={(e) => {
props.clientState.serverSetting.setF0Detector(e.target.value as F0Detector)
<select className="body-select" value={appState.serverSetting.setting.f0Detector} onChange={(e) => {
appState.serverSetting.setF0Detector(e.target.value as F0Detector)
}}>
{
Object.values(F0Detector).map(x => {
@ -112,12 +122,12 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
</div>
</div>
)
}, [props.clientState.serverSetting.setting.f0Detector, props.clientState.serverSetting.setF0Detector])
}, [appState.serverSetting.setting.f0Detector, appState.serverSetting.setF0Detector])
const recordIORow = useMemo(() => {
const setReocrdIO = async (val: number) => {
await props.clientState.serverSetting.setRecordIO(val)
await appState.serverSetting.setRecordIO(val)
if (val == 0) {
const imageContainer = document.getElementById("quality-control-analyze-image-container") as HTMLDivElement
imageContainer.innerHTML = ""
@ -149,7 +159,7 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">recordIO</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.serverSetting.setting.recordIO} onChange={(e) => {
<select className="body-select" value={appState.serverSetting.setting.recordIO} onChange={(e) => {
setReocrdIO(Number(e.target.value))
}}>
{
@ -199,10 +209,9 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
</div>
</>
)
}, [props.clientState.serverSetting.setting.recordIO, props.clientState.serverSetting.setRecordIO, outputAudioDeviceInfo, audioOutputForGUI])
}, [appState.serverSetting.setting.recordIO, appState.serverSetting.setRecordIO, outputAudioDeviceInfo, audioOutputForGUI])
const QualityControlContent = useMemo(() => {
if (!showQualityControl) return <></>
return (
<>
{noiseControlRow}
@ -211,24 +220,30 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
{recordIORow}
</>
)
}, [showQualityControl, gainControlRow, noiseControlRow, f0DetectorRow, recordIORow,])
}, [gainControlRow, noiseControlRow, f0DetectorRow, recordIORow])
const qualityControl = useMemo(() => {
return (
<>
<div className="body-row split-3-7 left-padding-1">
<div className="body-sub-section-title">Quality Control</div>
<div>
<input type="checkbox" checked={showQualityControl} onChange={(e) => {
setShowQualityControl(e.target.checked)
}} /> show
{appState.frontendManagerState.stateControls.openQualityControlCheckbox.trigger}
<div className="partition">
<div className="partition-header">
<span className="caret">
{accodionButton}
</span>
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openQualityControlCheckbox.updateState(!appState.frontendManagerState.stateControls.openQualityControlCheckbox.checked()) }}>
Quality Control
</span>
</div>
<div className="partition-content">
{QualityControlContent}
</div>
</div>
{QualityControlContent}
</>
)
}, [showQualityControl, QualityControlContent])
}, [QualityControlContent])
return {
qualityControl,

View File

@ -0,0 +1,195 @@
import React, { useEffect, useMemo, useState } from "react"
import { useAppState } from "./001_provider/001_AppStateProvider";
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
export const useSpeakerSetting = () => {
const appState = useAppState()
const accodionButton = useMemo(() => {
const accodionButtonProps: HeaderButtonProps = {
stateControlCheckbox: appState.frontendManagerState.stateControls.openSpeakerSettingCheckbox,
tooltip: "Open/Close",
onIcon: ["fas", "caret-up"],
offIcon: ["fas", "caret-up"],
animation: AnimationTypes.spinner,
tooltipClass: "tooltip-right",
};
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
}, []);
const [editSpeakerTargetId, setEditSpeakerTargetId] = useState<number>(0)
const [editSpeakerTargetName, setEditSpeakerTargetName] = useState<string>("")
useEffect(() => {
const src = appState.clientSetting.setting.correspondences?.find(x => {
return x.sid == appState.serverSetting.setting.srcId
})
const dst = appState.clientSetting.setting.correspondences?.find(x => {
return x.sid == appState.serverSetting.setting.dstId
})
const recommendedF0Factor = dst && src ? dst.correspondence / src.correspondence : 0
appState.serverSetting.setF0Factor(recommendedF0Factor)
}, [appState.serverSetting.setting.srcId, appState.serverSetting.setting.dstId])
const srcIdRow = useMemo(() => {
const selected = appState.clientSetting.setting.correspondences?.find(x => {
return x.sid == appState.serverSetting.setting.srcId
})
return (
<div className="body-row split-3-2-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Source Speaker Id</div>
<div className="body-select-container">
<select className="body-select" value={appState.serverSetting.setting.srcId} onChange={(e) => {
appState.serverSetting.setSrcId(Number(e.target.value))
}}>
{
// appState.clientSetting.setting.speakers.map(x => {
// return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
// })
appState.clientSetting.setting.correspondences?.map(x => {
return <option key={x.sid} value={x.sid}>{x.dirname}({x.sid})</option>
})
}
</select>
</div>
<div className="body-item-text">
<div>F0: {selected?.correspondence.toFixed(1) || ""}</div>
</div>
<div className="body-item-text"></div>
</div>
)
}, [appState.clientSetting.setting.speakers, appState.serverSetting.setting.srcId, appState.clientSetting.setting.correspondences, appState.serverSetting.setSrcId])
const dstIdRow = useMemo(() => {
const selected = appState.clientSetting.setting.correspondences?.find(x => {
return x.sid == appState.serverSetting.setting.dstId
})
return (
<div className="body-row split-3-2-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Destination Speaker Id</div>
<div className="body-select-container">
<select className="body-select" value={appState.serverSetting.setting.dstId} onChange={(e) => {
appState.serverSetting.setDstId(Number(e.target.value))
}}>
{
// appState.clientSetting.setting.speakers.map(x => {
// return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
// })
appState.clientSetting.setting.correspondences?.map(x => {
return <option key={x.sid} value={x.sid}>{x.dirname}({x.sid})</option>
})
}
</select>
</div>
<div className="body-item-text">
<div>F0: {selected?.correspondence.toFixed(1) || ""}</div>
</div>
<div className="body-item-text"></div>
</div>
)
}, [appState.clientSetting.setting.speakers, appState.serverSetting.setting.dstId, appState.clientSetting.setting.correspondences, appState.serverSetting.setDstId])
const editSpeakerIdMappingRow = useMemo(() => {
const onSetSpeakerMappingClicked = async () => {
const targetId = editSpeakerTargetId
const targetName = editSpeakerTargetName
const targetSpeaker = appState.clientSetting.setting.speakers.find(x => { return x.id == targetId })
if (targetSpeaker) {
if (targetName.length == 0) { // Delete
const newSpeakers = appState.clientSetting.setting.speakers.filter(x => { return x.id != targetId })
appState.clientSetting.setSpeakers(newSpeakers)
} else { // Update
targetSpeaker.name = targetName
appState.clientSetting.setSpeakers([...appState.clientSetting.setting.speakers])
}
} else {
if (targetName.length == 0) { // Noop
} else {// add
appState.clientSetting.setting.speakers.push({
id: targetId,
name: targetName
})
appState.clientSetting.setSpeakers([...appState.clientSetting.setting.speakers])
}
}
}
return (
<div className="body-row split-3-1-2-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Edit Speaker Mapping</div>
<div className="body-input-container">
<input type="number" min={1} max={256} step={1} value={editSpeakerTargetId} onChange={(e) => {
const id = Number(e.target.value)
setEditSpeakerTargetId(id)
setEditSpeakerTargetName(appState.clientSetting.setting.speakers.find(x => { return x.id == id })?.name || "")
}} />
</div>
<div className="body-input-container">
<input type="text" value={editSpeakerTargetName} onChange={(e) => {
setEditSpeakerTargetName(e.target.value)
}} />
</div>
<div className="body-button-container">
<div className="body-button" onClick={onSetSpeakerMappingClicked}>set</div>
</div>
</div>
)
}, [appState.clientSetting.setting.speakers, editSpeakerTargetId, editSpeakerTargetName])
const f0FactorRow = useMemo(() => {
const src = appState.clientSetting.setting.correspondences?.find(x => {
return x.sid == appState.serverSetting.setting.srcId
})
const dst = appState.clientSetting.setting.correspondences?.find(x => {
return x.sid == appState.serverSetting.setting.dstId
})
const recommendedF0Factor = dst && src ? dst.correspondence / src.correspondence : 0
return (
<div className="body-row split-3-2-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">F0 Factor</div>
<div className="body-input-container">
<input type="range" className="body-item-input-slider" min="0.1" max="5.0" step="0.1" value={appState.serverSetting.setting.f0Factor} onChange={(e) => {
appState.serverSetting.setF0Factor(Number(e.target.value))
}}></input>
<span className="body-item-input-slider-val">{appState.serverSetting.setting.f0Factor.toFixed(1)}</span>
</div>
<div className="body-item-text"></div>
<div className="body-item-text">recommend: {recommendedF0Factor.toFixed(1)}</div>
</div>
)
}, [appState.serverSetting.setting.f0Factor, appState.serverSetting.setting.srcId, appState.serverSetting.setting.dstId, appState.clientSetting.setting.correspondences, appState.serverSetting.setF0Factor])
const speakerSetting = useMemo(() => {
return (
<>
{appState.frontendManagerState.stateControls.openSpeakerSettingCheckbox.trigger}
<div className="partition">
<div className="partition-header">
<span className="caret">
{accodionButton}
</span>
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openSpeakerSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openSpeakerSettingCheckbox.checked()) }}>
Speaker Setting
</span>
</div>
<div className="partition-content">
{srcIdRow}
{dstIdRow}
{f0FactorRow}
</div>
</div>
</>
)
}, [srcIdRow, dstIdRow, editSpeakerIdMappingRow, f0FactorRow])
return {
speakerSetting,
}
}

View File

@ -0,0 +1,84 @@
import React, { useMemo } from "react"
import { useAppState } from "./001_provider/001_AppStateProvider";
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
export type ConvertSettingState = {
convertSetting: JSX.Element;
}
export const useConvertSetting = (): ConvertSettingState => {
const appState = useAppState()
const accodionButton = useMemo(() => {
const accodionButtonProps: HeaderButtonProps = {
stateControlCheckbox: appState.frontendManagerState.stateControls.openConverterSettingCheckbox,
tooltip: "Open/Close",
onIcon: ["fas", "caret-up"],
offIcon: ["fas", "caret-up"],
animation: AnimationTypes.spinner,
tooltipClass: "tooltip-right",
};
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
}, []);
const inputChunkNumRow = useMemo(() => {
return (
<div className="body-row split-3-2-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Input Chunk Num(128sample/chunk)</div>
<div className="body-input-container">
<input type="number" min={1} max={256} step={1} value={appState.clientSetting.setting.inputChunkNum} onChange={(e) => {
appState.clientSetting.setInputChunkNum(Number(e.target.value))
}} />
</div>
<div className="body-item-text">
<div>buff: {(appState.clientSetting.setting.inputChunkNum * 128 * 1000 / 24000).toFixed(1)}ms</div>
</div>
<div className="body-item-text"></div>
</div>
)
}, [appState.clientSetting.setting.inputChunkNum, appState.clientSetting.setInputChunkNum])
const gpuRow = useMemo(() => {
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">GPU</div>
<div className="body-input-container">
<input type="number" min={-2} max={5} step={1} value={appState.serverSetting.setting.gpu} onChange={(e) => {
appState.serverSetting.setGpu(Number(e.target.value))
}} />
</div>
</div>
)
}, [appState.serverSetting.setting.gpu, appState.serverSetting.setGpu])
const convertSetting = useMemo(() => {
return (
<>
{appState.frontendManagerState.stateControls.openConverterSettingCheckbox.trigger}
<div className="partition">
<div className="partition-header">
<span className="caret">
{accodionButton}
</span>
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openConverterSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openConverterSettingCheckbox.checked()) }}>
Converter Setting
</span>
</div>
<div className="partition-content">
{inputChunkNumRow}
{gpuRow}
</div>
</div>
</>
)
}, [inputChunkNumRow, gpuRow])
return {
convertSetting,
}
}

View File

@ -1,45 +1,54 @@
import { BufferSize, DownSamplingMode, F0Detector, Protocol, SampleRate, VoiceChangerMode } from "@dannadori/voice-changer-client-js"
import { BufferSize, DownSamplingMode, Protocol, SampleRate, VoiceChangerMode } from "@dannadori/voice-changer-client-js"
import React, { useMemo, useState } from "react"
import { ClientState } from "@dannadori/voice-changer-client-js";
export type UseAdvancedSettingProps = {
clientState: ClientState
}
import { useAppState } from "./001_provider/001_AppStateProvider";
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
export type AdvancedSettingState = {
advancedSetting: JSX.Element;
}
export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSettingState => {
const [showAdvancedSetting, setShowAdvancedSetting] = useState<boolean>(false)
export const useAdvancedSetting = (): AdvancedSettingState => {
const appState = useAppState()
const accodionButton = useMemo(() => {
const accodionButtonProps: HeaderButtonProps = {
stateControlCheckbox: appState.frontendManagerState.stateControls.openAdvancedSettingCheckbox,
tooltip: "Open/Close",
onIcon: ["fas", "caret-up"],
offIcon: ["fas", "caret-up"],
animation: AnimationTypes.spinner,
tooltipClass: "tooltip-right",
};
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
}, []);
const mmvcServerUrlRow = useMemo(() => {
const onSetServerClicked = async () => {
const input = document.getElementById("mmvc-server-url") as HTMLInputElement
props.clientState.clientSetting.setServerUrl(input.value)
appState.clientSetting.setServerUrl(input.value)
}
return (
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">MMVC Server</div>
<div className="body-input-container">
<input type="text" defaultValue={props.clientState.clientSetting.setting.mmvcServerUrl} id="mmvc-server-url" className="body-item-input" />
<input type="text" defaultValue={appState.clientSetting.setting.mmvcServerUrl} id="mmvc-server-url" className="body-item-input" />
</div>
<div className="body-button-container">
<div className="body-button" onClick={onSetServerClicked}>set</div>
</div>
</div>
)
}, [props.clientState.clientSetting.setting.mmvcServerUrl, props.clientState.clientSetting.setServerUrl])
}, [appState.clientSetting.setting.mmvcServerUrl, appState.clientSetting.setServerUrl])
const protocolRow = useMemo(() => {
const onProtocolChanged = async (val: Protocol) => {
props.clientState.clientSetting.setProtocol(val)
appState.clientSetting.setProtocol(val)
}
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Protocol</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.clientSetting.setting.protocol} onChange={(e) => {
<select className="body-select" value={appState.clientSetting.setting.protocol} onChange={(e) => {
onProtocolChanged(e.target.value as
Protocol)
}}>
@ -52,7 +61,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
</div>
</div>
)
}, [props.clientState.clientSetting.setting.protocol, props.clientState.clientSetting.setProtocol])
}, [appState.clientSetting.setting.protocol, appState.clientSetting.setProtocol])
const sampleRateRow = useMemo(() => {
@ -60,8 +69,8 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Sample Rate</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.clientSetting.setting.sampleRate} onChange={(e) => {
props.clientState.clientSetting.setSampleRate(Number(e.target.value) as SampleRate)
<select className="body-select" value={appState.clientSetting.setting.sampleRate} onChange={(e) => {
appState.clientSetting.setSampleRate(Number(e.target.value) as SampleRate)
}}>
{
Object.values(SampleRate).map(x => {
@ -72,7 +81,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
</div>
</div>
)
}, [props.clientState.clientSetting.setting.sampleRate, props.clientState.clientSetting.setSampleRate])
}, [appState.clientSetting.setting.sampleRate, appState.clientSetting.setSampleRate])
const bufferSizeRow = useMemo(() => {
return (
@ -80,8 +89,8 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Buffer Size</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.clientSetting.setting.bufferSize} onChange={(e) => {
props.clientState.clientSetting.setBufferSize(Number(e.target.value) as BufferSize)
<select className="body-select" value={appState.clientSetting.setting.bufferSize} onChange={(e) => {
appState.clientSetting.setBufferSize(Number(e.target.value) as BufferSize)
}}>
{
Object.values(BufferSize).map(x => {
@ -92,7 +101,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
</div>
</div>
)
}, [props.clientState.clientSetting.setting.bufferSize, props.clientState.clientSetting.setBufferSize])
}, [appState.clientSetting.setting.bufferSize, appState.clientSetting.setBufferSize])
const convertChunkNumRow = useMemo(() => {
return (
@ -100,13 +109,13 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Convert Chunk Num(128sample/chunk)</div>
<div className="body-input-container">
<input type="number" min={1} max={256} step={1} value={props.clientState.serverSetting.setting.convertChunkNum} onChange={(e) => {
props.clientState.serverSetting.setConvertChunkNum(Number(e.target.value))
<input type="number" min={1} max={256} step={1} value={appState.serverSetting.setting.convertChunkNum} onChange={(e) => {
appState.serverSetting.setConvertChunkNum(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.serverSetting.setting.convertChunkNum, props.clientState.serverSetting.setConvertChunkNum])
}, [appState.serverSetting.setting.convertChunkNum, appState.serverSetting.setConvertChunkNum])
const minConvertSizeRow = useMemo(() => {
return (
@ -114,52 +123,52 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Min Convert Size(byte)</div>
<div className="body-input-container">
<input type="number" min={0} max={8196} step={8196} value={props.clientState.serverSetting.setting.minConvertSize} onChange={(e) => {
props.clientState.serverSetting.setMinConvertSize(Number(e.target.value))
<input type="number" min={0} max={8196} step={8196} value={appState.serverSetting.setting.minConvertSize} onChange={(e) => {
appState.serverSetting.setMinConvertSize(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.serverSetting.setting.minConvertSize, props.clientState.serverSetting.setMinConvertSize])
}, [appState.serverSetting.setting.minConvertSize, appState.serverSetting.setMinConvertSize])
const crossFadeOverlapRateRow = useMemo(() => {
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Cross Fade Overlap Rate</div>
<div className="body-input-container">
<input type="number" min={0.1} max={1} step={0.1} value={props.clientState.serverSetting.setting.crossFadeOverlapRate} onChange={(e) => {
props.clientState.serverSetting.setCrossFadeOverlapRate(Number(e.target.value))
<input type="number" min={0.1} max={1} step={0.1} value={appState.serverSetting.setting.crossFadeOverlapRate} onChange={(e) => {
appState.serverSetting.setCrossFadeOverlapRate(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.serverSetting.setting.crossFadeOverlapRate, props.clientState.serverSetting.setCrossFadeOverlapRate])
}, [appState.serverSetting.setting.crossFadeOverlapRate, appState.serverSetting.setCrossFadeOverlapRate])
const crossFadeOffsetRateRow = useMemo(() => {
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Cross Fade Offset Rate</div>
<div className="body-input-container">
<input type="number" min={0} max={1} step={0.1} value={props.clientState.serverSetting.setting.crossFadeOffsetRate} onChange={(e) => {
props.clientState.serverSetting.setCrossFadeOffsetRate(Number(e.target.value))
<input type="number" min={0} max={1} step={0.1} value={appState.serverSetting.setting.crossFadeOffsetRate} onChange={(e) => {
appState.serverSetting.setCrossFadeOffsetRate(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.serverSetting.setting.crossFadeOffsetRate, props.clientState.serverSetting.setCrossFadeOffsetRate])
}, [appState.serverSetting.setting.crossFadeOffsetRate, appState.serverSetting.setCrossFadeOffsetRate])
const crossFadeEndRateRow = useMemo(() => {
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Cross Fade End Rate</div>
<div className="body-input-container">
<input type="number" min={0} max={1} step={0.1} value={props.clientState.serverSetting.setting.crossFadeEndRate} onChange={(e) => {
props.clientState.serverSetting.setCrossFadeEndRate(Number(e.target.value))
<input type="number" min={0} max={1} step={0.1} value={appState.serverSetting.setting.crossFadeEndRate} onChange={(e) => {
appState.serverSetting.setCrossFadeEndRate(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.serverSetting.setting.crossFadeEndRate, props.clientState.serverSetting.setCrossFadeEndRate])
}, [appState.serverSetting.setting.crossFadeEndRate, appState.serverSetting.setCrossFadeEndRate])
const voiceChangeModeRow = useMemo(() => {
@ -167,8 +176,8 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">Voice Change Mode</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.clientSetting.setting.voiceChangerMode} onChange={(e) => {
props.clientState.clientSetting.setVoiceChangerMode(e.target.value as VoiceChangerMode)
<select className="body-select" value={appState.clientSetting.setting.voiceChangerMode} onChange={(e) => {
appState.clientSetting.setVoiceChangerMode(e.target.value as VoiceChangerMode)
}}>
{
Object.values(VoiceChangerMode).map(x => {
@ -179,7 +188,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
</div>
</div>
)
}, [props.clientState.clientSetting.setting.voiceChangerMode, props.clientState.clientSetting.setVoiceChangerMode])
}, [appState.clientSetting.setting.voiceChangerMode, appState.clientSetting.setVoiceChangerMode])
const downSamplingModeRow = useMemo(() => {
@ -187,8 +196,8 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">DownSamplingMode</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.clientSetting.setting.downSamplingMode} onChange={(e) => {
props.clientState.clientSetting.setDownSamplingMode(e.target.value as DownSamplingMode)
<select className="body-select" value={appState.clientSetting.setting.downSamplingMode} onChange={(e) => {
appState.clientSetting.setDownSamplingMode(e.target.value as DownSamplingMode)
}}>
{
Object.values(DownSamplingMode).map(x => {
@ -199,7 +208,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
</div>
</div>
)
}, [props.clientState.clientSetting.setting.downSamplingMode, props.clientState.clientSetting.setDownSamplingMode])
}, [appState.clientSetting.setting.downSamplingMode, appState.clientSetting.setDownSamplingMode])
const workletSettingRow = useMemo(() => {
@ -209,9 +218,9 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Trancate Num</div>
<div className="body-input-container">
<input type="number" min={5} max={300} step={1} value={props.clientState.workletSetting.setting.numTrancateTreshold} onChange={(e) => {
props.clientState.workletSetting.setSetting({
...props.clientState.workletSetting.setting,
<input type="number" min={5} max={300} step={1} value={appState.workletSetting.setting.numTrancateTreshold} onChange={(e) => {
appState.workletSetting.setSetting({
...appState.workletSetting.setting,
numTrancateTreshold: Number(e.target.value)
})
}} />
@ -222,9 +231,9 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
{/* <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Trancate Vol</div>
<div className="body-input-container">
<input type="number" min={0.0001} max={0.0009} step={0.0001} value={props.clientState.workletSetting.setting.volTrancateThreshold} onChange={(e) => {
props.clientState.workletSetting.setSetting({
...props.clientState.workletSetting.setting,
<input type="number" min={0.0001} max={0.0009} step={0.0001} value={appState.workletSetting.setting.volTrancateThreshold} onChange={(e) => {
appState.workletSetting.setSetting({
...appState.workletSetting.setting,
volTrancateThreshold: Number(e.target.value)
})
}} />
@ -233,9 +242,9 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Trancate Vol Length</div>
<div className="body-input-container">
<input type="number" min={16} max={128} step={1} value={props.clientState.workletSetting.setting.volTrancateLength} onChange={(e) => {
props.clientState.workletSetting.setSetting({
...props.clientState.workletSetting.setting,
<input type="number" min={16} max={128} step={1} value={appState.workletSetting.setting.volTrancateLength} onChange={(e) => {
appState.workletSetting.setSetting({
...appState.workletSetting.setting,
volTrancateLength: Number(e.target.value)
})
}} />
@ -243,11 +252,10 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
</div> */}
</>
)
}, [props.clientState.workletSetting.setting, props.clientState.workletSetting.setSetting])
}, [appState.workletSetting.setting, appState.workletSetting.setSetting])
const advanceSettingContent = useMemo(() => {
if (!showAdvancedSetting) return <></>
return (
<>
<div className="body-row divider"></div>
@ -272,24 +280,30 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
</>
)
}, [showAdvancedSetting, mmvcServerUrlRow, protocolRow, sampleRateRow, bufferSizeRow, convertChunkNumRow, minConvertSizeRow, crossFadeOverlapRateRow, crossFadeOffsetRateRow, crossFadeEndRateRow, voiceChangeModeRow, workletSettingRow, downSamplingModeRow])
}, [mmvcServerUrlRow, protocolRow, sampleRateRow, bufferSizeRow, convertChunkNumRow, minConvertSizeRow, crossFadeOverlapRateRow, crossFadeOffsetRateRow, crossFadeEndRateRow, voiceChangeModeRow, workletSettingRow, downSamplingModeRow])
const advancedSetting = useMemo(() => {
return (
<>
<div className="body-row split-3-7 left-padding-1">
<div className="body-sub-section-title">Advanced Setting</div>
<div>
<input type="checkbox" checked={showAdvancedSetting} onChange={(e) => {
setShowAdvancedSetting(e.target.checked)
}} /> show
{appState.frontendManagerState.stateControls.openAdvancedSettingCheckbox.trigger}
<div className="partition">
<div className="partition-header">
<span className="caret">
{accodionButton}
</span>
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openAdvancedSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openAdvancedSettingCheckbox.checked()) }}>
Advanced Setting
</span>
</div>
<div className="partition-content">
{advanceSettingContent}
</div>
</div>
{advanceSettingContent}
</>
)
}, [showAdvancedSetting, advanceSettingContent])
}, [advanceSettingContent])
return {
advancedSetting,

View File

@ -0,0 +1,37 @@
import { IconName, IconPrefix } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useMemo } from "react";
import { StateControlCheckbox } from "../hooks/useStateControlCheckbox";
export const AnimationTypes = {
colored: "colored",
spinner: "spinner",
} as const;
export type AnimationTypes = typeof AnimationTypes[keyof typeof AnimationTypes];
export type HeaderButtonProps = {
stateControlCheckbox: StateControlCheckbox;
tooltip: string;
onIcon: [IconPrefix, IconName];
offIcon: [IconPrefix, IconName];
animation: AnimationTypes;
tooltipClass?: string;
};
export const HeaderButton = (props: HeaderButtonProps) => {
const headerButton = useMemo(() => {
const tooltipClass = props.tooltipClass || "tooltip-bottom";
return (
<div className={`rotate-button-container ${tooltipClass}`} data-tooltip={props.tooltip}>
{props.stateControlCheckbox.trigger}
<label htmlFor={props.stateControlCheckbox.className} className="rotate-lable">
<div className={props.animation}>
<FontAwesomeIcon icon={props.onIcon} className="spin-on" />
<FontAwesomeIcon icon={props.offIcon} className="spin-off" />
</div>
</label>
</div>
);
}, []);
return headerButton;
};

View File

@ -6,4 +6,19 @@ export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted"
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback"
export const INDEXEDDB_KEY_AUDIO_OUTPUT = "INDEXEDDB_KEY_AUDIO_OUTPUT"
export const INDEXEDDB_KEY_AUDIO_OUTPUT = "INDEXEDDB_KEY_AUDIO_OUTPUT"
// State Control Checkbox
export const OpenServerControlCheckbox = "open-server-control-checkbox"
export const OpenModelSettingCheckbox = "open-model-setting-checkbox"
export const OpenDeviceSettingCheckbox = "open-device-setting-checkbox"
export const OpenQualityControlCheckbox = "open-quality-control-checkbox"
export const OpenSpeakerSettingCheckbox = "open-speaker-setting-checkbox"
export const OpenConverterSettingCheckbox = "open-converter-setting-checkbox"
export const OpenAdvancedSettingCheckbox = "open-advanced-setting-checkbox"

View File

@ -0,0 +1,70 @@
/* 前提条件 */
.rotate-button-container {
height: var(--header-height);
width: var(--header-height);
position: relative;
}
.rotate-button {
display: none;
}
.rotate-button ~ .rotate-lable {
padding: 2px;
position: absolute;
transition: all 0.3s;
cursor: pointer;
height: var(--header-height);
width: var(--header-height);
}
.rotate-button ~ .rotate-lable > * {
width: 100%;
height: 100%;
float: left;
transition: all 0.3s;
.spin-on {
width: 100%;
height: 100%;
display: none;
}
.spin-off {
width: 100%;
height: 100%;
display: blcok;
}
}
.rotate-button ~ .rotate-lable > .colored {
color: rgba(200, 200, 200, 0.8);
background: rgba(0, 0, 0, 1);
transition: all 0.3s;
.spin-on {
display: none;
}
.spin-off {
display: block;
}
}
.rotate-button:checked ~ .rotate-lable > .colored {
color: rgba(50, 240, 50, 0.8);
background: rgba(60, 60, 60, 1);
transition: all 0.3s;
.spin-on {
display: block;
}
.spin-off {
display: none;
}
}
.rotate-button:checked ~ .rotate-lable > .spinner {
width: 100%;
height: 100%;
transform: rotate(180deg);
transition: all 0.3s;
box-sizing: border-box;
.spin-on {
display: block;
}
.spin-off {
display: none;
}
}

View File

@ -1,6 +1,8 @@
@import url("https://fonts.googleapis.com/css2?family=Chicle&family=Poppins:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Yusei+Magic&display=swap");
@import "./101_RotatedButton.css";
:root {
--text-color: #333;
--company-color1: rgba(64, 119, 187, 1);
@ -11,7 +13,7 @@
--company-color3-alpha: rgba(255, 255, 255, 0.3);
--global-shadow-color: rgba(0, 0, 0, 0.4);
--sidebar-transition-time: 0.3s;
--sidebar-transition-time: 0.2s;
--sidebar-transition-time-quick: 0.1s;
--sidebar-transition-animation: ease-in-out;
@ -52,6 +54,8 @@ body {
height: 100%;
width: 100%;
}
/* Main + Section Partition*/
.main-body {
height: 100%;
width: 100%;
@ -60,9 +64,61 @@ body {
display: flex;
flex-direction: column;
font-size: 1rem;
user-select: none;
/* Title */
.top-title {
.title {
font-size: 3rem;
}
.top-title-version {
margin-left: 2rem;
font-size: 1.2rem;
background: linear-gradient(transparent 60%, yellow 30%);
}
.belongings {
margin-left: 1rem;
margin-right: 1rem;
.link {
margin-left: 1rem;
}
}
}
/* Partition */
.partition {
width: 100%;
.partition-header {
font-weight: 700;
color: rgb(71, 69, 69);
display: flex;
.caret {
width: 2rem;
}
.title {
font-size: 1.1rem;
}
}
.partition-content {
position: static;
overflow-y: hidden;
}
.row-split {
}
}
}
.body-row {
.state-control-checkbox:checked + .partition .partition-content {
max-height: 700px;
background: #fff;
transition: all var(--sidebar-transition-time) var(--sidebar-transition-animation);
}
.state-control-checkbox + .partition .partition-content {
max-height: 0px;
background: #eef;
transition: all var(--sidebar-transition-time) var(--sidebar-transition-animation);
}
/* ROW */
.split-6-4 {
display: flex;
width: 100%;
@ -332,32 +388,6 @@ body {
background-color: rgba(31, 42, 36, 0.1);
}
.body-top-title {
font-size: 3rem;
}
.body-top-title-version {
margin-left: 2rem;
font-size: 1.2rem;
background: linear-gradient(transparent 60%, yellow 30%);
}
.body-top-title-belongings {
display: flex;
align-items: flex-end;
justify-content: flex-start;
& > div {
margin-left: 10px;
margin-right: 10px;
}
& > .belonging-item {
& > .link {
text-decoration: none;
& > span {
font-size: small;
}
}
}
}
.body-section-title {
font-size: 1.5rem;
color: rgb(51, 49, 49);

View File

@ -0,0 +1,100 @@
import React, { useMemo, useRef } from "react";
import { useEffect } from "react";
export type StateControlCheckbox = {
trigger: JSX.Element;
updateState: (newVal: boolean) => void;
checked: () => boolean
className: string;
};
export const useStateControlCheckbox = (className: string, changeCallback?: (newVal: boolean) => void): StateControlCheckbox => {
const currentValForTriggerCallbackRef = useRef<boolean>(false);
// (4) トリガチェックボックス
const callback = useMemo(() => {
console.log("generate callback function", className);
return (newVal: boolean) => {
if (!changeCallback) {
return;
}
// 値が同じときはスルー (== 初期値(undefined)か、値が違ったのみ発火)
if (currentValForTriggerCallbackRef.current === newVal) {
return;
}
// 初期値(undefined)か、値が違ったのみ発火
currentValForTriggerCallbackRef.current = newVal;
changeCallback(currentValForTriggerCallbackRef.current);
};
}, []);
const trigger = useMemo(() => {
if (changeCallback) {
return (
<input
type="checkbox"
className={`${className} state-control-checkbox rotate-button`}
id={`${className}`}
onChange={(e) => {
callback(e.target.checked);
}}
/>
);
} else {
return <input type="checkbox" className={`${className} state-control-checkbox rotate-button`} id={`${className}`} />;
}
}, []);
const checked = useMemo(() => {
return () => {
const checkboxes = document.querySelectorAll(`.${className}`);
if (checkboxes.length == 0) {
return false
}
const box = checkboxes[0] as HTMLInputElement
return box.checked
}
}, []);
useEffect(() => {
const checkboxes = document.querySelectorAll(`.${className}`);
// (1) On/Off同期
checkboxes.forEach((x) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
x.onchange = (ev) => {
updateState(ev.target.checked);
};
});
// (2) 全エレメントoff
const removers = document.querySelectorAll(`.${className}-remover`);
removers.forEach((x) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
x.onclick = (ev) => {
if (ev.target.className.indexOf(`${className}-remover`) > 0) {
updateState(false);
}
};
});
}, []);
// (3) ステート変更
const updateState = useMemo(() => {
return (newVal: boolean) => {
const currentCheckboxes = document.querySelectorAll(`.${className}`);
currentCheckboxes.forEach((y) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
y.checked = newVal;
});
if (changeCallback) {
callback(newVal);
}
};
}, []);
return {
trigger,
updateState,
checked,
className,
};
};