574 lines
No EOL
21 KiB
TypeScript
574 lines
No EOL
21 KiB
TypeScript
import {Icon, IconManager} from "tc-shared/FileManager";
|
|
import {spawnBookmarkModal} from "tc-shared/ui/modal/ModalBookmarks";
|
|
import {
|
|
add_current_server,
|
|
Bookmark,
|
|
bookmarks,
|
|
BookmarkType,
|
|
boorkmak_connect,
|
|
DirectoryBookmark
|
|
} from "tc-shared/bookmarks";
|
|
import {ConnectionHandler, DisconnectReason} from "tc-shared/ConnectionHandler";
|
|
import {Sound} from "tc-shared/sound/Sounds";
|
|
import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
|
|
import {spawnPermissionEdit} from "tc-shared/ui/modal/permission/ModalPermissionEdit";
|
|
import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
|
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
|
import {PermissionType} from "tc-shared/permission/PermissionType";
|
|
import {openBanList} from "tc-shared/ui/modal/ModalBanList";
|
|
import {spawnQueryManage} from "tc-shared/ui/modal/ModalQueryManage";
|
|
import {spawnQueryCreate} from "tc-shared/ui/modal/ModalQuery";
|
|
import {spawnSettingsModal} from "tc-shared/ui/modal/ModalSettings";
|
|
import {spawnAbout} from "tc-shared/ui/modal/ModalAbout";
|
|
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
|
|
import {control_bar} from "tc-shared/ui/frames/ControlBar";
|
|
import * as loader from "tc-loader";
|
|
import {formatMessage} from "tc-shared/ui/frames/chat";
|
|
import * as slog from "tc-shared/ui/frames/server_log";
|
|
|
|
export interface HRItem { }
|
|
|
|
export interface MenuItem {
|
|
append_item(label: string): MenuItem;
|
|
append_hr(): HRItem;
|
|
delete_item(item: MenuItem | HRItem);
|
|
items() : (MenuItem | HRItem)[];
|
|
|
|
icon(klass?: string | Promise<Icon> | Icon) : string;
|
|
label(value?: string) : string;
|
|
visible(value?: boolean) : boolean;
|
|
disabled(value?: boolean) : boolean;
|
|
click(callback: () => any) : this;
|
|
}
|
|
|
|
export interface MenuBarDriver {
|
|
initialize();
|
|
|
|
append_item(label: string) : MenuItem;
|
|
delete_item(item: MenuItem);
|
|
items() : MenuItem[];
|
|
|
|
flush_changes();
|
|
}
|
|
|
|
let _driver: MenuBarDriver;
|
|
export function driver() : MenuBarDriver {
|
|
return _driver;
|
|
}
|
|
|
|
export function set_driver(driver: MenuBarDriver) {
|
|
_driver = driver;
|
|
}
|
|
|
|
export interface NativeActions {
|
|
open_dev_tools();
|
|
reload_page();
|
|
|
|
check_native_update();
|
|
open_change_log();
|
|
|
|
quit();
|
|
|
|
show_dev_tools(): boolean;
|
|
}
|
|
export let native_actions: NativeActions;
|
|
|
|
namespace html {
|
|
class HTMLHrItem implements HRItem {
|
|
readonly html_tag: JQuery;
|
|
|
|
constructor() {
|
|
this.html_tag = $.spawn("hr");
|
|
}
|
|
}
|
|
|
|
class HTMLMenuItem implements MenuItem {
|
|
readonly html_tag: JQuery;
|
|
readonly _label_tag: JQuery;
|
|
readonly _label_icon_tag: JQuery;
|
|
readonly _label_text_tag: JQuery;
|
|
readonly _submenu_tag: JQuery;
|
|
|
|
private _items: (MenuItem | HRItem)[] = [];
|
|
private _label: string;
|
|
private _callback_click: () => any;
|
|
|
|
|
|
constructor(label: string, mode: "side" | "down") {
|
|
this._label = label;
|
|
|
|
this.html_tag = $.spawn("div").addClass("container-menu-item type-" + mode);
|
|
|
|
this._label_tag = $.spawn("div").addClass("menu-item");
|
|
this._label_icon_tag = $.spawn("div").addClass("container-icon").appendTo(this._label_tag);
|
|
$.spawn("div").addClass("container-label").append(
|
|
this._label_text_tag = $.spawn("a").text(label)
|
|
).appendTo(this._label_tag);
|
|
this._label_tag.on('click', event => {
|
|
if(event.isDefaultPrevented())
|
|
return;
|
|
|
|
const disabled = this.html_tag.hasClass("disabled");
|
|
if(this._callback_click && !disabled) {
|
|
this._callback_click();
|
|
}
|
|
|
|
event.preventDefault();
|
|
if(disabled)
|
|
event.stopPropagation();
|
|
else
|
|
HTMLMenuBarDriver.instance().close();
|
|
});
|
|
|
|
this._submenu_tag = $.spawn("div").addClass("sub-menu");
|
|
|
|
this.html_tag.append(this._label_tag);
|
|
this.html_tag.append(this._submenu_tag);
|
|
}
|
|
|
|
append_item(label: string): MenuItem {
|
|
const item = new HTMLMenuItem(label, "side");
|
|
this._items.push(item);
|
|
this._submenu_tag.append(item.html_tag);
|
|
this.html_tag.addClass('sub-entries');
|
|
return item;
|
|
}
|
|
|
|
append_hr(): HRItem {
|
|
const item = new HTMLHrItem();
|
|
this._items.push(item);
|
|
this._submenu_tag.append(item.html_tag);
|
|
return item;
|
|
}
|
|
|
|
delete_item(item: MenuItem | HRItem) {
|
|
this._items.remove(item);
|
|
(item as any).html_tag.detach();
|
|
this.html_tag.toggleClass('sub-entries', this._items.length > 0);
|
|
}
|
|
|
|
disabled(value?: boolean): boolean {
|
|
if(typeof(value) === "undefined")
|
|
return this.html_tag.hasClass("disabled");
|
|
|
|
this.html_tag.toggleClass("disabled", value);
|
|
return value;
|
|
}
|
|
|
|
items(): (MenuItem | HRItem)[] {
|
|
return this._items;
|
|
}
|
|
|
|
label(value?: string): string {
|
|
if(typeof(value) === "undefined" || this._label === value)
|
|
return this._label;
|
|
|
|
return this._label;
|
|
}
|
|
|
|
visible(value?: boolean): boolean {
|
|
if(typeof(value) === "undefined")
|
|
return this.html_tag.is(':visible'); //FIXME!
|
|
|
|
this.html_tag.toggle(!!value);
|
|
return value;
|
|
}
|
|
|
|
click(callback: () => any): this {
|
|
this._callback_click = callback;
|
|
return this;
|
|
}
|
|
|
|
icon(klass?: string | Promise<Icon> | Icon): string {
|
|
this._label_icon_tag.children().remove();
|
|
if(typeof(klass) === "string")
|
|
$.spawn("div").addClass("icon_em " + klass).appendTo(this._label_icon_tag);
|
|
else
|
|
IconManager.generate_tag(klass).appendTo(this._label_icon_tag);
|
|
return "";
|
|
}
|
|
|
|
}
|
|
|
|
export class HTMLMenuBarDriver implements MenuBarDriver {
|
|
private static _instance: HTMLMenuBarDriver;
|
|
public static instance() : HTMLMenuBarDriver {
|
|
if(!this._instance)
|
|
this._instance = new HTMLMenuBarDriver();
|
|
return this._instance;
|
|
}
|
|
|
|
readonly html_tag: JQuery;
|
|
|
|
private _items: MenuItem[] = [];
|
|
constructor() {
|
|
this.html_tag = $.spawn("div").addClass("top-menu-bar");
|
|
}
|
|
|
|
append_item(label: string): MenuItem {
|
|
const item = new HTMLMenuItem(label, "down");
|
|
this._items.push(item);
|
|
|
|
this.html_tag.append(item.html_tag);
|
|
item._label_tag.on('click', enable_event => {
|
|
enable_event.preventDefault();
|
|
|
|
this.close();
|
|
item.html_tag.addClass("active");
|
|
|
|
setTimeout(() => {
|
|
$(document).one('click focusout', event => {
|
|
if(event.isDefaultPrevented()) return;
|
|
event.preventDefault();
|
|
|
|
item.html_tag.removeClass("active");
|
|
});
|
|
}, 0);
|
|
});
|
|
return item;
|
|
}
|
|
|
|
close() {
|
|
this.html_tag.find(".active").removeClass("active");
|
|
}
|
|
|
|
delete_item(item: MenuItem) {
|
|
return undefined;
|
|
}
|
|
|
|
items(): MenuItem[] {
|
|
return this._items;
|
|
}
|
|
|
|
flush_changes() { /* unused, all changed were made instantly */ }
|
|
|
|
initialize() {
|
|
$("#top-menu-bar").replaceWith(this.html_tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
let _items_bookmark: {
|
|
root: MenuItem,
|
|
manage: MenuItem,
|
|
add_current: MenuItem
|
|
};
|
|
|
|
export function rebuild_bookmarks() {
|
|
if(!_items_bookmark) {
|
|
_items_bookmark = {
|
|
root: driver().append_item(tr("Favorites")),
|
|
|
|
add_current: undefined,
|
|
manage: undefined
|
|
};
|
|
_items_bookmark.manage = _items_bookmark.root.append_item(tr("Manage bookmarks"));
|
|
_items_bookmark.manage.icon("client-bookmark_manager");
|
|
_items_bookmark.manage.click(() => spawnBookmarkModal());
|
|
|
|
_items_bookmark.add_current = _items_bookmark.root.append_item(tr("Add current server to bookmarks"));
|
|
_items_bookmark.add_current.icon('client-bookmark_add');
|
|
_items_bookmark.add_current.click(() => add_current_server());
|
|
_state_updater["bookmarks.ac"] = { item: _items_bookmark.add_current, conditions: [condition_connected]};
|
|
}
|
|
|
|
_items_bookmark.root.items().filter(e => e !== _items_bookmark.add_current && e !== _items_bookmark.manage).forEach(e => {
|
|
_items_bookmark.root.delete_item(e);
|
|
});
|
|
_items_bookmark.root.append_hr();
|
|
|
|
const build_bookmark = (root: MenuItem, entry: DirectoryBookmark | Bookmark) => {
|
|
if(entry.type == BookmarkType.DIRECTORY) {
|
|
const directory = entry as DirectoryBookmark;
|
|
const item = root.append_item(directory.display_name);
|
|
item.icon('client-folder');
|
|
for(const entry of directory.content)
|
|
build_bookmark(item, entry);
|
|
if(directory.content.length == 0)
|
|
item.disabled(true);
|
|
} else {
|
|
const bookmark = entry as Bookmark;
|
|
const item = root.append_item(bookmark.display_name);
|
|
item.icon(IconManager.load_cached_icon(bookmark.last_icon_id || 0));
|
|
item.click(() => boorkmak_connect(bookmark));
|
|
}
|
|
};
|
|
|
|
for(const entry of bookmarks().content)
|
|
build_bookmark(_items_bookmark.root, entry);
|
|
driver().flush_changes();
|
|
}
|
|
|
|
/* will be called on connection handler change or on client connect state or mic state change etc... */
|
|
let _state_updater: {[key: string]:{ item: MenuItem; conditions: (() => boolean)[], update_handler?: (item: MenuItem) => any }} = {};
|
|
export function update_state() {
|
|
for(const _key of Object.keys(_state_updater)) {
|
|
const item = _state_updater[_key];
|
|
if(item.update_handler) {
|
|
if(item.update_handler(item.item))
|
|
continue;
|
|
}
|
|
let enabled = true;
|
|
for(const condition of item.conditions)
|
|
if(!condition()) {
|
|
enabled = false;
|
|
break;
|
|
}
|
|
item.item.disabled(!enabled);
|
|
}
|
|
driver().flush_changes();
|
|
}
|
|
|
|
const condition_connected = () => {
|
|
const scon = server_connections ? server_connections.active_connection_handler() : undefined;
|
|
return scon && scon.connected;
|
|
};
|
|
|
|
declare namespace native {
|
|
export function initialize();
|
|
}
|
|
|
|
export function initialize() {
|
|
const driver_ = driver();
|
|
driver_.initialize();
|
|
|
|
/* build connection */
|
|
let item: MenuItem;
|
|
{
|
|
const menu = driver_.append_item(tr("Connection"));
|
|
item = menu.append_item(tr("Connect to a server"));
|
|
item.icon('client-connect');
|
|
item.click(() => spawnConnectModal({}));
|
|
|
|
const do_disconnect = (handlers: ConnectionHandler[]) => {
|
|
for(const handler of handlers) {
|
|
handler.cancel_reconnect(true);
|
|
handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
|
|
server_connections.active_connection_handler().serverConnection.disconnect();
|
|
handler.sound.play(Sound.CONNECTION_DISCONNECTED);
|
|
handler.log.log(slog.Type.DISCONNECTED, {});
|
|
}
|
|
control_bar.update_connection_state();
|
|
update_state();
|
|
};
|
|
item = menu.append_item(tr("Disconnect from current server"));
|
|
item.icon('client-disconnect');
|
|
item.disabled(true);
|
|
item.click(() => {
|
|
const handler = server_connections.active_connection_handler();
|
|
do_disconnect([handler]);
|
|
});
|
|
_state_updater["connection.dc"] = { item: item, conditions: [() => condition_connected()]};
|
|
|
|
item = menu.append_item(tr("Disconnect from all servers"));
|
|
item.icon('client-disconnect');
|
|
item.click(() => {
|
|
do_disconnect(server_connections.server_connection_handlers());
|
|
});
|
|
_state_updater["connection.dca"] = { item: item, conditions: [], update_handler: (item) => {
|
|
item.visible(server_connections && server_connections.server_connection_handlers().length > 1);
|
|
return true;
|
|
}};
|
|
|
|
if(__build.target !== "web") {
|
|
menu.append_hr();
|
|
|
|
item = menu.append_item(tr("Quit"));
|
|
item.icon('client-close_button');
|
|
item.click(() => native_actions.quit());
|
|
}
|
|
}
|
|
{
|
|
rebuild_bookmarks();
|
|
}
|
|
|
|
if(false) {
|
|
const menu = driver_.append_item(tr("Self"));
|
|
/* Microphone | Sound | Away */
|
|
}
|
|
|
|
{
|
|
const menu = driver_.append_item(tr("Permissions"));
|
|
|
|
item = menu.append_item(tr("Server Groups"));
|
|
item.icon("client-permission_server_groups");
|
|
item.click(() => {
|
|
spawnPermissionEdit(server_connections.active_connection_handler(), "sg").open();
|
|
});
|
|
_state_updater["permission.sg"] = { item: item, conditions: [condition_connected]};
|
|
|
|
item = menu.append_item(tr("Client Permissions"));
|
|
item.icon("client-permission_client");
|
|
item.click(() => {
|
|
spawnPermissionEdit(server_connections.active_connection_handler(), "clp").open();
|
|
});
|
|
_state_updater["permission.clp"] = { item: item, conditions: [condition_connected]};
|
|
|
|
item = menu.append_item(tr("Channel Client Permissions"));
|
|
item.icon("client-permission_client");
|
|
item.click(() => {
|
|
spawnPermissionEdit(server_connections.active_connection_handler(), "clchp").open();
|
|
});
|
|
_state_updater["permission.chclp"] = { item: item, conditions: [condition_connected]};
|
|
|
|
item = menu.append_item(tr("Channel Groups"));
|
|
item.icon("client-permission_channel");
|
|
item.click(() => {
|
|
spawnPermissionEdit(server_connections.active_connection_handler(), "cg").open();
|
|
});
|
|
_state_updater["permission.cg"] = { item: item, conditions: [condition_connected]};
|
|
|
|
item = menu.append_item(tr("Channel Permissions"));
|
|
item.icon("client-permission_channel");
|
|
item.click(() => {
|
|
spawnPermissionEdit(server_connections.active_connection_handler(), "chp").open();
|
|
});
|
|
_state_updater["permission.cp"] = { item: item, conditions: [condition_connected]};
|
|
|
|
menu.append_hr();
|
|
item = menu.append_item(tr("List Privilege Keys"));
|
|
item.icon("client-token");
|
|
item.click(() => {
|
|
createErrorModal(tr("Not implemented"), tr("Privilege key list is not implemented yet!")).open();
|
|
});
|
|
_state_updater["permission.pk"] = { item: item, conditions: [condition_connected]};
|
|
|
|
item = menu.append_item(tr("Use Privilege Key"));
|
|
item.icon("client-token_use");
|
|
item.click(() => {
|
|
//TODO: Fixeme use one method for the control bar and here!
|
|
createInputModal(tr("Use token"), tr("Please enter your token/privilege key"), message => message.length > 0, result => {
|
|
if(!result) return;
|
|
const scon = server_connections.active_connection_handler();
|
|
|
|
if(scon.serverConnection.connected)
|
|
scon.serverConnection.send_command("tokenuse", {
|
|
token: result
|
|
}).then(() => {
|
|
createInfoModal(tr("Use token"), tr("Toke successfully used!")).open();
|
|
}).catch(error => {
|
|
//TODO tr
|
|
createErrorModal(tr("Use token"), formatMessage(tr("Failed to use token: {}"), error instanceof CommandResult ? error.message : error)).open();
|
|
});
|
|
}).open();
|
|
});
|
|
_state_updater["permission.upk"] = { item: item, conditions: [condition_connected]};
|
|
}
|
|
|
|
{
|
|
const menu = driver_.append_item(tr("Tools"));
|
|
|
|
/*
|
|
item = menu.append_item(tr("Manage Playlists"));
|
|
item.icon('client-music');
|
|
item.click(() => {
|
|
const scon = server_connections.active_connection_handler();
|
|
if(scon && scon.connected) {
|
|
Modals.spawnPlaylistManage(scon);
|
|
} else {
|
|
createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open();
|
|
}
|
|
});
|
|
_state_updater["tools.pl"] = { item: item, conditions: [condition_connected]};
|
|
*/
|
|
|
|
item = menu.append_item(tr("Ban List"));
|
|
item.icon('client-ban_list');
|
|
item.click(() => {
|
|
const scon = server_connections.active_connection_handler();
|
|
if(scon && scon.connected) {
|
|
if(scon.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) {
|
|
openBanList(scon);
|
|
} else {
|
|
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open();
|
|
scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
|
}
|
|
} else {
|
|
createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open();
|
|
}
|
|
});
|
|
_state_updater["tools.bl"] = { item: item, conditions: [condition_connected]};
|
|
|
|
item = menu.append_item(tr("Query List"));
|
|
item.icon('client-server_query');
|
|
item.click(() => {
|
|
const scon = server_connections.active_connection_handler();
|
|
if(scon && scon.connected) {
|
|
if(scon.permissions.neededPermission(PermissionType.B_CLIENT_QUERY_LIST).granted(1) || scon.permissions.neededPermission(PermissionType.B_CLIENT_QUERY_LIST_OWN).granted(1)) {
|
|
spawnQueryManage(scon);
|
|
} else {
|
|
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the server query list")).open();
|
|
scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
|
}
|
|
} else {
|
|
createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open();
|
|
}
|
|
});
|
|
_state_updater["tools.ql"] = { item: item, conditions: [condition_connected]};
|
|
|
|
item = menu.append_item(tr("Query Create"));
|
|
item.icon('client-server_query');
|
|
item.click(() => {
|
|
const scon = server_connections.active_connection_handler();
|
|
if(scon && scon.connected) {
|
|
if(scon.permissions.neededPermission(PermissionType.B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN).granted(1) || scon.permissions.neededPermission(PermissionType.B_CLIENT_QUERY_CREATE).granted(1)) {
|
|
spawnQueryCreate(scon);
|
|
} else {
|
|
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to create a server query login")).open();
|
|
scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
|
}
|
|
} else {
|
|
createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open();
|
|
}
|
|
});
|
|
_state_updater["tools.qc"] = { item: item, conditions: [condition_connected]};
|
|
menu.append_hr();
|
|
|
|
item = menu.append_item(tr("Settings"));
|
|
item.icon("client-settings");
|
|
item.click(() => spawnSettingsModal());
|
|
}
|
|
|
|
{
|
|
const menu = driver_.append_item(tr("Help"));
|
|
|
|
if(__build.target !== "web") {
|
|
item = menu.append_item(tr("Check for updates"));
|
|
item.click(() => native_actions.check_native_update());
|
|
|
|
item = menu.append_item(tr("Open changelog"));
|
|
item.click(() => native_actions.open_change_log());
|
|
}
|
|
|
|
item = menu.append_item(tr("Visit TeaSpeak.de"));
|
|
item.click(() => window.open('https://teaspeak.de/', '_blank'));
|
|
|
|
item = menu.append_item(tr("Visit TeaSpeak forum"));
|
|
item.click(() => window.open('https://forum.teaspeak.de/', '_blank'));
|
|
|
|
if(__build.target !== "web" && typeof(native_actions.show_dev_tools) === "function" && native_actions.show_dev_tools()) {
|
|
menu.append_hr();
|
|
item = menu.append_item(tr("Open developer tools"));
|
|
item.click(() => native_actions.open_dev_tools());
|
|
|
|
item = menu.append_item(tr("Reload UI"));
|
|
item.click(() => native_actions.reload_page());
|
|
}
|
|
|
|
menu.append_hr();
|
|
item = menu.append_item(__build.target === "web" ? tr("About TeaWeb") : tr("About TeaClient"));
|
|
item.click(() => spawnAbout())
|
|
}
|
|
|
|
update_state();
|
|
}
|
|
|
|
/* default is HTML, the client will override this */
|
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
function: async () => {
|
|
if(!driver())
|
|
set_driver(html.HTMLMenuBarDriver.instance());
|
|
},
|
|
priority: 100,
|
|
name: "Menu bar init"
|
|
}); |