Reworked the side bar algorithm. This should heavily improve the memory footprint especially on muti connection sessions

canary
WolverinDEV 2020-12-09 18:45:11 +01:00
parent c7a01ce79a
commit 3d02669d20
16 changed files with 423 additions and 171 deletions

View File

@ -32,8 +32,8 @@ export class ConnectionManager {
private _container_log_server: JQuery;
private _container_channel_tree: JQuery;
private _container_hostbanner: JQuery;
private _container_chat: JQuery;
private containerChannelVideo: ReplaceableContainer;
private containerSideBar: HTMLDivElement;
private containerFooter: HTMLDivElement;
constructor() {
@ -41,10 +41,10 @@ export class ConnectionManager {
this.event_registry.enableDebug("connection-manager");
this.containerChannelVideo = new ReplaceableContainer(document.getElementById("channel-video") as HTMLDivElement);
this.containerSideBar = document.getElementById("chat") as HTMLDivElement;
this._container_log_server = $("#server-log");
this._container_channel_tree = $("#channelTree");
this._container_hostbanner = $("#hostbanner");
this._container_chat = $("#chat");
this.containerFooter = document.getElementById("container-footer") as HTMLDivElement;
this.set_active_connection(undefined);
@ -112,7 +112,6 @@ export class ConnectionManager {
private set_active_connection_(handler: ConnectionHandler) {
this._container_channel_tree.children().detach();
this._container_chat.children().detach();
this._container_log_server.children().detach();
this._container_hostbanner.children().detach();
this.containerChannelVideo.replaceWith(handler?.video_frame.getContainer());
@ -120,8 +119,8 @@ export class ConnectionManager {
if(handler) {
this._container_hostbanner.append(handler.hostbanner.html_tag);
this._container_channel_tree.append(handler.channelTree.tag_tree());
this._container_chat.append(handler.side_bar.html_tag());
this._container_log_server.append(handler.log.getHTMLTag());
handler.side_bar.renderInto(this.containerSideBar);
}
const old_handler = this.active_handler;
this.active_handler = handler;

View File

@ -369,6 +369,13 @@ export class Settings extends StaticSettings {
valueType: "boolean",
};
static readonly KEY_CHAT_HIGHLIGHT_CODE: ValuedSettingsKey<boolean> = {
key: 'chat_highlight_code',
defaultValue: true,
description: 'Enables code highlighting within the chat (Client restart required)',
valueType: "boolean",
};
static readonly KEY_CHAT_TAG_URLS: ValuedSettingsKey<boolean> = {
key: 'chat_tag_urls',
defaultValue: true,

View File

@ -11,6 +11,7 @@ import {rendererReact, rendererText} from "tc-shared/text/bbcode/renderer";
import {MenuEntryType, spawn_context_menu} from "tc-shared/ui/elements/ContextMenu";
import '!style-loader!css-loader!highlight.js/styles/darcula.css';
import {Settings, settings} from "tc-shared/settings";
const registerLanguage = (name, language: Promise<any>) => {
language.then(lan => hljs.registerLanguage(name, lan)).catch(error => {
@ -86,6 +87,9 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
function: async () => {
let reactId = 0;
if(!settings.static_global(Settings.KEY_CHAT_HIGHLIGHT_CODE)) {
return;
}
/* override default parser */
rendererReact.registerCustomRenderer(new class extends ElementRenderer<TagElement, React.ReactNode> {
tags(): string | string[] {

View File

@ -524,7 +524,7 @@ export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
conversation.setActiveClientEntry(this);
privateConversations.setSelectedConversation(conversation);
sideBar.showPrivateConversations();
sideBar.private_conversations().focusInput();
sideBar.privateConversationsController().focusInput();
}
showContextMenu(x: number, y: number, on_close: () => void = undefined) {

View File

@ -0,0 +1,37 @@
.rendererContainer {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
justify-content: stretch;
min-height: 200px;
min-width: 200px;
}
.container {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
justify-content: stretch;
min-height: 200px;
}
.frameContainer {
width: 100%;
flex-grow: 1;
flex-shrink: 1;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
min-width: 350px;
min-height: 16em;
display: flex;
flex-direction: column;
}

View File

@ -0,0 +1,38 @@
import {Registry} from "tc-shared/events";
import {PrivateConversationUIEvents} from "tc-shared/ui/frames/side/PrivateConversationDefinitions";
import {ConversationUIEvents} from "tc-shared/ui/frames/side/ConversationDefinitions";
import {ClientInfoEvents} from "tc-shared/ui/frames/side/ClientInfoDefinitions";
/* TODO: Somehow outsource the event registries to IPC? */
export type SideBarType = "none" | "channel-chat" | "private-chat" | "client-info" | "music-manage";
export interface SideBarTypeData {
"none": {},
"channel-chat": {
events: Registry<ConversationUIEvents>,
handlerId: string
},
"private-chat": {
events: Registry<PrivateConversationUIEvents>,
handlerId: string
},
"client-info": {
events: Registry<ClientInfoEvents>,
},
"music-manage": {
}
}
export type SideBarNotifyContentData<T extends SideBarType> = {
content: T,
data: SideBarTypeData[T]
}
export interface SideBarEvents {
query_content: {},
query_content_data: { content: SideBarType },
notify_content: { content: SideBarType },
notify_content_data: SideBarNotifyContentData<SideBarType>
}

View File

@ -0,0 +1,132 @@
import {SideHeaderEvents, SideHeaderState} from "tc-shared/ui/frames/side/HeaderDefinitions";
import {Registry} from "tc-shared/events";
import React = require("react");
import {SideHeaderRenderer} from "tc-shared/ui/frames/side/HeaderRenderer";
import {ConversationPanel} from "tc-shared/ui/frames/side/AbstractConversationRenderer";
import {SideBarEvents, SideBarType, SideBarTypeData} from "tc-shared/ui/frames/SideBarDefinitions";
import {useContext, useState} from "react";
import {ClientInfoRenderer} from "tc-shared/ui/frames/side/ClientInfoRenderer";
import {PrivateConversationsPanel} from "tc-shared/ui/frames/side/PrivateConversationRenderer";
const cssStyle = require("./SideBar.scss");
const EventContent = React.createContext<Registry<SideBarEvents>>(undefined);
function useContentData<T extends SideBarType>(type: T) : SideBarTypeData[T] {
const events = useContext(EventContent);
const [ contentData, setContentData ] = useState(() => {
events.fire("query_content_data", { content: type });
return undefined;
});
events.reactUse("notify_content_data", event => event.content === type && setContentData(event.data));
return contentData;
}
const ContentRendererChannelConversation = () => {
const contentData = useContentData("channel-chat");
if(!contentData) { return null; }
return (
<ConversationPanel
key={"channel-chat"}
events={contentData.events}
handlerId={contentData.handlerId}
messagesDeletable={true}
noFirstMessageOverlay={false}
/>
);
};
const ContentRendererPrivateConversation = () => {
const contentData = useContentData("private-chat");
if(!contentData) { return null; }
return (
<PrivateConversationsPanel
events={contentData.events}
handlerId={contentData.handlerId}
/>
);
};
const ContentRendererClientInfo = () => {
const contentData = useContentData("client-info");
if(!contentData) { return null; }
return (
<ClientInfoRenderer
events={contentData.events}
/>
);
};
const SideBarFrame = (props: { type: SideBarType }) => {
switch (props.type) {
case "channel-chat":
return <ContentRendererChannelConversation key={props.type} />;
case "private-chat":
return <ContentRendererPrivateConversation key={props.type} />;
case "client-info":
return <ContentRendererClientInfo key={props.type} />;
case "music-manage":
/* TODO! */
case "none":
default:
return null;
}
}
const SideBarHeader = (props: { type: SideBarType, eventsHeader: Registry<SideHeaderEvents> }) => {
let headerState: SideHeaderState;
switch (props.type) {
case "none":
headerState = { state: "none" };
break;
case "channel-chat":
headerState = { state: "conversation", mode: "channel" };
break;
case "private-chat":
headerState = { state: "conversation", mode: "private" };
break;
case "client-info":
headerState = { state: "client" };
break;
case "music-manage":
headerState = { state: "music-bot" };
break;
}
return <SideHeaderRenderer state={headerState} events={props.eventsHeader} />;
}
export const SideBarRenderer = (props: {
handlerId: string,
events: Registry<SideBarEvents>,
eventsHeader: Registry<SideHeaderEvents>
}) => {
const [ content, setContent ] = useState<SideBarType>(() => {
props.events.fire("query_content");
return "none";
});
props.events.reactUse("notify_content", event => setContent(event.content));
return (
<EventContent.Provider value={props.events}>
<div className={cssStyle.container}>
<SideBarHeader eventsHeader={props.eventsHeader} type={content} />
<div className={cssStyle.frameContainer}>
<SideBarFrame type={content} />
</div>
</div>
</EventContent.Provider>
)
};

View File

@ -1,35 +1,40 @@
import {ClientEntry, LocalClientEntry, MusicClientEntry} from "../../tree/Client";
import {ClientEntry, MusicClientEntry} from "../../tree/Client";
import {ConnectionHandler} from "../../ConnectionHandler";
import {MusicInfo} from "../../ui/frames/side/music_info";
import {ChannelConversationController} from "./side/ChannelConversationController";
import {PrivateConversationController} from "./side/PrivateConversationController";
import {ClientInfoController} from "tc-shared/ui/frames/side/ClientInfoController";
import {SideHeader} from "tc-shared/ui/frames/side/HeaderController";
import * as ReactDOM from "react-dom";
import {SideBarRenderer} from "tc-shared/ui/frames/SideBarRenderer";
import * as React from "react";
import {SideBarEvents, SideBarType} from "tc-shared/ui/frames/SideBarDefinitions";
import {Registry} from "tc-shared/events";
export enum FrameContent {
NONE,
PRIVATE_CHAT,
CHANNEL_CHAT,
CLIENT_INFO,
MUSIC_BOT
}
const cssStyle = require("./SideBar.scss");
export class Frame {
readonly handle: ConnectionHandler;
private htmlTag: JQuery;
private containerChannelChat: JQuery;
private _content_type: FrameContent;
private htmlTag: HTMLDivElement;
private currentType: SideBarType;
private uiEvents: Registry<SideBarEvents>;
private header: SideHeader;
private clientInfo: ClientInfoController;
private musicInfo: MusicInfo;
private clientInfo: ClientInfoController;
private channelConversations: ChannelConversationController;
private privateConversations: PrivateConversationController;
constructor(handle: ConnectionHandler) {
this.handle = handle;
this._content_type = FrameContent.NONE;
this.currentType = "none";
this.uiEvents = new Registry<SideBarEvents>();
this.uiEvents.on("query_content", () => this.uiEvents.fire_react("notify_content", { content: this.currentType }));
this.uiEvents.on("query_content_data", event => this.sendContentData(event.content));
this.privateConversations = new PrivateConversationController(handle);
this.channelConversations = new ChannelConversationController(handle);
this.clientInfo = new ClientInfoController(handle);
@ -42,9 +47,7 @@ export class Frame {
this.showChannelConversations();
}
html_tag() : JQuery { return this.htmlTag; }
content_type() : FrameContent { return this._content_type; }
html_tag() : HTMLDivElement { return this.htmlTag; }
destroy() {
this.header?.destroy();
@ -56,6 +59,12 @@ export class Frame {
this.clientInfo?.destroy();
this.clientInfo = undefined;
this.privateConversations?.destroy();
this.privateConversations = undefined;
this.channelConversations?.destroy();
this.channelConversations = undefined;
this.musicInfo && this.musicInfo.destroy();
this.musicInfo = undefined;
@ -64,19 +73,24 @@ export class Frame {
this.channelConversations && this.channelConversations.destroy();
this.channelConversations = undefined;
}
this.containerChannelChat && this.containerChannelChat.remove();
this.containerChannelChat = undefined;
renderInto(container: HTMLDivElement) {
ReactDOM.render(React.createElement(SideBarRenderer, {
key: this.handle.handlerId,
handlerId: this.handle.handlerId,
events: this.uiEvents,
eventsHeader: this.header["uiEvents"],
}), container);
}
private createHtmlTag() {
this.htmlTag = $("#tmpl_frame_chat").renderTag();
this.htmlTag.find(".container-info").replaceWith(this.header.getHtmlTag());
this.containerChannelChat = this.htmlTag.find(".container-chat");
this.htmlTag = document.createElement("div");
this.htmlTag.classList.add(cssStyle.container);
}
private_conversations() : PrivateConversationController {
privateConversationsController() : PrivateConversationController {
return this.privateConversations;
}
@ -88,73 +102,81 @@ export class Frame {
return this.musicInfo;
}
private clearSideBar() {
this._content_type = FrameContent.NONE;
this.containerChannelChat.children().detach();
private setCurrentContent(type: SideBarType) {
if(this.currentType === type) {
return;
}
this.currentType = type;
this.uiEvents.fire_react("notify_content", { content: this.currentType });
}
private sendContentData(content: SideBarType) {
switch (content) {
case "none":
this.uiEvents.fire_react("notify_content_data", {
content: "none",
data: {}
});
break;
case "channel-chat":
this.uiEvents.fire_react("notify_content_data", {
content: "channel-chat",
data: {
events: this.channelConversations["uiEvents"],
handlerId: this.handle.handlerId
}
});
break;
case "private-chat":
this.uiEvents.fire_react("notify_content_data", {
content: "private-chat",
data: {
events: this.privateConversations["uiEvents"],
handlerId: this.handle.handlerId
}
});
break;
case "client-info":
this.uiEvents.fire_react("notify_content_data", {
content: "client-info",
data: {
events: this.clientInfo["uiEvents"],
}
});
break;
case "music-manage":
this.uiEvents.fire_react("notify_content_data", {
content: "music-manage",
data: { }
});
break;
}
}
showPrivateConversations() {
if(this._content_type === FrameContent.PRIVATE_CHAT)
return;
this.header.setState({ state: "conversation", mode: "private" });
this.clearSideBar();
this._content_type = FrameContent.PRIVATE_CHAT;
this.containerChannelChat.append(this.privateConversations.htmlTag);
this.privateConversations.handlePanelShow();
this.setCurrentContent("private-chat");
}
showChannelConversations() {
if(this._content_type === FrameContent.CHANNEL_CHAT)
return;
this.header.setState({ state: "conversation", mode: "channel" });
this.clearSideBar();
this._content_type = FrameContent.CHANNEL_CHAT;
this.containerChannelChat.append(this.channelConversations.htmlTag);
this.channelConversations.handlePanelShow();
this.setCurrentContent("channel-chat");
}
showClientInfo(client: ClientEntry) {
this.clientInfo.setClient(client);
this.header.setState({ state: "client", ownClient: client instanceof LocalClientEntry });
if(this._content_type === FrameContent.CLIENT_INFO)
return;
this.clearSideBar();
this._content_type = FrameContent.CLIENT_INFO;
this.containerChannelChat.append(this.clientInfo.getHtmlTag());
this.setCurrentContent("client-info");
}
showMusicPlayer(client: MusicClientEntry) {
this.musicInfo.set_current_bot(client);
if(this._content_type === FrameContent.MUSIC_BOT)
return;
this.header.setState({ state: "music-bot" });
this.musicInfo.previous_frame_content = this._content_type;
this.clearSideBar();
this._content_type = FrameContent.MUSIC_BOT;
this.containerChannelChat.append(this.musicInfo.html_tag());
this.setCurrentContent("music-manage");
}
set_content(type: FrameContent) {
if(this._content_type === type) {
return;
}
if(type === FrameContent.CHANNEL_CHAT) {
this.showChannelConversations();
} else if(type === FrameContent.PRIVATE_CHAT) {
this.showPrivateConversations();
} else {
this.header.setState({ state: "none" });
this.clearSideBar();
this._content_type = FrameContent.NONE;
}
clearSideBar() {
this.setCurrentContent("none");
}
}

View File

@ -895,10 +895,12 @@ export const ConversationPanel = React.memo((props: { events: Registry<Conversat
currentChat.current.id = event.chatId;
updateChatBox();
});
props.events.reactUse("notify_conversation_state", event => {
chatEnabled.current = event.state === "normal" && event.sendEnabled;
updateChatBox();
});
props.events.reactUse("notify_send_enabled", event => {
if(event.chatId !== currentChat.current.id)
return;

View File

@ -3,20 +3,22 @@ import {ClientEntry, ClientType, LocalClientEntry} from "tc-shared/tree/Client";
import {
ClientForumInfo,
ClientGroupInfo,
ClientInfoEvents,
ClientInfoEvents, ClientInfoType,
ClientStatusInfo,
ClientVersionInfo
} from "tc-shared/ui/frames/side/ClientInfoDefinitions";
import * as ReactDOM from "react-dom";
import {ClientInfoRenderer} from "tc-shared/ui/frames/side/ClientInfoRenderer";
import {Registry} from "tc-shared/events";
import * as React from "react";
import * as i18nc from "../../../i18n/country";
import {openClientInfo} from "tc-shared/ui/modal/ModalClientInfo";
type CurrentClientInfo = {
type: ClientInfoType;
name: string,
uniqueId: string,
databaseId: number,
clientId: number,
description: string,
joinTimestamp: number,
leaveTimestamp: number,
@ -29,12 +31,19 @@ type CurrentClientInfo = {
version: ClientVersionInfo
}
export interface ClientInfoControllerEvents {
notify_client_changed: {
newClient: ClientEntry | undefined
}
}
export class ClientInfoController {
readonly events: Registry<ClientInfoControllerEvents>;
private readonly connection: ConnectionHandler;
private readonly listenerConnection: (() => void)[];
private readonly uiEvents: Registry<ClientInfoEvents>;
private readonly htmlContainer: HTMLDivElement;
private listenerClient: (() => void)[];
private currentClient: ClientEntry | undefined;
@ -42,6 +51,7 @@ export class ClientInfoController {
constructor(connection: ConnectionHandler) {
this.connection = connection;
this.events = new Registry<ClientInfoControllerEvents>();
this.uiEvents = new Registry<ClientInfoEvents>();
this.uiEvents.enableDebug("client-info");
@ -49,17 +59,6 @@ export class ClientInfoController {
this.listenerClient = [];
this.initialize();
this.htmlContainer = document.createElement("div");
this.htmlContainer.style.display = "flex";
this.htmlContainer.style.flexDirection = "column";
this.htmlContainer.style.justifyContent = "strech";
this.htmlContainer.style.height = "100%";
ReactDOM.render(React.createElement(ClientInfoRenderer, { events: this.uiEvents }), this.htmlContainer);
}
getHtmlTag() : HTMLDivElement {
return this.htmlContainer;
}
private initialize() {
@ -89,6 +88,7 @@ export class ClientInfoController {
}
this.currentClientStatus.leaveTimestamp = Date.now() / 1000;
this.currentClientStatus.clientId = 0;
this.currentClient = undefined;
this.unregisterClientEvents();
this.sendOnline();
@ -102,6 +102,7 @@ export class ClientInfoController {
}
}))
this.uiEvents.on("query_client", () => this.sendClient());
this.uiEvents.on("query_client_name", () => this.sendClientName());
this.uiEvents.on("query_client_description", () => this.sendClientDescription());
this.uiEvents.on("query_channel_group", () => this.sendChannelGroup());
@ -228,7 +229,12 @@ export class ClientInfoController {
private initializeClientInfo(client: ClientEntry) {
this.currentClientStatus = {
type: client instanceof LocalClientEntry ? "self" : client.properties.client_type === ClientType.CLIENT_QUERY ? "query" : "voice",
name: client.properties.client_nickname,
databaseId: client.properties.client_database_id,
uniqueId: client.properties.client_unique_identifier,
clientId: client.clientId(),
description: client.properties.client_description,
channelGroup: client.properties.client_channel_group_id,
serverGroups: client.assignedServerGroupIds(),
@ -250,8 +256,6 @@ export class ClientInfoController {
}
destroy() {
ReactDOM.unmountComponentAtNode(this.htmlContainer);
this.listenerClient.forEach(callback => callback());
this.listenerClient = [];
@ -266,25 +270,14 @@ export class ClientInfoController {
this.unregisterClientEvents();
this.currentClient = client;
this.currentClientStatus = undefined;
if(this.currentClient) {
this.currentClient.updateClientVariables().then(undefined);
this.registerClientEvents(this.currentClient);
this.initializeClientInfo(this.currentClient);
this.uiEvents.fire("notify_client", {
info: {
handlerId: this.connection.handlerId,
type: client instanceof LocalClientEntry ? "self" : client.properties.client_type === ClientType.CLIENT_QUERY ? "query" : "voice",
clientDatabaseId: client.properties.client_database_id,
clientId: client.clientId(),
clientUniqueId: client.properties.client_unique_identifier
}
});
} else {
this.currentClientStatus = undefined;
this.uiEvents.fire("notify_client", {
info: undefined
});
}
this.sendClient();
this.events.fire("notify_client_changed", { newClient: client });
}
getClient() : ClientEntry | undefined {
@ -316,6 +309,24 @@ export class ClientInfoController {
}
}
private sendClient() {
if(this.currentClientStatus) {
this.uiEvents.fire_react("notify_client", {
info: {
handlerId: this.connection.handlerId,
type: this.currentClientStatus.type,
clientDatabaseId: this.currentClientStatus.databaseId,
clientId: this.currentClientStatus.clientId,
clientUniqueId: this.currentClientStatus.uniqueId
}
});
} else {
this.uiEvents.fire_react("notify_client", {
info: undefined
});
}
}
private sendChannelGroup() {
if(typeof this.currentClientStatus === "undefined") {
this.uiEvents.fire_react("notify_channel_group", { group: undefined });

View File

@ -61,6 +61,7 @@ export interface ClientInfoEvents {
action_show_full_info: {},
action_edit_avatar: {},
query_client: {},
query_channel_group: {},
query_server_groups: {},
query_client_name: {},
@ -83,7 +84,6 @@ export interface ClientInfoEvents {
notify_version: { version: ClientVersionInfo },
notify_forum: { forum: ClientForumInfo },
/* reset all fields into "loading" state */
notify_client: {
info: ClientInfoInfo | undefined
}

View File

@ -439,7 +439,10 @@ const ServerGroupRenderer = () => {
const ClientInfoProvider = () => {
const events = useContext(EventsContext);
const [ client, setClient ] = useState<OptionalClientInfoInfo>({ type: "none", contextHash: guid() });
const [ client, setClient ] = useState<OptionalClientInfoInfo>(() => {
events.fire("query_client");
return { type: "none", contextHash: guid() };
});
events.reactUse("notify_client", event => {
if(event.info) {
setClient({

View File

@ -1,12 +1,8 @@
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import * as ReactDOM from "react-dom";
import {SideHeaderRenderer} from "./HeaderRenderer";
import * as React from "react";
import {SideHeaderEvents, SideHeaderState} from "tc-shared/ui/frames/side/HeaderDefinitions";
import * as _ from "lodash";
import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions";
import {Registry} from "tc-shared/events";
import {ChannelEntry, ChannelProperties} from "tc-shared/tree/Channel";
import {ClientEntry, LocalClientEntry} from "tc-shared/tree/Client";
import {LocalClientEntry} from "tc-shared/tree/Client";
import {openMusicManage} from "tc-shared/ui/modal/ModalMusicManage";
const ChannelInfoUpdateProperties: (keyof ChannelProperties)[] = [
@ -21,9 +17,8 @@ const ChannelInfoUpdateProperties: (keyof ChannelProperties)[] = [
"channel_maxfamilyclients"
];
/* TODO: Remove the ping interval handler. It's currently still there since the clients are not emiting the event yet */
/* TODO: Remove the ping interval handler. It's currently still there since the clients are not emitting the event yet */
export class SideHeader {
private readonly htmlTag: HTMLDivElement;
private readonly uiEvents: Registry<SideHeaderEvents>;
private connection: ConnectionHandler;
@ -32,7 +27,6 @@ export class SideHeader {
private listenerVoiceChannel: (() => void)[];
private listenerTextChannel: (() => void)[];
private currentState: SideHeaderState;
private currentVoiceChannel: ChannelEntry;
private currentTextChannel: ChannelEntry;
@ -44,13 +38,6 @@ export class SideHeader {
this.listenerVoiceChannel = [];
this.listenerTextChannel = [];
this.htmlTag = document.createElement("div");
this.htmlTag.style.display = "flex";
this.htmlTag.style.flexDirection = "column";
this.htmlTag.style.flexShrink = "0";
this.htmlTag.style.flexGrow = "0";
ReactDOM.render(React.createElement(SideHeaderRenderer, { events: this.uiEvents }), this.htmlTag);
this.initialize();
}
@ -75,8 +62,9 @@ export class SideHeader {
openMusicManage(this.connection, bot);
});
this.uiEvents.on("action_bot_manage", () => this.connection.side_bar.music_info().events.fire("action_song_add"));
this.uiEvents.on("action_bot_add_song", () => this.connection.side_bar.music_info().events.fire("action_song_add"));
this.uiEvents.on("query_client_info_own_client", () => this.sendClientInfoOwnClient());
this.uiEvents.on("query_current_channel_state", event => this.sendChannelState(event.mode));
this.uiEvents.on("query_private_conversations", () => this.sendPrivateConversationInfo());
this.uiEvents.on("query_ping", () => this.sendPing());
@ -110,6 +98,7 @@ export class SideHeader {
this.listenerConnection.push(this.connection.serverConnection.events.on("notify_ping_updated", () => this.sendPing()));
this.listenerConnection.push(this.connection.getPrivateConversations().events.on("notify_unread_count_changed", () => this.sendPrivateConversationInfo()));
this.listenerConnection.push(this.connection.getPrivateConversations().events.on(["notify_conversation_destroyed", "notify_conversation_destroyed"], () => this.sendPrivateConversationInfo()));
this.listenerConnection.push(this.connection.side_bar.getClientInfo().events.on("notify_client_changed", () => this.sendClientInfoOwnClient()));
}
setConnectionHandler(connection: ConnectionHandler) {
@ -123,23 +112,18 @@ export class SideHeader {
this.connection = connection;
if(connection) {
this.initializeConnection();
/* TODO: Update state! */
} else {
this.setState({ state: "none" });
}
this.sendPing();
this.sendPrivateConversationInfo();
this.sendChannelState("voice");
this.sendChannelState("text");
}
getConnectionHandler() : ConnectionHandler | undefined {
return this.connection;
}
getHtmlTag() : HTMLDivElement {
return this.htmlTag;
}
destroy() {
ReactDOM.unmountComponentAtNode(this.htmlTag);
this.listenerConnection.forEach(callback => callback());
this.listenerConnection = [];
@ -153,15 +137,6 @@ export class SideHeader {
this.pingUpdateInterval = undefined;
}
setState(state: SideHeaderState) {
if(_.isEqual(this.currentState, state)) {
return;
}
this.currentState = state;
this.uiEvents.fire_react("notify_header_state", { state: state });
}
private sendChannelState(mode: "voice" | "text") {
const channel = mode === "voice" ? this.currentVoiceChannel : this.currentTextChannel;
if(channel) {
@ -258,12 +233,29 @@ export class SideHeader {
}
private sendPrivateConversationInfo() {
const conversations = this.connection.getPrivateConversations();
this.uiEvents.fire_react("notify_private_conversations", {
info: {
open: conversations.getConversations().length,
unread: conversations.getUnreadCount()
}
});
if(this.connection) {
const conversations = this.connection.getPrivateConversations();
this.uiEvents.fire_react("notify_private_conversations", {
info: {
open: conversations.getConversations().length,
unread: conversations.getUnreadCount()
}
});
} else {
this.uiEvents.fire_react("notify_private_conversations", {
info: {
open: 0,
unread: 0
}
});
}
}
private sendClientInfoOwnClient() {
if(this.connection) {
this.uiEvents.fire_react("notify_client_info_own_client", { isOwnClient: this.connection.side_bar.getClientInfo().getClient() instanceof LocalClientEntry });
} else {
this.uiEvents.fire_react("notify_client_info_own_client", { isOwnClient: false });
}
}
}

View File

@ -12,7 +12,6 @@ export type SideHeaderStateConversation = {
export type SideHeaderStateClient = {
state: "client",
ownClient: boolean
}
export type SideHeaderStateMusicBot = {
@ -46,12 +45,10 @@ export interface SideHeaderEvents {
action_open_conversation: {},
query_current_channel_state: { mode: "voice" | "text" },
query_ping: {},
query_private_conversations: {},
query_client_info_own_client: {},
query_ping: {},
notify_header_state: {
state: SideHeaderState
},
notify_current_channel_state: {
mode: "voice" | "text",
state: SideHeaderChannelState
@ -61,5 +58,8 @@ export interface SideHeaderEvents {
},
notify_private_conversations: {
info: PrivateConversationInfo
},
notify_client_info_own_client: {
isOwnClient: boolean
}
}

View File

@ -236,11 +236,19 @@ const BlockBottomLeft = () => {
}
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(state.ownClient) {
if(ownClient) {
return null;
} else {
return <BlockButtonOpenConversation key={"button-open-conversation"} />;
@ -258,19 +266,16 @@ const BlockBottomRight = () => {
}
}
export const SideHeaderRenderer = (props: { events: Registry<SideHeaderEvents> }) => {
const [ state, setState ] = useState<SideHeaderState>({ state: "none" });
props.events.reactUse("notify_header_state", event => setState(event.state));
export const SideHeaderRenderer = React.memo((props: { events: Registry<SideHeaderEvents>, state: SideHeaderState }) => {
return (
<EventsContext.Provider value={props.events}>
<StateContext.Provider value={state}>
<StateContext.Provider value={props.state}>
<div className={cssStyle.container}>
<div className={cssStyle.lane}>
<BlockTopLeft />
<BlockTopRight />
</div>
<div className={cssStyle.lane + " " + (state.state === "music-bot" ? cssStyle.musicBotInfo : "")}>
<div className={cssStyle.lane + " " + (props.state.state === "music-bot" ? cssStyle.musicBotInfo : "")}>
<BlockBottomLeft />
<BlockBottomRight />
</div>
@ -278,4 +283,4 @@ export const SideHeaderRenderer = (props: { events: Registry<SideHeaderEvents> }
</StateContext.Provider>
</EventsContext.Provider>
);
}
})

View File

@ -215,12 +215,12 @@ const OpenConversationsPanel = React.memo(() => {
});
export const PrivateConversationsPanel = (props: { events: Registry<PrivateConversationUIEvents>, handler: ConnectionHandler }) => (
<HandlerIdContext.Provider value={props.handler.handlerId}>
export const PrivateConversationsPanel = (props: { events: Registry<PrivateConversationUIEvents>, handlerId: string }) => (
<HandlerIdContext.Provider value={props.handlerId}>
<EventContext.Provider value={props.events}>
<ContextDivider id={"seperator-conversation-list-messages"} direction={"horizontal"} defaultValue={25} separatorClassName={cssStyle.divider}>
<OpenConversationsPanel />
<ConversationPanel events={props.events as any} handlerId={props.handler.handlerId} noFirstMessageOverlay={true} messagesDeletable={false} />
<ConversationPanel events={props.events as any} handlerId={props.handlerId} noFirstMessageOverlay={true} messagesDeletable={false} />
</ContextDivider>
</EventContext.Provider>
</HandlerIdContext.Provider>