246 lines
No EOL
9.8 KiB
TypeScript
246 lines
No EOL
9.8 KiB
TypeScript
import {Registry} from "tc-shared/events";
|
|
import {
|
|
ConnectionComponent,
|
|
ConnectionStatus,
|
|
ConnectionStatusEvents
|
|
} from "tc-shared/ui/frames/footer/StatusDefinitions";
|
|
import * as React from "react";
|
|
import {useContext, useEffect, useRef, useState} from "react";
|
|
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
|
import {network} from "tc-shared/ui/frames/chat";
|
|
import {date_format} from "tc-shared/utils/DateUtils";
|
|
|
|
const cssStyle = require("./Renderer.scss");
|
|
export const StatusEvents = React.createContext<Registry<ConnectionStatusEvents>>(undefined);
|
|
|
|
const ConnectionStateRenderer = React.memo((props: { state: ConnectionStatus, isGeneral: boolean, onClick?: () => void }) => {
|
|
let statusClass;
|
|
let statusBody;
|
|
let title;
|
|
switch (props.state.type) {
|
|
case "disconnected":
|
|
title = tr("Not connected");
|
|
statusClass = cssStyle.disconnected;
|
|
statusBody = <Translatable key={"disconnected"}>Disconnected</Translatable>;
|
|
break;
|
|
|
|
case "connecting-signalling":
|
|
statusClass = cssStyle.connecting;
|
|
|
|
switch (props.state.state) {
|
|
case "initializing":
|
|
title = tr("Initializing connection");
|
|
statusBody = <Translatable key={"connecting-signalling-initializing"}>Initializing</Translatable>;
|
|
break;
|
|
|
|
case "connecting":
|
|
title = tr("Connecting to target");
|
|
statusBody = <Translatable key={"connecting-signalling-connecting"}>Connecting</Translatable>;
|
|
break;
|
|
|
|
case "authentication":
|
|
title = tr("Authenticating");
|
|
statusBody = <Translatable key={"connecting-signalling-authenticating"}>Authenticating</Translatable>;
|
|
break;
|
|
}
|
|
break;
|
|
case "connecting-voice":
|
|
title = tr("Establishing audio connection");
|
|
statusClass = cssStyle.connecting;
|
|
if(props.isGeneral) {
|
|
statusBody = <Translatable key={"connecting-voice-general"}>Audio connecting</Translatable>;
|
|
} else {
|
|
statusBody = <Translatable key={"connecting-voice-general"}>Connecting</Translatable>;
|
|
}
|
|
break;
|
|
|
|
case "connecting-video":
|
|
title = tr("Establishing video connection");
|
|
statusClass = cssStyle.connecting;
|
|
if(props.isGeneral) {
|
|
statusBody = <Translatable key={"connecting-video-general"}>Video connecting</Translatable>;
|
|
} else {
|
|
statusBody = <Translatable key={"connecting-video-general"}>Connecting</Translatable>;
|
|
}
|
|
break;
|
|
|
|
case "unhealthy":
|
|
title = props.state.reason;
|
|
statusClass = cssStyle.unhealthy;
|
|
statusBody = <Translatable key={"unhealthy"}>Unhealthy</Translatable>;
|
|
break;
|
|
|
|
case "healthy":
|
|
title = tr("Connection is healthy");
|
|
statusClass = cssStyle.healthy;
|
|
statusBody = <Translatable key={"healthy"}>Healthy</Translatable>;
|
|
break;
|
|
|
|
case "unsupported":
|
|
title = tr("Not supported");
|
|
statusClass = cssStyle.disconnected;
|
|
statusBody = <Translatable key={"not-supported"}>Not supported</Translatable>;
|
|
break;
|
|
|
|
default:
|
|
statusClass = cssStyle.unhealthy;
|
|
statusBody = <Translatable key={"invalid-state"}>Invalid state</Translatable>;
|
|
break;
|
|
}
|
|
|
|
return <div className={cssStyle.status + " " + statusClass} title={title} onClick={props.onClick}>{statusBody}</div>;
|
|
})
|
|
|
|
export const StatusTextRenderer = React.memo(() => {
|
|
const events = useContext(StatusEvents);
|
|
|
|
const [ status, setStatus ] = useState<ConnectionStatus>(() => {
|
|
events.fire("query_connection_status");
|
|
return { type: "disconnected" };
|
|
});
|
|
events.reactUse("notify_connection_status", event => setStatus(event.status), undefined, []);
|
|
|
|
return (
|
|
<div className={cssStyle.rtcStatus}>
|
|
<div><Translatable>Connection status:</Translatable></div>
|
|
<ConnectionStateRenderer
|
|
state={status}
|
|
isGeneral={true}
|
|
onClick={() => events.fire("action_toggle_component_detail")}
|
|
/>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const ComponentStatusRenderer = React.memo((props: { component: ConnectionComponent }) => {
|
|
const events = useContext(StatusEvents);
|
|
const [ status, setStatus ] = useState<ConnectionStatus>(() => {
|
|
events.fire("query_component_status", { component: props.component });
|
|
return { type: "disconnected" };
|
|
});
|
|
events.reactUse("notify_component_status", event => event.component === props.component && setStatus(event.status),
|
|
undefined, []);
|
|
|
|
let title;
|
|
switch (props.component) {
|
|
case "signaling":
|
|
title = <Translatable key={"control"}>Control</Translatable>;
|
|
break;
|
|
case "video":
|
|
title = <Translatable key={"video"}>Video</Translatable>;
|
|
break;
|
|
case "voice":
|
|
title = <Translatable key={"voice"}>Voice</Translatable>;
|
|
break;
|
|
}
|
|
|
|
let body;
|
|
switch (status.type) {
|
|
case "healthy":
|
|
body = (
|
|
<React.Fragment key={"healthy"}>
|
|
<div className={cssStyle.row}>
|
|
<div className={cssStyle.key}><Translatable>Incoming:</Translatable></div>
|
|
<div className={cssStyle.value}>{network.byteSizeToString(status.bytesReceived)}</div>
|
|
</div>
|
|
<div className={cssStyle.row}>
|
|
<div className={cssStyle.key}><Translatable>Outgoing:</Translatable></div>
|
|
<div className={cssStyle.value}>{network.byteSizeToString(status.bytesSend)}</div>
|
|
</div>
|
|
</React.Fragment>
|
|
);
|
|
break;
|
|
|
|
case "connecting-signalling":
|
|
case "connecting-voice":
|
|
case "connecting-video":
|
|
case "disconnected":
|
|
case "unsupported":
|
|
let text;
|
|
switch (props.component) {
|
|
case "signaling":
|
|
text = <Translatable key={"signalling"}>The control component is the main server connection. All actions are transceived by this connection.</Translatable>;
|
|
break;
|
|
case "video":
|
|
text = <Translatable key={"video"}>The video component transmits and receives video data. It's used to transmit camara and screen data.</Translatable>;
|
|
break;
|
|
case "voice":
|
|
text = <Translatable key={"voice"}>The voice component transmits and receives audio data.</Translatable>;
|
|
break;
|
|
}
|
|
body = (
|
|
<div className={cssStyle.row + " " + cssStyle.error} key={"description"}>
|
|
<div className={cssStyle.text}>{text}</div>
|
|
</div>
|
|
);
|
|
break;
|
|
|
|
case "unhealthy":
|
|
let errorText;
|
|
if(status.retryTimestamp) {
|
|
let time = Math.ceil((status.retryTimestamp - Date.now()) / 1000);
|
|
let minutes = Math.floor(time / 60);
|
|
let seconds = time % 60;
|
|
errorText = <VariadicTranslatable key={"retry"} text={"Error occurred. Retry in {}."}>{(minutes > 0 ? minutes + "m" : "") + seconds + "s"}</VariadicTranslatable>;
|
|
} else {
|
|
errorText = <Translatable key={"no-retry"}>Error occurred. No retry.</Translatable>;
|
|
}
|
|
body = (
|
|
<div className={cssStyle.row + " " + cssStyle.error} key={"error"}>
|
|
<div className={cssStyle.text + " " + cssStyle.errorRow}>{errorText}</div>
|
|
<div className={cssStyle.text + " " + cssStyle.error} title={status.reason}>{status.reason}</div>
|
|
</div>
|
|
);
|
|
break;
|
|
}
|
|
|
|
return (<>
|
|
<div className={cssStyle.row + " " + cssStyle.title}>
|
|
<div className={cssStyle.key}>{title}</div>
|
|
<div className={cssStyle.value}>
|
|
<ConnectionStateRenderer state={status} isGeneral={false} />
|
|
</div>
|
|
</div>
|
|
{body}
|
|
</>);
|
|
})
|
|
|
|
export const StatusDetailRenderer = () => {
|
|
const events = useContext(StatusEvents);
|
|
|
|
const refContainer = useRef<HTMLDivElement>();
|
|
const refShowId = useRef(0);
|
|
|
|
const [ shown, setShown ] = useState(() => {
|
|
events.fire("query_component_detail_state");
|
|
return false;
|
|
});
|
|
events.reactUse("notify_component_detail_state", event => {
|
|
if(event.shown) {
|
|
refShowId.current++;
|
|
}
|
|
setShown(event.shown);
|
|
});
|
|
|
|
useEffect(() => {
|
|
if(!shown) { return; }
|
|
|
|
const listener = (event: MouseEvent) => {
|
|
const target = event.target as HTMLDivElement;
|
|
if(!refContainer.current?.contains(target)) {
|
|
events.fire("action_toggle_component_detail", { shown: false });
|
|
}
|
|
};
|
|
document.addEventListener("click", listener);
|
|
return () => document.removeEventListener("click", listener);
|
|
}, [shown]);
|
|
|
|
return (
|
|
<div className={cssStyle.rtcStatusDetail + " " + (shown ? cssStyle.shown : "")} ref={refContainer}>
|
|
<div className={cssStyle.header}><Translatable>Connection Details</Translatable></div>
|
|
<ComponentStatusRenderer component={"signaling"} key={"rev-0-" + refShowId.current} />
|
|
<ComponentStatusRenderer component={"voice"} key={"rev-1-" + refShowId.current} />
|
|
<ComponentStatusRenderer component={"video"} key={"rev-2-" + refShowId.current} />
|
|
</div>
|
|
)
|
|
}; |