Implemented React based modal provider to create models on the fly.

Also, use this to replace Alertify alert and confirm dialog.
This commit is contained in:
Aditya Toshniwal authored and Akshay Joshi committed 2021-12-03 12:50:24 +05:30
1 parent 9c0c046a38
commit dfdaf7f6d1
7 files changed
+262 -16

No files matched your search

+20 -1
View File
@@ -167,7 +167,7 @@ basicSettings = createMuiTheme(basicSettings, {
top: 0,
zIndex: 9999,
}
}
},
},
transitions: {
duration: {
@@ -199,6 +199,9 @@ basicSettings = createMuiTheme(basicSettings, {
},
MuiCheckbox: {
disableTouchRipple: true,
},
MuiDialogTitle: {
disableTypography: true,
}
},
});
@@ -390,6 +393,22 @@ function getFinalTheme(baseTheme) {
color: baseTheme.palette.text.muted,
},
},
MuiDialogContent: {
root: {
padding: 0,
userSelect: 'text',
}
},
MuiDialogTitle: {
root: {
fontWeight: 'bold',
padding: '5px 10px',
cursor: 'move',
display: 'flex',
alignItems: 'center',
...mixins.panelBorder.bottom,
}
},
}
}, baseTheme);
}
+43 -6
View File
@@ -15,6 +15,12 @@ import CustomPropTypes from '../custom_prop_types';
const useStyles = makeStyles((theme)=>({
primaryButton: {
'&.MuiButton-outlinedSizeSmall': {
height: '28px',
'& .MuiSvgIcon-root': {
height: '0.8em',
}
},
'&.Mui-disabled': {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.disabledMain,
@@ -28,6 +34,13 @@ const useStyles = makeStyles((theme)=>({
backgroundColor: theme.palette.default.main,
color: theme.palette.default.contrastText,
border: '1px solid '+theme.palette.default.borderColor,
whiteSpace: 'nowrap',
'&.MuiButton-outlinedSizeSmall': {
height: '28px',
'& .MuiSvgIcon-root': {
height: '0.8em',
}
},
'&.Mui-disabled': {
color: theme.palette.default.disabledContrastText,
borderColor: theme.palette.default.disabledBorderColor
@@ -52,35 +65,59 @@ const useStyles = makeStyles((theme)=>({
backgroundColor: theme.custom.icon.hoverMain,
color: theme.custom.icon.hoverContrastText,
}
},
xsButton: {
padding: '2px 1px',
height: '24px',
'& .MuiSvgIcon-root': {
height: '0.8em',
}
},
noBorder: {
border: 0,
}
}));
/* pgAdmin primary button */
export const PrimaryButton = forwardRef((props, ref)=>{
let {children, className, ...otherProps} = props;
let {children, className, size, noBorder, ...otherProps} = props;
const classes = useStyles();
let allClassName = [classes.primaryButton, className];
if(size == 'xs') {
size = undefined;
allClassName.push(classes.xsButton);
}
noBorder && allClassName.push(classes.noBorder);
return (
<Button ref={ref} variant="contained" color="primary" className={clsx(classes.primaryButton, className)} {...otherProps}>{children}</Button>
<Button ref={ref} variant="contained" color="primary" size={size} className={clsx(allClassName)} {...otherProps}>{children}</Button>
);
});
PrimaryButton.displayName = 'PrimaryButton';
PrimaryButton.propTypes = {
size: PropTypes.string,
noBorder: PropTypes.bool,
children: CustomPropTypes.children,
className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
};
/* pgAdmin default button */
export const DefaultButton = forwardRef((props, ref)=>{
let {children, className, ...otherProps} = props;
let {children, className, size, noBorder, ...otherProps} = props;
const classes = useStyles();
let allClassName = [classes.defaultButton, className];
if(size == 'xs') {
size = undefined;
allClassName.push(classes.xsButton);
}
noBorder && allClassName.push(classes.noBorder);
return (
<Button ref={ref} variant="outlined" color="default" className={clsx(classes.defaultButton, className)} {...otherProps}>{children}</Button>
<Button ref={ref} variant="outlined" color="default" size={size} className={clsx(allClassName)} {...otherProps}>{children}</Button>
);
});
DefaultButton.displayName = 'DefaultButton';
DefaultButton.propTypes = {
size: PropTypes.string,
noBorder: PropTypes.bool,
children: CustomPropTypes.children,
className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
};
@@ -0,0 +1,92 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { Box, Dialog, DialogContent, DialogTitle, Paper } from '@material-ui/core';
import React from 'react';
import {getEpoch} from 'sources/utils';
import { PgIconButton } from '../components/Buttons';
import Draggable from 'react-draggable';
import CloseIcon from '@material-ui/icons/CloseRounded';
import CustomPropTypes from '../custom_prop_types';
import PropTypes from 'prop-types';
const ModalContext = React.createContext({});
export function useModal() {
return React.useContext(ModalContext);
}
export default function ModalProvider({children}) {
const [modals, setModals] = React.useState([]);
const showModal = (title, content, modalOptions)=>{
let id = getEpoch().toString() + Math.random();
setModals((prev)=>[...prev, {
id: id,
title: title,
content: content,
...modalOptions,
}]);
};
const closeModal = (id)=>{
setModals((prev)=>{
return prev.filter((o)=>o.id!=id);
});
};
const modalContext = React.useMemo(()=>({
showModal: showModal,
closeModal: closeModal,
}), []);
return (
<ModalContext.Provider value={modalContext}>
{children}
{modals.map((modalOptions, i)=>(
<ModalContainer key={i} {...modalOptions}/>
))}
</ModalContext.Provider>
);
}
ModalProvider.propTypes = {
children: CustomPropTypes.children,
};
function PaperComponent(props) {
return (
<Draggable cancel={'[class*="MuiDialogContent-root"]'}>
<Paper {...props} style={{minWidth: '600px'}} />
</Draggable>
);
}
function ModalContainer({id, title, content}) {
let useModalRef = useModal();
let closeModal = ()=>useModalRef.closeModal(id);
return (
<Dialog
open={true}
onClose={closeModal}
PaperComponent={PaperComponent}
disableBackdropClick
>
<DialogTitle>
<Box marginRight="0.25rem">{title}</Box>
<Box marginLeft="auto"><PgIconButton icon={<CloseIcon />} size="xs" noBorder onClick={closeModal}/></Box>
</DialogTitle>
<DialogContent>
{content(closeModal)}
</DialogContent>
</Dialog>
);
}
ModalContainer.propTypes = {
id: PropTypes.string,
title: CustomPropTypes.children,
content: CustomPropTypes.children,
};
+96 -8
View File
@@ -16,30 +16,54 @@ import CustomPropTypes from '../custom_prop_types';
import gettext from 'sources/gettext';
import pgWindow from 'sources/window';
import Alertify from 'pgadmin.alertifyjs';
import ModalProvider, { useModal } from './ModalProvider';
import { DefaultButton, PrimaryButton } from '../components/Buttons';
import { Box } from '@material-ui/core';
import { makeStyles } from '@material-ui/styles';
import HTMLReactParse from 'html-react-parser';
import CloseIcon from '@material-ui/icons/CloseRounded';
import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
import PropTypes from 'prop-types';
const AUTO_HIDE_DURATION = 3000; // In milliseconds
let snackbarRef;
function SnackbarUtilsConfigurator() {
snackbarRef = useSnackbar();
return <></>;
}
let notifierInitialized = false;
export function initializeNotifier(notifierContainer) {
notifierInitialized = true;
const RefLoad = ()=>{
snackbarRef = useSnackbar();
return <></>;
};
ReactDOM.render(
<Theme>
<SnackbarProvider
maxSnack={30}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}>
<SnackbarUtilsConfigurator />
<RefLoad />
</SnackbarProvider>
</Theme>, notifierContainer
);
}
export const FinalNotifyContent = React.forwardRef(({children}, ref) => {
let modalRef;
let modalInitialized = false;
export function initializeModalProvider(modalContainer) {
modalInitialized = true;
const RefLoad = ()=>{
modalRef = useModal();
return <></>;
};
ReactDOM.render(
<Theme>
<ModalProvider>
<RefLoad />
</ModalProvider>
</Theme>, modalContainer
);
}
const FinalNotifyContent = React.forwardRef(({children}, ref) => {
return <SnackbarContent style= {{justifyContent:'end'}} ref={ref}>{children}</SnackbarContent>;
});
FinalNotifyContent.displayName = 'FinalNotifyContent';
@@ -47,6 +71,38 @@ FinalNotifyContent.propTypes = {
children: CustomPropTypes.children,
};
const useAlertStyles = makeStyles((theme)=>({
footer: {
display: 'flex',
justifyContent: 'flex-end',
padding: '0.5rem',
...theme.mixins.panelBorder.top,
},
margin: {
marginLeft: '0.25rem',
}
}));
function AlertContent({text, confirm, onOkClick, onCancelClick}) {
const classes = useAlertStyles();
return (
<Box display="flex" flexDirection="column" height="100%">
<Box flexGrow="1" p={2}>{HTMLReactParse(text)}</Box>
<Box className={classes.footer}>
<DefaultButton startIcon={<CloseIcon />} onClick={onCancelClick} autoFocus={!confirm}>Close</DefaultButton>
{confirm &&
<PrimaryButton className={classes.margin} startIcon={<CheckRoundedIcon />} onClick={onOkClick} autoFocus={confirm}>OK</PrimaryButton>
}
</Box>
</Box>
);
}
AlertContent.propTypes = {
text: PropTypes.string,
confirm: PropTypes.bool,
onOkClick: PropTypes.func,
onCancelClick: PropTypes.func,
};
var Notifier = {
success(msg, autoHideDuration = AUTO_HIDE_DURATION) {
this._callNotify(msg, MESSAGE_TYPE.SUCCESS, autoHideDuration);
@@ -170,7 +226,39 @@ var Notifier = {
Alertify.alert().show().set(
'message', msg.replace(new RegExp(/\r?\n/, 'g'), '<br />')
).set('title', promptmsg).set('closable', true);
}
},
alert: (title, text, onCancelClick)=>{
if(!modalInitialized) {
initializeModalProvider(document.getElementById('modalContainer'));
}
modalRef.showModal(title, (closeModal)=>{
const onCancelClickClose = ()=>{
onCancelClick && onCancelClick();
closeModal();
};
return (
<AlertContent text={text} onCancelClick={onCancelClickClose} />
);
});
},
confirm: (title, text, onOkClick, onCancelClick)=>{
if(!modalInitialized) {
initializeModalProvider(document.getElementById('modalContainer'));
}
modalRef.showModal(title, (closeModal)=>{
const onCancelClickClose = ()=>{
onCancelClick && onCancelClick();
closeModal();
};
const onOkClickClose = ()=>{
onOkClick && onOkClick();
closeModal();
};
return (
<AlertContent text={text} confirm onOkClick={onOkClickClose} onCancelClick={onCancelClickClose} />
);
});
},
};
if(window.frameElement) {