Rewrite pgAdmin main menu bar to use React. #5615
parent
d5e6786bc7
commit
ff9daec5ec
|
@ -389,7 +389,7 @@ function addCommonMenus(menu) {
|
|||
|
||||
let _menuItem = new gui.MenuItem({
|
||||
label: menuItem.label,
|
||||
enabled: !menuItem.is_disabled,
|
||||
enabled: !menuItem.isDisabled,
|
||||
type: menuItem.type || 'normal',
|
||||
priority: menuItem.priority,
|
||||
...(submenu.items.length > 0) && {
|
||||
|
@ -435,7 +435,7 @@ function getSubMenu(menuItem) {
|
|||
let menuType = typeof item.checked == 'boolean' ? 'checkbox' : item.type;
|
||||
submenu.append(new gui.MenuItem({
|
||||
label: item.label,
|
||||
enabled: !item.is_disabled,
|
||||
enabled: !item.isDisabled,
|
||||
priority: item.priority,
|
||||
type: menuType,
|
||||
checked: item.checked,
|
||||
|
@ -472,7 +472,7 @@ function addMacMenu(menu) {
|
|||
new gui.MenuItem({
|
||||
label: menuItem.label,
|
||||
type: menuItem.type || 'normal',
|
||||
enabled: !menuItem.is_disabled,
|
||||
enabled: !menuItem.isDisabled,
|
||||
priority: menuItem.priority,
|
||||
...(submenu.items.length > 0) && {
|
||||
submenu: submenu,
|
||||
|
@ -516,7 +516,7 @@ function enableDisableMenuItem(menu, menuItem) {
|
|||
if (el?.label == menu?.label) {
|
||||
el.submenu.items.forEach((sub) => {
|
||||
if (sub.label == menuItem.label) {
|
||||
sub.enabled = !menuItem.is_disabled;
|
||||
sub.enabled = !menuItem.isDisabled;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -560,7 +560,7 @@ function refreshMenuItems(menu) {
|
|||
item.menu_items.forEach((subItem) => {
|
||||
submenu.append(new gui.MenuItem({
|
||||
label: subItem.label,
|
||||
enabled: !subItem.is_disabled,
|
||||
enabled: !subItem.isDisabled,
|
||||
priority: subItem.priority,
|
||||
type: [true, false].includes(subItem.checked) ? 'checkbox' : 'normal',
|
||||
checked: subItem.checked,
|
||||
|
@ -572,8 +572,9 @@ function refreshMenuItems(menu) {
|
|||
}
|
||||
let _menuItem = new gui.MenuItem({
|
||||
label: item.label,
|
||||
enabled: !item.is_disabled,
|
||||
enabled: !item.isDisabled,
|
||||
priority: item.priority,
|
||||
type: item.type,
|
||||
...(submenu.items.length > 0) && {
|
||||
submenu: submenu,
|
||||
},
|
||||
|
@ -583,10 +584,6 @@ function refreshMenuItems(menu) {
|
|||
});
|
||||
|
||||
el.submenu.append(_menuItem);
|
||||
if (['create', 'register'].includes(item.category)) {
|
||||
let separator_menu = new gui.MenuItem({ type: 'separator' });
|
||||
el.submenu.append(separator_menu);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
});
|
|
@ -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 %}
|
|
@ -7,6 +7,12 @@
|
|||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import MainMenuFactory from '../../browser/static/js/MainMenuFactory';
|
||||
import AppMenuBar from '../js/AppMenuBar';
|
||||
import Theme from '../js/Theme';
|
||||
|
||||
define('app', [
|
||||
'sources/pgadmin', 'bundled_browser',
|
||||
], function(pgAdmin) {
|
||||
|
@ -38,6 +44,14 @@ define('app', [
|
|||
initializeModules(pgAdmin.Browser);
|
||||
initializeModules(pgAdmin.Tools);
|
||||
|
||||
// create menus after all modules are initialized.
|
||||
pgAdmin.Browser.create_menus();
|
||||
// Add menus from back end.
|
||||
pgAdmin.Browser.utils.addBackendMenus(pgAdmin.Browser);
|
||||
|
||||
// Create menus after all modules are initialized.
|
||||
MainMenuFactory.createMainMenus();
|
||||
|
||||
const menuContainerEle = document.querySelector('#main-menu-container');
|
||||
if(menuContainerEle) {
|
||||
ReactDOM.render(<Theme><AppMenuBar /></Theme>, document.querySelector('#main-menu-container'));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
import { Box, makeStyles } from '@material-ui/core';
|
||||
import React, { useState } from 'react';
|
||||
import { PrimaryButton } from './components/Buttons';
|
||||
import { PgMenu, PgMenuDivider, PgMenuItem, PgSubMenu } from './components/Menu';
|
||||
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||
import AccountCircleRoundedIcon from '@material-ui/icons/AccountCircleRounded';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const useStyles = makeStyles((theme)=>({
|
||||
root: {
|
||||
height: '32px',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastText,
|
||||
padding: '0 0.5rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
logo: {
|
||||
width: '96px',
|
||||
height: '100%',
|
||||
/*
|
||||
* Using the SVG postgresql logo, modified to set the background color as #FFF
|
||||
* https://wiki.postgresql.org/images/9/90/PostgreSQL_logo.1color_blue.svg
|
||||
* background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 42 42' style='enable-background:new 0 0 42 42;' xml:space='preserve'%3E%3Cstyle type='text/css'%3E .st0%7Bstroke:%23000000;stroke-width:3.3022;%7D .st1%7Bfill:%23336791;%7D .st2%7Bfill:none;stroke:%23FFFFFF;stroke-width:1.1007;stroke-linecap:round;stroke-linejoin:round;%7D .st3%7Bfill:none;stroke:%23FFFFFF;stroke-width:1.1007;stroke-linecap:round;stroke-linejoin:bevel;%7D .st4%7Bfill:%23FFFFFF;stroke:%23FFFFFF;stroke-width:0.3669;%7D .st5%7Bfill:%23FFFFFF;stroke:%23FFFFFF;stroke-width:0.1835;%7D .st6%7Bfill:none;stroke:%23FFFFFF;stroke-width:0.2649;stroke-linecap:round;stroke-linejoin:round;%7D%0A%3C/style%3E%3Cg id='orginal'%3E%3C/g%3E%3Cg id='Layer_x0020_3'%3E%3Cpath class='st0' d='M31.3,30c0.3-2.1,0.2-2.4,1.7-2.1l0.4,0c1.2,0.1,2.8-0.2,3.7-0.6c2-0.9,3.1-2.4,1.2-2 c-4.4,0.9-4.7-0.6-4.7-0.6c4.7-7,6.7-15.8,5-18c-4.6-5.9-12.6-3.1-12.7-3l0,0c-0.9-0.2-1.9-0.3-3-0.3c-2,0-3.5,0.5-4.7,1.4 c0,0-14.3-5.9-13.6,7.4c0.1,2.8,4,21.3,8.7,15.7c1.7-2,3.3-3.8,3.3-3.8c0.8,0.5,1.8,0.8,2.8,0.7l0.1-0.1c0,0.3,0,0.5,0,0.8 c-1.2,1.3-0.8,1.6-3.2,2.1c-2.4,0.5-1,1.4-0.1,1.6c1.1,0.3,3.7,0.7,5.5-1.8l-0.1,0.3c0.5,0.4,0.4,2.7,0.5,4.4 c0.1,1.7,0.2,3.2,0.5,4.1c0.3,0.9,0.7,3.3,3.9,2.6C29.1,38.3,31.1,37.5,31.3,30'/%3E%3Cpath class='st1' d='M38.3,25.3c-4.4,0.9-4.7-0.6-4.7-0.6c4.7-7,6.7-15.8,5-18c-4.6-5.9-12.6-3.1-12.7-3l0,0 c-0.9-0.2-1.9-0.3-3-0.3c-2,0-3.5,0.5-4.7,1.4c0,0-14.3-5.9-13.6,7.4c0.1,2.8,4,21.3,8.7,15.7c1.7-2,3.3-3.8,3.3-3.8 c0.8,0.5,1.8,0.8,2.8,0.7l0.1-0.1c0,0.3,0,0.5,0,0.8c-1.2,1.3-0.8,1.6-3.2,2.1c-2.4,0.5-1,1.4-0.1,1.6c1.1,0.3,3.7,0.7,5.5-1.8 l-0.1,0.3c0.5,0.4,0.8,2.4,0.7,4.3c-0.1,1.9-0.1,3.2,0.3,4.2c0.4,1,0.7,3.3,3.9,2.6c2.6-0.6,4-2,4.2-4.5c0.1-1.7,0.4-1.5,0.5-3 l0.2-0.7c0.3-2.3,0-3.1,1.7-2.8l0.4,0c1.2,0.1,2.8-0.2,3.7-0.6C39,26.4,40.2,24.9,38.3,25.3L38.3,25.3z'/%3E%3Cpath class='st2' d='M21.8,26.6c-0.1,4.4,0,8.8,0.5,9.8c0.4,1.1,1.3,3.2,4.5,2.5c2.6-0.6,3.6-1.7,4-4.1c0.3-1.8,0.9-6.7,1-7.7'/%3E%3Cpath class='st2' d='M18,4.7c0,0-14.3-5.8-13.6,7.4c0.1,2.8,4,21.3,8.7,15.7c1.7-2,3.2-3.7,3.2-3.7'/%3E%3Cpath class='st2' d='M25.7,3.6c-0.5,0.2,7.9-3.1,12.7,3c1.7,2.2-0.3,11-5,18'/%3E%3Cpath class='st3' d='M33.5,24.6c0,0,0.3,1.5,4.7,0.6c1.9-0.4,0.8,1.1-1.2,2c-1.6,0.8-5.3,0.9-5.3-0.1 C31.6,24.5,33.6,25.3,33.5,24.6c-0.1-0.6-1.1-1.2-1.7-2.7c-0.5-1.3-7.3-11.2,1.9-9.7c0.3-0.1-2.4-8.7-11-8.9 c-8.6-0.1-8.3,10.6-8.3,10.6'/%3E%3Cpath class='st2' d='M19.4,25.6c-1.2,1.3-0.8,1.6-3.2,2.1c-2.4,0.5-1,1.4-0.1,1.6c1.1,0.3,3.7,0.7,5.5-1.8c0.5-0.8,0-2-0.7-2.3 C20.5,25.1,20,24.9,19.4,25.6L19.4,25.6z'/%3E%3Cpath class='st2' d='M19.3,25.5c-0.1-0.8,0.3-1.7,0.7-2.8c0.6-1.6,2-3.3,0.9-8.5c-0.8-3.9-6.5-0.8-6.5-0.3c0,0.5,0.3,2.7-0.1,5.2 c-0.5,3.3,2.1,6,5,5.7'/%3E%3Cpath class='st4' d='M18,13.8c0,0.2,0.3,0.7,0.8,0.7c0.5,0.1,0.9-0.3,0.9-0.5c0-0.2-0.3-0.4-0.8-0.4C18.4,13.6,18,13.7,18,13.8 L18,13.8z'/%3E%3Cpath class='st5' d='M32,13.5c0,0.2-0.3,0.7-0.8,0.7c-0.5,0.1-0.9-0.3-0.9-0.5c0-0.2,0.3-0.4,0.8-0.4C31.6,13.2,32,13.3,32,13.5 L32,13.5z'/%3E%3Cpath class='st2' d='M33.7,12.2c0.1,1.4-0.3,2.4-0.4,3.9c-0.1,2.2,1,4.7-0.6,7.2'/%3E%3Cpath class='st6' d='M2.7,6.6'/%3E%3C/g%3E%3C/svg%3E%0A") 0 0 no-repeat;
|
||||
*/
|
||||
background: 'url(data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDUgNTAiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojZmZmO30uY2xzLTJ7ZmlsbDojMzI2ODkzO308L3N0eWxlPjwvZGVmcz48dGl0bGU+cGdBZG1pbjwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNTguOTQsNDEuNGEyLjQ4LDIuNDgsMCwwLDEtMi4yNy0zLjQ5TDY0LDIxLjI5VjZhNiw2LDAsMCwwLTYtNkg2QTYsNiwwLDAsMCwwLDZWNDRhNiw2LDAsMCwwLDYsNkg1OGE2LDYsMCwwLDAsNi02VjQxLjRaIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMjkuMjUsMzAuMTdhMTMuMTMsMTMuMTMsMCwwLDEtMS44Mi02LjkzLDEzLDEzLDAsMCwxLDEuODItNi44OCwxMi41LDEyLjUsMCwwLDEsMS40OC0xLjk1LDEwLjQ0LDEwLjQ0LDAsMCwwLTMuMjUtMi44OSwxMS4xNiwxMS4xNiwwLDAsMC01LjY1LTEuNDVxLTQuNDgsMC02LjcyLDIuNjRWMTAuNDRINy41MVY0MC4zNmExLDEsMCwwLDAsMSwxaDZhMSwxLDAsMCwwLDEtMVYzMS4xOWE4LjQ3LDguNDcsMCwwLDAsNi4zNCwyLjQsMTEuMjYsMTEuMjYsMCwwLDAsNS42NS0xLjQ1LDEwLjUzLDEwLjUzLDAsMCwwLDIuMDYtMS41NkMyOS40NCwzMC40NCwyOS4zNCwzMC4zMSwyOS4yNSwzMC4xN1pNMjMuNiwyNS44YTQuNTIsNC41MiwwLDAsMS0zLjQ1LDEuNDQsNC40OCw0LjQ4LDAsMCwxLTMuNDQtMS40NCw1LjYsNS42LDAsMCwxLTEuMzUtNCw1LjU5LDUuNTksMCwwLDEsMS4zNS00LDQuNDYsNC40NiwwLDAsMSwzLjQ0LTEuNDUsNC40OSw0LjQ5LDAsMCwxLDMuNDUsMS40NSw1LjYzLDUuNjMsMCwwLDEsMS4zNCw0QTUuNjQsNS42NCwwLDAsMSwyMy42LDI1LjhaIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNTYuNDksMTIuNjNWMzEuMjRxMCw2LjM1LTMuNDQsOS41MXQtOS45MiwzLjE3YTI1LjQyLDI1LjQyLDAsMCwxLTYuMy0uNzUsMTUsMTUsMCwwLDEtNS0yLjIzbDIuODktNS41OWExMC4xNywxMC4xNywwLDAsMCwzLjUxLDEuNzksMTQuMzcsMTQuMzcsMCwwLDAsNC4xOC42NUE2LjUzLDYuNTMsMCwwLDAsNDcsMzYuNGE1LjM3LDUuMzcsMCwwLDAsMS40Ny00LjExdi0uNzZjLTEuNTQsMS44LTMuNzksMi42OS02Ljc2LDIuNjlhMTEuNywxMS43LDAsMCwxLTUuNTktMS4zNkExMC4zNywxMC4zNywwLDAsMSwzMi4wOSwyOWExMC44OSwxMC44OSwwLDAsMS0xLjUxLTUuNzcsMTAuODYsMTAuODYsMCwwLDEsMS41MS01Ljc0LDEwLjQyLDEwLjQyLDAsMCwxLDQuMDctMy44NiwxMS43MSwxMS43MSwwLDAsMSw1LjU5LTEuMzdjMy4yNSwwLDUuNjMsMS4wNiw3LjE0LDMuMTVWMTIuNjNabS05LjMsMTMuOTVhNC40LDQuNCwwLDAsMCwxLjQtMy4zNiw0LjM0LDQuMzQsMCwwLDAtMS4zOC0zLjM0LDUuNjUsNS42NSwwLDAsMC03LjE2LDAsNC4zLDQuMywwLDAsMC0xLjQxLDMuMzQsNC4zNSw0LjM1LDAsMCwwLDEuNDMsMy4zNiw1LjA4LDUuMDgsMCwwLDAsMy41NywxLjNBNSw1LDAsMCwwLDQ3LjE5LDI2LjU4WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTgzLjQzLDMyLjg5SDcxbC0yLDUuMDlhMSwxLDAsMCwxLS45My42Mkg2MS43M2ExLDEsMCwwLDEtLjkxLTEuNEw3Mi45MSw5LjhhMSwxLDAsMCwxLC45Mi0uNmg2Ljg5YTEsMSwwLDAsMSwuOTEuNkw5My43NywzNy4yYTEsMSwwLDAsMS0uOTIsMS40SDg2LjQxYTEsMSwwLDAsMS0uOTMtLjYyWk04MSwyNi43NmwtMy43OC05LjQxLTMuNzgsOS40MVoiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMjAuNDQsOC40NFYzNy42YTEsMSwwLDAsMS0xLDFoLTUuNmExLDEsMCwwLDEtMS0xVjM2LjMzUTExMC42MiwzOSwxMDYuMTYsMzlhMTEuMjksMTEuMjksMCwwLDEtNS42Ny0xLjQ1LDEwLjU0LDEwLjU0LDAsMCwxLTQtNC4xNEExMi42MiwxMi42MiwwLDAsMSw5NSwyNy4xOCwxMi41MywxMi41MywwLDAsMSw5Ni40NCwyMWExMC4zNSwxMC4zNSwwLDAsMSw0LTQuMDksMTEuNDgsMTEuNDgsMCwwLDEsNS42Ny0xLjQzLDguMjQsOC4yNCwwLDAsMSw2LjMsMi4zNVY4LjQ0YTEsMSwwLDAsMSwxLTFoNkExLDEsMCwwLDEsMTIwLjQ0LDguNDRabS05LjE5LDIyLjc1YTUuNzEsNS43MSwwLDAsMCwxLjM0LTQsNS42LDUuNiwwLDAsMC0xLjMyLTMuOTUsNC40Nyw0LjQ3LDAsMCwwLTMuNDMtMS40Myw0LjUzLDQuNTMsMCwwLDAtMy40NCwxLjQzLDUuNTEsNS41MSwwLDAsMC0xLjM0LDMuOTUsNS42Nyw1LjY3LDAsMCwwLDEuMzQsNCw0Ljc3LDQuNzcsMCwwLDAsNi44NSwwWiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTE2MSwxOGMxLjY2LDEuNjgsMi41LDQuMjEsMi41LDcuNnYxMmExLDEsMCwwLDEtMSwxaC02YTEsMSwwLDAsMS0xLTFWMjYuODhhNS42Nyw1LjY3LDAsMCwwLS45LTMuNTMsMy4wOSwzLjA5LDAsMCwwLTIuNTUtMS4xMywzLjYyLDMuNjIsMCwwLDAtMi44OSwxLjI2LDUuNzEsNS43MSwwLDAsMC0xLjEsMy44MlYzNy42YTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYyNi44OGMwLTMuMTEtMS4xNC00LjY2LTMuNDQtNC42NmEzLjcsMy43LDAsMCwwLTIuOTQsMS4yNiw1LjcxLDUuNzEsMCwwLDAtMS4wOSwzLjgyVjM3LjZhMSwxLDAsMCwxLTEsMWgtNmExLDEsMCwwLDEtMS0xVjE2Ljg0YTEsMSwwLDAsMSwxLTFoNS42YTEsMSwwLDAsMSwxLDF2MS4zOWE4LDgsMCwwLDEsMy0yLjA4LDEwLjIzLDEwLjIzLDAsMCwxLDMuOC0uNjksMTAsMTAsMCwwLDEsNC4yOS44OEE3LjI4LDcuMjgsMCwwLDEsMTQ2LjQyLDE5YTguODUsOC44NSwwLDAsMSwzLjQxLTIuNjUsMTAuOTMsMTAuOTMsMCwwLDEsNC40OS0uOTJBOSw5LDAsMCwxLDE2MSwxOFoiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xNjguMTIsMTIuMWEzLjkxLDMuOTEsMCwwLDEtMS4zNC0yLjc5QTQuMTYsNC4xNiwwLDAsMSwxNjgsNi4xOWE1LDUsMCwwLDEsMy42Ny0xLjM2QTUuMjUsNS4yNSwwLDAsMSwxNzUuMTgsNmEzLjc1LDMuNzUsMCwwLDEsMS4zNCwzLDQuMSw0LjEsMCwwLDEtMS4zNCwzLjEzLDUuNjgsNS42OCwwLDAsMS03LjA2LDBabS41NCwzLjc0aDZhMSwxLDAsMCwxLDEsMVYzNy42YTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYxNi44NEExLDEsMCwwLDEsMTY4LjY2LDE1Ljg0WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTIwMS41NSwxOHEyLjU5LDIuNTIsMi41OSw3LjZ2MTJhMSwxLDAsMCwxLTEsMWgtNmExLDEsMCwwLDEtMS0xVjI2Ljg4cTAtNC42Ni0zLjc0LTQuNjZhNC4zLDQuMywwLDAsMC0zLjMsMS4zNCw1LjgzLDUuODMsMCwwLDAtMS4yNCw0djEwYTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYxNi44NGExLDEsMCwwLDEsMS0xaDUuNjFhMSwxLDAsMCwxLDEsMXYxLjQ3YTkuMDUsOS4wNSwwLDAsMSwzLjE5LTIuMTIsMTAuNzgsMTAuNzgsMCwwLDEsNC0uNzNBOS4zNCw5LjM0LDAsMCwxLDIwMS41NSwxOFoiLz48L3N2Zz4=) 0 0 no-repeat',
|
||||
backgroundPositionY: 'center',
|
||||
},
|
||||
menus: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '2px',
|
||||
marginLeft: '16px',
|
||||
|
||||
'& .MuiButton-containedPrimary': {
|
||||
padding: '2px 8px',
|
||||
}
|
||||
},
|
||||
menuButton: {
|
||||
fontSize: '0.925rem',
|
||||
},
|
||||
userMenu: {
|
||||
marginLeft: 'auto',
|
||||
'& .MuiButton-containedPrimary': {
|
||||
fontSize: '0.825rem',
|
||||
}
|
||||
},
|
||||
gravatar: {
|
||||
marginRight: '4px',
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
|
||||
export default function AppMenuBar() {
|
||||
const classes = useStyles();
|
||||
const [,setRefresh] = useState(false);
|
||||
|
||||
const reRenderMenus = ()=>setRefresh((prev)=>!prev);
|
||||
|
||||
useEffect(()=>{
|
||||
pgAdmin.Browser.Events.on('pgadmin:nw-enable-disable-menu-items', ()=>{
|
||||
reRenderMenus();
|
||||
});
|
||||
pgAdmin.Browser.Events.on('pgadmin:nw-refresh-menu-item', ()=>{
|
||||
reRenderMenus();
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getPgMenuItem = (menuItem, i)=>{
|
||||
if(menuItem.type == 'separator') {
|
||||
return <PgMenuDivider key={i}/>;
|
||||
}
|
||||
const hasCheck = typeof menuItem.checked == 'boolean';
|
||||
|
||||
return <PgMenuItem
|
||||
key={i}
|
||||
disabled={menuItem.isDisabled}
|
||||
onClick={()=>{
|
||||
menuItem.callback();
|
||||
if(hasCheck) {
|
||||
reRenderMenus();
|
||||
}
|
||||
}}
|
||||
hasCheck={hasCheck}
|
||||
checked={menuItem.checked}
|
||||
>{menuItem.label}</PgMenuItem>;
|
||||
};
|
||||
|
||||
const userMenuInfo = pgAdmin.Browser.utils.userMenuInfo;
|
||||
|
||||
return(
|
||||
<>
|
||||
<Box className={classes.root}>
|
||||
<div className={classes.logo} />
|
||||
<div className={classes.menus}>
|
||||
{pgAdmin.Browser.MainMenus?.map((menu, i)=>{
|
||||
return (
|
||||
<PgMenu
|
||||
menuButton={<PrimaryButton key={i} className={classes.menuButton} data-label={menu.label}>{menu.label}<KeyboardArrowDownIcon fontSize="small" /></PrimaryButton>}
|
||||
label={menu.label}
|
||||
key={menu.name}
|
||||
>
|
||||
{menu.getMenuItems().map((menuItem, i)=>{
|
||||
const submenus = menuItem.getMenuItems();
|
||||
if(submenus) {
|
||||
return <PgSubMenu key={i} label={menuItem.label}>
|
||||
{submenus.map((submenuItem, si)=>{
|
||||
return getPgMenuItem(submenuItem, si);
|
||||
})}
|
||||
</PgSubMenu>;
|
||||
}
|
||||
return getPgMenuItem(menuItem, i);
|
||||
})}
|
||||
</PgMenu>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{userMenuInfo &&
|
||||
<div className={classes.userMenu}>
|
||||
<PgMenu
|
||||
menuButton={
|
||||
<PrimaryButton className={classes.menuButton} data-test="loggedin-username">
|
||||
<div className={classes.gravatar}>
|
||||
{userMenuInfo.gravatar &&
|
||||
<img src={userMenuInfo.gravatar} width = "18" height = "18"
|
||||
alt = "Gravatar image for {{ username }}" />}
|
||||
{!userMenuInfo.gravatar && <AccountCircleRoundedIcon />}
|
||||
</div>
|
||||
{ userMenuInfo.username } ({userMenuInfo.auth_source})
|
||||
<KeyboardArrowDownIcon fontSize="small" />
|
||||
</PrimaryButton>
|
||||
}
|
||||
label={userMenuInfo.username}
|
||||
align="end"
|
||||
>
|
||||
{userMenuInfo.menus.map((menuItem, i)=>{
|
||||
return getPgMenuItem(menuItem, i);
|
||||
})}
|
||||
</PgMenu>
|
||||
</div>}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -88,6 +88,7 @@ basicSettings = createMuiTheme(basicSettings, {
|
|||
root: {
|
||||
textTransform: 'none',
|
||||
padding: basicSettings.spacing(0.5, 1.5),
|
||||
fontSize: 'inherit',
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.60,
|
||||
},
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
MenuItem,
|
||||
ControlledMenu,
|
||||
applyStatics,
|
||||
Menu,
|
||||
SubMenu,
|
||||
} from '@szhsin/react-menu';
|
||||
export {MenuDivider as PgMenuDivider} from '@szhsin/react-menu';
|
||||
import { shortcutToString } from './ShortcutTitle';
|
||||
|
@ -25,14 +27,14 @@ const useStyles = makeStyles((theme)=>({
|
|||
'& .szh-menu__divider': {
|
||||
margin: 0,
|
||||
background: theme.otherVars.borderColor,
|
||||
}
|
||||
},
|
||||
menuItem: {
|
||||
display: 'flex',
|
||||
padding: '4px 8px',
|
||||
'&.szh-menu__item--active, &.szh-menu__item--hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
'& .szh-menu__item': {
|
||||
display: 'flex',
|
||||
padding: '4px 8px',
|
||||
'&.szh-menu__item--active, &.szh-menu__item--hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastText,
|
||||
}
|
||||
}
|
||||
},
|
||||
checkIcon: {
|
||||
|
@ -48,10 +50,19 @@ const useStyles = makeStyles((theme)=>({
|
|||
}
|
||||
}));
|
||||
|
||||
export function PgMenu({open, className, label, ...props}) {
|
||||
export function PgMenu({open, className, label, menuButton, ...props}) {
|
||||
const classes = useStyles();
|
||||
const state = open ? 'open' : 'closed';
|
||||
props.anchorRef?.current?.setAttribute('data-state', state);
|
||||
|
||||
if(menuButton) {
|
||||
return <Menu
|
||||
{...props}
|
||||
menuButton={menuButton}
|
||||
className={clsx(classes.menu, className)}
|
||||
aria-label={label || 'Menu'}
|
||||
/>;
|
||||
}
|
||||
return (
|
||||
<ControlledMenu
|
||||
state={state}
|
||||
|
@ -68,8 +79,15 @@ PgMenu.propTypes = {
|
|||
className: CustomPropTypes.className,
|
||||
label: PropTypes.string,
|
||||
anchorRef: CustomPropTypes.ref,
|
||||
menuButton: PropTypes.oneOfType([React.ReactNode, undefined]),
|
||||
};
|
||||
|
||||
export const PgSubMenu = applyStatics(SubMenu)(({label, ...props})=>{
|
||||
return (
|
||||
<SubMenu label={label} itemProps={{'data-label': label}} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false, accesskey, shortcut, children, ...props})=>{
|
||||
const classes = useStyles();
|
||||
let onClick = props.onClick;
|
||||
|
@ -80,7 +98,7 @@ export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false
|
|||
};
|
||||
}
|
||||
const dataLabel = typeof(children) == 'string' ? children : undefined;
|
||||
return <MenuItem {...props} onClick={onClick} className={classes.menuItem} data-label={dataLabel} data-checked={checked}>
|
||||
return <MenuItem {...props} onClick={onClick} data-label={dataLabel} data-checked={checked}>
|
||||
{hasCheck && <CheckIcon className={classes.checkIcon} style={checked ? {} : {visibility: 'hidden'}} />}
|
||||
{children}
|
||||
{(shortcut || accesskey) && <div className={classes.shortcut}>({shortcutToString(shortcut, accesskey)})</div>}
|
||||
|
|
|
@ -12,7 +12,7 @@ import gettext from 'sources/gettext';
|
|||
export default class Menu {
|
||||
constructor(name, label, id, index, addSepratior) {
|
||||
this.label = label;
|
||||
this.name = name;
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.index = index || 1;
|
||||
this.menuItems = [],
|
||||
|
@ -102,42 +102,11 @@ export default class Menu {
|
|||
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) {
|
||||
constructor(options, onDisableChange, onChangeChecked) {
|
||||
let menu_opts = [
|
||||
'name', 'label', 'priority', 'module', 'callback', 'data', 'enable',
|
||||
'category', 'target', 'url', 'node',
|
||||
|
@ -158,7 +127,9 @@ export class MenuItem {
|
|||
};
|
||||
}
|
||||
this.onDisableChange = onDisableChange;
|
||||
this.changeChecked = onChangeChacked;
|
||||
this.changeChecked = onChangeChecked;
|
||||
this._isDisabled = true;
|
||||
this.checkAndSetDisabled();
|
||||
}
|
||||
|
||||
static create(options) {
|
||||
|
@ -170,6 +141,10 @@ export class MenuItem {
|
|||
this.changeChecked?.(this);
|
||||
}
|
||||
|
||||
getMenuItems() {
|
||||
return this.menu_items;
|
||||
}
|
||||
|
||||
contextMenuCallback(self) {
|
||||
self.callback();
|
||||
}
|
||||
|
@ -184,11 +159,19 @@ export class MenuItem {
|
|||
};
|
||||
}
|
||||
|
||||
setDisabled(disabled) {
|
||||
this.is_disabled = disabled;
|
||||
checkAndSetDisabled(node, item, forceDisable) {
|
||||
if(!_.isUndefined(forceDisable)) {
|
||||
this._isDisabled = forceDisable;
|
||||
} else {
|
||||
this._isDisabled = this.disabled(node, item);
|
||||
}
|
||||
this.onDisableChange?.(this.parentMenu, this);
|
||||
}
|
||||
|
||||
get isDisabled() {
|
||||
return this._isDisabled;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks this menu enable/disable state based on the selection.
|
||||
*/
|
||||
|
@ -222,7 +205,3 @@ export class MenuItem {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getContextMenu(menu, item, node) {
|
||||
return Menu.getContextMenus(menu, item, node);
|
||||
}
|
|
@ -130,18 +130,6 @@
|
|||
.opacity-5 {
|
||||
opacity: 0.5; }
|
||||
|
||||
.pg-navbar {
|
||||
font-size: $navbar-font-size;
|
||||
background-color: $navbar-bg;
|
||||
padding-left: 0rem;
|
||||
padding-right: 0.5rem;
|
||||
& .nav-item .nav-link{
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.pg-docker {
|
||||
position:absolute;
|
||||
left:0px;
|
||||
|
@ -548,7 +536,7 @@ fieldset.inline-fieldset > div {
|
|||
.pg-panel-statistics-container,
|
||||
.pg-panel-dependencies-container,
|
||||
.pg-panel-dependents-container,
|
||||
.pg-prop-coll-container, {
|
||||
.pg-prop-coll-container {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border-radius: $card-border-radius;
|
||||
|
|
|
@ -160,8 +160,8 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
|
|||
|
||||
def initiate_backup(self):
|
||||
self.page.retry_click(
|
||||
(By.LINK_TEXT,
|
||||
NavMenuLocators.tools_menu_link_text),
|
||||
(By.CSS_SELECTOR,
|
||||
NavMenuLocators.tools_menu_css),
|
||||
(By.CSS_SELECTOR,
|
||||
NavMenuLocators.backup_obj_css))
|
||||
|
||||
|
@ -197,7 +197,7 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
|
|||
|
||||
def initiate_restore(self):
|
||||
tools_menu = self.driver.find_element(
|
||||
By.LINK_TEXT, NavMenuLocators.tools_menu_link_text)
|
||||
By.CSS_SELECTOR, NavMenuLocators.tools_menu_css)
|
||||
tools_menu.click()
|
||||
|
||||
restore_obj = self.page.find_by_css_selector(
|
||||
|
|
|
@ -109,8 +109,8 @@ class PGUtilitiesMaintenanceFeatureTest(BaseFeatureTest):
|
|||
self.server['db_password'],
|
||||
self.database_name)
|
||||
self.page.retry_click(
|
||||
(By.LINK_TEXT,
|
||||
NavMenuLocators.tools_menu_link_text),
|
||||
(By.CSS_SELECTOR,
|
||||
NavMenuLocators.tools_menu_css),
|
||||
(By.CSS_SELECTOR, NavMenuLocators.maintenance_obj_css))
|
||||
maintenance_obj = self.wait.until(EC.visibility_of_element_located(
|
||||
(By.CSS_SELECTOR, NavMenuLocators.maintenance_obj_css)))
|
||||
|
|
|
@ -335,14 +335,16 @@ CREATE TABLE public.nonintpkey
|
|||
return False
|
||||
|
||||
def _view_data_grid(self, table_name):
|
||||
self.page.driver.find_element(By.LINK_TEXT, "Object").click()
|
||||
self.page.driver.find_element(By.CSS_SELECTOR,
|
||||
NavMenuLocators.object_menu_css).click()
|
||||
ActionChains(
|
||||
self.page.driver
|
||||
).move_to_element(
|
||||
self.page.driver.find_element(
|
||||
By.LINK_TEXT, NavMenuLocators.view_data_link_text)
|
||||
By.CSS_SELECTOR, NavMenuLocators.view_data_link_css)
|
||||
).perform()
|
||||
self.page.find_by_partial_link_text("All Rows").click()
|
||||
|
||||
self.page.find_by_css_selector("li[data-label='All Rows']").click()
|
||||
|
||||
# wait until datagrid frame is loaded.
|
||||
self.page.wait_for_query_tool_loading_indicator_to_appear()
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
##########################################################################
|
||||
|
||||
import secrets
|
||||
import time
|
||||
|
||||
from selenium.webdriver import ActionChains
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
|
@ -17,6 +18,7 @@ from regression.feature_utils.tree_area_locators import TreeAreaLocators
|
|||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.common.by import By
|
||||
from regression.feature_utils.locators import NavMenuLocators
|
||||
|
||||
|
||||
class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
|
||||
|
@ -66,13 +68,17 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
|
|||
function_node.click()
|
||||
|
||||
def _debug_function(self):
|
||||
self.page.driver.find_element(By.LINK_TEXT, "Object").click()
|
||||
self.page.driver.find_element(By.CSS_SELECTOR,
|
||||
NavMenuLocators.object_menu_css).click()
|
||||
ActionChains(
|
||||
self.page.driver
|
||||
).move_to_element(
|
||||
self.page.driver.find_element(By.LINK_TEXT, "Debugging")
|
||||
self.page.driver.find_element(
|
||||
By.CSS_SELECTOR, "div[data-label='Debugging']")
|
||||
).perform()
|
||||
self.page.driver.find_element(By.LINK_TEXT, "Debug").click()
|
||||
time.sleep(2)
|
||||
self.page.driver.find_element(
|
||||
By.CSS_SELECTOR, "li[data-label='Debug']").click()
|
||||
|
||||
# We need to check if debugger plugin is installed or not
|
||||
try:
|
||||
|
|
|
@ -72,7 +72,7 @@ class CheckRoleMembershipControlFeatureTest(BaseFeatureTest):
|
|||
|
||||
def _check_role_membership_control(self):
|
||||
self.page.driver.find_element(
|
||||
By.LINK_TEXT, NavMenuLocators.object_menu_link_text).click()
|
||||
By.CSS_SELECTOR, NavMenuLocators.object_menu_css).click()
|
||||
property_object = self.wait.until(EC.visibility_of_element_located(
|
||||
(By.CSS_SELECTOR, NavMenuLocators.properties_obj_css)))
|
||||
property_object.click()
|
||||
|
|
|
@ -30,23 +30,23 @@ class BrowserToolBarLocators():
|
|||
class NavMenuLocators:
|
||||
"This will contains element locators of navigation menu bar"
|
||||
|
||||
file_menu_css = "#mnu_file"
|
||||
file_menu_css = "button[data-label='File']"
|
||||
|
||||
preference_menu_item_css = "#mnu_preferences"
|
||||
preference_menu_item_css = "li[data-label='Preferences']"
|
||||
|
||||
tools_menu_link_text = "Tools"
|
||||
tools_menu_css = "button[data-label='Tools']"
|
||||
|
||||
view_data_link_text = "View/Edit Data"
|
||||
view_data_link_css = "div[data-label='View/Edit Data']"
|
||||
|
||||
object_menu_link_text = "Object"
|
||||
object_menu_css = "button[data-label='Object']"
|
||||
|
||||
properties_obj_css = "#show_obj_properties.dropdown-item:not(.disabled)"
|
||||
properties_obj_css = "li[data-label='Properties...']"
|
||||
|
||||
backup_obj_css = "#backup_object.dropdown-item:not(.disabled)"
|
||||
backup_obj_css = "li[data-label='Backup...']"
|
||||
|
||||
restore_obj_css = "#restore_object.dropdown-item:not(.disabled)"
|
||||
restore_obj_css = "li[data-label='Restore...']"
|
||||
|
||||
maintenance_obj_css = "#maintenance.dropdown-item:not(.disabled)"
|
||||
maintenance_obj_css = "li[data-label='Maintenance...']"
|
||||
|
||||
show_system_objects_pref_label_xpath = \
|
||||
"//label[contains(text(), 'Show system objects?')]"
|
||||
|
|
|
@ -40,8 +40,8 @@ class PgadminPage:
|
|||
# pgAdmin related methods
|
||||
def login_to_app(self, user_detail):
|
||||
self.driver.switch_to.default_content()
|
||||
if not (self.check_if_element_exist_by_xpath(
|
||||
'//a[@id="navbar-user"]', 1)):
|
||||
if not (self.check_if_element_exist_by_css_selector(
|
||||
'button[data-test="loggedin-username"]', 1)):
|
||||
user_edt_box_el = self.driver.find_element(By.NAME, 'email')
|
||||
user_edt_box_el.send_keys(user_detail['login_username'])
|
||||
password_edt_box_el = self.driver.find_element(By.NAME, 'password')
|
||||
|
@ -55,7 +55,8 @@ class PgadminPage:
|
|||
attempt = 0
|
||||
while attempt < 4:
|
||||
try:
|
||||
self.click_element(self.find_by_partial_link_text("File"))
|
||||
self.click_element(self.find_by_css_selector(
|
||||
"button[data-label='File']"))
|
||||
break
|
||||
except (TimeoutException, NoSuchWindowException):
|
||||
self.driver.refresh()
|
||||
|
@ -66,7 +67,8 @@ class PgadminPage:
|
|||
except TimeoutException:
|
||||
attempt = attempt + 1
|
||||
|
||||
self.find_by_partial_link_text("Reset Layout").click()
|
||||
self.click_element(self.find_by_css_selector(
|
||||
"li[data-label='Reset Layout']"))
|
||||
self.click_modal('OK')
|
||||
self.wait_for_reloading_indicator_to_disappear()
|
||||
|
||||
|
@ -136,26 +138,26 @@ class PgadminPage:
|
|||
(By.XPATH, server_tree_xpath)))
|
||||
|
||||
def open_query_tool(self):
|
||||
self.driver.find_element(By.LINK_TEXT, "Tools").click()
|
||||
tools_menu = self.driver.find_element(By.ID, 'mnu_tools')
|
||||
|
||||
query_tool = tools_menu.find_element(By.ID, 'query_tool')
|
||||
|
||||
self.enable_menu_item(query_tool, 10)
|
||||
|
||||
self.find_by_partial_link_text("Query Tool").click()
|
||||
self.click_element(self.find_by_css_selector(
|
||||
"button[data-label='Tools']"))
|
||||
self.click_element(self.find_by_css_selector(
|
||||
"li[data-label='Query Tool']"))
|
||||
|
||||
self.wait_for_element_to_be_visible(
|
||||
self.driver, "//div[@id='btn-conn-status']", 5)
|
||||
|
||||
def open_view_data(self, table_name):
|
||||
self.driver.find_element(By.LINK_TEXT, "Object").click()
|
||||
self.click_element(self.find_by_css_selector(
|
||||
NavMenuLocators.object_menu_css))
|
||||
|
||||
ActionChains(
|
||||
self.driver
|
||||
).move_to_element(
|
||||
self.driver.find_element(By.LINK_TEXT, "View/Edit Data")
|
||||
self.driver.find_element(
|
||||
By.CSS_SELECTOR, NavMenuLocators.view_data_link_css)
|
||||
).perform()
|
||||
self.find_by_partial_link_text("All Rows").click()
|
||||
self.click_element(self.find_by_css_selector(
|
||||
"li[data-label='All Rows']"))
|
||||
time.sleep(1)
|
||||
# wait until datagrid frame is loaded.
|
||||
|
||||
|
@ -327,10 +329,10 @@ class PgadminPage:
|
|||
self.driver.execute_script(
|
||||
self.js_executor_scrollintoview_arg, server_to_remove)
|
||||
self.click_element(server_to_remove)
|
||||
object_menu_item = self.find_by_partial_link_text("Object")
|
||||
self.click_element(object_menu_item)
|
||||
delete_menu_item = self.find_by_partial_link_text("Remove Server")
|
||||
self.click_element(delete_menu_item)
|
||||
self.click_element(self.find_by_css_selector(
|
||||
"button[data-label='Object']"))
|
||||
self.click_element(self.find_by_css_selector(
|
||||
"li[data-label='Remove Server']"))
|
||||
self.driver.switch_to.default_content()
|
||||
self.click_modal('Yes')
|
||||
time.sleep(1)
|
||||
|
@ -969,6 +971,18 @@ class PgadminPage:
|
|||
pass
|
||||
return element_found
|
||||
|
||||
def check_if_element_exist_by_css_selector(self, selector, timeout=5):
|
||||
"""This function will verify if an element exist and on that basis
|
||||
will return True or False. Will handle exception internally"""
|
||||
element_found = False
|
||||
try:
|
||||
WebDriverWait(self.driver, timeout, .01).until(
|
||||
EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))
|
||||
element_found = True
|
||||
except Exception:
|
||||
pass
|
||||
return element_found
|
||||
|
||||
def wait_for_element(self, find_method_with_args):
|
||||
def element_if_it_exists(driver):
|
||||
try:
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('layout related functions test', function() {
|
|||
};
|
||||
|
||||
_.extend(pgBrowser,{
|
||||
'menus': {
|
||||
'all_menus_cache': {
|
||||
'file': {
|
||||
'mnu_locklayout': {
|
||||
'menu_items': [
|
||||
|
@ -42,7 +42,7 @@ describe('layout related functions test', function() {
|
|||
},
|
||||
});
|
||||
|
||||
menu_items = pgBrowser.menus.file.mnu_locklayout.menu_items;
|
||||
menu_items = pgBrowser.all_menus_cache.file.mnu_locklayout.menu_items;
|
||||
});
|
||||
|
||||
describe('for menu actions', function() {
|
||||
|
|
|
@ -131,7 +131,6 @@ let webpackShimConfig = {
|
|||
'pgadmin.browser.layout': path.join(__dirname, './pgadmin/browser/static/js/layout'),
|
||||
'pgadmin.browser.runtime': path.join(__dirname, './pgadmin/browser/static/js/runtime'),
|
||||
'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'),
|
||||
'pgadmin.browser.menu': path.join(__dirname, './pgadmin/browser/static/js/menu'),
|
||||
'pgadmin.browser.activity': path.join(__dirname, './pgadmin/browser/static/js/activity'),
|
||||
'pgadmin.browser.messages': '/browser/js/messages',
|
||||
'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'),
|
||||
|
@ -238,7 +237,7 @@ let webpackShimConfig = {
|
|||
pgLibs: [
|
||||
'pgadmin.browser.error',
|
||||
'pgadmin.browser.collection',
|
||||
'pgadmin.browser.events', 'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin',
|
||||
'pgadmin.browser.events', 'pgadmin.browser.panel', 'pgadmin',
|
||||
'pgadmin.browser.frame', 'pgadmin.browser',
|
||||
'pgadmin.browser.node',
|
||||
'pgadmin.settings', 'pgadmin.preferences', 'pgadmin.sqlfoldcode',
|
||||
|
|
Loading…
Reference in New Issue