A lot of updates

canary
WolverinDEV 2020-04-21 15:18:16 +02:00
parent 0abd6c3178
commit 7871d7c189
17 changed files with 293 additions and 223 deletions

View File

@ -1,4 +1,8 @@
# Changelog:
* **21.04.20**
- Clicking on the music bot does not longer results in the insufficient permission sound when the client has no permissions
- Fixed permission editor overflow
* **18.04.20**
- Recoded the channel tree using React
- Heavily improved channel tree performance on large servers (fluent scroll & updates)

View File

@ -345,10 +345,6 @@ loader.register_task(loader.Stage.SETUP, {
const container = document.createElement("div");
container.setAttribute('id', "mouse-move");
const inner_container = document.createElement("div");
inner_container.classList.add("container");
container.append(inner_container);
body.append(container);
}
/* tooltip container */

View File

@ -257,22 +257,6 @@ $animation_seperator_length: .1s;
}
}
#mouse-move {
display: none;
position: absolute;
z-index: 10000;
.container {
position: relative;
display: block;
border: 2px solid gray;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
}
html, body {
overflow: hidden;
}

View File

@ -166,6 +166,7 @@
width: 25%;
min-width: 10em;
min-height: 10em;
overflow: hidden;
background-color: #222226;

View File

@ -262,7 +262,7 @@ export class CommandHelper extends AbstractCommandHandler {
});
}
request_playlist_songs(playlist_id: number) : Promise<PlaylistSong[]> {
request_playlist_songs(playlist_id: number, process_result?: boolean) : Promise<PlaylistSong[]> {
let bulked_response = false;
let bulk_index = 0;
@ -314,7 +314,7 @@ export class CommandHelper extends AbstractCommandHandler {
};
this.handler_boss.register_single_handler(single_handler);
this.connection.send_command("playlistsonglist", {playlist_id: playlist_id}).catch(error => {
this.connection.send_command("playlistsonglist", {playlist_id: playlist_id}, { process_result: process_result }).catch(error => {
this.handler_boss.remove_single_handler(single_handler);
if(error instanceof CommandResult) {
if(error.id == ErrorID.EMPTY_RESULT) {

View File

@ -493,14 +493,15 @@ export class ServerSettings extends SettingsBase {
server?<T>(key: string | SettingsKey<T>, _default?: T) : T {
if(this._destroyed) throw "destroyed";
return StaticSettings.resolveKey(Settings.keyify(key), _default, key => this.cacheServer[key]);
const kkey = Settings.keyify(key);
return StaticSettings.resolveKey(kkey, typeof _default === "undefined" ? kkey.default_value : _default, key => this.cacheServer[key]);
}
changeServer<T>(key: string | SettingsKey<T>, value?: T) {
if(this._destroyed) throw "destroyed";
key = Settings.keyify(key);
if(this.cacheServer[key.key] == value) return;
if(this.cacheServer[key.key] === value) return;
this._server_settings_updated = true;
this.cacheServer[key.key] = StaticSettings.transformOtS(value);

View File

@ -1,167 +0,0 @@
import {ChannelTree} from "tc-shared/ui/view";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {ClientEntry} from "tc-shared/ui/client";
import {ChannelEntry} from "tc-shared/ui/channel";
export class ClientMover {
static readonly listener_root = $(document);
static readonly move_element = $("#mouse-move");
readonly channel_tree: ChannelTree;
selected_client: ClientEntry | ClientEntry[];
hovered_channel: HTMLDivElement;
callback: (channel?: ChannelEntry) => any;
enabled: boolean = true;
private _bound_finish;
private _bound_move;
private _active: boolean = false;
private origin_point: {x: number, y: number} = undefined;
constructor(tree: ChannelTree) {
this.channel_tree = tree;
}
is_active() { return this._active; }
private hover_text() {
if($.isArray(this.selected_client)) {
return this.selected_client.filter(client => !!client).map(client => client.clientNickName()).join(", ");
} else if(this.selected_client) {
return (<ClientEntry>this.selected_client).clientNickName();
} else
return "";
}
private bbcode_text() {
if($.isArray(this.selected_client)) {
return this.selected_client.filter(client => !!client).map(client => client.create_bbcode()).join(", ");
} else if(this.selected_client) {
return (<ClientEntry>this.selected_client).create_bbcode();
} else
return "";
}
activate(client: ClientEntry | ClientEntry[], callback: (channel?: ChannelEntry) => any, event: any) {
this.finish_listener(undefined);
if(!this.enabled)
return false;
this.selected_client = client;
this.callback = callback;
log.debug(LogCategory.GENERAL, tr("Starting mouse move"));
ClientMover.listener_root.on('mouseup', this._bound_finish = this.finish_listener.bind(this)).on('mousemove', this._bound_move = this.move_listener.bind(this));
{
const content = ClientMover.move_element.find(".container");
content.empty();
content.append($.spawn("a").text(this.hover_text()));
}
this.move_listener(event);
}
private move_listener(event) {
if(!this.enabled)
return;
//console.log("Mouse move: " + event.pageX + " - " + event.pageY);
if(!event.pageX || !event.pageY) return;
if(!this.origin_point)
this.origin_point = {x: event.pageX, y: event.pageY};
ClientMover.move_element.css({
"top": (event.pageY - 1) + "px",
"left": (event.pageX + 10) + "px"
});
if(!this._active) {
const d_x = this.origin_point.x - event.pageX;
const d_y = this.origin_point.y - event.pageY;
this._active = Math.sqrt(d_x * d_x + d_y * d_y) > 5 * 5;
if(this._active) {
if($.isArray(this.selected_client)) {
this.channel_tree.onSelect(this.selected_client[0], true);
for(const client of this.selected_client.slice(1))
this.channel_tree.onSelect(client, false, true);
} else {
this.channel_tree.onSelect(this.selected_client, true);
}
ClientMover.move_element.show();
}
}
const elements = document.elementsFromPoint(event.pageX, event.pageY);
while(elements.length > 0) {
if(elements[0].classList.contains("container-channel")) break;
elements.pop_front();
}
if(this.hovered_channel) {
this.hovered_channel.classList.remove("move-selected");
this.hovered_channel = undefined;
}
if(elements.length > 0) {
elements[0].classList.add("move-selected");
this.hovered_channel = elements[0] as HTMLDivElement;
}
}
private finish_listener(event) {
ClientMover.move_element.hide();
log.debug(LogCategory.GENERAL, tr("Finishing mouse move"));
const channel_id = this.hovered_channel ? parseInt(this.hovered_channel.getAttribute("channel-id")) : 0;
ClientMover.listener_root.unbind('mouseleave', this._bound_finish);
ClientMover.listener_root.unbind('mouseup', this._bound_finish);
ClientMover.listener_root.unbind('mousemove', this._bound_move);
if(this.hovered_channel) {
this.hovered_channel.classList.remove("move-selected");
this.hovered_channel = undefined;
}
this.origin_point = undefined;
if(!this._active) {
this.selected_client = undefined;
this.callback = undefined;
return;
}
this._active = false;
if(this.callback) {
if(!channel_id)
this.callback(undefined);
else {
this.callback(this.channel_tree.findChannel(channel_id));
}
this.callback = undefined;
}
/* test for the chat box */
{
const elements = document.elementsFromPoint(event.pageX, event.pageY);
console.error(elements);
while(elements.length > 0) {
if(elements[0].classList.contains("client-chat-box-field")) break;
elements.pop_front();
}
if(elements.length > 0) {
const element = $(<HTMLTextAreaElement>elements[0]);
element.val((element.val() || "") + this.bbcode_text());
}
}
}
deactivate() {
this.callback = undefined;
this.finish_listener(undefined);
}
}

View File

@ -16,6 +16,7 @@ html:root {
height: 100%;
align-items: center;
background: var(--menu-bar-background);
border-radius: 5px;
/* tmp fix for ultra small devices */
overflow-y: visible;

View File

@ -736,7 +736,7 @@ export class MusicInfo {
this._current_bot.updateClientVariables(true).catch(error => {
log.warn(LogCategory.CLIENT, tr("Failed to update music bot variables: %o"), error);
}).then(() => {
this.handle.handle.serverConnection.command_helper.request_playlist_songs(this._current_bot.properties.client_playlist_id).then(songs => {
this.handle.handle.serverConnection.command_helper.request_playlist_songs(this._current_bot.properties.client_playlist_id, false).then(songs => {
this.playlist_subscribe(false); /* we're allowed to see the playlist */
if(!songs) {
this._container_playlist.find(".overlay-empty").removeClass("hidden");

View File

@ -244,7 +244,7 @@ export class ChannelEntryView extends TreeEntry<ChannelEntryViewProperties, {}>
const collapsed_indicator = this.props.channel.child_channel_head || this.props.channel.clients(false).length > 0;
return <div className={this.classList(viewStyle.treeEntry, channelStyle.channelEntry, this.props.channel.isSelected() && viewStyle.selected)}
style={{ paddingLeft: this.props.depth * 16 + 2, top: this.props.offset }}
onMouseDown={e => this.onMouseDown(e as any)}
onMouseUp={e => this.onMouseUp(e as any)}
onDoubleClick={() => this.onDoubleClick()}
onContextMenu={e => this.onContextMenu(e as any)}
>
@ -260,10 +260,12 @@ export class ChannelEntryView extends TreeEntry<ChannelEntryViewProperties, {}>
this.props.channel.collapsed = !this.props.channel.collapsed;
}
private onMouseDown(event: MouseEvent) {
private onMouseUp(event: MouseEvent) {
if(event.button !== 0) return; /* only left mouse clicks */
const channel = this.props.channel;
if(channel.channelTree.isClientMoveActive()) return;
channel.channelTree.events.fire("action_select_entries", {
entries: [ channel ],
mode: "auto"

View File

@ -364,7 +364,7 @@ export class ClientEntry extends TreeEntry<ClientEntryProperties, ClientEntrySta
<div className={this.classList(clientStyle.clientEntry, viewStyle.treeEntry, this.props.client.isSelected() && viewStyle.selected)}
style={{ paddingLeft: (this.props.depth * 16 + 2) + "px", top: this.props.offset }}
onDoubleClick={() => this.onDoubleClick()}
onMouseDown={e => this.onMouseDown(e as any)}
onMouseUp={e => this.onMouseUp(e as any)}
onContextMenu={e => this.onContextMenu(e as any)}
>
<UnreadMarker entry={this.props.client} />
@ -403,10 +403,11 @@ export class ClientEntry extends TreeEntry<ClientEntryProperties, ClientEntrySta
this.setState({ rename: false });
}
private onMouseDown(event: MouseEvent) {
private onMouseUp(event: MouseEvent) {
if(event.button !== 0) return; /* only left mouse clicks */
const tree = this.props.client.channelTree;
if(tree.isClientMoveActive()) return;
tree.events.fire("action_select_entries", { entries: [this.props.client], mode: "auto" });
}

View File

@ -77,7 +77,7 @@ export class ServerEntry extends TreeEntry<ServerEntryProperties, ServerEntrySta
return <div className={this.classList(serverStyle.serverEntry, viewStyle.treeEntry, this.props.server.isSelected() && viewStyle.selected )}
style={{ top: this.props.offset }}
onMouseDown={e => this.onMouseDown(e as any)}
onMouseUp={e => this.onMouseUp(e as any)}
onContextMenu={e => this.onContextMenu(e as any)}
>
<UnreadMarker entry={this.props.server} />
@ -87,8 +87,9 @@ export class ServerEntry extends TreeEntry<ServerEntryProperties, ServerEntrySta
</div>
}
private onMouseDown(event: MouseEvent) {
private onMouseUp(event: MouseEvent) {
if(event.button !== 0) return; /* only left mouse clicks */
if(this.props.server.channelTree.isClientMoveActive()) return;
this.props.server.channelTree.events.fire("action_select_entries", {
entries: [ this.props.server ],

View File

@ -0,0 +1,22 @@
html:root {
--channel-tree-move-color: hsla(220, 5%, 2%, 1);
--channel-tree-move-background: hsla(0, 0%, 25%, 1);
--channel-tree-move-border: hsla(220, 4%, 40%, 1);
}
.moveContainer {
position: absolute;
display: block;
border: 2px solid var(--channel-tree-move-border);
background-color: var(--channel-tree-move-background);
z-index: 10000;
margin-left: 5px;
padding-left: .25em;
padding-right: .25em;
border-radius: 2px;
color: var(--channel-tree-move-color);
}

View File

@ -0,0 +1,95 @@
import {ReactComponentBase} from "tc-shared/ui/react-elements/ReactComponentBase";
import * as React from "react";
import * as ReactDOM from "react-dom";
import {ChannelTreeView} from "tc-shared/ui/tree/View";
const moveStyle = require("./TreeEntryMove.scss");
export interface TreeEntryMoveProps {
onMoveEnd: (point: { x: number, y: number }) => void;
}
export interface TreeEntryMoveState {
tree_view: ChannelTreeView;
begin: { x: number, y: number };
description: string;
}
export class TreeEntryMove extends ReactComponentBase<TreeEntryMoveProps, TreeEntryMoveState> {
private readonly domContainer;
private readonly document_mouse_out_listener;
private readonly document_mouse_listener;
private readonly ref_container: React.RefObject<HTMLDivElement>;
private current: { x: number, y: number };
constructor(props) {
super(props);
this.ref_container = React.createRef();
this.domContainer = document.getElementById("mouse-move");
this.document_mouse_out_listener = (e: MouseEvent) => {
if(e.type === "mouseup") {
if(e.button !== 0) return;
this.props.onMoveEnd({ x: e.pageX, y: e.pageY });
}
this.disableEntryMove();
};
this.document_mouse_listener = (e: MouseEvent) => {
this.current = { x: e.pageX, y: e.pageY };
const container = this.ref_container.current;
if(!container) return;
container.style.top = e.pageY + "px";
container.style.left = e.pageX + "px";
};
}
enableEntryMove(view: ChannelTreeView, description: string, begin: { x: number, y: null }, current: { x: number, y: null }, callback_enabled?: () => void) {
this.setState({
tree_view: view,
begin: begin,
description: description
}, callback_enabled);
this.current = current;
document.addEventListener("mousemove", this.document_mouse_listener);
document.addEventListener("mouseleave", this.document_mouse_out_listener);
document.addEventListener("mouseup", this.document_mouse_out_listener);
}
private disableEntryMove() {
this.setState({
tree_view: null
});
document.removeEventListener("mousemove", this.document_mouse_listener);
document.removeEventListener("mouseleave", this.document_mouse_out_listener);
document.removeEventListener("mouseup", this.document_mouse_out_listener);
}
protected defaultState(): TreeEntryMoveState {
return {
tree_view: null,
begin: { x: 0, y: 0},
description: ""
}
}
isActive() { return !!this.state.tree_view; }
render() {
if(!this.state.tree_view)
return null;
return ReactDOM.createPortal(this.renderPortal(), this.domContainer);
}
private renderPortal() {
return <div style={{ top: this.current.y, left: this.current.x }} className={moveStyle.moveContainer} ref={this.ref_container} >
{this.state.description}
</div>;
}
}

View File

@ -2,6 +2,7 @@
@import "../../../css/static/mixin";
html:root {
--channel-tree-entry-move: #313235;
--channel-tree-entry-selected: #2d2d2d;
--channel-tree-entry-hovered: #393939;
--channel-tree-entry-color: #828282;
@ -41,7 +42,6 @@ html:root {
line-height: 1;
}
.treeEntry {
position: absolute;
left: 0;
@ -98,6 +98,12 @@ html:root {
@include transition(opacity $button_hover_animation_time);
}
}
&.move {
.treeEntry.selected {
background-color: var(--channel-tree-entry-move);
}
}
}
.channelTreeContainer {

View File

@ -16,13 +16,15 @@ import {ClientEntry as ClientEntryView} from "./Client";
import {ChannelEntry} from "tc-shared/ui/channel";
import {ServerEntry} from "tc-shared/ui/server";
import {ClientEntry, LocalClientEntry} from "tc-shared/ui/client";
import {ClientEntry, ClientType} from "tc-shared/ui/client";
const viewStyle = require("./View.scss");
export interface ChannelTreeViewProperties {
tree: ChannelTree;
onMoveStart: (start: { x: number, y: number }, current: { x: number, y: number }) => void;
moveThreshold?: number;
}
export interface ChannelTreeViewState {
@ -52,6 +54,9 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
private listener_state_collapsed;
private update_timeout;
private mouse_move: { x: number, y: number, down: boolean, fired: boolean } = { x: 0, y: 0, down: false, fired: false };
private document_mouse_listener;
private in_view_callbacks: {
index: number,
callback: () => void,
@ -95,6 +100,26 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
this.listener_client_change = () => this.handleTreeUpdate();
this.listener_channel_change = () => this.handleTreeUpdate();
this.listener_state_collapsed = () => this.handleTreeUpdate();
this.document_mouse_listener = (e: MouseEvent) => {
if(e.type !== "mouseleave" && e.button !== 0)
return;
this.mouse_move.down = false;
this.mouse_move.fired = false;
this.removeDocumentMouseListener();
}
}
private registerDocumentMouseListener() {
document.addEventListener("mouseleave", this.document_mouse_listener);
document.addEventListener("mouseup", this.document_mouse_listener);
}
private removeDocumentMouseListener() {
document.removeEventListener("mouseleave", this.document_mouse_listener);
document.removeEventListener("mouseup", this.document_mouse_listener);
}
private handleTreeUpdate() {
@ -137,8 +162,8 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
}
return (
<div className={viewStyle.channelTreeContainer} onScroll={() => this.onScroll()} ref={this.ref_container} >
<div className={viewStyle.channelTree} style={{height: (this.flat_tree.length * ChannelTreeView.EntryHeight) + "px"}}>
<div className={viewStyle.channelTreeContainer} onScroll={() => this.onScroll()} ref={this.ref_container} onMouseDown={e => this.onMouseDown(e as any)} onMouseMove={e => this.onMouseMove(e as any)} >
<div className={this.classList(viewStyle.channelTree, this.props.tree.isClientMoveActive() && viewStyle.move)} style={{height: (this.flat_tree.length * ChannelTreeView.EntryHeight) + "px"}}>
{elements}
</div>
</div>
@ -157,7 +182,10 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
});
if(entry.collapsed) return;
this.flat_tree.push(...entry.clients(false).map(e => {
let clients = entry.clients(false);
if(!this.props.tree.areServerQueriesShown())
clients = clients.filter(e => e.properties.client_type_exact !== ClientType.CLIENT_QUERY);
this.flat_tree.push(...clients.map(e => {
return {
entry: e,
rendered: <ClientEntryView key={"client-" + e.clientId()} client={e} offset={this.build_top_offset += ChannelTreeView.EntryHeight} depth={depth + 1} ref={e.view} />
@ -195,12 +223,44 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
this.handleTreeUpdate();
}
@EventHandler<ChannelTreeEvents>("notify_query_view_state_changed")
private handleQueryViewStateChange() {
this.handleTreeUpdate();
}
@EventHandler<ChannelTreeEvents>("notify_entry_move_begin")
private handleEntryMoveBegin() {
this.handleTreeUpdate();
}
@EventHandler<ChannelTreeEvents>("notify_entry_move_end")
private handleEntryMoveEnd() {
this.handleTreeUpdate();
}
private onScroll() {
this.setState({
scroll_offset: this.ref_container.current.scrollTop
});
}
private onMouseDown(e: MouseEvent) {
if(e.button !== 0) return; /* left button only */
this.mouse_move.down = true;
this.mouse_move.x = e.pageX;
this.mouse_move.y = e.pageY;
this.registerDocumentMouseListener();
}
private onMouseMove(e: MouseEvent) {
if(!this.mouse_move.down || this.mouse_move.fired) return;
if(Math.abs((this.mouse_move.x - e.pageX) * (this.mouse_move.y - e.pageY)) > (this.props.moveThreshold || 9)) {
this.mouse_move.fired = true;
this.props.onMoveStart({x: this.mouse_move.x, y: this.mouse_move.y}, {x: e.pageX, y: e.pageY});
}
}
scrollEntryInView(entry: TreeEntry, callback?: () => void) {
const index = this.flat_tree.findIndex(e => e.entry === entry);
if(index === -1) {
@ -234,4 +294,22 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
this.in_view_callbacks.push(cb);
}
}
getEntryFromPoint(pageX: number, pageY: number) {
const container = this.ref_container.current;
if(!container) return;
const bounds = container.getBoundingClientRect();
pageY -= bounds.y;
pageX -= bounds.x;
if(pageX < 0 || pageY < 0)
return undefined;
if(pageX > container.clientWidth)
return undefined;
const total_offset = container.scrollTop + pageY;
return this.flat_tree[Math.floor(total_offset / ChannelTreeView.EntryHeight)].entry;
}
}

View File

@ -9,7 +9,6 @@ import {Sound} from "tc-shared/sound/Sounds";
import {Group} from "tc-shared/permission/GroupManager";
import * as server_log from "tc-shared/ui/frames/server_log";
import {ServerAddress, ServerEntry} from "tc-shared/ui/server";
import {ClientMover} from "tc-shared/ui/client_move";
import {ChannelEntry, ChannelSubscribeMode} from "tc-shared/ui/channel";
import {ClientEntry, LocalClientEntry, MusicClientEntry} from "tc-shared/ui/client";
import {ConnectionHandler, ViewReasonId} from "tc-shared/ConnectionHandler";
@ -27,6 +26,7 @@ import {spawnBanClient} from "tc-shared/ui/modal/ModalBanClient";
import {formatMessage} from "tc-shared/ui/frames/chat";
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
import {tra} from "tc-shared/i18n/localize";
import {TreeEntryMove} from "tc-shared/ui/tree/TreeEntryMove";
export interface ChannelTreeEvents {
action_select_entries: {
@ -41,7 +41,11 @@ export interface ChannelTreeEvents {
},
notify_selection_changed: {},
notify_root_channel_changed: {}
notify_root_channel_changed: {},
notify_query_view_state_changed: { queries_shown: boolean },
notify_entry_move_begin: {},
notify_entry_move_end: {}
}
export class ChannelTreeEntrySelect {
@ -187,9 +191,8 @@ export class ChannelTree {
channels: ChannelEntry[] = [];
clients: ClientEntry[] = [];
readonly client_mover: ClientMover;
readonly view: React.RefObject<ChannelTreeView>;
readonly view_move: React.RefObject<TreeEntryMove>;
readonly selection: ChannelTreeEntrySelect;
private readonly _tag_container: JQuery;
@ -206,14 +209,17 @@ export class ChannelTree {
this.events = new Registry<ChannelTreeEvents>();
this.client = client;
this.view = React.createRef();
this.view_move = React.createRef();
this.server = new ServerEntry(this, "undefined", undefined);
this.selection = new ChannelTreeEntrySelect(this);
this._tag_container = $.spawn("div").addClass("channel-tree-container");
ReactDOM.render(<ChannelTreeView tree={this} ref={this.view} />, this._tag_container[0]);
ReactDOM.render([
<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} />
], this._tag_container[0]);
this.client_mover = new ClientMover(this);
this.reset();
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
@ -291,6 +297,7 @@ export class ChannelTree {
invalidPermission: !channelCreate,
callback: () => this.spawnCreateChannel()
},
contextmenu.Entry.HR(),
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_collapse_all",
@ -1001,20 +1008,9 @@ export class ChannelTree {
if(this._show_queries == flag) return;
this._show_queries = flag;
//TODO: FIXME!
/*
const channels: ChannelEntry[] = []
for(const client of this.clients)
if(client.properties.client_type == ClientType.CLIENT_QUERY) {
if(this._show_queries)
client.tag.show();
else
client.tag.hide();
if(channels.indexOf(client.currentChannel()) == -1)
channels.push(client.currentChannel());
}
*/
this.events.fire("notify_query_view_state_changed", { queries_shown: flag });
}
areServerQueriesShown() { return this._show_queries; }
get_first_channel?() : ChannelEntry {
return this.channel_first;
@ -1081,4 +1077,53 @@ 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();
}
}