A lot of updates and added a new feature
parent
001bececbe
commit
3412faf125
|
@ -1,4 +1,11 @@
|
||||||
# Changelog:
|
# Changelog:
|
||||||
|
* **18.12.20**
|
||||||
|
- Added the ability to send private messages to multiple clients
|
||||||
|
- Channel client count now updates within the side bar header
|
||||||
|
- The client now supports the new server channel sidebar mode (File transfer is in progress)
|
||||||
|
- Correctly parsing and render `client://` urls
|
||||||
|
- Updating the client talk status if the client has been moved
|
||||||
|
|
||||||
* **13.12.20**
|
* **13.12.20**
|
||||||
- Directly connection when hitting enter on the address line
|
- Directly connection when hitting enter on the address line
|
||||||
|
|
||||||
|
|
|
@ -134,6 +134,8 @@ html:root {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
border-radius: 5px 5px 0 0;
|
border-radius: 5px 5px 0 0;
|
||||||
|
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
|
|
|
@ -24,8 +24,6 @@ import {FileTransferState, TransferProvider} from "./file/Transfer";
|
||||||
import {traj, tr} from "./i18n/localize";
|
import {traj, tr} from "./i18n/localize";
|
||||||
import {md5} from "./crypto/md5";
|
import {md5} from "./crypto/md5";
|
||||||
import {guid} from "./crypto/uid";
|
import {guid} from "./crypto/uid";
|
||||||
import {ServerEventLog} from "./ui/frames/log/ServerEventLog";
|
|
||||||
import {EventType} from "./ui/frames/log/Definitions";
|
|
||||||
import {PluginCmdRegistry} from "./connection/PluginCmdHandler";
|
import {PluginCmdRegistry} from "./connection/PluginCmdHandler";
|
||||||
import {W2GPluginCmdHandler} from "./video-viewer/W2GPlugin";
|
import {W2GPluginCmdHandler} from "./video-viewer/W2GPlugin";
|
||||||
import {VoiceConnectionStatus, WhisperSessionInitializeData} from "./connection/VoiceConnection";
|
import {VoiceConnectionStatus, WhisperSessionInitializeData} from "./connection/VoiceConnection";
|
||||||
|
@ -41,6 +39,8 @@ import {ChannelConversationManager} from "./conversations/ChannelConversationMan
|
||||||
import {PrivateConversationManager} from "tc-shared/conversations/PrivateConversationManager";
|
import {PrivateConversationManager} from "tc-shared/conversations/PrivateConversationManager";
|
||||||
import {SelectedClientInfo} from "./SelectedClientInfo";
|
import {SelectedClientInfo} from "./SelectedClientInfo";
|
||||||
import {SideBarManager} from "tc-shared/SideBarManager";
|
import {SideBarManager} from "tc-shared/SideBarManager";
|
||||||
|
import {ServerEventLog} from "tc-shared/connectionlog/ServerEventLog";
|
||||||
|
import {EventType} from "tc-shared/connectionlog/Definitions";
|
||||||
|
|
||||||
export enum InputHardwareState {
|
export enum InputHardwareState {
|
||||||
MISSING,
|
MISSING,
|
||||||
|
@ -270,7 +270,7 @@ export class ConnectionHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info(LogCategory.CLIENT, tr("Start connection to %s:%d"), server_address.host, server_address.port);
|
log.info(LogCategory.CLIENT, tr("Start connection to %s:%d"), server_address.host, server_address.port);
|
||||||
this.log.log(EventType.CONNECTION_BEGIN, {
|
this.log.log("connection.begin", {
|
||||||
address: {
|
address: {
|
||||||
server_hostname: server_address.host,
|
server_hostname: server_address.host,
|
||||||
server_port: server_address.port
|
server_port: server_address.port
|
||||||
|
@ -303,7 +303,7 @@ export class ConnectionHandler {
|
||||||
server_address.host = "127.0.0.1";
|
server_address.host = "127.0.0.1";
|
||||||
} else if(dns.supported() && !server_address.host.match(Regex.IP_V4) && !server_address.host.match(Regex.IP_V6)) {
|
} else if(dns.supported() && !server_address.host.match(Regex.IP_V4) && !server_address.host.match(Regex.IP_V6)) {
|
||||||
const id = ++this._connect_initialize_id;
|
const id = ++this._connect_initialize_id;
|
||||||
this.log.log(EventType.CONNECTION_HOSTNAME_RESOLVE, {});
|
this.log.log("connection.hostname.resolve", {});
|
||||||
try {
|
try {
|
||||||
const resolved = await dns.resolve_address(server_address, { timeout: 5000 }) || {} as any;
|
const resolved = await dns.resolve_address(server_address, { timeout: 5000 }) || {} as any;
|
||||||
if(id != this._connect_initialize_id)
|
if(id != this._connect_initialize_id)
|
||||||
|
@ -311,7 +311,7 @@ export class ConnectionHandler {
|
||||||
|
|
||||||
server_address.host = typeof(resolved.target_ip) === "string" ? resolved.target_ip : server_address.host;
|
server_address.host = typeof(resolved.target_ip) === "string" ? resolved.target_ip : server_address.host;
|
||||||
server_address.port = typeof(resolved.target_port) === "number" ? resolved.target_port : server_address.port;
|
server_address.port = typeof(resolved.target_port) === "number" ? resolved.target_port : server_address.port;
|
||||||
this.log.log(EventType.CONNECTION_HOSTNAME_RESOLVED, {
|
this.log.log("connection.hostname.resolved", {
|
||||||
address: {
|
address: {
|
||||||
server_port: server_address.port,
|
server_port: server_address.port,
|
||||||
server_hostname: server_address.host
|
server_hostname: server_address.host
|
||||||
|
@ -348,7 +348,7 @@ export class ConnectionHandler {
|
||||||
log.warn(LogCategory.CLIENT, tr("Failed to successfully disconnect from server: {}"), error);
|
log.warn(LogCategory.CLIENT, tr("Failed to successfully disconnect from server: {}"), error);
|
||||||
}
|
}
|
||||||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||||
this.log.log(EventType.DISCONNECTED, {});
|
this.log.log("disconnected", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
getClient() : LocalClientEntry { return this.localClient; }
|
getClient() : LocalClientEntry { return this.localClient; }
|
||||||
|
@ -386,7 +386,7 @@ export class ConnectionHandler {
|
||||||
this.connection_state = event.newState;
|
this.connection_state = event.newState;
|
||||||
if(event.newState === ConnectionState.CONNECTED) {
|
if(event.newState === ConnectionState.CONNECTED) {
|
||||||
log.info(LogCategory.CLIENT, tr("Client connected"));
|
log.info(LogCategory.CLIENT, tr("Client connected"));
|
||||||
this.log.log(EventType.CONNECTION_CONNECTED, {
|
this.log.log("connection.connected", {
|
||||||
serverAddress: {
|
serverAddress: {
|
||||||
server_port: this.channelTree.server.remote_address.port,
|
server_port: this.channelTree.server.remote_address.port,
|
||||||
server_hostname: this.channelTree.server.remote_address.host
|
server_hostname: this.channelTree.server.remote_address.host
|
||||||
|
@ -487,12 +487,12 @@ export class ConnectionHandler {
|
||||||
case DisconnectReason.HANDLER_DESTROYED:
|
case DisconnectReason.HANDLER_DESTROYED:
|
||||||
if(data) {
|
if(data) {
|
||||||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||||
this.log.log(EventType.DISCONNECTED, {});
|
this.log.log("disconnected", {});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DisconnectReason.DNS_FAILED:
|
case DisconnectReason.DNS_FAILED:
|
||||||
log.error(LogCategory.CLIENT, tr("Failed to resolve hostname: %o"), data);
|
log.error(LogCategory.CLIENT, tr("Failed to resolve hostname: %o"), data);
|
||||||
this.log.log(EventType.CONNECTION_HOSTNAME_RESOLVE_ERROR, {
|
this.log.log("connection.hostname.resolve.error", {
|
||||||
message: data as any
|
message: data as any
|
||||||
});
|
});
|
||||||
this.sound.play(Sound.CONNECTION_REFUSED);
|
this.sound.play(Sound.CONNECTION_REFUSED);
|
||||||
|
@ -539,7 +539,7 @@ export class ConnectionHandler {
|
||||||
this._certificate_modal.open();
|
this._certificate_modal.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.log.log(EventType.CONNECTION_FAILED, {
|
this.log.log("connection.failed", {
|
||||||
serverAddress: {
|
serverAddress: {
|
||||||
server_hostname: this.serverConnection.remote_address().host,
|
server_hostname: this.serverConnection.remote_address().host,
|
||||||
server_port: this.serverConnection.remote_address().port
|
server_port: this.serverConnection.remote_address().port
|
||||||
|
@ -594,7 +594,7 @@ export class ConnectionHandler {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case DisconnectReason.SERVER_CLOSED:
|
case DisconnectReason.SERVER_CLOSED:
|
||||||
this.log.log(EventType.SERVER_CLOSED, {message: data.reasonmsg});
|
this.log.log("server.closed", {message: data.reasonmsg});
|
||||||
|
|
||||||
createErrorModal(
|
createErrorModal(
|
||||||
tr("Server closed"),
|
tr("Server closed"),
|
||||||
|
@ -606,7 +606,7 @@ export class ConnectionHandler {
|
||||||
auto_reconnect = true;
|
auto_reconnect = true;
|
||||||
break;
|
break;
|
||||||
case DisconnectReason.SERVER_REQUIRES_PASSWORD:
|
case DisconnectReason.SERVER_REQUIRES_PASSWORD:
|
||||||
this.log.log(EventType.SERVER_REQUIRES_PASSWORD, {});
|
this.log.log("server.requires.password", {});
|
||||||
|
|
||||||
createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
|
createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
|
||||||
if(!(typeof password === "string")) return;
|
if(!(typeof password === "string")) return;
|
||||||
|
@ -647,7 +647,7 @@ export class ConnectionHandler {
|
||||||
this.sound.play(Sound.CONNECTION_BANNED);
|
this.sound.play(Sound.CONNECTION_BANNED);
|
||||||
break;
|
break;
|
||||||
case DisconnectReason.CLIENT_BANNED:
|
case DisconnectReason.CLIENT_BANNED:
|
||||||
this.log.log(EventType.SERVER_BANNED, {
|
this.log.log("server.banned", {
|
||||||
invoker: {
|
invoker: {
|
||||||
client_name: data["invokername"],
|
client_name: data["invokername"],
|
||||||
client_id: parseInt(data["invokerid"]),
|
client_id: parseInt(data["invokerid"]),
|
||||||
|
@ -678,7 +678,7 @@ export class ConnectionHandler {
|
||||||
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
|
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.log.log(EventType.RECONNECT_SCHEDULED, {timeout: 5000});
|
this.log.log("reconnect.scheduled", {timeout: 5000});
|
||||||
|
|
||||||
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
|
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
|
||||||
const server_address = this.serverConnection.remote_address();
|
const server_address = this.serverConnection.remote_address();
|
||||||
|
@ -686,7 +686,7 @@ export class ConnectionHandler {
|
||||||
|
|
||||||
this._reconnect_timer = setTimeout(() => {
|
this._reconnect_timer = setTimeout(() => {
|
||||||
this._reconnect_timer = undefined;
|
this._reconnect_timer = undefined;
|
||||||
this.log.log(EventType.RECONNECT_EXECUTE, {});
|
this.log.log("reconnect.execute", {});
|
||||||
log.info(LogCategory.NETWORKING, tr("Reconnecting..."));
|
log.info(LogCategory.NETWORKING, tr("Reconnecting..."));
|
||||||
|
|
||||||
this.startConnection(server_address.host + ":" + server_address.port, profile, false, Object.assign(this.reconnect_properties(profile), {auto_reconnect_attempt: true}));
|
this.startConnection(server_address.host + ":" + server_address.port, profile, false, Object.assign(this.reconnect_properties(profile), {auto_reconnect_attempt: true}));
|
||||||
|
@ -698,7 +698,7 @@ export class ConnectionHandler {
|
||||||
|
|
||||||
cancel_reconnect(log_event: boolean) {
|
cancel_reconnect(log_event: boolean) {
|
||||||
if(this._reconnect_timer) {
|
if(this._reconnect_timer) {
|
||||||
if(log_event) this.log.log(EventType.RECONNECT_CANCELED, {});
|
if(log_event) this.log.log("reconnect.canceled", {});
|
||||||
clearTimeout(this._reconnect_timer);
|
clearTimeout(this._reconnect_timer);
|
||||||
this._reconnect_timer = undefined;
|
this._reconnect_timer = undefined;
|
||||||
}
|
}
|
||||||
|
@ -791,7 +791,7 @@ export class ConnectionHandler {
|
||||||
this.clientStatusSync = true;
|
this.clientStatusSync = true;
|
||||||
this.serverConnection.send_command("clientupdate", localClientUpdates).catch(error => {
|
this.serverConnection.send_command("clientupdate", localClientUpdates).catch(error => {
|
||||||
log.warn(LogCategory.GENERAL, tr("Failed to update client audio hardware properties. Error: %o"), error);
|
log.warn(LogCategory.GENERAL, tr("Failed to update client audio hardware properties. Error: %o"), error);
|
||||||
this.log.log(EventType.ERROR_CUSTOM, { message: tr("Failed to update audio hardware properties.") });
|
this.log.log("error.custom", { message: tr("Failed to update audio hardware properties.") });
|
||||||
this.clientStatusSync = false;
|
this.clientStatusSync = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -837,7 +837,7 @@ export class ConnectionHandler {
|
||||||
//client_output_hardware: this.client_status.sound_playback_supported
|
//client_output_hardware: this.client_status.sound_playback_supported
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
log.warn(LogCategory.GENERAL, tr("Failed to sync handler state with server. Error: %o"), error);
|
log.warn(LogCategory.GENERAL, tr("Failed to sync handler state with server. Error: %o"), error);
|
||||||
this.log.log(EventType.ERROR_CUSTOM, {message: tr("Failed to sync handler state with server.")});
|
this.log.log("error.custom", {message: tr("Failed to sync handler state with server.")});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1154,7 +1154,7 @@ export class ConnectionHandler {
|
||||||
client_away_message: typeof(this.client_status.away) === "string" ? this.client_status.away : "",
|
client_away_message: typeof(this.client_status.away) === "string" ? this.client_status.away : "",
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
log.warn(LogCategory.GENERAL, tr("Failed to update away status. Error: %o"), error);
|
log.warn(LogCategory.GENERAL, tr("Failed to update away status. Error: %o"), error);
|
||||||
this.log.log(EventType.ERROR_CUSTOM, {message: tr("Failed to update away status.")});
|
this.log.log("error.custom", {message: tr("Failed to update away status.")});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.event_registry.fire("notify_state_updated", {
|
this.event_registry.fire("notify_state_updated", {
|
||||||
|
|
|
@ -6,6 +6,8 @@ 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";
|
import {SideBarController} from "tc-shared/ui/frames/SideBarController";
|
||||||
|
import {ServerEventLogController} from "tc-shared/ui/frames/log/Controller";
|
||||||
|
import {ServerLogFrame} from "tc-shared/ui/frames/log/Renderer";
|
||||||
|
|
||||||
export let server_connections: ConnectionManager;
|
export let server_connections: ConnectionManager;
|
||||||
|
|
||||||
|
@ -30,33 +32,36 @@ export class ConnectionManager {
|
||||||
private connection_handlers: ConnectionHandler[] = [];
|
private connection_handlers: ConnectionHandler[] = [];
|
||||||
private active_handler: ConnectionHandler | undefined;
|
private active_handler: ConnectionHandler | undefined;
|
||||||
|
|
||||||
private _container_log_server: JQuery;
|
|
||||||
private _container_channel_tree: JQuery;
|
private _container_channel_tree: JQuery;
|
||||||
private _container_hostbanner: JQuery;
|
private _container_hostbanner: JQuery;
|
||||||
private containerChannelVideo: ReplaceableContainer;
|
private containerChannelVideo: ReplaceableContainer;
|
||||||
private containerSideBar: HTMLDivElement;
|
private containerSideBar: HTMLDivElement;
|
||||||
private containerFooter: HTMLDivElement;
|
private containerFooter: HTMLDivElement;
|
||||||
|
private containerServerLog: HTMLDivElement;
|
||||||
|
|
||||||
private sideBarController: SideBarController;
|
private sideBarController: SideBarController;
|
||||||
|
private serverLogController: ServerEventLogController;
|
||||||
|
|
||||||
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.sideBarController = new SideBarController();
|
||||||
|
this.serverLogController = new ServerEventLogController();
|
||||||
|
|
||||||
this.containerChannelVideo = new ReplaceableContainer(document.getElementById("channel-video") as HTMLDivElement);
|
this.containerChannelVideo = new ReplaceableContainer(document.getElementById("channel-video") as HTMLDivElement);
|
||||||
this._container_log_server = $("#server-log");
|
this.containerServerLog = document.getElementById("server-log") as HTMLDivElement;
|
||||||
|
this.containerFooter = document.getElementById("container-footer") as HTMLDivElement;
|
||||||
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.sideBarController.renderInto(document.getElementById("chat") as HTMLDivElement);
|
this.sideBarController.renderInto(document.getElementById("chat") as HTMLDivElement);
|
||||||
this.set_active_connection(undefined);
|
this.set_active_connection(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeFooter() {
|
initializeReactComponents() {
|
||||||
ReactDOM.render(React.createElement(FooterRenderer), this.containerFooter);
|
ReactDOM.render(React.createElement(FooterRenderer), this.containerFooter);
|
||||||
|
ReactDOM.render(React.createElement(ServerLogFrame, { events: this.serverLogController.events }), this.containerServerLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
events() : Registry<ConnectionManagerEvents> {
|
events() : Registry<ConnectionManagerEvents> {
|
||||||
|
@ -117,16 +122,15 @@ export class ConnectionManager {
|
||||||
|
|
||||||
private set_active_connection_(handler: ConnectionHandler) {
|
private set_active_connection_(handler: ConnectionHandler) {
|
||||||
this.sideBarController.setConnection(handler);
|
this.sideBarController.setConnection(handler);
|
||||||
|
this.serverLogController.setConnectionHandler(handler);
|
||||||
|
|
||||||
this._container_channel_tree.children().detach();
|
this._container_channel_tree.children().detach();
|
||||||
this._container_log_server.children().detach();
|
|
||||||
this._container_hostbanner.children().detach();
|
this._container_hostbanner.children().detach();
|
||||||
this.containerChannelVideo.replaceWith(handler?.video_frame.getContainer());
|
this.containerChannelVideo.replaceWith(handler?.video_frame.getContainer());
|
||||||
|
|
||||||
if(handler) {
|
if(handler) {
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const old_handler = this.active_handler;
|
const old_handler = this.active_handler;
|
||||||
|
@ -184,7 +188,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
name: "server manager init",
|
name: "server manager init",
|
||||||
function: async () => {
|
function: async () => {
|
||||||
server_connections = new ConnectionManager();
|
server_connections = new ConnectionManager();
|
||||||
server_connections.initializeFooter();
|
server_connections.initializeReactComponents();
|
||||||
},
|
},
|
||||||
priority: 80
|
priority: 80
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@ export class SideBarManager {
|
||||||
constructor(connection: ConnectionHandler) {
|
constructor(connection: ConnectionHandler) {
|
||||||
this.events = new Registry<SideBarManagerEvents>();
|
this.events = new Registry<SideBarManagerEvents>();
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.currentType = "channel-chat";
|
this.currentType = "channel";
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {}
|
destroy() {}
|
||||||
|
@ -38,8 +38,8 @@ export class SideBarManager {
|
||||||
this.setSideBarContent("private-chat");
|
this.setSideBarContent("private-chat");
|
||||||
}
|
}
|
||||||
|
|
||||||
showChannelConversations() {
|
showChannel() {
|
||||||
this.setSideBarContent("channel-chat");
|
this.setSideBarContent("channel");
|
||||||
}
|
}
|
||||||
|
|
||||||
showClientInfo(client: ClientEntry) {
|
showClientInfo(client: ClientEntry) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as log from "../log";
|
import * as log from "../log";
|
||||||
import {LogCategory, logError} from "../log";
|
import {LogCategory, logError, logWarn} from "../log";
|
||||||
import {AbstractServerConnection, CommandOptions, ServerCommand} from "../connection/ConnectionBase";
|
import {AbstractServerConnection, CommandOptions, ServerCommand} from "../connection/ConnectionBase";
|
||||||
import {Sound} from "../sound/Sounds";
|
import {Sound} from "../sound/Sounds";
|
||||||
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
||||||
|
@ -20,10 +20,10 @@ import {batch_updates, BatchUpdateType, flush_batched_updates} from "../ui/react
|
||||||
import {OutOfViewClient} from "../ui/frames/side/PrivateConversationController";
|
import {OutOfViewClient} from "../ui/frames/side/PrivateConversationController";
|
||||||
import {renderBBCodeAsJQuery} from "../text/bbcode";
|
import {renderBBCodeAsJQuery} from "../text/bbcode";
|
||||||
import {tr} from "../i18n/localize";
|
import {tr} from "../i18n/localize";
|
||||||
import {EventClient, EventType} from "../ui/frames/log/Definitions";
|
|
||||||
import {ErrorCode} from "../connection/ErrorCode";
|
import {ErrorCode} from "../connection/ErrorCode";
|
||||||
import {server_connections} from "tc-shared/ConnectionManager";
|
import {server_connections} from "tc-shared/ConnectionManager";
|
||||||
import {ChannelEntry} from "tc-shared/tree/Channel";
|
import {ChannelEntry} from "tc-shared/tree/Channel";
|
||||||
|
import {EventClient} from "tc-shared/connectionlog/Definitions";
|
||||||
|
|
||||||
export class ServerConnectionCommandBoss extends AbstractCommandHandlerBoss {
|
export class ServerConnectionCommandBoss extends AbstractCommandHandlerBoss {
|
||||||
constructor(connection: AbstractServerConnection) {
|
constructor(connection: AbstractServerConnection) {
|
||||||
|
@ -56,6 +56,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
this["initserver"] = this.handleCommandServerInit;
|
this["initserver"] = this.handleCommandServerInit;
|
||||||
this["notifychannelmoved"] = this.handleNotifyChannelMoved;
|
this["notifychannelmoved"] = this.handleNotifyChannelMoved;
|
||||||
this["notifychanneledited"] = this.handleNotifyChannelEdited;
|
this["notifychanneledited"] = this.handleNotifyChannelEdited;
|
||||||
|
this["notifychanneldescriptionchanged"] = this.handleNotifyChannelDescriptionChanged;
|
||||||
this["notifytextmessage"] = this.handleNotifyTextMessage;
|
this["notifytextmessage"] = this.handleNotifyTextMessage;
|
||||||
this["notifyclientchatcomposing"] = this.notifyClientChatComposing;
|
this["notifyclientchatcomposing"] = this.notifyClientChatComposing;
|
||||||
this["notifyclientchatclosed"] = this.handleNotifyClientChatClosed;
|
this["notifyclientchatclosed"] = this.handleNotifyClientChatClosed;
|
||||||
|
@ -116,18 +117,18 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
if(res.id == ErrorCode.SERVER_INSUFFICIENT_PERMISSIONS) { //Permission error
|
if(res.id == ErrorCode.SERVER_INSUFFICIENT_PERMISSIONS) { //Permission error
|
||||||
const permission = this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number);
|
const permission = this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number);
|
||||||
res.message = tr("Insufficient client permissions. Failed on permission ") + (permission ? permission.name : "unknown");
|
res.message = tr("Insufficient client permissions. Failed on permission ") + (permission ? permission.name : "unknown");
|
||||||
this.connection_handler.log.log(EventType.ERROR_PERMISSION, {
|
this.connection_handler.log.log("error.permission", {
|
||||||
permission: this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number)
|
permission: this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number)
|
||||||
});
|
});
|
||||||
this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
||||||
} else if(res.id != ErrorCode.DATABASE_EMPTY_RESULT) {
|
} else if(res.id != ErrorCode.DATABASE_EMPTY_RESULT) {
|
||||||
this.connection_handler.log.log(EventType.ERROR_CUSTOM, {
|
this.connection_handler.log.log("error.custom", {
|
||||||
message: res.extra_message.length == 0 ? res.message : res.extra_message
|
message: res.extra_message.length == 0 ? res.message : res.extra_message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(typeof(ex) === "string") {
|
} else if(typeof(ex) === "string") {
|
||||||
this.connection_handler.log.log(EventType.CONNECTION_COMMAND_ERROR, {error: ex});
|
this.connection_handler.log.log("connection.command.error", {error: ex});
|
||||||
} else {
|
} else {
|
||||||
log.error(LogCategory.NETWORKING, tr("Invalid promise result type: %s. Result: %o"), typeof (ex), ex);
|
log.error(LogCategory.NETWORKING, tr("Invalid promise result type: %s. Result: %o"), typeof (ex), ex);
|
||||||
}
|
}
|
||||||
|
@ -204,7 +205,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
if(properties.virtualserver_hostmessage_mode == 1) {
|
if(properties.virtualserver_hostmessage_mode == 1) {
|
||||||
/* show in log */
|
/* show in log */
|
||||||
if(properties.virtualserver_hostmessage)
|
if(properties.virtualserver_hostmessage)
|
||||||
this.connection_handler.log.log(EventType.SERVER_HOST_MESSAGE, {
|
this.connection_handler.log.log("server.host.message", {
|
||||||
message: properties.virtualserver_hostmessage
|
message: properties.virtualserver_hostmessage
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -219,7 +220,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
if(properties.virtualserver_hostmessage_mode == 3) {
|
if(properties.virtualserver_hostmessage_mode == 3) {
|
||||||
/* first let the client initialize his stuff */
|
/* first let the client initialize his stuff */
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.connection_handler.log.log(EventType.SERVER_HOST_MESSAGE_DISCONNECT, {
|
this.connection_handler.log.log("server.host.message.disconnect", {
|
||||||
message: properties.virtualserver_welcomemessage
|
message: properties.virtualserver_welcomemessage
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -233,7 +234,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
|
|
||||||
/* welcome message */
|
/* welcome message */
|
||||||
if(properties.virtualserver_welcomemessage) {
|
if(properties.virtualserver_welcomemessage) {
|
||||||
this.connection_handler.log.log(EventType.SERVER_WELCOME_MESSAGE, {
|
this.connection_handler.log.log("server.welcome.message", {
|
||||||
message: properties.virtualserver_welcomemessage
|
message: properties.virtualserver_welcomemessage
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -504,7 +505,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
|
|
||||||
if(this.connection_handler.areQueriesShown() || client.properties.client_type != ClientType.CLIENT_QUERY) {
|
if(this.connection_handler.areQueriesShown() || client.properties.client_type != ClientType.CLIENT_QUERY) {
|
||||||
const own_channel = this.connection.client.getClient().currentChannel();
|
const own_channel = this.connection.client.getClient().currentChannel();
|
||||||
this.connection_handler.log.log(channel == own_channel ? EventType.CLIENT_VIEW_ENTER_OWN_CHANNEL : EventType.CLIENT_VIEW_ENTER, {
|
this.connection_handler.log.log(channel == own_channel ? "client.view.enter.own.channel" : "client.view.enter", {
|
||||||
channel_from: old_channel ? old_channel.log_data() : undefined,
|
channel_from: old_channel ? old_channel.log_data() : undefined,
|
||||||
channel_to: channel ? channel.log_data() : undefined,
|
channel_to: channel ? channel.log_data() : undefined,
|
||||||
client: client.log_data(),
|
client: client.log_data(),
|
||||||
|
@ -592,7 +593,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
let channel_to = tree.findChannel(targetChannelId);
|
let channel_to = tree.findChannel(targetChannelId);
|
||||||
|
|
||||||
const is_own_channel = channel_from == own_channel;
|
const is_own_channel = channel_from == own_channel;
|
||||||
this.connection_handler.log.log(is_own_channel ? EventType.CLIENT_VIEW_LEAVE_OWN_CHANNEL : EventType.CLIENT_VIEW_LEAVE, {
|
this.connection_handler.log.log(is_own_channel ? "client.view.leave.own.channel" : "client.view.leave", {
|
||||||
channel_from: channel_from ? channel_from.log_data() : undefined,
|
channel_from: channel_from ? channel_from.log_data() : undefined,
|
||||||
channel_to: channel_to ? channel_to.log_data() : undefined,
|
channel_to: channel_to ? channel_to.log_data() : undefined,
|
||||||
client: client.log_data(),
|
client: client.log_data(),
|
||||||
|
@ -673,7 +674,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
const own_channel = this.connection.client.getClient().currentChannel();
|
const own_channel = this.connection.client.getClient().currentChannel();
|
||||||
const event = self ? EventType.CLIENT_VIEW_MOVE_OWN : (channelFrom == own_channel || channel_to == own_channel ? EventType.CLIENT_VIEW_MOVE_OWN_CHANNEL : EventType.CLIENT_VIEW_MOVE);
|
const event = self ? "client.view.move.own" : (channelFrom == own_channel || channel_to == own_channel ? "client.view.move.own.channel" : "client.view.move");
|
||||||
this.connection_handler.log.log(event, {
|
this.connection_handler.log.log(event, {
|
||||||
channel_from: channelFrom ? {
|
channel_from: channelFrom ? {
|
||||||
channel_id: channelFrom.channelId,
|
channel_id: channelFrom.channelId,
|
||||||
|
@ -779,6 +780,19 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleNotifyChannelDescriptionChanged(json) {
|
||||||
|
json = json[0];
|
||||||
|
|
||||||
|
let tree = this.connection.client.channelTree;
|
||||||
|
let channel = tree.findChannel(parseInt(json["cid"]));
|
||||||
|
if(!channel) {
|
||||||
|
logWarn(LogCategory.NETWORKING, tr("Received channel description changed notify for invalid channel: %o"), json["cid"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.handleDescriptionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
handleNotifyTextMessage(json) {
|
handleNotifyTextMessage(json) {
|
||||||
json = json[0]; //Only one bulk
|
json = json[0]; //Only one bulk
|
||||||
|
|
||||||
|
@ -815,7 +829,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
});
|
});
|
||||||
if(targetIsOwn) {
|
if(targetIsOwn) {
|
||||||
this.connection_handler.sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
|
this.connection_handler.sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
|
||||||
this.connection_handler.log.log(EventType.PRIVATE_MESSAGE_RECEIVED, {
|
this.connection_handler.log.log("private.message.received", {
|
||||||
message: json["msg"],
|
message: json["msg"],
|
||||||
sender: {
|
sender: {
|
||||||
client_unique_id: json["invokeruid"],
|
client_unique_id: json["invokeruid"],
|
||||||
|
@ -825,7 +839,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.connection_handler.sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
|
this.connection_handler.sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
|
||||||
this.connection_handler.log.log(EventType.PRIVATE_MESSAGE_SEND, {
|
this.connection_handler.log.log("private.message.send", {
|
||||||
message: json["msg"],
|
message: json["msg"],
|
||||||
target: {
|
target: {
|
||||||
client_unique_id: json["invokeruid"],
|
client_unique_id: json["invokeruid"],
|
||||||
|
@ -858,7 +872,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
const invoker = this.connection_handler.channelTree.findClient(parseInt(json["invokerid"]));
|
const invoker = this.connection_handler.channelTree.findClient(parseInt(json["invokerid"]));
|
||||||
const conversations = this.connection_handler.getChannelConversations();
|
const conversations = this.connection_handler.getChannelConversations();
|
||||||
|
|
||||||
this.connection_handler.log.log(EventType.GLOBAL_MESSAGE, {
|
this.connection_handler.log.log("global.message", {
|
||||||
isOwnMessage: invoker instanceof LocalClientEntry,
|
isOwnMessage: invoker instanceof LocalClientEntry,
|
||||||
message: json["msg"],
|
message: json["msg"],
|
||||||
sender: {
|
sender: {
|
||||||
|
@ -976,7 +990,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
unique_id: json["invokeruid"]
|
unique_id: json["invokeruid"]
|
||||||
}, json["msg"]);
|
}, json["msg"]);
|
||||||
|
|
||||||
this.connection_handler.log.log(EventType.CLIENT_POKE_RECEIVED, {
|
this.connection_handler.log.log("client.poke.received", {
|
||||||
sender: this.loggable_invoker(json["invokeruid"], json["invokerid"], json["invokername"]),
|
sender: this.loggable_invoker(json["invokeruid"], json["invokerid"], json["invokername"]),
|
||||||
message: json["msg"]
|
message: json["msg"]
|
||||||
});
|
});
|
||||||
|
|
|
@ -172,6 +172,7 @@ export enum ErrorCode {
|
||||||
|
|
||||||
CUSTOM_ERROR = 0xFFFF,
|
CUSTOM_ERROR = 0xFFFF,
|
||||||
|
|
||||||
|
/** @deprecated Use SERVER_INSUFFICIENT_PERMISSIONS */
|
||||||
PERMISSION_ERROR = ErrorCode.SERVER_INSUFFICIENT_PERMISSIONS,
|
PERMISSION_ERROR = ErrorCode.SERVER_INSUFFICIENT_PERMISSIONS,
|
||||||
EMPTY_RESULT = ErrorCode.DATABASE_EMPTY_RESULT
|
EMPTY_RESULT = ErrorCode.DATABASE_EMPTY_RESULT
|
||||||
}
|
}
|
|
@ -0,0 +1,345 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import {ServerEventLog} from "tc-shared/connectionlog/ServerEventLog";
|
||||||
|
import {ViewReasonId} from "tc-shared/ConnectionHandler";
|
||||||
|
import {PermissionInfo} from "tc-shared/permission/PermissionManager";
|
||||||
|
|
||||||
|
/* FIXME: Remove this! */
|
||||||
|
export enum EventType {
|
||||||
|
CONNECTION_BEGIN = "connection.begin",
|
||||||
|
CONNECTION_HOSTNAME_RESOLVE = "connection.hostname.resolve",
|
||||||
|
CONNECTION_HOSTNAME_RESOLVE_ERROR = "connection.hostname.resolve.error",
|
||||||
|
CONNECTION_HOSTNAME_RESOLVED = "connection.hostname.resolved",
|
||||||
|
CONNECTION_LOGIN = "connection.login",
|
||||||
|
CONNECTION_CONNECTED = "connection.connected",
|
||||||
|
CONNECTION_FAILED = "connection.failed",
|
||||||
|
|
||||||
|
DISCONNECTED = "disconnected",
|
||||||
|
|
||||||
|
CONNECTION_VOICE_CONNECT = "connection.voice.connect",
|
||||||
|
CONNECTION_VOICE_CONNECT_FAILED = "connection.voice.connect.failed",
|
||||||
|
CONNECTION_VOICE_CONNECT_SUCCEEDED = "connection.voice.connect.succeeded",
|
||||||
|
CONNECTION_VOICE_DROPPED = "connection.voice.dropped",
|
||||||
|
|
||||||
|
CONNECTION_COMMAND_ERROR = "connection.command.error",
|
||||||
|
|
||||||
|
GLOBAL_MESSAGE = "global.message",
|
||||||
|
|
||||||
|
SERVER_WELCOME_MESSAGE = "server.welcome.message",
|
||||||
|
SERVER_HOST_MESSAGE = "server.host.message",
|
||||||
|
SERVER_HOST_MESSAGE_DISCONNECT = "server.host.message.disconnect",
|
||||||
|
|
||||||
|
SERVER_CLOSED = "server.closed",
|
||||||
|
SERVER_BANNED = "server.banned",
|
||||||
|
SERVER_REQUIRES_PASSWORD = "server.requires.password",
|
||||||
|
|
||||||
|
CLIENT_VIEW_ENTER = "client.view.enter",
|
||||||
|
CLIENT_VIEW_LEAVE = "client.view.leave",
|
||||||
|
CLIENT_VIEW_MOVE = "client.view.move",
|
||||||
|
|
||||||
|
CLIENT_VIEW_ENTER_OWN_CHANNEL = "client.view.enter.own.channel",
|
||||||
|
CLIENT_VIEW_LEAVE_OWN_CHANNEL = "client.view.leave.own.channel",
|
||||||
|
CLIENT_VIEW_MOVE_OWN_CHANNEL = "client.view.move.own.channel",
|
||||||
|
|
||||||
|
CLIENT_VIEW_MOVE_OWN = "client.view.move.own",
|
||||||
|
|
||||||
|
CLIENT_NICKNAME_CHANGED = "client.nickname.changed",
|
||||||
|
CLIENT_NICKNAME_CHANGED_OWN = "client.nickname.changed.own",
|
||||||
|
CLIENT_NICKNAME_CHANGE_FAILED = "client.nickname.change.failed",
|
||||||
|
|
||||||
|
CLIENT_SERVER_GROUP_ADD = "client.server.group.add",
|
||||||
|
CLIENT_SERVER_GROUP_REMOVE = "client.server.group.remove",
|
||||||
|
CLIENT_CHANNEL_GROUP_CHANGE = "client.channel.group.change",
|
||||||
|
|
||||||
|
PRIVATE_MESSAGE_RECEIVED = "private.message.received",
|
||||||
|
PRIVATE_MESSAGE_SEND = "private.message.send",
|
||||||
|
|
||||||
|
CHANNEL_CREATE = "channel.create",
|
||||||
|
CHANNEL_DELETE = "channel.delete",
|
||||||
|
|
||||||
|
ERROR_CUSTOM = "error.custom",
|
||||||
|
ERROR_PERMISSION = "error.permission",
|
||||||
|
|
||||||
|
CLIENT_POKE_RECEIVED = "client.poke.received",
|
||||||
|
CLIENT_POKE_SEND = "client.poke.send",
|
||||||
|
|
||||||
|
RECONNECT_SCHEDULED = "reconnect.scheduled",
|
||||||
|
RECONNECT_EXECUTE = "reconnect.execute",
|
||||||
|
RECONNECT_CANCELED = "reconnect.canceled",
|
||||||
|
|
||||||
|
WEBRTC_FATAL_ERROR = "webrtc.fatal.error"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventClient = {
|
||||||
|
client_unique_id: string;
|
||||||
|
client_name: string;
|
||||||
|
client_id: number;
|
||||||
|
}
|
||||||
|
export type EventChannelData = {
|
||||||
|
channel_id: number;
|
||||||
|
channel_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventServerAddress = {
|
||||||
|
server_hostname: string;
|
||||||
|
server_port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace event {
|
||||||
|
export type EventGlobalMessage = {
|
||||||
|
isOwnMessage: boolean;
|
||||||
|
sender: EventClient;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
export type EventConnectBegin = {
|
||||||
|
address: EventServerAddress;
|
||||||
|
client_nickname: string;
|
||||||
|
}
|
||||||
|
export type EventErrorCustom = {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventReconnectScheduled = {
|
||||||
|
timeout: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventReconnectCanceled = { }
|
||||||
|
export type EventReconnectExecute = { }
|
||||||
|
|
||||||
|
export type EventErrorPermission = {
|
||||||
|
permission: PermissionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventWelcomeMessage = {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventHostMessageDisconnect = {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventClientMove = {
|
||||||
|
channel_from?: EventChannelData;
|
||||||
|
channel_from_own: boolean;
|
||||||
|
|
||||||
|
channel_to?: EventChannelData;
|
||||||
|
channel_to_own: boolean;
|
||||||
|
|
||||||
|
client: EventClient;
|
||||||
|
client_own: boolean;
|
||||||
|
|
||||||
|
invoker?: EventClient;
|
||||||
|
|
||||||
|
message?: string;
|
||||||
|
reason: ViewReasonId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventClientEnter = {
|
||||||
|
channel_from?: EventChannelData;
|
||||||
|
channel_to?: EventChannelData;
|
||||||
|
|
||||||
|
client: EventClient;
|
||||||
|
invoker?: EventClient;
|
||||||
|
|
||||||
|
message?: string;
|
||||||
|
|
||||||
|
reason: ViewReasonId;
|
||||||
|
ban_time?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventClientLeave = {
|
||||||
|
channel_from?: EventChannelData;
|
||||||
|
channel_to?: EventChannelData;
|
||||||
|
|
||||||
|
client: EventClient;
|
||||||
|
invoker?: EventClient;
|
||||||
|
|
||||||
|
message?: string;
|
||||||
|
|
||||||
|
reason: ViewReasonId;
|
||||||
|
ban_time?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventChannelCreate = {
|
||||||
|
creator: EventClient,
|
||||||
|
channel: EventChannelData,
|
||||||
|
ownAction: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventChannelToggle = {
|
||||||
|
channel: EventChannelData
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventChannelDelete = {
|
||||||
|
deleter: EventClient,
|
||||||
|
channel: EventChannelData,
|
||||||
|
ownAction: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventConnectionConnected = {
|
||||||
|
serverName: string,
|
||||||
|
serverAddress: EventServerAddress,
|
||||||
|
own_client: EventClient;
|
||||||
|
}
|
||||||
|
export type EventConnectionFailed = {
|
||||||
|
serverAddress: EventServerAddress
|
||||||
|
}
|
||||||
|
export type EventConnectionLogin = {}
|
||||||
|
export type EventConnectionHostnameResolve = {};
|
||||||
|
export type EventConnectionHostnameResolved = {
|
||||||
|
address: EventServerAddress;
|
||||||
|
}
|
||||||
|
export type EventConnectionHostnameResolveError = {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventConnectionVoiceConnectFailed = {
|
||||||
|
reason: string;
|
||||||
|
reconnect_delay: number; /* if less or equal to 0 reconnect is prohibited */
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventConnectionVoiceConnectSucceeded = {}
|
||||||
|
|
||||||
|
export type EventConnectionVoiceConnect = {
|
||||||
|
attemptCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventConnectionVoiceDropped = {}
|
||||||
|
|
||||||
|
export type EventConnectionCommandError = {
|
||||||
|
error: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventClientNicknameChanged = {
|
||||||
|
client: EventClient;
|
||||||
|
|
||||||
|
old_name: string;
|
||||||
|
new_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventClientNicknameChangeFailed = {
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventServerClosed = {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventServerRequiresPassword = {}
|
||||||
|
|
||||||
|
export type EventServerBanned = {
|
||||||
|
message: string;
|
||||||
|
time: number;
|
||||||
|
|
||||||
|
invoker: EventClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventClientPokeReceived = {
|
||||||
|
sender: EventClient,
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventClientPokeSend = {
|
||||||
|
target: EventClient,
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventPrivateMessageSend = {
|
||||||
|
target: EventClient,
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventPrivateMessageReceived = {
|
||||||
|
sender: EventClient,
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventWebrtcFatalError = {
|
||||||
|
message: string,
|
||||||
|
retryTimeout: number | 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LogMessage = {
|
||||||
|
type: EventType;
|
||||||
|
uniqueId: string;
|
||||||
|
timestamp: number;
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TypeInfo {
|
||||||
|
"connection.begin" : event.EventConnectBegin;
|
||||||
|
"global.message": event.EventGlobalMessage;
|
||||||
|
|
||||||
|
"error.custom": event.EventErrorCustom;
|
||||||
|
"error.permission": event.EventErrorPermission;
|
||||||
|
|
||||||
|
"connection.hostname.resolved": event.EventConnectionHostnameResolved;
|
||||||
|
"connection.hostname.resolve": event.EventConnectionHostnameResolve;
|
||||||
|
"connection.hostname.resolve.error": event.EventConnectionHostnameResolveError;
|
||||||
|
"connection.failed": event.EventConnectionFailed;
|
||||||
|
"connection.login": event.EventConnectionLogin;
|
||||||
|
"connection.connected": event.EventConnectionConnected;
|
||||||
|
"connection.voice.dropped": event.EventConnectionVoiceDropped;
|
||||||
|
"connection.voice.connect": event.EventConnectionVoiceConnect;
|
||||||
|
"connection.voice.connect.failed": event.EventConnectionVoiceConnectFailed;
|
||||||
|
"connection.voice.connect.succeeded": event.EventConnectionVoiceConnectSucceeded;
|
||||||
|
"connection.command.error": event.EventConnectionCommandError;
|
||||||
|
|
||||||
|
"reconnect.scheduled": event.EventReconnectScheduled;
|
||||||
|
"reconnect.canceled": event.EventReconnectCanceled;
|
||||||
|
"reconnect.execute": event.EventReconnectExecute;
|
||||||
|
|
||||||
|
"server.welcome.message": event.EventWelcomeMessage;
|
||||||
|
"server.host.message": event.EventWelcomeMessage;
|
||||||
|
"server.host.message.disconnect": event.EventHostMessageDisconnect;
|
||||||
|
|
||||||
|
"server.closed": event.EventServerClosed;
|
||||||
|
"server.requires.password": event.EventServerRequiresPassword;
|
||||||
|
"server.banned": event.EventServerBanned;
|
||||||
|
|
||||||
|
"client.view.enter": event.EventClientEnter;
|
||||||
|
"client.view.move": event.EventClientMove;
|
||||||
|
"client.view.leave": event.EventClientLeave;
|
||||||
|
|
||||||
|
"client.view.enter.own.channel": event.EventClientEnter;
|
||||||
|
"client.view.move.own.channel": event.EventClientMove;
|
||||||
|
"client.view.leave.own.channel": event.EventClientLeave;
|
||||||
|
|
||||||
|
"client.view.move.own": event.EventClientMove;
|
||||||
|
|
||||||
|
"client.nickname.change.failed": event.EventClientNicknameChangeFailed,
|
||||||
|
"client.nickname.changed": event.EventClientNicknameChanged,
|
||||||
|
"client.nickname.changed.own": event.EventClientNicknameChanged,
|
||||||
|
|
||||||
|
"channel.create": event.EventChannelCreate,
|
||||||
|
"channel.show": event.EventChannelToggle,
|
||||||
|
"channel.hide": event.EventChannelToggle,
|
||||||
|
"channel.delete": event.EventChannelDelete,
|
||||||
|
|
||||||
|
"client.poke.received": event.EventClientPokeReceived,
|
||||||
|
"client.poke.send": event.EventClientPokeSend,
|
||||||
|
|
||||||
|
"private.message.received": event.EventPrivateMessageReceived,
|
||||||
|
"private.message.send": event.EventPrivateMessageSend,
|
||||||
|
|
||||||
|
"webrtc.fatal.error": event.EventWebrtcFatalError
|
||||||
|
|
||||||
|
"disconnected": any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventDispatcher<EventType extends keyof TypeInfo> {
|
||||||
|
log(data: TypeInfo[EventType], logger: ServerEventLog) : React.ReactNode;
|
||||||
|
notify(data: TypeInfo[EventType], logger: ServerEventLog);
|
||||||
|
sound(data: TypeInfo[EventType], logger: ServerEventLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerLogUIEvents {
|
||||||
|
"query_log": {},
|
||||||
|
"notify_log": {
|
||||||
|
log: LogMessage[]
|
||||||
|
},
|
||||||
|
"notify_log_add": {
|
||||||
|
event: LogMessage
|
||||||
|
},
|
||||||
|
"notify_show": {}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import {EventType} from "../../../ui/frames/log/Definitions";
|
import {settings, Settings} from "tc-shared/settings";
|
||||||
import {Settings, settings} from "../../../settings";
|
import {EventType, TypeInfo} from "tc-shared/connectionlog/Definitions";
|
||||||
|
|
||||||
const focusDefaultStatus = {};
|
const focusDefaultStatus: {[T in keyof TypeInfo]?: boolean} = {};
|
||||||
focusDefaultStatus[EventType.CLIENT_POKE_RECEIVED] = true;
|
focusDefaultStatus["client.poke.received"] = true;
|
||||||
|
|
||||||
export function requestWindowFocus() {
|
export function requestWindowFocus() {
|
||||||
if(__build.target === "web") {
|
if(__build.target === "web") {
|
|
@ -1,29 +1,27 @@
|
||||||
import * as loader from "tc-loader";
|
import * as loader from "tc-loader";
|
||||||
import {Stage} from "tc-loader";
|
import {Stage} from "tc-loader";
|
||||||
import * as log from "../../../log";
|
|
||||||
import {LogCategory} from "../../../log";
|
|
||||||
import {EventClient, EventServerAddress, EventType, TypeInfo} from "../../../ui/frames/log/Definitions";
|
|
||||||
import {renderBBCodeAsText} from "../../../text/bbcode";
|
|
||||||
import {format_time} from "../../../ui/frames/chat";
|
|
||||||
import {ViewReasonId} from "../../../ConnectionHandler";
|
|
||||||
import {findLogDispatcher} from "../../../ui/frames/log/DispatcherLog";
|
|
||||||
import {formatDate} from "../../../MessageFormatter";
|
|
||||||
import {Settings, settings} from "../../../settings";
|
|
||||||
import {server_connections} from "tc-shared/ConnectionManager";
|
import {server_connections} from "tc-shared/ConnectionManager";
|
||||||
import {getIconManager} from "tc-shared/file/Icons";
|
import {getIconManager} from "tc-shared/file/Icons";
|
||||||
import { tra, tr } from "tc-shared/i18n/localize";
|
import { tra, tr } from "tc-shared/i18n/localize";
|
||||||
|
import {EventClient, EventServerAddress, EventType, TypeInfo} from "tc-shared/connectionlog/Definitions";
|
||||||
|
import {Settings, settings} from "tc-shared/settings";
|
||||||
|
import {format_time} from "tc-shared/ui/frames/chat";
|
||||||
|
import {ViewReasonId} from "tc-shared/ConnectionHandler";
|
||||||
|
import {formatDate} from "tc-shared/MessageFormatter";
|
||||||
|
import {renderBBCodeAsText} from "tc-shared/text/bbcode";
|
||||||
|
import {LogCategory, logInfo} from "tc-shared/log";
|
||||||
|
|
||||||
export type DispatcherLog<T extends keyof TypeInfo> = (data: TypeInfo[T], handlerId: string, eventType: T) => void;
|
export type DispatcherLog<T extends keyof TypeInfo> = (data: TypeInfo[T], handlerId: string, eventType: T) => void;
|
||||||
|
|
||||||
const notificationDefaultStatus = {};
|
const notificationDefaultStatus: {[T in keyof TypeInfo]?: boolean} = {};
|
||||||
notificationDefaultStatus[EventType.CLIENT_POKE_RECEIVED] = true;
|
notificationDefaultStatus["client.poke.received"] = true;
|
||||||
notificationDefaultStatus[EventType.SERVER_BANNED] = true;
|
notificationDefaultStatus["server.banned"] = true;
|
||||||
notificationDefaultStatus[EventType.SERVER_CLOSED] = true;
|
notificationDefaultStatus["server.closed"] = true;
|
||||||
notificationDefaultStatus[EventType.SERVER_HOST_MESSAGE_DISCONNECT] = true;
|
notificationDefaultStatus["server.host.message.disconnect"] = true;
|
||||||
notificationDefaultStatus[EventType.GLOBAL_MESSAGE] = true;
|
notificationDefaultStatus["global.message"] = true;
|
||||||
notificationDefaultStatus[EventType.CONNECTION_FAILED] = true;
|
notificationDefaultStatus["connection.failed"] = true;
|
||||||
notificationDefaultStatus[EventType.PRIVATE_MESSAGE_RECEIVED] = true;
|
notificationDefaultStatus["private.message.received"] = true;
|
||||||
notificationDefaultStatus[EventType.CONNECTION_VOICE_DROPPED] = true;
|
notificationDefaultStatus["connection.voice.dropped"] = true;
|
||||||
|
|
||||||
let windowFocused = false;
|
let windowFocused = false;
|
||||||
|
|
||||||
|
@ -210,7 +208,7 @@ registerDispatcher(EventType.SERVER_BANNED, (data, handlerId) => {
|
||||||
|
|
||||||
spawnServerNotification(handlerId, {
|
spawnServerNotification(handlerId, {
|
||||||
body: data.invoker.client_id > 0 ? tra("You've been banned from the server by {0} for {1}.{2}", data.invoker.client_name, time, reason) :
|
body: data.invoker.client_id > 0 ? tra("You've been banned from the server by {0} for {1}.{2}", data.invoker.client_name, time, reason) :
|
||||||
tra("You've been banned from the server for {0}.{1}", time, reason)
|
tra("You've been banned from the server for {0}.{1}", time, reason)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -320,7 +318,7 @@ registerDispatcher(EventType.CLIENT_VIEW_MOVE, (data, handlerId) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_VIEW_MOVE_OWN_CHANNEL, findLogDispatcher(EventType.CLIENT_VIEW_MOVE));
|
registerDispatcher(EventType.CLIENT_VIEW_MOVE_OWN_CHANNEL, findNotificationDispatcher(EventType.CLIENT_VIEW_MOVE));
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_VIEW_MOVE_OWN, (data, handlerId) => {
|
registerDispatcher(EventType.CLIENT_VIEW_MOVE_OWN, (data, handlerId) => {
|
||||||
let message;
|
let message;
|
||||||
|
@ -406,7 +404,7 @@ registerDispatcher(EventType.CLIENT_VIEW_LEAVE_OWN_CHANNEL, (data, handlerId) =>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return findLogDispatcher(EventType.CLIENT_VIEW_LEAVE)(data, handlerId, EventType.CLIENT_VIEW_LEAVE);
|
return findNotificationDispatcher("client.view.leave")(data, handlerId, EventType.CLIENT_VIEW_LEAVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
spawnClientNotification(handlerId, data.client, {
|
spawnClientNotification(handlerId, data.client, {
|
||||||
|
@ -482,19 +480,19 @@ registerDispatcher(EventType.PRIVATE_MESSAGE_RECEIVED, (data, handlerId) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.WEBRTC_FATAL_ERROR, (data, handlerId) => {
|
registerDispatcher(EventType.WEBRTC_FATAL_ERROR, (data, handlerId) => {
|
||||||
if(data.retryTimeout) {
|
if(data.retryTimeout) {
|
||||||
let time = Math.ceil(data.retryTimeout / 1000);
|
let time = Math.ceil(data.retryTimeout / 1000);
|
||||||
let minutes = Math.floor(time / 60);
|
let minutes = Math.floor(time / 60);
|
||||||
let seconds = time % 60;
|
let seconds = time % 60;
|
||||||
|
|
||||||
spawnServerNotification(handlerId, {
|
spawnServerNotification(handlerId, {
|
||||||
body: tra("WebRTC connection closed due to a fatal error:\n{}\nRetry scheduled in {}.", data.message, (minutes > 0 ? minutes + "m" : "") + seconds + "s")
|
body: tra("WebRTC connection closed due to a fatal error:\n{}\nRetry scheduled in {}.", data.message, (minutes > 0 ? minutes + "m" : "") + seconds + "s")
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
spawnServerNotification(handlerId, {
|
spawnServerNotification(handlerId, {
|
||||||
body: tra("WebRTC connection closed due to a fatal error:\n{}\nNo retry scheduled.", data.message)
|
body: tra("WebRTC connection closed due to a fatal error:\n{}\nNo retry scheduled.", data.message)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* snipped PRIVATE_MESSAGE_SEND */
|
/* snipped PRIVATE_MESSAGE_SEND */
|
||||||
|
@ -509,14 +507,14 @@ loader.register_task(Stage.LOADED, {
|
||||||
|
|
||||||
/* yeahr fuck safari */
|
/* yeahr fuck safari */
|
||||||
const promise = Notification.requestPermission(result => {
|
const promise = Notification.requestPermission(result => {
|
||||||
log.info(LogCategory.GENERAL, tr("Notification permission request (callback) resulted in %s"), result);
|
logInfo(LogCategory.GENERAL, tr("Notification permission request (callback) resulted in %s"), result);
|
||||||
})
|
})
|
||||||
|
|
||||||
if(typeof promise !== "undefined" && 'then' in promise) {
|
if(typeof promise !== "undefined" && 'then' in promise) {
|
||||||
promise.then(result => {
|
promise.then(result => {
|
||||||
log.info(LogCategory.GENERAL, tr("Notification permission request resulted in %s"), result);
|
logInfo(LogCategory.GENERAL, tr("Notification permission request resulted in %s"), result);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
log.warn(LogCategory.GENERAL, tr("Failed to execute notification permission request: %O"), error);
|
logInfo(LogCategory.GENERAL, tr("Failed to execute notification permission request: %O"), error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
|
@ -0,0 +1,61 @@
|
||||||
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
|
import * as React from "react";
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
import {Settings, settings} from "tc-shared/settings";
|
||||||
|
import {LogMessage, TypeInfo} from "tc-shared/connectionlog/Definitions";
|
||||||
|
import {findNotificationDispatcher, isNotificationEnabled} from "tc-shared/connectionlog/DispatcherNotifications";
|
||||||
|
import {isFocusRequestEnabled, requestWindowFocus} from "tc-shared/connectionlog/DispatcherFocus";
|
||||||
|
|
||||||
|
let uniqueLogEventId = 0;
|
||||||
|
export interface ServerEventLogEvents {
|
||||||
|
notify_log_add: { event: LogMessage }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServerEventLog {
|
||||||
|
readonly events: Registry<ServerEventLogEvents>;
|
||||||
|
private readonly connection: ConnectionHandler;
|
||||||
|
|
||||||
|
private maxHistoryLength: number = 100;
|
||||||
|
private eventLog: LogMessage[] = [];
|
||||||
|
|
||||||
|
constructor(connection: ConnectionHandler) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.events = new Registry<ServerEventLogEvents>();
|
||||||
|
}
|
||||||
|
|
||||||
|
log<T extends keyof TypeInfo>(type: T, data: TypeInfo[T]) {
|
||||||
|
const event = {
|
||||||
|
data: data,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
type: type as any,
|
||||||
|
uniqueId: "log-" + Date.now() + "-" + (++uniqueLogEventId)
|
||||||
|
};
|
||||||
|
|
||||||
|
if(settings.global(Settings.FN_EVENTS_LOG_ENABLED(type), true)) {
|
||||||
|
this.eventLog.push(event);
|
||||||
|
while(this.eventLog.length > this.maxHistoryLength) {
|
||||||
|
this.eventLog.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events.fire("notify_log_add", { event: event });
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isNotificationEnabled(type as any)) {
|
||||||
|
const notification = findNotificationDispatcher(type);
|
||||||
|
if(notification) notification(data, this.connection.handlerId, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isFocusRequestEnabled(type as any)) {
|
||||||
|
requestWindowFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistory() : LogMessage[] {
|
||||||
|
return this.eventLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.events.destroy();
|
||||||
|
this.eventLog = undefined;
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,7 +59,6 @@ export abstract class AbstractChat<Events extends AbstractConversationEvents> {
|
||||||
protected errorMessage: string;
|
protected errorMessage: string;
|
||||||
|
|
||||||
private conversationMode: ChannelConversationMode;
|
private conversationMode: ChannelConversationMode;
|
||||||
protected crossChannelChatSupported: boolean = true;
|
|
||||||
|
|
||||||
protected unreadTimestamp: number;
|
protected unreadTimestamp: number;
|
||||||
protected unreadState: boolean = false;
|
protected unreadState: boolean = false;
|
||||||
|
@ -338,6 +337,9 @@ export interface AbstractChatManagerEvents<ConversationType> {
|
||||||
},
|
},
|
||||||
notify_unread_count_changed: {
|
notify_unread_count_changed: {
|
||||||
unreadConversations: number
|
unreadConversations: number
|
||||||
|
},
|
||||||
|
notify_cross_conversation_support_changed: {
|
||||||
|
crossConversationSupported: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,18 +353,23 @@ export abstract class AbstractChatManager<ManagerEvents extends AbstractChatMana
|
||||||
private selectedConversation: ConversationType;
|
private selectedConversation: ConversationType;
|
||||||
|
|
||||||
private currentUnreadCount: number;
|
private currentUnreadCount: number;
|
||||||
|
private crossConversationSupport: boolean;
|
||||||
|
|
||||||
/* FIXME: Access modifier */
|
/* FIXME: Access modifier */
|
||||||
public historyUiStates: {[id: string]: {
|
public historyUiStates: {[id: string]: {
|
||||||
executingUIHistoryQuery: boolean,
|
executingUIHistoryQuery: boolean,
|
||||||
historyErrorMessage: string | undefined,
|
historyErrorMessage: string | undefined,
|
||||||
historyRetryTimestamp: number
|
historyRetryTimestamp: number
|
||||||
}} = {};
|
}} = {};
|
||||||
|
|
||||||
protected constructor(connection: ConnectionHandler) {
|
protected constructor(connection: ConnectionHandler) {
|
||||||
|
this.connection = connection;
|
||||||
this.events = new Registry<ManagerEvents>();
|
this.events = new Registry<ManagerEvents>();
|
||||||
this.listenerConnection = [];
|
this.listenerConnection = [];
|
||||||
this.currentUnreadCount = 0;
|
this.currentUnreadCount = 0;
|
||||||
|
|
||||||
|
this.crossConversationSupport = true;
|
||||||
|
|
||||||
this.listenerUnreadTimestamp = () => {
|
this.listenerUnreadTimestamp = () => {
|
||||||
let count = this.getConversations().filter(conversation => conversation.isUnread()).length;
|
let count = this.getConversations().filter(conversation => conversation.isUnread()).length;
|
||||||
if(count === this.currentUnreadCount) { return; }
|
if(count === this.currentUnreadCount) { return; }
|
||||||
|
@ -387,6 +394,10 @@ export abstract class AbstractChatManager<ManagerEvents extends AbstractChatMana
|
||||||
return this.currentUnreadCount;
|
return this.currentUnreadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasCrossConversationSupport() : boolean {
|
||||||
|
return this.crossConversationSupport;
|
||||||
|
}
|
||||||
|
|
||||||
getSelectedConversation() : ConversationType {
|
getSelectedConversation() : ConversationType {
|
||||||
return this.selectedConversation;
|
return this.selectedConversation;
|
||||||
}
|
}
|
||||||
|
@ -432,4 +443,13 @@ export abstract class AbstractChatManager<ManagerEvents extends AbstractChatMana
|
||||||
|
|
||||||
this.listenerUnreadTimestamp();
|
this.listenerUnreadTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected setCrossConversationSupport(supported: boolean) {
|
||||||
|
if(this.crossConversationSupport === supported) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.crossConversationSupport = supported;
|
||||||
|
this.events.fire("notify_cross_conversation_support_changed", { crossConversationSupported: supported });
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -16,6 +16,7 @@ import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
|
||||||
import {LocalClientEntry} from "tc-shared/tree/Client";
|
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";
|
||||||
|
import {ServerFeature} from "tc-shared/connection/ServerFeatures";
|
||||||
|
|
||||||
export interface ChannelConversationEvents extends AbstractConversationEvents {
|
export interface ChannelConversationEvents extends AbstractConversationEvents {
|
||||||
notify_messages_deleted: { messages: string[] },
|
notify_messages_deleted: { messages: string[] },
|
||||||
|
@ -45,7 +46,7 @@ export class ChannelConversation extends AbstractChat<ChannelConversationEvents>
|
||||||
this.setUnreadTimestamp(unreadTimestamp);
|
this.setUnreadTimestamp(unreadTimestamp);
|
||||||
this.preventUnreadUpdate = false;
|
this.preventUnreadUpdate = false;
|
||||||
|
|
||||||
this.events.on(["notify_unread_state_changed", "notify_read_state_changed"], event => {
|
this.events.on(["notify_unread_state_changed", "notify_read_state_changed"], () => {
|
||||||
this.handle.connection.channelTree.findChannel(this.conversationId)?.setUnread(this.isReadable() && this.isUnread());
|
this.handle.connection.channelTree.findChannel(this.conversationId)?.setUnread(this.isReadable() && this.isUnread());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -175,7 +176,6 @@ export class ChannelConversation extends AbstractChat<ChannelConversationEvents>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "unsupported":
|
case "unsupported":
|
||||||
this.crossChannelChatSupported = false;
|
|
||||||
this.setConversationMode(ChannelConversationMode.Private, false);
|
this.setConversationMode(ChannelConversationMode.Private, false);
|
||||||
this.setCurrentMode("normal");
|
this.setCurrentMode("normal");
|
||||||
break;
|
break;
|
||||||
|
@ -348,6 +348,16 @@ export class ChannelConversationManager extends AbstractChatManager<ChannelConve
|
||||||
conversation.destroy();
|
conversation.destroy();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(event.newState === ConnectionState.CONNECTED) {
|
||||||
|
connection.serverFeatures.awaitFeatures().then(success => {
|
||||||
|
if(!success) { return; }
|
||||||
|
|
||||||
|
this.setCrossConversationSupport(connection.serverFeatures.supportsFeature(ServerFeature.ADVANCED_CHANNEL_CHAT));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setCrossConversationSupport(true);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.listenerConnection.push(connection.channelTree.events.on("notify_channel_updated", event => {
|
this.listenerConnection.push(connection.channelTree.events.on("notify_channel_updated", event => {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {parse as parseBBCode} from "vendor/xbbcode/parser";
|
||||||
import {fixupJQueryUrlTags} from "tc-shared/text/bbcode/url";
|
import {fixupJQueryUrlTags} from "tc-shared/text/bbcode/url";
|
||||||
import {fixupJQueryImageTags} from "tc-shared/text/bbcode/image";
|
import {fixupJQueryImageTags} from "tc-shared/text/bbcode/image";
|
||||||
import "./bbcode.scss";
|
import "./bbcode.scss";
|
||||||
|
import {BBCodeHandlerContext} from "vendor/xbbcode/renderer/react";
|
||||||
|
|
||||||
export const escapeBBCode = (text: string) => text.replace(/(\[)/g, "\\$1");
|
export const escapeBBCode = (text: string) => text.replace(/(\[)/g, "\\$1");
|
||||||
|
|
||||||
|
@ -80,11 +81,23 @@ function preprocessMessage(message: string, settings: BBCodeRenderOptions) : str
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BBCodeRenderer = (props: { message: string, settings: BBCodeRenderOptions }) => (
|
export const BBCodeRenderer = (props: { message: string, settings: BBCodeRenderOptions, handlerId?: string }) => {
|
||||||
<XBBCodeRenderer options={{ tag_whitelist: allowedBBCodes }} renderer={rendererReact}>
|
if(props.handlerId) {
|
||||||
{preprocessMessage(props.message, props.settings)}
|
return (
|
||||||
</XBBCodeRenderer>
|
<BBCodeHandlerContext.Provider value={props.handlerId} key={"handler-id"}>
|
||||||
);
|
<XBBCodeRenderer options={{ tag_whitelist: allowedBBCodes }} renderer={rendererReact}>
|
||||||
|
{preprocessMessage(props.message, props.settings)}
|
||||||
|
</XBBCodeRenderer>
|
||||||
|
</BBCodeHandlerContext.Provider>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<XBBCodeRenderer options={{ tag_whitelist: allowedBBCodes }} renderer={rendererReact} key={"no-handler-id"}>
|
||||||
|
{preprocessMessage(props.message, props.settings)}
|
||||||
|
</XBBCodeRenderer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function renderBBCodeAsJQuery(message: string, settings: BBCodeRenderOptions) : JQuery[] {
|
export function renderBBCodeAsJQuery(message: string, settings: BBCodeRenderOptions) : JQuery[] {
|
||||||
|
|
|
@ -8,4 +8,6 @@ export const rendererHTML = new HTMLRenderer(rendererReact);
|
||||||
|
|
||||||
import "./emoji";
|
import "./emoji";
|
||||||
import "./highlight";
|
import "./highlight";
|
||||||
import "./youtube";
|
import "./youtube";
|
||||||
|
import "./url";
|
||||||
|
import "./image";
|
|
@ -4,8 +4,10 @@ import * as loader from "tc-loader";
|
||||||
import {ElementRenderer} from "vendor/xbbcode/renderer/base";
|
import {ElementRenderer} from "vendor/xbbcode/renderer/base";
|
||||||
import {TagElement} from "vendor/xbbcode/elements";
|
import {TagElement} from "vendor/xbbcode/elements";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ReactRenderer from "vendor/xbbcode/renderer/react";
|
import ReactRenderer, {BBCodeHandlerContext} from "vendor/xbbcode/renderer/react";
|
||||||
import {rendererReact, rendererText} from "tc-shared/text/bbcode/renderer";
|
import {rendererReact, rendererText} from "tc-shared/text/bbcode/renderer";
|
||||||
|
import {ClientTag} from "tc-shared/ui/tree/EntryTags";
|
||||||
|
import {useContext} from "react";
|
||||||
|
|
||||||
function spawnUrlContextMenu(pageX: number, pageY: number, target: string) {
|
function spawnUrlContextMenu(pageX: number, pageY: number, target: string) {
|
||||||
contextmenu.spawn_context_menu(pageX, pageY, {
|
contextmenu.spawn_context_menu(pageX, pageY, {
|
||||||
|
@ -31,6 +33,8 @@ function spawnUrlContextMenu(pageX: number, pageY: number, target: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ClientUrlRegex = /client:\/\/([0-9]+)\/([-A-Za-z0-9+/=]+)~/g;
|
||||||
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
name: "XBBCode code tag init",
|
name: "XBBCode code tag init",
|
||||||
function: async () => {
|
function: async () => {
|
||||||
|
@ -39,17 +43,36 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
const regexUrl = /^(?:[a-zA-Z]{1,16}):(?:\/{1,3}|\\)[-a-zA-Z0-9:;,@#%&()~_?+=\/\\.]*$/g;
|
const regexUrl = /^(?:[a-zA-Z]{1,16}):(?:\/{1,3}|\\)[-a-zA-Z0-9:;,@#%&()~_?+=\/\\.]*$/g;
|
||||||
rendererReact.registerCustomRenderer(new class extends ElementRenderer<TagElement, React.ReactNode> {
|
rendererReact.registerCustomRenderer(new class extends ElementRenderer<TagElement, React.ReactNode> {
|
||||||
render(element: TagElement, renderer: ReactRenderer): React.ReactNode {
|
render(element: TagElement, renderer: ReactRenderer): React.ReactNode {
|
||||||
let target;
|
let target: string;
|
||||||
if (!element.options)
|
if (!element.options) {
|
||||||
target = rendererText.render(element);
|
target = rendererText.render(element);
|
||||||
else
|
} else {
|
||||||
target = element.options;
|
target = element.options;
|
||||||
|
}
|
||||||
|
|
||||||
regexUrl.lastIndex = 0;
|
regexUrl.lastIndex = 0;
|
||||||
if (!regexUrl.test(target))
|
if (!regexUrl.test(target)) {
|
||||||
target = '#';
|
target = '#';
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlerId = useContext(BBCodeHandlerContext);
|
||||||
|
|
||||||
|
if(handlerId) {
|
||||||
|
/* TS3-Protocol for a client */
|
||||||
|
if(target.match(ClientUrlRegex)) {
|
||||||
|
const clientData = target.match(ClientUrlRegex);
|
||||||
|
const clientDatabaseId = parseInt(clientData[1]);
|
||||||
|
const clientUniqueId = clientDatabaseId[2];
|
||||||
|
|
||||||
|
return <ClientTag
|
||||||
|
clientName={rendererText.renderContent(element).join("")}
|
||||||
|
clientUniqueId={clientUniqueId}
|
||||||
|
clientDatabaseId={clientDatabaseId > 0 ? clientDatabaseId : undefined}
|
||||||
|
handlerId={handlerId}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: Implement client URLs */
|
|
||||||
return <a key={"er-" + ++reactId} className={"xbbcode xbbcode-tag-url"} href={target} target={"_blank"} onContextMenu={event => {
|
return <a key={"er-" + ++reactId} className={"xbbcode xbbcode-tag-url"} href={target} target={"_blank"} onContextMenu={event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
spawnUrlContextMenu(event.pageX, event.pageY, target);
|
spawnUrlContextMenu(event.pageX, event.pageY, target);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {renderMarkdownAsBBCode} from "../text/markdown";
|
||||||
import {escapeBBCode} from "../text/bbcode";
|
import {escapeBBCode} from "../text/bbcode";
|
||||||
import {parse as parseBBCode} from "vendor/xbbcode/parser";
|
import {parse as parseBBCode} from "vendor/xbbcode/parser";
|
||||||
import {TagElement} from "vendor/xbbcode/elements";
|
import {TagElement} from "vendor/xbbcode/elements";
|
||||||
import * as React from "react";
|
|
||||||
import {regexImage} from "tc-shared/text/bbcode/image";
|
import {regexImage} from "tc-shared/text/bbcode/image";
|
||||||
|
|
||||||
interface UrlKnifeUrl {
|
interface UrlKnifeUrl {
|
||||||
|
|
|
@ -18,10 +18,10 @@ import {formatMessage} from "../ui/frames/chat";
|
||||||
import {Registry} from "../events";
|
import {Registry} from "../events";
|
||||||
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
||||||
import {spawnFileTransferModal} from "../ui/modal/transfer/ModalFileTransfer";
|
import {spawnFileTransferModal} from "../ui/modal/transfer/ModalFileTransfer";
|
||||||
import {EventChannelData} from "../ui/frames/log/Definitions";
|
|
||||||
import {ErrorCode} from "../connection/ErrorCode";
|
import {ErrorCode} from "../connection/ErrorCode";
|
||||||
import {ClientIcon} from "svg-sprites/client-icons";
|
import {ClientIcon} from "svg-sprites/client-icons";
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
|
import {EventChannelData} from "tc-shared/connectionlog/Definitions";
|
||||||
|
|
||||||
export enum ChannelType {
|
export enum ChannelType {
|
||||||
PERMANENT,
|
PERMANENT,
|
||||||
|
@ -45,7 +45,16 @@ export enum ChannelSubscribeMode {
|
||||||
export enum ChannelConversationMode {
|
export enum ChannelConversationMode {
|
||||||
Public = 0,
|
Public = 0,
|
||||||
Private = 1,
|
Private = 1,
|
||||||
None = 2
|
None = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ChannelSidebarMode {
|
||||||
|
Conversation = 0,
|
||||||
|
Description = 1,
|
||||||
|
FileTransfer = 2,
|
||||||
|
|
||||||
|
/* Only used within client side */
|
||||||
|
Unknown = 0xFF
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChannelProperties {
|
export class ChannelProperties {
|
||||||
|
@ -79,8 +88,10 @@ export class ChannelProperties {
|
||||||
//Only after request
|
//Only after request
|
||||||
channel_description: string = "";
|
channel_description: string = "";
|
||||||
|
|
||||||
channel_conversation_mode: ChannelConversationMode = 0;
|
channel_conversation_mode: ChannelConversationMode = ChannelConversationMode.Public;
|
||||||
channel_conversation_history_length: number = -1;
|
channel_conversation_history_length: number = -1;
|
||||||
|
|
||||||
|
channel_sidebar_mode: ChannelSidebarMode = ChannelSidebarMode.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelEvents extends ChannelTreeEntryEvents {
|
export interface ChannelEvents extends ChannelTreeEntryEvents {
|
||||||
|
@ -99,7 +110,8 @@ export interface ChannelEvents extends ChannelTreeEntryEvents {
|
||||||
},
|
},
|
||||||
notify_collapsed_state_changed: {
|
notify_collapsed_state_changed: {
|
||||||
collapsed: boolean
|
collapsed: boolean
|
||||||
}
|
},
|
||||||
|
notify_description_changed: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ParsedChannelName {
|
export class ParsedChannelName {
|
||||||
|
@ -173,10 +185,9 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
private _destroyed = false;
|
private _destroyed = false;
|
||||||
|
|
||||||
private cachedPasswordHash: string;
|
private cachedPasswordHash: string;
|
||||||
private _cached_channel_description: string = undefined;
|
private channelDescriptionCached: boolean;
|
||||||
private _cached_channel_description_promise: Promise<string> = undefined;
|
private channelDescriptionCallback: ((success: boolean) => void)[];
|
||||||
private _cached_channel_description_promise_resolve: any = undefined;
|
private channelDescriptionPromise: Promise<string>;
|
||||||
private _cached_channel_description_promise_reject: any = undefined;
|
|
||||||
|
|
||||||
private collapsed: boolean;
|
private collapsed: boolean;
|
||||||
private subscribed: boolean;
|
private subscribed: boolean;
|
||||||
|
@ -212,18 +223,20 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
|
|
||||||
this.collapsed = this.channelTree.client.settings.server(Settings.FN_SERVER_CHANNEL_COLLAPSED(this.channelId));
|
this.collapsed = this.channelTree.client.settings.server(Settings.FN_SERVER_CHANNEL_COLLAPSED(this.channelId));
|
||||||
this.subscriptionMode = this.channelTree.client.settings.server(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this.channelId));
|
this.subscriptionMode = this.channelTree.client.settings.server(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this.channelId));
|
||||||
|
|
||||||
|
this.channelDescriptionCached = false;
|
||||||
|
this.channelDescriptionCallback = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this._destroyed = true;
|
this._destroyed = true;
|
||||||
|
|
||||||
|
this.channelDescriptionCallback.forEach(callback => callback(false));
|
||||||
|
this.channelDescriptionCallback = [];
|
||||||
|
|
||||||
this.client_list.forEach(e => this.unregisterClient(e, true));
|
this.client_list.forEach(e => this.unregisterClient(e, true));
|
||||||
this.client_list = [];
|
this.client_list = [];
|
||||||
|
|
||||||
this._cached_channel_description_promise = undefined;
|
|
||||||
this._cached_channel_description_promise_resolve = undefined;
|
|
||||||
this._cached_channel_description_promise_reject = undefined;
|
|
||||||
|
|
||||||
this.channel_previous = undefined;
|
this.channel_previous = undefined;
|
||||||
this.parent = undefined;
|
this.parent = undefined;
|
||||||
this.channel_next = undefined;
|
this.channel_next = undefined;
|
||||||
|
@ -248,18 +261,39 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
return this.parsed_channel_name.text;
|
return this.parsed_channel_name.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
getChannelDescription() : Promise<string> {
|
async getChannelDescription() : Promise<string> {
|
||||||
if(this._cached_channel_description) return new Promise<string>(resolve => resolve(this._cached_channel_description));
|
if(this.channelDescriptionPromise) {
|
||||||
if(this._cached_channel_description_promise) return this._cached_channel_description_promise;
|
return this.channelDescriptionPromise;
|
||||||
|
}
|
||||||
|
|
||||||
this.channelTree.client.serverConnection.send_command("channelgetdescription", {cid: this.channelId}).catch(error => {
|
const promise = this.doGetChannelDescription();
|
||||||
this._cached_channel_description_promise_reject(error);
|
this.channelDescriptionPromise = promise;
|
||||||
});
|
promise
|
||||||
|
.then(() => this.channelDescriptionPromise = undefined)
|
||||||
|
.catch(() => this.channelDescriptionPromise = undefined);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
return this._cached_channel_description_promise = new Promise<string>((resolve, reject) => {
|
private async doGetChannelDescription() {
|
||||||
this._cached_channel_description_promise_resolve = resolve;
|
if(!this.channelDescriptionCached) {
|
||||||
this._cached_channel_description_promise_reject = reject;
|
await this.channelTree.client.serverConnection.send_command("channelgetdescription", {
|
||||||
});
|
cid: this.channelId
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!this.channelDescriptionCached) {
|
||||||
|
/* since the channel description is a low command it will not be processed in sync */
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
this.channelDescriptionCallback.push(succeeded => {
|
||||||
|
if(succeeded) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(tr("failed to receive description"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.properties.channel_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerClient(client: ClientEntry) {
|
registerClient(client: ClientEntry) {
|
||||||
|
@ -411,7 +445,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.getSideBar().showChannelConversations();
|
this.channelTree.client.getSideBar().showChannel();
|
||||||
},
|
},
|
||||||
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)
|
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)
|
||||||
}, {
|
}, {
|
||||||
|
@ -575,29 +609,27 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
let key = variable.key;
|
let key = variable.key;
|
||||||
let value = variable.value;
|
let value = variable.value;
|
||||||
|
|
||||||
if(!JSON.map_field_to(this.properties, value, variable.key)) {
|
const hasUpdate = JSON.map_field_to(this.properties, value, variable.key);
|
||||||
/* no update */
|
|
||||||
continue;
|
if(key == "channel_description") {
|
||||||
|
this.channelDescriptionCached = true;
|
||||||
|
this.channelDescriptionCallback.forEach(callback => callback(true));
|
||||||
|
this.channelDescriptionCallback = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key == "channel_name") {
|
if(hasUpdate) {
|
||||||
this.parsed_channel_name = new ParsedChannelName(value, this.hasParent());
|
if(key == "channel_name") {
|
||||||
} else if(key == "channel_order") {
|
this.parsed_channel_name = new ParsedChannelName(value, this.hasParent());
|
||||||
let order = this.channelTree.findChannel(this.properties.channel_order);
|
} else if(key == "channel_order") {
|
||||||
this.channelTree.moveChannel(this, order, this.parent, false);
|
let order = this.channelTree.findChannel(this.properties.channel_order);
|
||||||
} else if(key === "channel_icon_id") {
|
this.channelTree.moveChannel(this, order, this.parent, false);
|
||||||
this.properties.channel_icon_id = variable.value as any >>> 0; /* unsigned 32 bit number! */
|
} else if(key === "channel_icon_id") {
|
||||||
} else if(key == "channel_description") {
|
this.properties.channel_icon_id = variable.value as any >>> 0; /* unsigned 32 bit number! */
|
||||||
this._cached_channel_description = undefined;
|
} else if(key === "channel_flag_conversation_private") {
|
||||||
if(this._cached_channel_description_promise_resolve)
|
/* "fix" for older TeaSpeak server versions (pre. 1.4.22) */
|
||||||
this._cached_channel_description_promise_resolve(value);
|
this.properties.channel_conversation_mode = value === "1" ? 0 : 1;
|
||||||
this._cached_channel_description_promise = undefined;
|
variables.push({ key: "channel_conversation_mode", value: this.properties.channel_conversation_mode + "" });
|
||||||
this._cached_channel_description_promise_resolve = undefined;
|
}
|
||||||
this._cached_channel_description_promise_reject = undefined;
|
|
||||||
} else if(key === "channel_flag_conversation_private") {
|
|
||||||
/* "fix" for older TeaSpeak server versions (pre. 1.4.22) */
|
|
||||||
this.properties.channel_conversation_mode = value === "1" ? 0 : 1;
|
|
||||||
variables.push({ key: "channel_conversation_mode", value: this.properties.channel_conversation_mode + "" });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* devel-block(log-channel-property-updates) */
|
/* devel-block(log-channel-property-updates) */
|
||||||
|
@ -791,4 +823,14 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
return subscribed ? ClientIcon.ChannelGreenSubscribed : ClientIcon.ChannelGreen;
|
return subscribed ? ClientIcon.ChannelGreenSubscribed : ClientIcon.ChannelGreen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDescriptionChanged() {
|
||||||
|
if(!this.channelDescriptionCached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.channelDescriptionCached = false;
|
||||||
|
this.properties.channel_description = undefined;
|
||||||
|
this.events.fire("notify_description_changed");
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -115,6 +115,12 @@ export class ChannelTree {
|
||||||
this.tagContainer = $.spawn("div").addClass("channel-tree-container");
|
this.tagContainer = $.spawn("div").addClass("channel-tree-container");
|
||||||
renderChannelTree(this, this.tagContainer[0], { popoutButton: true });
|
renderChannelTree(this, this.tagContainer[0], { popoutButton: true });
|
||||||
|
|
||||||
|
this.events.on("notify_channel_list_received", () => {
|
||||||
|
if(!this.selectedEntry) {
|
||||||
|
this.setSelectedEntry(this.client.getClient().currentChannel());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,13 +183,13 @@ export class ChannelTree {
|
||||||
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.getSideBar().showChannelConversations();
|
this.client.getSideBar().showChannel();
|
||||||
}
|
}
|
||||||
} 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 conversation = this.client.getChannelConversations().findOrCreateConversation(0);
|
const conversation = this.client.getChannelConversations().findOrCreateConversation(0);
|
||||||
this.client.getChannelConversations().setSelectedConversation(conversation);
|
this.client.getChannelConversations().setSelectedConversation(conversation);
|
||||||
this.client.getSideBar().showChannelConversations()
|
this.client.getSideBar().showChannel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -559,6 +565,24 @@ export class ChannelTree {
|
||||||
}, {width: 400, maxLength: 512}).open();
|
}, {width: 400, maxLength: 512}).open();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
client_menu.push({
|
||||||
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
|
icon_class: ClientIcon.ChangeNickname,
|
||||||
|
name: tr("Send private message"),
|
||||||
|
callback: () => {
|
||||||
|
createInputModal(tr("Send private message"), tr("Message:<br>"), text => !!text, result => {
|
||||||
|
if (typeof(result) === "string") {
|
||||||
|
for (const client of clients) {
|
||||||
|
this.client.serverConnection.send_command("sendtextmessage", {
|
||||||
|
target: client.clientId(),
|
||||||
|
msg: result,
|
||||||
|
targetmode: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {width: 400, maxLength: 1024 * 8}).open();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
client_menu.push({
|
client_menu.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
|
|
|
@ -22,7 +22,6 @@ import * as hex from "../crypto/hex";
|
||||||
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
||||||
import {spawnClientVolumeChange, spawnMusicBotVolumeChange} from "../ui/modal/ModalChangeVolumeNew";
|
import {spawnClientVolumeChange, spawnMusicBotVolumeChange} from "../ui/modal/ModalChangeVolumeNew";
|
||||||
import {spawnPermissionEditorModal} from "../ui/modal/permission/ModalPermissionEditor";
|
import {spawnPermissionEditorModal} from "../ui/modal/permission/ModalPermissionEditor";
|
||||||
import {EventClient, EventType} from "../ui/frames/log/Definitions";
|
|
||||||
import {W2GPluginCmdHandler} from "../video-viewer/W2GPlugin";
|
import {W2GPluginCmdHandler} from "../video-viewer/W2GPlugin";
|
||||||
import {global_client_actions} from "../events/GlobalEvents";
|
import {global_client_actions} from "../events/GlobalEvents";
|
||||||
import {ClientIcon} from "svg-sprites/client-icons";
|
import {ClientIcon} from "svg-sprites/client-icons";
|
||||||
|
@ -31,6 +30,7 @@ import {VoicePlayerEvents, VoicePlayerState} from "../voice/VoicePlayer";
|
||||||
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
|
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
|
||||||
import {VideoClient} from "tc-shared/connection/VideoConnection";
|
import {VideoClient} from "tc-shared/connection/VideoConnection";
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
|
import {EventClient} from "tc-shared/connectionlog/Definitions";
|
||||||
|
|
||||||
export enum ClientType {
|
export enum ClientType {
|
||||||
CLIENT_VOICE,
|
CLIENT_VOICE,
|
||||||
|
@ -573,7 +573,7 @@ export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
|
||||||
clid: this.clientId(),
|
clid: this.clientId(),
|
||||||
msg: result
|
msg: result
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.channelTree.client.log.log(EventType.CLIENT_POKE_SEND, {
|
this.channelTree.client.log.log("client.poke.send", {
|
||||||
target: this.log_data(),
|
target: this.log_data(),
|
||||||
message: result
|
message: result
|
||||||
});
|
});
|
||||||
|
@ -771,7 +771,7 @@ export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
|
||||||
if(variable.key == "client_nickname") {
|
if(variable.key == "client_nickname") {
|
||||||
if(variable.value !== old_value && typeof(old_value) === "string") {
|
if(variable.value !== old_value && typeof(old_value) === "string") {
|
||||||
if(!(this instanceof LocalClientEntry)) { /* own changes will be logged somewhere else */
|
if(!(this instanceof LocalClientEntry)) { /* own changes will be logged somewhere else */
|
||||||
this.channelTree.client.log.log(EventType.CLIENT_NICKNAME_CHANGED, {
|
this.channelTree.client.log.log("client.nickname.changed", {
|
||||||
client: this.log_data(),
|
client: this.log_data(),
|
||||||
new_name: variable.value,
|
new_name: variable.value,
|
||||||
old_name: old_value
|
old_name: old_value
|
||||||
|
@ -996,7 +996,7 @@ export class LocalClientEntry extends ClientEntry {
|
||||||
this.updateVariables({ key: "client_nickname", value: new_name }); /* change it locally */
|
this.updateVariables({ key: "client_nickname", value: new_name }); /* change it locally */
|
||||||
return this.handle.serverConnection.send_command("clientupdate", { client_nickname: new_name }).then(() => {
|
return this.handle.serverConnection.send_command("clientupdate", { client_nickname: new_name }).then(() => {
|
||||||
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, new_name);
|
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, new_name);
|
||||||
this.channelTree.client.log.log(EventType.CLIENT_NICKNAME_CHANGED_OWN, {
|
this.channelTree.client.log.log("client.nickname.changed.own", {
|
||||||
client: this.log_data(),
|
client: this.log_data(),
|
||||||
old_name: old_name,
|
old_name: old_name,
|
||||||
new_name: new_name,
|
new_name: new_name,
|
||||||
|
@ -1004,7 +1004,7 @@ export class LocalClientEntry extends ClientEntry {
|
||||||
return true;
|
return true;
|
||||||
}).catch((e: CommandResult) => {
|
}).catch((e: CommandResult) => {
|
||||||
this.updateVariables({ key: "client_nickname", value: old_name }); /* change it back */
|
this.updateVariables({ key: "client_nickname", value: old_name }); /* change it back */
|
||||||
this.channelTree.client.log.log(EventType.CLIENT_NICKNAME_CHANGE_FAILED, {
|
this.channelTree.client.log.log("client.nickname.change.failed", {
|
||||||
reason: e.extra_message
|
reason: e.extra_message
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -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.getSideBar().showChannelConversations();
|
this.channelTree.client.getSideBar().showChannel();
|
||||||
},
|
},
|
||||||
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)
|
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -9,6 +9,7 @@ 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";
|
import {LogCategory, logWarn} from "tc-shared/log";
|
||||||
|
import {ChannelBarController} from "tc-shared/ui/frames/side/ChannelBarController";
|
||||||
|
|
||||||
export class SideBarController {
|
export class SideBarController {
|
||||||
private readonly uiEvents: Registry<SideBarEvents>;
|
private readonly uiEvents: Registry<SideBarEvents>;
|
||||||
|
@ -18,8 +19,8 @@ export class SideBarController {
|
||||||
|
|
||||||
private header: SideHeaderController;
|
private header: SideHeaderController;
|
||||||
private clientInfo: ClientInfoController;
|
private clientInfo: ClientInfoController;
|
||||||
private channelConversations: ChannelConversationController;
|
|
||||||
private privateConversations: PrivateConversationController;
|
private privateConversations: PrivateConversationController;
|
||||||
|
private channelBar: ChannelBarController;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.listenerConnection = [];
|
this.listenerConnection = [];
|
||||||
|
@ -28,8 +29,8 @@ export class SideBarController {
|
||||||
this.uiEvents.on("query_content", () => this.sendContent());
|
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.channelBar = new ChannelBarController();
|
||||||
this.privateConversations = new PrivateConversationController();
|
this.privateConversations = new PrivateConversationController();
|
||||||
this.channelConversations = new ChannelConversationController();
|
|
||||||
this.clientInfo = new ClientInfoController();
|
this.clientInfo = new ClientInfoController();
|
||||||
this.header = new SideHeaderController();
|
this.header = new SideHeaderController();
|
||||||
}
|
}
|
||||||
|
@ -45,8 +46,8 @@ export class SideBarController {
|
||||||
this.currentConnection = connection;
|
this.currentConnection = connection;
|
||||||
this.header.setConnectionHandler(connection);
|
this.header.setConnectionHandler(connection);
|
||||||
this.clientInfo.setConnectionHandler(connection);
|
this.clientInfo.setConnectionHandler(connection);
|
||||||
this.channelConversations.setConnectionHandler(connection);
|
|
||||||
this.privateConversations.setConnectionHandler(connection);
|
this.privateConversations.setConnectionHandler(connection);
|
||||||
|
this.channelBar.setConnectionHandler(connection);
|
||||||
|
|
||||||
if(connection) {
|
if(connection) {
|
||||||
this.listenerConnection.push(connection.getSideBar().events.on("notify_content_type_changed", () => this.sendContent()));
|
this.listenerConnection.push(connection.getSideBar().events.on("notify_content_type_changed", () => this.sendContent()));
|
||||||
|
@ -59,14 +60,14 @@ export class SideBarController {
|
||||||
this.header?.destroy();
|
this.header?.destroy();
|
||||||
this.header = undefined;
|
this.header = undefined;
|
||||||
|
|
||||||
|
this.channelBar?.destroy();
|
||||||
|
this.channelBar = undefined;
|
||||||
|
|
||||||
this.clientInfo?.destroy();
|
this.clientInfo?.destroy();
|
||||||
this.clientInfo = undefined;
|
this.clientInfo = undefined;
|
||||||
|
|
||||||
this.privateConversations?.destroy();
|
this.privateConversations?.destroy();
|
||||||
this.privateConversations = undefined;
|
this.privateConversations = undefined;
|
||||||
|
|
||||||
this.channelConversations?.destroy();
|
|
||||||
this.channelConversations = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderInto(container: HTMLDivElement) {
|
renderInto(container: HTMLDivElement) {
|
||||||
|
@ -93,17 +94,16 @@ export class SideBarController {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "channel-chat":
|
case "channel":
|
||||||
if(!this.currentConnection) {
|
if(!this.currentConnection) {
|
||||||
logWarn(LogCategory.GENERAL, tr("Received channel chat content data request without an active connection."));
|
logWarn(LogCategory.GENERAL, tr("Received channel chat content data request without an active connection."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.uiEvents.fire_react("notify_content_data", {
|
this.uiEvents.fire_react("notify_content_data", {
|
||||||
content: "channel-chat",
|
content: "channel",
|
||||||
data: {
|
data: {
|
||||||
events: this.channelConversations["uiEvents"],
|
events: this.channelBar.uiEvents,
|
||||||
handlerId: this.currentConnection.handlerId
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
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 {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";
|
import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions";
|
||||||
|
import {ChannelBarUiEvents} from "tc-shared/ui/frames/side/ChannelBarDefinitions";
|
||||||
|
|
||||||
/* TODO: Somehow outsource the event registries to IPC? */
|
/* TODO: Somehow outsource the event registries to IPC? */
|
||||||
|
|
||||||
export type SideBarType = "none" | "channel-chat" | "private-chat" | "client-info" | "music-manage";
|
export type SideBarType = "none" | "channel" | "private-chat" | "client-info" | "music-manage";
|
||||||
export interface SideBarTypeData {
|
export interface SideBarTypeData {
|
||||||
"none": {},
|
"none": {},
|
||||||
"channel-chat": {
|
"channel": {
|
||||||
events: Registry<AbstractConversationUiEvents>,
|
events: Registry<ChannelBarUiEvents>
|
||||||
handlerId: string
|
|
||||||
},
|
},
|
||||||
"private-chat": {
|
"private-chat": {
|
||||||
events: Registry<PrivateConversationUIEvents>,
|
events: Registry<PrivateConversationUIEvents>,
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import {SideHeaderEvents, SideHeaderState} from "tc-shared/ui/frames/side/HeaderDefinitions";
|
import {SideHeaderEvents, SideHeaderState} from "tc-shared/ui/frames/side/HeaderDefinitions";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import React = require("react");
|
|
||||||
import {SideHeaderRenderer} from "tc-shared/ui/frames/side/HeaderRenderer";
|
import {SideHeaderRenderer} from "tc-shared/ui/frames/side/HeaderRenderer";
|
||||||
import {ConversationPanel} from "tc-shared/ui/frames/side/AbstractConversationRenderer";
|
|
||||||
import {SideBarEvents, SideBarType, SideBarTypeData} from "tc-shared/ui/frames/SideBarDefinitions";
|
import {SideBarEvents, SideBarType, SideBarTypeData} from "tc-shared/ui/frames/SideBarDefinitions";
|
||||||
import {useContext, useState} from "react";
|
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";
|
||||||
|
import {ChannelBarRenderer} from "tc-shared/ui/frames/side/ChannelBarRenderer";
|
||||||
|
import {LogCategory, logWarn} from "tc-shared/log";
|
||||||
|
import React = require("react");
|
||||||
|
|
||||||
const cssStyle = require("./SideBarRenderer.scss");
|
const cssStyle = require("./SideBarRenderer.scss");
|
||||||
|
|
||||||
|
@ -23,17 +24,14 @@ function useContentData<T extends SideBarType>(type: T) : SideBarTypeData[T] {
|
||||||
return contentData;
|
return contentData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentRendererChannelConversation = () => {
|
const ContentRendererChannel = () => {
|
||||||
const contentData = useContentData("channel-chat");
|
const contentData = useContentData("channel");
|
||||||
if(!contentData) { return null; }
|
if(!contentData) { return null; }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConversationPanel
|
<ChannelBarRenderer
|
||||||
key={"channel-chat"}
|
key={"channel"}
|
||||||
events={contentData.events}
|
events={contentData.events}
|
||||||
handlerId={contentData.handlerId}
|
|
||||||
messagesDeletable={true}
|
|
||||||
noFirstMessageOverlay={false}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -63,8 +61,8 @@ const ContentRendererClientInfo = () => {
|
||||||
|
|
||||||
const SideBarFrame = (props: { type: SideBarType }) => {
|
const SideBarFrame = (props: { type: SideBarType }) => {
|
||||||
switch (props.type) {
|
switch (props.type) {
|
||||||
case "channel-chat":
|
case "channel":
|
||||||
return <ContentRendererChannelConversation key={props.type} />;
|
return <ContentRendererChannel key={props.type} />;
|
||||||
|
|
||||||
case "private-chat":
|
case "private-chat":
|
||||||
return <ContentRendererPrivateConversation key={props.type} />;
|
return <ContentRendererPrivateConversation key={props.type} />;
|
||||||
|
@ -88,7 +86,7 @@ const SideBarHeader = (props: { type: SideBarType, eventsHeader: Registry<SideHe
|
||||||
headerState = { state: "none" };
|
headerState = { state: "none" };
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "channel-chat":
|
case "channel":
|
||||||
headerState = { state: "conversation", mode: "channel" };
|
headerState = { state: "conversation", mode: "channel" };
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -103,6 +101,11 @@ const SideBarHeader = (props: { type: SideBarType, eventsHeader: Registry<SideHe
|
||||||
case "music-manage":
|
case "music-manage":
|
||||||
headerState = { state: "music-bot" };
|
headerState = { state: "music-bot" };
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
logWarn(LogCategory.GENERAL, tr("Side bar header with invalid type: %s"), props.type);
|
||||||
|
headerState = { state: "none" };
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SideHeaderRenderer state={headerState} events={props.eventsHeader} />;
|
return <SideHeaderRenderer state={headerState} events={props.eventsHeader} />;
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
import {ServerEventLogUiEvents} from "tc-shared/ui/frames/log/Definitions";
|
||||||
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
|
|
||||||
|
export class ServerEventLogController {
|
||||||
|
readonly events: Registry<ServerEventLogUiEvents>;
|
||||||
|
|
||||||
|
private currentConnection: ConnectionHandler;
|
||||||
|
private listenerConnection: (() => void)[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.events = new Registry<ServerEventLogUiEvents>();
|
||||||
|
|
||||||
|
this.events.on("query_handler_id", () => this.events.fire_react("notify_handler_id", { handlerId: this.currentConnection?.handlerId }));
|
||||||
|
this.events.on("query_log", () => this.sendLogs());
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.listenerConnection?.forEach(callback => callback());
|
||||||
|
this.listenerConnection = [];
|
||||||
|
|
||||||
|
this.events.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
setConnectionHandler(handler: ConnectionHandler) {
|
||||||
|
if(this.currentConnection === handler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listenerConnection?.forEach(callback => callback());
|
||||||
|
this.listenerConnection = [];
|
||||||
|
|
||||||
|
this.currentConnection = handler;
|
||||||
|
|
||||||
|
if(this.currentConnection) {
|
||||||
|
this.listenerConnection.push(this.currentConnection.log.events.on("notify_log_add", event => {
|
||||||
|
this.events.fire_react("notify_log_add", { event: event.event });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events.fire_react("notify_handler_id", { handlerId: handler?.handlerId });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private sendLogs() {
|
||||||
|
const logs = this.currentConnection?.log.getHistory() || [];
|
||||||
|
this.events.fire_react("notify_log", { events: logs });
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,348 +1,16 @@
|
||||||
import {PermissionInfo} from "../../../permission/PermissionManager";
|
import {LogMessage} from "tc-shared/connectionlog/Definitions";
|
||||||
import {ViewReasonId} from "../../../ConnectionHandler";
|
|
||||||
import * as React from "react";
|
|
||||||
import {ServerEventLog} from "../../../ui/frames/log/ServerEventLog";
|
|
||||||
|
|
||||||
/* FIXME: Remove this! */
|
export interface ServerEventLogUiEvents {
|
||||||
export enum EventType {
|
query_handler_id: {},
|
||||||
CONNECTION_BEGIN = "connection.begin",
|
query_log: {},
|
||||||
CONNECTION_HOSTNAME_RESOLVE = "connection.hostname.resolve",
|
|
||||||
CONNECTION_HOSTNAME_RESOLVE_ERROR = "connection.hostname.resolve.error",
|
|
||||||
CONNECTION_HOSTNAME_RESOLVED = "connection.hostname.resolved",
|
|
||||||
CONNECTION_LOGIN = "connection.login",
|
|
||||||
CONNECTION_CONNECTED = "connection.connected",
|
|
||||||
CONNECTION_FAILED = "connection.failed",
|
|
||||||
|
|
||||||
DISCONNECTED = "disconnected",
|
notify_log_add: {
|
||||||
|
|
||||||
CONNECTION_VOICE_CONNECT = "connection.voice.connect",
|
|
||||||
CONNECTION_VOICE_CONNECT_FAILED = "connection.voice.connect.failed",
|
|
||||||
CONNECTION_VOICE_CONNECT_SUCCEEDED = "connection.voice.connect.succeeded",
|
|
||||||
CONNECTION_VOICE_DROPPED = "connection.voice.dropped",
|
|
||||||
|
|
||||||
CONNECTION_COMMAND_ERROR = "connection.command.error",
|
|
||||||
|
|
||||||
GLOBAL_MESSAGE = "global.message",
|
|
||||||
|
|
||||||
SERVER_WELCOME_MESSAGE = "server.welcome.message",
|
|
||||||
SERVER_HOST_MESSAGE = "server.host.message",
|
|
||||||
SERVER_HOST_MESSAGE_DISCONNECT = "server.host.message.disconnect",
|
|
||||||
|
|
||||||
SERVER_CLOSED = "server.closed",
|
|
||||||
SERVER_BANNED = "server.banned",
|
|
||||||
SERVER_REQUIRES_PASSWORD = "server.requires.password",
|
|
||||||
|
|
||||||
CLIENT_VIEW_ENTER = "client.view.enter",
|
|
||||||
CLIENT_VIEW_LEAVE = "client.view.leave",
|
|
||||||
CLIENT_VIEW_MOVE = "client.view.move",
|
|
||||||
|
|
||||||
CLIENT_VIEW_ENTER_OWN_CHANNEL = "client.view.enter.own.channel",
|
|
||||||
CLIENT_VIEW_LEAVE_OWN_CHANNEL = "client.view.leave.own.channel",
|
|
||||||
CLIENT_VIEW_MOVE_OWN_CHANNEL = "client.view.move.own.channel",
|
|
||||||
|
|
||||||
CLIENT_VIEW_MOVE_OWN = "client.view.move.own",
|
|
||||||
|
|
||||||
CLIENT_NICKNAME_CHANGED = "client.nickname.changed",
|
|
||||||
CLIENT_NICKNAME_CHANGED_OWN = "client.nickname.changed.own",
|
|
||||||
CLIENT_NICKNAME_CHANGE_FAILED = "client.nickname.change.failed",
|
|
||||||
|
|
||||||
CLIENT_SERVER_GROUP_ADD = "client.server.group.add",
|
|
||||||
CLIENT_SERVER_GROUP_REMOVE = "client.server.group.remove",
|
|
||||||
CLIENT_CHANNEL_GROUP_CHANGE = "client.channel.group.change",
|
|
||||||
|
|
||||||
PRIVATE_MESSAGE_RECEIVED = "private.message.received",
|
|
||||||
PRIVATE_MESSAGE_SEND = "private.message.send",
|
|
||||||
|
|
||||||
CHANNEL_CREATE = "channel.create",
|
|
||||||
CHANNEL_DELETE = "channel.delete",
|
|
||||||
|
|
||||||
ERROR_CUSTOM = "error.custom",
|
|
||||||
ERROR_PERMISSION = "error.permission",
|
|
||||||
|
|
||||||
CLIENT_POKE_RECEIVED = "client.poke.received",
|
|
||||||
CLIENT_POKE_SEND = "client.poke.send",
|
|
||||||
|
|
||||||
RECONNECT_SCHEDULED = "reconnect.scheduled",
|
|
||||||
RECONNECT_EXECUTE = "reconnect.execute",
|
|
||||||
RECONNECT_CANCELED = "reconnect.canceled",
|
|
||||||
|
|
||||||
WEBRTC_FATAL_ERROR = "webrtc.fatal.error"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventClient = {
|
|
||||||
client_unique_id: string;
|
|
||||||
client_name: string;
|
|
||||||
client_id: number;
|
|
||||||
}
|
|
||||||
export type EventChannelData = {
|
|
||||||
channel_id: number;
|
|
||||||
channel_name: string;
|
|
||||||
}
|
|
||||||
export type EventServerData = {
|
|
||||||
server_name: string;
|
|
||||||
server_unique_id: string;
|
|
||||||
}
|
|
||||||
export type EventServerAddress = {
|
|
||||||
server_hostname: string;
|
|
||||||
server_port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace event {
|
|
||||||
export type EventGlobalMessage = {
|
|
||||||
isOwnMessage: boolean;
|
|
||||||
sender: EventClient;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
export type EventConnectBegin = {
|
|
||||||
address: EventServerAddress;
|
|
||||||
client_nickname: string;
|
|
||||||
}
|
|
||||||
export type EventErrorCustom = {
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventReconnectScheduled = {
|
|
||||||
timeout: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventReconnectCanceled = { }
|
|
||||||
export type EventReconnectExecute = { }
|
|
||||||
|
|
||||||
export type EventErrorPermission = {
|
|
||||||
permission: PermissionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventWelcomeMessage = {
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventHostMessageDisconnect = {
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventClientMove = {
|
|
||||||
channel_from?: EventChannelData;
|
|
||||||
channel_from_own: boolean;
|
|
||||||
|
|
||||||
channel_to?: EventChannelData;
|
|
||||||
channel_to_own: boolean;
|
|
||||||
|
|
||||||
client: EventClient;
|
|
||||||
client_own: boolean;
|
|
||||||
|
|
||||||
invoker?: EventClient;
|
|
||||||
|
|
||||||
message?: string;
|
|
||||||
reason: ViewReasonId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventClientEnter = {
|
|
||||||
channel_from?: EventChannelData;
|
|
||||||
channel_to?: EventChannelData;
|
|
||||||
|
|
||||||
client: EventClient;
|
|
||||||
invoker?: EventClient;
|
|
||||||
|
|
||||||
message?: string;
|
|
||||||
|
|
||||||
reason: ViewReasonId;
|
|
||||||
ban_time?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventClientLeave = {
|
|
||||||
channel_from?: EventChannelData;
|
|
||||||
channel_to?: EventChannelData;
|
|
||||||
|
|
||||||
client: EventClient;
|
|
||||||
invoker?: EventClient;
|
|
||||||
|
|
||||||
message?: string;
|
|
||||||
|
|
||||||
reason: ViewReasonId;
|
|
||||||
ban_time?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventChannelCreate = {
|
|
||||||
creator: EventClient,
|
|
||||||
channel: EventChannelData,
|
|
||||||
ownAction: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventChannelToggle = {
|
|
||||||
channel: EventChannelData
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventChannelDelete = {
|
|
||||||
deleter: EventClient,
|
|
||||||
channel: EventChannelData,
|
|
||||||
ownAction: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventConnectionConnected = {
|
|
||||||
serverName: string,
|
|
||||||
serverAddress: EventServerAddress,
|
|
||||||
own_client: EventClient;
|
|
||||||
}
|
|
||||||
export type EventConnectionFailed = {
|
|
||||||
serverAddress: EventServerAddress
|
|
||||||
}
|
|
||||||
export type EventConnectionLogin = {}
|
|
||||||
export type EventConnectionHostnameResolve = {};
|
|
||||||
export type EventConnectionHostnameResolved = {
|
|
||||||
address: EventServerAddress;
|
|
||||||
}
|
|
||||||
export type EventConnectionHostnameResolveError = {
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventConnectionVoiceConnectFailed = {
|
|
||||||
reason: string;
|
|
||||||
reconnect_delay: number; /* if less or equal to 0 reconnect is prohibited */
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventConnectionVoiceConnectSucceeded = {}
|
|
||||||
|
|
||||||
export type EventConnectionVoiceConnect = {
|
|
||||||
attemptCount: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventConnectionVoiceDropped = {}
|
|
||||||
|
|
||||||
export type EventConnectionCommandError = {
|
|
||||||
error: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventClientNicknameChanged = {
|
|
||||||
client: EventClient;
|
|
||||||
|
|
||||||
old_name: string;
|
|
||||||
new_name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventClientNicknameChangeFailed = {
|
|
||||||
reason: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventServerClosed = {
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventServerRequiresPassword = {}
|
|
||||||
|
|
||||||
export type EventServerBanned = {
|
|
||||||
message: string;
|
|
||||||
time: number;
|
|
||||||
|
|
||||||
invoker: EventClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventClientPokeReceived = {
|
|
||||||
sender: EventClient,
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventClientPokeSend = {
|
|
||||||
target: EventClient,
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventPrivateMessageSend = {
|
|
||||||
target: EventClient,
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventPrivateMessageReceived = {
|
|
||||||
sender: EventClient,
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventWebrtcFatalError = {
|
|
||||||
message: string,
|
|
||||||
retryTimeout: number | 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LogMessage = {
|
|
||||||
type: EventType;
|
|
||||||
uniqueId: string;
|
|
||||||
timestamp: number;
|
|
||||||
data: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TypeInfo {
|
|
||||||
"connection.begin" : event.EventConnectBegin;
|
|
||||||
"global.message": event.EventGlobalMessage;
|
|
||||||
|
|
||||||
"error.custom": event.EventErrorCustom;
|
|
||||||
"error.permission": event.EventErrorPermission;
|
|
||||||
|
|
||||||
"connection.hostname.resolved": event.EventConnectionHostnameResolved;
|
|
||||||
"connection.hostname.resolve": event.EventConnectionHostnameResolve;
|
|
||||||
"connection.hostname.resolve.error": event.EventConnectionHostnameResolveError;
|
|
||||||
"connection.failed": event.EventConnectionFailed;
|
|
||||||
"connection.login": event.EventConnectionLogin;
|
|
||||||
"connection.connected": event.EventConnectionConnected;
|
|
||||||
"connection.voice.dropped": event.EventConnectionVoiceDropped;
|
|
||||||
"connection.voice.connect": event.EventConnectionVoiceConnect;
|
|
||||||
"connection.voice.connect.failed": event.EventConnectionVoiceConnectFailed;
|
|
||||||
"connection.voice.connect.succeeded": event.EventConnectionVoiceConnectSucceeded;
|
|
||||||
"connection.command.error": event.EventConnectionCommandError;
|
|
||||||
|
|
||||||
"reconnect.scheduled": event.EventReconnectScheduled;
|
|
||||||
"reconnect.canceled": event.EventReconnectCanceled;
|
|
||||||
"reconnect.execute": event.EventReconnectExecute;
|
|
||||||
|
|
||||||
"server.welcome.message": event.EventWelcomeMessage;
|
|
||||||
"server.host.message": event.EventWelcomeMessage;
|
|
||||||
"server.host.message.disconnect": event.EventHostMessageDisconnect;
|
|
||||||
|
|
||||||
"server.closed": event.EventServerClosed;
|
|
||||||
"server.requires.password": event.EventServerRequiresPassword;
|
|
||||||
"server.banned": event.EventServerBanned;
|
|
||||||
|
|
||||||
"client.view.enter": event.EventClientEnter;
|
|
||||||
"client.view.move": event.EventClientMove;
|
|
||||||
"client.view.leave": event.EventClientLeave;
|
|
||||||
|
|
||||||
"client.view.enter.own.channel": event.EventClientEnter;
|
|
||||||
"client.view.move.own.channel": event.EventClientMove;
|
|
||||||
"client.view.leave.own.channel": event.EventClientLeave;
|
|
||||||
|
|
||||||
"client.view.move.own": event.EventClientMove;
|
|
||||||
|
|
||||||
"client.nickname.change.failed": event.EventClientNicknameChangeFailed,
|
|
||||||
"client.nickname.changed": event.EventClientNicknameChanged,
|
|
||||||
"client.nickname.changed.own": event.EventClientNicknameChanged,
|
|
||||||
|
|
||||||
"channel.create": event.EventChannelCreate,
|
|
||||||
"channel.show": event.EventChannelToggle,
|
|
||||||
"channel.hide": event.EventChannelToggle,
|
|
||||||
"channel.delete": event.EventChannelDelete,
|
|
||||||
|
|
||||||
"client.poke.received": event.EventClientPokeReceived,
|
|
||||||
"client.poke.send": event.EventClientPokeSend,
|
|
||||||
|
|
||||||
"private.message.received": event.EventPrivateMessageReceived,
|
|
||||||
"private.message.send": event.EventPrivateMessageSend,
|
|
||||||
|
|
||||||
"webrtc.fatal.error": event.EventWebrtcFatalError
|
|
||||||
|
|
||||||
"disconnected": any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EventDispatcher<EventType extends keyof TypeInfo> {
|
|
||||||
log(data: TypeInfo[EventType], logger: ServerEventLog) : React.ReactNode;
|
|
||||||
notify(data: TypeInfo[EventType], logger: ServerEventLog);
|
|
||||||
sound(data: TypeInfo[EventType], logger: ServerEventLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerLogUIEvents {
|
|
||||||
"query_log": {},
|
|
||||||
"notify_log": {
|
|
||||||
log: LogMessage[]
|
|
||||||
},
|
|
||||||
"notify_log_add": {
|
|
||||||
event: LogMessage
|
event: LogMessage
|
||||||
},
|
},
|
||||||
"notify_show": {}
|
notify_log: {
|
||||||
|
events: LogMessage[]
|
||||||
|
},
|
||||||
|
notify_handler_id: {
|
||||||
|
handlerId: string | undefined
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -17,7 +17,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
min-height: 2em;
|
min-height: 1em;
|
||||||
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import {LogMessage, ServerLogUIEvents} from "tc-shared/ui/frames/log/Definitions";
|
|
||||||
import {VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
import {VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {useEffect, useRef, useState} from "react";
|
import {useContext, useEffect, useRef, useState} from "react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {findLogDispatcher} from "tc-shared/ui/frames/log/DispatcherLog";
|
import {findLogEventRenderer} from "./RendererEvent";
|
||||||
|
import {LogMessage} from "tc-shared/connectionlog/Definitions";
|
||||||
|
import {ServerEventLogUiEvents} from "tc-shared/ui/frames/log/Definitions";
|
||||||
|
import {useDependentState} from "tc-shared/ui/react-elements/Helper";
|
||||||
|
|
||||||
const cssStyle = require("./Renderer.scss");
|
const cssStyle = require("./Renderer.scss");
|
||||||
|
|
||||||
|
const HandlerIdContext = React.createContext<string>(undefined);
|
||||||
|
const EventsContext = React.createContext<Registry<ServerEventLogUiEvents>>(undefined);
|
||||||
|
|
||||||
const LogFallbackDispatcher = (_unused, __unused, eventType) => (
|
const LogFallbackDispatcher = (_unused, __unused, eventType) => (
|
||||||
<div className={cssStyle.errorMessage}>
|
<div className={cssStyle.errorMessage}>
|
||||||
<VariadicTranslatable text={"Missing log entry builder for {0}"}>
|
<VariadicTranslatable text={"Missing log entry builder for {0}"}>
|
||||||
|
@ -15,12 +20,14 @@ const LogFallbackDispatcher = (_unused, __unused, eventType) => (
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const LogEntryRenderer = React.memo((props: { entry: LogMessage, handlerId: string }) => {
|
const LogEntryRenderer = React.memo((props: { entry: LogMessage }) => {
|
||||||
const dispatcher = findLogDispatcher(props.entry.type as any) || LogFallbackDispatcher;
|
const handlerId = useContext(HandlerIdContext);
|
||||||
const rendered = dispatcher(props.entry.data, props.handlerId, props.entry.type);
|
const dispatcher = findLogEventRenderer(props.entry.type as any) || LogFallbackDispatcher;
|
||||||
|
const rendered = dispatcher(props.entry.data, handlerId, props.entry.type);
|
||||||
|
|
||||||
if(!rendered) /* hide message */
|
if(!rendered) { /* hide message */
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const date = new Date(props.entry.timestamp);
|
const date = new Date(props.entry.timestamp);
|
||||||
return (
|
return (
|
||||||
|
@ -35,25 +42,27 @@ const LogEntryRenderer = React.memo((props: { entry: LogMessage, handlerId: stri
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ServerLogRenderer = (props: { events: Registry<ServerLogUIEvents>, handlerId: string }) => {
|
const ServerLogRenderer = () => {
|
||||||
const [ logs, setLogs ] = useState<LogMessage[] | "loading">(() => {
|
const handlerId = useContext(HandlerIdContext);
|
||||||
props.events.fire_react("query_log");
|
const events = useContext(EventsContext);
|
||||||
|
const [ logs, setLogs ] = useDependentState<LogMessage[] | "loading">(() => {
|
||||||
|
events.fire_react("query_log");
|
||||||
return "loading";
|
return "loading";
|
||||||
});
|
}, [ handlerId ]);
|
||||||
|
|
||||||
const [ revision, setRevision ] = useState(0);
|
const [ revision, setRevision ] = useState(0);
|
||||||
|
|
||||||
const refContainer = useRef<HTMLDivElement>();
|
const refContainer = useRef<HTMLDivElement>();
|
||||||
const scrollOffset = useRef<number | "bottom">("bottom");
|
const scrollOffset = useRef<number | "bottom">("bottom");
|
||||||
|
|
||||||
props.events.reactUse("notify_log", event => {
|
events.reactUse("notify_log", event => {
|
||||||
const logs = event.log.slice(0);
|
const logs = event.events.slice(0);
|
||||||
logs.splice(0, Math.max(0, logs.length - 100));
|
logs.splice(0, Math.max(0, logs.length - 100));
|
||||||
logs.sort((a, b) => a.timestamp - b.timestamp);
|
logs.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
setLogs(logs);
|
setLogs(logs);
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("notify_log_add", event => {
|
events.reactUse("notify_log_add", event => {
|
||||||
if(logs === "loading") {
|
if(logs === "loading") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -72,10 +81,6 @@ export const ServerLogRenderer = (props: { events: Registry<ServerLogUIEvents>,
|
||||||
refContainer.current.scrollTop = scrollOffset.current === "bottom" ? refContainer.current.scrollHeight : scrollOffset.current;
|
refContainer.current.scrollTop = scrollOffset.current === "bottom" ? refContainer.current.scrollHeight : scrollOffset.current;
|
||||||
};
|
};
|
||||||
|
|
||||||
props.events.reactUse("notify_show", () => {
|
|
||||||
requestAnimationFrame(fixScroll);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = requestAnimationFrame(fixScroll);
|
const id = requestAnimationFrame(fixScroll);
|
||||||
return () => cancelAnimationFrame(id);
|
return () => cancelAnimationFrame(id);
|
||||||
|
@ -91,7 +96,23 @@ export const ServerLogRenderer = (props: { events: Registry<ServerLogUIEvents>,
|
||||||
|
|
||||||
scrollOffset.current = shouldFollow ? "bottom" : top;
|
scrollOffset.current = shouldFollow ? "bottom" : top;
|
||||||
}}>
|
}}>
|
||||||
{logs === "loading" ? null : logs.map(e => <LogEntryRenderer key={e.uniqueId} entry={e} handlerId={props.handlerId} />)}
|
{logs === "loading" ? null : logs.map(e => <LogEntryRenderer key={e.uniqueId} entry={e} />)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ServerLogFrame = (props: { events: Registry<ServerEventLogUiEvents> }) => {
|
||||||
|
const [ handlerId, setHandlerId ] = useState<string>(() => {
|
||||||
|
props.events.fire("query_handler_id");
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
props.events.reactUse("notify_handler_id", event => setHandlerId(event.handlerId));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EventsContext.Provider value={props.events}>
|
||||||
|
<HandlerIdContext.Provider value={handlerId}>
|
||||||
|
<ServerLogRenderer />
|
||||||
|
</HandlerIdContext.Provider>
|
||||||
|
</EventsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {ViewReasonId} from "tc-shared/ConnectionHandler";
|
import {ViewReasonId} from "tc-shared/ConnectionHandler";
|
||||||
import {EventChannelData, EventClient, EventType, TypeInfo} from "tc-shared/ui/frames/log/Definitions";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
||||||
import {formatDate} from "tc-shared/MessageFormatter";
|
import {formatDate} from "tc-shared/MessageFormatter";
|
||||||
|
@ -8,22 +7,23 @@ import {format_time} from "tc-shared/ui/frames/chat";
|
||||||
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
import {XBBCodeRenderer} from "vendor/xbbcode/react";
|
import {XBBCodeRenderer} from "vendor/xbbcode/react";
|
||||||
import {ChannelTag, ClientTag} from "tc-shared/ui/tree/EntryTags";
|
import {ChannelTag, ClientTag} from "tc-shared/ui/tree/EntryTags";
|
||||||
|
import {EventChannelData, EventClient, EventType, TypeInfo} from "tc-shared/connectionlog/Definitions";
|
||||||
|
|
||||||
const cssStyle = require("./DispatcherLog.scss");
|
const cssStyle = require("./DispatcherLog.scss");
|
||||||
const cssStyleRenderer = require("./Renderer.scss");
|
const cssStyleRenderer = require("./Renderer.scss");
|
||||||
|
|
||||||
export type DispatcherLog<T extends keyof TypeInfo> = (data: TypeInfo[T], handlerId: string, eventType: T) => React.ReactNode;
|
export type RendererEvent<T extends keyof TypeInfo> = (data: TypeInfo[T], handlerId: string, eventType: T) => React.ReactNode;
|
||||||
|
|
||||||
const dispatchers: {[key: string]: DispatcherLog<any>} = { };
|
const dispatchers: {[T in keyof TypeInfo]?: RendererEvent<T>} = { };
|
||||||
function registerDispatcher<T extends keyof TypeInfo>(key: T, builder: DispatcherLog<T>) {
|
function registerRenderer<T extends keyof TypeInfo>(key: T, builder: RendererEvent<T>) {
|
||||||
dispatchers[key] = builder;
|
dispatchers[key] = builder as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findLogDispatcher<T extends keyof TypeInfo>(type: T) : DispatcherLog<T> {
|
export function findLogEventRenderer<T extends keyof TypeInfo>(type: T) : RendererEvent<T> {
|
||||||
return dispatchers[type];
|
return dispatchers[type] as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRegisteredLogDispatchers() : TypeInfo[] {
|
export function getRegisteredLogEventRenderer() : TypeInfo[] {
|
||||||
return Object.keys(dispatchers) as any;
|
return Object.keys(dispatchers) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,66 +46,66 @@ const ChannelRenderer = (props: { channel: EventChannelData, handlerId: string,
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
registerDispatcher(EventType.ERROR_CUSTOM, data => <div className={cssStyleRenderer.errorMessage}>{data.message}</div>);
|
registerRenderer(EventType.ERROR_CUSTOM, data => <div className={cssStyleRenderer.errorMessage}>{data.message}</div>);
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_BEGIN, data => (
|
registerRenderer(EventType.CONNECTION_BEGIN, data => (
|
||||||
<VariadicTranslatable text={"Connecting to {0}{1}"}>
|
<VariadicTranslatable text={"Connecting to {0}{1}"}>
|
||||||
<>{data.address.server_hostname}</>
|
<>{data.address.server_hostname}</>
|
||||||
<>{data.address.server_port == 9987 ? "" : (":" + data.address.server_port)}</>
|
<>{data.address.server_port == 9987 ? "" : (":" + data.address.server_port)}</>
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_HOSTNAME_RESOLVE, () => (
|
registerRenderer(EventType.CONNECTION_HOSTNAME_RESOLVE, () => (
|
||||||
<Translatable>Resolving hostname</Translatable>
|
<Translatable>Resolving hostname</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_HOSTNAME_RESOLVED, data => (
|
registerRenderer(EventType.CONNECTION_HOSTNAME_RESOLVED, data => (
|
||||||
<VariadicTranslatable text={"Hostname resolved successfully to {0}:{1}"}>
|
<VariadicTranslatable text={"Hostname resolved successfully to {0}:{1}"}>
|
||||||
<>{data.address.server_hostname}</>
|
<>{data.address.server_hostname}</>
|
||||||
<>{data.address.server_port}</>
|
<>{data.address.server_port}</>
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_HOSTNAME_RESOLVE_ERROR, data => (
|
registerRenderer(EventType.CONNECTION_HOSTNAME_RESOLVE_ERROR, data => (
|
||||||
<VariadicTranslatable text={"Failed to resolve hostname. Connecting to given hostname. Error: {0}"}>
|
<VariadicTranslatable text={"Failed to resolve hostname. Connecting to given hostname. Error: {0}"}>
|
||||||
<>{data.message}</>
|
<>{data.message}</>
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_LOGIN, () => (
|
registerRenderer(EventType.CONNECTION_LOGIN, () => (
|
||||||
<Translatable>Logging in...</Translatable>
|
<Translatable>Logging in...</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_FAILED, () => (
|
registerRenderer(EventType.CONNECTION_FAILED, () => (
|
||||||
<Translatable>Connect failed.</Translatable>
|
<Translatable>Connect failed.</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_CONNECTED, (data,handlerId) => (
|
registerRenderer(EventType.CONNECTION_CONNECTED, (data,handlerId) => (
|
||||||
<VariadicTranslatable text={"Connected as {0}"}>
|
<VariadicTranslatable text={"Connected as {0}"}>
|
||||||
<ClientRenderer client={data.own_client} handlerId={handlerId} />
|
<ClientRenderer client={data.own_client} handlerId={handlerId} />
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_VOICE_CONNECT, () => (
|
registerRenderer(EventType.CONNECTION_VOICE_CONNECT, () => (
|
||||||
<Translatable>Connecting voice bridge.</Translatable>
|
<Translatable>Connecting voice bridge.</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_VOICE_CONNECT_SUCCEEDED, () => (
|
registerRenderer(EventType.CONNECTION_VOICE_CONNECT_SUCCEEDED, () => (
|
||||||
<Translatable>Voice bridge successfully connected.</Translatable>
|
<Translatable>Voice bridge successfully connected.</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_VOICE_CONNECT_FAILED, (data) => (
|
registerRenderer(EventType.CONNECTION_VOICE_CONNECT_FAILED, (data) => (
|
||||||
<VariadicTranslatable text={"Failed to setup voice bridge: {0}. Allow reconnect: {1}"}>
|
<VariadicTranslatable text={"Failed to setup voice bridge: {0}. Allow reconnect: {1}"}>
|
||||||
<>{data.reason}</>
|
<>{data.reason}</>
|
||||||
{data.reconnect_delay > 0 ? <Translatable>Yes</Translatable> : <Translatable>No</Translatable>}
|
{data.reconnect_delay > 0 ? <Translatable>Yes</Translatable> : <Translatable>No</Translatable>}
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_VOICE_DROPPED, () => (
|
registerRenderer(EventType.CONNECTION_VOICE_DROPPED, () => (
|
||||||
<Translatable>Voice bridge has been dropped. Trying to reconnect.</Translatable>
|
<Translatable>Voice bridge has been dropped. Trying to reconnect.</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.ERROR_PERMISSION, data => (
|
registerRenderer(EventType.ERROR_PERMISSION, data => (
|
||||||
<div className={cssStyleRenderer.errorMessage}>
|
<div className={cssStyleRenderer.errorMessage}>
|
||||||
<VariadicTranslatable text={"Insufficient client permissions. Failed on permission {0}"}>
|
<VariadicTranslatable text={"Insufficient client permissions. Failed on permission {0}"}>
|
||||||
<>{data.permission ? data.permission.name : <Translatable>unknown</Translatable>}</>
|
<>{data.permission ? data.permission.name : <Translatable>unknown</Translatable>}</>
|
||||||
|
@ -113,7 +113,7 @@ registerDispatcher(EventType.ERROR_PERMISSION, data => (
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_VIEW_ENTER, (data, handlerId) => {
|
registerRenderer(EventType.CLIENT_VIEW_ENTER, (data, handlerId) => {
|
||||||
switch (data.reason) {
|
switch (data.reason) {
|
||||||
case ViewReasonId.VREASON_USER_ACTION:
|
case ViewReasonId.VREASON_USER_ACTION:
|
||||||
if(data.channel_from) {
|
if(data.channel_from) {
|
||||||
|
@ -189,7 +189,7 @@ registerDispatcher(EventType.CLIENT_VIEW_ENTER, (data, handlerId) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_VIEW_ENTER_OWN_CHANNEL, (data, handlerId) => {
|
registerRenderer(EventType.CLIENT_VIEW_ENTER_OWN_CHANNEL, (data, handlerId) => {
|
||||||
switch (data.reason) {
|
switch (data.reason) {
|
||||||
case ViewReasonId.VREASON_USER_ACTION:
|
case ViewReasonId.VREASON_USER_ACTION:
|
||||||
if(data.channel_from) {
|
if(data.channel_from) {
|
||||||
|
@ -265,7 +265,7 @@ registerDispatcher(EventType.CLIENT_VIEW_ENTER_OWN_CHANNEL, (data, handlerId) =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_VIEW_MOVE, (data, handlerId) => {
|
registerRenderer(EventType.CLIENT_VIEW_MOVE, (data, handlerId) => {
|
||||||
switch (data.reason) {
|
switch (data.reason) {
|
||||||
case ViewReasonId.VREASON_MOVED:
|
case ViewReasonId.VREASON_MOVED:
|
||||||
return (
|
return (
|
||||||
|
@ -308,9 +308,9 @@ registerDispatcher(EventType.CLIENT_VIEW_MOVE, (data, handlerId) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_VIEW_MOVE_OWN_CHANNEL, findLogDispatcher(EventType.CLIENT_VIEW_MOVE));
|
registerRenderer(EventType.CLIENT_VIEW_MOVE_OWN_CHANNEL, findLogEventRenderer(EventType.CLIENT_VIEW_MOVE));
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_VIEW_MOVE_OWN, (data, handlerId) => {
|
registerRenderer(EventType.CLIENT_VIEW_MOVE_OWN, (data, handlerId) => {
|
||||||
switch (data.reason) {
|
switch (data.reason) {
|
||||||
case ViewReasonId.VREASON_MOVED:
|
case ViewReasonId.VREASON_MOVED:
|
||||||
return (
|
return (
|
||||||
|
@ -353,7 +353,7 @@ registerDispatcher(EventType.CLIENT_VIEW_MOVE_OWN, (data, handlerId) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_VIEW_LEAVE, (data, handlerId) => {
|
registerRenderer(EventType.CLIENT_VIEW_LEAVE, (data, handlerId) => {
|
||||||
switch (data.reason) {
|
switch (data.reason) {
|
||||||
case ViewReasonId.VREASON_USER_ACTION:
|
case ViewReasonId.VREASON_USER_ACTION:
|
||||||
return (
|
return (
|
||||||
|
@ -434,7 +434,7 @@ registerDispatcher(EventType.CLIENT_VIEW_LEAVE, (data, handlerId) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_VIEW_LEAVE_OWN_CHANNEL, (data, handlerId) => {
|
registerRenderer(EventType.CLIENT_VIEW_LEAVE_OWN_CHANNEL, (data, handlerId) => {
|
||||||
switch (data.reason) {
|
switch (data.reason) {
|
||||||
case ViewReasonId.VREASON_USER_ACTION:
|
case ViewReasonId.VREASON_USER_ACTION:
|
||||||
return (
|
return (
|
||||||
|
@ -456,24 +456,24 @@ registerDispatcher(EventType.CLIENT_VIEW_LEAVE_OWN_CHANNEL, (data, handlerId) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return findLogDispatcher(EventType.CLIENT_VIEW_LEAVE)(data, handlerId, EventType.CLIENT_VIEW_LEAVE);
|
return findLogEventRenderer(EventType.CLIENT_VIEW_LEAVE)(data, handlerId, EventType.CLIENT_VIEW_LEAVE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.SERVER_WELCOME_MESSAGE,data => (
|
registerRenderer(EventType.SERVER_WELCOME_MESSAGE,(data, handlerId) => (
|
||||||
<BBCodeRenderer message={"[color=green]" + data.message + "[/color]"} settings={{convertSingleUrls: false}} />
|
<BBCodeRenderer message={"[color=green]" + data.message + "[/color]"} settings={{convertSingleUrls: false}} handlerId={handlerId} />
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.SERVER_HOST_MESSAGE,data => (
|
registerRenderer(EventType.SERVER_HOST_MESSAGE,(data, handlerId) => (
|
||||||
<BBCodeRenderer message={"[color=green]" + data.message + "[/color]"} settings={{convertSingleUrls: false}} />
|
<BBCodeRenderer message={"[color=green]" + data.message + "[/color]"} settings={{convertSingleUrls: false}} handlerId={handlerId} />
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.SERVER_HOST_MESSAGE_DISCONNECT,data => (
|
registerRenderer(EventType.SERVER_HOST_MESSAGE_DISCONNECT,(data, handlerId) => (
|
||||||
<BBCodeRenderer message={"[color=red]" + data.message + "[/color]"} settings={{convertSingleUrls: false}} />
|
<BBCodeRenderer message={"[color=red]" + data.message + "[/color]"} settings={{convertSingleUrls: false}} handlerId={handlerId} />
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_NICKNAME_CHANGED,(data, handlerId) => (
|
registerRenderer(EventType.CLIENT_NICKNAME_CHANGED,(data, handlerId) => (
|
||||||
<VariadicTranslatable text={"{0} changed his nickname from \"{1}\" to \"{2}\""}>
|
<VariadicTranslatable text={"{0} changed his nickname from \"{1}\" to \"{2}\""}>
|
||||||
<ClientRenderer client={data.client} handlerId={handlerId} />
|
<ClientRenderer client={data.client} handlerId={handlerId} />
|
||||||
<>{data.old_name}</>
|
<>{data.old_name}</>
|
||||||
|
@ -481,44 +481,44 @@ registerDispatcher(EventType.CLIENT_NICKNAME_CHANGED,(data, handlerId) => (
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_NICKNAME_CHANGED_OWN,() => (
|
registerRenderer(EventType.CLIENT_NICKNAME_CHANGED_OWN,() => (
|
||||||
<Translatable>Nickname successfully changed.</Translatable>
|
<Translatable>Nickname successfully changed.</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_NICKNAME_CHANGE_FAILED,(data) => (
|
registerRenderer(EventType.CLIENT_NICKNAME_CHANGE_FAILED,(data) => (
|
||||||
<VariadicTranslatable text={"Failed to change nickname: {0}"}>
|
<VariadicTranslatable text={"Failed to change nickname: {0}"}>
|
||||||
<>{data.reason}</>
|
<>{data.reason}</>
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.GLOBAL_MESSAGE, (data, handlerId) => <>
|
registerRenderer(EventType.GLOBAL_MESSAGE, (data, handlerId) => <>
|
||||||
<VariadicTranslatable text={"{} send a server message: {1}"}>
|
<VariadicTranslatable text={"{} send a server message: {1}"}>
|
||||||
<ClientRenderer client={data.sender} handlerId={handlerId} />
|
<ClientRenderer client={data.sender} handlerId={handlerId} />
|
||||||
<XBBCodeRenderer>{data.message}</XBBCodeRenderer>
|
<BBCodeRenderer settings={{ convertSingleUrls: false }} message={data.message} handlerId={handlerId} />
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
</>);
|
</>);
|
||||||
|
|
||||||
|
|
||||||
registerDispatcher(EventType.DISCONNECTED,() => (
|
registerRenderer(EventType.DISCONNECTED,() => (
|
||||||
<Translatable>Disconnected from server</Translatable>
|
<Translatable>Disconnected from server</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.RECONNECT_SCHEDULED,data => (
|
registerRenderer(EventType.RECONNECT_SCHEDULED,data => (
|
||||||
<VariadicTranslatable text={"Reconnecting in {0}."}>
|
<VariadicTranslatable text={"Reconnecting in {0}."}>
|
||||||
<>{format_time(data.timeout, tr("now"))}</>
|
<>{format_time(data.timeout, tr("now"))}</>
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.RECONNECT_CANCELED,() => (
|
registerRenderer(EventType.RECONNECT_CANCELED,() => (
|
||||||
<Translatable>Reconnect canceled.</Translatable>
|
<Translatable>Reconnect canceled.</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.RECONNECT_CANCELED,() => (
|
registerRenderer(EventType.RECONNECT_CANCELED,() => (
|
||||||
<Translatable>Reconnecting...</Translatable>
|
<Translatable>Reconnecting...</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
registerDispatcher(EventType.SERVER_BANNED,(data, handlerId) => {
|
registerRenderer(EventType.SERVER_BANNED,(data, handlerId) => {
|
||||||
const time = data.time === 0 ? <Translatable>ever</Translatable> : <>{format_time(data.time * 1000, tr("one second"))}</>;
|
const time = data.time === 0 ? <Translatable>ever</Translatable> : <>{format_time(data.time * 1000, tr("one second"))}</>;
|
||||||
const reason = data.message ? <> <Translatable>Reason:</Translatable> {data.message}</> : undefined;
|
const reason = data.message ? <> <Translatable>Reason:</Translatable> {data.message}</> : undefined;
|
||||||
|
|
||||||
|
@ -544,11 +544,11 @@ registerDispatcher(EventType.SERVER_BANNED,(data, handlerId) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.SERVER_REQUIRES_PASSWORD,() => (
|
registerRenderer(EventType.SERVER_REQUIRES_PASSWORD,() => (
|
||||||
<Translatable>Server requires a password to connect.</Translatable>
|
<Translatable>Server requires a password to connect.</Translatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.SERVER_CLOSED,data => {
|
registerRenderer(EventType.SERVER_CLOSED,data => {
|
||||||
if(data.message)
|
if(data.message)
|
||||||
return (
|
return (
|
||||||
<VariadicTranslatable text={"Server has been closed ({})."}>
|
<VariadicTranslatable text={"Server has been closed ({})."}>
|
||||||
|
@ -558,7 +558,7 @@ registerDispatcher(EventType.SERVER_CLOSED,data => {
|
||||||
return <Translatable>Server has been closed.</Translatable>;
|
return <Translatable>Server has been closed.</Translatable>;
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.CONNECTION_COMMAND_ERROR,data => {
|
registerRenderer(EventType.CONNECTION_COMMAND_ERROR,data => {
|
||||||
let message;
|
let message;
|
||||||
if(typeof data.error === "string")
|
if(typeof data.error === "string")
|
||||||
message = data.error;
|
message = data.error;
|
||||||
|
@ -576,7 +576,7 @@ registerDispatcher(EventType.CONNECTION_COMMAND_ERROR,data => {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.CHANNEL_CREATE,(data, handlerId) => {
|
registerRenderer(EventType.CHANNEL_CREATE,(data, handlerId) => {
|
||||||
if(data.ownAction) {
|
if(data.ownAction) {
|
||||||
return (
|
return (
|
||||||
<VariadicTranslatable text={"Channel {} has been created."}>
|
<VariadicTranslatable text={"Channel {} has been created."}>
|
||||||
|
@ -593,13 +593,13 @@ registerDispatcher(EventType.CHANNEL_CREATE,(data, handlerId) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher("channel.show",(data, handlerId) => (
|
registerRenderer("channel.show",(data, handlerId) => (
|
||||||
<VariadicTranslatable text={"Channel {} has appeared."}>
|
<VariadicTranslatable text={"Channel {} has appeared."}>
|
||||||
<ChannelRenderer channel={data.channel} handlerId={handlerId} />
|
<ChannelRenderer channel={data.channel} handlerId={handlerId} />
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CHANNEL_DELETE,(data, handlerId) => {
|
registerRenderer(EventType.CHANNEL_DELETE,(data, handlerId) => {
|
||||||
if(data.ownAction) {
|
if(data.ownAction) {
|
||||||
return (
|
return (
|
||||||
<VariadicTranslatable text={"Channel {} has been deleted."}>
|
<VariadicTranslatable text={"Channel {} has been deleted."}>
|
||||||
|
@ -616,24 +616,24 @@ registerDispatcher(EventType.CHANNEL_DELETE,(data, handlerId) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher("channel.hide",(data, handlerId) => (
|
registerRenderer("channel.hide",(data, handlerId) => (
|
||||||
<VariadicTranslatable text={"Channel {} has disappeared."}>
|
<VariadicTranslatable text={"Channel {} has disappeared."}>
|
||||||
<ChannelRenderer channel={data.channel} handlerId={handlerId} />
|
<ChannelRenderer channel={data.channel} handlerId={handlerId} />
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_POKE_SEND,(data, handlerId) => (
|
registerRenderer(EventType.CLIENT_POKE_SEND,(data, handlerId) => (
|
||||||
<VariadicTranslatable text={"You poked {}."}>
|
<VariadicTranslatable text={"You poked {}."}>
|
||||||
<ClientRenderer client={data.target} handlerId={handlerId} />
|
<ClientRenderer client={data.target} handlerId={handlerId} />
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
));
|
));
|
||||||
|
|
||||||
registerDispatcher(EventType.CLIENT_POKE_RECEIVED,(data, handlerId) => {
|
registerRenderer(EventType.CLIENT_POKE_RECEIVED,(data, handlerId) => {
|
||||||
if(data.message) {
|
if(data.message) {
|
||||||
return (
|
return (
|
||||||
<VariadicTranslatable text={"You received a poke from {}: {}"}>
|
<VariadicTranslatable text={"You received a poke from {}: {}"}>
|
||||||
<ClientRenderer client={data.sender} handlerId={handlerId} />
|
<ClientRenderer client={data.sender} handlerId={handlerId} />
|
||||||
<BBCodeRenderer message={data.message} settings={{ convertSingleUrls: false }} />
|
<BBCodeRenderer message={data.message} settings={{ convertSingleUrls: false }} handlerId={handlerId} />
|
||||||
</VariadicTranslatable>
|
</VariadicTranslatable>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -645,10 +645,10 @@ registerDispatcher(EventType.CLIENT_POKE_RECEIVED,(data, handlerId) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerDispatcher(EventType.PRIVATE_MESSAGE_RECEIVED, () => undefined);
|
registerRenderer(EventType.PRIVATE_MESSAGE_RECEIVED, () => undefined);
|
||||||
registerDispatcher(EventType.PRIVATE_MESSAGE_SEND, () => undefined);
|
registerRenderer(EventType.PRIVATE_MESSAGE_SEND, () => undefined);
|
||||||
|
|
||||||
registerDispatcher(EventType.WEBRTC_FATAL_ERROR, (data) => {
|
registerRenderer(EventType.WEBRTC_FATAL_ERROR, (data) => {
|
||||||
if(data.retryTimeout) {
|
if(data.retryTimeout) {
|
||||||
let time = Math.ceil(data.retryTimeout / 1000);
|
let time = Math.ceil(data.retryTimeout / 1000);
|
||||||
let minutes = Math.floor(time / 60);
|
let minutes = Math.floor(time / 60);
|
|
@ -1,86 +0,0 @@
|
||||||
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
|
||||||
import * as React from "react";
|
|
||||||
import {LogMessage, ServerLogUIEvents, TypeInfo} from "tc-shared/ui/frames/log/Definitions";
|
|
||||||
import {Registry} from "tc-shared/events";
|
|
||||||
import * as ReactDOM from "react-dom";
|
|
||||||
import {ServerLogRenderer} from "tc-shared/ui/frames/log/Renderer";
|
|
||||||
import {findNotificationDispatcher, isNotificationEnabled} from "tc-shared/ui/frames/log/DispatcherNotifications";
|
|
||||||
import {Settings, settings} from "tc-shared/settings";
|
|
||||||
import {isFocusRequestEnabled, requestWindowFocus} from "tc-shared/ui/frames/log/DispatcherFocus";
|
|
||||||
|
|
||||||
const cssStyle = require("./Renderer.scss");
|
|
||||||
|
|
||||||
let uniqueLogEventId = 0;
|
|
||||||
export class ServerEventLog {
|
|
||||||
private readonly connection: ConnectionHandler;
|
|
||||||
private readonly uiEvents: Registry<ServerLogUIEvents>;
|
|
||||||
private readonly listenerHandlerVisibilityChanged;
|
|
||||||
|
|
||||||
private htmlTag: HTMLDivElement;
|
|
||||||
|
|
||||||
private maxHistoryLength: number = 100;
|
|
||||||
|
|
||||||
private eventLog: LogMessage[] = [];
|
|
||||||
|
|
||||||
constructor(connection: ConnectionHandler) {
|
|
||||||
this.connection = connection;
|
|
||||||
this.uiEvents = new Registry<ServerLogUIEvents>();
|
|
||||||
this.htmlTag = document.createElement("div");
|
|
||||||
this.htmlTag.classList.add(cssStyle.htmlTag);
|
|
||||||
|
|
||||||
this.uiEvents.on("query_log", () => {
|
|
||||||
this.uiEvents.fire_react("notify_log", { log: this.eventLog.slice() });
|
|
||||||
});
|
|
||||||
|
|
||||||
ReactDOM.render(<ServerLogRenderer events={this.uiEvents} handlerId={this.connection.handlerId} />, this.htmlTag);
|
|
||||||
|
|
||||||
this.connection.events().on("notify_visibility_changed", this.listenerHandlerVisibilityChanged =event => {
|
|
||||||
if(event.visible) {
|
|
||||||
this.uiEvents.fire("notify_show");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
log<T extends keyof TypeInfo>(type: T, data: TypeInfo[T]) {
|
|
||||||
const event = {
|
|
||||||
data: data,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
type: type as any,
|
|
||||||
uniqueId: "log-" + Date.now() + "-" + (++uniqueLogEventId)
|
|
||||||
};
|
|
||||||
|
|
||||||
if(settings.global(Settings.FN_EVENTS_LOG_ENABLED(type), true)) {
|
|
||||||
this.eventLog.push(event);
|
|
||||||
while(this.eventLog.length > this.maxHistoryLength)
|
|
||||||
this.eventLog.pop_front();
|
|
||||||
|
|
||||||
this.uiEvents.fire_react("notify_log_add", { event: event });
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isNotificationEnabled(type as any)) {
|
|
||||||
const notification = findNotificationDispatcher(type);
|
|
||||||
if(notification) notification(data, this.connection.handlerId, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isFocusRequestEnabled(type as any)) {
|
|
||||||
requestWindowFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getHTMLTag() {
|
|
||||||
return this.htmlTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
if(this.htmlTag) {
|
|
||||||
ReactDOM.unmountComponentAtNode(this.htmlTag);
|
|
||||||
this.htmlTag?.remove();
|
|
||||||
this.htmlTag = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connection.events().off(this.listenerHandlerVisibilityChanged);
|
|
||||||
this.eventLog = undefined;
|
|
||||||
|
|
||||||
this.uiEvents.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,8 +29,6 @@ export abstract class AbstractConversationController<
|
||||||
protected currentSelectedConversation: ConversationType;
|
protected currentSelectedConversation: ConversationType;
|
||||||
protected currentSelectedListener: (() => void)[];
|
protected currentSelectedListener: (() => void)[];
|
||||||
|
|
||||||
protected crossChannelChatSupported = true;
|
|
||||||
|
|
||||||
protected constructor() {
|
protected constructor() {
|
||||||
this.uiEvents = new Registry<Events>();
|
this.uiEvents = new Registry<Events>();
|
||||||
this.currentSelectedListener = [];
|
this.currentSelectedListener = [];
|
||||||
|
@ -68,6 +66,12 @@ export abstract class AbstractConversationController<
|
||||||
|
|
||||||
protected registerConversationManagerEvents(manager: Manager) {
|
protected registerConversationManagerEvents(manager: Manager) {
|
||||||
this.listenerManager.push(manager.events.on("notify_selected_changed", event => this.setCurrentlySelected(event.newConversation)));
|
this.listenerManager.push(manager.events.on("notify_selected_changed", event => this.setCurrentlySelected(event.newConversation)));
|
||||||
|
this.listenerManager.push(manager.events.on("notify_cross_conversation_support_changed", () => {
|
||||||
|
const currentConversation = this.getCurrentConversation();
|
||||||
|
if(currentConversation) {
|
||||||
|
this.reportStateToUI(currentConversation);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected registerConversationEvents(conversation: ConversationType) {
|
protected registerConversationEvents(conversation: ConversationType) {
|
||||||
|
@ -138,7 +142,7 @@ export abstract class AbstractConversationController<
|
||||||
this.uiEvents.fire_react("notify_conversation_state", {
|
this.uiEvents.fire_react("notify_conversation_state", {
|
||||||
chatId: conversation.getChatId(),
|
chatId: conversation.getChatId(),
|
||||||
state: "private",
|
state: "private",
|
||||||
crossChannelChatSupported: this.crossChannelChatSupported
|
crossChannelChatSupported: this.conversationManager.hasCrossConversationSupport()
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -154,7 +158,7 @@ export abstract class AbstractConversationController<
|
||||||
chatFrameMaxMessageCount: kMaxChatFrameMessageSize,
|
chatFrameMaxMessageCount: kMaxChatFrameMessageSize,
|
||||||
unreadTimestamp: conversation.getUnreadTimestamp(),
|
unreadTimestamp: conversation.getUnreadTimestamp(),
|
||||||
|
|
||||||
showUserSwitchEvents: conversation.isPrivate() || !this.crossChannelChatSupported,
|
showUserSwitchEvents: conversation.isPrivate() || !this.conversationManager.hasCrossConversationSupport(),
|
||||||
sendEnabled: conversation.isSendEnabled(),
|
sendEnabled: conversation.isSendEnabled(),
|
||||||
|
|
||||||
events: [...conversation.getPresentEvents(), ...conversation.getPresentMessages()]
|
events: [...conversation.getPresentEvents(), ...conversation.getPresentMessages()]
|
||||||
|
@ -251,18 +255,6 @@ export abstract class AbstractConversationController<
|
||||||
return this.currentSelectedConversation;
|
return this.currentSelectedConversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setCrossChannelChatSupport(flag: boolean) {
|
|
||||||
if(this.crossChannelChatSupported === flag) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.crossChannelChatSupported = flag;
|
|
||||||
const currentConversation = this.getCurrentConversation();
|
|
||||||
if(currentConversation) {
|
|
||||||
this.reportStateToUI(currentConversation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler<AbstractConversationUiEvents>("query_conversation_state")
|
@EventHandler<AbstractConversationUiEvents>("query_conversation_state")
|
||||||
protected handleQueryConversationState(event: AbstractConversationUiEvents["query_conversation_state"]) {
|
protected handleQueryConversationState(event: AbstractConversationUiEvents["query_conversation_state"]) {
|
||||||
const conversation = this.conversationManager?.findConversationById(event.chatId);
|
const conversation = this.conversationManager?.findConversationById(event.chatId);
|
||||||
|
|
|
@ -56,6 +56,9 @@ html:root {
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
|
||||||
.containerMessages {
|
.containerMessages {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
|
|
|
@ -26,9 +26,9 @@ import {ChatBox} from "tc-shared/ui/react-elements/ChatBox";
|
||||||
|
|
||||||
const cssStyle = require("./AbstractConversationRenderer.scss");
|
const cssStyle = require("./AbstractConversationRenderer.scss");
|
||||||
|
|
||||||
const ChatMessageTextRenderer = React.memo((props: { text: string }) => {
|
const ChatMessageTextRenderer = React.memo((props: { text: string, handlerId: string }) => {
|
||||||
if(typeof props.text !== "string") { debugger; }
|
if(typeof props.text !== "string") { debugger; }
|
||||||
return <BBCodeRenderer settings={{ convertSingleUrls: true }} message={props.text || ""} />;
|
return <BBCodeRenderer settings={{ convertSingleUrls: true }} message={props.text || ""} handlerId={props.handlerId} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
const ChatEventMessageRenderer = React.memo((props: {
|
const ChatEventMessageRenderer = React.memo((props: {
|
||||||
|
@ -71,7 +71,7 @@ const ChatEventMessageRenderer = React.memo((props: {
|
||||||
<br /> { /* Only for copy purposes */ }
|
<br /> { /* Only for copy purposes */ }
|
||||||
</div>
|
</div>
|
||||||
<div className={cssStyle.text}>
|
<div className={cssStyle.text}>
|
||||||
<ChatMessageTextRenderer text={props.message.message} />
|
<ChatMessageTextRenderer text={props.message.message} handlerId={props.handlerId} />
|
||||||
</div>
|
</div>
|
||||||
<br style={{ content: " ", display: "none" }} /> { /* Only for copy purposes */ }
|
<br style={{ content: " ", display: "none" }} /> { /* Only for copy purposes */ }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
import {ChannelBarMode, ChannelBarUiEvents} from "tc-shared/ui/frames/side/ChannelBarDefinitions";
|
||||||
|
import {ChannelEntry, ChannelSidebarMode} from "tc-shared/tree/Channel";
|
||||||
|
import {ChannelConversationController} from "tc-shared/ui/frames/side/ChannelConversationController";
|
||||||
|
import {ChannelDescriptionController} from "tc-shared/ui/frames/side/ChannelDescriptionController";
|
||||||
|
import {LocalClientEntry} from "tc-shared/tree/Client";
|
||||||
|
|
||||||
|
export class ChannelBarController {
|
||||||
|
readonly uiEvents: Registry<ChannelBarUiEvents>;
|
||||||
|
|
||||||
|
private channelConversations: ChannelConversationController;
|
||||||
|
private description: ChannelDescriptionController;
|
||||||
|
|
||||||
|
private currentConnection: ConnectionHandler;
|
||||||
|
private listenerConnection: (() => void)[];
|
||||||
|
|
||||||
|
private currentChannel: ChannelEntry;
|
||||||
|
private listenerChannel: (() => void)[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.uiEvents = new Registry<ChannelBarUiEvents>();
|
||||||
|
this.listenerConnection = [];
|
||||||
|
this.listenerChannel = [];
|
||||||
|
|
||||||
|
this.channelConversations = new ChannelConversationController();
|
||||||
|
this.description = new ChannelDescriptionController();
|
||||||
|
|
||||||
|
this.uiEvents.on("query_mode", () => this.notifyChannelMode());
|
||||||
|
this.uiEvents.on("query_channel_id", () => this.notifyChannelId());
|
||||||
|
this.uiEvents.on("query_data", event => this.notifyModeData(event.mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.listenerConnection.forEach(callback => callback());
|
||||||
|
this.listenerConnection = [];
|
||||||
|
|
||||||
|
this.listenerChannel.forEach(callback => callback());
|
||||||
|
this.listenerChannel = [];
|
||||||
|
|
||||||
|
this.currentChannel = undefined;
|
||||||
|
this.currentConnection = undefined;
|
||||||
|
|
||||||
|
this.channelConversations?.destroy();
|
||||||
|
this.channelConversations = undefined;
|
||||||
|
|
||||||
|
this.description?.destroy();
|
||||||
|
this.description = undefined;
|
||||||
|
|
||||||
|
this.uiEvents.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
setConnectionHandler(handler: ConnectionHandler) {
|
||||||
|
if(this.currentConnection === handler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.channelConversations.setConnectionHandler(handler);
|
||||||
|
|
||||||
|
this.listenerConnection.forEach(callback => callback());
|
||||||
|
this.listenerConnection = [];
|
||||||
|
|
||||||
|
this.currentConnection = handler;
|
||||||
|
|
||||||
|
const selectedEntry = handler?.channelTree.getSelectedEntry();
|
||||||
|
if(selectedEntry instanceof ChannelEntry) {
|
||||||
|
this.setChannel(selectedEntry);
|
||||||
|
} else {
|
||||||
|
this.setChannel(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(handler) {
|
||||||
|
this.listenerConnection.push(handler.channelTree.events.on("notify_selected_entry_changed", event => {
|
||||||
|
if(event.newEntry instanceof ChannelEntry) {
|
||||||
|
this.setChannel(event.newEntry);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.listenerConnection.push(handler.channelTree.events.on("notify_client_moved", event => {
|
||||||
|
if(event.client instanceof LocalClientEntry) {
|
||||||
|
if(event.oldChannel === this.currentChannel || event.newChannel === this.currentChannel) {
|
||||||
|
/* The mode may changed since we can now write in the channel */
|
||||||
|
this.notifyChannelMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.listenerConnection.push(handler.getChannelConversations().events.on("notify_cross_conversation_support_changed", () => {
|
||||||
|
this.notifyChannelMode();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setChannel(channel: ChannelEntry) {
|
||||||
|
if(this.currentChannel === channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.description.setChannel(channel);
|
||||||
|
|
||||||
|
this.listenerChannel.forEach(callback => callback());
|
||||||
|
this.listenerChannel = [];
|
||||||
|
|
||||||
|
this.currentChannel = channel;
|
||||||
|
this.notifyChannelId();
|
||||||
|
|
||||||
|
if(channel) {
|
||||||
|
this.listenerChannel.push(channel.events.on("notify_properties_updated", event => {
|
||||||
|
if("channel_sidebar_mode" in event.updated_properties) {
|
||||||
|
this.notifyChannelMode();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifyChannelId() {
|
||||||
|
this.uiEvents.fire_react("notify_channel_id", {
|
||||||
|
channelId: this.currentChannel ? this.currentChannel.channelId : -1,
|
||||||
|
handlerId: this.currentConnection ? this.currentConnection.handlerId : "unbound"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifyChannelMode() {
|
||||||
|
let mode: ChannelBarMode = "none";
|
||||||
|
|
||||||
|
if(this.currentChannel) {
|
||||||
|
switch(this.currentChannel.properties.channel_sidebar_mode) {
|
||||||
|
case ChannelSidebarMode.Description:
|
||||||
|
mode = "description";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChannelSidebarMode.FileTransfer:
|
||||||
|
mode = "file-transfer";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChannelSidebarMode.Conversation:
|
||||||
|
mode = "conversation";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChannelSidebarMode.Unknown:
|
||||||
|
default:
|
||||||
|
if(this.currentConnection) {
|
||||||
|
const channelConversation = this.currentConnection.getChannelConversations();
|
||||||
|
if(channelConversation.hasCrossConversationSupport() || this.currentChannel === this.currentConnection.getClient().currentChannel()) {
|
||||||
|
mode = "conversation";
|
||||||
|
} else {
|
||||||
|
/* A really old TeaSpeak server or a TeamSpeak server. */
|
||||||
|
mode = "description";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mode = "none";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uiEvents.fire_react("notify_mode", { mode: mode });
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifyModeData(mode: ChannelBarMode) {
|
||||||
|
switch (mode) {
|
||||||
|
case "none":
|
||||||
|
this.uiEvents.fire_react("notify_data", { content: "none", data: {} });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "conversation":
|
||||||
|
this.uiEvents.fire_react("notify_data", {
|
||||||
|
content: "conversation",
|
||||||
|
data: {
|
||||||
|
events: this.channelConversations.getUiEvents()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "description":
|
||||||
|
this.uiEvents.fire_react("notify_data", {
|
||||||
|
content: "description",
|
||||||
|
data: {
|
||||||
|
events: this.description.uiEvents
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "file-transfer":
|
||||||
|
this.uiEvents.fire_react("notify_data", {
|
||||||
|
content: "file-transfer",
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/* TODO! */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions";
|
||||||
|
import {ChannelDescriptionUiEvents} from "tc-shared/ui/frames/side/ChannelDescriptionDefinitions";
|
||||||
|
|
||||||
|
export type ChannelBarMode = "conversation" | "description" | "file-transfer" | "none";
|
||||||
|
|
||||||
|
export interface ChannelBarModeData {
|
||||||
|
"conversation": {
|
||||||
|
events: Registry<ChannelConversationUiEvents>,
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
events: Registry<ChannelDescriptionUiEvents>
|
||||||
|
},
|
||||||
|
"file-transfer": {
|
||||||
|
/* TODO! */
|
||||||
|
},
|
||||||
|
"none": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChannelBarNotifyModeData<T extends keyof ChannelBarModeData> = {
|
||||||
|
content: T,
|
||||||
|
data: ChannelBarModeData[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelBarUiEvents {
|
||||||
|
query_mode: {},
|
||||||
|
query_channel_id: {},
|
||||||
|
query_data: { mode: ChannelBarMode },
|
||||||
|
|
||||||
|
notify_mode: { mode: ChannelBarMode },
|
||||||
|
notify_channel_id: {
|
||||||
|
channelId: number,
|
||||||
|
handlerId: string
|
||||||
|
},
|
||||||
|
notify_data: ChannelBarNotifyModeData<ChannelBarMode>
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
import {ChannelBarMode, ChannelBarModeData, ChannelBarUiEvents} from "tc-shared/ui/frames/side/ChannelBarDefinitions";
|
||||||
|
import {useContext, useState} from "react";
|
||||||
|
import * as React from "react";
|
||||||
|
import {ConversationPanel} from "tc-shared/ui/frames/side/AbstractConversationRenderer";
|
||||||
|
import {useDependentState} from "tc-shared/ui/react-elements/Helper";
|
||||||
|
import {ChannelDescriptionRenderer} from "tc-shared/ui/frames/side/ChannelDescriptionRenderer";
|
||||||
|
|
||||||
|
const EventContext = React.createContext<Registry<ChannelBarUiEvents>>(undefined);
|
||||||
|
const ChannelContext = React.createContext<{ channelId: number, handlerId: string }>(undefined);
|
||||||
|
|
||||||
|
function useModeData<T extends ChannelBarMode>(type: T, dependencies: any[]) : ChannelBarModeData[T] {
|
||||||
|
const events = useContext(EventContext);
|
||||||
|
const [ contentData, setContentData ] = useDependentState(() => {
|
||||||
|
events.fire("query_data", { mode: type });
|
||||||
|
return undefined;
|
||||||
|
}, dependencies);
|
||||||
|
events.reactUse("notify_data", event => event.content === type && setContentData(event.data));
|
||||||
|
|
||||||
|
return contentData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModeRenderer = () => {
|
||||||
|
const events = useContext(EventContext);
|
||||||
|
const channelContext = useContext(ChannelContext);
|
||||||
|
const [ mode, setMode ] = useDependentState<ChannelBarMode>(() => {
|
||||||
|
events.fire("query_mode");
|
||||||
|
return "none";
|
||||||
|
}, [ channelContext.channelId, channelContext.handlerId ]);
|
||||||
|
events.reactUse("notify_mode", event => setMode(event.mode));
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case "conversation":
|
||||||
|
return <ModeRendererConversation key={"conversation"} />;
|
||||||
|
|
||||||
|
case "description":
|
||||||
|
return <ModeRendererDescription key={"description"} />;
|
||||||
|
|
||||||
|
case "file-transfer":
|
||||||
|
/* TODO! */
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case "none":
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModeRendererConversation = React.memo(() => {
|
||||||
|
const channelContext = useContext(ChannelContext);
|
||||||
|
const data = useModeData("conversation", [ channelContext ]);
|
||||||
|
if(!data) { return null; }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConversationPanel
|
||||||
|
key={"conversation"}
|
||||||
|
events={data.events}
|
||||||
|
handlerId={channelContext.handlerId}
|
||||||
|
messagesDeletable={true}
|
||||||
|
noFirstMessageOverlay={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ModeRendererDescription = React.memo(() => {
|
||||||
|
const channelContext = useContext(ChannelContext);
|
||||||
|
const data = useModeData("description", [ channelContext ]);
|
||||||
|
if(!data) { return null; }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChannelDescriptionRenderer events={data.events} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChannelBarRenderer = (props: { events: Registry<ChannelBarUiEvents> }) => {
|
||||||
|
const [ channelContext, setChannelContext ] = useState<{ channelId: number, handlerId: string }>(() => {
|
||||||
|
props.events.fire("query_channel_id");
|
||||||
|
return { channelId: -1, handlerId: "unbound" };
|
||||||
|
});
|
||||||
|
props.events.reactUse("notify_channel_id", event => setChannelContext({ handlerId: event.handlerId, channelId: event.channelId }));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EventContext.Provider value={props.events}>
|
||||||
|
<ChannelContext.Provider value={channelContext}>
|
||||||
|
<ModeRenderer />
|
||||||
|
</ChannelContext.Provider>
|
||||||
|
</EventContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
|
@ -11,7 +11,6 @@ import {
|
||||||
ChannelConversationManager,
|
ChannelConversationManager,
|
||||||
ChannelConversationManagerEvents
|
ChannelConversationManagerEvents
|
||||||
} from "tc-shared/conversations/ChannelConversationManager";
|
} from "tc-shared/conversations/ChannelConversationManager";
|
||||||
import {ServerFeature} from "tc-shared/connection/ServerFeatures";
|
|
||||||
import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions";
|
import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions";
|
||||||
|
|
||||||
export class ChannelConversationController extends AbstractConversationController<
|
export class ChannelConversationController extends AbstractConversationController<
|
||||||
|
@ -75,18 +74,6 @@ export class ChannelConversationController extends AbstractConversationControlle
|
||||||
|
|
||||||
this.handlePanelShow();
|
this.handlePanelShow();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.connectionListener.push(connection.events().on("notify_connection_state_changed", event => {
|
|
||||||
if(event.newState === ConnectionState.CONNECTED) {
|
|
||||||
connection.serverFeatures.awaitFeatures().then(success => {
|
|
||||||
if(!success) { return; }
|
|
||||||
|
|
||||||
this.setCrossChannelChatSupport(connection.serverFeatures.supportsFeature(ServerFeature.ADVANCED_CHANNEL_CHAT));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setCrossChannelChatSupport(true);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler<AbstractConversationUiEvents>("action_delete_message")
|
@EventHandler<AbstractConversationUiEvents>("action_delete_message")
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
import {ChannelEntry} from "tc-shared/tree/Channel";
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
import {
|
||||||
|
ChannelDescriptionStatus,
|
||||||
|
ChannelDescriptionUiEvents
|
||||||
|
} from "tc-shared/ui/frames/side/ChannelDescriptionDefinitions";
|
||||||
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {LogCategory, logError} from "tc-shared/log";
|
||||||
|
import {ErrorCode} from "tc-shared/connection/ErrorCode";
|
||||||
|
|
||||||
|
export class ChannelDescriptionController {
|
||||||
|
readonly uiEvents: Registry<ChannelDescriptionUiEvents>;
|
||||||
|
private currentChannel: ChannelEntry;
|
||||||
|
private listenerChannel: (() => void)[];
|
||||||
|
|
||||||
|
private descriptionSendPending = false;
|
||||||
|
private cachedDescriptionStatus: ChannelDescriptionStatus;
|
||||||
|
private cachedDescriptionAge: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.uiEvents = new Registry<ChannelDescriptionUiEvents>();
|
||||||
|
this.listenerChannel = [];
|
||||||
|
|
||||||
|
this.uiEvents.on("query_description", () => this.notifyDescription());
|
||||||
|
this.uiEvents.enableDebug("channel-description");
|
||||||
|
|
||||||
|
this.cachedDescriptionAge = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.listenerChannel.forEach(callback => callback());
|
||||||
|
this.listenerChannel = [];
|
||||||
|
|
||||||
|
this.currentChannel = undefined;
|
||||||
|
|
||||||
|
this.uiEvents.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
setChannel(channel: ChannelEntry) {
|
||||||
|
if(this.currentChannel === channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listenerChannel.forEach(callback => callback());
|
||||||
|
this.listenerChannel = [];
|
||||||
|
|
||||||
|
this.currentChannel = channel;
|
||||||
|
this.cachedDescriptionStatus = undefined;
|
||||||
|
|
||||||
|
if(channel) {
|
||||||
|
this.listenerChannel.push(channel.events.on("notify_properties_updated", event => {
|
||||||
|
if("channel_description" in event.updated_properties) {
|
||||||
|
this.notifyDescription().then(undefined);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.listenerChannel.push(channel.events.on("notify_description_changed", () => {
|
||||||
|
this.notifyDescription().then(undefined);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notifyDescription().then(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async notifyDescription() {
|
||||||
|
if(this.descriptionSendPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.descriptionSendPending = true;
|
||||||
|
try {
|
||||||
|
if(Date.now() - this.cachedDescriptionAge > 5000 || !this.cachedDescriptionStatus) {
|
||||||
|
await this.updateCachedDescriptionStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uiEvents.fire_react("notify_description", { status: this.cachedDescriptionStatus });
|
||||||
|
} finally {
|
||||||
|
this.descriptionSendPending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateCachedDescriptionStatus() {
|
||||||
|
try {
|
||||||
|
let description;
|
||||||
|
if(this.currentChannel) {
|
||||||
|
description = await new Promise<any>((resolve, reject) => {
|
||||||
|
this.currentChannel.getChannelDescription().then(resolve).catch(reject);
|
||||||
|
setTimeout(() => reject(tr("timeout")), 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cachedDescriptionStatus = {
|
||||||
|
status: "success",
|
||||||
|
description: description,
|
||||||
|
handlerId: this.currentChannel.channelTree.client.handlerId
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if(error instanceof CommandResult) {
|
||||||
|
if(error.id === ErrorCode.SERVER_INSUFFICIENT_PERMISSIONS) {
|
||||||
|
const permission = this.currentChannel?.channelTree.client.permissions.resolveInfo(parseInt(error.json["failed_permid"]));
|
||||||
|
this.cachedDescriptionStatus = {
|
||||||
|
status: "no-permissions",
|
||||||
|
failedPermission: permission ? permission.name : "unknown"
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = error.formattedMessage();
|
||||||
|
} else if(typeof error !== "string") {
|
||||||
|
logError(LogCategory.GENERAL, tr("Failed to get channel descriptions: %o"), error);
|
||||||
|
error = tr("lookup the console");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cachedDescriptionStatus = {
|
||||||
|
status: "error",
|
||||||
|
reason: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.cachedDescriptionAge = Date.now();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
export type ChannelDescriptionStatus = {
|
||||||
|
status: "success",
|
||||||
|
description: string,
|
||||||
|
handlerId: string
|
||||||
|
} | {
|
||||||
|
status: "error",
|
||||||
|
reason: string
|
||||||
|
} | {
|
||||||
|
status: "no-permissions",
|
||||||
|
failedPermission: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ChannelDescriptionUiEvents {
|
||||||
|
query_description: {},
|
||||||
|
notify_description: {
|
||||||
|
status: ChannelDescriptionStatus,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
@import "../../../../css/static/mixin";
|
||||||
|
@import "../../../../css/static/properties";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
&.centeredText {
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--chat-overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
color: #ac5353;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.centeredText .text {
|
||||||
|
align-self: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.descriptionContainer {
|
||||||
|
padding: .5em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
@include chat-scrollbar();
|
||||||
|
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
import {
|
||||||
|
ChannelDescriptionStatus,
|
||||||
|
ChannelDescriptionUiEvents
|
||||||
|
} from "tc-shared/ui/frames/side/ChannelDescriptionDefinitions";
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
import * as React from "react";
|
||||||
|
import {useState} from "react";
|
||||||
|
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
||||||
|
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
||||||
|
import {allowedBBCodes, BBCodeRenderer} from "tc-shared/text/bbcode";
|
||||||
|
import {parse as parseBBCode} from "vendor/xbbcode/parser";
|
||||||
|
|
||||||
|
const cssStyle = require("./ChannelDescriptionRenderer.scss");
|
||||||
|
|
||||||
|
const CenteredTextRenderer = (props: { children?: React.ReactElement | (React.ReactElement | string)[], className?: string }) => {
|
||||||
|
return (
|
||||||
|
<div className={cssStyle.container + " " + cssStyle.centeredText + " " + props.className}>
|
||||||
|
<div className={cssStyle.text}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DescriptionRenderer = React.memo((props: { description: string, handlerId: string }) => {
|
||||||
|
if(!props.description) {
|
||||||
|
return (
|
||||||
|
<CenteredTextRenderer key={"no-description"}>
|
||||||
|
<Translatable>Channel has no description</Translatable>
|
||||||
|
</CenteredTextRenderer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={"description"} className={cssStyle.descriptionContainer}>
|
||||||
|
<BBCodeRenderer settings={{ convertSingleUrls: false }} message={props.description} handlerId={props.handlerId} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
const DescriptionErrorRenderer = React.memo((props: { error: string }) => (
|
||||||
|
<CenteredTextRenderer className={cssStyle.error}>
|
||||||
|
<Translatable>An error happened while fetching the channel description:</Translatable><br />
|
||||||
|
{props.error}
|
||||||
|
</CenteredTextRenderer>
|
||||||
|
));
|
||||||
|
|
||||||
|
const PermissionErrorRenderer = React.memo((props: { failedPermission: string }) => (
|
||||||
|
<CenteredTextRenderer>
|
||||||
|
<Translatable>You don't have the permission to watch the channel description.</Translatable>
|
||||||
|
<VariadicTranslatable text={"(Missing permission {})"}><code>{props.failedPermission}</code></VariadicTranslatable>
|
||||||
|
</CenteredTextRenderer>
|
||||||
|
));
|
||||||
|
|
||||||
|
export const ChannelDescriptionRenderer = React.memo((props: { events: Registry<ChannelDescriptionUiEvents> }) => {
|
||||||
|
const [ description, setDescription ] = useState<ChannelDescriptionStatus | { status: "loading" }>(() => {
|
||||||
|
props.events.fire("query_description");
|
||||||
|
return { status: "loading" };
|
||||||
|
});
|
||||||
|
props.events.reactUse("notify_description", event => setDescription(event.status));
|
||||||
|
|
||||||
|
switch (description.status) {
|
||||||
|
case "success":
|
||||||
|
return (
|
||||||
|
<DescriptionRenderer description={description.description} key={"description"} handlerId={description.handlerId} />
|
||||||
|
);
|
||||||
|
|
||||||
|
case "error":
|
||||||
|
return (
|
||||||
|
<DescriptionErrorRenderer error={description.reason} key={"error"} />
|
||||||
|
);
|
||||||
|
|
||||||
|
case "no-permissions":
|
||||||
|
return (
|
||||||
|
<PermissionErrorRenderer failedPermission={description.failedPermission} />
|
||||||
|
);
|
||||||
|
|
||||||
|
case "loading":
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<CenteredTextRenderer key={"loading"}>
|
||||||
|
<Translatable>loading channel description</Translatable> <LoadingDots />
|
||||||
|
</CenteredTextRenderer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
|
@ -51,7 +51,7 @@ export class SideHeaderController {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.uiEvents.on("action_switch_channel_chat", () => {
|
this.uiEvents.on("action_switch_channel_chat", () => {
|
||||||
this.connection.getSideBar().showChannelConversations();
|
this.connection.getSideBar().showChannel();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.uiEvents.on("action_bot_manage", () => {
|
this.uiEvents.on("action_bot_manage", () => {
|
||||||
|
|
|
@ -3,15 +3,15 @@ import {useRef, useState} from "react";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
||||||
import {FlatInputField} from "tc-shared/ui/react-elements/InputField";
|
import {FlatInputField} from "tc-shared/ui/react-elements/InputField";
|
||||||
import {EventType} from "tc-shared/ui/frames/log/Definitions";
|
|
||||||
import {
|
|
||||||
getRegisteredNotificationDispatchers,
|
|
||||||
isNotificationEnabled
|
|
||||||
} from "tc-shared/ui/frames/log/DispatcherNotifications";
|
|
||||||
import {Settings, settings} from "tc-shared/settings";
|
import {Settings, settings} from "tc-shared/settings";
|
||||||
import {Checkbox} from "tc-shared/ui/react-elements/Checkbox";
|
import {Checkbox} from "tc-shared/ui/react-elements/Checkbox";
|
||||||
import {Tooltip} from "tc-shared/ui/react-elements/Tooltip";
|
import {Tooltip} from "tc-shared/ui/react-elements/Tooltip";
|
||||||
import {isFocusRequestEnabled} from "tc-shared/ui/frames/log/DispatcherFocus";
|
import {TypeInfo} from "tc-shared/connectionlog/Definitions";
|
||||||
|
import {
|
||||||
|
getRegisteredNotificationDispatchers,
|
||||||
|
isNotificationEnabled
|
||||||
|
} from "tc-shared/connectionlog/DispatcherNotifications";
|
||||||
|
import {isFocusRequestEnabled} from "tc-shared/connectionlog/DispatcherFocus";
|
||||||
|
|
||||||
const cssStyle = require("./Notifications.scss");
|
const cssStyle = require("./Notifications.scss");
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ interface EventGroup {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
events?: string[];
|
events?: (keyof TypeInfo)[];
|
||||||
subgroups?: EventGroup[];
|
subgroups?: EventGroup[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,23 +340,23 @@ const knownEventGroups: EventGroup[] = [
|
||||||
key: "client-messages",
|
key: "client-messages",
|
||||||
name: "Messages",
|
name: "Messages",
|
||||||
events: [
|
events: [
|
||||||
EventType.CLIENT_POKE_RECEIVED,
|
"client.poke.received",
|
||||||
EventType.CLIENT_POKE_SEND,
|
"client.poke.send",
|
||||||
EventType.PRIVATE_MESSAGE_SEND,
|
"private.message.send",
|
||||||
EventType.PRIVATE_MESSAGE_RECEIVED
|
"private.message.received"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "client-view",
|
key: "client-view",
|
||||||
name: "View",
|
name: "View",
|
||||||
events: [
|
events: [
|
||||||
EventType.CLIENT_VIEW_ENTER,
|
"client.view.enter",
|
||||||
EventType.CLIENT_VIEW_ENTER_OWN_CHANNEL,
|
"client.view.enter.own.channel",
|
||||||
EventType.CLIENT_VIEW_MOVE,
|
"client.view.move",
|
||||||
EventType.CLIENT_VIEW_MOVE_OWN,
|
"client.view.move.own",
|
||||||
EventType.CLIENT_VIEW_MOVE_OWN_CHANNEL,
|
"client.view.move.own.channel",
|
||||||
EventType.CLIENT_VIEW_LEAVE,
|
"client.view.leave",
|
||||||
EventType.CLIENT_VIEW_LEAVE_OWN_CHANNEL
|
"client.view.leave.own.channel"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -365,45 +365,45 @@ const knownEventGroups: EventGroup[] = [
|
||||||
key: "server",
|
key: "server",
|
||||||
name: "Server",
|
name: "Server",
|
||||||
events: [
|
events: [
|
||||||
EventType.GLOBAL_MESSAGE,
|
"global.message",
|
||||||
EventType.SERVER_CLOSED,
|
"server.closed",
|
||||||
EventType.SERVER_BANNED,
|
"server.banned",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "connection",
|
key: "connection",
|
||||||
name: "Connection",
|
name: "Connection",
|
||||||
events: [
|
events: [
|
||||||
EventType.CONNECTION_BEGIN,
|
"connection.begin",
|
||||||
EventType.CONNECTION_CONNECTED,
|
"connection.connected",
|
||||||
EventType.CONNECTION_FAILED
|
"connection.failed"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const groupNames: { [key: string]: string } = {};
|
const groupNames: { [T in keyof TypeInfo]?: string } = {};
|
||||||
groupNames[EventType.CLIENT_POKE_RECEIVED] = tr("You received a poke");
|
groupNames["client.poke.received"] = tr("You received a poke");
|
||||||
groupNames[EventType.CLIENT_POKE_SEND] = tr("You send a poke");
|
groupNames["client.poke.send"] = tr("You send a poke");
|
||||||
groupNames[EventType.PRIVATE_MESSAGE_SEND] = tr("You received a private message");
|
groupNames["private.message.send"] = tr("You received a private message");
|
||||||
groupNames[EventType.PRIVATE_MESSAGE_RECEIVED] = tr("You send a private message");
|
groupNames["private.message.received"] = tr("You send a private message");
|
||||||
|
|
||||||
groupNames[EventType.CLIENT_VIEW_ENTER] = tr("A client enters your view");
|
groupNames["client.view.enter"] = tr("A client enters your view");
|
||||||
groupNames[EventType.CLIENT_VIEW_ENTER_OWN_CHANNEL] = tr("A client enters your view and your channel");
|
groupNames["client.view.enter.own.channel"] = tr("A client enters your view and your channel");
|
||||||
|
|
||||||
groupNames[EventType.CLIENT_VIEW_MOVE] = tr("A client switches/gets moved/kicked");
|
groupNames["client.view.move"] = tr("A client switches/gets moved/kicked");
|
||||||
groupNames[EventType.CLIENT_VIEW_MOVE_OWN_CHANNEL] = tr("A client switches/gets moved/kicked in to/out of your channel");
|
groupNames["client.view.move.own.channel"] = tr("A client switches/gets moved/kicked in to/out of your channel");
|
||||||
groupNames[EventType.CLIENT_VIEW_MOVE_OWN] = tr("You've been moved or kicked");
|
groupNames["client.view.move.own"] = tr("You've been moved or kicked");
|
||||||
|
|
||||||
groupNames[EventType.CLIENT_VIEW_LEAVE] = tr("A client leaves/disconnects of your view");
|
groupNames["client.view.leave"] = tr("A client leaves/disconnects of your view");
|
||||||
groupNames[EventType.CLIENT_VIEW_LEAVE_OWN_CHANNEL] = tr("A client leaves/disconnects of your channel");
|
groupNames["client.view.leave.own.channel"] = tr("A client leaves/disconnects of your channel");
|
||||||
|
|
||||||
groupNames[EventType.GLOBAL_MESSAGE] = tr("A server message has been send");
|
groupNames["global.message"] = tr("A server message has been send");
|
||||||
groupNames[EventType.SERVER_CLOSED] = tr("The server has been closed");
|
groupNames["server.closed"] = tr("The server has been closed");
|
||||||
groupNames[EventType.SERVER_BANNED] = tr("You've been banned from the server");
|
groupNames["server.banned"] = tr("You've been banned from the server");
|
||||||
|
|
||||||
groupNames[EventType.CONNECTION_BEGIN] = tr("You're connecting to a server");
|
groupNames["connection.begin"] = tr("You're connecting to a server");
|
||||||
groupNames[EventType.CONNECTION_CONNECTED] = tr("You've successfully connected to the server");
|
groupNames["connection.connected"] = tr("You've successfully connected to the server");
|
||||||
groupNames[EventType.CONNECTION_FAILED] = tr("You're connect attempt failed");
|
groupNames["connection.failed"] = tr("You're connect attempt failed");
|
||||||
|
|
||||||
function initializeController(events: Registry<NotificationSettingsEvents>) {
|
function initializeController(events: Registry<NotificationSettingsEvents>) {
|
||||||
let filter = undefined;
|
let filter = undefined;
|
||||||
|
|
|
@ -258,6 +258,8 @@ class ChannelTreeController {
|
||||||
this.sendChannelInfo(event.newChannel);
|
this.sendChannelInfo(event.newChannel);
|
||||||
this.sendChannelStatusIcon(event.newChannel);
|
this.sendChannelStatusIcon(event.newChannel);
|
||||||
this.sendChannelTreeEntries();
|
this.sendChannelTreeEntries();
|
||||||
|
|
||||||
|
this.sendClientTalkStatus(event.client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler<ChannelTreeEvents>("notify_selected_entry_changed")
|
@EventHandler<ChannelTreeEvents>("notify_selected_entry_changed")
|
||||||
|
|
|
@ -14,7 +14,6 @@ import * as log from "tc-shared/log";
|
||||||
import {LogCategory, logDebug, logError, logTrace} from "tc-shared/log";
|
import {LogCategory, logDebug, logError, logTrace} from "tc-shared/log";
|
||||||
import {Regex} from "tc-shared/ui/modal/ModalConnect";
|
import {Regex} from "tc-shared/ui/modal/ModalConnect";
|
||||||
import {AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
|
import {AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
import {EventType} from "tc-shared/ui/frames/log/Definitions";
|
|
||||||
import {WrappedWebSocket} from "tc-backend/web/connection/WrappedWebSocket";
|
import {WrappedWebSocket} from "tc-backend/web/connection/WrappedWebSocket";
|
||||||
import {AbstractVoiceConnection} from "tc-shared/connection/VoiceConnection";
|
import {AbstractVoiceConnection} from "tc-shared/connection/VoiceConnection";
|
||||||
import {parseCommand} from "tc-backend/web/connection/CommandParser";
|
import {parseCommand} from "tc-backend/web/connection/CommandParser";
|
||||||
|
@ -27,7 +26,6 @@ import {ServerFeature} from "tc-shared/connection/ServerFeatures";
|
||||||
import {RTCConnection} from "tc-shared/connection/rtc/Connection";
|
import {RTCConnection} from "tc-shared/connection/rtc/Connection";
|
||||||
import {RtpVideoConnection} from "tc-shared/connection/rtc/video/Connection";
|
import {RtpVideoConnection} from "tc-shared/connection/rtc/video/Connection";
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
|
||||||
|
|
||||||
class ReturnListener<T> {
|
class ReturnListener<T> {
|
||||||
resolve: (value?: T | PromiseLike<T>) => void;
|
resolve: (value?: T | PromiseLike<T>) => void;
|
||||||
|
@ -286,7 +284,7 @@ export class ServerConnection extends AbstractServerConnection {
|
||||||
|
|
||||||
private startHandshake() {
|
private startHandshake() {
|
||||||
this.updateConnectionState(ConnectionState.INITIALISING);
|
this.updateConnectionState(ConnectionState.INITIALISING);
|
||||||
this.client.log.log(EventType.CONNECTION_LOGIN, {});
|
this.client.log.log("connection.login", {});
|
||||||
this.handshakeHandler.initialize();
|
this.handshakeHandler.initialize();
|
||||||
this.handshakeHandler.startHandshake();
|
this.handshakeHandler.startHandshake();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {ConnectionStatistics, ServerConnectionEvents} from "tc-shared/connection
|
||||||
import {ConnectionState} from "tc-shared/ConnectionHandler";
|
import {ConnectionState} from "tc-shared/ConnectionHandler";
|
||||||
import {VoiceBridge, VoicePacket, VoiceWhisperPacket} from "./bridge/VoiceBridge";
|
import {VoiceBridge, VoicePacket, VoiceWhisperPacket} from "./bridge/VoiceBridge";
|
||||||
import {NativeWebRTCVoiceBridge} from "./bridge/NativeWebRTCVoiceBridge";
|
import {NativeWebRTCVoiceBridge} from "./bridge/NativeWebRTCVoiceBridge";
|
||||||
import {EventType} from "tc-shared/ui/frames/log/Definitions";
|
|
||||||
import {
|
import {
|
||||||
kUnknownWhisperClientUniqueId,
|
kUnknownWhisperClientUniqueId,
|
||||||
WhisperSession,
|
WhisperSession,
|
||||||
|
@ -191,7 +190,7 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
||||||
}, payload)))
|
}, payload)))
|
||||||
};
|
};
|
||||||
this.voiceBridge.callbackDisconnect = () => {
|
this.voiceBridge.callbackDisconnect = () => {
|
||||||
this.connection.client.log.log(EventType.CONNECTION_VOICE_DROPPED, { });
|
this.connection.client.log.log("connection.voice.dropped", { });
|
||||||
if(!this.connectionLostModalOpen) {
|
if(!this.connectionLostModalOpen) {
|
||||||
this.connectionLostModalOpen = true;
|
this.connectionLostModalOpen = true;
|
||||||
const modal = createErrorModal(tr("Voice connection lost"), tr("Lost voice connection to the target server. Trying to reconnect..."));
|
const modal = createErrorModal(tr("Voice connection lost"), tr("Lost voice connection to the target server. Trying to reconnect..."));
|
||||||
|
@ -202,14 +201,14 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
||||||
this.executeVoiceBridgeReconnect();
|
this.executeVoiceBridgeReconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.client.log.log(EventType.CONNECTION_VOICE_CONNECT, { attemptCount: this.connectAttemptCounter });
|
this.connection.client.log.log("connection.voice.connect", { attemptCount: this.connectAttemptCounter });
|
||||||
this.setConnectionState(VoiceConnectionStatus.Connecting);
|
this.setConnectionState(VoiceConnectionStatus.Connecting);
|
||||||
this.voiceBridge.connect().then(result => {
|
this.voiceBridge.connect().then(result => {
|
||||||
if(result.type === "success") {
|
if(result.type === "success") {
|
||||||
this.lastConnectAttempt = 0;
|
this.lastConnectAttempt = 0;
|
||||||
this.connectAttemptCounter = 0;
|
this.connectAttemptCounter = 0;
|
||||||
|
|
||||||
this.connection.client.log.log(EventType.CONNECTION_VOICE_CONNECT_SUCCEEDED, { });
|
this.connection.client.log.log("connection.voice.connect.succeeded", { });
|
||||||
const currentInput = this.voiceRecorder()?.input;
|
const currentInput = this.voiceRecorder()?.input;
|
||||||
if(currentInput) {
|
if(currentInput) {
|
||||||
this.voiceBridge.setInput(currentInput).catch(error => {
|
this.voiceBridge.setInput(currentInput).catch(error => {
|
||||||
|
@ -226,7 +225,7 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
||||||
let doReconnect = result.allowReconnect && this.connectAttemptCounter < 5;
|
let doReconnect = result.allowReconnect && this.connectAttemptCounter < 5;
|
||||||
logWarn(LogCategory.VOICE, tr("Failed to setup voice bridge: %s. Reconnect: %o"), result.message, doReconnect);
|
logWarn(LogCategory.VOICE, tr("Failed to setup voice bridge: %s. Reconnect: %o"), result.message, doReconnect);
|
||||||
|
|
||||||
this.connection.client.log.log(EventType.CONNECTION_VOICE_CONNECT_FAILED, {
|
this.connection.client.log.log("connection.voice.connect.failed", {
|
||||||
reason: result.message,
|
reason: result.message,
|
||||||
reconnect_delay: doReconnect ? 1 : 0
|
reconnect_delay: doReconnect ? 1 : 0
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue