Reworked the channel tree events
This commit is contained in:
parent
999c3990b5
commit
5bdc99acb0
14 changed files with 266 additions and 178 deletions
|
@ -1,4 +1,8 @@
|
||||||
# Changelog:
|
# Changelog:
|
||||||
|
* **16.09.20**
|
||||||
|
- Updating group prefix/suffixes when the group naming mode changes
|
||||||
|
- Added an client talk power indicator
|
||||||
|
|
||||||
* **25.09.20**
|
* **25.09.20**
|
||||||
- Update the translation files
|
- Update the translation files
|
||||||
- Made the server tabs moveable
|
- Made the server tabs moveable
|
||||||
|
|
|
@ -197,6 +197,7 @@ export class ConnectionHandler {
|
||||||
this.serverConnection.getVoiceConnection().setWhisperSessionInitializer(this.initializeWhisperSession.bind(this));
|
this.serverConnection.getVoiceConnection().setWhisperSessionInitializer(this.initializeWhisperSession.bind(this));
|
||||||
|
|
||||||
this.serverFeatures = new ServerFeatures(this);
|
this.serverFeatures = new ServerFeatures(this);
|
||||||
|
this.groups = new GroupManager(this);
|
||||||
|
|
||||||
this.channelTree = new ChannelTree(this);
|
this.channelTree = new ChannelTree(this);
|
||||||
this.fileManager = new FileManager(this);
|
this.fileManager = new FileManager(this);
|
||||||
|
@ -209,7 +210,6 @@ export class ConnectionHandler {
|
||||||
this.sound = new SoundManager(this);
|
this.sound = new SoundManager(this);
|
||||||
this.hostbanner = new Hostbanner(this);
|
this.hostbanner = new Hostbanner(this);
|
||||||
|
|
||||||
this.groups = new GroupManager(this);
|
|
||||||
this._local_client = new LocalClientEntry(this);
|
this._local_client = new LocalClientEntry(this);
|
||||||
|
|
||||||
this.event_registry.register_handler(this);
|
this.event_registry.register_handler(this);
|
||||||
|
|
|
@ -315,7 +315,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
if(ignoreOrder) {
|
if(ignoreOrder) {
|
||||||
for(let ch of tree.channels) {
|
for(let ch of tree.channels) {
|
||||||
if(ch.properties.channel_order == channel.channelId) {
|
if(ch.properties.channel_order == channel.channelId) {
|
||||||
tree.moveChannel(ch, channel, channel.parent); //Corrent the order :)
|
tree.moveChannel(ch, channel, channel.parent, true); //Corrent the order :)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,7 +324,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
key: string,
|
key: string,
|
||||||
value: string
|
value: string
|
||||||
}[] = [];
|
}[] = [];
|
||||||
for(let key in json) {
|
for(let key of Object.keys(json)) {
|
||||||
if(key === "cid") continue;
|
if(key === "cid") continue;
|
||||||
if(key === "cpid") continue;
|
if(key === "cpid") continue;
|
||||||
if(key === "invokerid") continue;
|
if(key === "invokerid") continue;
|
||||||
|
@ -692,7 +692,7 @@ export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
tree.moveChannel(channel, prev, parent);
|
tree.moveChannel(channel, prev, parent, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNotifyChannelEdited(json) {
|
handleNotifyChannelEdited(json) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {LogCategory} from "./log";
|
||||||
import {guid} from "./crypto/uid";
|
import {guid} from "./crypto/uid";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {useEffect} from "react";
|
import {useEffect} from "react";
|
||||||
|
import {unstable_batchedUpdates} from "react-dom";
|
||||||
|
|
||||||
export interface Event<Events, T = keyof Events> {
|
export interface Event<Events, T = keyof Events> {
|
||||||
readonly type: T;
|
readonly type: T;
|
||||||
|
@ -39,6 +40,9 @@ export class Registry<Events extends { [key: string]: any } = { [key: string]: a
|
||||||
private debugPrefix = undefined;
|
private debugPrefix = undefined;
|
||||||
private warnUnhandledEvents = false;
|
private warnUnhandledEvents = false;
|
||||||
|
|
||||||
|
private pendingCallbacks: { type: any, data: any }[] = [];
|
||||||
|
private pendingCallbacksTimeout: number = 0;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.registryUuid = "evreg_data_" + guid();
|
this.registryUuid = "evreg_data_" + guid();
|
||||||
}
|
}
|
||||||
|
@ -200,11 +204,23 @@ export class Registry<Events extends { [key: string]: any } = { [key: string]: a
|
||||||
}
|
}
|
||||||
|
|
||||||
fire_async<T extends keyof Events>(event_type: T, data?: Events[T], callback?: () => void) {
|
fire_async<T extends keyof Events>(event_type: T, data?: Events[T], callback?: () => void) {
|
||||||
/* TODO: Optimize, bundle them */
|
if(!this.pendingCallbacksTimeout) {
|
||||||
setTimeout(() => {
|
this.pendingCallbacksTimeout = setTimeout(() => this.invokeAsyncCallbacks());
|
||||||
this.fire(event_type, data);
|
this.pendingCallbacks = [];
|
||||||
if(typeof callback === "function")
|
}
|
||||||
callback();
|
this.pendingCallbacks.push({ type: event_type, data: data });
|
||||||
|
}
|
||||||
|
|
||||||
|
private invokeAsyncCallbacks() {
|
||||||
|
const callbacks = this.pendingCallbacks;
|
||||||
|
this.pendingCallbacksTimeout = 0;
|
||||||
|
this.pendingCallbacks = undefined;
|
||||||
|
|
||||||
|
unstable_batchedUpdates(() => {
|
||||||
|
let index = callbacks.length;
|
||||||
|
while(index--) {
|
||||||
|
this.fire(callbacks[index].type, callbacks[index].data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,8 @@ export class GroupPermissionRequest {
|
||||||
promise: LaterPromise<PermissionValue[]>;
|
promise: LaterPromise<PermissionValue[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type GroupUpdate = { key: GroupProperty, group: Group, oldValue: any, newValue: any };
|
||||||
|
|
||||||
export interface GroupManagerEvents {
|
export interface GroupManagerEvents {
|
||||||
notify_reset: {},
|
notify_reset: {},
|
||||||
notify_groups_created: {
|
notify_groups_created: {
|
||||||
|
@ -42,7 +44,11 @@ export interface GroupManagerEvents {
|
||||||
notify_groups_deleted: {
|
notify_groups_deleted: {
|
||||||
groups: Group[],
|
groups: Group[],
|
||||||
cause: "list-update" | "reset" | "user-action"
|
cause: "list-update" | "reset" | "user-action"
|
||||||
}
|
},
|
||||||
|
notify_groups_updated: { updates: GroupUpdate[] },
|
||||||
|
|
||||||
|
/* will be fired when all server and channel groups have been received */
|
||||||
|
notify_groups_received: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GroupProperty = "name" | "icon" | "sort-id" | "save-db" | "name-mode";
|
export type GroupProperty = "name" | "icon" | "sort-id" | "save-db" | "name-mode";
|
||||||
|
@ -82,41 +88,43 @@ export class Group {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePropertiesFromGroupList(data: any) {
|
updatePropertiesFromGroupList(data: any) : GroupUpdate[] {
|
||||||
const updates: GroupProperty[] = [];
|
const updates = [] as GroupUpdate[];
|
||||||
|
|
||||||
if(this.name !== data["name"]) {
|
if(this.name !== data["name"]) {
|
||||||
|
updates.push({ key: "name", group: this, oldValue: this.name, newValue: data["name"] });
|
||||||
this.name = data["name"];
|
this.name = data["name"];
|
||||||
updates.push("name");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* icon */
|
/* icon */
|
||||||
let value = parseInt(data["iconid"]) >>> 0;
|
let value = parseInt(data["iconid"]) >>> 0;
|
||||||
if(value !== this.properties.iconid) {
|
if(value !== this.properties.iconid) {
|
||||||
|
updates.push({ key: "icon", group: this, oldValue: this.properties.iconid, newValue: value });
|
||||||
this.properties.iconid = value;
|
this.properties.iconid = value;
|
||||||
updates.push("icon");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value = parseInt(data["sortid"]);
|
value = parseInt(data["sortid"]);
|
||||||
if(value !== this.properties.sortid) {
|
if(value !== this.properties.sortid) {
|
||||||
|
updates.push({ key: "sort-id", group: this, oldValue: this.properties.sortid, newValue: value });
|
||||||
this.properties.sortid = value;
|
this.properties.sortid = value;
|
||||||
updates.push("sort-id");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let flag = parseInt(data["savedb"]) >= 1;
|
let flag = parseInt(data["savedb"]) >= 1;
|
||||||
if(flag !== this.properties.savedb) {
|
if(flag !== this.properties.savedb) {
|
||||||
|
updates.push({ key: "save-db", group: this, oldValue: this.properties.savedb, newValue: flag });
|
||||||
this.properties.savedb = flag;
|
this.properties.savedb = flag;
|
||||||
updates.push("save-db");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value = parseInt(data["namemode"]);
|
value = parseInt(data["namemode"]);
|
||||||
if(value !== this.properties.namemode) {
|
if(value !== this.properties.namemode) {
|
||||||
|
updates.push({ key: "name-mode", group: this, oldValue: this.properties.namemode, newValue: flag });
|
||||||
this.properties.namemode = value;
|
this.properties.namemode = value;
|
||||||
updates.push("name-mode");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(updates.length > 0)
|
if(updates.length > 0) {
|
||||||
this.events.fire("notify_properties_updated", { updated_properties: updates });
|
this.events.fire("notify_properties_updated", { updated_properties: updates.map(e => e.key) });
|
||||||
|
}
|
||||||
|
return updates;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +155,7 @@ export class GroupManager extends AbstractCommandHandler {
|
||||||
serverGroups: Group[] = [];
|
serverGroups: Group[] = [];
|
||||||
channelGroups: Group[] = [];
|
channelGroups: Group[] = [];
|
||||||
|
|
||||||
|
private allGroupsReceived = false;
|
||||||
private readonly connectionStateListener;
|
private readonly connectionStateListener;
|
||||||
private groupPermissionRequests: GroupPermissionRequest[] = [];
|
private groupPermissionRequests: GroupPermissionRequest[] = [];
|
||||||
|
|
||||||
|
@ -155,8 +164,9 @@ export class GroupManager extends AbstractCommandHandler {
|
||||||
this.connectionHandler = client;
|
this.connectionHandler = client;
|
||||||
|
|
||||||
this.connectionStateListener = (event: ConnectionEvents["notify_connection_state_changed"]) => {
|
this.connectionStateListener = (event: ConnectionEvents["notify_connection_state_changed"]) => {
|
||||||
if(event.new_state === ConnectionState.DISCONNECTING || event.new_state === ConnectionState.UNCONNECTED || event.new_state === ConnectionState.CONNECTING)
|
if(event.new_state === ConnectionState.DISCONNECTING || event.new_state === ConnectionState.UNCONNECTED || event.new_state === ConnectionState.CONNECTING) {
|
||||||
this.reset();
|
this.reset();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
client.serverConnection.command_handler_boss().register_handler(this);
|
client.serverConnection.command_handler_boss().register_handler(this);
|
||||||
|
@ -174,15 +184,18 @@ export class GroupManager extends AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
if(this.serverGroups.length === 0 && this.channelGroups.length === 0)
|
if(this.serverGroups.length === 0 && this.channelGroups.length === 0) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
log.debug(LogCategory.PERMISSIONS, tr("Resetting server/channel groups"));
|
log.debug(LogCategory.PERMISSIONS, tr("Resetting server/channel groups"));
|
||||||
this.serverGroups = [];
|
this.serverGroups = [];
|
||||||
this.channelGroups = [];
|
this.channelGroups = [];
|
||||||
|
this.allGroupsReceived = false;
|
||||||
|
|
||||||
for(const permission of this.groupPermissionRequests)
|
for(const permission of this.groupPermissionRequests) {
|
||||||
permission.promise.rejected(tr("Group manager reset"));
|
permission.promise.rejected(tr("Group manager reset"));
|
||||||
|
}
|
||||||
this.groupPermissionRequests = [];
|
this.groupPermissionRequests = [];
|
||||||
this.events.fire("notify_reset");
|
this.events.fire("notify_reset");
|
||||||
}
|
}
|
||||||
|
@ -215,9 +228,11 @@ export class GroupManager extends AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
findServerGroup(id: number) : Group | undefined {
|
findServerGroup(id: number) : Group | undefined {
|
||||||
for(let group of this.serverGroups)
|
for(let group of this.serverGroups) {
|
||||||
if(group.id === id)
|
if(group.id === id) {
|
||||||
return group;
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,6 +247,7 @@ export class GroupManager extends AbstractCommandHandler {
|
||||||
let groupList = target == GroupTarget.SERVER ? this.serverGroups : this.channelGroups;
|
let groupList = target == GroupTarget.SERVER ? this.serverGroups : this.channelGroups;
|
||||||
const deleteGroups = groupList.slice(0);
|
const deleteGroups = groupList.slice(0);
|
||||||
const newGroups: Group[] = [];
|
const newGroups: Group[] = [];
|
||||||
|
const groupUpdates: GroupUpdate[] = [];
|
||||||
|
|
||||||
const isInitialList = groupList.length === 0;
|
const isInitialList = groupList.length === 0;
|
||||||
for(const groupData of json) {
|
for(const groupData of json) {
|
||||||
|
@ -253,14 +269,15 @@ export class GroupManager extends AbstractCommandHandler {
|
||||||
group = new Group(this, groupId, target, type, groupData["name"]);
|
group = new Group(this, groupId, target, type, groupData["name"]);
|
||||||
groupList.push(group);
|
groupList.push(group);
|
||||||
newGroups.push(group);
|
newGroups.push(group);
|
||||||
|
group.updatePropertiesFromGroupList(groupData);
|
||||||
} else {
|
} else {
|
||||||
group = deleteGroups.splice(groupIndex, 1)[0];
|
group = deleteGroups.splice(groupIndex, 1)[0];
|
||||||
|
groupUpdates.push(...group.updatePropertiesFromGroupList(groupData));
|
||||||
}
|
}
|
||||||
|
|
||||||
group.requiredMemberRemovePower = parseInt(groupData["n_member_removep"]);
|
group.requiredMemberRemovePower = parseInt(groupData["n_member_removep"]);
|
||||||
group.requiredMemberAddPower = parseInt(groupData["n_member_addp"]);
|
group.requiredMemberAddPower = parseInt(groupData["n_member_addp"]);
|
||||||
group.requiredModifyPower = parseInt(groupData["n_modifyp"]);
|
group.requiredModifyPower = parseInt(groupData["n_modifyp"]);
|
||||||
group.updatePropertiesFromGroupList(groupData);
|
|
||||||
group.events.fire("notify_needed_powers_updated");
|
group.events.fire("notify_needed_powers_updated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +293,13 @@ export class GroupManager extends AbstractCommandHandler {
|
||||||
if(deleteGroups.length !== 0) {
|
if(deleteGroups.length !== 0) {
|
||||||
this.events.fire("notify_groups_deleted", { groups: deleteGroups, cause: "list-update" });
|
this.events.fire("notify_groups_deleted", { groups: deleteGroups, cause: "list-update" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.events.fire("notify_groups_updated", { updates: groupUpdates });
|
||||||
|
|
||||||
|
if(!this.allGroupsReceived && this.serverGroups.length > 0 && this.channelGroups.length > 0) {
|
||||||
|
this.allGroupsReceived = true;
|
||||||
|
this.events.fire("notify_groups_received");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request_permissions(group: Group) : Promise<PermissionValue[]> {
|
request_permissions(group: Group) : Promise<PermissionValue[]> {
|
||||||
|
|
|
@ -15,14 +15,13 @@ import {openChannelInfo} from "../ui/modal/ModalChannelInfo";
|
||||||
import {createChannelModal} from "../ui/modal/ModalCreateChannel";
|
import {createChannelModal} from "../ui/modal/ModalCreateChannel";
|
||||||
import {formatMessage} from "../ui/frames/chat";
|
import {formatMessage} from "../ui/frames/chat";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {Registry} from "../events";
|
import {Registry} from "../events";
|
||||||
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
||||||
import {ChannelEntryView as ChannelEntryView} from "../ui/tree/Channel";
|
|
||||||
import {spawnFileTransferModal} from "../ui/modal/transfer/ModalFileTransfer";
|
import {spawnFileTransferModal} from "../ui/modal/transfer/ModalFileTransfer";
|
||||||
import {ViewReasonId} from "../ConnectionHandler";
|
import {ViewReasonId} from "../ConnectionHandler";
|
||||||
import {EventChannelData} from "../ui/frames/log/Definitions";
|
import {EventChannelData} from "../ui/frames/log/Definitions";
|
||||||
import {ErrorCode} from "../connection/ErrorCode";
|
import {ErrorCode} from "../connection/ErrorCode";
|
||||||
|
import {ClientIcon} from "svg-sprites/client-icons";
|
||||||
|
|
||||||
export enum ChannelType {
|
export enum ChannelType {
|
||||||
PERMANENT,
|
PERMANENT,
|
||||||
|
@ -94,32 +93,28 @@ export interface ChannelEvents extends ChannelTreeEntryEvents {
|
||||||
},
|
},
|
||||||
notify_collapsed_state_changed: {
|
notify_collapsed_state_changed: {
|
||||||
collapsed: boolean
|
collapsed: boolean
|
||||||
},
|
}
|
||||||
|
|
||||||
notify_children_changed: {},
|
|
||||||
notify_clients_changed: {}, /* will also be fired when clients haven been reordered */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ParsedChannelName {
|
export class ParsedChannelName {
|
||||||
readonly original_name: string;
|
readonly originalName: string;
|
||||||
alignment: "center" | "right" | "left" | "normal";
|
alignment: "center" | "right" | "left" | "normal" | "repetitive";
|
||||||
repetitive: boolean;
|
|
||||||
text: string; /* does not contain any alignment codes */
|
text: string; /* does not contain any alignment codes */
|
||||||
|
|
||||||
constructor(name: string, has_parent_channel: boolean) {
|
constructor(name: string, hasParentChannel: boolean) {
|
||||||
this.original_name = name;
|
this.originalName = name;
|
||||||
this.parse(has_parent_channel);
|
this.parse(hasParentChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private parse(has_parent_channel: boolean) {
|
private parse(has_parent_channel: boolean) {
|
||||||
this.alignment = "normal";
|
this.alignment = "normal";
|
||||||
|
|
||||||
parse_type:
|
parse_type:
|
||||||
if(!has_parent_channel && this.original_name.charAt(0) == '[') {
|
if(!has_parent_channel && this.originalName.charAt(0) == '[') {
|
||||||
let end = this.original_name.indexOf(']');
|
let end = this.originalName.indexOf(']');
|
||||||
if(end === -1) break parse_type;
|
if(end === -1) break parse_type;
|
||||||
|
|
||||||
let options = this.original_name.substr(1, end - 1);
|
let options = this.originalName.substr(1, end - 1);
|
||||||
if(options.indexOf("spacer") === -1) break parse_type;
|
if(options.indexOf("spacer") === -1) break parse_type;
|
||||||
options = options.substr(0, options.indexOf("spacer"));
|
options = options.substr(0, options.indexOf("spacer"));
|
||||||
|
|
||||||
|
@ -139,17 +134,16 @@ export class ParsedChannelName {
|
||||||
this.alignment = "center";
|
this.alignment = "center";
|
||||||
break;
|
break;
|
||||||
case "*":
|
case "*":
|
||||||
this.alignment = "center";
|
this.alignment = "repetitive";
|
||||||
this.repetitive = true;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break parse_type;
|
break parse_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.text = this.original_name.substr(end + 1);
|
this.text = this.originalName.substr(end + 1);
|
||||||
}
|
}
|
||||||
if(!this.text && this.alignment === "normal")
|
if(!this.text && this.alignment === "normal")
|
||||||
this.text = this.original_name;
|
this.text = this.originalName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +158,6 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
child_channel_head?: ChannelEntry;
|
child_channel_head?: ChannelEntry;
|
||||||
|
|
||||||
readonly events: Registry<ChannelEvents>;
|
readonly events: Registry<ChannelEvents>;
|
||||||
readonly view: React.RefObject<ChannelEntryView>;
|
|
||||||
|
|
||||||
parsed_channel_name: ParsedChannelName;
|
parsed_channel_name: ParsedChannelName;
|
||||||
|
|
||||||
|
@ -184,13 +177,12 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
private _subscribe_mode: ChannelSubscribeMode;
|
private _subscribe_mode: ChannelSubscribeMode;
|
||||||
|
|
||||||
private client_list: ClientEntry[] = []; /* this list is sorted correctly! */
|
private client_list: ClientEntry[] = []; /* this list is sorted correctly! */
|
||||||
private readonly client_property_listener;
|
private readonly clientPropertyChangedListener;
|
||||||
|
|
||||||
constructor(channelId, channelName) {
|
constructor(channelId, channelName) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.events = new Registry<ChannelEvents>();
|
this.events = new Registry<ChannelEvents>();
|
||||||
this.view = React.createRef<ChannelEntryView>();
|
|
||||||
|
|
||||||
this.properties = new ChannelProperties();
|
this.properties = new ChannelProperties();
|
||||||
this.channelId = channelId;
|
this.channelId = channelId;
|
||||||
|
@ -199,9 +191,10 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
|
|
||||||
this.parsed_channel_name = new ParsedChannelName("undefined", false);
|
this.parsed_channel_name = new ParsedChannelName("undefined", false);
|
||||||
|
|
||||||
this.client_property_listener = (event: ClientEvents["notify_properties_updated"]) => {
|
this.clientPropertyChangedListener = (event: ClientEvents["notify_properties_updated"]) => {
|
||||||
if(typeof event.updated_properties.client_nickname !== "undefined" || typeof event.updated_properties.client_talk_power !== "undefined")
|
if("client_nickname" in event.updated_properties || "client_talk_power" in event.updated_properties) {
|
||||||
this.reorderClientList(true);
|
this.reorderClientList(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.events.on("notify_properties_updated", event => {
|
this.events.on("notify_properties_updated", event => {
|
||||||
|
@ -262,20 +255,16 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
}
|
}
|
||||||
|
|
||||||
registerClient(client: ClientEntry) {
|
registerClient(client: ClientEntry) {
|
||||||
client.events.on("notify_properties_updated", this.client_property_listener);
|
client.events.on("notify_properties_updated", this.clientPropertyChangedListener);
|
||||||
this.client_list.push(client);
|
this.client_list.push(client);
|
||||||
this.reorderClientList(false);
|
this.reorderClientList(false);
|
||||||
|
|
||||||
this.events.fire("notify_clients_changed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterClient(client: ClientEntry, no_event?: boolean) {
|
unregisterClient(client: ClientEntry, noEvent?: boolean) {
|
||||||
client.events.off("notify_properties_updated", this.client_property_listener);
|
client.events.off("notify_properties_updated", this.clientPropertyChangedListener);
|
||||||
if(!this.client_list.remove(client))
|
if(!this.client_list.remove(client)) {
|
||||||
log.warn(LogCategory.CHANNEL, tr("Unregistered unknown client from channel %s"), this.channelName());
|
log.warn(LogCategory.CHANNEL, tr("Unregistered unknown client from channel %s"), this.channelName());
|
||||||
|
}
|
||||||
if(!no_event)
|
|
||||||
this.events.fire("notify_clients_changed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private reorderClientList(fire_event: boolean) {
|
private reorderClientList(fire_event: boolean) {
|
||||||
|
@ -299,7 +288,7 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
/* only fire if really something has changed ;) */
|
/* only fire if really something has changed ;) */
|
||||||
for(let index = 0; index < this.client_list.length; index++) {
|
for(let index = 0; index < this.client_list.length; index++) {
|
||||||
if(this.client_list[index] !== original_list[index]) {
|
if(this.client_list[index] !== original_list[index]) {
|
||||||
this.events.fire("notify_clients_changed");
|
this.channelTree.events.fire("notify_channel_client_order_changed", { channel: this });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,7 +322,7 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
}, result);
|
}, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
clients_ordered() : ClientEntry[] {
|
channelClientsOrdered() : ClientEntry[] {
|
||||||
return this.client_list;
|
return this.client_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,7 +584,7 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
info_update = true;
|
info_update = true;
|
||||||
} else if(key == "channel_order") {
|
} else if(key == "channel_order") {
|
||||||
let order = this.channelTree.findChannel(this.properties.channel_order);
|
let order = this.channelTree.findChannel(this.properties.channel_order);
|
||||||
this.channelTree.moveChannel(this, order, this.parent);
|
this.channelTree.moveChannel(this, order, this.parent, true);
|
||||||
} else if(key === "channel_icon_id") {
|
} else if(key === "channel_icon_id") {
|
||||||
this.properties.channel_icon_id = variable.value as any >>> 0; /* unsigned 32 bit number! */
|
this.properties.channel_icon_id = variable.value as any >>> 0; /* unsigned 32 bit number! */
|
||||||
} else if(key == "channel_description") {
|
} else if(key == "channel_description") {
|
||||||
|
@ -746,7 +735,6 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
|
|
||||||
this._flag_collapsed = flag;
|
this._flag_collapsed = flag;
|
||||||
this.events.fire("notify_collapsed_state_changed", { collapsed: flag });
|
this.events.fire("notify_collapsed_state_changed", { collapsed: flag });
|
||||||
this.view.current?.forceUpdate();
|
|
||||||
this.channelTree.client.settings.changeServer(Settings.FN_SERVER_CHANNEL_COLLAPSED(this.channelId), flag);
|
this.channelTree.client.settings.changeServer(Settings.FN_SERVER_CHANNEL_COLLAPSED(this.channelId), flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -780,4 +768,21 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
||||||
channel_id: this.channelId
|
channel_id: this.channelId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStatusIcon() : ClientIcon | undefined {
|
||||||
|
if(this.parsed_channel_name.alignment !== "normal") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscribed = this.flag_subscribed;
|
||||||
|
if (this.properties.channel_flag_password === true && !this.cached_password()) {
|
||||||
|
return subscribed ? ClientIcon.ChannelYellowSubscribed : ClientIcon.ChannelYellow;
|
||||||
|
} else if (!this.properties.channel_flag_maxclients_unlimited && this.clients().length >= this.properties.channel_maxclients) {
|
||||||
|
return subscribed ? ClientIcon.ChannelRedSubscribed : ClientIcon.ChannelRed;
|
||||||
|
} else if (!this.properties.channel_flag_maxfamilyclients_unlimited && this.properties.channel_maxfamilyclients >= 0 && this.clients(true).length >= this.properties.channel_maxfamilyclients) {
|
||||||
|
return subscribed ? ClientIcon.ChannelRedSubscribed : ClientIcon.ChannelRed;
|
||||||
|
} else {
|
||||||
|
return subscribed ? ClientIcon.ChannelGreenSubscribed : ClientIcon.ChannelGreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
||||||
import {MenuEntryType} from "tc-shared/ui/elements/ContextMenu";
|
import {MenuEntryType} from "tc-shared/ui/elements/ContextMenu";
|
||||||
import * as log from "tc-shared/log";
|
import * as log from "tc-shared/log";
|
||||||
import {LogCategory} from "tc-shared/log";
|
import {LogCategory, logWarn} from "tc-shared/log";
|
||||||
import {Settings, settings} from "tc-shared/settings";
|
import {Settings, settings} from "tc-shared/settings";
|
||||||
import {PermissionType} from "tc-shared/permission/PermissionType";
|
import {PermissionType} from "tc-shared/permission/PermissionType";
|
||||||
import {KeyCode, SpecialKey} from "tc-shared/PPTListener";
|
import {KeyCode, SpecialKey} from "tc-shared/PPTListener";
|
||||||
|
@ -14,7 +14,6 @@ import {ChannelTreeEntry} from "./ChannelTreeEntry";
|
||||||
import {ConnectionHandler, ViewReasonId} from "tc-shared/ConnectionHandler";
|
import {ConnectionHandler, ViewReasonId} from "tc-shared/ConnectionHandler";
|
||||||
import {createChannelModal} from "tc-shared/ui/modal/ModalCreateChannel";
|
import {createChannelModal} from "tc-shared/ui/modal/ModalCreateChannel";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {ChannelTreeView} from "tc-shared/ui/tree/View";
|
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ppt from "tc-backend/ppt";
|
import * as ppt from "tc-backend/ppt";
|
||||||
|
@ -25,8 +24,8 @@ import {spawnBanClient} from "tc-shared/ui/modal/ModalBanClient";
|
||||||
import {formatMessage} from "tc-shared/ui/frames/chat";
|
import {formatMessage} from "tc-shared/ui/frames/chat";
|
||||||
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
|
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
|
||||||
import {tra} from "tc-shared/i18n/localize";
|
import {tra} from "tc-shared/i18n/localize";
|
||||||
import {TreeEntryMove} from "tc-shared/ui/tree/TreeEntryMove";
|
|
||||||
import {EventType} from "tc-shared/ui/frames/log/Definitions";
|
import {EventType} from "tc-shared/ui/frames/log/Definitions";
|
||||||
|
import {renderChannelTree} from "tc-shared/ui/tree/Controller";
|
||||||
|
|
||||||
export interface ChannelTreeEvents {
|
export interface ChannelTreeEvents {
|
||||||
action_select_entries: {
|
action_select_entries: {
|
||||||
|
@ -40,25 +39,19 @@ export interface ChannelTreeEvents {
|
||||||
mode: "auto" | "exclusive" | "append" | "remove";
|
mode: "auto" | "exclusive" | "append" | "remove";
|
||||||
},
|
},
|
||||||
|
|
||||||
notify_selection_changed: {},
|
/* general tree notified */
|
||||||
notify_root_channel_changed: {},
|
|
||||||
notify_tree_reset: {},
|
notify_tree_reset: {},
|
||||||
|
notify_selection_changed: {},
|
||||||
notify_query_view_state_changed: { queries_shown: boolean },
|
notify_query_view_state_changed: { queries_shown: boolean },
|
||||||
|
|
||||||
notify_entry_move_begin: {},
|
notify_entry_move_begin: {},
|
||||||
notify_entry_move_end: {},
|
notify_entry_move_end: {},
|
||||||
|
|
||||||
notify_client_enter_view: {
|
/* channel tree events */
|
||||||
client: ClientEntry,
|
notify_channel_created: { channel: ChannelEntry },
|
||||||
reason: ViewReasonId,
|
notify_channel_moved: { channel: ChannelEntry },
|
||||||
isServerJoin: boolean
|
notify_channel_deleted: { channel: ChannelEntry },
|
||||||
},
|
notify_channel_client_order_changed: { channel: ChannelEntry },
|
||||||
notify_client_leave_view: {
|
|
||||||
client: ClientEntry,
|
|
||||||
reason: ViewReasonId,
|
|
||||||
message?: string,
|
|
||||||
isServerLeave: boolean
|
|
||||||
},
|
|
||||||
|
|
||||||
notify_channel_updated: {
|
notify_channel_updated: {
|
||||||
channel: ChannelEntry,
|
channel: ChannelEntry,
|
||||||
|
@ -67,6 +60,26 @@ export interface ChannelTreeEvents {
|
||||||
},
|
},
|
||||||
|
|
||||||
notify_channel_list_received: {}
|
notify_channel_list_received: {}
|
||||||
|
|
||||||
|
/* client events */
|
||||||
|
notify_client_enter_view: {
|
||||||
|
client: ClientEntry,
|
||||||
|
reason: ViewReasonId,
|
||||||
|
isServerJoin: boolean,
|
||||||
|
targetChannel: ChannelEntry
|
||||||
|
},
|
||||||
|
notify_client_moved: {
|
||||||
|
client: ClientEntry,
|
||||||
|
oldChannel: ChannelEntry,
|
||||||
|
newChannel: ChannelEntry
|
||||||
|
}
|
||||||
|
notify_client_leave_view: {
|
||||||
|
client: ClientEntry,
|
||||||
|
reason: ViewReasonId,
|
||||||
|
message?: string,
|
||||||
|
isServerLeave: boolean,
|
||||||
|
sourceChannel: ChannelEntry
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChannelTreeEntrySelect {
|
export class ChannelTreeEntrySelect {
|
||||||
|
@ -198,8 +211,11 @@ export class ChannelTreeEntrySelect {
|
||||||
console.warn("Received entry select event with unknown mode: %s", event.mode);
|
console.warn("Received entry select event with unknown mode: %s", event.mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO!
|
||||||
if(this.selected_entries.length === 1)
|
if(this.selected_entries.length === 1)
|
||||||
this.handle.view.current?.scrollEntryInView(this.selected_entries[0] as any);
|
this.handle.view.current?.scrollEntryInView(this.selected_entries[0] as any);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,8 +228,8 @@ export class ChannelTree {
|
||||||
channels: ChannelEntry[] = [];
|
channels: ChannelEntry[] = [];
|
||||||
clients: ClientEntry[] = [];
|
clients: ClientEntry[] = [];
|
||||||
|
|
||||||
readonly view: React.RefObject<ChannelTreeView>;
|
//readonly view: React.RefObject<ChannelTreeView>;
|
||||||
readonly view_move: React.RefObject<TreeEntryMove>;
|
//readonly view_move: React.RefObject<TreeEntryMove>;
|
||||||
readonly selection: ChannelTreeEntrySelect;
|
readonly selection: ChannelTreeEntrySelect;
|
||||||
|
|
||||||
private readonly _tag_container: JQuery;
|
private readonly _tag_container: JQuery;
|
||||||
|
@ -231,33 +247,38 @@ export class ChannelTree {
|
||||||
this.events.enableDebug("channel-tree");
|
this.events.enableDebug("channel-tree");
|
||||||
|
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.view = React.createRef();
|
|
||||||
this.view_move = React.createRef();
|
|
||||||
|
|
||||||
this.server = new ServerEntry(this, "undefined", undefined);
|
this.server = new ServerEntry(this, "undefined", undefined);
|
||||||
this.selection = new ChannelTreeEntrySelect(this);
|
this.selection = new ChannelTreeEntrySelect(this);
|
||||||
|
|
||||||
this._tag_container = $.spawn("div").addClass("channel-tree-container");
|
this._tag_container = $.spawn("div").addClass("channel-tree-container");
|
||||||
|
renderChannelTree(this, this._tag_container[0]);
|
||||||
|
/*
|
||||||
ReactDOM.render([
|
ReactDOM.render([
|
||||||
<ChannelTreeView key={"tree"} onMoveStart={(a,b) => this.onChannelEntryMove(a, b)} tree={this} ref={this.view} />,
|
<ChannelTreeView key={"tree"} onMoveStart={(a,b) => this.onChannelEntryMove(a, b)} tree={this} ref={this.view} />,
|
||||||
<TreeEntryMove key={"move"} onMoveEnd={(point) => this.onMoveEnd(point.x, point.y)} ref={this.view_move} />
|
<TreeEntryMove key={"move"} onMoveEnd={(point) => this.onMoveEnd(point.x, point.y)} ref={this.view_move} />
|
||||||
], this._tag_container[0]);
|
], this._tag_container[0]);
|
||||||
|
*/
|
||||||
|
|
||||||
this.reset();
|
this.reset();
|
||||||
|
|
||||||
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
||||||
|
/*
|
||||||
|
TODO: Move this into the channel tree renderer
|
||||||
this._tag_container.on("contextmenu", (event) => {
|
this._tag_container.on("contextmenu", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const entry = this.view.current?.getEntryFromPoint(event.pageX, event.pageY);
|
const entry = this.view.current?.getEntryFromPoint(event.pageX, event.pageY);
|
||||||
if(entry) {
|
if(entry) {
|
||||||
if(this.selection.is_multi_select())
|
if(this.selection.is_multi_select()) {
|
||||||
this.open_multiselect_context_menu(this.selection.selected_entries, event.pageX, event.pageY);
|
this.open_multiselect_context_menu(this.selection.selected_entries, event.pageX, event.pageY);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.selection.clear_selection();
|
this.selection.clear_selection();
|
||||||
this.showContextMenu(event.pageX, event.pageY);
|
this.showContextMenu(event.pageX, event.pageY);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
this._listener_document_key = event => this.handle_key_press(event);
|
this._listener_document_key = event => this.handle_key_press(event);
|
||||||
|
@ -293,6 +314,25 @@ export class ChannelTree {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findEntryId(entryId: number) : ServerEntry | ChannelEntry | ClientEntry {
|
||||||
|
/* TODO: Build a cache and don't iterate over everything */
|
||||||
|
if(this.server.uniqueEntryId === entryId) {
|
||||||
|
return this.server;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelIndex = this.channels.findIndex(channel => channel.uniqueEntryId === entryId);
|
||||||
|
if(channelIndex !== -1) {
|
||||||
|
return this.channels[channelIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientIndex = this.clients.findIndex(client => client.uniqueEntryId === entryId);
|
||||||
|
if(clientIndex !== -1) {
|
||||||
|
return this.clients[clientIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
ReactDOM.unmountComponentAtNode(this._tag_container[0]);
|
ReactDOM.unmountComponentAtNode(this._tag_container[0]);
|
||||||
|
|
||||||
|
@ -320,7 +360,6 @@ export class ChannelTree {
|
||||||
this.server.reset();
|
this.server.reset();
|
||||||
this.server.remote_address = Object.assign({}, address);
|
this.server.remote_address = Object.assign({}, address);
|
||||||
this.server.properties.virtualserver_name = serverName;
|
this.server.properties.virtualserver_name = serverName;
|
||||||
this.events.fire("notify_root_channel_changed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rootChannel() : ChannelEntry[] {
|
rootChannel() : ChannelEntry[] {
|
||||||
|
@ -338,19 +377,20 @@ export class ChannelTree {
|
||||||
|
|
||||||
batch_updates(BatchUpdateType.CHANNEL_TREE);
|
batch_updates(BatchUpdateType.CHANNEL_TREE);
|
||||||
try {
|
try {
|
||||||
if(!this.channels.remove(channel))
|
if(!this.channels.remove(channel)) {
|
||||||
log.warn(LogCategory.CHANNEL, tr("Deleting an unknown channel!"));
|
log.warn(LogCategory.CHANNEL, tr("Deleting an unknown channel!"));
|
||||||
|
}
|
||||||
|
|
||||||
channel.children(false).forEach(e => this.deleteChannel(e));
|
channel.children(false).forEach(e => this.deleteChannel(e));
|
||||||
if(channel.clients(false).length !== 0) {
|
if(channel.clients(false).length !== 0) {
|
||||||
log.warn(LogCategory.CHANNEL, tr("Deleting a non empty channel! This could cause some errors."));
|
log.warn(LogCategory.CHANNEL, tr("Deleting a non empty channel! This could cause some errors."));
|
||||||
for(const client of channel.clients(false))
|
for(const client of channel.clients(false)) {
|
||||||
this.deleteClient(client, { reason: ViewReasonId.VREASON_SYSTEM, serverLeave: false });
|
this.deleteClient(client, { reason: ViewReasonId.VREASON_SYSTEM, serverLeave: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const is_root_tree = !channel.parent;
|
|
||||||
this.unregisterChannelFromTree(channel);
|
this.unregisterChannelFromTree(channel);
|
||||||
if(is_root_tree) this.events.fire("notify_root_channel_changed");
|
this.events.fire("notify_channel_deleted", { channel: channel });
|
||||||
} finally {
|
} finally {
|
||||||
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
|
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
|
||||||
}
|
}
|
||||||
|
@ -360,7 +400,8 @@ export class ChannelTree {
|
||||||
channel.channelTree = this;
|
channel.channelTree = this;
|
||||||
this.channels.push(channel);
|
this.channels.push(channel);
|
||||||
|
|
||||||
this.moveChannel(channel, previous, parent);
|
this.moveChannel(channel, previous, parent, false);
|
||||||
|
this.events.fire("notify_channel_created", { channel: channel });
|
||||||
}
|
}
|
||||||
|
|
||||||
findChannel(channelId: number) : ChannelEntry | undefined {
|
findChannel(channelId: number) : ChannelEntry | undefined {
|
||||||
|
@ -379,63 +420,56 @@ export class ChannelTree {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unregisterChannelFromTree(channel: ChannelEntry, new_parent?: ChannelEntry) {
|
private unregisterChannelFromTree(channel: ChannelEntry) {
|
||||||
let oldChannelParent;
|
|
||||||
if(channel.parent) {
|
if(channel.parent) {
|
||||||
if(channel.parent.child_channel_head === channel)
|
if(channel.parent.child_channel_head === channel) {
|
||||||
channel.parent.child_channel_head = channel.channel_next;
|
channel.parent.child_channel_head = channel.channel_next;
|
||||||
|
}
|
||||||
/* We need only trigger this once.
|
|
||||||
If the new parent is equal to the old one with applying the "new" parent this event will get triggered */
|
|
||||||
oldChannelParent = channel.parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(channel.channel_previous)
|
if(channel.channel_previous) {
|
||||||
channel.channel_previous.channel_next = channel.channel_next;
|
channel.channel_previous.channel_next = channel.channel_next;
|
||||||
|
}
|
||||||
|
|
||||||
if(channel.channel_next)
|
if(channel.channel_next) {
|
||||||
channel.channel_next.channel_previous = channel.channel_previous;
|
channel.channel_next.channel_previous = channel.channel_previous;
|
||||||
|
}
|
||||||
|
|
||||||
if(channel === this.channel_last)
|
if(channel === this.channel_last) {
|
||||||
this.channel_last = channel.channel_previous;
|
this.channel_last = channel.channel_previous;
|
||||||
|
}
|
||||||
|
|
||||||
if(channel === this.channel_first)
|
if(channel === this.channel_first) {
|
||||||
this.channel_first = channel.channel_next;
|
this.channel_first = channel.channel_next;
|
||||||
|
}
|
||||||
|
|
||||||
channel.channel_next = undefined;
|
channel.channel_next = undefined;
|
||||||
channel.channel_previous = undefined;
|
channel.channel_previous = undefined;
|
||||||
channel.parent = undefined;
|
channel.parent = undefined;
|
||||||
|
|
||||||
if(oldChannelParent && oldChannelParent !== new_parent)
|
|
||||||
oldChannelParent.events.fire("notify_children_changed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
moveChannel(channel: ChannelEntry, channel_previous: ChannelEntry, parent: ChannelEntry) {
|
moveChannel(channel: ChannelEntry, channelPrevious: ChannelEntry, parent: ChannelEntry, triggerMoveEvent: boolean) {
|
||||||
if(channel_previous != null && channel_previous.parent != parent) {
|
if(channelPrevious != null && channelPrevious.parent != parent) {
|
||||||
console.error(tr("Invalid channel move (different parents! (%o|%o)"), channel_previous.parent, parent);
|
console.error(tr("Invalid channel move (different parents! (%o|%o)"), channelPrevious.parent, parent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let root_tree_updated = !channel.parent;
|
this.unregisterChannelFromTree(channel);
|
||||||
this.unregisterChannelFromTree(channel, parent);
|
channel.channel_previous = channelPrevious;
|
||||||
channel.channel_previous = channel_previous;
|
|
||||||
channel.channel_next = undefined;
|
channel.channel_next = undefined;
|
||||||
channel.parent = parent;
|
channel.parent = parent;
|
||||||
|
|
||||||
if(channel_previous) {
|
if(channelPrevious) {
|
||||||
if(channel_previous == this.channel_last)
|
if(channelPrevious == this.channel_last) {
|
||||||
this.channel_last = channel;
|
this.channel_last = channel;
|
||||||
|
}
|
||||||
|
|
||||||
channel.channel_next = channel_previous.channel_next;
|
channel.channel_next = channelPrevious.channel_next;
|
||||||
channel_previous.channel_next = channel;
|
channelPrevious.channel_next = channel;
|
||||||
|
|
||||||
if(channel.channel_next)
|
if(channel.channel_next) {
|
||||||
channel.channel_next.channel_previous = channel;
|
channel.channel_next.channel_previous = channel;
|
||||||
|
}
|
||||||
if(!channel.parent_channel())
|
|
||||||
root_tree_updated = true;
|
|
||||||
else
|
|
||||||
channel.parent.events.fire("notify_children_changed");
|
|
||||||
} else {
|
} else {
|
||||||
if(parent) {
|
if(parent) {
|
||||||
let children = parent.children();
|
let children = parent.children();
|
||||||
|
@ -446,7 +480,6 @@ export class ChannelTree {
|
||||||
channel.channel_next = children[0];
|
channel.channel_next = children[0];
|
||||||
channel.channel_next.channel_previous = channel;
|
channel.channel_next.channel_previous = channel;
|
||||||
}
|
}
|
||||||
parent.events.fire("notify_children_changed");
|
|
||||||
} else {
|
} else {
|
||||||
channel.channel_next = this.channel_first;
|
channel.channel_next = this.channel_first;
|
||||||
if(this.channel_first)
|
if(this.channel_first)
|
||||||
|
@ -454,14 +487,9 @@ export class ChannelTree {
|
||||||
|
|
||||||
this.channel_first = channel;
|
this.channel_first = channel;
|
||||||
this.channel_last = this.channel_last || channel;
|
this.channel_last = this.channel_last || channel;
|
||||||
root_tree_updated = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//channel.update_family_index();
|
|
||||||
//channel.children(true).forEach(e => e.update_family_index());
|
|
||||||
//channel.clients(true).forEach(e => e.update_family_index());
|
|
||||||
|
|
||||||
if(channel.channel_previous == channel) { /* shall never happen */
|
if(channel.channel_previous == channel) { /* shall never happen */
|
||||||
channel.channel_previous = undefined;
|
channel.channel_previous = undefined;
|
||||||
debugger;
|
debugger;
|
||||||
|
@ -471,22 +499,23 @@ export class ChannelTree {
|
||||||
debugger;
|
debugger;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(root_tree_updated)
|
if(triggerMoveEvent) {
|
||||||
this.events.fire("notify_root_channel_changed");
|
this.events.fire("notify_channel_moved", { channel: channel });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteClient(client: ClientEntry, reason: { reason: ViewReasonId, message?: string, serverLeave: boolean }) {
|
deleteClient(client: ClientEntry, reason: { reason: ViewReasonId, message?: string, serverLeave: boolean }) {
|
||||||
const old_channel = client.currentChannel();
|
const oldChannel = client.currentChannel();
|
||||||
old_channel?.unregisterClient(client);
|
oldChannel?.unregisterClient(client);
|
||||||
this.clients.remove(client);
|
this.clients.remove(client);
|
||||||
|
|
||||||
client.events.fire("notify_left_view", reason);
|
if(oldChannel) {
|
||||||
if(old_channel) {
|
this.events.fire("notify_client_leave_view", { client: client, message: reason.message, reason: reason.reason, isServerLeave: reason.serverLeave, sourceChannel: oldChannel });
|
||||||
this.events.fire("notify_client_leave_view", { client: client, message: reason.message, reason: reason.reason, isServerLeave: reason.serverLeave });
|
this.client.side_bar.info_frame().update_channel_client_count(oldChannel);
|
||||||
this.client.side_bar.info_frame().update_channel_client_count(old_channel);
|
} else {
|
||||||
|
logWarn(LogCategory.CHANNEL, tr("Deleting client %s from channel tree which hasn't a channel."), client.clientId());
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: Trigger the notify_clients_changed event!
|
|
||||||
const voice_connection = this.client.serverConnection.getVoiceConnection();
|
const voice_connection = this.client.serverConnection.getVoiceConnection();
|
||||||
if(client.getVoiceClient()) {
|
if(client.getVoiceClient()) {
|
||||||
const voiceClient = client.getVoiceClient();
|
const voiceClient = client.getVoiceClient();
|
||||||
|
@ -530,7 +559,7 @@ export class ChannelTree {
|
||||||
client["_channel"] = channel;
|
client["_channel"] = channel;
|
||||||
channel.registerClient(client);
|
channel.registerClient(client);
|
||||||
|
|
||||||
this.events.fire("notify_client_enter_view", { client: client, reason: reason.reason, isServerJoin: reason.isServerJoin });
|
this.events.fire("notify_client_enter_view", { client: client, reason: reason.reason, isServerJoin: reason.isServerJoin, targetChannel: channel });
|
||||||
return client;
|
return client;
|
||||||
} finally {
|
} finally {
|
||||||
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
|
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
|
||||||
|
@ -553,9 +582,7 @@ export class ChannelTree {
|
||||||
this.client.side_bar.info_frame().update_channel_client_count(targetChannel);
|
this.client.side_bar.info_frame().update_channel_client_count(targetChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(oldChannel && targetChannel) {
|
this.events.fire("notify_client_moved", { oldChannel: oldChannel, newChannel: targetChannel, client: client });
|
||||||
client.events.fire("notify_client_moved", { oldChannel: oldChannel, newChannel: targetChannel });
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
|
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
|
||||||
}
|
}
|
||||||
|
@ -916,7 +943,7 @@ export class ChannelTree {
|
||||||
|
|
||||||
private select_next_channel(channel: ChannelEntry, select_client: boolean) {
|
private select_next_channel(channel: ChannelEntry, select_client: boolean) {
|
||||||
if(select_client) {
|
if(select_client) {
|
||||||
const clients = channel.clients_ordered();
|
const clients = channel.channelClientsOrdered();
|
||||||
if(clients.length > 0) {
|
if(clients.length > 0) {
|
||||||
this.events.fire("action_select_entries", {
|
this.events.fire("action_select_entries", {
|
||||||
mode: "exclusive",
|
mode: "exclusive",
|
||||||
|
@ -974,7 +1001,7 @@ export class ChannelTree {
|
||||||
if(siblings.length == 0) break;
|
if(siblings.length == 0) break;
|
||||||
previous = siblings.last();
|
previous = siblings.last();
|
||||||
}
|
}
|
||||||
const clients = previous.clients_ordered();
|
const clients = previous.channelClientsOrdered();
|
||||||
if(clients.length > 0) {
|
if(clients.length > 0) {
|
||||||
this.events.fire("action_select_entries", {
|
this.events.fire("action_select_entries", {
|
||||||
mode: "exclusive",
|
mode: "exclusive",
|
||||||
|
@ -990,7 +1017,7 @@ export class ChannelTree {
|
||||||
}
|
}
|
||||||
} else if(selected.hasParent()) {
|
} else if(selected.hasParent()) {
|
||||||
const channel = selected.parent_channel();
|
const channel = selected.parent_channel();
|
||||||
const clients = channel.clients_ordered();
|
const clients = channel.channelClientsOrdered();
|
||||||
if(clients.length > 0) {
|
if(clients.length > 0) {
|
||||||
this.events.fire("action_select_entries", {
|
this.events.fire("action_select_entries", {
|
||||||
mode: "exclusive",
|
mode: "exclusive",
|
||||||
|
@ -1012,7 +1039,7 @@ export class ChannelTree {
|
||||||
}
|
}
|
||||||
} else if(selected instanceof ClientEntry) {
|
} else if(selected instanceof ClientEntry) {
|
||||||
const channel = selected.currentChannel();
|
const channel = selected.currentChannel();
|
||||||
const clients = channel.clients_ordered();
|
const clients = channel.channelClientsOrdered();
|
||||||
const index = clients.indexOf(selected);
|
const index = clients.indexOf(selected);
|
||||||
if(index > 0) {
|
if(index > 0) {
|
||||||
this.events.fire("action_select_entries", {
|
this.events.fire("action_select_entries", {
|
||||||
|
@ -1034,7 +1061,7 @@ export class ChannelTree {
|
||||||
this.select_next_channel(selected, true);
|
this.select_next_channel(selected, true);
|
||||||
} else if(selected instanceof ClientEntry){
|
} else if(selected instanceof ClientEntry){
|
||||||
const channel = selected.currentChannel();
|
const channel = selected.currentChannel();
|
||||||
const clients = channel.clients_ordered();
|
const clients = channel.channelClientsOrdered();
|
||||||
const index = clients.indexOf(selected);
|
const index = clients.indexOf(selected);
|
||||||
if(index + 1 < clients.length) {
|
if(index + 1 < clients.length) {
|
||||||
this.events.fire("action_select_entries", {
|
this.events.fire("action_select_entries", {
|
||||||
|
@ -1131,6 +1158,7 @@ export class ChannelTree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
private onChannelEntryMove(start, current) {
|
private onChannelEntryMove(start, current) {
|
||||||
const move = this.view_move.current;
|
const move = this.view_move.current;
|
||||||
if(!move) return;
|
if(!move) return;
|
||||||
|
@ -1175,8 +1203,10 @@ export class ChannelTree {
|
||||||
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
|
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
isClientMoveActive() {
|
isClientMoveActive() {
|
||||||
return !!this.view_move.current?.isActive();
|
//return !!this.view_move.current?.isActive();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,12 +5,18 @@ export interface ChannelTreeEntryEvents {
|
||||||
notify_unread_state_change: { unread: boolean }
|
notify_unread_state_change: { unread: boolean }
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChannelTreeEntry<Events extends ChannelTreeEntryEvents> {
|
let treeEntryIdCounter = 0;
|
||||||
|
export abstract class ChannelTreeEntry<Events extends ChannelTreeEntryEvents> {
|
||||||
readonly events: Registry<Events>;
|
readonly events: Registry<Events>;
|
||||||
|
readonly uniqueEntryId: number;
|
||||||
|
|
||||||
protected selected_: boolean = false;
|
protected selected_: boolean = false;
|
||||||
protected unread_: boolean = false;
|
protected unread_: boolean = false;
|
||||||
|
|
||||||
|
protected constructor() {
|
||||||
|
this.uniqueEntryId = treeEntryIdCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
/* called from the channel tree */
|
/* called from the channel tree */
|
||||||
protected onSelect(singleSelect: boolean) {
|
protected onSelect(singleSelect: boolean) {
|
||||||
if(this.selected_ === true) return;
|
if(this.selected_ === true) return;
|
||||||
|
@ -36,4 +42,6 @@ export class ChannelTreeEntry<Events extends ChannelTreeEntryEvents> {
|
||||||
this.events.fire("notify_unread_state_change", { unread: flag });
|
this.events.fire("notify_unread_state_change", { unread: flag });
|
||||||
}
|
}
|
||||||
isUnread() { return this.unread_; }
|
isUnread() { return this.unread_; }
|
||||||
|
|
||||||
|
abstract showContextMenu(pageX: number, pageY: number, on_close?);
|
||||||
}
|
}
|
|
@ -19,8 +19,6 @@ import {spawnChangeLatency} from "../ui/modal/ModalChangeLatency";
|
||||||
import {formatMessage} from "../ui/frames/chat";
|
import {formatMessage} from "../ui/frames/chat";
|
||||||
import {spawnYesNo} from "../ui/modal/ModalYesNo";
|
import {spawnYesNo} from "../ui/modal/ModalYesNo";
|
||||||
import * as hex from "../crypto/hex";
|
import * as hex from "../crypto/hex";
|
||||||
import {ClientEntry as ClientEntryView} from "../ui/tree/Client";
|
|
||||||
import * as React from "react";
|
|
||||||
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";
|
||||||
|
@ -30,6 +28,7 @@ import {global_client_actions} from "../events/GlobalEvents";
|
||||||
import {ClientIcon} from "svg-sprites/client-icons";
|
import {ClientIcon} from "svg-sprites/client-icons";
|
||||||
import {VoiceClient} from "../voice/VoiceClient";
|
import {VoiceClient} from "../voice/VoiceClient";
|
||||||
import {VoicePlayerEvents, VoicePlayerState} from "../voice/VoicePlayer";
|
import {VoicePlayerEvents, VoicePlayerState} from "../voice/VoicePlayer";
|
||||||
|
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
|
||||||
|
|
||||||
export enum ClientType {
|
export enum ClientType {
|
||||||
CLIENT_VOICE,
|
CLIENT_VOICE,
|
||||||
|
@ -83,6 +82,10 @@ export class ClientProperties {
|
||||||
client_total_bytes_downloaded: number = 0;
|
client_total_bytes_downloaded: number = 0;
|
||||||
|
|
||||||
client_talk_power: number = 0;
|
client_talk_power: number = 0;
|
||||||
|
client_talk_request: number = 0;
|
||||||
|
client_talk_request_msg: string = "";
|
||||||
|
client_is_talker: boolean = false;
|
||||||
|
|
||||||
client_is_priority_speaker: boolean = false;
|
client_is_priority_speaker: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,14 +142,6 @@ export class ClientConnectionInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientEvents extends ChannelTreeEntryEvents {
|
export interface ClientEvents extends ChannelTreeEntryEvents {
|
||||||
notify_enter_view: {},
|
|
||||||
notify_client_moved: { oldChannel: ChannelEntry, newChannel: ChannelEntry }
|
|
||||||
notify_left_view: {
|
|
||||||
reason: ViewReasonId;
|
|
||||||
message?: string;
|
|
||||||
serverLeave: boolean;
|
|
||||||
},
|
|
||||||
|
|
||||||
notify_properties_updated: {
|
notify_properties_updated: {
|
||||||
updated_properties: {[Key in keyof ClientProperties]: ClientProperties[Key]};
|
updated_properties: {[Key in keyof ClientProperties]: ClientProperties[Key]};
|
||||||
client_properties: ClientProperties
|
client_properties: ClientProperties
|
||||||
|
@ -183,7 +178,6 @@ const StatusIconUpdateKeys: (keyof ClientProperties)[] = [
|
||||||
|
|
||||||
export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
|
export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
|
||||||
readonly events: Registry<ClientEvents>;
|
readonly events: Registry<ClientEvents>;
|
||||||
readonly view: React.RefObject<ClientEntryView> = React.createRef<ClientEntryView>();
|
|
||||||
channelTree: ChannelTree;
|
channelTree: ChannelTree;
|
||||||
|
|
||||||
protected _clientId: number;
|
protected _clientId: number;
|
||||||
|
@ -997,7 +991,7 @@ export class LocalClientEntry extends ClientEntry {
|
||||||
tr("Change name") +
|
tr("Change name") +
|
||||||
(contextmenu.get_provider().html_format_enabled() ? "</b>" : ""),
|
(contextmenu.get_provider().html_format_enabled() ? "</b>" : ""),
|
||||||
icon_class: "client-change_nickname",
|
icon_class: "client-change_nickname",
|
||||||
callback: () => this.openRename(),
|
callback: () => this.openRenameModal(), /* FIXME: Pass the UI event registry */
|
||||||
type: contextmenu.MenuEntryType.ENTRY
|
type: contextmenu.MenuEntryType.ENTRY
|
||||||
}, {
|
}, {
|
||||||
name: tr("Change description"),
|
name: tr("Change description"),
|
||||||
|
@ -1046,20 +1040,20 @@ export class LocalClientEntry extends ClientEntry {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openRename() : void {
|
openRenameModal() {
|
||||||
const view = this.channelTree.view.current;
|
createInputModal(tr("Enter your new name"), tr("Enter your new client name"), text => text.length >= 3 && text.length <= 30, value => {
|
||||||
if(!view) return; //TODO: Fallback input modal
|
if(value) {
|
||||||
view.scrollEntryInView(this, () => {
|
this.renameSelf(value as string).then(result => {
|
||||||
const own_view = this.view.current;
|
if(!result) {
|
||||||
if(!own_view) {
|
createErrorModal(tr("Failed change nickname"), tr("Failed to change your client nickname")).open();
|
||||||
return; //TODO: Fallback input modal
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}).open();
|
||||||
|
}
|
||||||
|
|
||||||
own_view.setState({
|
openRename(events: Registry<ChannelTreeUIEvents>) : void {
|
||||||
rename: true,
|
events.fire("notify_client_name_edit", { initialValue: this.clientNickName(), treeEntryId: this.uniqueEntryId });
|
||||||
renameInitialName: this.properties.client_nickname
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,6 @@ import {spawnAvatarList} from "../ui/modal/ModalAvatarList";
|
||||||
import {connection_log} from "../ui/modal/ModalConnect";
|
import {connection_log} from "../ui/modal/ModalConnect";
|
||||||
import * as top_menu from "../ui/frames/MenuBar";
|
import * as top_menu from "../ui/frames/MenuBar";
|
||||||
import {control_bar_instance} from "../ui/frames/control-bar";
|
import {control_bar_instance} from "../ui/frames/control-bar";
|
||||||
import { ServerEntry as ServerEntryView } from "../ui/tree/Server";
|
|
||||||
import * as React from "react";
|
|
||||||
import {Registry} from "../events";
|
import {Registry} from "../events";
|
||||||
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
||||||
|
|
||||||
|
@ -138,7 +136,6 @@ export class ServerEntry extends ChannelTreeEntry<ServerEvents> {
|
||||||
properties: ServerProperties;
|
properties: ServerProperties;
|
||||||
|
|
||||||
readonly events: Registry<ServerEvents>;
|
readonly events: Registry<ServerEvents>;
|
||||||
readonly view: React.Ref<ServerEntryView>;
|
|
||||||
|
|
||||||
private info_request_promise: Promise<void> = undefined;
|
private info_request_promise: Promise<void> = undefined;
|
||||||
private info_request_promise_resolve: any = undefined;
|
private info_request_promise_resolve: any = undefined;
|
||||||
|
@ -157,7 +154,6 @@ export class ServerEntry extends ChannelTreeEntry<ServerEvents> {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.events = new Registry<ServerEvents>();
|
this.events = new Registry<ServerEvents>();
|
||||||
this.view = React.createRef();
|
|
||||||
|
|
||||||
this.properties = new ServerProperties();
|
this.properties = new ServerProperties();
|
||||||
this.channelTree = tree;
|
this.channelTree = tree;
|
||||||
|
@ -266,7 +262,7 @@ export class ServerEntry extends ChannelTreeEntry<ServerEvents> {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
spawnContextMenu(x: number, y: number, on_close: () => void = () => {}) {
|
showContextMenu(x: number, y: number, on_close: () => void = () => {}) {
|
||||||
contextmenu.spawn_context_menu(x, y, ...this.contextMenuItems(),
|
contextmenu.spawn_context_menu(x, y, ...this.contextMenuItems(),
|
||||||
contextmenu.Entry.CLOSE(on_close)
|
contextmenu.Entry.CLOSE(on_close)
|
||||||
);
|
);
|
||||||
|
|
|
@ -184,10 +184,13 @@ export class InfoFrame {
|
||||||
}
|
}
|
||||||
|
|
||||||
update_channel_client_count(channel: ChannelEntry) {
|
update_channel_client_count(channel: ChannelEntry) {
|
||||||
if(channel === this._channel_text)
|
if(channel === this._channel_text) {
|
||||||
this.update_channel_limit(channel, this._html_tag.find(".value-text-limit"));
|
this.update_channel_limit(channel, this._html_tag.find(".value-text-limit"));
|
||||||
if(channel === this._channel_voice)
|
}
|
||||||
|
|
||||||
|
if(channel === this._channel_voice) {
|
||||||
this.update_channel_limit(channel, this._html_tag.find(".value-voice-limit"));
|
this.update_channel_limit(channel, this._html_tag.find(".value-voice-limit"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private update_channel_limit(channel: ChannelEntry, tag: JQuery) {
|
private update_channel_limit(channel: ChannelEntry, tag: JQuery) {
|
||||||
|
|
|
@ -152,7 +152,7 @@ export namespace callbacks {
|
||||||
|
|
||||||
if(!client) {
|
if(!client) {
|
||||||
if(current_connection.channelTree.server.properties.virtualserver_unique_identifier === client_unique_id) {
|
if(current_connection.channelTree.server.properties.virtualserver_unique_identifier === client_unique_id) {
|
||||||
current_connection.channelTree.server.spawnContextMenu(mouse_coordinates.x, mouse_coordinates.y);
|
current_connection.channelTree.server.showContextMenu(mouse_coordinates.x, mouse_coordinates.y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,9 @@ import {WebModalRenderer} from "../../../ui/react-elements/external-modal/Popout
|
||||||
import {ClientModalRenderer} from "../../../ui/react-elements/external-modal/PopoutRendererClient";
|
import {ClientModalRenderer} from "../../../ui/react-elements/external-modal/PopoutRendererClient";
|
||||||
import {setupJSRender} from "../../../ui/jsrender";
|
import {setupJSRender} from "../../../ui/jsrender";
|
||||||
|
|
||||||
|
import "../../../file/RemoteAvatars";
|
||||||
|
import "../../../file/RemoteIcons";
|
||||||
|
|
||||||
let modalRenderer: ModalRenderer;
|
let modalRenderer: ModalRenderer;
|
||||||
let modalInstance: AbstractModal;
|
let modalInstance: AbstractModal;
|
||||||
let modalClass: new <T>(events: Registry<T>, userData: any) => AbstractModal;
|
let modalClass: new <T>(events: Registry<T>, userData: any) => AbstractModal;
|
||||||
|
|
|
@ -31,3 +31,8 @@ registerHandler({
|
||||||
name: "css-editor",
|
name: "css-editor",
|
||||||
loadClass: async () => await import("tc-shared/ui/modal/css-editor/Renderer")
|
loadClass: async () => await import("tc-shared/ui/modal/css-editor/Renderer")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerHandler({
|
||||||
|
name: "channel-tree",
|
||||||
|
loadClass: async () => await import("tc-shared/ui/tree/RendererModal")
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue