323 lines
11 KiB
TypeScript
323 lines
11 KiB
TypeScript
import * as React from "react";
|
|
import {useContext, useState} from "react";
|
|
import {Registry} from "tc-shared/events";
|
|
import {
|
|
PrivateConversationInfo,
|
|
SideHeaderChannelState,
|
|
SideHeaderEvents,
|
|
SideHeaderPingInfo,
|
|
SideHeaderServerInfo,
|
|
SideHeaderState
|
|
} from "tc-shared/ui/frames/side/HeaderDefinitions";
|
|
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
|
import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
|
|
import {getIconManager} from "tc-shared/file/Icons";
|
|
import {tra} from "tc-shared/i18n/localize";
|
|
|
|
const StateContext = React.createContext<SideHeaderState>(undefined);
|
|
const EventsContext = React.createContext<Registry<SideHeaderEvents>>(undefined);
|
|
|
|
|
|
const cssStyle = require("./HeaderRenderer.scss");
|
|
|
|
const Block = (props: { children: [React.ReactElement, React.ReactElement], target: "left" | "right" }) => (
|
|
<div className={cssStyle.block + " " + cssStyle[props.target]}>
|
|
<div className={cssStyle.title}>{props.children[0]}</div>
|
|
{props.children[1]}
|
|
</div>
|
|
)
|
|
|
|
const ChannelStateRenderer = (props: { info: SideHeaderChannelState }) => {
|
|
if(props.info.state === "not-connected") {
|
|
return <div className={cssStyle.value} key={"not-connected"}><Translatable>Not connected</Translatable></div>;
|
|
} else {
|
|
let limit;
|
|
if(props.info.channelMaxUser === -1) {
|
|
limit = <Translatable key={"unlimited"}>Unlimited</Translatable>
|
|
} else {
|
|
limit = props.info.channelMaxUser;
|
|
}
|
|
|
|
let icon;
|
|
if(props.info.channelIcon.iconId !== 0) {
|
|
const remoteIcon = getIconManager().resolveIcon(props.info.channelIcon.iconId, props.info.channelIcon.serverUniqueId, props.info.channelIcon.handlerId);
|
|
icon = <RemoteIconRenderer icon={remoteIcon} className={cssStyle.icon} key={"icon-" + props.info.channelIcon.iconId} />;
|
|
}
|
|
|
|
return (
|
|
<React.Fragment key={"connected"}>
|
|
<div className={cssStyle.value}>{icon}{props.info.channelName}</div>
|
|
<div className={cssStyle.smallValue}>{props.info.channelUserCount} / {limit}</div>
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
const BlockChannelState = (props: { mode: "voice" | "text" }) => {
|
|
const events = useContext(EventsContext);
|
|
const [ info, setInfo ] = useState<SideHeaderChannelState>(() => {
|
|
events.fire("query_current_channel_state", { mode: props.mode });
|
|
return { state: "not-connected" };
|
|
});
|
|
|
|
events.reactUse("notify_current_channel_state", event => event.mode === props.mode && setInfo(event.state));
|
|
|
|
let title;
|
|
if(props.mode === "voice") {
|
|
title = <Translatable key={"voice"}>You're talking in Channel</Translatable>;
|
|
} else {
|
|
title = <Translatable key={"text"}>You're chatting in Channel</Translatable>;
|
|
}
|
|
|
|
return (
|
|
<Block target={"left"}>
|
|
{title}
|
|
<ChannelStateRenderer info={info} />
|
|
</Block>
|
|
);
|
|
}
|
|
|
|
const ServerInfoRenderer = React.memo((props: { info: SideHeaderServerInfo | undefined }) => {
|
|
if(!props.info) {
|
|
return <div className={cssStyle.value} key={"not-connected"}><Translatable>Not connected</Translatable></div>;
|
|
}
|
|
|
|
let icon;
|
|
if(props.info.icon.iconId !== 0) {
|
|
const remoteIcon = getIconManager().resolveIcon(props.info.icon.iconId, props.info.icon.serverUniqueId, props.info.icon.handlerId);
|
|
icon = <RemoteIconRenderer icon={remoteIcon} className={cssStyle.icon} key={"icon-" + props.info.icon.iconId} />;
|
|
}
|
|
|
|
return (
|
|
<div className={cssStyle.value} key={"connected"}>{icon}{props.info.name}</div>
|
|
);
|
|
});
|
|
|
|
const BlockServerState = () => {
|
|
const events = useContext(EventsContext);
|
|
const [ info, setInfo ] = useState<SideHeaderServerInfo | undefined>(() => {
|
|
events.fire("query_server_info");
|
|
return undefined;
|
|
});
|
|
|
|
events.reactUse("notify_server_info", event => setInfo(event.info));
|
|
return (
|
|
<Block target={"left"}>
|
|
<Translatable>You're chatting on Server</Translatable>
|
|
<ServerInfoRenderer info={info} />
|
|
</Block>
|
|
);
|
|
}
|
|
|
|
const BlockPing = () => {
|
|
const events = useContext(EventsContext);
|
|
const [ pingInfo, setPingInfo ] = useState<SideHeaderPingInfo>(() => {
|
|
events.fire("query_ping");
|
|
return undefined;
|
|
});
|
|
|
|
events.reactUse("notify_ping", event => setPingInfo(event.ping), undefined, [ ]);
|
|
|
|
let value, title;
|
|
if(!pingInfo) {
|
|
value = (
|
|
<div className={cssStyle.value} key={"not-connected"} title={tr("You're not connected to any server")}>
|
|
<Translatable>Not connected</Translatable>
|
|
</div>
|
|
);
|
|
} else {
|
|
let pingClass;
|
|
if(pingInfo.native <= 30) {
|
|
pingClass = cssStyle.veryGood;
|
|
} else if(pingInfo.native <= 50) {
|
|
pingClass = cssStyle.good;
|
|
} else if(pingInfo.native <= 90) {
|
|
pingClass = cssStyle.medium;
|
|
} else if(pingInfo.native <= 200) {
|
|
pingClass = cssStyle.poor;
|
|
} else {
|
|
pingClass = cssStyle.veryPoor;
|
|
}
|
|
|
|
if(pingInfo.javaScript === undefined) {
|
|
title = tra("Ping: {}ms", pingInfo.native.toFixed(3));
|
|
} else {
|
|
title = tra("Native: {}ms\nJavascript: {}ms", pingInfo.native.toFixed(3), pingInfo.javaScript.toFixed(3));
|
|
}
|
|
value = <div className={cssStyle.value + " " + cssStyle.ping + " " + pingClass} key={"ping"} title={title}>{pingInfo.native.toFixed(0)}ms</div>;
|
|
}
|
|
|
|
return (
|
|
<Block target={"right"}>
|
|
<Translatable>Your Ping</Translatable>
|
|
{value}
|
|
</Block>
|
|
);
|
|
};
|
|
|
|
const BlockPrivateChats = (props: { asButton: boolean }) => {
|
|
const events = useContext(EventsContext);
|
|
const [ info, setInfo ] = useState<PrivateConversationInfo>(() => {
|
|
events.fire("query_private_conversations");
|
|
return { unread: 0, open: 0 };
|
|
});
|
|
|
|
events.reactUse("notify_private_conversations", event => setInfo(event.info));
|
|
|
|
let body;
|
|
if(info.open === 0) {
|
|
body = <Translatable key={"no-conversations"}>No conversations</Translatable>;
|
|
} else if(info.open === 1) {
|
|
body = <Translatable key={"1-conversations"}>One conversation</Translatable>;
|
|
} else {
|
|
body = <VariadicTranslatable text={"{} conversations"} key={"n-conversations"}>{info.open}</VariadicTranslatable>;
|
|
}
|
|
|
|
let title;
|
|
if(info.unread === 0) {
|
|
title = <Translatable key={"zero"}>Private Chats</Translatable>;
|
|
} else {
|
|
title = (
|
|
<React.Fragment key={"unread"}>
|
|
<Translatable>Private Chats</Translatable>
|
|
<div className={cssStyle.containerIndicator}>
|
|
{info.unread}
|
|
</div>
|
|
</React.Fragment>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Block target={"right"}>
|
|
{title}
|
|
<div className={cssStyle.value + " " + (props.asButton ? cssStyle.button : "")} onClick={() => props.asButton && events.fire("action_open_conversation")}>
|
|
{body}
|
|
</div>
|
|
</Block>
|
|
);
|
|
}
|
|
|
|
const BlockButtonSwitchToChannelChat = () => {
|
|
const events = useContext(EventsContext);
|
|
return (
|
|
<Block target={"left"}>
|
|
<> </>
|
|
<div className={cssStyle.value + " " + cssStyle.button} onClick={() => events.fire("action_switch_channel_chat")}>
|
|
<Translatable>Switch to channel chat</Translatable>
|
|
</div>
|
|
</Block>
|
|
)
|
|
}
|
|
|
|
const BlockButtonOpenConversation = () => {
|
|
const events = useContext(EventsContext);
|
|
return (
|
|
<Block target={"right"}>
|
|
<> </>
|
|
<div className={cssStyle.value + " " + cssStyle.button} onClick={() => events.fire("action_open_conversation")}>
|
|
<Translatable>Open conversation</Translatable>
|
|
</div>
|
|
</Block>
|
|
)
|
|
}
|
|
|
|
const BlockButtonBotManage = () => {
|
|
const events = useContext(EventsContext);
|
|
return (
|
|
<Block target={"left"}>
|
|
<> </>
|
|
<div className={cssStyle.value + " " + cssStyle.button} onClick={() => events.fire("action_bot_manage")}>
|
|
<Translatable>Manage bot</Translatable>
|
|
</div>
|
|
</Block>
|
|
)
|
|
}
|
|
|
|
const BlockButtonBotSongAdd = () => {
|
|
const events = useContext(EventsContext);
|
|
return (
|
|
<Block target={"right"}>
|
|
<> </>
|
|
<div className={cssStyle.value + " " + cssStyle.button + " " + cssStyle.botAddSong} onClick={() => events.fire("action_bot_add_song")}>
|
|
<Translatable>Add song</Translatable>
|
|
</div>
|
|
</Block>
|
|
)
|
|
}
|
|
|
|
|
|
const BlockTopLeft = () => <BlockChannelState mode={"voice"} />;
|
|
const BlockTopRight = () => <BlockPing />;
|
|
|
|
const BlockBottomLeft = () => {
|
|
const state = useContext(StateContext);
|
|
|
|
switch (state.state) {
|
|
case "conversation":
|
|
if(state.mode === "private") {
|
|
return <BlockButtonSwitchToChannelChat key={"switch-channel-chat"} />;
|
|
} else if(state.mode === "server") {
|
|
return <BlockServerState key={"server"} />
|
|
} else {
|
|
return <BlockChannelState mode={"text"} key={"text-state"} />;
|
|
}
|
|
|
|
case "music-bot":
|
|
return <BlockButtonBotManage key={"button-manage-bot"} />;
|
|
|
|
case "none":
|
|
case "client":
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const BlockBottomRight = () => {
|
|
const events = useContext(EventsContext);
|
|
const state = useContext(StateContext);
|
|
|
|
const [ ownClient, setOwnClient ] = useState(() => {
|
|
events.fire("query_client_info_own_client");
|
|
return false;
|
|
});
|
|
|
|
events.reactUse("notify_client_info_own_client", event => setOwnClient(event.isOwnClient));
|
|
|
|
switch (state.state) {
|
|
case "client":
|
|
if(ownClient) {
|
|
return null;
|
|
} else {
|
|
return <BlockButtonOpenConversation key={"button-open-conversation"} />;
|
|
}
|
|
|
|
case "conversation":
|
|
return <BlockPrivateChats key={"conversation"} asButton={state.mode !== "private"} />;
|
|
|
|
case "music-bot":
|
|
return <BlockButtonBotSongAdd key={"button-add-song"} />;
|
|
|
|
case "none":
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export const SideHeaderRenderer = React.memo((props: { events: Registry<SideHeaderEvents>, state: SideHeaderState }) => {
|
|
return (
|
|
<EventsContext.Provider value={props.events}>
|
|
<StateContext.Provider value={props.state}>
|
|
<div className={cssStyle.container}>
|
|
<div className={cssStyle.lane}>
|
|
<BlockTopLeft />
|
|
<BlockTopRight />
|
|
</div>
|
|
<div className={cssStyle.lane + " " + (props.state.state === "music-bot" ? cssStyle.musicBotInfo : "")}>
|
|
<BlockBottomLeft />
|
|
<BlockBottomRight />
|
|
</div>
|
|
</div>
|
|
</StateContext.Provider>
|
|
</EventsContext.Provider>
|
|
);
|
|
}) |