Adding a channel popout/popin button for the channel popout renderer
parent
bf8c6ed857
commit
583cdd146e
|
@ -50,24 +50,6 @@ export default class implements ApplicationLoader {
|
|||
container.setAttribute('id', "sounds");
|
||||
body.append(container);
|
||||
}
|
||||
|
||||
/* mouse move container */
|
||||
{
|
||||
const container = document.createElement("div");
|
||||
container.setAttribute('id', "mouse-move");
|
||||
|
||||
body.append(container);
|
||||
}
|
||||
|
||||
/* tooltip container */
|
||||
{
|
||||
const container = document.createElement("div");
|
||||
container.setAttribute('id', "global-tooltip");
|
||||
|
||||
container.append(document.createElement("a"));
|
||||
|
||||
body.append(container);
|
||||
}
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
|
|||
import {tra} from "tc-shared/i18n/localize";
|
||||
import {EventType} from "tc-shared/ui/frames/log/Definitions";
|
||||
import {renderChannelTree} from "tc-shared/ui/tree/Controller";
|
||||
import {ChannelTreePopoutController} from "tc-shared/ui/tree/popout/Controller";
|
||||
|
||||
export interface ChannelTreeEvents {
|
||||
action_select_entries: {
|
||||
|
@ -43,6 +44,7 @@ export interface ChannelTreeEvents {
|
|||
notify_tree_reset: {},
|
||||
notify_selection_changed: {},
|
||||
notify_query_view_state_changed: { queries_shown: boolean },
|
||||
notify_popout_state_changed: { popoutShown: boolean },
|
||||
|
||||
notify_entry_move_begin: {},
|
||||
notify_entry_move_end: {},
|
||||
|
@ -231,19 +233,18 @@ export class ChannelTree {
|
|||
/* whatever all channels have been initiaized */
|
||||
channelsInitialized: boolean = false;
|
||||
|
||||
//readonly view: React.RefObject<ChannelTreeView>;
|
||||
//readonly view_move: React.RefObject<TreeEntryMove>;
|
||||
readonly selection: ChannelTreeEntrySelect;
|
||||
readonly popoutController: ChannelTreePopoutController;
|
||||
|
||||
private readonly _tag_container: JQuery;
|
||||
private readonly tagContainer: JQuery;
|
||||
|
||||
private _show_queries: boolean;
|
||||
private channel_last?: ChannelEntry;
|
||||
private channel_first?: ChannelEntry;
|
||||
|
||||
private _tag_container_focused = false;
|
||||
private _listener_document_click;
|
||||
private _listener_document_key;
|
||||
private tagContainerFocused = false;
|
||||
private listenerDocumentClick;
|
||||
private listenerDocumentKeyPress;
|
||||
|
||||
constructor(client) {
|
||||
this.events = new Registry<ChannelTreeEvents>();
|
||||
|
@ -253,21 +254,16 @@ export class ChannelTree {
|
|||
|
||||
this.server = new ServerEntry(this, "undefined", undefined);
|
||||
this.selection = new ChannelTreeEntrySelect(this);
|
||||
this.popoutController = new ChannelTreePopoutController(this);
|
||||
|
||||
this._tag_container = $.spawn("div").addClass("channel-tree-container");
|
||||
renderChannelTree(this, 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.tagContainer = $.spawn("div").addClass("channel-tree-container");
|
||||
renderChannelTree(this, this.tagContainer[0], { popoutButton: true });
|
||||
|
||||
this.reset();
|
||||
|
||||
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
||||
/*
|
||||
TODO: Move this into the channel tree renderer
|
||||
TODO: Show the context menu when clicked on no channel
|
||||
this._tag_container.on("contextmenu", (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
|
@ -284,24 +280,25 @@ export class ChannelTree {
|
|||
*/
|
||||
}
|
||||
|
||||
this._listener_document_key = event => this.handle_key_press(event);
|
||||
this._listener_document_click = event => {
|
||||
this._tag_container_focused = false;
|
||||
/* 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._tag_container[0]) {
|
||||
this._tag_container_focused = true;
|
||||
if(element === this.tagContainer[0]) {
|
||||
this.tagContainerFocused = true;
|
||||
break;
|
||||
}
|
||||
element = element.parentNode as HTMLElement;
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', this._listener_document_click);
|
||||
document.addEventListener('keydown', this._listener_document_key);
|
||||
document.addEventListener('click', this.listenerDocumentClick);
|
||||
document.addEventListener('keydown', this.listenerDocumentKeyPress);
|
||||
}
|
||||
|
||||
tag_tree() : JQuery {
|
||||
return this._tag_container;
|
||||
return this.tagContainer;
|
||||
}
|
||||
|
||||
channelsOrdered() : ChannelEntry[] {
|
||||
|
@ -337,13 +334,13 @@ export class ChannelTree {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
ReactDOM.unmountComponentAtNode(this._tag_container[0]);
|
||||
ReactDOM.unmountComponentAtNode(this.tagContainer[0]);
|
||||
|
||||
this._listener_document_click && document.removeEventListener('click', this._listener_document_click);
|
||||
this._listener_document_click = undefined;
|
||||
this.listenerDocumentClick && document.removeEventListener('click', this.listenerDocumentClick);
|
||||
this.listenerDocumentClick = undefined;
|
||||
|
||||
this._listener_document_key && document.removeEventListener('keydown', this._listener_document_key);
|
||||
this._listener_document_key = undefined;
|
||||
this.listenerDocumentKeyPress && document.removeEventListener('keydown', this.listenerDocumentKeyPress);
|
||||
this.listenerDocumentKeyPress = undefined;
|
||||
|
||||
if(this.server) {
|
||||
this.server.destroy();
|
||||
|
@ -354,7 +351,8 @@ export class ChannelTree {
|
|||
this.channel_first = undefined;
|
||||
this.channel_last = undefined;
|
||||
|
||||
this._tag_container.remove();
|
||||
this.popoutController.destroy();
|
||||
this.tagContainer.remove();
|
||||
this.selection.destroy();
|
||||
this.events.destroy();
|
||||
}
|
||||
|
@ -992,7 +990,7 @@ export class ChannelTree {
|
|||
}
|
||||
|
||||
handle_key_press(event: KeyboardEvent) {
|
||||
if(!this._tag_container_focused || !this.selection.is_anything_selected() || this.selection.is_multi_select()) return;
|
||||
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) {
|
||||
|
|
|
@ -301,7 +301,7 @@ const QueryButton = () => {
|
|||
>
|
||||
{toggle}
|
||||
<DropdownEntry icon={ClientIcon.ServerQuery} text={<Translatable>Manage server queries</Translatable>}
|
||||
onClick={() => events.fire("action_query_manage")}/>
|
||||
onClick={() => events.fire("action_query_manage")} key={"manage-entries"} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
@ -347,16 +347,16 @@ export const ControlBar2 = (props: { events: Registry<ControlBarEvents>, classNa
|
|||
|
||||
if(mode !== "channel-popout") {
|
||||
items.push(<ConnectButton key={"connect"} />);
|
||||
items.push(<BookmarkButton key={"bookmarks"} />);
|
||||
items.push(<div className={cssStyle.divider + " " + cssStyle.hideSmallPopout} key={"divider-1"} />);
|
||||
}
|
||||
items.push(<BookmarkButton key={"bookmarks"} />);
|
||||
items.push(<div className={cssStyle.divider + " " + cssStyle.hideSmallPopout} />);
|
||||
items.push(<AwayButton key={"away"} />);
|
||||
items.push(<MicrophoneButton key={"microphone"} />);
|
||||
items.push(<SpeakerButton key={"speaker"} />);
|
||||
items.push(<div className={cssStyle.divider + " " + cssStyle.hideSmallPopout} />);
|
||||
items.push(<div className={cssStyle.divider + " " + cssStyle.hideSmallPopout} key={"divider-2"} />);
|
||||
items.push(<SubscribeButton key={"subscribe"} />);
|
||||
items.push(<QueryButton key={"query"} />);
|
||||
items.push(<div className={cssStyle.spacer} />);
|
||||
items.push(<div className={cssStyle.spacer} key={"spacer"} />);
|
||||
items.push(<HostButton key={"hostbutton"} />);
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
.empty {
|
||||
/* legacy values, we're using em now */
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
|
@ -2,13 +2,15 @@ import * as React from "react";
|
|||
import {RemoteIcon} from "tc-shared/file/Icons";
|
||||
import {useState} from "react";
|
||||
|
||||
const cssStyle = require("./Icon.scss");
|
||||
|
||||
export const IconRenderer = (props: {
|
||||
icon: string;
|
||||
title?: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
if(!props.icon) {
|
||||
return <div className={"icon-container icon-empty " + props.className} title={props.title} />;
|
||||
return <div className={cssStyle.empty + " icon-container icon-empty " + props.className} title={props.title} />;
|
||||
} else if(typeof props.icon === "string") {
|
||||
return <div className={"icon " + props.icon + " " + props.className} title={props.title} />;
|
||||
} else {
|
||||
|
|
|
@ -40,7 +40,7 @@ export abstract class AbstractModal {
|
|||
protected constructor() {}
|
||||
|
||||
abstract renderBody() : ReactElement;
|
||||
abstract title() : string | React.ReactElement<Translatable>;
|
||||
abstract title() : string | React.ReactElement;
|
||||
|
||||
/* only valid for the "inline" modals */
|
||||
type() : ModalType { return "none"; }
|
||||
|
|
|
@ -108,8 +108,9 @@ export abstract class AbstractExternalModalController extends EventControllerBas
|
|||
return;
|
||||
|
||||
this.doDestroyWindow();
|
||||
if(this.ipcChannel)
|
||||
if(this.ipcChannel) {
|
||||
ipc.getInstance().deleteChannel(this.ipcChannel);
|
||||
}
|
||||
|
||||
this.destroyIPC();
|
||||
this.modalState = ModalState.DESTROYED;
|
||||
|
@ -117,6 +118,7 @@ export abstract class AbstractExternalModalController extends EventControllerBas
|
|||
}
|
||||
|
||||
protected handleWindowClosed() {
|
||||
/* no other way currently */
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,13 +19,16 @@ import {VoiceConnectionEvents, VoiceConnectionStatus} from "tc-shared/connection
|
|||
import {spawnFileTransferModal} from "tc-shared/ui/modal/transfer/ModalFileTransfer";
|
||||
import {GroupManager, GroupManagerEvents} from "tc-shared/permission/GroupManager";
|
||||
import {ServerEntry} from "tc-shared/tree/Server";
|
||||
import {spawnChannelTreePopout} from "tc-shared/ui/tree/popout/Controller";
|
||||
import {server_connections} from "tc-shared/ConnectionManager";
|
||||
|
||||
export function renderChannelTree(channelTree: ChannelTree, target: HTMLElement) {
|
||||
export interface ChannelTreeRendererOptions {
|
||||
popoutButton: boolean;
|
||||
}
|
||||
|
||||
export function renderChannelTree(channelTree: ChannelTree, target: HTMLElement, options: ChannelTreeRendererOptions) {
|
||||
const events = new Registry<ChannelTreeUIEvents>();
|
||||
events.enableDebug("channel-tree-view");
|
||||
initializeChannelTreeController(events, channelTree);
|
||||
initializeChannelTreeController(events, channelTree, options);
|
||||
|
||||
ReactDOM.render(<ChannelTreeRenderer handlerId={channelTree.client.handlerId} events={events} />, target);
|
||||
|
||||
|
@ -40,10 +43,6 @@ export function renderChannelTree(channelTree: ChannelTree, target: HTMLElement)
|
|||
events.fire("notify_destroy");
|
||||
events.destroy();
|
||||
});
|
||||
|
||||
(window as any).chan_pop = () => {
|
||||
spawnChannelTreePopout(channelTree.client);
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: Client move is not a part of the channel tree, it's part of our own controller here */
|
||||
|
@ -86,6 +85,7 @@ const ClientTalkStatusUpdateKeys: (keyof ClientProperties)[] = [
|
|||
class ChannelTreeController {
|
||||
readonly events: Registry<ChannelTreeUIEvents>;
|
||||
readonly channelTree: ChannelTree;
|
||||
readonly options: ChannelTreeRendererOptions;
|
||||
|
||||
/* the key here is the unique entry id! */
|
||||
private eventListeners: {[key: number]: (() => void)[]} = {};
|
||||
|
@ -96,9 +96,10 @@ class ChannelTreeController {
|
|||
private readonly groupUpdatedListener;
|
||||
private readonly groupsReceivedListener;
|
||||
|
||||
constructor(events, channelTree) {
|
||||
constructor(events, channelTree, options: ChannelTreeRendererOptions) {
|
||||
this.events = events;
|
||||
this.channelTree = channelTree;
|
||||
this.options = options;
|
||||
|
||||
this.connectionStateListener = this.handleConnectionStateChanged.bind(this);
|
||||
this.voiceConnectionStateListener = this.handleVoiceConnectionStateChanged.bind(this);
|
||||
|
@ -184,6 +185,11 @@ class ChannelTreeController {
|
|||
}
|
||||
|
||||
/* general channel tree event handlers */
|
||||
@EventHandler<ChannelTreeEvents>("notify_popout_state_changed")
|
||||
private handlePoputStateChanged() {
|
||||
this.sendPopoutState();
|
||||
}
|
||||
|
||||
@EventHandler<ChannelTreeEvents>("notify_channel_list_received")
|
||||
private handleChannelListReceived() {
|
||||
this.channelTreeInitialized = true;
|
||||
|
@ -338,6 +344,13 @@ class ChannelTreeController {
|
|||
}
|
||||
|
||||
/* notify state update methods */
|
||||
public sendPopoutState() {
|
||||
this.events.fire_async("notify_popout_state", {
|
||||
showButton: this.options.popoutButton,
|
||||
shown: this.channelTree.popoutController.hasBeenPopedOut()
|
||||
});
|
||||
}
|
||||
|
||||
public sendChannelTreeEntries() {
|
||||
const entries = [] as ChannelTreeEntry[];
|
||||
|
||||
|
@ -520,13 +533,14 @@ class ChannelTreeController {
|
|||
}
|
||||
}
|
||||
|
||||
export function initializeChannelTreeController(events: Registry<ChannelTreeUIEvents>, channelTree: ChannelTree) {
|
||||
export function initializeChannelTreeController(events: Registry<ChannelTreeUIEvents>, channelTree: ChannelTree, options: ChannelTreeRendererOptions) {
|
||||
/* initialize the general update handler */
|
||||
const controller = new ChannelTreeController(events, channelTree);
|
||||
const controller = new ChannelTreeController(events, channelTree, options);
|
||||
controller.initialize();
|
||||
events.on("notify_destroy", () => controller.destroy());
|
||||
|
||||
/* initialize the query handlers */
|
||||
events.on("query_popout_state", () => controller.sendPopoutState());
|
||||
|
||||
events.on("query_unread_state", event => {
|
||||
const entry = channelTree.findEntryId(event.treeEntryId);
|
||||
|
@ -624,6 +638,14 @@ export function initializeChannelTreeController(events: Registry<ChannelTreeUIEv
|
|||
controller.sendServerStatus(entry);
|
||||
});
|
||||
|
||||
events.on("action_toggle_popout", event => {
|
||||
if(event.shown) {
|
||||
channelTree.popoutController.popout();
|
||||
} else {
|
||||
channelTree.popoutController.popin();
|
||||
}
|
||||
})
|
||||
|
||||
events.on("action_set_collapsed_state", event => {
|
||||
const entry = channelTree.findEntryId(event.treeEntryId);
|
||||
if(!entry || !(entry instanceof ChannelEntry)) {
|
||||
|
|
|
@ -28,6 +28,7 @@ export type ServerState = { state: "disconnected" } | { state: "connecting", tar
|
|||
|
||||
export interface ChannelTreeUIEvents {
|
||||
/* actions */
|
||||
action_toggle_popout: { shown: boolean },
|
||||
action_show_context_menu: { treeEntryId: number, pageX: number, pageY: number },
|
||||
action_start_entry_move: { start: { x: number, y: number }, current: { x: number, y: number } },
|
||||
action_set_collapsed_state: { treeEntryId: number, state: "collapsed" | "expended" },
|
||||
|
@ -44,6 +45,8 @@ export interface ChannelTreeUIEvents {
|
|||
|
||||
/* queries */
|
||||
query_tree_entries: {},
|
||||
query_popout_state: {},
|
||||
|
||||
query_unread_state: { treeEntryId: number },
|
||||
query_select_state: { treeEntryId: number },
|
||||
|
||||
|
@ -60,6 +63,7 @@ export interface ChannelTreeUIEvents {
|
|||
|
||||
/* notifies */
|
||||
notify_tree_entries: { entries: ChannelTreeEntry[] },
|
||||
notify_popout_state: { shown: boolean, showButton: boolean },
|
||||
|
||||
notify_channel_info: { treeEntryId: number, info: ChannelEntryInfo },
|
||||
notify_channel_icon: { treeEntryId: number, icon: ClientIcon },
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
ClientIcons,
|
||||
ClientNameInfo, ClientTalkIconState, ServerState
|
||||
} from "tc-shared/ui/tree/Definitions";
|
||||
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
|
||||
import {ChannelTreeView, PopoutButton} from "tc-shared/ui/tree/RendererView";
|
||||
import * as React from "react";
|
||||
import {ChannelIconClass, ChannelIconsRenderer, RendererChannel} from "tc-shared/ui/tree/RendererChannel";
|
||||
import {ClientIcon} from "svg-sprites/client-icons";
|
||||
|
@ -69,6 +69,10 @@ export class RDPChannelTree {
|
|||
|
||||
readonly refMove = React.createRef<RendererMove>();
|
||||
readonly refTree = React.createRef<ChannelTreeView>();
|
||||
readonly refPopoutButton = React.createRef<PopoutButton>();
|
||||
|
||||
popoutShown: boolean = false;
|
||||
popoutButtonShown: boolean = false;
|
||||
|
||||
private treeRevision: number = 0;
|
||||
private orderedTree: RDPEntry[] = [];
|
||||
|
@ -198,6 +202,7 @@ export class RDPChannelTree {
|
|||
}));
|
||||
|
||||
this.events.fire("query_tree_entries");
|
||||
this.events.fire("query_popout_state");
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -268,6 +273,13 @@ export class RDPChannelTree {
|
|||
|
||||
this.refMove.current.enableEntryMove(event.entries, event.begin, event.current);
|
||||
}
|
||||
|
||||
@EventHandler<ChannelTreeUIEvents>("notify_popout_state")
|
||||
private handleNotifyPopoutState(event: ChannelTreeUIEvents["notify_popout_state"]) {
|
||||
this.popoutShown = event.shown;
|
||||
this.popoutButtonShown = event.showButton;
|
||||
this.refPopoutButton.current?.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class RDPEntry {
|
||||
|
|
|
@ -14,15 +14,21 @@ import {ClientIcon} from "svg-sprites/client-icons";
|
|||
|
||||
const viewStyle = require("./View.scss");
|
||||
|
||||
const PopoutButton = (props: {}) => {
|
||||
return (
|
||||
<div className={viewStyle.popoutButton}>
|
||||
<div className={viewStyle.button}>
|
||||
<ClientIconRenderer icon={ClientIcon.ChannelPopout} />
|
||||
export class PopoutButton extends React.Component<{ tree: RDPChannelTree }, {}> {
|
||||
render() {
|
||||
if(!this.props.tree.popoutButtonShown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={viewStyle.popoutButton} onClick={() => this.props.tree.events.fire("action_toggle_popout", { shown: !this.props.tree.popoutShown })}>
|
||||
<div className={viewStyle.button} title={this.props.tree.popoutShown ? tr("Popin the second channel tree view") : tr("Popout the channel tree view")}>
|
||||
<ClientIconRenderer icon={this.props.tree.popoutShown ? ClientIcon.ChannelPopin : ClientIcon.ChannelPopout} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ChannelTreeViewProperties {
|
||||
events: Registry<ChannelTreeUIEvents>;
|
||||
|
@ -203,7 +209,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
ref={this.props.dataProvider.refMove}
|
||||
/>
|
||||
</div>
|
||||
<PopoutButton />
|
||||
<PopoutButton tree={this.props.dataProvider} ref={this.props.dataProvider.refPopoutButton} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||
import {Registry} from "tc-shared/events";
|
||||
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
|
||||
import {spawnExternalModal} from "tc-shared/ui/react-elements/external-modal";
|
||||
|
@ -7,35 +6,114 @@ import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions";
|
|||
import {
|
||||
initializePopoutControlBarController
|
||||
} from "tc-shared/ui/frames/control-bar/Controller";
|
||||
import {server_connections} from "tc-shared/ConnectionManager";
|
||||
import {ChannelTree} from "tc-shared/tree/ChannelTree";
|
||||
import {ModalController} from "tc-shared/ui/react-elements/ModalDefinitions";
|
||||
import {ChannelTreePopoutEvents} from "tc-shared/ui/tree/popout/Definitions";
|
||||
import {ConnectionState} from "tc-shared/ConnectionHandler";
|
||||
|
||||
export function spawnChannelTreePopout(handler: ConnectionHandler) {
|
||||
const eventsTree = new Registry<ChannelTreeUIEvents>();
|
||||
eventsTree.enableDebug("channel-tree-view-modal");
|
||||
initializeChannelTreeController(eventsTree, handler.channelTree);
|
||||
export class ChannelTreePopoutController {
|
||||
readonly channelTree: ChannelTree;
|
||||
|
||||
const eventsControlBar = new Registry<ControlBarEvents>();
|
||||
initializePopoutControlBarController(eventsControlBar, handler);
|
||||
private popoutInstance: ModalController;
|
||||
private uiEvents: Registry<ChannelTreePopoutEvents>;
|
||||
private treeEvents: Registry<ChannelTreeUIEvents>;
|
||||
private controlBarEvents: Registry<ControlBarEvents>;
|
||||
|
||||
let handlerDestroyListener;
|
||||
server_connections.events().on("notify_handler_deleted", handlerDestroyListener = event => {
|
||||
if(event.handler !== handler) {
|
||||
private generalEvents: (() => void)[];
|
||||
|
||||
constructor(channelTree: ChannelTree) {
|
||||
this.channelTree = channelTree;
|
||||
|
||||
this.generalEvents = [];
|
||||
this.generalEvents.push(this.channelTree.server.events.on("notify_properties_updated", event => {
|
||||
if("virtualserver_name" in event.updated_properties) {
|
||||
this.sendTitle();
|
||||
}
|
||||
}));
|
||||
|
||||
this.generalEvents.push(this.channelTree.client.events().on("notify_connection_state_changed", () => this.sendTitle()));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.popin();
|
||||
this.generalEvents?.forEach(callback => callback());
|
||||
this.generalEvents = undefined;
|
||||
}
|
||||
|
||||
hasBeenPopedOut() {
|
||||
return !!this.popoutInstance;
|
||||
}
|
||||
|
||||
popout() {
|
||||
if(this.popoutInstance) {
|
||||
/* TODO: Request focus on that window? */
|
||||
return;
|
||||
}
|
||||
|
||||
modal.destroy();
|
||||
});
|
||||
this.uiEvents = new Registry<ChannelTreePopoutEvents>();
|
||||
this.uiEvents.on("query_title", () => this.sendTitle());
|
||||
|
||||
const modal = spawnExternalModal("channel-tree", { tree: eventsTree, controlBar: eventsControlBar }, { handlerId: handler.handlerId }, "channel-tree-" + handler.handlerId);
|
||||
modal.show();
|
||||
this.treeEvents = new Registry<ChannelTreeUIEvents>();
|
||||
initializeChannelTreeController(this.treeEvents, this.channelTree, { popoutButton: false });
|
||||
|
||||
modal.getEvents().on("destroy", () => {
|
||||
server_connections.events().off("notify_handler_deleted", handlerDestroyListener);
|
||||
this.controlBarEvents = new Registry<ControlBarEvents>();
|
||||
initializePopoutControlBarController(this.controlBarEvents, this.channelTree.client);
|
||||
|
||||
eventsTree.fire("notify_destroy");
|
||||
eventsTree.destroy();
|
||||
this.popoutInstance = spawnExternalModal("channel-tree", {
|
||||
tree: this.treeEvents,
|
||||
controlBar: this.controlBarEvents,
|
||||
base: this.uiEvents
|
||||
}, { handlerId: this.channelTree.client.handlerId }, "channel-tree-" + this.channelTree.client.handlerId);
|
||||
|
||||
eventsControlBar.fire("notify_destroy");
|
||||
eventsControlBar.destroy();
|
||||
});
|
||||
this.popoutInstance.getEvents().one("destroy", () => {
|
||||
this.treeEvents.fire("notify_destroy");
|
||||
this.treeEvents.destroy();
|
||||
this.treeEvents = undefined;
|
||||
|
||||
this.controlBarEvents.fire("notify_destroy");
|
||||
this.controlBarEvents.destroy();
|
||||
this.controlBarEvents = undefined;
|
||||
|
||||
this.uiEvents.destroy();
|
||||
this.uiEvents = undefined;
|
||||
|
||||
this.popoutInstance = undefined;
|
||||
this.channelTree.events.fire("notify_popout_state_changed", { popoutShown: false });
|
||||
});
|
||||
this.popoutInstance.show();
|
||||
|
||||
this.channelTree.events.fire("notify_popout_state_changed", { popoutShown: true });
|
||||
}
|
||||
|
||||
popin() {
|
||||
if(!this.popoutInstance) { return; }
|
||||
|
||||
this.popoutInstance.destroy();
|
||||
this.popoutInstance = undefined; /* not needed, but just to ensure (will be set within the destroy callback already) */
|
||||
}
|
||||
|
||||
private sendTitle() {
|
||||
if(!this.uiEvents) { return; }
|
||||
|
||||
let title;
|
||||
switch (this.channelTree.client.connection_state) {
|
||||
case ConnectionState.INITIALISING:
|
||||
case ConnectionState.CONNECTING:
|
||||
case ConnectionState.AUTHENTICATING:
|
||||
const address = this.channelTree.server.remote_address;
|
||||
title = tra("Connecting to {}", address.host + (address.port === 9987 ? "" : `:${address.port}`));
|
||||
break;
|
||||
|
||||
case ConnectionState.DISCONNECTING:
|
||||
case ConnectionState.UNCONNECTED:
|
||||
title = tr("Not connected");
|
||||
break;
|
||||
|
||||
case ConnectionState.CONNECTED:
|
||||
title = this.channelTree.server.properties.virtualserver_name;
|
||||
break;
|
||||
}
|
||||
|
||||
this.uiEvents.fire_async("notify_title", { title: title });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface ChannelTreePopoutEvents {
|
||||
query_title: {},
|
||||
notify_title: { title: string }
|
||||
}
|
|
@ -2,13 +2,25 @@ import {AbstractModal} from "tc-shared/ui/react-elements/ModalDefinitions";
|
|||
import {Registry, RegistryMap} from "tc-shared/events";
|
||||
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
|
||||
import * as React from "react";
|
||||
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
||||
import {ChannelTreeRenderer} from "tc-shared/ui/tree/Renderer";
|
||||
import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions";
|
||||
import {ControlBar2} from "tc-shared/ui/frames/control-bar/Renderer";
|
||||
import {ChannelTreePopoutEvents} from "tc-shared/ui/tree/popout/Definitions";
|
||||
import {useState} from "react";
|
||||
|
||||
const TitleRenderer = (props: { events: Registry<ChannelTreePopoutEvents> }) => {
|
||||
const [ title, setTitle ] = useState<string>(() => {
|
||||
props.events.fire("query_title");
|
||||
return tr("Channel tree popout");
|
||||
});
|
||||
|
||||
props.events.reactUse("notify_title", event => setTitle(event.title));
|
||||
return <>{title}</>;
|
||||
}
|
||||
|
||||
const cssStyle = require("./RendererModal.scss");
|
||||
class ChannelTreeModal extends AbstractModal {
|
||||
readonly eventsUI: Registry<ChannelTreePopoutEvents>;
|
||||
readonly eventsTree: Registry<ChannelTreeUIEvents>;
|
||||
readonly eventsControlBar: Registry<ControlBarEvents>;
|
||||
|
||||
|
@ -18,8 +30,15 @@ class ChannelTreeModal extends AbstractModal {
|
|||
super();
|
||||
|
||||
this.handlerId = userData.handlerId;
|
||||
this.eventsUI = registryMap["base"] as any;
|
||||
this.eventsTree = registryMap["tree"] as any;
|
||||
this.eventsControlBar = registryMap["controlBar"] as any;
|
||||
|
||||
this.eventsUI.fire("query_title");
|
||||
}
|
||||
|
||||
protected onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
renderBody(): React.ReactElement {
|
||||
|
@ -35,8 +54,8 @@ class ChannelTreeModal extends AbstractModal {
|
|||
)
|
||||
}
|
||||
|
||||
title(): string | React.ReactElement<Translatable> {
|
||||
return <Translatable>Channel tree</Translatable>;
|
||||
title(): React.ReactElement {
|
||||
return <TitleRenderer events={this.eventsUI} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -542,7 +542,7 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
logWarn(LogCategory.CLIENT, tr("Failed to clear the whisper target: %o"), error);
|
||||
});
|
||||
}
|
||||
this.voiceBridge.stopWhispering();
|
||||
this.voiceBridge?.stopWhispering();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue