mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Rewrite pgAdmin main menu bar to use React. #5615
This commit is contained in:
@@ -554,32 +554,18 @@ def index():
|
||||
check_browser_upgrade()
|
||||
store_setting('LastUpdateCheck', today)
|
||||
|
||||
auth_only_internal = False
|
||||
auth_source = []
|
||||
|
||||
session['allow_save_password'] = True
|
||||
|
||||
if config.SERVER_MODE:
|
||||
if session['auth_source_manager']['current_source'] == INTERNAL:
|
||||
auth_only_internal = True
|
||||
auth_source = session['auth_source_manager'][
|
||||
'source_friendly_name']
|
||||
|
||||
if not config.MASTER_PASSWORD_REQUIRED and 'pass_enc_key' in session:
|
||||
session['allow_save_password'] = False
|
||||
if config.SERVER_MODE and not config.MASTER_PASSWORD_REQUIRED and \
|
||||
'pass_enc_key' in session:
|
||||
session['allow_save_password'] = False
|
||||
|
||||
response = Response(render_template(
|
||||
MODULE_NAME + "/index.html",
|
||||
username=current_user.username,
|
||||
auth_source=auth_source,
|
||||
is_admin=current_user.has_role("Administrator"),
|
||||
logout_url=get_logout_url(),
|
||||
requirejs=True,
|
||||
basejs=True,
|
||||
mfa_enabled=is_mfa_enabled(),
|
||||
login_url=login_url,
|
||||
_=gettext,
|
||||
auth_only_internal=auth_only_internal,
|
||||
_=gettext
|
||||
))
|
||||
|
||||
# Set the language cookie after login, so next time the user will have that
|
||||
@@ -662,6 +648,16 @@ def utils():
|
||||
|
||||
for submodule in current_blueprint.submodules:
|
||||
snippets.extend(submodule.jssnippets)
|
||||
|
||||
auth_only_internal = False
|
||||
auth_source = []
|
||||
|
||||
if config.SERVER_MODE:
|
||||
if session['auth_source_manager']['current_source'] == INTERNAL:
|
||||
auth_only_internal = True
|
||||
auth_source = session['auth_source_manager'][
|
||||
'source_friendly_name']
|
||||
|
||||
return make_response(
|
||||
render_template(
|
||||
'browser/js/utils.js',
|
||||
@@ -684,6 +680,13 @@ def utils():
|
||||
vw_edt_default_placeholder=VW_EDT_DEFAULT_PLACEHOLDER,
|
||||
enable_psql=config.ENABLE_PSQL,
|
||||
pgadmin_server_locale=default_locale,
|
||||
_=gettext,
|
||||
auth_only_internal=auth_only_internal,
|
||||
mfa_enabled=is_mfa_enabled(),
|
||||
is_admin=current_user.has_role("Administrator"),
|
||||
login_url=login_url,
|
||||
username=current_user.username,
|
||||
auth_source=auth_source
|
||||
),
|
||||
200, {'Content-Type': MIMETYPE_APP_JS})
|
||||
|
||||
|
||||
@@ -8,55 +8,51 @@
|
||||
//////////////////////////////////////////////////////////////
|
||||
import gettext from 'sources/gettext';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { getBrowser } from '../../../static/js/utils';
|
||||
import Menu, { MenuItem } from './new_menu';
|
||||
import Menu, { MenuItem } from '../../../static/js/helpers/Menu';
|
||||
import getApiInstance from '../../../static/js/api_instance';
|
||||
import url_for from 'sources/url_for';
|
||||
import Notifier from '../../../static/js/helpers/Notifier';
|
||||
|
||||
export let MainMenus = [
|
||||
const MAIN_MENUS = [
|
||||
{ label: gettext('File'), name: 'file', id: 'mnu_file', index: 0, addSepratior: true },
|
||||
{ label: gettext('Object'), name: 'object', id: 'mnu_obj', index: 1, addSepratior: true },
|
||||
{ label: gettext('Tools'), name: 'tools', id: 'mnu_tools', index: 2, addSepratior: true },
|
||||
{ label: gettext('Help'), name: 'help', id: 'mnu_help', index: 5, addSepratior: false }
|
||||
];
|
||||
|
||||
let {name: browser} = getBrowser();
|
||||
|
||||
export default function createMainMenus() {
|
||||
pgAdmin.Browser.MainMenus = [];
|
||||
MainMenus.forEach((_menu) => {
|
||||
let menuObj = Menu.create(_menu.name, _menu.label, _menu.id, _menu.index, _menu.addSepratior);
|
||||
pgAdmin.Browser.MainMenus.push(menuObj);
|
||||
// Don't add menuItems for Object menu as it's menuItems get changed on tree selection.
|
||||
if(_menu.name !== 'object') {
|
||||
menuObj.addMenuItems(Object.values(pgAdmin.Browser.menus[_menu.name]));
|
||||
menuObj.menuItems.forEach((menuItem, index)=> {
|
||||
menuItem?.menu_items?.forEach((item, indx)=> {
|
||||
item.below && menuItem?.menu_items.splice(indx+1, 0, getSeparator());
|
||||
export default class MainMenuFactory {
|
||||
static createMainMenus() {
|
||||
pgAdmin.Browser.MainMenus = [];
|
||||
MAIN_MENUS.forEach((_menu) => {
|
||||
let menuObj = Menu.create(_menu.name, _menu.label, _menu.id, _menu.index, _menu.addSepratior);
|
||||
pgAdmin.Browser.MainMenus.push(menuObj);
|
||||
// Don't add menuItems for Object menu as it's menuItems get changed on tree selection.
|
||||
if(_menu.name !== 'object') {
|
||||
menuObj.addMenuItems(Object.values(pgAdmin.Browser.all_menus_cache[_menu.name]));
|
||||
menuObj.getMenuItems().forEach((menuItem, index)=> {
|
||||
menuItem?.getMenuItems()?.forEach((item, indx)=> {
|
||||
item.below && menuItem?.getMenuItems().splice(indx+1, 0, MainMenuFactory.getSeparator());
|
||||
});
|
||||
if(menuItem.below) {
|
||||
menuObj.addMenuItem(MainMenuFactory.getSeparator(), index+1);
|
||||
}
|
||||
});
|
||||
if(menuItem.below) {
|
||||
menuObj.addMenuItem(getSeparator(), index+1);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function getSeparator() {
|
||||
return new MenuItem({type: 'separator'});
|
||||
}
|
||||
pgAdmin.Browser.enable_disable_menus();
|
||||
}
|
||||
|
||||
export function refreshMainMenuItems(menu, menuItems) {
|
||||
if(browser == 'Nwjs') {
|
||||
static getSeparator(label, priority) {
|
||||
return new MenuItem({type: 'separator', label, priority});
|
||||
}
|
||||
|
||||
static refreshMainMenuItems(menu, menuItems) {
|
||||
menu.setMenuItems(menuItems);
|
||||
pgAdmin.Browser.Events.trigger('pgadmin:nw-refresh-menu-item', menu);
|
||||
}
|
||||
}
|
||||
|
||||
// Factory to create menu items for main menu.
|
||||
export class MainMenuItemFactory {
|
||||
static create(options) {
|
||||
static createMenuItem(options) {
|
||||
return new MenuItem({...options, callback: () => {
|
||||
// Some callbacks registered in 'callbacks' check and call specifiec callback function
|
||||
if (options.module && 'callbacks' in options.module && options.module.callbacks[options.callback]) {
|
||||
@@ -83,4 +79,35 @@ export class MainMenuItemFactory {
|
||||
pgAdmin.Browser.Events.trigger('pgadmin:nw-update-checked-menu-item', item);
|
||||
});
|
||||
}
|
||||
|
||||
static getContextMenu(menuList, item, node) {
|
||||
Menu.sortMenus(menuList);
|
||||
|
||||
let ctxMenus = {};
|
||||
let ctxIndex = 1;
|
||||
menuList.forEach(ctx => {
|
||||
let ctx_uid = _.uniqueId('ctx_');
|
||||
let sub_ctx_item = {};
|
||||
ctx.checkAndSetDisabled(node, item);
|
||||
if (ctx.getMenuItems()) {
|
||||
// Menu.sortMenus(ctx.getMenuItems());
|
||||
ctx.getMenuItems().forEach((c) => {
|
||||
c.checkAndSetDisabled(node, item);
|
||||
if (!c.isDisabled) {
|
||||
sub_ctx_item[ctx_uid + _.uniqueId('_sub_')] = c.getContextItem(c.label, c.isDisabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!ctx.isDisabled) {
|
||||
if(ctx.type == 'separator') {
|
||||
ctxMenus[ctx_uid + '_' + ctx.priority + '_' + + ctxIndex + '_sep'] = '----';
|
||||
} else {
|
||||
ctxMenus[ctx_uid + '_' + ctx.priority + '_' + + ctxIndex + '_itm'] = ctx.getContextItem(ctx.label, ctx.isDisabled, sub_ctx_item);
|
||||
}
|
||||
}
|
||||
ctxIndex++;
|
||||
});
|
||||
|
||||
return ctxMenus;
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { generateNodeUrl } from './node_ajax';
|
||||
import { getBrowser } from '../../../static/js/utils';
|
||||
import createMainMenus, {refreshMainMenuItems, MainMenuItemFactory} from './main_menu';
|
||||
import { getContextMenu} from './new_menu';
|
||||
import MainMenuFactory from './MainMenuFactory';
|
||||
import _ from 'lodash';
|
||||
import Notify, {initializeModalProvider, initializeNotifier} from '../../../static/js/helpers/Notifier';
|
||||
import { checkMasterPassword } from '../../../static/js/Dialogs/index';
|
||||
@@ -26,7 +24,7 @@ define('pgadmin.browser', [
|
||||
'sources/tree/tree_init',
|
||||
'pgadmin.browser.utils', 'wcdocker', 'jquery.contextmenu',
|
||||
'pgadmin.browser.preferences', 'pgadmin.browser.messages',
|
||||
'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin.browser.layout',
|
||||
'pgadmin.browser.panel', 'pgadmin.browser.layout',
|
||||
'pgadmin.browser.runtime', 'pgadmin.browser.error', 'pgadmin.browser.frame',
|
||||
'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',
|
||||
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
||||
@@ -290,9 +288,9 @@ define('pgadmin.browser', [
|
||||
// We also support showing dashboards, HTML file, external URL
|
||||
frames: {},
|
||||
/* Menus */
|
||||
// pgAdmin.Browser.MenuItem.add_menus(...) will register all the menus
|
||||
// add_menus(...) will register all the menus
|
||||
// in this container
|
||||
menus: {
|
||||
all_menus_cache: {
|
||||
// All context menu goes here under certain menu types.
|
||||
// i.e. context: {'server': [...], 'server-group': [...]}
|
||||
context: {},
|
||||
@@ -309,7 +307,8 @@ define('pgadmin.browser', [
|
||||
// Help menus
|
||||
help: {},
|
||||
},
|
||||
native_context_menus: {},
|
||||
MainMenus: [],
|
||||
BrowserContextMenu: [],
|
||||
|
||||
add_panels: function() {
|
||||
/* Add hooked-in panels by extensions */
|
||||
@@ -378,40 +377,37 @@ define('pgadmin.browser', [
|
||||
let category = {
|
||||
'common': []
|
||||
};
|
||||
for(let _key of Object.keys(obj.menus[name][d._type])){
|
||||
let menuCategory = obj.menus[name][d._type][_key].category;
|
||||
let menuItem = obj.menus[name][d._type][_key];
|
||||
|
||||
if(menuCategory in this.menu_categories) {
|
||||
category[menuCategory] ? category[menuCategory].push(menuItem): category[menuCategory] = [menuItem];
|
||||
} else if (!_.isUndefined(menuCategory)) {
|
||||
category[menuCategory] ? category[menuCategory].push(menuItem): category[menuCategory] = [menuItem];
|
||||
} else {
|
||||
category['common'].push(menuItem);
|
||||
}
|
||||
const nodeTypeMenus = obj.all_menus_cache[name][d._type];
|
||||
for(let key of Object.keys(nodeTypeMenus)) {
|
||||
let menuItem = nodeTypeMenus[key];
|
||||
let menuCategory = menuItem.category ?? 'common';
|
||||
category[menuCategory] = category[menuCategory] ?? [];
|
||||
category[menuCategory].push(menuItem);
|
||||
}
|
||||
let menuItemList = [];
|
||||
|
||||
for(let c in category) {
|
||||
|
||||
if((c in obj.menu_categories || category[c].length > 1) && c != 'common' ) {
|
||||
let is_all_option_dis = true;
|
||||
category[c].forEach((c)=> {
|
||||
c.is_disabled = c.disabled(d, item);
|
||||
|
||||
c.setDisabled( c.is_disabled);
|
||||
|
||||
if(is_all_option_dis)
|
||||
is_all_option_dis = c.is_disabled;
|
||||
let allMenuItemsDisabled = true;
|
||||
category[c].forEach((mi)=> {
|
||||
mi.checkAndSetDisabled(d, item);
|
||||
if(allMenuItemsDisabled) {
|
||||
allMenuItemsDisabled = mi.isDisabled;
|
||||
}
|
||||
});
|
||||
|
||||
let label = c in obj.menu_categories ? obj.menu_categories[c].label : c;
|
||||
let priority = c in obj.menu_categories ? obj.menu_categories[c].priority || 10 : 10;
|
||||
if(!is_all_option_dis && skipDisabled){
|
||||
let _menuItem = MainMenuItemFactory.create({
|
||||
const categoryMenuOptions = obj.menu_categories[c];
|
||||
let label = categoryMenuOptions?.label ?? c;
|
||||
let priority = categoryMenuOptions?.priority ?? 10;
|
||||
|
||||
if(categoryMenuOptions?.above) {
|
||||
menuItemList.push(MainMenuFactory.getSeparator(label, priority));
|
||||
}
|
||||
if(!allMenuItemsDisabled && skipDisabled) {
|
||||
let _menuItem = MainMenuFactory.createMenuItem({
|
||||
name: c,
|
||||
label: label,
|
||||
module:c,
|
||||
module: c,
|
||||
category: c,
|
||||
menu_items: category[c],
|
||||
priority: priority
|
||||
@@ -419,7 +415,7 @@ define('pgadmin.browser', [
|
||||
|
||||
menuItemList.push(_menuItem);
|
||||
} else if(!skipDisabled){
|
||||
let _menuItem = MainMenuItemFactory.create({
|
||||
let _menuItem = MainMenuFactory.createMenuItem({
|
||||
name: c,
|
||||
label: label,
|
||||
module:c,
|
||||
@@ -430,15 +426,18 @@ define('pgadmin.browser', [
|
||||
|
||||
menuItemList.push(_menuItem);
|
||||
}
|
||||
if(categoryMenuOptions?.below) {
|
||||
menuItemList.push(MainMenuFactory.getSeparator(label, priority));
|
||||
}
|
||||
} else {
|
||||
category[c].forEach((c)=> {
|
||||
c.is_disabled = c.disabled(d, item);
|
||||
c.checkAndSetDisabled(d, item);
|
||||
});
|
||||
|
||||
category[c].forEach((m)=> {
|
||||
if(!skipDisabled) {
|
||||
menuItemList.push(m);
|
||||
} else if(skipDisabled && !m.is_disabled){
|
||||
} else if(skipDisabled && !m.isDisabled){
|
||||
menuItemList.push(m);
|
||||
}
|
||||
});
|
||||
@@ -449,77 +448,37 @@ define('pgadmin.browser', [
|
||||
},
|
||||
// Enable/disable menu options
|
||||
enable_disable_menus: function(item) {
|
||||
// Mechanism to enable/disable menus depending on the condition.
|
||||
let obj = this,
|
||||
// menu navigation bar
|
||||
navbar = $('#navbar-menu > ul').first(),
|
||||
// Drop down menu for objects
|
||||
$obj_mnu = navbar.find('li#mnu_obj .dropdown-menu').first(),
|
||||
// data for current selected object
|
||||
d = item ? obj.tree.itemData(item) : undefined,
|
||||
update_menuitem = function(m, o) {
|
||||
if (m instanceof pgAdmin.Browser.MenuItem) {
|
||||
m.update(d, item);
|
||||
}
|
||||
else {
|
||||
for (let key in m) {
|
||||
update_menuitem(m[key], o);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let obj = this;
|
||||
let d = item ? obj.tree.itemData(item) : undefined;
|
||||
toolBar.enable(gettext('View Data'), false);
|
||||
toolBar.enable(gettext('Filtered Rows'), false);
|
||||
|
||||
// All menus from the object menus (except the create drop-down
|
||||
// menu) needs to be removed.
|
||||
$obj_mnu.empty();
|
||||
|
||||
// All menus (except for the object menus) are already present.
|
||||
// They will just require to check, wheather they are
|
||||
// They will just require to check, whether they are
|
||||
// enabled/disabled.
|
||||
let {name: browser} = getBrowser();
|
||||
if(browser == 'Nwjs') {
|
||||
pgBrowser.MainMenus.forEach((menu) => {
|
||||
menu.menuItems.forEach((mitem) => {
|
||||
mitem.setDisabled(mitem.disabled(d, item));
|
||||
});
|
||||
pgBrowser.MainMenus.filter((m)=>m.name != 'object').forEach((menu) => {
|
||||
menu.menuItems.forEach((mitem) => {
|
||||
mitem.checkAndSetDisabled(d, item);
|
||||
});
|
||||
}else {
|
||||
_.each([
|
||||
{name: 'file', id: '#mnu_file', label: gettext('File')},
|
||||
{name: 'management', id: '#mnu_management', label: gettext('Management')},
|
||||
{name: 'tools', id: '#mnu_tools', label: gettext('Tools')},
|
||||
{name: 'help', id:'#mnu_help', label: gettext('Help')}], function(o) {
|
||||
_.each( obj.menus[o.name], function(m) {
|
||||
update_menuitem(m, o);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Create the object menu dynamically
|
||||
if (item && obj.menus['object'] && obj.menus['object'][d._type]) {
|
||||
if(browser == 'Nwjs') {
|
||||
let menuItemList = obj.getMenuList('object', item, d);
|
||||
let objectMenu = pgBrowser.MainMenus.filter((menu) => menu.name == 'object');
|
||||
objectMenu.length > 0 && refreshMainMenuItems(objectMenu[0], menuItemList);
|
||||
let ctxMenuList = obj.getMenuList('context', item, d, true);
|
||||
obj.native_context_menus = getContextMenu(ctxMenuList, item, d);
|
||||
} else {
|
||||
pgAdmin.Browser.MenuCreator(
|
||||
obj.Nodes, $obj_mnu, obj.menus['object'][d._type], obj.menu_categories, d, item
|
||||
);
|
||||
}
|
||||
let objectMenu = pgBrowser.MainMenus.find((menu) => menu.name == 'object');
|
||||
if (item && obj.all_menus_cache['object']?.[d._type]) {
|
||||
let menuItemList = obj.getMenuList('object', item, d);
|
||||
objectMenu && MainMenuFactory.refreshMainMenuItems(objectMenu, menuItemList);
|
||||
let ctxMenuList = obj.getMenuList('context', item, d, true);
|
||||
obj.BrowserContextMenu = MainMenuFactory.getContextMenu(ctxMenuList, item, d);
|
||||
} else {
|
||||
// Create a dummy 'no object seleted' menu
|
||||
let create_submenu = pgAdmin.Browser.MenuGroup(
|
||||
obj.menu_categories['create'], [{
|
||||
$el: $('<li><a class="dropdown-item disabled" href="#" role="menuitem">' + gettext('No object selected') + '</a></li>'),
|
||||
priority: 1,
|
||||
objectMenu && MainMenuFactory.refreshMainMenuItems(objectMenu, [
|
||||
MainMenuFactory.createMenuItem({
|
||||
name: '',
|
||||
label: gettext('No object selected'),
|
||||
category: 'create',
|
||||
update: function() {/*This is intentional (SonarQube)*/},
|
||||
}], false);
|
||||
$obj_mnu.append(create_submenu.$el);
|
||||
priority: 1,
|
||||
enable: false,
|
||||
})
|
||||
]);
|
||||
}
|
||||
},
|
||||
init: function() {
|
||||
@@ -611,20 +570,10 @@ define('pgadmin.browser', [
|
||||
autoHide: false,
|
||||
build: function(element) {
|
||||
let item = obj.tree.itemFrom(element),
|
||||
d = obj.tree.itemData(item),
|
||||
menus = obj.menus['context'][d._type],
|
||||
$div = $('<div></div>'),
|
||||
context_menu = {};
|
||||
|
||||
if(item) obj.tree.select(item);
|
||||
let {name: browser} = getBrowser();
|
||||
if(browser == 'Nwjs'){
|
||||
context_menu = obj.native_context_menus;
|
||||
} else {
|
||||
pgAdmin.Browser.MenuCreator(
|
||||
obj.Nodes, $div, menus, obj.menu_categories, d, item, context_menu
|
||||
);
|
||||
}
|
||||
context_menu = obj.BrowserContextMenu;
|
||||
|
||||
return {
|
||||
autoHide: false,
|
||||
@@ -641,7 +590,6 @@ define('pgadmin.browser', [
|
||||
|
||||
// Register scripts and add menus
|
||||
pgBrowser.utils.registerScripts(this);
|
||||
pgBrowser.utils.addMenus(obj);
|
||||
|
||||
let headers = {};
|
||||
headers[pgAdmin.csrf_token_header] = pgAdmin.csrf_token;
|
||||
@@ -780,16 +728,11 @@ define('pgadmin.browser', [
|
||||
// Add menus of module/extension at appropriate menu
|
||||
add_menus: function(menus) {
|
||||
let self = this,
|
||||
pgMenu = this.menus,
|
||||
MenuItem = pgAdmin.Browser.MenuItem;
|
||||
let {name: browser} = getBrowser();
|
||||
// MenuItem = pgAdmin.Browser.MenuItem;
|
||||
pgMenu = this.all_menus_cache;
|
||||
|
||||
_.each(menus, function(m) {
|
||||
_.each(m.applies, function(a) {
|
||||
/* We do support menu type only from this list */
|
||||
// if ($.inArray(a, [
|
||||
// 'context', 'file', 'edit', 'object',
|
||||
// 'management', 'tools', 'help']) >= 0) {
|
||||
if(['context', 'file', 'edit', 'object','management', 'tools', 'help'].indexOf(a) > -1){
|
||||
let _menus;
|
||||
|
||||
@@ -835,42 +778,23 @@ define('pgadmin.browser', [
|
||||
};
|
||||
}
|
||||
|
||||
if(browser == 'Nwjs') {
|
||||
return MainMenuItemFactory.create({
|
||||
name: _m.name,
|
||||
label: _m.label,
|
||||
module: _m.module,
|
||||
category: _m.category,
|
||||
callback: typeof _m.module == 'object' && _m.module[_m.callback] && _m.callback in _m.module[_m.callback] ? _m.module[_m.callback] : _m.callback,
|
||||
priority: _m.priority,
|
||||
data: _m.data,
|
||||
url: _m.url || '#',
|
||||
target: _m.target,
|
||||
icon: _m.icon,
|
||||
enable: enable ? enable : true,
|
||||
node: _m.node,
|
||||
checked: _m.checked,
|
||||
below: _m.below,
|
||||
applies: _m.applies,
|
||||
});
|
||||
} else {
|
||||
return new MenuItem({
|
||||
name: _m.name,
|
||||
label: _m.label,
|
||||
module: _m.module,
|
||||
category: _m.category,
|
||||
callback: _m.callback,
|
||||
priority: _m.priority,
|
||||
data: _m.data,
|
||||
url: _m.url || '#',
|
||||
target: _m.target,
|
||||
icon: _m.icon,
|
||||
enable,
|
||||
node: _m.node,
|
||||
checked: _m.checked,
|
||||
below: _m.below,
|
||||
});
|
||||
}
|
||||
return MainMenuFactory.createMenuItem({
|
||||
name: _m.name,
|
||||
label: _m.label,
|
||||
module: _m.module,
|
||||
category: _m.category,
|
||||
callback: typeof _m.module == 'object' && _m.module[_m.callback] && _m.callback in _m.module[_m.callback] ? _m.module[_m.callback] : _m.callback,
|
||||
priority: _m.priority,
|
||||
data: _m.data,
|
||||
url: _m.url || '#',
|
||||
target: _m.target,
|
||||
icon: _m.icon,
|
||||
enable: enable ? enable : true,
|
||||
node: _m.node,
|
||||
checked: _m.checked,
|
||||
below: _m.below,
|
||||
applies: _m.applies,
|
||||
});
|
||||
};
|
||||
|
||||
if (!_.has(_menus, m.name)) {
|
||||
@@ -895,62 +819,6 @@ define('pgadmin.browser', [
|
||||
});
|
||||
});
|
||||
},
|
||||
// Create the menus
|
||||
create_menus: function () {
|
||||
let { name: browser } = getBrowser();
|
||||
// Add Native menus if NWjs app
|
||||
if (browser == 'Nwjs') {
|
||||
createMainMenus();
|
||||
this.enable_disable_menus();
|
||||
} else {
|
||||
/* Create menus */
|
||||
let navbar = $('#navbar-menu > ul').first();
|
||||
let obj = this;
|
||||
|
||||
_.each([
|
||||
{ menu: 'file', id: '#mnu_file' },
|
||||
{ menu: 'management', id: '#mnu_management' },
|
||||
{ menu: 'tools', id: '#mnu_tools' },
|
||||
{ menu: 'help', id: '#mnu_help' }],
|
||||
function (o) {
|
||||
let $mnu = navbar.children(o.id).first(),
|
||||
$dropdown = $mnu.children('.dropdown-menu').first();
|
||||
$dropdown.empty();
|
||||
if (o.menu == 'help') {
|
||||
$dropdown.append('<div id="quick-search-component"></div>');
|
||||
$dropdown.append('<div class="menu-groups"><span class="fa fa-list" style="font-weight:900 !important;"></span> ' + gettext('SUGGESTED SITES') + '</div>');
|
||||
}
|
||||
|
||||
if (pgAdmin.Browser.MenuCreator(
|
||||
obj.Nodes, $dropdown, obj.menus[o.menu], obj.menu_categories
|
||||
)) {
|
||||
$mnu.removeClass('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
navbar.children('#mnu_obj').removeClass('d-none');
|
||||
obj.enable_disable_menus();
|
||||
}
|
||||
},
|
||||
// General function to handle callbacks for object or dialog help.
|
||||
showHelp: function(type, url, node, item) {
|
||||
if (type == 'object_help') {
|
||||
// Construct the URL
|
||||
let server = pgBrowser.tree.getTreeNodeHierarchy(item).server;
|
||||
let baseUrl = pgBrowser.utils.pg_help_path;
|
||||
let fullUrl = help.getHelpUrl(baseUrl, url, server.version);
|
||||
|
||||
window.open(fullUrl, 'postgres_help');
|
||||
} else if(type == 'dialog_help') {
|
||||
if (pgWindow && pgWindow.default) {
|
||||
pgWindow.default.open(url, 'pgadmin_help');
|
||||
}
|
||||
else {
|
||||
window.open(url, 'pgadmin_help');
|
||||
}
|
||||
}
|
||||
$('#live-search-field').focus();
|
||||
},
|
||||
_findTreeChildNode: function(_i, _d, _o) {
|
||||
let loaded = _o.t.wasLoad(_i),
|
||||
onLoad = function() {
|
||||
@@ -2224,27 +2092,6 @@ define('pgadmin.browser', [
|
||||
brace_matching: pgBrowser.utils.braceMatching,
|
||||
indent_with_tabs: pgBrowser.utils.is_indent_with_tabs,
|
||||
},
|
||||
|
||||
// This function will return the name and version of the browser.
|
||||
get_browser: function() {
|
||||
let ua=navigator.userAgent,tem,M=ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
||||
if(/trident/i.test(M[1])) {
|
||||
tem=/\brv[ :]+(\d+)/g.exec(ua) || [];
|
||||
return {name:'IE', version:(tem[1]||'')};
|
||||
}
|
||||
|
||||
if(M[1]==='Chrome') {
|
||||
tem=ua.match(/\bOPR|Edge\/(\d+)/);
|
||||
if(tem!=null) {return {name:tem[0], version:tem[1]};}
|
||||
}
|
||||
|
||||
M=M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
|
||||
if((tem=ua.match(/version\/(\d+)/i))!=null) {M.splice(1,1,tem[1]);}
|
||||
return {
|
||||
name: M[0],
|
||||
version: M[1],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/* Remove paste event mapping from CodeMirror's emacsy KeyMap binding
|
||||
@@ -2258,9 +2105,6 @@ define('pgadmin.browser', [
|
||||
if (pgBrowser.utils.useSpaces == 'True') {
|
||||
pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
|
||||
}
|
||||
setTimeout(function(){
|
||||
$('#mnu_about').closest('li').before('<li class="dropdown-divider"></li>');
|
||||
}, 100);
|
||||
|
||||
return pgAdmin.Browser;
|
||||
});
|
||||
|
||||
@@ -113,8 +113,8 @@ _.extend(pgBrowser, {
|
||||
|
||||
lock_layout: function(docker, op) {
|
||||
let menu_items = [];
|
||||
if('mnu_locklayout' in this.menus['file']) {
|
||||
menu_items = this.menus['file']['mnu_locklayout']['menu_items'];
|
||||
if('mnu_locklayout' in this.all_menus_cache['file']) {
|
||||
menu_items = this.all_menus_cache['file']['mnu_locklayout']['menu_items'];
|
||||
}
|
||||
|
||||
switch(op) {
|
||||
|
||||
@@ -1,521 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import _ from 'lodash';
|
||||
import {MenuItem as NewMenuItem} from './new_menu';
|
||||
|
||||
define([
|
||||
'sources/pgadmin', 'jquery', 'sources/utils', 'sources/gettext',
|
||||
], function(pgAdmin, $, pgadminUtils, gettext) {
|
||||
'use strict';
|
||||
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
|
||||
// Individual menu-item class
|
||||
let MenuItem = pgAdmin.Browser.MenuItem = function(opts) {
|
||||
let menu_opts = [
|
||||
'name', 'label', 'priority', 'module', 'callback', 'data', 'enable',
|
||||
'category', 'target', 'url' /* Do not show icon in the menus, 'icon' */ , 'node',
|
||||
'checked', 'below', 'menu_items',
|
||||
],
|
||||
defaults = {
|
||||
url: '#',
|
||||
target: '_self',
|
||||
enable: true,
|
||||
};
|
||||
_.extend(this, defaults, _.pick(opts, menu_opts));
|
||||
};
|
||||
|
||||
_.extend(pgAdmin.Browser.MenuItem.prototype, {
|
||||
/*
|
||||
* Keeps track of the jQuery object representing this menu-item. This will
|
||||
* be used by the update function to enable/disable the individual item.
|
||||
*/
|
||||
$el: null,
|
||||
/*
|
||||
* Generate the UI for this menu-item. enable/disable, based on the
|
||||
* currently selected object.
|
||||
*/
|
||||
generate: function(node, item) {
|
||||
this.create_el(node, item);
|
||||
|
||||
this.context = {
|
||||
name: this.label,
|
||||
/* icon: this.icon || this.module && (this.module.type), */
|
||||
disabled: this.is_disabled,
|
||||
callback: this.context_menu_callback.bind(this, item),
|
||||
};
|
||||
|
||||
return this.$el;
|
||||
},
|
||||
|
||||
/*
|
||||
* Create the jquery element for the menu-item.
|
||||
*/
|
||||
create_el: function(node, item) {
|
||||
|
||||
if(this.menu_items) {
|
||||
_.each(this.menu_items, function(submenu_item){
|
||||
submenu_item.generate(node, item);
|
||||
});
|
||||
let create_submenu = pgAdmin.Browser.MenuGroup({
|
||||
'label': this.label,
|
||||
'id': this.name,
|
||||
}, this.menu_items);
|
||||
this.$el = create_submenu.$el;
|
||||
} else {
|
||||
let data_disabled = null;
|
||||
if(this.data != undefined && this.data.data_disabled != undefined){
|
||||
data_disabled = this.data.data_disabled;
|
||||
}
|
||||
let url = $('<a></a>', {
|
||||
'id': this.name,
|
||||
'href': this.url,
|
||||
'target': this.target,
|
||||
'data-toggle': 'pg-menu',
|
||||
'role': 'menuitem',
|
||||
'data-disabled': data_disabled,
|
||||
}).data('pgMenu', {
|
||||
module: this.module || pgAdmin.Browser,
|
||||
cb: this.callback,
|
||||
data: this.data,
|
||||
}).addClass('dropdown-item');
|
||||
|
||||
this.is_disabled = this.disabled(node, item);
|
||||
if (this.icon) {
|
||||
url.append($('<i></i>', {
|
||||
'class': this.icon,
|
||||
}));
|
||||
} else if(!_.isUndefined(this.checked)) {
|
||||
url.append($('<i></i>', {
|
||||
'class': 'fa fa-check '+ (this.checked?'':'visibility-hidden'),
|
||||
}));
|
||||
}
|
||||
|
||||
url.addClass((this.is_disabled ? ' disabled' : ''));
|
||||
|
||||
let textSpan = $('<span data-test="menu-item-text"></span>').text(' ' + this.label);
|
||||
|
||||
url.append(textSpan);
|
||||
|
||||
let mnu_element = $('<li/>').append(url);
|
||||
// Check if below parameter is defined and true then we need to add
|
||||
// separator.
|
||||
if (!_.isUndefined(this.below) && this.below === true) {
|
||||
mnu_element.append('<li class="dropdown-divider"></li>');
|
||||
}
|
||||
|
||||
this.$el = mnu_element;
|
||||
}
|
||||
|
||||
},
|
||||
/*
|
||||
* Updates the enable/disable state of the menu-item based on the current
|
||||
* selection using the disabled function. This also creates a object
|
||||
* for this menu, which can be used in the context-menu.
|
||||
*/
|
||||
update: function(node, item) {
|
||||
|
||||
if (this.$el && !this.$el.find('.dropdown-item').hasClass('disabled')) {
|
||||
this.$el.find('.dropdown-item').addClass('disabled');
|
||||
}
|
||||
|
||||
this.is_disabled = this.disabled(node, item);
|
||||
if (this.$el && !this.is_disabled) {
|
||||
this.$el.find('.dropdown-item').removeClass('disabled');
|
||||
}
|
||||
|
||||
this.context = {
|
||||
name: this.label,
|
||||
/* icon: this.icon || (this.module && this.module.type), */
|
||||
disabled: this.is_disabled,
|
||||
callback: this.context_menu_callback.bind(this, item),
|
||||
};
|
||||
},
|
||||
|
||||
/*
|
||||
* This will be called when context-menu is clicked.
|
||||
*/
|
||||
context_menu_callback: function(item) {
|
||||
let o = this,
|
||||
cb;
|
||||
|
||||
if (o.module['callbacks'] && (
|
||||
o.callback in o.module['callbacks']
|
||||
)) {
|
||||
cb = o.module['callbacks'][o.callback];
|
||||
} else if (o.callback in o.module) {
|
||||
cb = o.module[o.callback];
|
||||
}
|
||||
if (cb) {
|
||||
cb.apply(o.module, [o.data, item]);
|
||||
} else {
|
||||
pgAdmin.Browser.report_error(
|
||||
pgadminUtils.sprintf('Developer Warning: Callback - "%s" not found!', o.cb)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Checks this menu enable/disable state based on the selection.
|
||||
*/
|
||||
disabled: function(node, item) {
|
||||
if (this.enable == undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.node) {
|
||||
if (!node) {
|
||||
return true;
|
||||
}
|
||||
if (_.isArray(this.node) ? (
|
||||
_.indexOf(this.node, node) == -1
|
||||
) : (this.node != node._type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isBoolean(this.enable)) return !this.enable;
|
||||
if (_.isFunction(this.enable)) return !this.enable.apply(this.module, [node, item, this.data]);
|
||||
if (this.module && _.isBoolean(this.module[this.enable])) return !this.module[this.enable];
|
||||
if (this.module && _.isFunction(this.module[this.enable])) return !(this.module[this.enable]).apply(this.module, [node, item, this.data]);
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/*
|
||||
* Change the checked value and update the checked icon on the menu
|
||||
*/
|
||||
change_checked(isChecked) {
|
||||
if(!_.isUndefined(this.checked)) {
|
||||
this.checked = isChecked;
|
||||
if(this.checked) {
|
||||
this.$el.find('.fa-check').removeClass('visibility-hidden');
|
||||
} else {
|
||||
this.$el.find('.fa-check').addClass('visibility-hidden');
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* This a class for creating a menu group, mainly used by the submenu
|
||||
* creation logic.
|
||||
*
|
||||
* Arguments:
|
||||
* 1. Options to render the submenu DOM Element.
|
||||
* i.e. label, icon, above (separator), below (separator)
|
||||
* 2. List of menu-items comes under this submenu.
|
||||
* 3. Did we rendered separator after the menu-item/submenu?
|
||||
* 4. A unique-id for this menu-item.
|
||||
*
|
||||
* Returns a object, similar to the menu-item, which has his own jQuery
|
||||
* Element, context menu representing object, etc.
|
||||
*
|
||||
*/
|
||||
|
||||
pgAdmin.Browser.MenuGroup = function(opts, items, prev, ctx) {
|
||||
let template = _.template([
|
||||
'<% if (above) { %><li class="dropdown-divider"></li><% } %>',
|
||||
'<li class="dropdown-submenu" role="menuitem">',
|
||||
' <a href="#" class="dropdown-item">',
|
||||
' <% if (icon) { %><i class="<%= icon %>"></i><% } %>',
|
||||
' <span><%= label %></span>',
|
||||
' </a>',
|
||||
' <ul class="dropdown-menu">',
|
||||
' </ul>',
|
||||
'</li>',
|
||||
'<% if (below) { %><li class="dropdown-divider"></li><% } %>',
|
||||
].join('\n')),
|
||||
data = {
|
||||
'label': opts.label,
|
||||
'icon': opts.icon,
|
||||
'above': opts.above && !prev,
|
||||
'below': opts.below,
|
||||
},
|
||||
m,
|
||||
$el = $(template(data)),
|
||||
$menu = $el.find('.dropdown-menu'),
|
||||
submenus = {},
|
||||
ctxId = 1;
|
||||
|
||||
ctx = _.uniqueId(ctx + '_sub_');
|
||||
|
||||
// Sort by alphanumeric ordered first
|
||||
items.sort(function(a, b) {
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
// Sort by priority
|
||||
items.sort(function(a, b) {
|
||||
return a.priority - b.priority;
|
||||
});
|
||||
|
||||
for (let idx in items) {
|
||||
m = items[idx];
|
||||
$menu.append(m.$el);
|
||||
if (!m.is_disabled) {
|
||||
submenus[ctx + ctxId] = m.context;
|
||||
}
|
||||
ctxId++;
|
||||
}
|
||||
|
||||
let is_disabled = (_.size(submenus) == 0);
|
||||
|
||||
return {
|
||||
$el: $el,
|
||||
priority: opts.priority || 10,
|
||||
label: opts.label,
|
||||
above: data['above'],
|
||||
below: opts.below,
|
||||
is_disabled: is_disabled,
|
||||
context: {
|
||||
name: opts.label,
|
||||
icon: opts.icon,
|
||||
items: submenus,
|
||||
disabled: is_disabled,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* A function to generate menus (submenus) based on the categories.
|
||||
* Attach the current selected browser tree node to each of the generated
|
||||
* menu-items.
|
||||
*
|
||||
* Arguments:
|
||||
* 1. nodes_obj - Nodes object contains each node object.
|
||||
* 2. jQuery Element on which you may want to created the menus
|
||||
* 3. list of menu-items
|
||||
* 4. categories - metadata information about the categories, based on which
|
||||
* the submenu (menu-group) will be created (if any).
|
||||
* 5. d - Data object for the selected browser tree item.
|
||||
* 6. item - The selected browser tree item
|
||||
* 7. menu_items - A empty object on which the context menu for the given
|
||||
* list of menu-items.
|
||||
*
|
||||
* Returns if any menu generated for the given input.
|
||||
*/
|
||||
pgAdmin.Browser.MenuCreator = function(
|
||||
nodes_obj, $mnu, menus, categories, d, item, menu_items
|
||||
) {
|
||||
let showMenu = true;
|
||||
|
||||
/* We check showMenu function is defined by the respective node, if it is
|
||||
* defined then call the function which will return true or false.
|
||||
*/
|
||||
if (d && nodes_obj[d._type] && !_.isUndefined(nodes_obj[d._type].showMenu))
|
||||
showMenu = nodes_obj[d._type].showMenu(d, item);
|
||||
|
||||
if (!showMenu) {
|
||||
menu_items = menu_items || {};
|
||||
menu_items[_.uniqueId('ctx_')+ '1_1_ms'] = {
|
||||
disabled : true,
|
||||
name: gettext('No menu available for this object.'),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
let groups = {
|
||||
'common': [],
|
||||
},
|
||||
common, idx = 0,
|
||||
ctxId = _.uniqueId('ctx_'),
|
||||
update_menuitem = function(m) {
|
||||
if (m instanceof MenuItem) {
|
||||
if (m.$el) {
|
||||
m.$el.remove();
|
||||
delete m.$el;
|
||||
}
|
||||
m.generate(d, item);
|
||||
let group = groups[m.category || 'common'] =
|
||||
groups[m.category || 'common'] || [];
|
||||
group.push(m);
|
||||
} else if(m instanceof NewMenuItem) {
|
||||
if (m.$el) {
|
||||
m.$el.remove();
|
||||
delete m.$el;
|
||||
}
|
||||
m.generate(this, self, this.context_menu_callback, item);
|
||||
let group = groups[m.category || 'common'] =
|
||||
groups[m.category || 'common'] || [];
|
||||
group.push(m);
|
||||
}
|
||||
else {
|
||||
for (let key in m) {
|
||||
update_menuitem(m[key]);
|
||||
}
|
||||
}
|
||||
},
|
||||
ctxIdx = 1;
|
||||
|
||||
for (idx in menus) {
|
||||
update_menuitem(menus[idx]);
|
||||
}
|
||||
|
||||
// Not all menu creator requires the context menu structure.
|
||||
menu_items = menu_items || {};
|
||||
|
||||
common = groups['common'];
|
||||
delete groups['common'];
|
||||
|
||||
let prev = true;
|
||||
|
||||
for (let name in groups) {
|
||||
let g = groups[name],
|
||||
c = categories[name] || {
|
||||
'label': name,
|
||||
single: false,
|
||||
},
|
||||
menu_group = pgAdmin.Browser.MenuGroup(c, g, prev, ctxId);
|
||||
|
||||
if (g.length <= 1 && !c.single) {
|
||||
prev = false;
|
||||
for (idx in g) {
|
||||
common.push(g[idx]);
|
||||
}
|
||||
} else {
|
||||
prev = g.below;
|
||||
common.push(menu_group);
|
||||
}
|
||||
}
|
||||
|
||||
// The menus will be created based on the priority given.
|
||||
// Menu with lowest value has the highest priority. If the priority is
|
||||
// same, then - it will be ordered by label.
|
||||
// Sort by alphanumeric ordered first
|
||||
common.sort(function(a, b) {
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
// Sort by priority
|
||||
common.sort(function(a, b) {
|
||||
return a.priority - b.priority;
|
||||
});
|
||||
let len = _.size(common);
|
||||
|
||||
for (idx in common) {
|
||||
item = common[idx];
|
||||
|
||||
item.priority = (item.priority || 10);
|
||||
$mnu.append(item.$el);
|
||||
let prefix = ctxId + '_' + item.priority + '_' + ctxIdx;
|
||||
|
||||
if (ctxIdx != 1 && item.above && !item.is_disabled) {
|
||||
// For creatign the seprator any string will do.
|
||||
menu_items[prefix + '_ma'] = '----';
|
||||
}
|
||||
|
||||
if (!item.is_disabled) {
|
||||
menu_items[prefix + '_ms'] = item.context;
|
||||
}
|
||||
|
||||
if (ctxId != len && item.below && !item.is_disabled) {
|
||||
menu_items[prefix + '_mz'] = '----';
|
||||
}
|
||||
ctxIdx++;
|
||||
}
|
||||
|
||||
return (len > 0);
|
||||
};
|
||||
|
||||
// MENU PUBLIC CLASS DEFINITION
|
||||
// ==============================
|
||||
let Menu = function(element, options) {
|
||||
this.$element = $(element);
|
||||
this.options = $.extend({}, Menu.DEFAULTS, options);
|
||||
this.isLoading = false;
|
||||
};
|
||||
|
||||
Menu.DEFAULTS = {};
|
||||
|
||||
Menu.prototype.toggle = function(ev) {
|
||||
let $parent = this.$element.closest('.dropdown-item');
|
||||
if ($parent.hasClass('disabled')) {
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
let d = this.$element.data('pgMenu');
|
||||
if (d.cb) {
|
||||
let cb = d.module && d.module['callbacks'] && d.module['callbacks'][d.cb] || d.module && d.module[d.cb];
|
||||
cb = cb || d.cb;
|
||||
if (cb) {
|
||||
cb.apply(d.module, [d.data, pgAdmin.Browser.tree.selected()]);
|
||||
ev.preventDefault();
|
||||
} else {
|
||||
pgAdmin.Browser.report_error('Developer Warning: Callback - "' + d.cb + '" not found!');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// BUTTON PLUGIN DEFINITION
|
||||
// ========================
|
||||
|
||||
function Plugin(option, ev) {
|
||||
return this.each(function() {
|
||||
let $this = $(this);
|
||||
let data = $this.data('pg.menu');
|
||||
let options = typeof option == 'object' && option;
|
||||
|
||||
if (!data) $this.data('pg.menu', (data = new Menu(this, options)));
|
||||
|
||||
data.toggle(ev);
|
||||
});
|
||||
}
|
||||
|
||||
let old = $.fn.button;
|
||||
|
||||
$.fn.pgmenu = Plugin;
|
||||
$.fn.pgmenu.Constructor = Menu;
|
||||
|
||||
|
||||
// BUTTON NO CONFLICT
|
||||
// ==================
|
||||
|
||||
$.fn.pgmenu.noConflict = function() {
|
||||
$.fn.pgmenu = old;
|
||||
return this;
|
||||
};
|
||||
|
||||
// MENU DATA-API
|
||||
// =============
|
||||
|
||||
$(document)
|
||||
.on('click.pg.menu.data-api', '[data-toggle^="pg-menu"]', function(ev) {
|
||||
let $menu = $(ev.target);
|
||||
if (!$menu.hasClass('dropdown-item'))
|
||||
$menu = $menu.closest('.dropdown-item');
|
||||
Plugin.call($menu, 'toggle', ev);
|
||||
})
|
||||
.on(
|
||||
'focus.pg.menu.data-api blur.pg.menu.data-api',
|
||||
'[data-toggle^="pg-menu"]',
|
||||
function(ev) {
|
||||
$(ev.target).closest('.menu').toggleClass(
|
||||
'focus', /^focus(in)?$/.test(ev.type)
|
||||
);
|
||||
})
|
||||
.on('mouseenter', '.dropdown-submenu', function(ev) {
|
||||
$(ev.currentTarget).removeClass('dropdown-submenu-visible')
|
||||
.addClass('dropdown-submenu-visible');
|
||||
$(ev.currentTarget).find('.dropdown-menu').first().addClass('show');
|
||||
})
|
||||
.on('mouseleave', '.dropdown-submenu', function(ev) {
|
||||
$(ev.currentTarget).removeClass('dropdown-submenu-visible');
|
||||
$(ev.currentTarget).find('.dropdown-menu').first().removeClass('show');
|
||||
})
|
||||
.on('hidden.bs.dropdown', function(ev) {
|
||||
$(ev.target)
|
||||
.find('.dropdown-submenu.dropdown-submenu-visible')
|
||||
.removeClass('dropdown-submenu-visible')
|
||||
.find('.dropdown-menu.show')
|
||||
.removeClass('show');
|
||||
});
|
||||
|
||||
return pgAdmin.Browser.MenuItem;
|
||||
});
|
||||
@@ -1,228 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import _ from 'lodash';
|
||||
import gettext from 'sources/gettext';
|
||||
|
||||
export default class Menu {
|
||||
constructor(name, label, id, index, addSepratior) {
|
||||
this.label = label;
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.index = index || 1;
|
||||
this.menuItems = [],
|
||||
this.addSepratior = addSepratior || false;
|
||||
}
|
||||
|
||||
static create(name, label, id, index, addSepratior) {
|
||||
let menuObj = new Menu(name, label, id, index, addSepratior);
|
||||
return menuObj;
|
||||
}
|
||||
|
||||
addMenuItem(menuItem, index=null) {
|
||||
if (menuItem instanceof MenuItem) {
|
||||
menuItem.parentMenu = this;
|
||||
if(index) {
|
||||
this.menuItems.splice(index, 0, menuItem);
|
||||
} else {
|
||||
this.menuItems.push(menuItem);
|
||||
Menu.sortMenus(this.menuItems);
|
||||
}
|
||||
} else {
|
||||
throw new Error(gettext('Invalid MenuItem instance'));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
addMenuItems(menuItems) {
|
||||
menuItems.forEach((item) => {
|
||||
if (item instanceof MenuItem) {
|
||||
item.parentMenu = this;
|
||||
this.menuItems.push(item);
|
||||
if(item?.menu_items && item.menu_items.length > 0) {
|
||||
item.menu_items.forEach((i)=> {
|
||||
i.parentMenu = item;
|
||||
});
|
||||
Menu.sortMenus(item.menu_items);
|
||||
}
|
||||
} else {
|
||||
let subItems = Object.values(item);
|
||||
subItems.forEach((subItem)=> {
|
||||
if (subItem instanceof MenuItem) {
|
||||
subItem.parentMenu = this;
|
||||
this.menuItems.push(subItem);
|
||||
} else {
|
||||
throw new Error(gettext('Invalid MenuItem instance'));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by alphanumeric ordered first
|
||||
this.menuItems.sort(function (a, b) {
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
|
||||
// Sort by priority
|
||||
this.menuItems.sort(function (a, b) {
|
||||
return a.priority - b.priority;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
setMenuItems(menuItems) {
|
||||
this.menuItems = menuItems;
|
||||
Menu.sortMenus(this.menuItems);
|
||||
|
||||
this.menuItems.forEach((item)=> {
|
||||
if(item?.menu_items?.length > 0) {
|
||||
Menu.sortMenus(item.menu_items);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static sortMenus(menuItems) {
|
||||
// Sort by alphanumeric ordered first
|
||||
menuItems.sort(function (a, b) {
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
|
||||
// Sort by priority
|
||||
menuItems.sort(function (a, b) {
|
||||
return a.priority - b.priority;
|
||||
});
|
||||
}
|
||||
|
||||
getMenuItems() {
|
||||
return this.menuItems;
|
||||
}
|
||||
|
||||
static getContextMenus(menuList, item, node) {
|
||||
Menu.sortMenus(menuList);
|
||||
|
||||
let ctxMenus = {};
|
||||
let ctxIndex = 1;
|
||||
menuList.forEach(ctx => {
|
||||
let ctx_uid = _.uniqueId('ctx_');
|
||||
let sub_ctx_item = {};
|
||||
ctx.is_disabled = ctx.disabled(node, item);
|
||||
if ('menu_items' in ctx && ctx.menu_items) {
|
||||
Menu.sortMenus(ctx.menu_items);
|
||||
ctx.menu_items.forEach((c) => {
|
||||
c.is_disabled = c.disabled(node, item);
|
||||
if (!c.is_disabled) {
|
||||
sub_ctx_item[ctx_uid + _.uniqueId('_sub_')] = c.getContextItem(c.label, c.is_disabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!ctx.is_disabled) {
|
||||
ctxMenus[ctx_uid + '_' + ctx.priority + '_' + + ctxIndex + '_itm'] = ctx.getContextItem(ctx.label, ctx.is_disabled, sub_ctx_item);
|
||||
|
||||
if (_.size(sub_ctx_item) > 0 && ['register', 'create'].includes(ctx.category)) {
|
||||
ctxMenus[ctx_uid + '_' + ctx.priority + '_' + + ctxIndex + '_sep'] = '----';
|
||||
}
|
||||
}
|
||||
ctxIndex++;
|
||||
});
|
||||
|
||||
return ctxMenus;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class MenuItem {
|
||||
constructor(options, onDisableChange, onChangeChacked) {
|
||||
let menu_opts = [
|
||||
'name', 'label', 'priority', 'module', 'callback', 'data', 'enable',
|
||||
'category', 'target', 'url', 'node',
|
||||
'checked', 'below', 'menu_items', 'is_checkbox', 'action', 'applies', 'is_native_only', 'type'
|
||||
];
|
||||
let defaults = {
|
||||
url: '#',
|
||||
target: '_self',
|
||||
enable: true,
|
||||
type: 'normal'
|
||||
};
|
||||
_.extend(this, defaults, _.pick(options, menu_opts));
|
||||
if (!this.callback) {
|
||||
this.callback = (item) => {
|
||||
if (item.url != '#') {
|
||||
window.open(item.url);
|
||||
}
|
||||
};
|
||||
}
|
||||
this.onDisableChange = onDisableChange;
|
||||
this.changeChecked = onChangeChacked;
|
||||
}
|
||||
|
||||
static create(options) {
|
||||
return MenuItem(options);
|
||||
}
|
||||
|
||||
change_checked(isChecked) {
|
||||
this.checked = isChecked;
|
||||
this.changeChecked?.(this);
|
||||
}
|
||||
|
||||
contextMenuCallback(self) {
|
||||
self.callback();
|
||||
}
|
||||
|
||||
getContextItem(label, is_disabled, sub_ctx_item) {
|
||||
let self = this;
|
||||
return {
|
||||
name: label,
|
||||
disabled: is_disabled,
|
||||
callback: () => { this.contextMenuCallback(self); },
|
||||
...(sub_ctx_item && Object.keys(sub_ctx_item).length > 0) && { items: sub_ctx_item }
|
||||
};
|
||||
}
|
||||
|
||||
setDisabled(disabled) {
|
||||
this.is_disabled = disabled;
|
||||
this.onDisableChange?.(this.parentMenu, this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks this menu enable/disable state based on the selection.
|
||||
*/
|
||||
disabled(node, item) {
|
||||
if (this.enable == undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.node) {
|
||||
if (!node) {
|
||||
return true;
|
||||
}
|
||||
if (_.isArray(this.node) ? (
|
||||
_.indexOf(this.node, node) == -1
|
||||
) : (this.node != node._type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isBoolean(this.enable)) return !this.enable;
|
||||
if (_.isFunction(this.enable)) {
|
||||
return !this.enable.apply(this.module, [node, item, this.data]);
|
||||
}
|
||||
if (this.module && _.isBoolean(this.module[this.enable])) {
|
||||
return !this.module[this.enable];
|
||||
}
|
||||
if (this.module && _.isFunction(this.module[this.enable])) {
|
||||
return !(this.module[this.enable]).apply(this.module, [node, item, this.data]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getContextMenu(menu, item, node) {
|
||||
return Menu.getContextMenus(menu, item, node);
|
||||
}
|
||||
@@ -6,8 +6,8 @@
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import {MenuItem as NewMenuItem} from '../new_menu';
|
||||
import { MainMenus } from '../main_menu';
|
||||
import {MenuItem as NewMenuItem} from '../../../../static/js/helpers/Menu';
|
||||
import { MainMenus } from '../MainMenuFactory';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { getBrowser } from '../../../../static/js/utils';
|
||||
|
||||
@@ -24,7 +24,7 @@ export function menuSearch(param, props) {
|
||||
|
||||
const iterItem = (subMenus, path, parentPath) => {
|
||||
subMenus.forEach((subMenu) =>{
|
||||
if(subMenu instanceof NewMenuItem || subMenu instanceof pgAdmin.Browser.MenuItem) {
|
||||
if(subMenu instanceof NewMenuItem) {
|
||||
if(subMenu.type != 'separator' && subMenu?.label?.toLowerCase().indexOf(param.toLowerCase()) != -1){
|
||||
let localPath = path;
|
||||
if(parentPath) {
|
||||
@@ -40,11 +40,11 @@ export function menuSearch(param, props) {
|
||||
result.push(subMenu);
|
||||
}
|
||||
}
|
||||
if(subMenu.menu_items) {
|
||||
iterItem(subMenu.menu_items, getMenuName(subMenu), path);
|
||||
if(subMenu.getMenuItems()) {
|
||||
iterItem(subMenu.getMenuItems(), getMenuName(subMenu), path);
|
||||
}
|
||||
} else {
|
||||
if(typeof(subMenu) == 'object' && !(subMenu instanceof NewMenuItem || subMenu instanceof pgAdmin.Browser.MenuItem)) {
|
||||
if(typeof(subMenu) == 'object' && !(subMenu instanceof NewMenuItem)) {
|
||||
iterItem(Object.values(subMenu), path, parentPath);
|
||||
} else {
|
||||
iterItem(subMenu, path, parentPath);
|
||||
@@ -67,10 +67,10 @@ export function menuSearch(param, props) {
|
||||
if(menu.name == 'object') {
|
||||
let selectedNode = pgAdmin.Browser.tree.selected();
|
||||
if(selectedNode) {
|
||||
subMenus = pgAdmin.Browser.menus[menu.name][selectedNode._metadata.data._type];
|
||||
subMenus = pgAdmin.Browser.all_menus_cache[menu.name][selectedNode._metadata.data._type];
|
||||
}
|
||||
} else {
|
||||
subMenus = pgAdmin.Browser.menus[menu.name];
|
||||
subMenus = pgAdmin.Browser.all_menus_cache[menu.name];
|
||||
}
|
||||
iterItem(Object.values(subMenus), getMenuName(menu));
|
||||
});
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% if config.SERVER_MODE and config.SHOW_GRAVATAR_IMAGE -%}
|
||||
{% import 'browser/macros/gravatar_icon.macro' as IMG with context %}
|
||||
{% elif config.SERVER_MODE %}
|
||||
{% import 'browser/macros/static_user_icon.macro' as IMG with context %}
|
||||
{% endif %}
|
||||
|
||||
{% block title %}{{ config.APP_NAME }}{% endblock %}
|
||||
|
||||
{% block init_script %}
|
||||
@@ -74,17 +68,6 @@ require.onResourceLoad = function (context, map, depMaps) {
|
||||
}
|
||||
};
|
||||
|
||||
{% if config.SERVER_MODE %}
|
||||
window.onload = function(e){
|
||||
setTimeout(function() {
|
||||
var gravatarImg = {{ IMG.PREPARE_HTML()|safe }}
|
||||
var navbarUser = document.getElementById("navbar-user");
|
||||
if (navbarUser) {
|
||||
navbarUser.innerHTML = gravatarImg;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block css_link %}
|
||||
@@ -99,80 +82,7 @@ window.onload = function(e){
|
||||
</div>
|
||||
</div>
|
||||
{% if current_app.PGADMIN_RUNTIME | string() == 'False' %}
|
||||
<nav class="navbar fixed-top navbar-expand-lg navbar-dark pg-navbar">
|
||||
<a class="navbar-brand pgadmin_header_logo" onClick="return false;" href="{{ '#' }}"
|
||||
title="{{ config.APP_NAME }} {{ _('logo') }}" aria-label="{ config.APP_NAME }} {{ _('logo') }}">
|
||||
<i class="app-icon {{ config.APP_ICON }}" aria-hidden="true"></i>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#navbar-menu" aria-controls="navbar-menu">
|
||||
<span class="sr-only">{{ _('Toggle navigation') }}</span>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbar-menu" role="navigation">
|
||||
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li id="mnu_file" class="nav-item active dropdown d-none">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
|
||||
_('File') }} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu"></ul>
|
||||
</li>
|
||||
<li id="mnu_obj" class="nav-item active dropdown ">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
|
||||
_('Object') }} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu"></ul>
|
||||
</li>
|
||||
<li id="mnu_management" class="nav-item active dropdown d-none">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
|
||||
_('Management') }} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" aria-hidden="true"></ul>
|
||||
</li>
|
||||
<li id="mnu_tools" class="nav-item active dropdown d-none">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
|
||||
_('Tools') }} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu"></ul>
|
||||
</li>
|
||||
<li id="mnu_help" class="nav-item active dropdown d-none">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
|
||||
_('Help') }} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu help_menu" role="menu"></ul>
|
||||
</li>
|
||||
</ul>
|
||||
{% if config.SERVER_MODE %}
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item active dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown"
|
||||
role="button" aria-expanded="false" id="navbar-user"></a>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
{% if auth_only_internal %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" role="menuitem" onclick="pgAdmin.UserManagement.change_password(
|
||||
'{{ url_for('browser.change_password') }}'
|
||||
)">
|
||||
{{ _('Change Password') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown-divider"></li>
|
||||
{% endif %}
|
||||
{% if mfa_enabled is defined and mfa_enabled is true %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" role="menuitem" onclick="pgAdmin.UserManagement.show_mfa(
|
||||
'{{ login_url("mfa.register", next_url="internal") }}'
|
||||
)">{{ _('Two-Factor Authentication') }}</a>
|
||||
</li>
|
||||
<li class="dropdown-divider"></li>
|
||||
{% endif %}
|
||||
{% if is_admin %}
|
||||
<li><a class="dropdown-item" href="#" role="menuitem" onclick="pgAdmin.UserManagement.show_users()">{{ _('Users') }}</a></li>
|
||||
<li class="dropdown-divider"></li>
|
||||
{% endif %}
|
||||
<li><a class="dropdown-item" role="menuitem" href="{{ logout_url }}">{{ _('Logout') }}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="main-menu-container"></div>
|
||||
<div id="dockerContainer" class="pg-docker"></div>
|
||||
{% else %}
|
||||
<div id="dockerContainer" class="pg-docker pg-docker-native"></div>
|
||||
|
||||
@@ -95,26 +95,74 @@ define('pgadmin.browser.utils',
|
||||
// after they all were loaded completely.
|
||||
},
|
||||
|
||||
addMenus: function (obj) {
|
||||
addBackendMenus: function (obj) {
|
||||
// Generate the menu items only when all the initial scripts
|
||||
// were loaded completely.
|
||||
//
|
||||
// First - register the menus from the other
|
||||
// modules/extensions.
|
||||
let self = this;
|
||||
if (this.counter.total == this.counter.loaded) {
|
||||
{% for key in ('File', 'Edit', 'Object' 'Tools', 'Management', 'Help') %}
|
||||
obj.add_menus({{ MENU_ITEMS(key, current_app.menu_items['%s_items' % key.lower()])}});
|
||||
{% endfor %}
|
||||
if('{{current_app.PGADMIN_RUNTIME}}' == 'False') {
|
||||
obj.create_menus();
|
||||
}
|
||||
} else {
|
||||
//recall after some time
|
||||
setTimeout(function(){ self.addMenus(obj); }, 3000);
|
||||
}
|
||||
{% for key in ('File', 'Edit', 'Object' 'Tools', 'Management', 'Help') %}
|
||||
obj.add_menus({{ MENU_ITEMS(key, current_app.menu_items['%s_items' % key.lower()])}});
|
||||
{% endfor %}
|
||||
},
|
||||
|
||||
{% if current_app.config.get('SERVER_MODE') %}
|
||||
userMenuInfo: {
|
||||
username: '{{username}}',
|
||||
auth_source: '{{auth_source}}',
|
||||
gravatar: {% if config.SHOW_GRAVATAR_IMAGE %}'{{ username | gravatar }}'{% else %}''{% endif %},
|
||||
menus: [
|
||||
{% if auth_only_internal %}
|
||||
{
|
||||
label: '{{ _('Change Password') }}',
|
||||
type: 'normal',
|
||||
callback: ()=>{
|
||||
pgAdmin.UserManagement.change_password(
|
||||
'{{ url_for('browser.change_password') }}'
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{% endif %}
|
||||
{% if mfa_enabled is defined and mfa_enabled is true %}
|
||||
{
|
||||
label: '{{ _('Two-Factor Authentication') }}',
|
||||
type: 'normal',
|
||||
callback: ()=>{
|
||||
pgAdmin.UserManagement.show_mfa(
|
||||
'{{ login_url("mfa.register", next_url="internal") }}'
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{% endif %}
|
||||
{% if is_admin %}
|
||||
{
|
||||
label: '{{ _('Users') }}',
|
||||
type: 'normal',
|
||||
callback: ()=>{
|
||||
pgAdmin.UserManagement.show_users()
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{% endif %}
|
||||
{
|
||||
label: '{{ _('Logout') }}',
|
||||
type: 'normal',
|
||||
callback: ()=>{
|
||||
window.location="{{ logout_url }}";
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{% endif %}
|
||||
|
||||
// load the module right now
|
||||
load_module: function(name, path, c) {
|
||||
let obj = this;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{##########################################################################
|
||||
We wrote separate macro because if user choose to disable Gravatar then
|
||||
we will not associate our application with Gravatar module which will make
|
||||
'gravatar' filter unavailable in Jinja templates
|
||||
###########################################################################}
|
||||
{% macro PREPARE_HTML() -%}
|
||||
'<img src = "{{ username | gravatar }}" width = "18" height = "18" alt = "Gravatar image for {{ username }}" > {{ username }} ({{auth_source}}) <span class="caret"></span>';
|
||||
{%- endmacro %}
|
||||
@@ -1,3 +0,0 @@
|
||||
{% macro PREPARE_HTML() -%}
|
||||
'<i class="fa fa-user-circle pg-login-icon" aria-hidden="true"></i> {{ username }} <span class="caret"></span>';
|
||||
{%- endmacro %}
|
||||
Reference in New Issue
Block a user