2019-06-30 16:03:28 +02:00
|
|
|
namespace contextmenu {
|
|
|
|
export interface MenuEntry {
|
|
|
|
callback?: () => void;
|
|
|
|
type: MenuEntryType;
|
|
|
|
name: (() => string) | string;
|
|
|
|
icon_class?: string;
|
|
|
|
icon_path?: string;
|
|
|
|
disabled?: boolean;
|
|
|
|
visible?: boolean;
|
|
|
|
|
|
|
|
checkbox_checked?: boolean;
|
|
|
|
|
|
|
|
invalidPermission?: boolean;
|
|
|
|
sub_menu?: MenuEntry[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum MenuEntryType {
|
|
|
|
CLOSE,
|
|
|
|
ENTRY,
|
|
|
|
CHECKBOX,
|
|
|
|
HR,
|
|
|
|
SUB_MENU
|
|
|
|
}
|
2019-02-17 16:08:10 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
export class Entry {
|
|
|
|
static HR() {
|
|
|
|
return {
|
|
|
|
callback: () => {},
|
|
|
|
type: MenuEntryType.HR,
|
|
|
|
name: "",
|
|
|
|
icon: ""
|
|
|
|
};
|
|
|
|
};
|
2019-02-17 16:08:10 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
static CLOSE(callback: () => void) {
|
|
|
|
return {
|
|
|
|
callback: callback,
|
|
|
|
type: MenuEntryType.CLOSE,
|
|
|
|
name: "",
|
|
|
|
icon: ""
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2019-01-20 18:43:14 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
export interface ContextMenuProvider {
|
2018-09-30 21:50:59 +02:00
|
|
|
despawn_context_menu();
|
2019-06-30 16:03:28 +02:00
|
|
|
spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]);
|
|
|
|
|
|
|
|
initialize();
|
|
|
|
finalize();
|
|
|
|
|
|
|
|
html_format_enabled() : boolean;
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
let provider: ContextMenuProvider;
|
|
|
|
export function spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]) {
|
|
|
|
if(!provider) {
|
|
|
|
console.error(tr("Failed to spawn context menu! Missing provider!"));
|
|
|
|
return;
|
|
|
|
}
|
2019-02-17 16:08:10 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
provider.spawn_context_menu(x, y, ...entries);
|
|
|
|
}
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
export function despawn_context_menu() {
|
|
|
|
if(!provider)
|
|
|
|
return;
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
provider.despawn_context_menu();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function get_provider() : ContextMenuProvider { return provider; }
|
|
|
|
export function set_provider(_provider: ContextMenuProvider) {
|
|
|
|
provider = _provider;
|
|
|
|
provider.initialize();
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
2018-04-11 17:56:09 +02:00
|
|
|
}
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
|
|
|
private _global_click_listener: (event) => any;
|
|
|
|
private _context_menu: JQuery;
|
|
|
|
private _close_callbacks: (() => any)[] = [];
|
2019-08-30 23:06:39 +02:00
|
|
|
private _visible = false;
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
despawn_context_menu() {
|
2019-08-30 23:06:39 +02:00
|
|
|
if(!this._visible)
|
2019-06-30 16:03:28 +02:00
|
|
|
return;
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-08-30 23:06:39 +02:00
|
|
|
let menu = this._context_menu || (this._context_menu = $(".context-menu"));
|
2019-06-30 16:03:28 +02:00
|
|
|
menu.animate({opacity: 0}, 100, () => menu.css("display", "none"));
|
2019-08-30 23:06:39 +02:00
|
|
|
this._visible = false;
|
2019-08-21 10:00:01 +02:00
|
|
|
for(const callback of this._close_callbacks) {
|
|
|
|
if(typeof(callback) !== "function") {
|
|
|
|
console.error(tr("Given close callback is not a function!. Callback: %o"), callback);
|
|
|
|
continue;
|
|
|
|
}
|
2019-06-30 16:03:28 +02:00
|
|
|
callback();
|
2019-08-21 10:00:01 +02:00
|
|
|
}
|
2019-06-30 16:03:28 +02:00
|
|
|
this._close_callbacks = [];
|
|
|
|
}
|
2018-09-30 21:50:59 +02:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
finalize() {
|
|
|
|
$(document).unbind('click', this._global_click_listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
initialize() {
|
|
|
|
this._global_click_listener = this.on_global_click.bind(this);
|
|
|
|
$(document).bind('click', this._global_click_listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
private on_global_click(event) {
|
|
|
|
let menu = this._context_menu || (this._context_menu = $(".context-menu"));
|
|
|
|
|
|
|
|
if(!menu.is(":visible")) return;
|
|
|
|
|
|
|
|
if ($(event.target).parents(".context-menu").length == 0) {
|
|
|
|
this.despawn_context_menu();
|
|
|
|
event.preventDefault();
|
2018-09-30 21:50:59 +02:00
|
|
|
}
|
2019-06-30 16:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private generate_tag(entry: contextmenu.MenuEntry) : JQuery {
|
|
|
|
if(entry.type == contextmenu.MenuEntryType.HR) {
|
|
|
|
return $.spawn("hr");
|
|
|
|
} else if(entry.type == contextmenu.MenuEntryType.ENTRY) {
|
|
|
|
let icon = entry.icon_class;
|
2018-09-30 21:50:59 +02:00
|
|
|
if(!icon || icon.length == 0) icon = "icon_empty";
|
2018-02-27 17:20:49 +01:00
|
|
|
else icon = "icon " + icon;
|
2018-04-11 17:56:09 +02:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
let tag = $.spawn("div").addClass("entry");
|
|
|
|
tag.append($.spawn("div").addClass(icon));
|
|
|
|
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
|
|
|
|
|
|
|
|
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
|
|
|
|
else {
|
|
|
|
tag.click( () => {
|
|
|
|
if($.isFunction(entry.callback))
|
|
|
|
entry.callback();
|
|
|
|
this.despawn_context_menu();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return tag;
|
|
|
|
} else if(entry.type == contextmenu.MenuEntryType.CHECKBOX) {
|
2019-08-21 10:00:01 +02:00
|
|
|
let checkbox = $.spawn("label").addClass("ccheckbox");
|
2019-06-30 16:03:28 +02:00
|
|
|
$.spawn("input").attr("type", "checkbox").prop("checked", !!entry.checkbox_checked).appendTo(checkbox);
|
|
|
|
$.spawn("span").addClass("checkmark").appendTo(checkbox);
|
|
|
|
|
|
|
|
let tag = $.spawn("div").addClass("entry");
|
|
|
|
tag.append(checkbox);
|
|
|
|
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
|
|
|
|
|
|
|
|
if(entry.disabled || entry.invalidPermission)
|
|
|
|
tag.addClass("disabled");
|
|
|
|
else {
|
|
|
|
tag.click( () => {
|
|
|
|
if($.isFunction(entry.callback))
|
|
|
|
entry.callback();
|
|
|
|
this.despawn_context_menu();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return tag;
|
|
|
|
} else if(entry.type == contextmenu.MenuEntryType.SUB_MENU) {
|
|
|
|
let icon = entry.icon_class;
|
|
|
|
if(!icon || icon.length == 0) icon = "icon_empty";
|
|
|
|
else icon = "icon " + icon;
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
let tag = $.spawn("div").addClass("entry").addClass("sub-container");
|
|
|
|
tag.append($.spawn("div").addClass(icon));
|
|
|
|
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
|
|
|
|
|
|
|
|
tag.append($.spawn("div").addClass("arrow right"));
|
|
|
|
|
|
|
|
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
|
|
|
|
else {
|
|
|
|
let menu = $.spawn("div").addClass("sub-menu").addClass("context-menu-container");
|
|
|
|
for(const e of entry.sub_menu) {
|
|
|
|
if(typeof(entry.visible) === 'boolean' && !entry.visible)
|
|
|
|
continue;
|
|
|
|
menu.append(this.generate_tag(e));
|
|
|
|
}
|
|
|
|
menu.appendTo(tag);
|
2019-03-07 15:30:53 +01:00
|
|
|
}
|
2019-06-30 16:03:28 +02:00
|
|
|
return tag;
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
2019-06-30 16:03:28 +02:00
|
|
|
return $.spawn("div").text("undefined");
|
2018-09-30 21:50:59 +02:00
|
|
|
}
|
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
spawn_context_menu(x: number, y: number, ...entries: contextmenu.MenuEntry[]) {
|
2019-08-30 23:06:39 +02:00
|
|
|
this._visible = true;
|
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
let menu_tag = this._context_menu || (this._context_menu = $(".context-menu"));
|
|
|
|
menu_tag.finish().empty().css("opacity", "0");
|
2019-02-17 16:08:10 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
const menu_container = $.spawn("div").addClass("context-menu-container");
|
|
|
|
this._close_callbacks = [];
|
2018-09-30 21:50:59 +02:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
for(const entry of entries){
|
|
|
|
if(typeof(entry.visible) === 'boolean' && !entry.visible)
|
|
|
|
continue;
|
2019-03-07 15:30:53 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
if(entry.type == contextmenu.MenuEntryType.CLOSE) {
|
2019-08-21 10:00:01 +02:00
|
|
|
if(entry.callback)
|
|
|
|
this._close_callbacks.push(entry.callback);
|
2019-06-30 16:03:28 +02:00
|
|
|
} else
|
|
|
|
menu_container.append(this.generate_tag(entry));
|
|
|
|
}
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
menu_tag.append(menu_container);
|
|
|
|
menu_tag.animate({opacity: 1}, 100).css("display", "block");
|
2019-03-25 20:04:04 +01:00
|
|
|
|
2019-06-30 16:03:28 +02:00
|
|
|
const width = menu_container.visible_width();
|
|
|
|
if(x + width + 5 > window.innerWidth)
|
|
|
|
menu_container.addClass("left");
|
|
|
|
|
|
|
|
// In the right position (the mouse)
|
|
|
|
menu_tag.css({
|
|
|
|
"top": y + "px",
|
|
|
|
"left": x + "px"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
html_format_enabled(): boolean {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2019-03-25 20:04:04 +01:00
|
|
|
|
2019-06-30 17:34:22 +02:00
|
|
|
contextmenu.set_provider(new HTMLContextMenuProvider());
|