318 lines
No EOL
13 KiB
TypeScript
318 lines
No EOL
13 KiB
TypeScript
import * as React from "react";
|
|
import {useContext, useState} from "react";
|
|
import {IpcRegistryDescription, Registry} from "tc-shared/events";
|
|
import {EchoTestEvents, TestState, VoiceConnectionState} from "./Definitions";
|
|
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
|
import {ClientIcon} from "svg-sprites/client-icons";
|
|
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
|
|
import {Checkbox} from "tc-shared/ui/react-elements/Checkbox";
|
|
import {Button} from "tc-shared/ui/react-elements/Button";
|
|
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
|
import {AbstractModal} from "tc-shared/ui/react-elements/modal/Definitions";
|
|
|
|
const cssStyle = require("./Renderer.scss");
|
|
|
|
const EchoTestEventRegistry = React.createContext<Registry<EchoTestEvents>>(undefined);
|
|
|
|
const VoiceStateOverlay = () => {
|
|
const events = useContext(EchoTestEventRegistry);
|
|
|
|
const [state, setState] = useState<"loading" | VoiceConnectionState>(() => {
|
|
events.fire("query_voice_connection_state");
|
|
return "loading";
|
|
});
|
|
const [message, setMessage] = useState(undefined);
|
|
|
|
events.reactUse("notify_voice_connection_state", event => {
|
|
setState(event.state);
|
|
setMessage(event.message);
|
|
});
|
|
|
|
let inner, shown = true, error = false;
|
|
switch (state) {
|
|
case "failed":
|
|
error = true;
|
|
inner = <a key={state}>
|
|
<Translatable>Voice connection establishment has been failed:</Translatable><br />
|
|
{message}
|
|
</a>;
|
|
break;
|
|
|
|
case "disconnected":
|
|
inner = <a key={state}><Translatable>Voice connection has been disconnected.</Translatable></a>;
|
|
break;
|
|
|
|
case "unsupported-server":
|
|
inner = <a key={state}><Translatable>Voice connection isn't supported by the server.</Translatable></a>;
|
|
break;
|
|
|
|
case "unsupported-client":
|
|
inner = <a key={state}>
|
|
<Translatable>Voice connection isn't supported by your browser.</Translatable><br/>
|
|
<Translatable>Please use another browser.</Translatable>
|
|
</a>;
|
|
break;
|
|
|
|
case "connecting":
|
|
inner = <a key={state}><Translatable>establishing voice connection</Translatable> <LoadingDots/></a>;
|
|
break;
|
|
|
|
case "loading":
|
|
inner = <a key={state}><Translatable>loading</Translatable> <LoadingDots/></a>;
|
|
break;
|
|
|
|
case "connected":
|
|
shown = false;
|
|
break;
|
|
|
|
default:
|
|
shown = false;
|
|
}
|
|
|
|
return (
|
|
<div className={cssStyle.overlay + " " + (shown ? cssStyle.shown : "") + " " + (error ? cssStyle.error : "")}>
|
|
{inner}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const TestStateOverlay = () => {
|
|
const events = useContext(EchoTestEventRegistry);
|
|
|
|
const [state, setState] = useState<{ state: "loading" } | TestState>(() => {
|
|
events.fire("query_test_state");
|
|
return {state: "loading"};
|
|
});
|
|
|
|
const [voiceConnected, setVoiceConnected] = useState<"loading" | boolean>(() => {
|
|
return "loading";
|
|
});
|
|
|
|
events.reactUse("notify_voice_connection_state", event => setVoiceConnected(event.state === "connected"));
|
|
events.reactUse("notify_test_state", event => setState(event.state));
|
|
|
|
let inner;
|
|
switch (state.state) {
|
|
case "loading":
|
|
case "initializing":
|
|
inner = <a key={"initializing"}><Translatable>initializing</Translatable> <LoadingDots/></a>;
|
|
break;
|
|
|
|
case "start-failed":
|
|
inner = <a key={"initializing"}>
|
|
<VariadicTranslatable text={"Failed to start echo test:\n{0}"}>
|
|
{state.error}
|
|
</VariadicTranslatable>
|
|
<br/>
|
|
<Button type={"small"} color={"green"} onClick={() => events.fire("action_start_test")}><Translatable>Try
|
|
again</Translatable></Button>
|
|
</a>;
|
|
break;
|
|
|
|
case "unsupported":
|
|
inner = <a key={"initializing"}>
|
|
<Translatable>Echo testing hasn't been supported by the
|
|
server.</Translatable>
|
|
</a>;
|
|
break;
|
|
|
|
case "muted":
|
|
if(state.microphone && state.speaker) {
|
|
inner = <a key={"muted-microphone-speaker"}>
|
|
<Translatable>Your speaker and microphone have been muted.</Translatable>
|
|
<br/>
|
|
<Button type={"small"} color={"green"} onClick={() => events.fire("action_unmute")} transparency={false}>
|
|
<Translatable>Unmute speaker and microphone</Translatable>
|
|
</Button>
|
|
</a>;
|
|
} else if(state.microphone) {
|
|
inner = <a key={"muted-microphone"}>
|
|
<Translatable>Your microphone has been muted.</Translatable>
|
|
<br/>
|
|
<Button type={"small"} color={"green"} onClick={() => events.fire("action_unmute")} transparency={false}>
|
|
<Translatable>Unmute microphone</Translatable>
|
|
</Button>
|
|
</a>;
|
|
} else {
|
|
inner = <a key={"muted-speaker"}>
|
|
<Translatable>Your speaker has been muted.</Translatable>
|
|
<br/>
|
|
<Button type={"small"} color={"green"} onClick={() => events.fire("action_unmute")} transparency={false}>
|
|
<Translatable>Unmute speaker</Translatable>
|
|
</Button>
|
|
</a>;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return (
|
|
<div className={cssStyle.overlay + " " + (state.state !== "running" && voiceConnected ? cssStyle.shown : "")}>
|
|
{inner}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const TroubleshootingSoundOverlay = () => {
|
|
const events = useContext(EchoTestEventRegistry);
|
|
|
|
const [visible, setVisible] = useState(false);
|
|
|
|
events.reactUse("notify_test_phase", event => setVisible(event.phase === "troubleshooting"));
|
|
|
|
return (
|
|
<div className={cssStyle.overlay + " " + cssStyle.troubleshoot + " " + (visible ? cssStyle.shown : "")}>
|
|
<div className={cssStyle.top}>
|
|
<div className={cssStyle.containerIcon}>
|
|
<ClientIconRenderer icon={ClientIcon.MicrophoneBroken} className={cssStyle.icon}/>
|
|
</div>
|
|
<div className={cssStyle.help}>
|
|
<h1><Translatable>Troubleshooting guide</Translatable></h1>
|
|
<ol>
|
|
<li>
|
|
<h2><Translatable>Correct microphone selected?</Translatable>
|
|
<Button type={"extra-small"}
|
|
onClick={() => events.fire("action_open_microphone_settings")}>
|
|
<Translatable>Open Microphone settings</Translatable>
|
|
</Button>
|
|
</h2>
|
|
<p>
|
|
<Translatable>Check within the settings, if the right microphone has been
|
|
selected.</Translatable>
|
|
<Translatable>The indicators will show you any voice activity.</Translatable>
|
|
</p>
|
|
</li>
|
|
<li>
|
|
<h2><Translatable>Are any addons blocking the microphone access?</Translatable></h2>
|
|
<p>
|
|
<Translatable>Some addons might block the access to your microphone. Try to disable all
|
|
addons and reload the site.</Translatable>
|
|
</p>
|
|
</li>
|
|
<li>
|
|
<h2><Translatable>Has WebRTC been enabled?</Translatable></h2>
|
|
<p>
|
|
<VariadicTranslatable
|
|
text={"In some cases, WebRTC has been disabled. Click {0} to troubleshoot any WebRTC related issues."}>
|
|
<a href={"https://test.webrtc.org"} hrefLang={"en"}
|
|
target={"_blank"}><Translatable>here</Translatable></a>
|
|
</VariadicTranslatable>
|
|
</p>
|
|
</li>
|
|
<li>
|
|
<h2><Translatable>Reload the site</Translatable></h2>
|
|
<p>
|
|
<Translatable>In some cases, reloading the site will already solve the issue for
|
|
you.</Translatable>
|
|
</p>
|
|
</li>
|
|
<li>
|
|
<h2><Translatable>Nothing worked? Submit an issue</Translatable></h2>
|
|
<p>
|
|
<VariadicTranslatable text={"If still nothing worked, try to seek help in our {0}."}>
|
|
<a href={"https://forum.teaspeak.de"} hrefLang={"en"}
|
|
target={"_blank"}><Translatable>forum</Translatable></a>
|
|
</VariadicTranslatable>
|
|
<VariadicTranslatable text={"You can also create a new issue/bug report {0}."}>
|
|
<a href={"https://github.com/TeaSpeak/TeaWeb/issues"} hrefLang={"en"}
|
|
target={"_blank"}><Translatable>here</Translatable></a>
|
|
</VariadicTranslatable>
|
|
</p>
|
|
</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
<div className={cssStyle.buttons}>
|
|
<Button type={"small"} color={"red"}
|
|
onClick={() => events.fire("action_troubleshooting_finished", {status: "aborted"})}>
|
|
<Translatable>Abort test</Translatable>
|
|
</Button>
|
|
|
|
<Button type={"small"} color={"green"}
|
|
onClick={() => events.fire("action_troubleshooting_finished", {status: "test-again"})}>
|
|
<Translatable>Test again</Translatable>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const TestToggle = () => {
|
|
const events = useContext(EchoTestEventRegistry);
|
|
|
|
const [state, setState] = useState<"loading" | boolean>(() => {
|
|
events.fire("query_test_state");
|
|
return "loading";
|
|
});
|
|
|
|
events.reactUse("notify_tests_toggle", event => setState(event.enabled));
|
|
|
|
return (
|
|
<Checkbox
|
|
value={state === true}
|
|
disabled={state === "loading"}
|
|
onChange={() => events.fire("action_toggle_tests", {enabled: state === false})}
|
|
label={<Translatable>Show this on the next connect</Translatable>}
|
|
/>
|
|
)
|
|
}
|
|
|
|
const EchoTestModalRenderer = () => {
|
|
const events = useContext(EchoTestEventRegistry);
|
|
|
|
return (
|
|
<div className={cssStyle.container}>
|
|
<h1 className={cssStyle.header}>
|
|
<Translatable>Welcome to the private echo test. Can you hear yourself speaking?</Translatable>
|
|
</h1>
|
|
<div className={cssStyle.buttons}>
|
|
<div className={cssStyle.buttonContainer}>
|
|
<div className={cssStyle.button + " " + cssStyle.success} title={tr("Yes")}
|
|
onClick={() => events.fire("action_test_result", {status: "success"})}>
|
|
<ClientIconRenderer icon={ClientIcon.Apply} className={cssStyle.icon}/>
|
|
</div>
|
|
<a><Translatable>Yes</Translatable></a>
|
|
</div>
|
|
<div className={cssStyle.buttonContainer}>
|
|
<div className={cssStyle.button + " " + cssStyle.fail} title={tr("No")}
|
|
onClick={() => events.fire("action_test_result", {status: "fail"})}>
|
|
<ClientIconRenderer icon={ClientIcon.Delete} className={cssStyle.icon}/>
|
|
</div>
|
|
<a><Translatable>No</Translatable></a>
|
|
</div>
|
|
|
|
<VoiceStateOverlay/>
|
|
<TestStateOverlay/>
|
|
</div>
|
|
<div className={cssStyle.footer}>
|
|
<TestToggle/>
|
|
<Button color={"red"} type={"small"}
|
|
onClick={() => events.fire("action_close")}><Translatable>Close</Translatable></Button>
|
|
</div>
|
|
<TroubleshootingSoundOverlay/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
class ModalEchoTest extends AbstractModal {
|
|
private readonly events: Registry<EchoTestEvents>;
|
|
|
|
constructor(events: IpcRegistryDescription<EchoTestEvents>) {
|
|
super();
|
|
|
|
this.events = Registry.fromIpcDescription(events);
|
|
}
|
|
|
|
renderBody(): React.ReactElement {
|
|
return (
|
|
<EchoTestEventRegistry.Provider value={this.events}>
|
|
<EchoTestModalRenderer />
|
|
</EchoTestEventRegistry.Provider>
|
|
);
|
|
}
|
|
|
|
renderTitle(): string | React.ReactElement<Translatable> {
|
|
return <Translatable>Voice echo test</Translatable>;
|
|
}
|
|
}
|
|
|
|
export = ModalEchoTest; |