From 00d3aaa1fd20f37d9349c056546007cd0079b947 Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Thu, 14 Nov 2024 13:36:42 +0530 Subject: [PATCH] Refactor menu building code to support sub-menus of any depth --- .../databases/schemas/static/js/schema.js | 2 +- .../static/js/compound_trigger.js | 4 +- .../foreign_key/static/js/foreign_key.js | 2 +- .../tables/partitions/static/js/partition.js | 4 +- .../schemas/tables/static/js/table.js | 4 +- .../schemas/views/schema_diff_view_utils.py | 2 +- .../schemas/views/static/js/mview.js | 6 +- .../servers/databases/static/js/database.js | 2 +- .../browser/static/js/MainMenuFactory.js | 219 +++++++++++++----- web/pgadmin/browser/static/js/browser.js | 175 +++----------- web/pgadmin/browser/static/js/node_ajax.js | 5 +- web/pgadmin/static/js/AppMenuBar.jsx | 26 ++- web/pgadmin/static/js/helpers/Menu.js | 42 ++-- .../sqleditor/static/js/SQLEditorModule.js | 5 +- 14 files changed, 261 insertions(+), 237 deletions(-) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js index 7a8a693aa..5e31000c1 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js @@ -64,7 +64,7 @@ define('pgadmin.node.schema', [ },{ name: 'generate_erd', node: 'schema', module: this, applies: ['object', 'context'], callback: 'generate_erd', - category: 'erd', priority: 5, label: gettext('ERD For Schema') + priority: 5, label: gettext('ERD For Schema') }]); }, can_create_schema: function(node) { diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js index 968b553c3..d7996fa85 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js @@ -82,12 +82,12 @@ define('pgadmin.node.compound_trigger', [ },{ name: 'enable_compound_trigger', node: 'compound_trigger', module: this, applies: ['object', 'context'], callback: 'enable_compound_trigger', - category: 'connect', priority: 3, label: gettext('Enable compound trigger'), + priority: 3, label: gettext('Enable compound trigger'), enable : 'canCreate_with_compound_trigger_enable', },{ name: 'disable_compound_trigger', node: 'compound_trigger', module: this, applies: ['object', 'context'], callback: 'disable_compound_trigger', - category: 'drop', priority: 3, label: gettext('Disable compound trigger'), + priority: 3, label: gettext('Disable compound trigger'), enable : 'canCreate_with_compound_trigger_disable', },{ name: 'create_compound_trigger_onView', node: 'view', module: this, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.js index 729b307de..acb7a5aa9 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.js @@ -47,7 +47,7 @@ define('pgadmin.node.foreign_key', [ },{ name: 'validate_foreign_key', node: 'foreign_key', module: this, applies: ['object', 'context'], callback: 'validate_foreign_key', - category: 'validate', priority: 4, label: gettext('Validate foreign key'), + priority: 4, label: gettext('Validate foreign key'), enable : 'is_not_valid', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js index a33859fab..cf218a6ca 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js @@ -90,7 +90,7 @@ function( },{ name: 'reset_table_stats', node: 'partition', module: this, applies: ['object', 'context'], callback: 'reset_table_stats', - category: 'Reset', priority: 4, label: gettext('Reset Statistics'), + priority: 4, label: gettext('Reset Statistics'), enable : 'canCreate', },{ name: 'detach_partition', node: 'partition', module: this, @@ -121,7 +121,7 @@ function( },{ name: 'count_table_rows', node: 'partition', module: pgBrowser.Nodes['table'], applies: ['object', 'context'], callback: 'count_table_rows', - category: 'Count', priority: 2, label: gettext('Count Rows'), + priority: 2, label: gettext('Count Rows'), enable: true, }]); }, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js index 01fbf1dd6..528c3221d 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js @@ -119,12 +119,12 @@ define('pgadmin.node.table', [ },{ name: 'count_table_rows', node: 'table', module: this, applies: ['object', 'context'], callback: 'count_table_rows', - category: 'Count', priority: 2, label: gettext('Count Rows'), + priority: 2, label: gettext('Count Rows'), enable: true, },{ name: 'generate_erd', node: 'table', module: this, applies: ['object', 'context'], callback: 'generate_erd', - category: 'erd', priority: 5, label: gettext('ERD For Table'), + priority: 5, label: gettext('ERD For Table'), enable: (_, item) => { return !('catalog' in pgAdmin.Browser.tree.getTreeNodeHierarchy(item)); } diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/schema_diff_view_utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/schema_diff_view_utils.py index 3aaa19cf4..fba3dac18 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/schema_diff_view_utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/schema_diff_view_utils.py @@ -19,7 +19,7 @@ from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry class SchemaDiffViewCompare(SchemaDiffObjectCompare): - view_keys_to_ignore = ['oid', 'schema', 'xmin', 'oid-2', 'setting', + view_keys_to_ignore = ['oid', 'schema', 'xmin', 'oid-2', 'setting', 'indrelid'] trigger_keys_to_ignore = ['xmin', 'tgrelid', 'tgfoid', 'tfunction', diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js index e07dbd5f2..0edadcbd6 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js @@ -87,8 +87,10 @@ define('pgadmin.node.mview', [ @property {data} - Allow create view option on schema node or system view nodes. */ - pgAdmin.Browser.add_menu_category( - 'refresh_mview', gettext('Refresh View'), 18, ''); + pgAdmin.Browser.add_menu_category({ + name: 'refresh_mview', label: gettext('Refresh View'), priority: 18 + }); + pgBrowser.add_menus([{ name: 'create_mview_on_coll', node: 'coll-mview', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js index f1823a2ac..7b80a118b 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js +++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js @@ -119,7 +119,7 @@ define('pgadmin.node.database', [ },{ name: 'generate_erd', node: 'database', module: this, applies: ['object', 'context'], callback: 'generate_erd', - category: 'erd', priority: 5, label: gettext('ERD For Database'), + priority: 5, label: gettext('ERD For Database'), enable: (node) => { return node.allowConn; } diff --git a/web/pgadmin/browser/static/js/MainMenuFactory.js b/web/pgadmin/browser/static/js/MainMenuFactory.js index 80f697f7f..eb3dc9cce 100644 --- a/web/pgadmin/browser/static/js/MainMenuFactory.js +++ b/web/pgadmin/browser/static/js/MainMenuFactory.js @@ -11,42 +11,14 @@ import pgAdmin from 'sources/pgadmin'; import Menu, { MenuItem } from '../../../static/js/helpers/Menu'; import getApiInstance from '../../../static/js/api_instance'; import url_for from 'sources/url_for'; -import { getBrowser } from '../../../static/js/utils'; -import { isMac } from '../../../static/js/keyboard_shortcuts'; 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 } + { label: gettext('File'), name: 'file', id: 'mnu_file', index: 0, addSeprator: true, hasDynamicMenuItems: false }, + { label: gettext('Object'), name: 'object', id: 'mnu_obj', index: 1, addSeprator: true, hasDynamicMenuItems: true }, + { label: gettext('Tools'), name: 'tools', id: 'mnu_tools', index: 2, addSeprator: true, hasDynamicMenuItems: false }, + { label: gettext('Help'), name: 'help', id: 'mnu_help', index: 5, addSeprator: false, hasDynamicMenuItems: false } ]; -let { name: browser } = getBrowser(); -if (browser == 'Electron') { - let controlKey = isMac() ? 'cmd' : 'ctrl'; - let fullScreenKey = isMac() ? 'F' : 'F10'; - - const RUNTIME_MENUS_OPTIONS = { - runtime : { - label: gettext('Runtime'), - name: 'runtime', - priority: 999, - submenus: { - configure: { label: gettext('Configure...'), name: 'configure', priority: 0, enable: true}, - view_log: { label: gettext('View log...'), name: 'view_log', priority: 1, enable: true}, - enter_full_screen: { label: gettext('Enter Full Screen'), name: 'enter_full_screen', enable: true, priority: 2, key: fullScreenKey, modifiers: isMac() ?`${controlKey}+ctrl` : controlKey}, - exit_full_screen: { label: gettext('Exit Full Screen'), name: 'exit_full_screen', enable: true, priority: 2, key: fullScreenKey, modifiers: isMac() ?`${controlKey}+ctrl` : controlKey}, - actual_size: { label: gettext('Actual Size'), name: 'actual_size', priority: 3, enable: true, key: '0', modifiers: controlKey}, - zoom_in: { label: gettext('Zoom In'), name: 'zoom_in', priority: 4, enable: true, key: '+', modifiers: controlKey}, - zoom_out: { label: gettext('Zoom Out'), name: 'zoom_out', enable: true, priority: 5, key: '-', modifiers: controlKey}, - } - } - }; - - pgAdmin.Browser.RUNTIME_MENUS_OPTIONS = RUNTIME_MENUS_OPTIONS; -} - - export default class MainMenuFactory { static electronCallbacks = {}; @@ -75,23 +47,17 @@ 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); + let menuObj = Menu.create(_menu.name, _menu.label, _menu.id, _menu.index, _menu.addSeprator, _menu.hasDynamicMenuItems); 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); - } - }); + menuObj.clearMenuItems(); + menuObj.addMenuItems(MainMenuFactory.createMenuItems(pgAdmin.Browser.all_menus_cache[_menu.name])); } }); - pgAdmin.Browser.enable_disable_menus(); + // enable disable will take care of dynamic menus. + MainMenuFactory.enableDisableMenus(); window.electronUI?.onMenuClick((menuName)=>{ MainMenuFactory.electronCallbacks[menuName]?.(); @@ -135,20 +101,165 @@ export default class MainMenuFactory { }); } - static getContextMenu(menuList) { - Menu.sortMenus(menuList); - return menuList; + static enableDisableMenus(item) { + let itemData = item ? pgAdmin.Browser.tree.itemData(item) : undefined; + + const checkForItems = (items)=>{ + items.forEach((mitem) => { + const subItems = mitem.getMenuItems() ?? []; + if(subItems.length > 0) { + checkForItems(subItems); + } else { + mitem.checkAndSetDisabled(itemData, item); + } + }); + }; + + // Non dynamic menus will be required to check whether enabled/disabled. + pgAdmin.Browser.MainMenus.filter((m)=>(!m.hasDynamicMenuItems)).forEach((menu) => { + checkForItems(menu.getMenuItems()); + }); + + pgAdmin.Browser.MainMenus.filter((m)=>(m.hasDynamicMenuItems)).forEach((menu) => { + let menuItemList = MainMenuFactory.getDynamicMenu(menu.name, item, itemData); + menu.setMenuItems(menuItemList); + }); + + // set the context menu as well + pgAdmin.Browser.BrowserContextMenu = MainMenuFactory.getDynamicMenu('context', item, itemData, true); + + pgAdmin.Browser.Events.trigger('pgadmin:refresh-app-menu'); } - static checkNoMenuOptionForNode(d){ - let selectedNodeFromNodes=pgAdmin.Browser.Nodes[d._type]; + static checkNoMenuOptionForNode(itemData){ + if(!itemData) { + return true; + } + let selectedNodeFromNodes=pgAdmin.Browser.Nodes[itemData._type]; let selectedNode=pgAdmin.Browser.tree.selected(); - let flag=!_.isUndefined(selectedNodeFromNodes.showMenu); - if(flag){ - let showMenu = selectedNodeFromNodes.showMenu(d, selectedNode); - return {flag:showMenu?false:flag,showMenu}; - } else{ - return {flag,showMenu:undefined}; + return selectedNodeFromNodes.showMenu?.(itemData, selectedNode) ?? true; + } + + static createMenuItems(items, skipDisabled=false, checkAndSetDisabled=()=>true) { + let retVal = []; + let categories = {}; + + const getNewMenuItem = (i)=>{ + const mi = MainMenuFactory.createMenuItem({...i}); + checkAndSetDisabled?.(mi); + if(skipDisabled && mi.isDisabled) { + return null; + } + return mi; + }; + + const getMenuCategory = (catName)=>{ + let category = pgAdmin.Browser.menu_categories[catName]; + + if(!category) { + // generate category on the fly. + category = { + name: catName, + label: catName, + priority: 10, + }; + } + + let cmi = categories[category.name]; + if(!cmi) { + cmi = getNewMenuItem({...category}); + // for easily finding again, note down. + categories[category.name] = cmi; + } + return cmi; + }; + + const applySeparators = (mi)=>{ + const newItems = []; + if(mi.above) { + newItems.push(MainMenuFactory.getSeparator(mi.label, mi.priority)); + } + newItems.push(mi); + if(mi.below) { + newItems.push(MainMenuFactory.getSeparator(mi.label, mi.priority)); + } + return newItems; + }; + + Object.entries(items).forEach(([k, i])=>{ + if('name' in i) { + const mi = getNewMenuItem(i); + if(!mi) return; + + if(i.category??'common' != 'common') { + const cmi = getMenuCategory(i.category); + if(cmi) { + cmi.addMenuItems([...applySeparators(mi)]); + } else { + retVal.push(...applySeparators(mi)); + } + } else { + retVal.push(...applySeparators(getNewMenuItem(i))); + } + } else { + // Can be a category + const cmi = getMenuCategory(k); + if(cmi) { + cmi.addMenuItems(MainMenuFactory.createMenuItems(i, skipDisabled, checkAndSetDisabled)); + } + } + }); + + // Push the category menus + Object.values(categories).forEach((cmi)=>{ + const items = cmi.getMenuItems(); + + // if there is only one menu in the category, then no need of the category. + if(items.length <= 1 && !cmi.single) { + retVal = retVal.concat(items); + return; + } + retVal.push(...applySeparators(cmi)); + }); + + Menu.sortMenus(retVal ?? []); + return retVal; + } + + static getDynamicMenu(name, item, itemData, skipDisabled=false) { + if(!item) { + return [MainMenuFactory.createMenuItem({ + name: '', + label: gettext('No object selected'), + category: 'create', + priority: 1, + enable: false, + })]; + } + const showMenu = MainMenuFactory.checkNoMenuOptionForNode(itemData); + if(!showMenu){ + return [MainMenuFactory.createMenuItem({ + enable : false, + label: gettext('No menu available for this object.'), + name:'', + priority: 1, + category: 'create', + })]; + } else { + const nodeTypeMenus = pgAdmin.Browser.all_menus_cache[name]?.[itemData._type] ?? []; + const menuItemList = MainMenuFactory.createMenuItems(nodeTypeMenus, skipDisabled, (mi)=>{ + return mi.checkAndSetDisabled(itemData, item); + }); + if(menuItemList.length == 0) { + return [MainMenuFactory.createMenuItem({ + enable : false, + label: gettext('No menu available for this object.'), + name:'', + priority: 1, + category: 'create', + })]; + } + return menuItemList; } } } diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index 96bc29705..8bbdab789 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -122,6 +122,7 @@ define('pgadmin.browser', [ menu_categories: { /* name, label (pair) */ 'register': { + name: 'register', label: gettext('Register'), priority: 1, /* separator above this menu */ @@ -131,6 +132,7 @@ define('pgadmin.browser', [ single: true, }, 'create': { + name: 'create', label: gettext('Create'), priority: 2, /* separator above this menu */ @@ -147,113 +149,9 @@ define('pgadmin.browser', [ scripts[n].push({'name': m, 'path': p, loaded: false}); }, masterpass_callback_queue: [], - getMenuList: function(name, item, d, skipDisabled=false) { - let obj = this; - //This 'checkNoMenuOptionForNode' function will check if showMenu flag is present or not for selected node - let {flag,showMenu}=MainMenuFactory.checkNoMenuOptionForNode(d); - if(flag){ - if(showMenu===false){ - return [MainMenuFactory.createMenuItem({ - enable : false, - label: gettext('No menu available for this object.'), - name:'', - priority: 1, - category: 'create', - })]; - } - }else{ - let category = { - 'common': [] - }; - 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 allMenuItemsDisabled = true; - category[c].forEach((mi)=> { - mi.checkAndSetDisabled(d, item); - if(allMenuItemsDisabled) { - allMenuItemsDisabled = mi.isDisabled; - } - }); - - 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) || !skipDisabled) { - let _menuItem = MainMenuFactory.createMenuItem({ - name: c, - label: label, - module: c, - category: c, - menu_items: category[c], - priority: priority - }); - - menuItemList.push(_menuItem); - } - if(categoryMenuOptions?.below) { - menuItemList.push(MainMenuFactory.getSeparator(label, priority)); - } - } else { - category[c].forEach((c)=> { - c.checkAndSetDisabled(d, item); - }); - - category[c].forEach((m)=> { - if(!skipDisabled || (skipDisabled && !m.isDisabled)) { - menuItemList.push(m); - } - }); - } - } - - return menuItemList; - } - }, // Enable/disable menu options enable_disable_menus: function(item) { - let obj = this; - let d = item ? obj.tree.itemData(item) : undefined; - - // All menus (except for the object menus) are already present. - // They will just require to check, whether they are - // enabled/disabled. - pgBrowser.MainMenus.filter((m)=>m.name != 'object').forEach((menu) => { - menu.menuItems.forEach((mitem) => { - mitem.checkAndSetDisabled(d, item); - }); - }); - - // Create the object menu dynamically - 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); - } else { - objectMenu && MainMenuFactory.refreshMainMenuItems(objectMenu, [ - MainMenuFactory.createMenuItem({ - name: '', - label: gettext('No object selected'), - category: 'create', - priority: 1, - enable: false, - }) - ]); - } + MainMenuFactory.enableDisableMenus(item); }, init: function() { let obj=this; @@ -414,29 +312,28 @@ define('pgadmin.browser', [ }); }, - add_menu_category: function( - id, label, priority, icon, above_separator, below_separator, single - ) { - this.menu_categories[id] = { - label: label, - priority: priority, - icon: icon, - above: (above_separator === true), - below: (below_separator === true), - single: single, + add_menu_category: function({name, ...options}) { + this.menu_categories[name] = { + label: '(No Label)', + priority: 10, + icon: '', + above: false, + below: false, + parent: null, + isCategory: true, + ...options, }; }, // Add menus of module/extension at appropriate menu add_menus: function(menus) { - let pgMenu = this.all_menus_cache; + const self = this; + let allMenus = this.all_menus_cache; _.each(menus, function(m) { _.each(m.applies, function(a) { /* We do support menu type only from this list */ if(['context', 'file', 'edit', 'object','management', 'tools', 'help'].indexOf(a) > -1){ - let _menus; - // If current node is not visible in browser tree // then return from here if(!checkNodeVisibility(m.node)) { @@ -448,16 +345,19 @@ define('pgadmin.browser', [ return; } } - - pgMenu[a] = pgMenu[a] || {}; - if (_.isString(m.node)) { - _menus = pgMenu[a][m.node] = pgMenu[a][m.node] || {}; - } else if (_.isString(m.category)) { - _menus = pgMenu[a][m.category] = pgMenu[a][m.category] || {}; - } - else { - _menus = pgMenu[a]; - } + const getFullPath = (currPath, currMenu)=>{ + if(currMenu.node) { + return currPath.concat([currMenu.node]); + } else if(currMenu.category??'common' != 'common') { + const currCat = self.menu_categories[currMenu.category]; + if(currCat?.category) { + return getFullPath(currPath.concat([currMenu.category]), currCat); + } + return [currMenu.category].concat(currPath); + } else { + return currPath; + } + }; let get_menuitem_obj = function(_m) { let enable = _m.enable; @@ -474,7 +374,7 @@ define('pgadmin.browser', [ }; } - return MainMenuFactory.createMenuItem({ + return { name: _m.name, label: _m.label, module: _m.module, @@ -490,20 +390,19 @@ define('pgadmin.browser', [ checked: _m.checked, below: _m.below, applies: _m.applies, - }); + }; }; - if (!_.has(_menus, m.name)) { - _menus[m.name] = get_menuitem_obj(m); + const menuPath = [a].concat(getFullPath([], m)).concat([m.name]); + const _menus = _.set(allMenus, menuPath, get_menuitem_obj(m)); - if(m.menu_items) { - let sub_menu_items = []; + if(m.menu_items) { + let sub_menu_items = []; - for(let mnu_val of m.menu_items) { - sub_menu_items.push(get_menuitem_obj(mnu_val)); - } - _menus[m.name]['menu_items'] = sub_menu_items; + for(let mnu_val of m.menu_items) { + sub_menu_items.push(get_menuitem_obj(mnu_val)); } + _menus[m.name]['menu_items'] = sub_menu_items; } } else { console.warn( diff --git a/web/pgadmin/browser/static/js/node_ajax.js b/web/pgadmin/browser/static/js/node_ajax.js index 4a1fc112c..877e645b9 100644 --- a/web/pgadmin/browser/static/js/node_ajax.js +++ b/web/pgadmin/browser/static/js/node_ajax.js @@ -136,7 +136,8 @@ export function getNodeAjaxOptions(url, nodeObj, treeNodeInfo, itemNodeData, par } /* Get the nodes list based on current selected node id */ -export function getNodeListById(nodeObj, treeNodeInfo, itemNodeData, params={}, filter=()=>true) { +export function getNodeListById(nodeObj, treeNodeInfo, itemNodeData, params={}, filter=()=>true, postTransform=(res)=>res) { + nodeObj = typeof(nodeObj) == 'string' ? pgAdmin.Browser.Nodes[nodeObj] : nodeObj; /* Transform the result to add image details */ const transform = (rows) => { let res = []; @@ -158,7 +159,7 @@ export function getNodeListById(nodeObj, treeNodeInfo, itemNodeData, params={}, } }); - return res; + return postTransform(res); }; return getNodeAjaxOptions('nodes', nodeObj, treeNodeInfo, itemNodeData, params, transform); diff --git a/web/pgadmin/static/js/AppMenuBar.jsx b/web/pgadmin/static/js/AppMenuBar.jsx index c83b04ac9..9f4164bf2 100644 --- a/web/pgadmin/static/js/AppMenuBar.jsx +++ b/web/pgadmin/static/js/AppMenuBar.jsx @@ -67,7 +67,7 @@ export default function AppMenuBar() { pgAdmin.Browser.Events.on('pgadmin:enable-disable-menu-items', _.debounce(()=>{ forceUpdate(); }, 100)); - pgAdmin.Browser.Events.on('pgadmin:refresh-menu-item', _.debounce(()=>{ + pgAdmin.Browser.Events.on('pgadmin:refresh-app-menu', _.debounce(()=>{ forceUpdate(); }, 100)); }, []); @@ -95,6 +95,18 @@ export default function AppMenuBar() { const userMenuInfo = pgAdmin.Browser.utils.userMenuInfo; + const getPgMenu = (menu)=>{ + return menu.getMenuItems()?.map((menuItem, i)=>{ + const submenus = menuItem.getMenuItems(); + if(submenus) { + return + {getPgMenu(menuItem)} + ; + } + return getPgMenuItem(menuItem, i); + }); + }; + return (
@@ -106,17 +118,7 @@ export default function AppMenuBar() { label={menu.label} key={menu.name} > - {menu.getMenuItems().map((menuItem, i)=>{ - const submenus = menuItem.getMenuItems(); - if(submenus) { - return - {submenus.map((submenuItem, si)=>{ - return getPgMenuItem(submenuItem, si); - })} - ; - } - return getPgMenuItem(menuItem, i); - })} + {getPgMenu(menu)} ); })} diff --git a/web/pgadmin/static/js/helpers/Menu.js b/web/pgadmin/static/js/helpers/Menu.js index 30ff26d8a..3db5aeb06 100644 --- a/web/pgadmin/static/js/helpers/Menu.js +++ b/web/pgadmin/static/js/helpers/Menu.js @@ -10,17 +10,18 @@ import _ from 'lodash'; import gettext from 'sources/gettext'; export default class Menu { - constructor(name, label, id, index, addSepratior) { + constructor(name, label, id, index, addSeprator, hasDynamicMenuItems) { this.label = label; this.name = name; this.id = id; this.index = index || 1; this.menuItems = []; - this.addSepratior = addSepratior || false; + this.addSeprator = addSeprator || false; + this.hasDynamicMenuItems = hasDynamicMenuItems; } - static create(name, label, id, index, addSepratior) { - let menuObj = new Menu(name, label, id, index, addSepratior); + static create(name, label, id, index, addSeprator, hasDynamicMenuItems) { + let menuObj = new Menu(name, label, id, index, addSeprator, hasDynamicMenuItems); return menuObj; } @@ -30,7 +31,7 @@ export default class Menu { label: this.label, name: this.name, index: this.index, - addSepratior: this.addSepratior, + addSeprator: this.addSeprator, }; } @@ -41,13 +42,10 @@ export default class Menu { 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) { @@ -59,7 +57,6 @@ export default class Menu { item.menu_items.forEach((i)=> { i.parentMenu = item; }); - Menu.sortMenus(item.menu_items); } } else { let subItems = Object.values(item); @@ -88,16 +85,16 @@ export default class Menu { setMenuItems(menuItems) { this.menuItems = menuItems; - Menu.sortMenus(this.menuItems); + } - this.menuItems.forEach((item)=> { - if(item?.menu_items?.length > 0) { - Menu.sortMenus(item.menu_items); - } - }); + clearMenuItems() { + this.menuItems = []; } static sortMenus(menuItems) { + if(!menuItems || menuItems.length <=0) { + return; + } // Sort by alphanumeric ordered first menuItems.sort(function (a, b) { return a.label.localeCompare(b.label); @@ -107,6 +104,10 @@ export default class Menu { menuItems.sort(function (a, b) { return a.priority - b.priority; }); + + menuItems.forEach((mi)=>{ + Menu.sortMenus(mi.getMenuItems()); + }); } getMenuItems() { @@ -117,9 +118,9 @@ export default class Menu { export class MenuItem { constructor(options, onDisableChange) { - let menu_opts = [ + let allowedOptions = [ 'name', 'label', 'priority', 'module', 'callback', 'data', 'enable', - 'category', 'target', 'url', 'node', + 'category', 'target', 'url', 'node', 'single', 'checked', 'below', 'menu_items', 'is_checkbox', 'action', 'applies', 'is_native_only', 'type', ]; let defaults = { @@ -127,7 +128,7 @@ export class MenuItem { target: '_self', enable: true, }; - _.extend(this, defaults, _.pick(options, menu_opts)); + _.extend(this, defaults, _.pick(options, allowedOptions)); if (!this.callback) { this.callback = (item) => { if (item.url != '#') { @@ -159,6 +160,11 @@ export class MenuItem { this.checked = isChecked; } + addMenuItems(items) { + this.menu_items = this.menu_items ?? []; + this.menu_items = this.menu_items.concat(items); + } + getMenuItems() { return this.menu_items; } diff --git a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js index a21bc2569..0795f949a 100644 --- a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js +++ b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js @@ -163,7 +163,10 @@ export default class SQLEditor { }); } - pgBrowser.add_menu_category('view_data', gettext('View/Edit Data'), 100, ''); + pgBrowser.add_menu_category({ + name: 'view_data', label: gettext('View/Edit Data'), priority: 100 + }); + pgBrowser.add_menus(menus); }