TeaWeb/shared/js/ui/frames/MenuBar.ts
2020-05-08 12:38:23 +02:00

570 lines
No EOL
21 KiB
TypeScript

import {spawnBookmarkModal} from "tc-shared/ui/modal/ModalBookmarks";
import {
add_server_to_bookmarks,
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 * as loader from "tc-loader";
import {formatMessage} from "tc-shared/ui/frames/chat";
import {control_bar_instance} from "tc-shared/ui/frames/control-bar";
import {icon_cache_loader, IconManager, LocalIcon} from "tc-shared/file/Icons";
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 | LocalIcon) : string; //FIXME: Native client must work as well!
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 | LocalIcon): 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_server_to_bookmarks(server_connections.active_connection()));
_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(icon_cache_loader.load_icon(bookmark.last_icon_id, bookmark.last_icon_server_id));
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() : 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.disconnectFromServer();
control_bar_instance()?.events().fire("update_state", { state: "connect-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();
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.all_connections());
});
_state_updater["connection.dca"] = { item: item, conditions: [], update_handler: (item) => {
item.visible(server_connections && server_connections.all_connections().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(), "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(), "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(), "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(), "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(), "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();
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();
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();
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();
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"
});