Fixed a minor channel tree select bug.

canary
WolverinDEV 2020-04-21 16:17:21 +02:00
parent 2019cfe549
commit f18957e75e
8 changed files with 59 additions and 47 deletions

View File

@ -32,7 +32,6 @@ import * as dns from "tc-backend/dns";
import * as top_menu from "tc-shared/ui/frames/MenuBar";
import {EventHandler, Registry} from "tc-shared/events";
import {ServerLog} from "tc-shared/ui/frames/server_log";
import {server} from "websocket";
export enum DisconnectReason {
HANDLER_DESTROYED,
@ -639,6 +638,7 @@ export class ConnectionHandler {
this.serverConnection.disconnect();
this.side_bar.private_conversations().clear_client_ids();
this.side_bar.channel_conversations().set_current_channel(0);
this.hostbanner.update();
if(auto_reconnect) {

View File

@ -50,6 +50,8 @@ export abstract class AbstractServerConnection {
//FIXME: Remove this this is currently only some kind of hack
updateConnectionState(state: ConnectionState) {
if(state === this.connection_state_) return;
const old_state = this.connection_state_;
this.connection_state_ = state;
if(this.onconnectionstatechanged)

View File

@ -1,5 +1,4 @@
import {ClientEvents, MusicClientEntry, SongInfo} from "tc-shared/ui/client";
import {PlaylistSong} from "tc-shared/connection/ServerConnectionDeclaration";
import {guid} from "tc-shared/crypto/uid";
import * as React from "react";
@ -232,7 +231,11 @@ export function ReactEventHandler<ObjectClass = React.Component<any, any>, Event
constructor.prototype.componentWillUnmount = function () {
const registry = registry_callback(this);
if(!registry) throw "Event registry returned for an event object is invalid";
try {
registry.unregister_handler(this);
} catch (error) {
console.warn("Failed to unregister event handler: %o", error);
}
if(typeof willUnmount === "function")
willUnmount.call(this, arguments);

View File

@ -29,8 +29,7 @@ import * as React from "react";
import * as ReactDOM from "react-dom";
import * as cbar from "./ui/frames/control-bar";
import * as global_ev_handler from "./events/ClientGlobalControlHandler";
import {ClientGlobalControlEvents, global_client_actions} from "tc-shared/events/GlobalEvents";
import {spawnSettingsModal} from "tc-shared/ui/modal/ModalSettings";
import {global_client_actions} from "tc-shared/events/GlobalEvents";
/* required import for init */
require("./proto").initialize();

View File

@ -38,7 +38,9 @@ export class LocalIconRenderer extends React.Component<LoadedIconRenderer, {}> {
render() {
const icon = this.props.icon;
if(icon.status === "loaded") {
if(!icon || icon.status === "empty" || icon.status === "destroyed")
return <div className={"icon-container icon-empty"} title={this.props.title} />;
else if(icon.status === "loaded") {
if(icon.icon_id >= 0 && icon.icon_id <= 1000) {
if(icon.icon_id === 0)
return <div className={"icon-container icon-empty"} title={this.props.title} />;
@ -49,15 +51,13 @@ export class LocalIconRenderer extends React.Component<LoadedIconRenderer, {}> {
return <div key={"loading"} className={"icon-container"} title={this.props.title}><div className={"icon_loading"} /></div>;
else if(icon.status === "error")
return <div key={"error"} className={"icon client-warning"} title={icon.error_message || tr("Failed to load icon")} />;
else if(icon.status === "empty" || icon.status === "destroyed")
return <div className={"icon-container icon-empty"} title={this.props.title} />;
}
componentDidMount(): void {
this.props.icon.status_change_callbacks.push(this.callback_state_update);
this.props.icon?.status_change_callbacks.push(this.callback_state_update);
}
componentWillUnmount(): void {
this.props.icon.status_change_callbacks.remove(this.callback_state_update);
this.props.icon?.status_change_callbacks.remove(this.callback_state_update);
}
}

View File

@ -28,6 +28,7 @@ let update_batches: {[key: number]:UpdateBatch} = {
0: generate_batch(),
1: generate_batch()
};
(window as any).update_batches = update_batches;
export function BatchUpdateAssignment(type: BatchUpdateType) {
return function (constructor: Function) {

View File

@ -31,6 +31,8 @@ export interface ChannelTreeViewState {
element_scroll_offset?: number; /* in px */
scroll_offset: number; /* in px */
view_height: number; /* in px */
tree_version: number;
}
type TreeEntry = ChannelEntry | ServerEntry | ClientEntry;
@ -67,6 +69,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
return {
scroll_offset: 0,
view_height: 0,
tree_version: 0
};
}
@ -238,6 +241,14 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
this.handleTreeUpdate();
}
@EventHandler<ChannelTreeEvents>("notify_tree_reset")
private handleTreeReset() {
this.rebuild_tree();
this.setState({
tree_version: this.state.tree_version + 1
});
}
private onScroll() {
this.setState({
scroll_offset: this.ref_container.current.scrollTop

View File

@ -42,6 +42,7 @@ export interface ChannelTreeEvents {
notify_selection_changed: {},
notify_root_channel_changed: {},
notify_tree_reset: {},
notify_query_view_state_changed: { queries_shown: boolean },
notify_entry_move_begin: {},
@ -283,37 +284,6 @@ export class ChannelTree {
this.events.destroy();
}
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
let channelCreate =
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_TEMPORARY).granted(1) ||
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_SEMI_PERMANENT).granted(1) ||
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_PERMANENT).granted(1);
contextmenu.spawn_context_menu(x, y,
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_create",
name: tr("Create channel"),
invalidPermission: !channelCreate,
callback: () => this.spawnCreateChannel()
},
contextmenu.Entry.HR(),
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_collapse_all",
name: tr("Collapse all channels"),
callback: () => this.collapse_channels()
},
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_expand_all",
name: tr("Expend all channels"),
callback: () => this.expand_channels()
},
contextmenu.Entry.CLOSE(on_close)
);
}
initialiseHead(serverName: string, address: ServerAddress) {
this.server.reset();
this.server.remote_address = Object.assign({}, address);
@ -572,6 +542,36 @@ export class ChannelTree {
return undefined;
}
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
let channelCreate =
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_TEMPORARY).granted(1) ||
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_SEMI_PERMANENT).granted(1) ||
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_PERMANENT).granted(1);
contextmenu.spawn_context_menu(x, y,
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_create",
name: tr("Create channel"),
invalidPermission: !channelCreate,
callback: () => this.spawnCreateChannel()
},
contextmenu.Entry.HR(),
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_collapse_all",
name: tr("Collapse all channels"),
callback: () => this.collapse_channels()
},
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_expand_all",
name: tr("Expend all channels"),
callback: () => this.expand_channels()
},
contextmenu.Entry.CLOSE(on_close)
);
}
private open_multiselect_context_menu(entries: ChannelTreeEntry<any>[], x: number, y: number) {
const clients = entries.filter(e => e instanceof ClientEntry) as ClientEntry[];
const channels = entries.filter(e => e instanceof ChannelEntry) as ChannelEntry[];
@ -811,13 +811,9 @@ export class ChannelTree {
this.channels = [];
this.channel_last = undefined;
this.channel_first = undefined;
this.events.fire("notify_tree_reset");
} finally {
try {
this.events.fire_async("notify_root_channel_changed", undefined, () => flush_batched_updates(BatchUpdateType.CHANNEL_TREE));
} catch (e) {
flush_batched_updates(BatchUpdateType.CHANNEL_TREE);
throw e;
}
}
}