Reworked the menu bar and added a backend specific interface for backend specific functions
parent
df73618bd0
commit
82a5b1127f
|
@ -1,6 +1,6 @@
|
|||
import {ConnectionHandler, DisconnectReason} from "./ConnectionHandler";
|
||||
import {Registry} from "./events";
|
||||
import * as top_menu from "./ui/frames/MenuBar";
|
||||
import * as top_menu from "./ui/frames/MenuBarOld";
|
||||
import * as loader from "tc-loader";
|
||||
import {Stage} from "tc-loader";
|
||||
|
||||
|
@ -107,8 +107,6 @@ export class ConnectionManager {
|
|||
});
|
||||
old_handler?.events().fire("notify_visibility_changed", { visible: false });
|
||||
handler?.events().fire("notify_visibility_changed", { visible: true });
|
||||
|
||||
top_menu.update_state(); //FIXME: Top menu should listen to our events!
|
||||
}
|
||||
|
||||
findConnection(handlerId: string) : ConnectionHandler | undefined {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export interface NativeClientBackend {
|
||||
openChangeLog() : void;
|
||||
openClientUpdater() : void;
|
||||
quit() : void;
|
||||
|
||||
showDeveloperOptions() : boolean;
|
||||
openDeveloperTools() : void;
|
||||
reloadWindow() : void;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface WebClientBackend {
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import {NativeClientBackend} from "tc-shared/backend/NativeClient";
|
||||
import {WebClientBackend} from "tc-shared/backend/WebClient";
|
||||
|
||||
let backend;
|
||||
export function getBackend(target: "native") : NativeClientBackend;
|
||||
export function getBackend(target: "web") : WebClientBackend;
|
||||
|
||||
export function getBackend(target) {
|
||||
if(__build.target === "client") {
|
||||
if(target !== "native") {
|
||||
throw "invalid target, expected native";
|
||||
}
|
||||
} else if(__build.target === "web") {
|
||||
if(target !== "web") {
|
||||
throw "invalid target, expected web";
|
||||
}
|
||||
} else {
|
||||
throw "invalid/unexpected build target";
|
||||
}
|
||||
|
||||
return backend;
|
||||
}
|
||||
|
||||
export function setBackend(instance: NativeClientBackend | WebClientBackend) {
|
||||
backend = instance;
|
||||
}
|
|
@ -4,7 +4,7 @@ import {guid} from "./crypto/uid";
|
|||
import {createErrorModal, createInfoModal, createInputModal} from "./ui/elements/Modal";
|
||||
import {defaultConnectProfile, findConnectProfile} from "./profiles/ConnectionProfile";
|
||||
import {spawnConnectModal} from "./ui/modal/ModalConnect";
|
||||
import * as top_menu from "./ui/frames/MenuBar";
|
||||
import * as top_menu from "./ui/frames/MenuBarOld";
|
||||
import {ConnectionHandler} from "./ConnectionHandler";
|
||||
import {server_connections} from "tc-shared/ConnectionManager";
|
||||
import {Registry} from "tc-shared/events";
|
||||
|
@ -257,7 +257,6 @@ export function add_server_to_bookmarks(server: ConnectionHandler) {
|
|||
}, name);
|
||||
save_bookmark(bookmark);
|
||||
|
||||
top_menu.rebuild_bookmarks();
|
||||
createInfoModal(tr("Server added"), tr("Server has been successfully added to your bookmarks.")).open();
|
||||
}
|
||||
}).open();
|
||||
|
|
|
@ -15,6 +15,7 @@ import {tr} from "../i18n/localize";
|
|||
import {spawnGlobalSettingsEditor} from "tc-shared/ui/modal/global-settings-editor/Controller";
|
||||
import {spawnModalCssVariableEditor} from "tc-shared/ui/modal/css-editor/Controller";
|
||||
import {server_connections} from "tc-shared/ConnectionManager";
|
||||
import {spawnAbout} from "tc-shared/ui/modal/ModalAbout";
|
||||
|
||||
/*
|
||||
function initialize_sounds(event_registry: Registry<ClientGlobalControlEvents>) {
|
||||
|
@ -150,6 +151,10 @@ export function initialize(event_registry: Registry<ClientGlobalControlEvents>)
|
|||
spawnGlobalSettingsEditor();
|
||||
break;
|
||||
|
||||
case "about":
|
||||
spawnAbout();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(tr("Received open window event for an unknown window: %s"), event.window);
|
||||
}
|
||||
|
@ -164,4 +169,8 @@ export function initialize(event_registry: Registry<ClientGlobalControlEvents>)
|
|||
event_registry.on("action_open_window_settings", event => {
|
||||
spawnSettingsModal(event.defaultCategory);
|
||||
});
|
||||
|
||||
event_registry.on("action_open_window_permissions", event => {
|
||||
spawnPermissionEditorModal(event.connection ? event.connection : server_connections.active_connection(), event.defaultTab);
|
||||
});
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
import {ConnectionHandler} from "../ConnectionHandler";
|
||||
import {Registry} from "../events";
|
||||
|
||||
export type PermissionEditorTab = "groups-server" | "groups-channel" | "channel" | "client" | "client-channel";
|
||||
export interface ClientGlobalControlEvents {
|
||||
/* open a basic window */
|
||||
action_open_window: {
|
||||
window:
|
||||
"settings" | /* use action_open_window_settings! */
|
||||
"about" |
|
||||
"settings-registry" |
|
||||
"css-variable-editor" |
|
||||
"bookmark-manage" |
|
||||
|
@ -29,10 +31,15 @@ export interface ClientGlobalControlEvents {
|
|||
/* some more specific window openings */
|
||||
action_open_window_connect: {
|
||||
newTab: boolean
|
||||
}
|
||||
},
|
||||
|
||||
action_open_window_settings: {
|
||||
defaultCategory?: string
|
||||
},
|
||||
|
||||
action_open_window_permissions: {
|
||||
connection?: ConnectionHandler,
|
||||
defaultTab: PermissionEditorTab
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import * as stats from "./stats";
|
|||
import * as fidentity from "./profiles/identities/TeaForumIdentity";
|
||||
import {defaultRecorder, RecorderProfile, setDefaultRecorder} from "tc-shared/voice/RecorderProfile";
|
||||
import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
|
||||
import * as top_menu from "./ui/frames/MenuBar";
|
||||
import * as top_menu from "./ui/frames/MenuBarOld";
|
||||
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
|
||||
import {formatMessage} from "tc-shared/ui/frames/chat";
|
||||
import {openModalNewcomer} from "tc-shared/ui/modal/ModalNewcomer";
|
||||
|
@ -29,6 +29,7 @@ import {MenuEntryType, spawn_context_menu} from "tc-shared/ui/elements/ContextMe
|
|||
import {copy_to_clipboard} from "tc-shared/utils/helpers";
|
||||
import {checkForUpdatedApp} from "tc-shared/update";
|
||||
import {setupJSRender} from "tc-shared/ui/jsrender";
|
||||
import {ConnectRequestData} from "tc-shared/ipc/ConnectHandler";
|
||||
import "svg-sprites/client-icons";
|
||||
|
||||
/* required import for init */
|
||||
|
@ -38,11 +39,11 @@ import "./ui/elements/ContextDivider";
|
|||
import "./ui/elements/Tab";
|
||||
import "./connection/CommandHandler";
|
||||
import "./connection/ConnectionBase";
|
||||
import {ConnectRequestData} from "tc-shared/ipc/ConnectHandler";
|
||||
import "./video-viewer/Controller";
|
||||
import "./profiles/ConnectionProfile";
|
||||
import "./update/UpdaterWeb";
|
||||
import "./file/LocalIcons";
|
||||
import "./ui/frames/menu-bar/MainMenu";
|
||||
|
||||
import {defaultConnectProfile, findConnectProfile} from "tc-shared/profiles/ConnectionProfile";
|
||||
import {server_connections} from "tc-shared/ConnectionManager";
|
||||
|
@ -189,7 +190,7 @@ function main() {
|
|||
});
|
||||
window.removeLoaderContextMenuHook();
|
||||
|
||||
top_menu.initialize();
|
||||
//top_menu.initialize();
|
||||
|
||||
const initial_handler = server_connections.spawn_server_connection();
|
||||
initial_handler.acquireInputHardware().then(() => {});
|
||||
|
|
|
@ -11,7 +11,7 @@ import {createServerModal} from "../ui/modal/ModalServerEdit";
|
|||
import {spawnIconSelect} from "../ui/modal/ModalIconSelect";
|
||||
import {spawnAvatarList} from "../ui/modal/ModalAvatarList";
|
||||
import {connection_log} from "../ui/modal/ModalConnect";
|
||||
import * as top_menu from "../ui/frames/MenuBar";
|
||||
import * as top_menu from "../ui/frames/MenuBarOld";
|
||||
import {Registry} from "../events";
|
||||
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
||||
|
||||
|
@ -311,7 +311,6 @@ export class ServerEntry extends ChannelTreeEntry<ServerEvents> {
|
|||
e.last_icon_server_id = this.properties.virtualserver_unique_identifier;
|
||||
});
|
||||
bookmarks.save_bookmark();
|
||||
top_menu.rebuild_bookmarks();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,578 +0,0 @@
|
|||
import {spawnBookmarkModal} from "../../ui/modal/ModalBookmarks";
|
||||
import {
|
||||
add_server_to_bookmarks,
|
||||
Bookmark,
|
||||
bookmarks,
|
||||
BookmarkType,
|
||||
boorkmak_connect,
|
||||
DirectoryBookmark
|
||||
} from "../../bookmarks";
|
||||
import {ConnectionHandler} from "../../ConnectionHandler";
|
||||
import {Sound} from "../../sound/Sounds";
|
||||
import {spawnConnectModal} from "../../ui/modal/ModalConnect";
|
||||
import {createErrorModal, createInfoModal, createInputModal} from "../../ui/elements/Modal";
|
||||
import {CommandResult} from "../../connection/ServerConnectionDeclaration";
|
||||
import {PermissionType} from "../../permission/PermissionType";
|
||||
import {openBanList} from "../../ui/modal/ModalBanList";
|
||||
import {spawnQueryManage} from "../../ui/modal/ModalQueryManage";
|
||||
import {spawnQueryCreate} from "../../ui/modal/ModalQuery";
|
||||
import {spawnAbout} from "../../ui/modal/ModalAbout";
|
||||
import * as loader from "tc-loader";
|
||||
import {formatMessage} from "../../ui/frames/chat";
|
||||
import {spawnPermissionEditorModal} from "../../ui/modal/permission/ModalPermissionEditor";
|
||||
import {global_client_actions} from "tc-shared/events/GlobalEvents";
|
||||
import {server_connections} from "tc-shared/ConnectionManager";
|
||||
import {generateIconJQueryTag, getIconManager, RemoteIcon} 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 | RemoteIcon) : 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;
|
||||
|
||||
private visible_: boolean = true;
|
||||
|
||||
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.visible_;
|
||||
|
||||
this.html_tag.toggleClass("hidden", this.visible_ = !!value);
|
||||
return value;
|
||||
}
|
||||
|
||||
click(callback: () => any): this {
|
||||
this._callback_click = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
icon(klass?: string | RemoteIcon): string {
|
||||
this._label_icon_tag.children().remove();
|
||||
if(typeof(klass) === "string") {
|
||||
$.spawn("div").addClass("icon_em " + klass).appendTo(this._label_icon_tag);
|
||||
} else {
|
||||
generateIconJQueryTag(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(getIconManager().resolveIcon(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();
|
||||
|
||||
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(() => {
|
||||
spawnPermissionEditorModal(server_connections.active_connection(), "groups-server");
|
||||
});
|
||||
_state_updater["permission.sg"] = { item: item, conditions: [condition_connected]};
|
||||
|
||||
item = menu.append_item(tr("Client Permissions"));
|
||||
item.icon("client-permission_client");
|
||||
item.click(() => {
|
||||
spawnPermissionEditorModal(server_connections.active_connection(), "client");
|
||||
});
|
||||
_state_updater["permission.clp"] = { item: item, conditions: [condition_connected]};
|
||||
|
||||
item = menu.append_item(tr("Channel Client Permissions"));
|
||||
item.icon("client-permission_client");
|
||||
item.click(() => {
|
||||
spawnPermissionEditorModal(server_connections.active_connection(), "client-channel");
|
||||
});
|
||||
_state_updater["permission.chclp"] = { item: item, conditions: [condition_connected]};
|
||||
|
||||
item = menu.append_item(tr("Channel Groups"));
|
||||
item.icon("client-permission_channel");
|
||||
item.click(() => {
|
||||
spawnPermissionEditorModal(server_connections.active_connection(), "groups-channel");
|
||||
});
|
||||
_state_updater["permission.cg"] = { item: item, conditions: [condition_connected]};
|
||||
|
||||
item = menu.append_item(tr("Channel Permissions"));
|
||||
item.icon("client-permission_channel");
|
||||
item.click(() => {
|
||||
spawnPermissionEditorModal(server_connections.active_connection(), "channel");
|
||||
});
|
||||
_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("Modify CSS variables"));
|
||||
item.click(() => global_client_actions.fire("action_open_window", { window: "css-variable-editor" }));
|
||||
|
||||
item = menu.append_item(tr("Open Registry"));
|
||||
item.click(() => global_client_actions.fire("action_open_window", { window: "settings-registry" }));
|
||||
|
||||
menu.append_hr();
|
||||
|
||||
item = menu.append_item(tr("Settings"));
|
||||
item.icon("client-settings");
|
||||
item.click(() => global_client_actions.fire("action_open_window_settings"));
|
||||
}
|
||||
|
||||
{
|
||||
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"
|
||||
});
|
|
@ -0,0 +1,361 @@
|
|||
import * as loader from "tc-loader";
|
||||
import {Stage} from "tc-loader";
|
||||
import {getMenuBarDriver, MenuBarEntry} from "tc-shared/ui/frames/menu-bar/index";
|
||||
import {ClientIcon} from "svg-sprites/client-icons";
|
||||
import {global_client_actions} from "tc-shared/events/GlobalEvents";
|
||||
import {server_connections} from "tc-shared/ConnectionManager";
|
||||
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||
import {
|
||||
add_server_to_bookmarks,
|
||||
Bookmark,
|
||||
bookmarkEvents,
|
||||
bookmarks,
|
||||
BookmarkType,
|
||||
boorkmak_connect,
|
||||
DirectoryBookmark
|
||||
} from "tc-shared/bookmarks";
|
||||
import {getBackend} from "tc-shared/backend";
|
||||
|
||||
function renderConnectionItems() {
|
||||
const items: MenuBarEntry[] = [];
|
||||
|
||||
const currentConnectionConnected = !!server_connections.active_connection()?.connected;
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Connect to a server"),
|
||||
icon: ClientIcon.Connect,
|
||||
click: () => global_client_actions.fire("action_open_window_connect", { newTab: currentConnectionConnected })
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Disconnect from current server"),
|
||||
icon: ClientIcon.Disconnect,
|
||||
disabled: !currentConnectionConnected,
|
||||
click: () => server_connections.active_connection()?.disconnectFromServer()
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Disconnect from all servers"),
|
||||
icon: ClientIcon.Disconnect,
|
||||
disabled: server_connections.all_connections().findIndex(e => e.connected) === -1,
|
||||
click: () => server_connections.all_connections().forEach(connection => connection.disconnectFromServer())
|
||||
});
|
||||
|
||||
if(__build.target === "client") {
|
||||
items.push({ type: "separator" });
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Quit"),
|
||||
icon: ClientIcon.CloseButton,
|
||||
click: () => getBackend("native").quit()
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function renderBookmarkItems() {
|
||||
const items: MenuBarEntry[] = [];
|
||||
|
||||
const renderBookmark = (bookmark: Bookmark | DirectoryBookmark): MenuBarEntry => {
|
||||
if(bookmark.type === BookmarkType.ENTRY) {
|
||||
return {
|
||||
type: "normal",
|
||||
label: bookmark.display_name,
|
||||
click: () => boorkmak_connect(bookmark),
|
||||
icon: bookmark.last_icon_id ? { serverUniqueId: bookmark.last_icon_server_id, iconId: bookmark.last_icon_id } : undefined
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: "normal",
|
||||
label: bookmark.display_name,
|
||||
icon: ClientIcon.Folder,
|
||||
children: bookmark.content.map(renderBookmark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
icon: ClientIcon.BookmarkManager,
|
||||
label: tr("Manage bookmarks"),
|
||||
click: () => global_client_actions.fire("action_open_window", { window: "bookmark-manage" })
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
icon: ClientIcon.BookmarkAdd,
|
||||
label: tr("Add current server to bookmarks"),
|
||||
disabled: !server_connections.active_connection()?.connected,
|
||||
click: () => add_server_to_bookmarks(server_connections.active_connection())
|
||||
});
|
||||
|
||||
const rootMarks = bookmarks().content;
|
||||
if(rootMarks.length !== 0) {
|
||||
items.push({ type: "separator" });
|
||||
items.push(...rootMarks.map(renderBookmark));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function renderPermissionItems() : MenuBarEntry[] {
|
||||
const items: MenuBarEntry[] = [];
|
||||
|
||||
const currentConnectionConnected = !!server_connections.active_connection()?.connected;
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Server Groups"),
|
||||
icon: ClientIcon.PermissionServerGroups,
|
||||
click: () => global_client_actions.fire("action_open_window_permissions", { defaultTab: "groups-server" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Client Permissions"),
|
||||
icon: ClientIcon.PermissionClient,
|
||||
click: () => global_client_actions.fire("action_open_window_permissions", { defaultTab: "client" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Channel Client Permissions"),
|
||||
icon: ClientIcon.PermissionClient,
|
||||
click: () => global_client_actions.fire("action_open_window_permissions", { defaultTab: "client-channel" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Channel Groups"),
|
||||
icon: ClientIcon.PermissionChannel,
|
||||
click: () => global_client_actions.fire("action_open_window_permissions", { defaultTab: "groups-channel" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Channel Permissions"),
|
||||
icon: ClientIcon.PermissionChannel,
|
||||
click: () => global_client_actions.fire("action_open_window_permissions", { defaultTab: "channel" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
items.push({ type: "separator" });
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("List Privilege Keys"),
|
||||
icon: ClientIcon.Token,
|
||||
click: () => global_client_actions.fire("action_open_window", { window: "token-list" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Use Privilege Key"),
|
||||
icon: ClientIcon.TokenUse,
|
||||
click: () => global_client_actions.fire("action_open_window", { window: "token-use" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function renderToolItems() : MenuBarEntry[] {
|
||||
const items: MenuBarEntry[] = [];
|
||||
|
||||
const currentConnectionConnected = !!server_connections.active_connection()?.connected;
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Ban List"),
|
||||
icon: ClientIcon.BanList,
|
||||
click: () => global_client_actions.fire("action_open_window", { window: "ban-list" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Query List"),
|
||||
icon: ClientIcon.ServerQuery,
|
||||
click: () => global_client_actions.fire("action_open_window", { window: "query-manage" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Query Create"),
|
||||
icon: ClientIcon.ServerQuery,
|
||||
click: () => global_client_actions.fire("action_open_window", { window: "query-create" }),
|
||||
disabled: !currentConnectionConnected
|
||||
});
|
||||
|
||||
items.push({ type: "separator" });
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Modify CSS variables"),
|
||||
click: () => global_client_actions.fire("action_open_window", { window: "css-variable-editor" })
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Open Registry"),
|
||||
click: () => global_client_actions.fire("action_open_window", { window: "settings-registry" })
|
||||
});
|
||||
|
||||
items.push({ type: "separator" });
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Settings"),
|
||||
click: () => global_client_actions.fire("action_open_window_settings")
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function renderHelpItems() : MenuBarEntry[] {
|
||||
const items: MenuBarEntry[] = [];
|
||||
|
||||
if(__build.target === "client") {
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Check for updates"),
|
||||
icon: ClientIcon.CheckUpdate,
|
||||
click: () => getBackend("native").openClientUpdater()
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Open client changelog"),
|
||||
click: () => getBackend("native").openChangeLog()
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Visit TeaSpeak.de"),
|
||||
click: () => window.open('https://teaspeak.de/', '_blank')
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Visit TeaSpeak forum"),
|
||||
click: () => window.open('https://forum.teaspeak.de/', '_blank')
|
||||
});
|
||||
|
||||
if(__build.target === "client" && getBackend("native").showDeveloperOptions()) {
|
||||
items.push({ type: "separator" });
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Open developer tools"),
|
||||
click: () => getBackend("native").openDeveloperTools()
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Reload UI"),
|
||||
click: () => getBackend("native").reloadWindow()
|
||||
});
|
||||
}
|
||||
items.push({ type: "separator" });
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: __build.target === "web" ? tr("About TeaWeb") : tr("About TeaClient"),
|
||||
click: () => global_client_actions.fire("action_open_window", { window: "about" })
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function updateMenuBar() {
|
||||
const items: MenuBarEntry[] = [];
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Connection"),
|
||||
children: renderConnectionItems()
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Favorites"),
|
||||
children: renderBookmarkItems()
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Permissions"),
|
||||
children: renderPermissionItems()
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Tools"),
|
||||
children: renderToolItems()
|
||||
});
|
||||
|
||||
items.push({
|
||||
type: "normal",
|
||||
label: tr("Help"),
|
||||
children: renderHelpItems()
|
||||
});
|
||||
|
||||
/* TODO: Check if it's not exactly the same menu bar */
|
||||
getMenuBarDriver().setEntries(items);
|
||||
}
|
||||
|
||||
let updateListener: MenuBarUpdateListener;
|
||||
class MenuBarUpdateListener {
|
||||
private generalHandlerEvents: (() => void)[] = [];
|
||||
private registeredHandlerEvents: {[key: string]: (() => void)[]} = {};
|
||||
|
||||
initializeListeners() {
|
||||
this.generalHandlerEvents.push(server_connections.events().on("notify_handler_created", event => {
|
||||
this.registerHandlerEvents(event.handler);
|
||||
}));
|
||||
this.generalHandlerEvents.push(server_connections.events().on("notify_handler_deleted", event => {
|
||||
this.registeredHandlerEvents[event.handlerId]?.forEach(callback => callback());
|
||||
delete this.registeredHandlerEvents[event.handlerId];
|
||||
}));
|
||||
this.generalHandlerEvents.push(server_connections.events().on("notify_active_handler_changed", () => {
|
||||
updateMenuBar();
|
||||
}));
|
||||
this.generalHandlerEvents.push(bookmarkEvents.on("notify_bookmarks_updated", () => {
|
||||
updateMenuBar();
|
||||
}))
|
||||
server_connections.all_connections().forEach(handler => this.registerHandlerEvents(handler));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.generalHandlerEvents.forEach(callback => callback());
|
||||
Object.keys(this.registeredHandlerEvents).forEach(id => this.registeredHandlerEvents[id].forEach(callback => callback()));
|
||||
|
||||
this.registeredHandlerEvents = {};
|
||||
this.generalHandlerEvents = [];
|
||||
}
|
||||
|
||||
private registerHandlerEvents(handler: ConnectionHandler) {
|
||||
const events = this.registeredHandlerEvents[handler.handlerId] = [];
|
||||
events.push(handler.events().on("notify_connection_state_changed", () => {
|
||||
updateMenuBar();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "menu bar entries init",
|
||||
function: async () => {
|
||||
updateMenuBar();
|
||||
|
||||
updateListener = new MenuBarUpdateListener();
|
||||
updateListener.initializeListeners();
|
||||
},
|
||||
priority: 50
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
import {ClientIcon} from "svg-sprites/client-icons";
|
||||
import {RemoteIconInfo} from "tc-shared/file/Icons";
|
||||
|
||||
export type MenuBarEntrySeparator = {
|
||||
uniqueId?: string,
|
||||
type: "separator"
|
||||
};
|
||||
export type MenuBarEntryNormal = {
|
||||
uniqueId?: string,
|
||||
type: "normal",
|
||||
label: string,
|
||||
|
||||
disabled?: boolean,
|
||||
visible?: boolean,
|
||||
|
||||
icon?: ClientIcon | RemoteIconInfo,
|
||||
click?: () => void,
|
||||
|
||||
children?: MenuBarEntry[]
|
||||
};
|
||||
|
||||
export type MenuBarEntry = MenuBarEntrySeparator | MenuBarEntryNormal;
|
||||
|
||||
export interface MenuBarDriver {
|
||||
/**
|
||||
* Separators on top level might not be rendered.
|
||||
* @param entries
|
||||
*/
|
||||
setEntries(entries: MenuBarEntry[]);
|
||||
|
||||
/**
|
||||
* Removes the menu bar
|
||||
*/
|
||||
clearEntries();
|
||||
}
|
||||
|
||||
let driver: MenuBarDriver;
|
||||
export function getMenuBarDriver() : MenuBarDriver {
|
||||
return driver;
|
||||
}
|
||||
|
||||
export function setMenuBarDriver(driver_: MenuBarDriver) {
|
||||
driver = driver_;
|
||||
}
|
|
@ -18,7 +18,7 @@ import * as log from "../../log";
|
|||
import {LogCategory} from "../../log";
|
||||
import * as i18nc from "../../i18n/country";
|
||||
import {formatMessage} from "../../ui/frames/chat";
|
||||
import * as top_menu from "../frames/MenuBar";
|
||||
import * as top_menu from "../frames/MenuBarOld";
|
||||
import {generateIconJQueryTag, getIconManager} from "tc-shared/file/Icons";
|
||||
|
||||
export function spawnBookmarkModal() {
|
||||
|
@ -402,9 +402,5 @@ export function spawnBookmarkModal() {
|
|||
});
|
||||
|
||||
modal.htmlTag.dividerfy().find(".modal-body").addClass("modal-bookmarks");
|
||||
modal.close_listener.push(() => {
|
||||
top_menu.rebuild_bookmarks();
|
||||
});
|
||||
|
||||
modal.open();
|
||||
}
|
|
@ -32,10 +32,10 @@ import {spawnGroupCreate} from "tc-shared/ui/modal/ModalGroupCreate";
|
|||
import {spawnModalGroupPermissionCopy} from "tc-shared/ui/modal/ModalGroupPermissionCopy";
|
||||
import {InternalModal} from "tc-shared/ui/react-elements/internal-modal/Controller";
|
||||
import {ErrorCode} from "tc-shared/connection/ErrorCode";
|
||||
import {PermissionEditorTab} from "tc-shared/events/GlobalEvents";
|
||||
|
||||
const cssStyle = require("./ModalPermissionEditor.scss");
|
||||
|
||||
export type PermissionEditorTab = "groups-server" | "groups-channel" | "channel" | "client" | "client-channel";
|
||||
export type PermissionEditorSubject =
|
||||
"groups-server"
|
||||
| "groups-channel"
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import {setBackend} from "tc-shared/backend";
|
||||
import {WebClientBackend} from "tc-shared/backend/WebClient";
|
||||
|
||||
setBackend(new class implements WebClientBackend {});
|
|
@ -0,0 +1,4 @@
|
|||
import {setMenuBarDriver} from "tc-shared/ui/frames/menu-bar";
|
||||
import {WebMenuBarDriver} from "tc-backend/web/ui/menu-bar/Controller";
|
||||
|
||||
setMenuBarDriver(new WebMenuBarDriver());
|
|
@ -9,6 +9,7 @@ import "./audio-lib";
|
|||
import "./hooks/ServerConnection";
|
||||
import "./hooks/ExternalModal";
|
||||
import "./hooks/AudioRecorder";
|
||||
import "./hooks/MenuBar";
|
||||
|
||||
import "./UnloadHandler";
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import {MenuBarDriver, MenuBarEntry} from "tc-shared/ui/frames/menu-bar";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import {MenuBarRenderer} from "tc-backend/web/ui/menu-bar/Renderer";
|
||||
|
||||
const cssStyle = require("./Renderer.scss");
|
||||
|
||||
let uniqueMenuEntryId = 0;
|
||||
export class WebMenuBarDriver implements MenuBarDriver {
|
||||
private readonly htmlContainer: HTMLDivElement;
|
||||
private currentEntries: MenuBarEntry[] = [];
|
||||
|
||||
constructor() {
|
||||
this.htmlContainer = document.createElement("div");
|
||||
this.htmlContainer.classList.add(cssStyle.container);
|
||||
document.body.append(this.htmlContainer);
|
||||
}
|
||||
|
||||
clearEntries() {
|
||||
this.currentEntries = [];
|
||||
this.renderMenu();
|
||||
}
|
||||
|
||||
setEntries(entries: MenuBarEntry[]) {
|
||||
if(this.currentEntries === entries) { return; }
|
||||
this.currentEntries = entries.slice(0);
|
||||
this.currentEntries.forEach(WebMenuBarDriver.fixupUniqueIds);
|
||||
this.renderMenu();
|
||||
}
|
||||
|
||||
private static fixupUniqueIds(entry: MenuBarEntry) {
|
||||
if(!entry.uniqueId) {
|
||||
entry.uniqueId = "item-" + (++uniqueMenuEntryId);
|
||||
}
|
||||
if(entry.type === "normal") {
|
||||
entry.children?.forEach(WebMenuBarDriver.fixupUniqueIds);
|
||||
}
|
||||
}
|
||||
|
||||
private renderMenu() {
|
||||
ReactDOM.render(React.createElement(MenuBarRenderer, { items: this.currentEntries }), this.htmlContainer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
.container {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
height: 1.5em;
|
||||
width: 100%;
|
||||
|
||||
background: #fafafa;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 201;
|
||||
|
||||
font-family: Arial, serif;
|
||||
|
||||
> .containerMenuItem {
|
||||
> .menuItem {
|
||||
.containerIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: .125em;
|
||||
margin-bottom: .125em;
|
||||
}
|
||||
}
|
||||
|
||||
.containerMenuItem {
|
||||
position: relative;
|
||||
|
||||
.menuItem {
|
||||
cursor: pointer;
|
||||
|
||||
padding-left: .4em;
|
||||
padding-right: .4em;
|
||||
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: max-content;
|
||||
|
||||
> * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.containerIcon {
|
||||
height: 1.2em;
|
||||
width: 1.2em;
|
||||
padding: .1em;
|
||||
font-size: 1em;
|
||||
|
||||
margin-right: .2em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.containerLabel {
|
||||
display: inline-block;
|
||||
align-self: center;
|
||||
|
||||
a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.disabled) {
|
||||
background-color: rgba(0, 0, 0, 0.27);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background-color: rgba(0, 0, 0, 0.13);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.subMenu {
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
|
||||
background: white;
|
||||
position: absolute;
|
||||
|
||||
top: 100%;
|
||||
border: 1px solid black;
|
||||
|
||||
> .container-menu-item {
|
||||
padding-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.typeSide {
|
||||
&.subEntries {
|
||||
padding-right: .8em;
|
||||
}
|
||||
|
||||
&.subEntries:after {
|
||||
position: absolute;
|
||||
|
||||
display: block;
|
||||
content: '>';
|
||||
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
right: .4em;
|
||||
}
|
||||
|
||||
> .subMenu {
|
||||
top: -1px; /* border */
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> .subMenu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: rgba(0, 0, 0, 0.27);
|
||||
|
||||
> .subMenu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
import {MenuBarEntry, MenuBarEntryNormal} from "tc-shared/ui/frames/menu-bar";
|
||||
import * as React from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
|
||||
import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
|
||||
import {getIconManager} from "tc-shared/file/Icons";
|
||||
|
||||
const cssStyle = require("./Renderer.scss");
|
||||
|
||||
const SubMenuRenderer = (props: { entry: MenuBarEntryNormal }) => {
|
||||
if(!props.entry.children) { return null; }
|
||||
|
||||
return (
|
||||
<div className={cssStyle.subMenu}>
|
||||
{props.entry.children.map(child => {
|
||||
if(child.type === "separator") {
|
||||
return <hr key={child.uniqueId} />
|
||||
} else {
|
||||
return <EntryRenderer entry={child} key={child.uniqueId} />;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MenuItemRenderer = (props: { entry: MenuBarEntryNormal }) => {
|
||||
let icon;
|
||||
if(typeof props.entry.icon === "string") {
|
||||
icon = <ClientIconRenderer icon={props.entry.icon} key={"client-icon"} />;
|
||||
} else if(typeof props.entry.icon === "object") {
|
||||
let remoteIcon = getIconManager().resolveIcon(props.entry.icon.iconId, props.entry.icon.serverUniqueId, props.entry.icon.handlerId);
|
||||
icon = <RemoteIconRenderer icon={remoteIcon} key={"remote-icon"} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cssStyle.menuItem} onClick={props.entry.click}>
|
||||
<div className={cssStyle.containerIcon}>{icon}</div>
|
||||
<div className={cssStyle.containerLabel}>{props.entry.label}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const EntryRenderer = React.memo((props: { entry: MenuBarEntryNormal }) => {
|
||||
let classList = [cssStyle.containerMenuItem, cssStyle.typeSide];
|
||||
if(props.entry.children?.length) {
|
||||
classList.push(cssStyle.subEntries);
|
||||
}
|
||||
if(props.entry.disabled) {
|
||||
classList.push(cssStyle.disabled);
|
||||
}
|
||||
|
||||
if(typeof props.entry.visible === "boolean" && !props.entry.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classList.join(" ")}>
|
||||
<MenuItemRenderer entry={props.entry} />
|
||||
<SubMenuRenderer entry={props.entry} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const MainEntryRenderer = React.memo((props: { entry: MenuBarEntry }) => {
|
||||
const [ active, setActive ] = useState(false);
|
||||
|
||||
if(props.entry.type !== "normal") { return null; }
|
||||
if(typeof props.entry.visible === "boolean" && !props.entry.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let classList = [cssStyle.containerMenuItem];
|
||||
if(props.entry.children?.length) {
|
||||
classList.push(cssStyle.subEntries);
|
||||
}
|
||||
if(props.entry.disabled) {
|
||||
classList.push(cssStyle.disabled);
|
||||
}
|
||||
if(active) {
|
||||
classList.push(cssStyle.active);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(!active) { return; }
|
||||
|
||||
const listener = (event: MouseEvent | FocusEvent) => {
|
||||
event.preventDefault();
|
||||
setActive(false);
|
||||
};
|
||||
|
||||
document.addEventListener("click", listener);
|
||||
document.addEventListener("focusout", listener);
|
||||
return () => {
|
||||
document.removeEventListener("click", listener);
|
||||
document.removeEventListener("focusout", listener);
|
||||
};
|
||||
}, [ active ])
|
||||
|
||||
return (
|
||||
<div className={classList.join(" ")}
|
||||
onClick={() => setActive(true)}
|
||||
>
|
||||
<MenuItemRenderer entry={props.entry} />
|
||||
<SubMenuRenderer entry={props.entry} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const MenuBarRenderer = (props: { items: MenuBarEntry[] }) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{props.items.map(item => <MainEntryRenderer entry={item} key={item.uniqueId} />)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue