Rewrite pgAdmin main menu bar to use React. #5615

This commit is contained in:
Aditya Toshniwal
2022-12-22 14:25:18 +05:30
committed by GitHub
parent d5e6786bc7
commit ff9daec5ec
26 changed files with 510 additions and 1046 deletions

View File

@@ -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'));
}
});

View File

@@ -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>
</>
);
}

View File

@@ -88,6 +88,7 @@ basicSettings = createMuiTheme(basicSettings, {
root: {
textTransform: 'none',
padding: basicSettings.spacing(0.5, 1.5),
fontSize: 'inherit',
'&.Mui-disabled': {
opacity: 0.60,
},

View File

@@ -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>}

View File

@@ -0,0 +1,207 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import gettext from 'sources/gettext';
export default class Menu {
constructor(name, label, id, index, addSepratior) {
this.label = label;
this.name = name;
this.id = id;
this.index = index || 1;
this.menuItems = [],
this.addSepratior = addSepratior || false;
}
static create(name, label, id, index, addSepratior) {
let menuObj = new Menu(name, label, id, index, addSepratior);
return menuObj;
}
addMenuItem(menuItem, index=null) {
if (menuItem instanceof MenuItem) {
menuItem.parentMenu = this;
if(index) {
this.menuItems.splice(index, 0, menuItem);
} else {
this.menuItems.push(menuItem);
Menu.sortMenus(this.menuItems);
}
} else {
throw new Error(gettext('Invalid MenuItem instance'));
}
}
addMenuItems(menuItems) {
menuItems.forEach((item) => {
if (item instanceof MenuItem) {
item.parentMenu = this;
this.menuItems.push(item);
if(item?.menu_items && item.menu_items.length > 0) {
item.menu_items.forEach((i)=> {
i.parentMenu = item;
});
Menu.sortMenus(item.menu_items);
}
} else {
let subItems = Object.values(item);
subItems.forEach((subItem)=> {
if (subItem instanceof MenuItem) {
subItem.parentMenu = this;
this.menuItems.push(subItem);
} else {
throw new Error(gettext('Invalid MenuItem instance'));
}
});
}
});
// Sort by alphanumeric ordered first
this.menuItems.sort(function (a, b) {
return a.label.localeCompare(b.label);
});
// Sort by priority
this.menuItems.sort(function (a, b) {
return a.priority - b.priority;
});
}
setMenuItems(menuItems) {
this.menuItems = menuItems;
Menu.sortMenus(this.menuItems);
this.menuItems.forEach((item)=> {
if(item?.menu_items?.length > 0) {
Menu.sortMenus(item.menu_items);
}
});
}
static sortMenus(menuItems) {
// Sort by alphanumeric ordered first
menuItems.sort(function (a, b) {
return a.label.localeCompare(b.label);
});
// Sort by priority
menuItems.sort(function (a, b) {
return a.priority - b.priority;
});
}
getMenuItems() {
return this.menuItems;
}
}
export class MenuItem {
constructor(options, onDisableChange, onChangeChecked) {
let menu_opts = [
'name', 'label', 'priority', 'module', 'callback', 'data', 'enable',
'category', 'target', 'url', 'node',
'checked', 'below', 'menu_items', 'is_checkbox', 'action', 'applies', 'is_native_only', 'type'
];
let defaults = {
url: '#',
target: '_self',
enable: true,
type: 'normal'
};
_.extend(this, defaults, _.pick(options, menu_opts));
if (!this.callback) {
this.callback = (item) => {
if (item.url != '#') {
window.open(item.url);
}
};
}
this.onDisableChange = onDisableChange;
this.changeChecked = onChangeChecked;
this._isDisabled = true;
this.checkAndSetDisabled();
}
static create(options) {
return MenuItem(options);
}
change_checked(isChecked) {
this.checked = isChecked;
this.changeChecked?.(this);
}
getMenuItems() {
return this.menu_items;
}
contextMenuCallback(self) {
self.callback();
}
getContextItem(label, is_disabled, sub_ctx_item) {
let self = this;
return {
name: label,
disabled: is_disabled,
callback: () => { this.contextMenuCallback(self); },
...(sub_ctx_item && Object.keys(sub_ctx_item).length > 0) && { items: sub_ctx_item }
};
}
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.
*/
disabled(node, item) {
if (this.enable == undefined) {
return false;
}
if (this.node) {
if (!node) {
return true;
}
if (_.isArray(this.node) ? (
_.indexOf(this.node, node) == -1
) : (this.node != node._type)) {
return true;
}
}
if (_.isBoolean(this.enable)) return !this.enable;
if (_.isFunction(this.enable)) {
return !this.enable.apply(this.module, [node, item, this.data]);
}
if (this.module && _.isBoolean(this.module[this.enable])) {
return !this.module[this.enable];
}
if (this.module && _.isFunction(this.module[this.enable])) {
return !(this.module[this.enable]).apply(this.module, [node, item, this.data]);
}
return false;
}
}

View File

@@ -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;