Implementing key tree control for the external channel trees

This commit is contained in:
WolverinDEV 2020-09-29 15:42:54 +02:00
parent 583cdd146e
commit 1f93fe433e
6 changed files with 285 additions and 329 deletions

View file

@ -4,7 +4,7 @@ import * as log from "tc-shared/log";
import {LogCategory, logWarn} from "tc-shared/log";
import {Settings, settings} from "tc-shared/settings";
import {PermissionType} from "tc-shared/permission/PermissionType";
import {KeyCode, SpecialKey} from "tc-shared/PPTListener";
import {SpecialKey} from "tc-shared/PPTListener";
import {Sound} from "tc-shared/sound/Sounds";
import {Group} from "tc-shared/permission/GroupManager";
import {ServerAddress, ServerEntry} from "./Server";
@ -86,14 +86,14 @@ export interface ChannelTreeEvents {
export class ChannelTreeEntrySelect {
readonly handle: ChannelTree;
selected_entries: ChannelTreeEntry<any>[] = [];
selectedEntries: ChannelTreeEntry<any>[] = [];
private readonly handler_select_entries;
private readonly handlerSelectEntries;
constructor(handle: ChannelTree) {
this.handle = handle;
this.handler_select_entries = e => {
this.handlerSelectEntries = e => {
batch_updates(BatchUpdateType.CHANNEL_TREE);
try {
this.handleSelectEntries(e)
@ -102,122 +102,248 @@ export class ChannelTreeEntrySelect {
}
};
this.handle.events.on("action_select_entries", this.handler_select_entries);
this.handle.events.on("action_select_entries", this.handlerSelectEntries);
}
reset() {
this.selected_entries.splice(0, this.selected_entries.length);
this.selectedEntries.splice(0, this.selectedEntries.length);
}
destroy() {
this.handle.events.off("action_select_entries", this.handler_select_entries);
this.selected_entries.splice(0, this.selected_entries.length);
this.handle.events.off("action_select_entries", this.handlerSelectEntries);
this.selectedEntries.splice(0, this.selectedEntries.length);
}
is_multi_select() {
return this.selected_entries.length > 1;
isMultiSelect() {
return this.selectedEntries.length > 1;
}
is_anything_selected() {
return this.selected_entries.length > 0;
isAnythingSelected() {
return this.selectedEntries.length > 0;
}
clear_selection() {
clearSelection() {
this.handleSelectEntries({
entries: [],
mode: "exclusive"
});
}
private handleSelectEntries(event: ChannelTreeEvents["action_select_entries"]) {
if(event.mode === "exclusive") {
let deleted_entries = this.selected_entries;
/**
* auto := Select/unselect/add/remove depending on the selected state & shift key state
* exclusive := Only selected these entries
* append := Append these entries to the current selection
* remove := Remove these entries from the current selection
*/
select(entries: ChannelTreeEntry<any>[], mode: "auto" | "exclusive" | "append" | "remove") {
entries = entries.filter(entry => !!entry);
if(mode === "exclusive") {
let deleted_entries = this.selectedEntries;
let new_entries = [];
this.selected_entries = [];
for(const new_entry of event.entries) {
if(!deleted_entries.remove(new_entry))
this.selectedEntries = [];
for(const new_entry of entries) {
if(!deleted_entries.remove(new_entry)) {
new_entries.push(new_entry);
this.selected_entries.push(new_entry);
}
this.selectedEntries.push(new_entry);
}
for(const deleted of deleted_entries)
for(const deleted of deleted_entries) {
deleted["onUnselect"]();
}
for(const new_entry of new_entries)
new_entry["onSelect"](!this.is_multi_select());
for(const new_entry of new_entries) {
new_entry["onSelect"](!this.isMultiSelect());
}
if(deleted_entries.length !== 0 || new_entries.length !== 0)
if(deleted_entries.length !== 0 || new_entries.length !== 0) {
this.handle.events.fire("notify_selection_changed");
} else if(event.mode === "append") {
}
} else if(mode === "append") {
let new_entries = [];
for(const entry of event.entries) {
if(this.selected_entries.findIndex(e => e === entry) !== -1)
for(const entry of entries) {
if(this.selectedEntries.findIndex(e => e === entry) !== -1)
continue;
this.selected_entries.push(entry);
this.selectedEntries.push(entry);
new_entries.push(entry);
}
for(const new_entry of new_entries)
new_entry["onSelect"](!this.is_multi_select());
if(new_entries.length !== 0)
this.handle.events.fire("notify_selection_changed");
} else if(event.mode === "remove") {
let deleted_entries = [];
for(const entry of event.entries) {
if(this.selected_entries.remove(entry))
deleted_entries.push(entry);
for(const new_entry of new_entries) {
new_entry["onSelect"](!this.isMultiSelect());
}
for(const deleted of deleted_entries)
deleted["onUnselect"]();
if(deleted_entries.length !== 0)
if(new_entries.length !== 0) {
this.handle.events.fire("notify_selection_changed");
} else if(event.mode === "auto") {
}
} else if(mode === "remove") {
let deleted_entries = [];
for(const entry of entries) {
if(this.selectedEntries.remove(entry)) {
deleted_entries.push(entry);
}
}
for(const deleted of deleted_entries) {
deleted["onUnselect"]();
}
if(deleted_entries.length !== 0) {
this.handle.events.fire("notify_selection_changed");
}
} else if(mode === "auto") {
let deleted_entries = [];
let new_entries = [];
if(ppt.key_pressed(SpecialKey.SHIFT)) {
for(const entry of event.entries) {
const index = this.selected_entries.findIndex(e => e === entry);
for(const entry of entries) {
const index = this.selectedEntries.findIndex(e => e === entry);
if(index === -1) {
this.selected_entries.push(entry);
this.selectedEntries.push(entry);
new_entries.push(entry);
} else {
this.selected_entries.splice(index, 1);
this.selectedEntries.splice(index, 1);
deleted_entries.push(entry);
}
}
} else {
deleted_entries = this.selected_entries.splice(0, this.selected_entries.length);
if(event.entries.length !== 0) {
const entry = event.entries[event.entries.length - 1];
this.selected_entries.push(entry);
deleted_entries = this.selectedEntries.splice(0, this.selectedEntries.length);
if(entries.length !== 0) {
const entry = entries[entries.length - 1];
this.selectedEntries.push(entry);
if(!deleted_entries.remove(entry))
new_entries.push(entry); /* entry wans't selected yet */
}
}
for(const deleted of deleted_entries)
for(const deleted of deleted_entries) {
deleted["onUnselect"]();
}
for(const new_entry of new_entries)
new_entry["onSelect"](!this.is_multi_select());
for(const new_entry of new_entries) {
new_entry["onSelect"](!this.isMultiSelect());
}
if(deleted_entries.length !== 0 || new_entries.length !== 0)
if(deleted_entries.length !== 0 || new_entries.length !== 0) {
this.handle.events.fire("notify_selection_changed");
}
} else {
console.warn("Received entry select event with unknown mode: %s", event.mode);
console.warn("Received entry select event with unknown mode: %s", mode);
}
/*
TODO!
if(this.selected_entries.length === 1)
this.handle.view.current?.scrollEntryInView(this.selected_entries[0] as any);
*/
*/
}
private selectNextChannel(currentChannel: ChannelEntry, selectClients: boolean) {
if(selectClients) {
const clients = currentChannel.channelClientsOrdered();
if(clients.length > 0) {
this.select([clients[0]], "exclusive");
return;
}
}
const children = currentChannel.children();
if(children.length > 0) {
this.select([children[0]], "exclusive")
return;
}
const next = currentChannel.channel_next;
if(next) {
this.select([next], "exclusive")
return;
}
let parent = currentChannel.parent_channel();
while(parent) {
const p_next = parent.channel_next;
if(p_next) {
this.select([p_next], "exclusive")
return;
}
parent = parent.parent_channel();
}
}
selectNextTreeEntry() {
if(this.selectedEntries.length !== 1) { return; }
const selected = this.selectedEntries[0];
if(selected instanceof ChannelEntry) {
this.selectNextChannel(selected, true);
} else if(selected instanceof ClientEntry){
const channel = selected.currentChannel();
const clients = channel.channelClientsOrdered();
const index = clients.indexOf(selected);
if(index + 1 < clients.length) {
this.select([clients[index + 1]], "exclusive");
return;
}
this.selectNextChannel(channel, false);
} else if(selected instanceof ServerEntry) {
this.select([this.handle.get_first_channel()], "exclusive");
}
}
selectPreviousTreeEntry() {
if(this.selectedEntries.length !== 1) { return; }
const selected = this.selectedEntries[0];
if(selected instanceof ChannelEntry) {
let previous = selected.channel_previous;
if(previous) {
while(true) {
const siblings = previous.children();
if(siblings.length == 0) break;
previous = siblings.last();
}
const clients = previous.channelClientsOrdered();
if(clients.length > 0) {
this.select([ clients.last() ], "exclusive");
return;
} else {
this.select([ previous ], "exclusive");
return;
}
} else if(selected.hasParent()) {
const channel = selected.parent_channel();
const clients = channel.channelClientsOrdered();
if(clients.length > 0) {
this.select([ clients.last() ], "exclusive");
return;
} else {
this.select([ channel ], "exclusive");
return;
}
} else {
this.select([ this.handle.server ], "exclusive");
}
} else if(selected instanceof ClientEntry) {
const channel = selected.currentChannel();
const clients = channel.channelClientsOrdered();
const index = clients.indexOf(selected);
if(index > 0) {
this.select([ clients[index - 1] ], "exclusive");
return;
}
this.select([ channel ], "exclusive");
return;
}
}
private handleSelectEntries(event: ChannelTreeEvents["action_select_entries"]) {
this.select(event.entries, event.mode);
}
}
@ -230,7 +356,7 @@ export class ChannelTree {
channels: ChannelEntry[] = [];
clients: ClientEntry[] = [];
/* whatever all channels have been initiaized */
/* whatever all channels have been initialized */
channelsInitialized: boolean = false;
readonly selection: ChannelTreeEntrySelect;
@ -242,10 +368,6 @@ export class ChannelTree {
private channel_last?: ChannelEntry;
private channel_first?: ChannelEntry;
private tagContainerFocused = false;
private listenerDocumentClick;
private listenerDocumentKeyPress;
constructor(client) {
this.events = new Registry<ChannelTreeEvents>();
this.events.enableDebug("channel-tree");
@ -279,22 +401,6 @@ export class ChannelTree {
});
*/
}
/* FIXME: Move this to the channel tree renderer */
this.listenerDocumentKeyPress = event => this.handle_key_press(event);
this.listenerDocumentClick = event => {
this.tagContainerFocused = false;
let element = event.target as HTMLElement;
while(element) {
if(element === this.tagContainer[0]) {
this.tagContainerFocused = true;
break;
}
element = element.parentNode as HTMLElement;
}
};
document.addEventListener('click', this.listenerDocumentClick);
document.addEventListener('keydown', this.listenerDocumentKeyPress);
}
tag_tree() : JQuery {
@ -336,12 +442,6 @@ export class ChannelTree {
destroy() {
ReactDOM.unmountComponentAtNode(this.tagContainer[0]);
this.listenerDocumentClick && document.removeEventListener('click', this.listenerDocumentClick);
this.listenerDocumentClick = undefined;
this.listenerDocumentKeyPress && document.removeEventListener('keydown', this.listenerDocumentKeyPress);
this.listenerDocumentKeyPress = undefined;
if(this.server) {
this.server.destroy();
this.server = undefined;
@ -674,7 +774,7 @@ export class ChannelTree {
msg: result
});
this.selection.clear_selection();
this.selection.clearSelection();
}
}, {width: 400, maxLength: 512}).open();
}
@ -691,7 +791,7 @@ export class ChannelTree {
clid: client.clientId(),
cid: target
});
this.selection.clear_selection();
this.selection.clearSelection();
}
});
if (!local_client) {//local client cant be kicked and/or banned or kicked
@ -711,7 +811,7 @@ export class ChannelTree {
});
}
}, {width: 400, maxLength: 255}).open();
this.selection.clear_selection();
this.selection.clearSelection();
}
});
@ -721,7 +821,7 @@ export class ChannelTree {
icon_class: "client-poke",
name: tr("Poke clients"),
callback: () => {
this.selection.clear_selection();
this.selection.clearSelection();
createInputModal(tr("Poke clients"), tr("Poke message:<br>"), text => true, result => {
if (result) {
const elements = clients.map(e => { return { clid: e.clientId() } as any });
@ -735,7 +835,7 @@ export class ChannelTree {
icon_class: "client-kick_server",
name: tr("Kick clients fom server"),
callback: () => {
this.selection.clear_selection();
this.selection.clearSelection();
createInputModal(tr("Kick clients from server"), tr("Kick reason:<br>"), text => true, result => {
if (result) {
for (const client of clients)
@ -753,7 +853,7 @@ export class ChannelTree {
name: tr("Ban clients"),
invalidPermission: !this.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {
this.selection.clear_selection();
this.selection.clearSelection();
spawnBanClient(this.client, (clients).map(entry => {
return {
name: entry.clientNickName(),
@ -791,7 +891,7 @@ export class ChannelTree {
this.client.serverConnection.send_command("musicbotdelete", {
botid: client.properties.client_database_id
});
this.selection.clear_selection();
this.selection.clearSelection();
}
});
},
@ -813,7 +913,7 @@ export class ChannelTree {
if(typeof result === "boolean" && result) {
for(const channel of channels)
this.client.serverConnection.send_command("channeldelete", { cid: channel.channelId });
this.selection.clear_selection();
this.selection.clearSelection();
}
});
}
@ -879,7 +979,7 @@ export class ChannelTree {
this.channelsInitialized = false;
batch_updates(BatchUpdateType.CHANNEL_TREE);
this.selection.clear_selection();
this.selection.clearSelection();
try {
this.selection.reset();
@ -944,149 +1044,6 @@ export class ChannelTree {
});
}
private select_next_channel(channel: ChannelEntry, select_client: boolean) {
if(select_client) {
const clients = channel.channelClientsOrdered();
if(clients.length > 0) {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ clients[0] ]
});
return;
}
}
const children = channel.children();
if(children.length > 0) {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ children[0] ]
});
return;
}
const next = channel.channel_next;
if(next) {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ next ]
});
return;
}
let parent = channel.parent_channel();
while(parent) {
const p_next = parent.channel_next;
if(p_next) {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ p_next ]
});
return;
}
parent = parent.parent_channel();
}
}
handle_key_press(event: KeyboardEvent) {
if(!this.tagContainerFocused || !this.selection.is_anything_selected() || this.selection.is_multi_select()) return;
const selected = this.selection.selected_entries[0];
if(event.keyCode == KeyCode.KEY_UP) {
event.preventDefault();
if(selected instanceof ChannelEntry) {
let previous = selected.channel_previous;
if(previous) {
while(true) {
const siblings = previous.children();
if(siblings.length == 0) break;
previous = siblings.last();
}
const clients = previous.channelClientsOrdered();
if(clients.length > 0) {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ clients.last() ]
});
return;
} else {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ previous ]
});
return;
}
} else if(selected.hasParent()) {
const channel = selected.parent_channel();
const clients = channel.channelClientsOrdered();
if(clients.length > 0) {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ clients.last() ]
});
return;
} else {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ channel ]
});
return;
}
} else {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ this.server ]
});
}
} else if(selected instanceof ClientEntry) {
const channel = selected.currentChannel();
const clients = channel.channelClientsOrdered();
const index = clients.indexOf(selected);
if(index > 0) {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ clients[index - 1] ]
});
return;
}
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ channel ]
});
return;
}
} else if(event.keyCode == KeyCode.KEY_DOWN) {
event.preventDefault();
if(selected instanceof ChannelEntry) {
this.select_next_channel(selected, true);
} else if(selected instanceof ClientEntry){
const channel = selected.currentChannel();
const clients = channel.channelClientsOrdered();
const index = clients.indexOf(selected);
if(index + 1 < clients.length) {
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ clients[index + 1] ]
});
return;
}
this.select_next_channel(channel, false);
} else if(selected instanceof ServerEntry)
this.events.fire("action_select_entries", {
mode: "exclusive",
entries: [ this.channel_first ]
});
} else if(event.keyCode == KeyCode.KEY_RETURN) {
if(selected instanceof ChannelEntry) {
selected.joinChannel();
}
}
}
toggle_server_queries(flag: boolean) {
if(this._show_queries == flag) return;
this._show_queries = flag;
@ -1112,7 +1069,7 @@ export class ChannelTree {
if(channels.length > 0) {
this.client.serverConnection.send_command('channelsubscribe', channels.map(e => { return {cid: e}; })).catch(error => {
console.warn(tr("Failed to subscribe to specific channels (%o)"), channels);
console.warn(tr("Failed to subscribe to specific channels (%o): %o"), channels, error);
});
}
}).catch(error => {
@ -1133,7 +1090,7 @@ export class ChannelTree {
if(channels.length > 0) {
this.client.serverConnection.send_command('channelunsubscribe', channels.map(e => { return {cid: e}; })).catch(error => {
console.warn(tr("Failed to unsubscribe to specific channels (%o)"), channels);
console.warn(tr("Failed to unsubscribe to specific channels (%o): %o"), channels, error);
});
}
}).catch(error => {
@ -1160,56 +1117,4 @@ export class ChannelTree {
this.collapse_channels(child);
}
}
/*
private onChannelEntryMove(start, current) {
const move = this.view_move.current;
if(!move) return;
const target = this.view.current.getEntryFromPoint(start.x, start.y);
if(target && this.selection.selected_entries.findIndex(e => e === target) === -1)
this.events.fire("action_select_entries", { mode: "auto", entries: [ target ]});
const selection = this.selection.selected_entries;
if(selection.length === 0 || selection.filter(e => !(e instanceof ClientEntry)).length > 0)
return;
move.enableEntryMove(this.view.current, selection.map(e => e as ClientEntry).map(e => e.clientNickName()).join(","), start, current, () => {
this.events.fire("notify_entry_move_begin");
});
}
private onMoveEnd(x: number, y: number) {
batch_updates(BatchUpdateType.CHANNEL_TREE);
try {
this.events.fire("notify_entry_move_end");
const selection = this.selection.selected_entries.filter(e => e instanceof ClientEntry) as ClientEntry[];
if(selection.length === 0) return;
this.selection.clear_selection();
const target = this.view.current.getEntryFromPoint(x, y);
let target_channel: ChannelEntry;
if(target instanceof ClientEntry)
target_channel = target.currentChannel();
else if(target instanceof ChannelEntry)
target_channel = target;
if(!target_channel) return;
selection.filter(e => e.currentChannel() !== target_channel).forEach(e => {
this.client.serverConnection.send_command("clientmove", {
clid: e.clientId(),
cid: target_channel.channelId
});
});
} finally {
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
}
}
*/
isClientMoveActive() {
//return !!this.view_move.current?.isActive();
return false;
}
}

View file

@ -657,7 +657,7 @@ export function initializeChannelTreeController(events: Registry<ChannelTreeUIEv
});
events.on("action_select", event => {
if(!event.ignoreClientMove && channelTree.isClientMoveActive()) {
if(!event.ignoreClientMove && moveSelection?.length) {
return;
}
@ -672,10 +672,15 @@ export function initializeChannelTreeController(events: Registry<ChannelTreeUIEv
entries.push(entry);
}
channelTree.events.fire("action_select_entries", {
mode: event.mode,
entries: entries
});
channelTree.selection.select(entries, event.mode);
});
events.on("action_select_auto", event => {
if(event.direction === "next") {
channelTree.selection.selectNextTreeEntry();
} else if(event.direction === "previous") {
channelTree.selection.selectPreviousTreeEntry();
}
});
events.on("action_show_context_menu", event => {
@ -685,8 +690,8 @@ export function initializeChannelTreeController(events: Registry<ChannelTreeUIEv
return;
}
if (channelTree.selection.is_multi_select() && entry.isSelected()) {
channelTree.open_multiselect_context_menu(channelTree.selection.selected_entries, event.pageX, event.pageY);
if (channelTree.selection.isMultiSelect() && entry.isSelected()) {
channelTree.open_multiselect_context_menu(channelTree.selection.selectedEntries, event.pageX, event.pageY);
return;
}
@ -698,13 +703,15 @@ export function initializeChannelTreeController(events: Registry<ChannelTreeUIEv
});
events.on("action_channel_join", event => {
if(!event.ignoreMultiSelect && channelTree.selection.is_multi_select()) {
if(!event.ignoreMultiSelect && channelTree.selection.isMultiSelect()) {
return;
}
const entry = channelTree.findEntryId(event.treeEntryId);
const entry = event.treeEntryId === "selected" ? channelTree.selection.selectedEntries[0] : channelTree.findEntryId(event.treeEntryId);
if(!entry || !(entry instanceof ChannelEntry)) {
logWarn(LogCategory.CHANNEL, tr("Tried to join an invalid tree entry with id %o"), event.treeEntryId);
if(event.treeEntryId !== "selected") {
logWarn(LogCategory.CHANNEL, tr("Tried to join an invalid tree entry with id %o"), event.treeEntryId);
}
return;
}
@ -737,7 +744,7 @@ export function initializeChannelTreeController(events: Registry<ChannelTreeUIEv
return;
}
if(channelTree.selection.is_multi_select()) {
if(channelTree.selection.isMultiSelect()) {
return;
}
@ -769,7 +776,7 @@ export function initializeChannelTreeController(events: Registry<ChannelTreeUIEv
let moveSelection: ClientEntry[];
events.on("action_start_entry_move", event => {
const selection = channelTree.selection.selected_entries.slice();
const selection = channelTree.selection.selectedEntries.slice();
if(selection.length === 0) { return; }
if(selection.findIndex(element => !(element instanceof ClientEntry)) !== -1) { return; }

View file

@ -37,7 +37,8 @@ export interface ChannelTreeUIEvents {
mode: "auto" | "exclusive" | "append" | "remove",
ignoreClientMove: boolean
},
action_channel_join: { treeEntryId: number, ignoreMultiSelect: boolean },
action_select_auto: { direction: "next" | "previous" },
action_channel_join: { treeEntryId: number | "selected", ignoreMultiSelect: boolean },
action_channel_open_file_browser: { treeEntryId: number },
action_client_double_click: { treeEntryId: number },
action_client_name_submit: { treeEntryId: number, name: string },

View file

@ -1,13 +1,59 @@
import {Registry} from "tc-shared/events";
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
import * as React from "react";
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
import {ChannelTreeView, PopoutButton} from "tc-shared/ui/tree/RendererView";
import {RDPChannelTree} from "./RendererDataProvider";
import {useEffect} from "react";
import {useEffect, useRef} from "react";
const viewStyle = require("./View.scss");
export const ChannelTreeRenderer = (props: { handlerId: string, events: Registry<ChannelTreeUIEvents> }) => {
const dataProvider = new RDPChannelTree(props.events, props.handlerId);
dataProvider.initialize();
useEffect(() => () => dataProvider.destroy());
return <ChannelTreeView events={props.events} dataProvider={dataProvider} ref={dataProvider.refTree} />;
return <ContainerView tree={dataProvider} events={props.events} />;
}
const ContainerView = (props: { tree: RDPChannelTree, events: Registry<ChannelTreeUIEvents> }) => {
const refContainer = useRef<HTMLDivElement>();
const focusWithin = useRef(false);
useEffect(() => {
let mouseDownListener;
document.addEventListener("mousedown", mouseDownListener = event => {
let target = event.target as HTMLElement;
while(target !== refContainer.current && target) { target = target.parentElement; }
focusWithin.current = !!target;
});
let keyListener;
document.addEventListener("keydown", keyListener = event => {
if(!focusWithin.current) { return; }
if(event.key === "ArrowUp") {
event.preventDefault();
props.events.fire("action_select_auto", { direction: "previous" });
} else if(event.key === "ArrowDown") {
event.preventDefault();
props.events.fire("action_select_auto", { direction: "next" });
} else if(event.key === "Enter") {
event.preventDefault();
props.events.fire("action_channel_join", { treeEntryId: "selected", ignoreMultiSelect: false });
}
});
return () => {
document.removeEventListener("mousedown", mouseDownListener);
document.removeEventListener("keypress", keyListener);
}
});
return (
<div className={viewStyle.treeContainer} ref={refContainer}>
<ChannelTreeView events={props.events} dataProvider={props.tree} ref={props.tree.refTree} />
<PopoutButton tree={props.tree} ref={props.tree.refPopoutButton} />
</div>
)
}

View file

@ -257,7 +257,7 @@ export class RDPChannelTree {
entry.destroy();
});
this.refTree?.current.setState({
this.refTree.current?.setState({
tree: this.orderedTree.slice(),
treeRevision: ++this.treeRevision
});

View file

@ -186,30 +186,27 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
}
return (
<div className={viewStyle.treeContainer}>
<div
className={viewStyle.channelTreeContainer + " " + (this.state.smoothScroll ? viewStyle.smoothScroll : "")}
onScroll={() => this.onScroll()}
ref={this.refContainer}
onMouseDown={e => this.onMouseDown(e)}
onMouseMove={e => this.onMouseMove(e)}>
<div
className={viewStyle.channelTreeContainer + " " + (this.state.smoothScroll ? viewStyle.smoothScroll : "")}
onScroll={() => this.onScroll()}
ref={this.refContainer}
onMouseDown={e => this.onMouseDown(e)}
onMouseMove={e => this.onMouseMove(e)}>
<div
className={viewStyle.channelTree}
style={{height: (this.state.tree.length * ChannelTreeView.EntryHeight) + "px"}}>
{elements}
</div>
<RendererMove
onMoveEnd={target => {
const targetEntry = this.getEntryFromPoint(target.x, target.y);
this.props.events.fire("action_move_entries", { treeEntryId: typeof targetEntry === "number" ? targetEntry : 0 });
}}
onMoveCancel={() => {
this.props.events.fire("action_move_entries", { treeEntryId: 0 });
}}
ref={this.props.dataProvider.refMove}
/>
className={viewStyle.channelTree}
style={{height: (this.state.tree.length * ChannelTreeView.EntryHeight) + "px"}}>
{elements}
</div>
<PopoutButton tree={this.props.dataProvider} ref={this.props.dataProvider.refPopoutButton} />
<RendererMove
onMoveEnd={target => {
const targetEntry = this.getEntryFromPoint(target.x, target.y);
this.props.events.fire("action_move_entries", { treeEntryId: typeof targetEntry === "number" ? targetEntry : 0 });
}}
onMoveCancel={() => {
this.props.events.fire("action_move_entries", { treeEntryId: 0 });
}}
ref={this.props.dataProvider.refMove}
/>
</div>
)
}