TeaWeb/shared/js/ui/frames/side/HeaderRenderer.tsx

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"}>
<>&nbsp;</>
<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"}>
<>&nbsp;</>
<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"}>
<>&nbsp;</>
<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"}>
<>&nbsp;</>
<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>
);
})