The sidebar now is completely disconnected from the connection handler itself and only renders what's needed.

master
WolverinDEV 2020-12-09 20:44:33 +01:00 committed by WolverinDEV
parent cb505d39cf
commit 787de619de
31 changed files with 777 additions and 566 deletions

View File

@ -7,6 +7,7 @@
- Enabled context menus for all clickable client tags - Enabled context menus for all clickable client tags
- Allowing to drag client tags - Allowing to drag client tags
- Fixed the context menu within popout windows for the web client - Fixed the context menu within popout windows for the web client
- Reworked the whole sidebar (Hightly decreased memory footprint)
* **08.12.20** * **08.12.20**
- Fixed the permission editor not resolving unique ids - Fixed the permission editor not resolving unique ids

View File

@ -13,7 +13,6 @@ import * as htmltags from "./ui/htmltags";
import {FilterMode, InputState, MediaStreamRequestResult} from "./voice/RecorderBase"; import {FilterMode, InputState, MediaStreamRequestResult} from "./voice/RecorderBase";
import {CommandResult} from "./connection/ServerConnectionDeclaration"; import {CommandResult} from "./connection/ServerConnectionDeclaration";
import {defaultRecorder, RecorderProfile} from "./voice/RecorderProfile"; import {defaultRecorder, RecorderProfile} from "./voice/RecorderProfile";
import {Frame} from "./ui/frames/chat_frame";
import {Hostbanner} from "./ui/frames/hostbanner"; import {Hostbanner} from "./ui/frames/hostbanner";
import {connection_log, Regex} from "./ui/modal/ModalConnect"; import {connection_log, Regex} from "./ui/modal/ModalConnect";
import {formatMessage} from "./ui/frames/chat"; import {formatMessage} from "./ui/frames/chat";
@ -40,7 +39,8 @@ import {ChannelVideoFrame} from "tc-shared/ui/frames/video/Controller";
import {global_client_actions} from "tc-shared/events/GlobalEvents"; import {global_client_actions} from "tc-shared/events/GlobalEvents";
import {ChannelConversationManager} from "./conversations/ChannelConversationManager"; import {ChannelConversationManager} from "./conversations/ChannelConversationManager";
import {PrivateConversationManager} from "tc-shared/conversations/PrivateConversationManager"; import {PrivateConversationManager} from "tc-shared/conversations/PrivateConversationManager";
import {ChannelConversationController} from "./ui/frames/side/ChannelConversationController"; import {SelectedClientInfo} from "./SelectedClientInfo";
import {SideBarManager} from "tc-shared/SideBarManager";
export enum InputHardwareState { export enum InputHardwareState {
MISSING, MISSING,
@ -145,7 +145,6 @@ export class ConnectionHandler {
permissions: PermissionManager; permissions: PermissionManager;
groups: GroupManager; groups: GroupManager;
side_bar: Frame;
video_frame: ChannelVideoFrame; video_frame: ChannelVideoFrame;
settings: ServerSettings; settings: ServerSettings;
@ -157,9 +156,13 @@ export class ConnectionHandler {
serverFeatures: ServerFeatures; serverFeatures: ServerFeatures;
private sideBar: SideBarManager;
private channelConversations: ChannelConversationManager; private channelConversations: ChannelConversationManager;
private privateConversations: PrivateConversationManager; private privateConversations: PrivateConversationManager;
private clientInfoManager: SelectedClientInfo;
private _clientId: number = 0; private _clientId: number = 0;
private localClient: LocalClientEntry; private localClient: LocalClientEntry;
@ -211,14 +214,15 @@ export class ConnectionHandler {
this.fileManager = new FileManager(this); this.fileManager = new FileManager(this);
this.permissions = new PermissionManager(this); this.permissions = new PermissionManager(this);
this.sideBar = new SideBarManager(this);
this.privateConversations = new PrivateConversationManager(this); this.privateConversations = new PrivateConversationManager(this);
this.channelConversations = new ChannelConversationManager(this); this.channelConversations = new ChannelConversationManager(this);
this.clientInfoManager = new SelectedClientInfo(this);
this.pluginCmdRegistry = new PluginCmdRegistry(this); this.pluginCmdRegistry = new PluginCmdRegistry(this);
this.video_frame = new ChannelVideoFrame(this); this.video_frame = new ChannelVideoFrame(this);
this.log = new ServerEventLog(this); this.log = new ServerEventLog(this);
this.side_bar = new Frame(this);
this.sound = new SoundManager(this); this.sound = new SoundManager(this);
this.hostbanner = new Hostbanner(this); this.hostbanner = new Hostbanner(this);
@ -358,6 +362,14 @@ export class ConnectionHandler {
return this.channelConversations; return this.channelConversations;
} }
getSelectedClientInfo() : SelectedClientInfo {
return this.clientInfoManager;
}
getSideBar() : SideBarManager {
return this.sideBar;
}
initializeLocalClient(clientId: number, acceptedName: string) { initializeLocalClient(clientId: number, acceptedName: string) {
this._clientId = clientId; this._clientId = clientId;
this.localClient["_clientId"] = clientId; this.localClient["_clientId"] = clientId;
@ -1042,8 +1054,11 @@ export class ConnectionHandler {
this.channelTree?.destroy(); this.channelTree?.destroy();
this.channelTree = undefined; this.channelTree = undefined;
this.side_bar?.destroy(); this.sideBar?.destroy();
this.side_bar = undefined; this.sideBar = undefined;
this.clientInfoManager?.destroy();
this.clientInfoManager = undefined;
this.log?.destroy(); this.log?.destroy();
this.log = undefined; this.log = undefined;

View File

@ -5,6 +5,7 @@ import {Stage} from "tc-loader";
import {FooterRenderer} from "tc-shared/ui/frames/footer/Renderer"; import {FooterRenderer} from "tc-shared/ui/frames/footer/Renderer";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import {SideBarController} from "tc-shared/ui/frames/SideBarController";
export let server_connections: ConnectionManager; export let server_connections: ConnectionManager;
@ -36,17 +37,21 @@ export class ConnectionManager {
private containerSideBar: HTMLDivElement; private containerSideBar: HTMLDivElement;
private containerFooter: HTMLDivElement; private containerFooter: HTMLDivElement;
private sideBarController: SideBarController;
constructor() { constructor() {
this.event_registry = new Registry<ConnectionManagerEvents>(); this.event_registry = new Registry<ConnectionManagerEvents>();
this.event_registry.enableDebug("connection-manager"); this.event_registry.enableDebug("connection-manager");
this.sideBarController = new SideBarController();
this.containerChannelVideo = new ReplaceableContainer(document.getElementById("channel-video") as HTMLDivElement); 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_log_server = $("#server-log");
this._container_channel_tree = $("#channelTree"); this._container_channel_tree = $("#channelTree");
this._container_hostbanner = $("#hostbanner"); this._container_hostbanner = $("#hostbanner");
this.containerFooter = document.getElementById("container-footer") as HTMLDivElement; this.containerFooter = document.getElementById("container-footer") as HTMLDivElement;
this.sideBarController.renderInto(document.getElementById("chat") as HTMLDivElement);
this.set_active_connection(undefined); this.set_active_connection(undefined);
} }
@ -111,6 +116,8 @@ export class ConnectionManager {
} }
private set_active_connection_(handler: ConnectionHandler) { private set_active_connection_(handler: ConnectionHandler) {
this.sideBarController.setConnection(handler);
this._container_channel_tree.children().detach(); this._container_channel_tree.children().detach();
this._container_log_server.children().detach(); this._container_log_server.children().detach();
this._container_hostbanner.children().detach(); this._container_hostbanner.children().detach();
@ -120,8 +127,8 @@ export class ConnectionManager {
this._container_hostbanner.append(handler.hostbanner.html_tag); this._container_hostbanner.append(handler.hostbanner.html_tag);
this._container_channel_tree.append(handler.channelTree.tag_tree()); this._container_channel_tree.append(handler.channelTree.tag_tree());
this._container_log_server.append(handler.log.getHTMLTag()); this._container_log_server.append(handler.log.getHTMLTag());
handler.side_bar.renderInto(this.containerSideBar);
} }
const old_handler = this.active_handler; const old_handler = this.active_handler;
this.active_handler = handler; this.active_handler = handler;
this.event_registry.fire("notify_active_handler_changed", { this.event_registry.fire("notify_active_handler_changed", {

View File

@ -0,0 +1,248 @@
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
import {
ClientForumInfo,
ClientInfoType,
ClientStatusInfo,
ClientVersionInfo
} from "tc-shared/ui/frames/side/ClientInfoDefinitions";
import {ClientEntry, ClientType, LocalClientEntry} from "tc-shared/tree/Client";
import {Registry} from "tc-shared/events";
import * as i18nc from "tc-shared/i18n/country";
export type CachedClientInfoCategory = "name" | "description" | "online-state" | "country" | "volume" | "status" | "forum-account" | "group-channel" | "groups-server" | "version";
export type CachedClientInfo = {
type: ClientInfoType;
name: string,
uniqueId: string,
databaseId: number,
clientId: number,
description: string,
joinTimestamp: number,
leaveTimestamp: number,
country: { name: string, flag: string },
volume: { volume: number, muted: boolean },
status: ClientStatusInfo,
forumAccount: ClientForumInfo | undefined,
channelGroup: number,
serverGroups: number[],
version: ClientVersionInfo
}
export interface ClientInfoManagerEvents {
notify_client_changed: { newClient: ClientEntry | undefined },
notify_cache_changed: { category: CachedClientInfoCategory },
}
export class SelectedClientInfo {
readonly events: Registry<ClientInfoManagerEvents>;
private readonly connection: ConnectionHandler;
private readonly listenerConnection: (() => void)[];
private listenerClient: (() => void)[];
private currentClient: ClientEntry | undefined;
private currentClientStatus: CachedClientInfo | undefined;
constructor(connection: ConnectionHandler) {
this.connection = connection;
this.events = new Registry<ClientInfoManagerEvents>();
this.listenerClient = [];
this.listenerConnection = [];
this.listenerConnection.push(connection.channelTree.events.on("notify_client_leave_view", event => {
if(event.client !== this.currentClient) {
return;
}
this.currentClientStatus.leaveTimestamp = Date.now() / 1000;
this.currentClientStatus.clientId = 0;
this.currentClient = undefined;
this.unregisterClientEvents();
this.events.fire("notify_cache_changed", { category: "online-state" });
}));
this.listenerConnection.push(connection.events().on("notify_connection_state_changed", event => {
if(event.newState !== ConnectionState.CONNECTED && this.currentClientStatus) {
this.currentClient = undefined;
this.currentClientStatus.leaveTimestamp = Date.now() / 1000;
this.events.fire("notify_cache_changed", { category: "online-state" });
}
}));
}
destroy() {
this.listenerConnection.forEach(callback => callback());
this.listenerConnection.splice(0, this.listenerConnection.length);
this.unregisterClientEvents();
}
getInfo() : CachedClientInfo {
return this.currentClientStatus;
}
setClient(client: ClientEntry | undefined) {
if(this.currentClient === client) {
return;
}
if(client.channelTree.client !== this.connection) {
throw tr("client does not belong to current connection handler");
}
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.events.fire("notify_client_changed", { newClient: client });
}
getClient() : ClientEntry | undefined {
return this.currentClient;
}
private unregisterClientEvents() {
this.listenerClient.forEach(callback => callback());
this.listenerClient = [];
}
private registerClientEvents(client: ClientEntry) {
const events = this.listenerClient;
events.push(client.events.on("notify_properties_updated", event => {
if('client_nickname' in event.updated_properties) {
this.currentClientStatus.name = event.client_properties.client_nickname;
this.events.fire("notify_cache_changed", { category: "name" });
}
if('client_description' in event.updated_properties) {
this.currentClientStatus.description = event.client_properties.client_description;
this.events.fire("notify_cache_changed", { category: "description" });
}
if('client_channel_group_id' in event.updated_properties) {
this.currentClientStatus.channelGroup = event.client_properties.client_channel_group_id;
this.events.fire("notify_cache_changed", { category: "group-channel" });
}
if('client_servergroups' in event.updated_properties) {
this.currentClientStatus.serverGroups = client.assignedServerGroupIds();
this.events.fire("notify_cache_changed", { category: "groups-server" });
}
/* Can happen since that variable isn't in view on client appearance */
if('client_lastconnected' in event.updated_properties) {
this.currentClientStatus.joinTimestamp = event.client_properties.client_lastconnected;
this.events.fire("notify_cache_changed", { category: "online-state" });
}
if('client_country' in event.updated_properties) {
this.updateCachedCountry(client);
this.events.fire("notify_cache_changed", { category: "country" });
}
for(const key of ["client_away", "client_away_message", "client_input_muted", "client_input_hardware", "client_output_muted", "client_output_hardware"]) {
if(key in event.updated_properties) {
this.updateCachedClientStatus(client);
this.events.fire("notify_cache_changed", { category: "status" });
break;
}
}
if('client_platform' in event.updated_properties || 'client_version' in event.updated_properties) {
this.currentClientStatus.version = {
platform: client.properties.client_platform,
version: client.properties.client_version
};
this.events.fire("notify_cache_changed", { category: "version" });
}
if('client_teaforo_flags' in event.updated_properties || 'client_teaforo_name' in event.updated_properties || 'client_teaforo_id' in event.updated_properties) {
this.updateForumAccount(client);
this.events.fire("notify_cache_changed", { category: "forum-account" });
}
}));
events.push(client.events.on("notify_audio_level_changed", () => {
this.updateCachedVolume(client);
this.events.fire("notify_cache_changed", { category: "volume" });
}));
events.push(client.events.on("notify_mute_state_change", () => {
this.updateCachedVolume(client);
this.events.fire("notify_cache_changed", { category: "volume" });
}));
}
private updateCachedClientStatus(client: ClientEntry) {
this.currentClientStatus.status = {
away: client.properties.client_away ? client.properties.client_away_message ? client.properties.client_away_message : true : false,
microphoneMuted: client.properties.client_input_muted,
microphoneDisabled: !client.properties.client_input_hardware,
speakerMuted: client.properties.client_output_muted,
speakerDisabled: !client.properties.client_output_hardware
};
}
private updateCachedCountry(client: ClientEntry) {
this.currentClientStatus.country = {
flag: client.properties.client_country,
name: i18nc.country_name(client.properties.client_country.toUpperCase()),
};
}
private updateCachedVolume(client: ClientEntry) {
this.currentClientStatus.volume = {
volume: client.getAudioVolume(),
muted: client.isMuted()
}
}
private updateForumAccount(client: ClientEntry) {
if(client.properties.client_teaforo_id) {
this.currentClientStatus.forumAccount = {
flags: client.properties.client_teaforo_flags,
nickname: client.properties.client_teaforo_name,
userId: client.properties.client_teaforo_id
};
} else {
this.currentClientStatus.forumAccount = undefined;
}
}
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(),
country: undefined,
forumAccount: undefined,
joinTimestamp: client.properties.client_lastconnected,
leaveTimestamp: 0,
status: undefined,
volume: undefined,
version: {
platform: client.properties.client_platform,
version: client.properties.client_version
}
};
this.updateCachedClientStatus(client);
this.updateCachedCountry(client);
this.updateCachedVolume(client);
this.updateForumAccount(client);
}
}

View File

@ -0,0 +1,58 @@
import {SideBarType} from "tc-shared/ui/frames/SideBarDefinitions";
import {Registry} from "tc-shared/events";
import {ClientEntry, MusicClientEntry} from "tc-shared/tree/Client";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
export interface SideBarManagerEvents {
notify_content_type_changed: { newContent: SideBarType }
}
export class SideBarManager {
readonly events: Registry<SideBarManagerEvents>;
private readonly connection: ConnectionHandler;
private currentType: SideBarType;
constructor(connection: ConnectionHandler) {
this.events = new Registry<SideBarManagerEvents>();
this.connection = connection;
this.currentType = "channel-chat";
}
destroy() {}
getSideBarContent() : SideBarType {
return this.currentType;
}
setSideBarContent(content: SideBarType) {
if(this.currentType === content) {
return;
}
this.currentType = content;
this.events.fire("notify_content_type_changed", { newContent: content });
}
showPrivateConversations() {
this.setSideBarContent("private-chat");
}
showChannelConversations() {
this.setSideBarContent("channel-chat");
}
showClientInfo(client: ClientEntry) {
this.connection.getSelectedClientInfo().setClient(client);
this.setSideBarContent("client-info");
}
showMusicPlayer(_client: MusicClientEntry) {
/* FIXME: TODO! */
this.setSideBarContent("music-manage");
}
clearSideBar() {
this.setSideBarContent("none");
}
}

View File

@ -4,7 +4,7 @@ import {
ChatMessage, ChatMessage,
ChatState, ChatState,
ConversationHistoryResponse ConversationHistoryResponse
} from "tc-shared/ui/frames/side/ConversationDefinitions"; } from "../ui/frames/side/AbstractConversationDefinitions";
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import {ConnectionHandler} from "tc-shared/ConnectionHandler"; import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {preprocessChatMessageForSend} from "tc-shared/text/chat"; import {preprocessChatMessageForSend} from "tc-shared/text/chat";
@ -16,7 +16,7 @@ import {guid} from "tc-shared/crypto/uid";
export const kMaxChatFrameMessageSize = 50; /* max 100 messages, since the server does not support more than 100 messages queried at once */ export const kMaxChatFrameMessageSize = 50; /* max 100 messages, since the server does not support more than 100 messages queried at once */
export interface AbstractChatEvents { export interface AbstractConversationEvents {
notify_chat_event: { notify_chat_event: {
triggerUnread: boolean, triggerUnread: boolean,
event: ChatEvent event: ChatEvent
@ -45,7 +45,7 @@ export interface AbstractChatEvents {
} }
} }
export abstract class AbstractChat<Events extends AbstractChatEvents> { export abstract class AbstractChat<Events extends AbstractConversationEvents> {
readonly events: Registry<Events>; readonly events: Registry<Events>;
protected readonly connection: ConnectionHandler; protected readonly connection: ConnectionHandler;
@ -341,7 +341,7 @@ export interface AbstractChatManagerEvents<ConversationType> {
} }
} }
export abstract class AbstractChatManager<ManagerEvents extends AbstractChatManagerEvents<ConversationType>, ConversationType extends AbstractChat<ConversationEvents>, ConversationEvents extends AbstractChatEvents> { export abstract class AbstractChatManager<ManagerEvents extends AbstractChatManagerEvents<ConversationType>, ConversationType extends AbstractChat<ConversationEvents>, ConversationEvents extends AbstractConversationEvents> {
readonly events: Registry<ManagerEvents>; readonly events: Registry<ManagerEvents>;
readonly connection: ConnectionHandler; readonly connection: ConnectionHandler;
protected readonly listenerConnection: (() => void)[]; protected readonly listenerConnection: (() => void)[];
@ -351,6 +351,12 @@ export abstract class AbstractChatManager<ManagerEvents extends AbstractChatMana
private selectedConversation: ConversationType; private selectedConversation: ConversationType;
private currentUnreadCount: number; private currentUnreadCount: number;
/* FIXME: Access modifier */
public historyUiStates: {[id: string]: {
executingUIHistoryQuery: boolean,
historyErrorMessage: string | undefined,
historyRetryTimestamp: number
}} = {};
protected constructor(connection: ConnectionHandler) { protected constructor(connection: ConnectionHandler) {
this.events = new Registry<ManagerEvents>(); this.events = new Registry<ManagerEvents>();
@ -418,6 +424,8 @@ export abstract class AbstractChatManager<ManagerEvents extends AbstractChatMana
this.setSelectedConversation(undefined); this.setSelectedConversation(undefined);
} }
delete this.historyUiStates[conversation.getChatId()];
conversation.events.off("notify_unread_state_changed", this.listenerUnreadTimestamp); conversation.events.off("notify_unread_state_changed", this.listenerUnreadTimestamp);
delete this.conversations[conversation.getChatId()]; delete this.conversations[conversation.getChatId()];
this.events.fire("notify_conversation_destroyed", { conversation: conversation }); this.events.fire("notify_conversation_destroyed", { conversation: conversation });

View File

@ -1,11 +1,11 @@
import { import {
AbstractChat, AbstractChat,
AbstractChatEvents, AbstractConversationEvents,
AbstractChatManager, AbstractChatManager,
AbstractChatManagerEvents, AbstractChatManagerEvents,
kMaxChatFrameMessageSize kMaxChatFrameMessageSize
} from "./AbstractConversion"; } from "./AbstractConversion";
import {ChatMessage, ConversationHistoryResponse} from "tc-shared/ui/frames/side/ConversationDefinitions"; import {ChatMessage, ConversationHistoryResponse} from "../ui/frames/side/AbstractConversationDefinitions";
import {Settings} from "tc-shared/settings"; import {Settings} from "tc-shared/settings";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration"; import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {ErrorCode} from "tc-shared/connection/ErrorCode"; import {ErrorCode} from "tc-shared/connection/ErrorCode";
@ -17,7 +17,7 @@ import {LocalClientEntry} from "tc-shared/tree/Client";
import {ServerCommand} from "tc-shared/connection/ConnectionBase"; import {ServerCommand} from "tc-shared/connection/ConnectionBase";
import {ChannelConversationMode} from "tc-shared/tree/Channel"; import {ChannelConversationMode} from "tc-shared/tree/Channel";
export interface ChannelConversationEvents extends AbstractChatEvents { export interface ChannelConversationEvents extends AbstractConversationEvents {
notify_messages_deleted: { messages: string[] }, notify_messages_deleted: { messages: string[] },
notify_messages_loaded: {} notify_messages_loaded: {}
} }

View File

@ -2,7 +2,7 @@ import * as loader from "tc-loader";
import {Stage} from "tc-loader"; import {Stage} from "tc-loader";
import { tr } from "tc-shared/i18n/localize"; import { tr } from "tc-shared/i18n/localize";
import {LogCategory, logDebug, logError, logInfo, logWarn} from "tc-shared/log"; import {LogCategory, logDebug, logError, logInfo, logWarn} from "tc-shared/log";
import {ChatEvent} from "tc-shared/ui/frames/side/ConversationDefinitions"; import {ChatEvent} from "../ui/frames/side/AbstractConversationDefinitions";
const clientUniqueId2StoreName = uniqueId => "conversation-" + uniqueId; const clientUniqueId2StoreName = uniqueId => "conversation-" + uniqueId;

View File

@ -1,11 +1,11 @@
import { import {
AbstractChat, AbstractChat,
AbstractChatEvents, AbstractConversationEvents,
AbstractChatManager, AbstractChatManager,
AbstractChatManagerEvents AbstractChatManagerEvents
} from "tc-shared/conversations/AbstractConversion"; } from "tc-shared/conversations/AbstractConversion";
import {ClientEntry} from "tc-shared/tree/Client"; import {ClientEntry} from "tc-shared/tree/Client";
import {ChatEvent, ChatMessage, ConversationHistoryResponse} from "tc-shared/ui/frames/side/ConversationDefinitions"; import {ChatEvent, ChatMessage, ConversationHistoryResponse} from "../ui/frames/side/AbstractConversationDefinitions";
import {ChannelTreeEvents} from "tc-shared/tree/ChannelTree"; import {ChannelTreeEvents} from "tc-shared/tree/ChannelTree";
import {queryConversationEvents, registerConversationEvent} from "tc-shared/conversations/PrivateConversationHistory"; import {queryConversationEvents, registerConversationEvent} from "tc-shared/conversations/PrivateConversationHistory";
import {LogCategory, logWarn} from "tc-shared/log"; import {LogCategory, logWarn} from "tc-shared/log";
@ -19,7 +19,7 @@ export type OutOfViewClient = {
let receivingEventUniqueIdIndex = 0; let receivingEventUniqueIdIndex = 0;
export interface PrivateConversationEvents extends AbstractChatEvents { export interface PrivateConversationEvents extends AbstractConversationEvents {
notify_partner_typing: {}, notify_partner_typing: {},
notify_partner_changed: { notify_partner_changed: {
chatId: string, chatId: string,

View File

@ -412,7 +412,7 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
callback: () => { callback: () => {
const conversation = this.channelTree.client.getChannelConversations().findOrCreateConversation(this.getChannelId()); const conversation = this.channelTree.client.getChannelConversations().findOrCreateConversation(this.getChannelId());
this.channelTree.client.getChannelConversations().setSelectedConversation(conversation); this.channelTree.client.getChannelConversations().setSelectedConversation(conversation);
this.channelTree.client.side_bar.showChannelConversations(); this.channelTree.client.getSideBar().showChannelConversations();
}, },
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT) visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)
}, { }, {

View File

@ -168,23 +168,22 @@ export class ChannelTree {
if(this.selectedEntry instanceof ClientEntry) { if(this.selectedEntry instanceof ClientEntry) {
if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CLIENT)) { if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CLIENT)) {
if(this.selectedEntry instanceof MusicClientEntry) { if(this.selectedEntry instanceof MusicClientEntry) {
this.client.side_bar.showMusicPlayer(this.selectedEntry); this.client.getSideBar().showMusicPlayer(this.selectedEntry);
} else { } else {
this.client.side_bar.showClientInfo(this.selectedEntry); this.client.getSideBar().showClientInfo(this.selectedEntry);
} }
} }
} else if(this.selectedEntry instanceof ChannelEntry) { } else if(this.selectedEntry instanceof ChannelEntry) {
if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)) { if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)) {
const conversation = this.client.getChannelConversations().findOrCreateConversation(this.selectedEntry.channelId); const conversation = this.client.getChannelConversations().findOrCreateConversation(this.selectedEntry.channelId);
this.client.getChannelConversations().setSelectedConversation(conversation); this.client.getChannelConversations().setSelectedConversation(conversation);
this.client.side_bar.showChannelConversations(); this.client.getSideBar().showChannelConversations();
} }
} else if(this.selectedEntry instanceof ServerEntry) { } else if(this.selectedEntry instanceof ServerEntry) {
if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)) { if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)) {
const sidebar = this.client.side_bar;
const conversation = this.client.getChannelConversations().findOrCreateConversation(0); const conversation = this.client.getChannelConversations().findOrCreateConversation(0);
this.client.getChannelConversations().setSelectedConversation(conversation); this.client.getChannelConversations().setSelectedConversation(conversation);
sidebar.showChannelConversations(); this.client.getSideBar().showChannelConversations()
} }
} }
} }

View File

@ -382,7 +382,7 @@ export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
type: contextmenu.MenuEntryType.ENTRY, type: contextmenu.MenuEntryType.ENTRY,
name: this.properties.client_type_exact === ClientType.CLIENT_MUSIC ? tr("Show bot info") : tr("Show client info"), name: this.properties.client_type_exact === ClientType.CLIENT_MUSIC ? tr("Show bot info") : tr("Show client info"),
callback: () => { callback: () => {
this.channelTree.client.side_bar.showClientInfo(this); this.channelTree.client.getSideBar().showClientInfo(this);
}, },
icon_class: "client-about", icon_class: "client-about",
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CLIENT) visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CLIENT)
@ -519,12 +519,13 @@ export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
open_text_chat() { open_text_chat() {
const privateConversations = this.channelTree.client.getPrivateConversations(); const privateConversations = this.channelTree.client.getPrivateConversations();
const sideBar = this.channelTree.client.side_bar;
const conversation = privateConversations.findOrCreateConversation(this); const conversation = privateConversations.findOrCreateConversation(this);
conversation.setActiveClientEntry(this); conversation.setActiveClientEntry(this);
privateConversations.setSelectedConversation(conversation); privateConversations.setSelectedConversation(conversation);
sideBar.showPrivateConversations();
sideBar.privateConversationsController().focusInput(); this.channelTree.client.getSideBar().showPrivateConversations();
/* FIXME: Draw focus to the input box! */
//sideBar.privateConversationsController().focusInput();
} }
showContextMenu(x: number, y: number, on_close: () => void = undefined) { showContextMenu(x: number, y: number, on_close: () => void = undefined) {

View File

@ -193,7 +193,7 @@ export class ServerEntry extends ChannelTreeEntry<ServerEvents> {
name: tr("Join server text channel"), name: tr("Join server text channel"),
callback: () => { callback: () => {
this.channelTree.client.getChannelConversations().setSelectedConversation(this.channelTree.client.getChannelConversations().findOrCreateConversation(0)); this.channelTree.client.getChannelConversations().setSelectedConversation(this.channelTree.client.getChannelConversations().findOrCreateConversation(0));
this.channelTree.client.side_bar.showChannelConversations(); this.channelTree.client.getSideBar().showChannelConversations();
}, },
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT) visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)
}, { }, {

View File

@ -1,61 +1,64 @@
import {ClientEntry, MusicClientEntry} from "../../tree/Client";
import {ConnectionHandler} from "../../ConnectionHandler"; import {ConnectionHandler} from "../../ConnectionHandler";
import {MusicInfo} from "../../ui/frames/side/music_info";
import {ChannelConversationController} from "./side/ChannelConversationController"; import {ChannelConversationController} from "./side/ChannelConversationController";
import {PrivateConversationController} from "./side/PrivateConversationController"; import {PrivateConversationController} from "./side/PrivateConversationController";
import {ClientInfoController} from "tc-shared/ui/frames/side/ClientInfoController"; import {ClientInfoController} from "tc-shared/ui/frames/side/ClientInfoController";
import {SideHeader} from "tc-shared/ui/frames/side/HeaderController"; import {SideHeaderController} from "tc-shared/ui/frames/side/HeaderController";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import {SideBarRenderer} from "tc-shared/ui/frames/SideBarRenderer"; import {SideBarRenderer} from "tc-shared/ui/frames/SideBarRenderer";
import * as React from "react"; import * as React from "react";
import {SideBarEvents, SideBarType} from "tc-shared/ui/frames/SideBarDefinitions"; import {SideBarEvents, SideBarType} from "tc-shared/ui/frames/SideBarDefinitions";
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import {LogCategory, logWarn} from "tc-shared/log";
const cssStyle = require("./SideBar.scss"); export class SideBarController {
private readonly uiEvents: Registry<SideBarEvents>;
export class Frame { private currentConnection: ConnectionHandler;
readonly handle: ConnectionHandler; private listenerConnection: (() => void)[];
private htmlTag: HTMLDivElement;
private currentType: SideBarType; private header: SideHeaderController;
private uiEvents: Registry<SideBarEvents>;
private header: SideHeader;
private musicInfo: MusicInfo;
private clientInfo: ClientInfoController; private clientInfo: ClientInfoController;
private channelConversations: ChannelConversationController; private channelConversations: ChannelConversationController;
private privateConversations: PrivateConversationController; private privateConversations: PrivateConversationController;
constructor(handle: ConnectionHandler) { constructor() {
this.handle = handle; this.listenerConnection = [];
this.currentType = "none";
this.uiEvents = new Registry<SideBarEvents>(); this.uiEvents = new Registry<SideBarEvents>();
this.uiEvents.on("query_content", () => this.uiEvents.fire_react("notify_content", { content: this.currentType })); this.uiEvents.on("query_content", () => this.sendContent());
this.uiEvents.on("query_content_data", event => this.sendContentData(event.content)); this.uiEvents.on("query_content_data", event => this.sendContentData(event.content));
this.privateConversations = new PrivateConversationController(handle); this.privateConversations = new PrivateConversationController();
this.channelConversations = new ChannelConversationController(handle); this.channelConversations = new ChannelConversationController();
this.clientInfo = new ClientInfoController(handle); this.clientInfo = new ClientInfoController();
this.musicInfo = new MusicInfo(this); this.header = new SideHeaderController();
this.header = new SideHeader();
this.handle.events().one("notify_handler_initialized", () => this.header.setConnectionHandler(handle));
this.createHtmlTag();
this.showChannelConversations();
} }
html_tag() : HTMLDivElement { return this.htmlTag; } setConnection(connection: ConnectionHandler) {
if(this.currentConnection === connection) {
return;
}
this.listenerConnection.forEach(callback => callback());
this.listenerConnection = [];
this.currentConnection = connection;
this.header.setConnectionHandler(connection);
this.clientInfo.setConnectionHandler(connection);
this.channelConversations.setConnectionHandler(connection);
this.privateConversations.setConnectionHandler(connection);
if(connection) {
this.listenerConnection.push(connection.getSideBar().events.on("notify_content_type_changed", () => this.sendContent()));
}
this.sendContent();
}
destroy() { destroy() {
this.header?.destroy(); this.header?.destroy();
this.header = undefined; this.header = undefined;
this.htmlTag && this.htmlTag.remove();
this.htmlTag = undefined;
this.clientInfo?.destroy(); this.clientInfo?.destroy();
this.clientInfo = undefined; this.clientInfo = undefined;
@ -64,51 +67,21 @@ export class Frame {
this.channelConversations?.destroy(); this.channelConversations?.destroy();
this.channelConversations = undefined; this.channelConversations = undefined;
this.musicInfo && this.musicInfo.destroy();
this.musicInfo = undefined;
this.privateConversations && this.privateConversations.destroy();
this.privateConversations = undefined;
this.channelConversations && this.channelConversations.destroy();
this.channelConversations = undefined;
} }
renderInto(container: HTMLDivElement) { renderInto(container: HTMLDivElement) {
ReactDOM.render(React.createElement(SideBarRenderer, { ReactDOM.render(React.createElement(SideBarRenderer, {
key: this.handle.handlerId,
handlerId: this.handle.handlerId,
events: this.uiEvents, events: this.uiEvents,
eventsHeader: this.header["uiEvents"], eventsHeader: this.header["uiEvents"],
}), container); }), container);
} }
private createHtmlTag() { private sendContent() {
this.htmlTag = document.createElement("div"); if(this.currentConnection) {
this.htmlTag.classList.add(cssStyle.container); this.uiEvents.fire("notify_content", { content: this.currentConnection.getSideBar().getSideBarContent() });
} else {
this.uiEvents.fire("notify_content", { content: "none" });
} }
privateConversationsController() : PrivateConversationController {
return this.privateConversations;
}
getClientInfo() : ClientInfoController {
return this.clientInfo;
}
music_info() : MusicInfo {
return this.musicInfo;
}
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) { private sendContentData(content: SideBarType) {
@ -121,26 +94,41 @@ export class Frame {
break; break;
case "channel-chat": case "channel-chat":
if(!this.currentConnection) {
logWarn(LogCategory.GENERAL, tr("Received channel chat content data request without an active connection."));
return;
}
this.uiEvents.fire_react("notify_content_data", { this.uiEvents.fire_react("notify_content_data", {
content: "channel-chat", content: "channel-chat",
data: { data: {
events: this.channelConversations["uiEvents"], events: this.channelConversations["uiEvents"],
handlerId: this.handle.handlerId handlerId: this.currentConnection.handlerId
} }
}); });
break; break;
case "private-chat": case "private-chat":
if(!this.currentConnection) {
logWarn(LogCategory.GENERAL, tr("Received private chat content data request without an active connection."));
return;
}
this.uiEvents.fire_react("notify_content_data", { this.uiEvents.fire_react("notify_content_data", {
content: "private-chat", content: "private-chat",
data: { data: {
events: this.privateConversations["uiEvents"], events: this.privateConversations["uiEvents"],
handlerId: this.handle.handlerId handlerId: this.currentConnection.handlerId
} }
}); });
break; break;
case "client-info": case "client-info":
if(!this.currentConnection) {
logWarn(LogCategory.GENERAL, tr("Received client info content data request without an active connection."));
return;
}
this.uiEvents.fire_react("notify_content_data", { this.uiEvents.fire_react("notify_content_data", {
content: "client-info", content: "client-info",
data: { data: {
@ -150,6 +138,11 @@ export class Frame {
break; break;
case "music-manage": case "music-manage":
if(!this.currentConnection) {
logWarn(LogCategory.GENERAL, tr("Received music bot content data request without an active connection."));
return;
}
this.uiEvents.fire_react("notify_content_data", { this.uiEvents.fire_react("notify_content_data", {
content: "music-manage", content: "music-manage",
data: { } data: { }
@ -157,26 +150,4 @@ export class Frame {
break; break;
} }
} }
showPrivateConversations() {
this.setCurrentContent("private-chat");
}
showChannelConversations() {
this.setCurrentContent("channel-chat");
}
showClientInfo(client: ClientEntry) {
this.clientInfo.setClient(client);
this.setCurrentContent("client-info");
}
showMusicPlayer(client: MusicClientEntry) {
this.musicInfo.set_current_bot(client);
this.setCurrentContent("music-manage");
}
clearSideBar() {
this.setCurrentContent("none");
}
} }

View File

@ -1,7 +1,8 @@
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import {PrivateConversationUIEvents} from "tc-shared/ui/frames/side/PrivateConversationDefinitions"; import {PrivateConversationUIEvents} from "tc-shared/ui/frames/side/PrivateConversationDefinitions";
import {ConversationUIEvents} from "tc-shared/ui/frames/side/ConversationDefinitions"; import {AbstractConversationUiEvents} from "./side/AbstractConversationDefinitions";
import {ClientInfoEvents} from "tc-shared/ui/frames/side/ClientInfoDefinitions"; import {ClientInfoEvents} from "tc-shared/ui/frames/side/ClientInfoDefinitions";
import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions";
/* TODO: Somehow outsource the event registries to IPC? */ /* TODO: Somehow outsource the event registries to IPC? */
@ -9,7 +10,7 @@ export type SideBarType = "none" | "channel-chat" | "private-chat" | "client-inf
export interface SideBarTypeData { export interface SideBarTypeData {
"none": {}, "none": {},
"channel-chat": { "channel-chat": {
events: Registry<ConversationUIEvents>, events: Registry<AbstractConversationUiEvents>,
handlerId: string handlerId: string
}, },
"private-chat": { "private-chat": {
@ -32,7 +33,9 @@ export type SideBarNotifyContentData<T extends SideBarType> = {
export interface SideBarEvents { export interface SideBarEvents {
query_content: {}, query_content: {},
query_content_data: { content: SideBarType }, query_content_data: { content: SideBarType },
query_header_data: {},
notify_content: { content: SideBarType }, notify_content: { content: SideBarType },
notify_content_data: SideBarNotifyContentData<SideBarType> notify_content_data: SideBarNotifyContentData<SideBarType>,
notify_header_data: { events: Registry<SideHeaderEvents> }
} }

View File

@ -8,7 +8,7 @@ import {useContext, useState} from "react";
import {ClientInfoRenderer} from "tc-shared/ui/frames/side/ClientInfoRenderer"; import {ClientInfoRenderer} from "tc-shared/ui/frames/side/ClientInfoRenderer";
import {PrivateConversationsPanel} from "tc-shared/ui/frames/side/PrivateConversationRenderer"; import {PrivateConversationsPanel} from "tc-shared/ui/frames/side/PrivateConversationRenderer";
const cssStyle = require("./SideBar.scss"); const cssStyle = require("./SideBarRenderer.scss");
const EventContent = React.createContext<Registry<SideBarEvents>>(undefined); const EventContent = React.createContext<Registry<SideBarEvents>>(undefined);
@ -109,7 +109,6 @@ const SideBarHeader = (props: { type: SideBarType, eventsHeader: Registry<SideHe
} }
export const SideBarRenderer = (props: { export const SideBarRenderer = (props: {
handlerId: string,
events: Registry<SideBarEvents>, events: Registry<SideBarEvents>,
eventsHeader: Registry<SideHeaderEvents> eventsHeader: Registry<SideHeaderEvents>
}) => { }) => {

View File

@ -1,14 +1,14 @@
import { import {
ChatHistoryState, ChatHistoryState,
ConversationUIEvents AbstractConversationUiEvents
} from "../../../ui/frames/side/ConversationDefinitions"; } from "./AbstractConversationDefinitions";
import {EventHandler, Registry} from "../../../events"; import {EventHandler, Registry} from "../../../events";
import * as log from "../../../log"; import * as log from "../../../log";
import {LogCategory} from "../../../log"; import {LogCategory} from "../../../log";
import {tra, tr} from "../../../i18n/localize"; import {tra, tr} from "../../../i18n/localize";
import { import {
AbstractChat, AbstractChat,
AbstractChatEvents, AbstractConversationEvents,
AbstractChatManager, AbstractChatManager,
AbstractChatManagerEvents AbstractChatManagerEvents
} from "tc-shared/conversations/AbstractConversion"; } from "tc-shared/conversations/AbstractConversion";
@ -16,50 +16,25 @@ import {
export const kMaxChatFrameMessageSize = 50; /* max 100 messages, since the server does not support more than 100 messages queried at once */ export const kMaxChatFrameMessageSize = 50; /* max 100 messages, since the server does not support more than 100 messages queried at once */
export abstract class AbstractConversationController< export abstract class AbstractConversationController<
Events extends ConversationUIEvents, Events extends AbstractConversationUiEvents,
Manager extends AbstractChatManager<ManagerEvents, ConversationType, ConversationEvents>, Manager extends AbstractChatManager<ManagerEvents, ConversationType, ConversationEvents>,
ManagerEvents extends AbstractChatManagerEvents<ConversationType>, ManagerEvents extends AbstractChatManagerEvents<ConversationType>,
ConversationType extends AbstractChat<ConversationEvents>, ConversationType extends AbstractChat<ConversationEvents>,
ConversationEvents extends AbstractChatEvents ConversationEvents extends AbstractConversationEvents
> { > {
protected readonly uiEvents: Registry<Events>; protected readonly uiEvents: Registry<Events>;
protected readonly conversationManager: Manager; protected conversationManager: Manager | undefined;
protected readonly listenerManager: (() => void)[]; protected listenerManager: (() => void)[];
private historyUiStates: {[id: string]: {
executingUIHistoryQuery: boolean,
historyErrorMessage: string | undefined,
historyRetryTimestamp: number
}} = {};
protected currentSelectedConversation: ConversationType; protected currentSelectedConversation: ConversationType;
protected currentSelectedListener: (() => void)[]; protected currentSelectedListener: (() => void)[];
protected crossChannelChatSupported = true; protected crossChannelChatSupported = true;
protected constructor(conversationManager: Manager) { protected constructor() {
this.uiEvents = new Registry<Events>(); this.uiEvents = new Registry<Events>();
this.currentSelectedListener = []; this.currentSelectedListener = [];
this.conversationManager = conversationManager;
this.listenerManager = []; this.listenerManager = [];
this.listenerManager.push(this.conversationManager.events.on("notify_selected_changed", event => {
this.currentSelectedListener.forEach(callback => callback());
this.currentSelectedListener = [];
this.currentSelectedConversation = event.newConversation;
this.uiEvents.fire_react("notify_selected_chat", { chatId: event.newConversation ? event.newConversation.getChatId() : "unselected" });
const conversation = event.newConversation;
if(conversation) {
this.registerConversationEvents(conversation);
}
}));
this.listenerManager.push(this.conversationManager.events.on("notify_conversation_destroyed", event => {
delete this.historyUiStates[event.conversation.getChatId()];
}));
} }
destroy() { destroy() {
@ -70,6 +45,31 @@ export abstract class AbstractConversationController<
this.uiEvents.destroy(); this.uiEvents.destroy();
} }
getUiEvents() : Registry<Events> {
return this.uiEvents;
}
protected setConversationManager(manager: Manager | undefined) {
if(this.conversationManager === manager) {
return;
}
this.listenerManager.forEach(callback => callback());
this.listenerManager = [];
this.conversationManager = manager;
if(manager) {
this.registerConversationManagerEvents(manager);
this.setCurrentlySelected(manager.getSelectedConversation());
} else {
this.setCurrentlySelected(undefined);
}
}
protected registerConversationManagerEvents(manager: Manager) {
this.listenerManager.push(manager.events.on("notify_selected_changed", event => this.setCurrentlySelected(event.newConversation)));
}
protected registerConversationEvents(conversation: ConversationType) { protected registerConversationEvents(conversation: ConversationType) {
this.currentSelectedListener.push(conversation.events.on("notify_unread_timestamp_changed", event => this.currentSelectedListener.push(conversation.events.on("notify_unread_timestamp_changed", event =>
this.uiEvents.fire_react("notify_unread_timestamp_changed", { chatId: conversation.getChatId(), timestamp: event.timestamp }))); this.uiEvents.fire_react("notify_unread_timestamp_changed", { chatId: conversation.getChatId(), timestamp: event.timestamp })));
@ -94,13 +94,30 @@ export abstract class AbstractConversationController<
})); }));
} }
protected setCurrentlySelected(conversation: ConversationType | undefined) {
if(this.currentSelectedConversation === conversation) {
return;
}
this.currentSelectedListener.forEach(callback => callback());
this.currentSelectedListener = [];
this.currentSelectedConversation = conversation;
this.uiEvents.fire_react("notify_selected_chat", { chatId: conversation ? conversation.getChatId() : "unselected" });
if(conversation) {
this.registerConversationEvents(conversation);
}
}
/* TODO: Is this even a thing? */
handlePanelShow() { handlePanelShow() {
this.uiEvents.fire_react("notify_panel_show"); this.uiEvents.fire_react("notify_panel_show");
} }
protected reportStateToUI(conversation: AbstractChat<any>) { protected reportStateToUI(conversation: AbstractChat<any>) {
let historyState: ChatHistoryState; let historyState: ChatHistoryState;
const localHistoryState = this.historyUiStates[conversation.getChatId()]; const localHistoryState = this.conversationManager.historyUiStates[conversation.getChatId()];
if(!localHistoryState) { if(!localHistoryState) {
historyState = conversation.hasHistory() ? "available" : "none"; historyState = conversation.hasHistory() ? "available" : "none";
} else { } else {
@ -171,7 +188,7 @@ export abstract class AbstractConversationController<
} }
} }
public uiQueryHistory(conversation: AbstractChat<any>, timestamp: number, enforce?: boolean) { public uiQueryHistory(conversation: AbstractChat<any>, timestamp: number, enforce?: boolean) {
const localHistoryState = this.historyUiStates[conversation.getChatId()] || (this.historyUiStates[conversation.getChatId()] = { const localHistoryState = this.conversationManager.historyUiStates[conversation.getChatId()] || (this.conversationManager.historyUiStates[conversation.getChatId()] = {
executingUIHistoryQuery: false, executingUIHistoryQuery: false,
historyErrorMessage: undefined, historyErrorMessage: undefined,
historyRetryTimestamp: 0 historyRetryTimestamp: 0
@ -242,13 +259,13 @@ export abstract class AbstractConversationController<
this.crossChannelChatSupported = flag; this.crossChannelChatSupported = flag;
const currentConversation = this.getCurrentConversation(); const currentConversation = this.getCurrentConversation();
if(currentConversation) { if(currentConversation) {
this.reportStateToUI(this.getCurrentConversation()); this.reportStateToUI(currentConversation);
} }
} }
@EventHandler<ConversationUIEvents>("query_conversation_state") @EventHandler<AbstractConversationUiEvents>("query_conversation_state")
protected handleQueryConversationState(event: ConversationUIEvents["query_conversation_state"]) { protected handleQueryConversationState(event: AbstractConversationUiEvents["query_conversation_state"]) {
const conversation = this.conversationManager.findConversationById(event.chatId); const conversation = this.conversationManager?.findConversationById(event.chatId);
if(!conversation) { if(!conversation) {
this.uiEvents.fire_react("notify_conversation_state", { this.uiEvents.fire_react("notify_conversation_state", {
state: "error", state: "error",
@ -267,9 +284,9 @@ export abstract class AbstractConversationController<
} }
} }
@EventHandler<ConversationUIEvents>("query_conversation_history") @EventHandler<AbstractConversationUiEvents>("query_conversation_history")
protected handleQueryHistory(event: ConversationUIEvents["query_conversation_history"]) { protected handleQueryHistory(event: AbstractConversationUiEvents["query_conversation_history"]) {
const conversation = this.conversationManager.findConversationById(event.chatId); const conversation = this.conversationManager?.findConversationById(event.chatId);
if(!conversation) { if(!conversation) {
this.uiEvents.fire_react("notify_conversation_history", { this.uiEvents.fire_react("notify_conversation_history", {
state: "error", state: "error",
@ -286,15 +303,15 @@ export abstract class AbstractConversationController<
this.uiQueryHistory(conversation, event.timestamp); this.uiQueryHistory(conversation, event.timestamp);
} }
@EventHandler<ConversationUIEvents>(["action_clear_unread_flag", "action_self_typing"]) @EventHandler<AbstractConversationUiEvents>(["action_clear_unread_flag", "action_self_typing"])
protected handleClearUnreadFlag(event: ConversationUIEvents["action_clear_unread_flag" | "action_self_typing"]) { protected handleClearUnreadFlag(event: AbstractConversationUiEvents["action_clear_unread_flag" | "action_self_typing"]) {
const conversation = this.conversationManager.findConversationById(event.chatId); const conversation = this.conversationManager?.findConversationById(event.chatId);
conversation?.setUnreadTimestamp(Date.now()); conversation?.setUnreadTimestamp(Date.now());
} }
@EventHandler<ConversationUIEvents>("action_send_message") @EventHandler<AbstractConversationUiEvents>("action_send_message")
protected handleSendMessage(event: ConversationUIEvents["action_send_message"]) { protected handleSendMessage(event: AbstractConversationUiEvents["action_send_message"]) {
const conversation = this.conversationManager.findConversationById(event.chatId); const conversation = this.conversationManager?.findConversationById(event.chatId);
if(!conversation) { if(!conversation) {
log.error(LogCategory.CLIENT, tr("Tried to send a chat message to an unknown conversation with id %s"), event.chatId); log.error(LogCategory.CLIENT, tr("Tried to send a chat message to an unknown conversation with id %s"), event.chatId);
return; return;
@ -303,9 +320,9 @@ export abstract class AbstractConversationController<
conversation.sendMessage(event.text); conversation.sendMessage(event.text);
} }
@EventHandler<ConversationUIEvents>("action_jump_to_present") @EventHandler<AbstractConversationUiEvents>("action_jump_to_present")
protected handleJumpToPresent(event: ConversationUIEvents["action_jump_to_present"]) { protected handleJumpToPresent(event: AbstractConversationUiEvents["action_jump_to_present"]) {
const conversation = this.conversationManager.findConversationById(event.chatId); const conversation = this.conversationManager?.findConversationById(event.chatId);
if(!conversation) { if(!conversation) {
log.error(LogCategory.CLIENT, tr("Tried to jump to present for an unknown conversation with id %s"), event.chatId); log.error(LogCategory.CLIENT, tr("Tried to jump to present for an unknown conversation with id %s"), event.chatId);
return; return;
@ -314,14 +331,14 @@ export abstract class AbstractConversationController<
this.reportStateToUI(conversation); this.reportStateToUI(conversation);
} }
@EventHandler<ConversationUIEvents>("query_selected_chat") @EventHandler<AbstractConversationUiEvents>("query_selected_chat")
private handleQuerySelectedChat() { private handleQuerySelectedChat() {
this.uiEvents.fire_react("notify_selected_chat", { chatId: this.currentSelectedConversation ? this.currentSelectedConversation.getChatId() : "unselected"}) this.uiEvents.fire_react("notify_selected_chat", { chatId: this.currentSelectedConversation ? this.currentSelectedConversation.getChatId() : "unselected"});
} }
@EventHandler<ConversationUIEvents>("action_select_chat") @EventHandler<AbstractConversationUiEvents>("action_select_chat")
private handleActionSelectChat(event: ConversationUIEvents["action_select_chat"]) { private handleActionSelectChat(event: AbstractConversationUiEvents["action_select_chat"]) {
const conversation = this.conversationManager.findConversationById(event.chatId); const conversation = this.conversationManager?.findConversationById(event.chatId);
this.conversationManager.setSelectedConversation(conversation); this.conversationManager.setSelectedConversation(conversation);
} }
} }

View File

@ -111,7 +111,7 @@ export interface ChatStatePrivate {
export type ChatStateData = ChatStateNormal | ChatStateNoPermissions | ChatStateError | ChatStateLoading | ChatStatePrivate; export type ChatStateData = ChatStateNormal | ChatStateNoPermissions | ChatStateError | ChatStateLoading | ChatStatePrivate;
export interface ConversationUIEvents { export interface AbstractConversationUiEvents {
action_select_chat: { chatId: "unselected" | string }, action_select_chat: { chatId: "unselected" | string },
action_clear_unread_flag: { chatId: string }, action_clear_unread_flag: { chatId: string },
action_self_typing: { chatId: string }, action_self_typing: { chatId: string },

View File

@ -15,8 +15,8 @@ import {
ChatEventPartnerAction, ChatEventPartnerAction,
ChatHistoryState, ChatHistoryState,
ChatMessage, ChatMessage,
ConversationUIEvents, ChatEventModeChanged AbstractConversationUiEvents, ChatEventModeChanged
} from "tc-shared/ui/frames/side/ConversationDefinitions"; } from "./AbstractConversationDefinitions";
import {TimestampRenderer} from "tc-shared/ui/react-elements/TimestampRenderer"; import {TimestampRenderer} from "tc-shared/ui/react-elements/TimestampRenderer";
import {BBCodeRenderer} from "tc-shared/text/bbcode"; import {BBCodeRenderer} from "tc-shared/text/bbcode";
import {getGlobalAvatarManagerFactory} from "tc-shared/file/Avatars"; import {getGlobalAvatarManagerFactory} from "tc-shared/file/Avatars";
@ -34,7 +34,7 @@ const ChatMessageTextRenderer = React.memo((props: { text: string }) => {
const ChatEventMessageRenderer = React.memo((props: { const ChatEventMessageRenderer = React.memo((props: {
message: ChatMessage, message: ChatMessage,
callbackDelete?: () => void, callbackDelete?: () => void,
events: Registry<ConversationUIEvents>, events: Registry<AbstractConversationUiEvents>,
handlerId: string, handlerId: string,
refHTMLElement?: Ref<HTMLDivElement> refHTMLElement?: Ref<HTMLDivElement>
@ -126,7 +126,7 @@ const UnreadEntry = (props: { refDiv: React.Ref<HTMLDivElement> }) => (
</div> </div>
); );
const LoadOderMessages = (props: { events: Registry<ConversationUIEvents>, chatId: string, state: ChatHistoryState | "error", errorMessage?: string, retryTimestamp?: number, timestamp: number | undefined }) => { const LoadOderMessages = (props: { events: Registry<AbstractConversationUiEvents>, chatId: string, state: ChatHistoryState | "error", errorMessage?: string, retryTimestamp?: number, timestamp: number | undefined }) => {
if(props.state === "none") if(props.state === "none")
return null; return null;
@ -172,7 +172,7 @@ const LoadOderMessages = (props: { events: Registry<ConversationUIEvents>, chatI
) )
}; };
const JumpToPresent = (props: { events: Registry<ConversationUIEvents>, chatId: string }) => ( const JumpToPresent = (props: { events: Registry<AbstractConversationUiEvents>, chatId: string }) => (
<div <div
className={cssStyle.containerLoadMessages + " " + cssStyle.present} className={cssStyle.containerLoadMessages + " " + cssStyle.present}
onClick={() => props.events.fire("action_jump_to_present", { chatId: props.chatId })} onClick={() => props.events.fire("action_jump_to_present", { chatId: props.chatId })}
@ -305,7 +305,7 @@ const ChatEventModeChangedRenderer = (props: { event: ChatEventModeChanged, refH
} }
} }
const PartnerTypingIndicator = (props: { events: Registry<ConversationUIEvents>, chatId: string, timeout?: number }) => { const PartnerTypingIndicator = (props: { events: Registry<AbstractConversationUiEvents>, chatId: string, timeout?: number }) => {
const kTypingTimeout = props.timeout || 5000; const kTypingTimeout = props.timeout || 5000;
@ -349,7 +349,7 @@ const PartnerTypingIndicator = (props: { events: Registry<ConversationUIEvents>,
}; };
interface ConversationMessagesProperties { interface ConversationMessagesProperties {
events: Registry<ConversationUIEvents>; events: Registry<AbstractConversationUiEvents>;
handlerId: string; handlerId: string;
noFirstMessageOverlay?: boolean noFirstMessageOverlay?: boolean
@ -698,8 +698,8 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
} }
} }
@EventHandler<ConversationUIEvents>("notify_selected_chat") @EventHandler<AbstractConversationUiEvents>("notify_selected_chat")
private handleNotifySelectedChat(event: ConversationUIEvents["notify_selected_chat"]) { private handleNotifySelectedChat(event: AbstractConversationUiEvents["notify_selected_chat"]) {
if(this.currentChatId === event.chatId) { if(this.currentChatId === event.chatId) {
return; return;
} }
@ -718,8 +718,8 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
} }
} }
@EventHandler<ConversationUIEvents>("notify_conversation_state") @EventHandler<AbstractConversationUiEvents>("notify_conversation_state")
private handleConversationStateUpdate(event: ConversationUIEvents["notify_conversation_state"]) { private handleConversationStateUpdate(event: AbstractConversationUiEvents["notify_conversation_state"]) {
if(event.chatId !== this.currentChatId) if(event.chatId !== this.currentChatId)
return; return;
@ -771,8 +771,8 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
} }
} }
@EventHandler<ConversationUIEvents>("notify_chat_event") @EventHandler<AbstractConversationUiEvents>("notify_chat_event")
private handleChatEvent(event: ConversationUIEvents["notify_chat_event"]) { private handleChatEvent(event: AbstractConversationUiEvents["notify_chat_event"]) {
if(event.chatId !== this.currentChatId || this.state.isBrowsingHistory) if(event.chatId !== this.currentChatId || this.state.isBrowsingHistory)
return; return;
@ -793,8 +793,8 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
this.forceUpdate(() => this.scrollToBottom()); this.forceUpdate(() => this.scrollToBottom());
} }
@EventHandler<ConversationUIEvents>("notify_chat_message_delete") @EventHandler<AbstractConversationUiEvents>("notify_chat_message_delete")
private handleMessageDeleted(event: ConversationUIEvents["notify_chat_message_delete"]) { private handleMessageDeleted(event: AbstractConversationUiEvents["notify_chat_message_delete"]) {
if(event.chatId !== this.currentChatId) { if(event.chatId !== this.currentChatId) {
return; return;
} }
@ -805,8 +805,8 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
this.forceUpdate(() => this.scrollToBottom()); this.forceUpdate(() => this.scrollToBottom());
} }
@EventHandler<ConversationUIEvents>("notify_unread_timestamp_changed") @EventHandler<AbstractConversationUiEvents>("notify_unread_timestamp_changed")
private handleUnreadTimestampChanged(event: ConversationUIEvents["notify_unread_timestamp_changed"]) { private handleUnreadTimestampChanged(event: AbstractConversationUiEvents["notify_unread_timestamp_changed"]) {
if (event.chatId !== this.currentChatId) if (event.chatId !== this.currentChatId)
return; return;
@ -823,13 +823,13 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
} }
} }
@EventHandler<ConversationUIEvents>("notify_panel_show") @EventHandler<AbstractConversationUiEvents>("notify_panel_show")
private handlePanelShow() { private handlePanelShow() {
this.fixScroll(); this.fixScroll();
} }
@EventHandler<ConversationUIEvents>("query_conversation_history") @EventHandler<AbstractConversationUiEvents>("query_conversation_history")
private handleQueryConversationHistory(event: ConversationUIEvents["query_conversation_history"]) { private handleQueryConversationHistory(event: AbstractConversationUiEvents["query_conversation_history"]) {
if (event.chatId !== this.currentChatId) if (event.chatId !== this.currentChatId)
return; return;
@ -838,8 +838,8 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
}); });
} }
@EventHandler<ConversationUIEvents>("notify_conversation_history") @EventHandler<AbstractConversationUiEvents>("notify_conversation_history")
private handleNotifyConversationHistory(event: ConversationUIEvents["notify_conversation_history"]) { private handleNotifyConversationHistory(event: AbstractConversationUiEvents["notify_conversation_history"]) {
if (event.chatId !== this.currentChatId) if (event.chatId !== this.currentChatId)
return; return;
@ -881,7 +881,7 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
} }
} }
export const ConversationPanel = React.memo((props: { events: Registry<ConversationUIEvents>, handlerId: string, messagesDeletable: boolean, noFirstMessageOverlay: boolean }) => { export const ConversationPanel = React.memo((props: { events: Registry<AbstractConversationUiEvents>, handlerId: string, messagesDeletable: boolean, noFirstMessageOverlay: boolean }) => {
const currentChat = useRef({ id: "unselected" }); const currentChat = useRef({ id: "unselected" });
const chatEnabled = useRef(false); const chatEnabled = useRef(false);

View File

@ -1,11 +1,9 @@
import * as React from "react";
import {ConnectionHandler, ConnectionState} from "../../../ConnectionHandler"; import {ConnectionHandler, ConnectionState} from "../../../ConnectionHandler";
import {EventHandler} from "../../../events"; import {EventHandler} from "../../../events";
import * as log from "../../../log"; import * as log from "../../../log";
import {LogCategory} from "../../../log"; import {LogCategory} from "../../../log";
import {tr} from "../../../i18n/localize"; import {tr} from "../../../i18n/localize";
import {ConversationUIEvents} from "../../../ui/frames/side/ConversationDefinitions"; import {AbstractConversationUiEvents} from "./AbstractConversationDefinitions";
import {ConversationPanel} from "./AbstractConversationRenderer";
import {AbstractConversationController} from "./AbstractConversationController"; import {AbstractConversationController} from "./AbstractConversationController";
import { import {
ChannelConversation, ChannelConversation,
@ -14,34 +12,22 @@ import {
ChannelConversationManagerEvents ChannelConversationManagerEvents
} from "tc-shared/conversations/ChannelConversationManager"; } from "tc-shared/conversations/ChannelConversationManager";
import {ServerFeature} from "tc-shared/connection/ServerFeatures"; import {ServerFeature} from "tc-shared/connection/ServerFeatures";
import ReactDOM = require("react-dom"); import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions";
export class ChannelConversationController extends AbstractConversationController< export class ChannelConversationController extends AbstractConversationController<
ConversationUIEvents, ChannelConversationUiEvents,
ChannelConversationManager, ChannelConversationManager,
ChannelConversationManagerEvents, ChannelConversationManagerEvents,
ChannelConversation, ChannelConversation,
ChannelConversationEvents ChannelConversationEvents
> { > {
readonly connection: ConnectionHandler; private connection: ConnectionHandler;
readonly htmlTag: HTMLDivElement; private connectionListener: (() => void)[];
constructor(connection: ConnectionHandler) { constructor() {
super(connection.getChannelConversations() as any); super();
this.connection = connection; this.connectionListener = [];
this.htmlTag = document.createElement("div");
this.htmlTag.style.display = "flex";
this.htmlTag.style.flexDirection = "column";
this.htmlTag.style.justifyContent = "stretch";
this.htmlTag.style.height = "100%";
ReactDOM.render(React.createElement(ConversationPanel, {
events: this.uiEvents,
handlerId: this.connection.handlerId,
noFirstMessageOverlay: false,
messagesDeletable: true
}), this.htmlTag);
/* /*
spawnExternalModal("conversation", this.uiEvents, { spawnExternalModal("conversation", this.uiEvents, {
handlerId: this.connection.handlerId, handlerId: this.connection.handlerId,
@ -52,7 +38,37 @@ export class ChannelConversationController extends AbstractConversationControlle
}); });
*/ */
this.uiEvents.on("notify_destroy", connection.events().on("notify_visibility_changed", event => { this.uiEvents.register_handler(this, true);
}
destroy() {
this.connectionListener.forEach(callback => callback());
this.connectionListener = [];
this.uiEvents.unregister_handler(this);
super.destroy();
}
setConnectionHandler(connection: ConnectionHandler) {
if(this.connection === connection) {
return;
}
this.connectionListener.forEach(callback => callback());
this.connectionListener = [];
this.connection = connection;
if(connection) {
this.initializeConnectionListener(connection);
/* FIXME: Update cross channel talk state! */
this.setConversationManager(connection.getChannelConversations());
} else {
this.setConversationManager(undefined);
}
}
private initializeConnectionListener(connection: ConnectionHandler) {
this.connectionListener.push(connection.events().on("notify_visibility_changed", event => {
if(!event.visible) { if(!event.visible) {
return; return;
} }
@ -60,9 +76,7 @@ export class ChannelConversationController extends AbstractConversationControlle
this.handlePanelShow(); this.handlePanelShow();
})); }));
this.uiEvents.register_handler(this, true); this.connectionListener.push(connection.events().on("notify_connection_state_changed", event => {
this.listenerManager.push(connection.events().on("notify_connection_state_changed", event => {
if(event.newState === ConnectionState.CONNECTED) { if(event.newState === ConnectionState.CONNECTED) {
connection.serverFeatures.awaitFeatures().then(success => { connection.serverFeatures.awaitFeatures().then(success => {
if(!success) { return; } if(!success) { return; }
@ -75,17 +89,9 @@ export class ChannelConversationController extends AbstractConversationControlle
})); }));
} }
destroy() { @EventHandler<AbstractConversationUiEvents>("action_delete_message")
ReactDOM.unmountComponentAtNode(this.htmlTag); private handleMessageDelete(event: AbstractConversationUiEvents["action_delete_message"]) {
this.htmlTag.remove(); const conversation = this.conversationManager?.findConversationById(event.chatId);
this.uiEvents.unregister_handler(this);
super.destroy();
}
@EventHandler<ConversationUIEvents>("action_delete_message")
private handleMessageDelete(event: ConversationUIEvents["action_delete_message"]) {
const conversation = this.conversationManager.findConversationById(event.chatId);
if(!conversation) { if(!conversation) {
log.error(LogCategory.CLIENT, tr("Tried to delete a chat message from an unknown conversation with id %s"), event.chatId); log.error(LogCategory.CLIENT, tr("Tried to delete a chat message from an unknown conversation with id %s"), event.chatId);
return; return;

View File

@ -0,0 +1,3 @@
import {AbstractConversationUiEvents} from "tc-shared/ui/frames/side/AbstractConversationDefinitions";
export interface ChannelConversationUiEvents extends AbstractConversationUiEvents {}

View File

@ -1,107 +1,23 @@
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler"; import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {ClientEntry, ClientType, LocalClientEntry} from "tc-shared/tree/Client";
import { import {
ClientForumInfo,
ClientGroupInfo, ClientGroupInfo,
ClientInfoEvents, ClientInfoType, ClientInfoEvents,
ClientStatusInfo,
ClientVersionInfo
} from "tc-shared/ui/frames/side/ClientInfoDefinitions"; } from "tc-shared/ui/frames/side/ClientInfoDefinitions";
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import * as i18nc from "../../../i18n/country";
import {openClientInfo} from "tc-shared/ui/modal/ModalClientInfo"; 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,
country: { name: string, flag: string },
volume: { volume: number, muted: boolean },
status: ClientStatusInfo,
forumAccount: ClientForumInfo | undefined,
channelGroup: number,
serverGroups: number[],
version: ClientVersionInfo
}
export interface ClientInfoControllerEvents {
notify_client_changed: {
newClient: ClientEntry | undefined
}
}
export class ClientInfoController { export class ClientInfoController {
readonly events: Registry<ClientInfoControllerEvents>;
private readonly connection: ConnectionHandler;
private readonly listenerConnection: (() => void)[];
private readonly uiEvents: Registry<ClientInfoEvents>; private readonly uiEvents: Registry<ClientInfoEvents>;
private listenerClient: (() => void)[]; private connection: ConnectionHandler;
private currentClient: ClientEntry | undefined; private listenerConnection: (() => void)[];
private currentClientStatus: CurrentClientInfo | undefined;
constructor(connection: ConnectionHandler) { constructor() {
this.connection = connection;
this.events = new Registry<ClientInfoControllerEvents>();
this.uiEvents = new Registry<ClientInfoEvents>(); this.uiEvents = new Registry<ClientInfoEvents>();
this.uiEvents.enableDebug("client-info"); this.uiEvents.enableDebug("client-info");
this.listenerConnection = []; this.listenerConnection = [];
this.listenerClient = [];
this.initialize();
}
private initialize() {
this.listenerConnection.push(this.connection.groups.events.on("notify_groups_updated", event => {
if(!this.currentClientStatus) {
return;
}
for(const update of event.updates) {
if(update.group.id === this.currentClientStatus.channelGroup) {
this.sendChannelGroup();
break;
}
}
for(const update of event.updates) {
if(this.currentClientStatus.serverGroups.indexOf(update.group.id) !== -1) {
this.sendServerGroups();
break;
}
}
}));
this.listenerConnection.push(this.connection.channelTree.events.on("notify_client_leave_view", event => {
if(event.client !== this.currentClient) {
return;
}
this.currentClientStatus.leaveTimestamp = Date.now() / 1000;
this.currentClientStatus.clientId = 0;
this.currentClient = undefined;
this.unregisterClientEvents();
this.sendOnline();
}));
this.listenerConnection.push(this.connection.events().on("notify_connection_state_changed", event => {
if(event.newState !== ConnectionState.CONNECTED && this.currentClientStatus) {
this.currentClient = undefined;
this.currentClientStatus.leaveTimestamp = Date.now() / 1000;
this.sendOnline();
}
}))
this.uiEvents.on("query_client", () => this.sendClient()); this.uiEvents.on("query_client", () => this.sendClient());
this.uiEvents.on("query_client_name", () => this.sendClientName()); this.uiEvents.on("query_client_name", () => this.sendClientName());
this.uiEvents.on("query_client_description", () => this.sendClientDescription()); this.uiEvents.on("query_client_description", () => this.sendClientDescription());
@ -114,179 +30,105 @@ export class ClientInfoController {
this.uiEvents.on("query_version", () => this.sendVersion()); this.uiEvents.on("query_version", () => this.sendVersion());
this.uiEvents.on("query_forum", () => this.sendForum()); this.uiEvents.on("query_forum", () => this.sendForum());
this.uiEvents.on("action_edit_avatar", () => this.connection.update_avatar()); this.uiEvents.on("action_edit_avatar", () => this.connection?.update_avatar());
this.uiEvents.on("action_show_full_info", () => this.currentClient && openClientInfo(this.currentClient)); this.uiEvents.on("action_show_full_info", () => {
const client = this.connection?.getSelectedClientInfo().getClient();
if(client) {
openClientInfo(client);
}
});
} }
private unregisterClientEvents() { destroy() {
this.listenerClient.forEach(callback => callback()); this.listenerConnection.forEach(callback => callback());
this.listenerClient = []; this.listenerConnection = [];
} }
private registerClientEvents(client: ClientEntry) { setConnectionHandler(connection: ConnectionHandler) {
const events = this.listenerClient; if(this.connection === connection) {
return;
events.push(client.events.on("notify_properties_updated", event => {
if('client_nickname' in event.updated_properties) {
this.currentClientStatus.name = event.client_properties.client_nickname;
this.sendClientName();
} }
if('client_description' in event.updated_properties) { this.listenerConnection.forEach(callback => callback());
this.currentClientStatus.description = event.client_properties.client_description; this.listenerConnection = [];
this.sendClientDescription();
this.connection = connection;
if(connection) {
this.initializeConnection(connection);
}
this.sendClient();
} }
if('client_channel_group_id' in event.updated_properties) { private initializeConnection(connection: ConnectionHandler) {
this.currentClientStatus.channelGroup = event.client_properties.client_channel_group_id; this.listenerConnection.push(connection.groups.events.on("notify_groups_updated", event => {
const info = this.connection?.getSelectedClientInfo().getInfo();
if(!info) {
return;
}
for(const update of event.updates) {
if(update.group.id === info.channelGroup) {
this.sendChannelGroup(); this.sendChannelGroup();
}
if('client_servergroups' in event.updated_properties) {
this.currentClientStatus.serverGroups = client.assignedServerGroupIds();
this.sendServerGroups();
}
/* Can happen since that variable isn't in view on client appearance */
if('client_lastconnected' in event.updated_properties) {
this.currentClientStatus.joinTimestamp = event.client_properties.client_lastconnected;
this.sendOnline();
}
if('client_country' in event.updated_properties) {
this.updateCachedCountry(client);
this.sendCountry();
}
for(const key of ["client_away", "client_away_message", "client_input_muted", "client_input_hardware", "client_output_muted", "client_output_hardware"]) {
if(key in event.updated_properties) {
this.updateCachedClientStatus(client);
this.sendClientStatus();
break; break;
} }
} }
if('client_platform' in event.updated_properties || 'client_version' in event.updated_properties) { for(const update of event.updates) {
this.currentClientStatus.version = { if(info.serverGroups.indexOf(update.group.id) !== -1) {
platform: client.properties.client_platform, this.sendServerGroups();
version: client.properties.client_version break;
};
this.sendVersion();
} }
}
}));
if('client_teaforo_flags' in event.updated_properties || 'client_teaforo_name' in event.updated_properties || 'client_teaforo_id' in event.updated_properties) { this.listenerConnection.push(connection.getSelectedClientInfo().events.on("notify_cache_changed", event => {
this.updateForumAccount(client); switch (event.category) {
case "name":
this.sendClientName();
break;
case "country":
this.sendCountry();
break;
case "description":
this.sendClientDescription();
break;
case "forum-account":
this.sendForum(); this.sendForum();
} break;
}));
events.push(client.events.on("notify_audio_level_changed", () => { case "group-channel":
this.updateCachedVolume(client); this.sendChannelGroup();
break;
case "groups-server":
this.sendServerGroups();
break;
case "online-state":
this.sendOnline();
break;
case "status":
this.sendClientStatus();
break;
case "version":
this.sendVolume(); this.sendVolume();
})); break;
events.push(client.events.on("notify_mute_state_change", () => { case "volume":
this.updateCachedVolume(client);
this.sendVolume(); this.sendVolume();
break;
}
})); }));
} }
private updateCachedClientStatus(client: ClientEntry) {
this.currentClientStatus.status = {
away: client.properties.client_away ? client.properties.client_away_message ? client.properties.client_away_message : true : false,
microphoneMuted: client.properties.client_input_muted,
microphoneDisabled: !client.properties.client_input_hardware,
speakerMuted: client.properties.client_output_muted,
speakerDisabled: !client.properties.client_output_hardware
};
}
private updateCachedCountry(client: ClientEntry) {
this.currentClientStatus.country = {
flag: client.properties.client_country,
name: i18nc.country_name(client.properties.client_country.toUpperCase()),
};
}
private updateCachedVolume(client: ClientEntry) {
this.currentClientStatus.volume = {
volume: client.getAudioVolume(),
muted: client.isMuted()
}
}
private updateForumAccount(client: ClientEntry) {
if(client.properties.client_teaforo_id) {
this.currentClientStatus.forumAccount = {
flags: client.properties.client_teaforo_flags,
nickname: client.properties.client_teaforo_name,
userId: client.properties.client_teaforo_id
};
} else {
this.currentClientStatus.forumAccount = undefined;
}
}
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(),
country: undefined,
forumAccount: undefined,
joinTimestamp: client.properties.client_lastconnected,
leaveTimestamp: 0,
status: undefined,
volume: undefined,
version: {
platform: client.properties.client_platform,
version: client.properties.client_version
}
};
this.updateCachedClientStatus(client);
this.updateCachedCountry(client);
this.updateCachedVolume(client);
this.updateForumAccount(client);
}
destroy() {
this.listenerClient.forEach(callback => callback());
this.listenerClient = [];
this.listenerConnection.forEach(callback => callback());
this.listenerConnection.splice(0, this.listenerConnection.length);
}
setClient(client: ClientEntry | undefined) {
if(this.currentClient === client) {
return;
}
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.sendClient();
this.events.fire("notify_client_changed", { newClient: client });
}
getClient() : ClientEntry | undefined {
return this.currentClient;
}
private generateGroupInfo(groupId: number, type: "channel" | "server") : ClientGroupInfo { private generateGroupInfo(groupId: number, type: "channel" | "server") : ClientGroupInfo {
const uniqueServerId = this.connection.channelTree.server.properties.virtualserver_unique_identifier; const uniqueServerId = this.connection?.channelTree.server.properties.virtualserver_unique_identifier;
const group = type === "channel" ? this.connection.groups.findChannelGroup(groupId) : this.connection.groups.findServerGroup(groupId); const group = type === "channel" ? this.connection?.groups.findChannelGroup(groupId) : this.connection?.groups.findServerGroup(groupId);
if(!group) { if(!group) {
return { return {
@ -310,14 +152,15 @@ export class ClientInfoController {
} }
private sendClient() { private sendClient() {
if(this.currentClientStatus) { const info = this.connection?.getSelectedClientInfo().getInfo();
if(info) {
this.uiEvents.fire_react("notify_client", { this.uiEvents.fire_react("notify_client", {
info: { info: {
handlerId: this.connection.handlerId, handlerId: this.connection.handlerId,
type: this.currentClientStatus.type, type: info.type,
clientDatabaseId: this.currentClientStatus.databaseId, clientDatabaseId: info.databaseId,
clientId: this.currentClientStatus.clientId, clientId: info.clientId,
clientUniqueId: this.currentClientStatus.uniqueId clientUniqueId: info.uniqueId
} }
}); });
} else { } else {
@ -328,19 +171,21 @@ export class ClientInfoController {
} }
private sendChannelGroup() { private sendChannelGroup() {
if(typeof this.currentClientStatus === "undefined") { const info = this.connection?.getSelectedClientInfo().getInfo();
if(typeof info === "undefined") {
this.uiEvents.fire_react("notify_channel_group", { group: undefined }); this.uiEvents.fire_react("notify_channel_group", { group: undefined });
} else { } else {
this.uiEvents.fire_react("notify_channel_group", { group: this.generateGroupInfo(this.currentClientStatus.channelGroup, "channel") }); this.uiEvents.fire_react("notify_channel_group", { group: this.generateGroupInfo(info.channelGroup, "channel") });
} }
} }
private sendServerGroups() { private sendServerGroups() {
if(this.currentClientStatus === undefined) { const info = this.connection?.getSelectedClientInfo().getInfo();
if(info === undefined) {
this.uiEvents.fire_react("notify_server_groups", { groups: [] }); this.uiEvents.fire_react("notify_server_groups", { groups: [] });
} else { } else {
this.uiEvents.fire_react("notify_server_groups", { this.uiEvents.fire_react("notify_server_groups", {
groups: this.currentClientStatus.serverGroups.map(group => this.generateGroupInfo(group, "server")) groups: info.serverGroups.map(group => this.generateGroupInfo(group, "server"))
.sort((a, b) => { .sort((a, b) => {
if (a.groupSortOrder < b.groupSortOrder) if (a.groupSortOrder < b.groupSortOrder)
return 1; return 1;
@ -361,8 +206,9 @@ export class ClientInfoController {
} }
private sendClientStatus() { private sendClientStatus() {
const info = this.connection?.getSelectedClientInfo().getInfo();
this.uiEvents.fire_react("notify_status", { this.uiEvents.fire_react("notify_status", {
status: this.currentClientStatus?.status || { status: info?.status || {
away: false, away: false,
speakerDisabled: false, speakerDisabled: false,
speakerMuted: false, speakerMuted: false,
@ -373,27 +219,31 @@ export class ClientInfoController {
} }
private sendClientName() { private sendClientName() {
this.uiEvents.fire_react("notify_client_name", { name: this.currentClientStatus?.name }); const info = this.connection?.getSelectedClientInfo().getInfo();
this.uiEvents.fire_react("notify_client_name", { name: info?.name });
} }
private sendClientDescription() { private sendClientDescription() {
this.uiEvents.fire_react("notify_client_description", { description: this.currentClientStatus?.description }); const info = this.connection?.getSelectedClientInfo().getInfo();
this.uiEvents.fire_react("notify_client_description", { description: info?.description });
} }
private sendOnline() { private sendOnline() {
const info = this.connection?.getSelectedClientInfo().getInfo();
this.uiEvents.fire_react("notify_online", { this.uiEvents.fire_react("notify_online", {
status: { status: {
leaveTimestamp: this.currentClientStatus ? this.currentClientStatus.leaveTimestamp : 0, leaveTimestamp: info ? info.leaveTimestamp : 0,
joinTimestamp: this.currentClientStatus ? this.currentClientStatus.joinTimestamp : 0 joinTimestamp: info ? info.joinTimestamp : 0
} }
}); });
} }
private sendCountry() { private sendCountry() {
const info = this.connection?.getSelectedClientInfo().getInfo();
this.uiEvents.fire_react("notify_country", { this.uiEvents.fire_react("notify_country", {
country: this.currentClientStatus ? { country: info ? {
name: this.currentClientStatus.country.name, name: info.country.name,
flag: this.currentClientStatus.country.flag flag: info.country.flag
} : { } : {
name: tr("Unknown"), name: tr("Unknown"),
flag: "xx" flag: "xx"
@ -402,10 +252,11 @@ export class ClientInfoController {
} }
private sendVolume() { private sendVolume() {
const info = this.connection?.getSelectedClientInfo().getInfo();
this.uiEvents.fire_react("notify_volume", { this.uiEvents.fire_react("notify_volume", {
volume: this.currentClientStatus ? { volume: info ? {
volume: this.currentClientStatus.volume.volume, volume: info.volume.volume,
muted: this.currentClientStatus.volume.muted muted: info.volume.muted
} : { } : {
volume: -1, volume: -1,
muted: false muted: false
@ -414,10 +265,11 @@ export class ClientInfoController {
} }
private sendVersion() { private sendVersion() {
const info = this.connection?.getSelectedClientInfo().getInfo();
this.uiEvents.fire_react("notify_version", { this.uiEvents.fire_react("notify_version", {
version: this.currentClientStatus ? { version: info ? {
platform: this.currentClientStatus.version.platform, platform: info.version.platform,
version: this.currentClientStatus.version.version version: info.version.version
} : { } : {
platform: tr("Unknown"), platform: tr("Unknown"),
version: tr("Unknown") version: tr("Unknown")
@ -426,6 +278,7 @@ export class ClientInfoController {
} }
private sendForum() { private sendForum() {
this.uiEvents.fire_react("notify_forum", { forum: this.currentClientStatus?.forumAccount }) const info = this.connection?.getSelectedClientInfo().getInfo();
this.uiEvents.fire_react("notify_forum", { forum: info?.forumAccount })
} }
} }

View File

@ -18,7 +18,7 @@ const ChannelInfoUpdateProperties: (keyof ChannelProperties)[] = [
]; ];
/* TODO: Remove the ping interval handler. It's currently still there since the clients are not emitting 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 { export class SideHeaderController {
private readonly uiEvents: Registry<SideHeaderEvents>; private readonly uiEvents: Registry<SideHeaderEvents>;
private connection: ConnectionHandler; private connection: ConnectionHandler;
@ -43,26 +43,32 @@ export class SideHeader {
private initialize() { private initialize() {
this.uiEvents.on("action_open_conversation", () => { this.uiEvents.on("action_open_conversation", () => {
const selectedClient = this.connection.side_bar.getClientInfo().getClient() const selectedClient = this.connection.getSelectedClientInfo().getClient()
if(selectedClient) { if(selectedClient) {
const conversations = this.connection.getPrivateConversations(); const conversations = this.connection.getPrivateConversations();
conversations.setSelectedConversation(conversations.findOrCreateConversation(selectedClient)); conversations.setSelectedConversation(conversations.findOrCreateConversation(selectedClient));
} }
this.connection.side_bar.showPrivateConversations(); this.connection.getSideBar().showPrivateConversations();
}); });
this.uiEvents.on("action_switch_channel_chat", () => { this.uiEvents.on("action_switch_channel_chat", () => {
this.connection.side_bar.showChannelConversations(); this.connection.getSideBar().showChannelConversations();
}); });
this.uiEvents.on("action_bot_manage", () => { this.uiEvents.on("action_bot_manage", () => {
const bot = this.connection.side_bar.music_info().current_bot(); /* FIXME: TODO! */
/*
const bot = this.connection.getSideBar().music_info().current_bot();
if(!bot) return; if(!bot) return;
openMusicManage(this.connection, bot); openMusicManage(this.connection, bot);
*/
}); });
this.uiEvents.on("action_bot_add_song", () => this.connection.side_bar.music_info().events.fire("action_song_add")); this.uiEvents.on("action_bot_add_song", () => {
/* FIXME: TODO! */
//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_client_info_own_client", () => this.sendClientInfoOwnClient());
this.uiEvents.on("query_current_channel_state", event => this.sendChannelState(event.mode)); this.uiEvents.on("query_current_channel_state", event => this.sendChannelState(event.mode));
@ -98,7 +104,7 @@ export class SideHeader {
this.listenerConnection.push(this.connection.serverConnection.events.on("notify_ping_updated", () => this.sendPing())); 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_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.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())); this.listenerConnection.push(this.connection.getSelectedClientInfo().events.on("notify_client_changed", () => this.sendClientInfoOwnClient()));
} }
setConnectionHandler(connection: ConnectionHandler) { setConnectionHandler(connection: ConnectionHandler) {
@ -253,7 +259,7 @@ export class SideHeader {
private sendClientInfoOwnClient() { private sendClientInfoOwnClient() {
if(this.connection) { if(this.connection) {
this.uiEvents.fire_react("notify_client_info_own_client", { isOwnClient: this.connection.side_bar.getClientInfo().getClient() instanceof LocalClientEntry }); this.uiEvents.fire_react("notify_client_info_own_client", { isOwnClient: this.connection.getSelectedClientInfo().getClient() instanceof LocalClientEntry });
} else { } else {
this.uiEvents.fire_react("notify_client_info_own_client", { isOwnClient: false }); this.uiEvents.fire_react("notify_client_info_own_client", { isOwnClient: false });
} }

View File

@ -1,11 +1,11 @@
import {Registry, RegistryMap} from "tc-shared/events"; import {Registry, RegistryMap} from "tc-shared/events";
import {ConversationUIEvents} from "tc-shared/ui/frames/side/ConversationDefinitions"; import {AbstractConversationUiEvents} from "./AbstractConversationDefinitions";
import {ConversationPanel} from "./AbstractConversationRenderer"; import {ConversationPanel} from "./AbstractConversationRenderer";
import * as React from "react"; import * as React from "react";
import {AbstractModal} from "tc-shared/ui/react-elements/ModalDefinitions"; import {AbstractModal} from "tc-shared/ui/react-elements/ModalDefinitions";
class PopoutConversationRenderer extends AbstractModal { class PopoutConversationRenderer extends AbstractModal {
private readonly events: Registry<ConversationUIEvents>; private readonly events: Registry<AbstractConversationUiEvents>;
private readonly userData: any; private readonly userData: any;
constructor(registryMap: RegistryMap, userData: any) { constructor(registryMap: RegistryMap, userData: any) {

View File

@ -4,12 +4,9 @@ import {
PrivateConversationInfo, PrivateConversationInfo,
PrivateConversationUIEvents PrivateConversationUIEvents
} from "../../../ui/frames/side/PrivateConversationDefinitions"; } from "../../../ui/frames/side/PrivateConversationDefinitions";
import * as ReactDOM from "react-dom";
import * as React from "react";
import {PrivateConversationsPanel} from "./PrivateConversationRenderer";
import { import {
ConversationUIEvents AbstractConversationUiEvents
} from "../../../ui/frames/side/ConversationDefinitions"; } from "./AbstractConversationDefinitions";
import * as log from "../../../log"; import * as log from "../../../log";
import {LogCategory} from "../../../log"; import {LogCategory} from "../../../log";
import {AbstractConversationController} from "./AbstractConversationController"; import {AbstractConversationController} from "./AbstractConversationController";
@ -48,35 +45,57 @@ export class PrivateConversationController extends AbstractConversationControlle
PrivateConversation, PrivateConversation,
PrivateConversationEvents PrivateConversationEvents
> { > {
public readonly htmlTag: HTMLDivElement; private connection: ConnectionHandler;
public readonly connection: ConnectionHandler; private connectionListener: (() => void)[];
private listenerConversation: {[key: string]:(() => void)[]}; private listenerConversation: {[key: string]:(() => void)[]};
constructor(connection: ConnectionHandler) { constructor() {
super(connection.getPrivateConversations()); super();
this.connection = connection; this.connectionListener = [];
this.listenerConversation = {}; this.listenerConversation = {};
this.htmlTag = document.createElement("div");
this.htmlTag.style.display = "flex";
this.htmlTag.style.flexDirection = "row";
this.htmlTag.style.justifyContent = "stretch";
this.htmlTag.style.height = "100%";
this.uiEvents.register_handler(this, true); this.uiEvents.register_handler(this, true);
this.uiEvents.enableDebug("private-conversations"); this.uiEvents.enableDebug("private-conversations");
}
ReactDOM.render(React.createElement(PrivateConversationsPanel, { events: this.uiEvents, handler: this.connection }), this.htmlTag); destroy() {
/* listenerConversation will be cleaned up via the listenerManager callbacks */
this.uiEvents.on("notify_destroy", connection.events().on("notify_visibility_changed", event => { this.uiEvents.unregister_handler(this);
super.destroy();
}
setConnectionHandler(connection: ConnectionHandler) {
if(this.connection === connection) {
return;
}
this.connectionListener.forEach(callback => callback());
this.connectionListener = [];
this.connection = connection;
if(connection) {
this.initializeConnectionListener(connection);
this.setConversationManager(connection.getPrivateConversations());
} else {
this.setConversationManager(undefined);
}
}
private initializeConnectionListener(connection: ConnectionHandler) {
this.connectionListener.push(connection.events().on("notify_visibility_changed", event => {
if(!event.visible) if(!event.visible)
return; return;
this.handlePanelShow(); this.handlePanelShow();
})); }));
}
this.listenerManager.push(this.conversationManager.events.on("notify_conversation_created", event => { protected registerConversationManagerEvents(manager: PrivateConversationManager) {
super.registerConversationManagerEvents(manager);
this.listenerManager.push(manager.events.on("notify_conversation_created", event => {
const conversation = event.conversation; const conversation = event.conversation;
const events = this.listenerConversation[conversation.getChatId()] = []; const events = this.listenerConversation[conversation.getChatId()] = [];
events.push(conversation.events.on("notify_partner_changed", event => { events.push(conversation.events.on("notify_partner_changed", event => {
@ -94,21 +113,17 @@ export class PrivateConversationController extends AbstractConversationControlle
this.reportConversationList(); this.reportConversationList();
})); }));
this.listenerManager.push(this.conversationManager.events.on("notify_conversation_destroyed", event => { this.listenerManager.push(manager.events.on("notify_conversation_destroyed", event => {
this.listenerConversation[event.conversation.getChatId()]?.forEach(callback => callback()); this.listenerConversation[event.conversation.getChatId()]?.forEach(callback => callback());
delete this.listenerConversation[event.conversation.getChatId()]; delete this.listenerConversation[event.conversation.getChatId()];
this.reportConversationList(); this.reportConversationList();
})); }));
this.listenerManager.push(this.conversationManager.events.on("notify_selected_changed", () => this.reportConversationList())); this.listenerManager.push(manager.events.on("notify_selected_changed", () => this.reportConversationList()));
} this.listenerManager.push(() => {
Object.values(this.listenerConversation).forEach(callbacks => callbacks.forEach(callback => callback()));
destroy() { this.listenerConversation = {};
ReactDOM.unmountComponentAtNode(this.htmlTag); });
this.htmlTag.remove();
this.uiEvents.unregister_handler(this);
super.destroy();
} }
focusInput() { focusInput() {
@ -117,8 +132,8 @@ export class PrivateConversationController extends AbstractConversationControlle
private reportConversationList() { private reportConversationList() {
this.uiEvents.fire_react("notify_private_conversations", { this.uiEvents.fire_react("notify_private_conversations", {
conversations: this.conversationManager.getConversations().map(generateConversationUiInfo), conversations: this.conversationManager ? this.conversationManager.getConversations().map(generateConversationUiInfo) : [],
selected: this.conversationManager.getSelectedConversation()?.clientUniqueId || "unselected" selected: this.conversationManager?.getSelectedConversation()?.clientUniqueId || "unselected"
}); });
} }
@ -129,7 +144,7 @@ export class PrivateConversationController extends AbstractConversationControlle
@EventHandler<PrivateConversationUIEvents>("action_close_chat") @EventHandler<PrivateConversationUIEvents>("action_close_chat")
private handleConversationClose(event: PrivateConversationUIEvents["action_close_chat"]) { private handleConversationClose(event: PrivateConversationUIEvents["action_close_chat"]) {
const conversation = this.conversationManager.findConversation(event.chatId); const conversation = this.conversationManager?.findConversation(event.chatId);
if(!conversation) { if(!conversation) {
log.error(LogCategory.CLIENT, tr("Tried to close a not existing private conversation with id %s"), event.chatId); log.error(LogCategory.CLIENT, tr("Tried to close a not existing private conversation with id %s"), event.chatId);
return; return;
@ -138,13 +153,8 @@ export class PrivateConversationController extends AbstractConversationControlle
this.conversationManager.closeConversation(conversation); this.conversationManager.closeConversation(conversation);
} }
@EventHandler<PrivateConversationUIEvents>("notify_partner_typing") @EventHandler<AbstractConversationUiEvents>("action_self_typing")
private handleNotifySelectChat(event: PrivateConversationUIEvents["notify_partner_typing"]) { protected handleActionSelfTyping1(_event: AbstractConversationUiEvents["action_self_typing"]) {
/* TODO, set active chat? MH 9/12/20: What?? */
}
@EventHandler<ConversationUIEvents>("action_self_typing")
protected handleActionSelfTyping1(_event: ConversationUIEvents["action_self_typing"]) {
const conversation = this.getCurrentConversation(); const conversation = this.getCurrentConversation();
if(!conversation) { if(!conversation) {
return; return;

View File

@ -1,4 +1,4 @@
import {ConversationUIEvents} from "../../../ui/frames/side/ConversationDefinitions"; import {AbstractConversationUiEvents} from "./AbstractConversationDefinitions";
export type PrivateConversationInfo = { export type PrivateConversationInfo = {
nickname: string; nickname: string;
@ -11,7 +11,7 @@ export type PrivateConversationInfo = {
unreadMessages: boolean; unreadMessages: boolean;
}; };
export interface PrivateConversationUIEvents extends ConversationUIEvents { export interface PrivateConversationUIEvents extends AbstractConversationUiEvents {
action_close_chat: { chatId: string }, action_close_chat: { chatId: string },
query_private_conversations: {}, query_private_conversations: {},

View File

@ -17,6 +17,13 @@ html:root {
--chat-private-selected-background: #2c2c2c; --chat-private-selected-background: #2c2c2c;
} }
.dividerContainer {
display: flex;
flex-direction: row;
justify-content: stretch;
height: 100%;
}
.divider { .divider {
width: 2px!important; width: 2px!important;
min-width: 2px!important; min-width: 2px!important;

View File

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

View File

@ -1,4 +1,4 @@
import {Frame, FrameContent} from "../../../ui/frames/chat_frame"; import {SideBarController, FrameContent} from "../SideBarController";
import {LogCategory} from "../../../log"; import {LogCategory} from "../../../log";
import {CommandResult, PlaylistSong} from "../../../connection/ServerConnectionDeclaration"; import {CommandResult, PlaylistSong} from "../../../connection/ServerConnectionDeclaration";
import {createErrorModal, createInputModal} from "../../../ui/elements/Modal"; import {createErrorModal, createInputModal} from "../../../ui/elements/Modal";
@ -67,7 +67,7 @@ interface LoadedSongData {
export class MusicInfo { export class MusicInfo {
readonly events: Registry<MusicSidebarEvents>; readonly events: Registry<MusicSidebarEvents>;
readonly handle: Frame; readonly handle: SideBarController;
private _html_tag: JQuery; private _html_tag: JQuery;
private _container_playlist: JQuery; private _container_playlist: JQuery;
@ -91,7 +91,7 @@ export class MusicInfo {
previous_frame_content: FrameContent; previous_frame_content: FrameContent;
constructor(handle: Frame) { constructor(handle: SideBarController) {
this.events = new Registry<MusicSidebarEvents>(); this.events = new Registry<MusicSidebarEvents>();
this.handle = handle; this.handle = handle;

View File

@ -11,7 +11,7 @@ export interface ContextDividerProperties {
separatorClassName?: string; separatorClassName?: string;
separatorActiveClassName?: string; separatorActiveClassName?: string;
children: [React.ReactElement, React.ReactElement]; children?: never;
} }
export interface ContextDividerState { export interface ContextDividerState {
@ -99,11 +99,9 @@ export class ContextDivider extends React.Component<ContextDividerProperties, Co
if(this.state.active && this.props.separatorClassName) if(this.state.active && this.props.separatorClassName)
separatorClassNames += " " + this.props.separatorClassName; separatorClassNames += " " + this.props.separatorClassName;
return [ return (
this.props.children[0], <div key={"context-separator"} ref={this.refSeparator} className={separatorClassNames} onMouseDown={e => this.startMovement(e)} onTouchStart={e => this.startMovement(e)} />
<div key={"context-separator"} ref={this.refSeparator} className={separatorClassNames} onMouseDown={e => this.startMovement(e)} onTouchStart={e => this.startMovement(e)} />, )
this.props.children[1]
];
} }
componentDidMount(): void { componentDidMount(): void {