///////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2025, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// import gettext from 'sources/gettext'; 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 withCheckPermission from './withCheckPermission'; const MAIN_MENUS = [ { 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 } ]; export default class MainMenuFactory { static electronCallbacks = {}; static toElectron() { // we support 2 levels of submenu return pgAdmin.Browser.MainMenus.map((m)=>{ return { ...m.serialize(), submenu: m.menuItems.map((sm)=>{ const smName = `${m.name}_${sm.name}`; MainMenuFactory.electronCallbacks[smName] = sm.callback; return { ...sm.serialize(), submenu: sm.getMenuItems()?.map((smsm)=>{ MainMenuFactory.electronCallbacks[`${smName}_${smsm.name}`] = smsm.callback; return { ...smsm.serialize(), }; }) }; }) }; }); } static createMainMenus() { pgAdmin.Browser.MainMenus = []; MAIN_MENUS.forEach((_menu) => { 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 hasDynamicMenuItems true as it's menuItems get changed on tree selection. if(!_menu.hasDynamicMenuItems) { menuObj.clearMenuItems(); menuObj.addMenuItems(MainMenuFactory.createMenuItems(pgAdmin.Browser.all_menus_cache[_menu.name])); } }); // enable disable will take care of dynamic menus. MainMenuFactory.enableDisableMenus(); window.electronUI?.onMenuClick((menuName)=>{ MainMenuFactory.electronCallbacks[menuName]?.(); }); window.electronUI?.setMenus(MainMenuFactory.toElectron()); } static getSeparator(label, priority) { return new MenuItem({type: 'separator', label, priority}); } static createMenuItem(options) { const callback = () => { // Some callbacks registered in 'callbacks' check and call specifiec callback function if (options.module && 'callbacks' in options.module && options.module.callbacks[options.callback]) { options.module.callbacks[options.callback].apply(options.module, [options.data, pgAdmin.Browser.tree?.selected()]); } else if (options?.module?.[options.callback]) { options.module[options.callback](options.data, pgAdmin.Browser.tree?.selected()); } else if (options?.callback) { options.callback(options); } else if (options.url != '#') { let api = getApiInstance(); api( url_for('tools.initialize') ).then(()=>{ window.open(options.url); }).catch(()=>{ pgAdmin.Browser.notifier.error(gettext('Error in opening window')); }); } }; return new MenuItem({...options, callback: withCheckPermission(options, callback)}, (menu, item)=> { pgAdmin.Browser.Events.trigger('pgadmin:enable-disable-menu-items', menu, item); window.electronUI?.enableDisableMenuItems(menu?.serialize(), item?.serialize()); }); } 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); window.electronUI?.setMenus(MainMenuFactory.toElectron()); pgAdmin.Browser.Events.trigger('pgadmin:refresh-app-menu'); } static checkNoMenuOptionForNode(itemData){ if(!itemData) { return true; } let selectedNodeFromNodes=pgAdmin.Browser.Nodes[itemData._type]; let selectedNode=pgAdmin.Browser.tree.selected(); 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; } } }