mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
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:
parent
9c0c046a38
commit
dfdaf7f6d1
@ -144,6 +144,7 @@
|
||||
"react": "^17.0.1",
|
||||
"react-aspen": "^1.1.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-draggable": "^4.4.4",
|
||||
"react-select": "^4.2.1",
|
||||
"react-table": "^7.6.3",
|
||||
"react-virtualized-auto-sizer": "^1.0.6",
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]),
|
||||
};
|
||||
|
92
web/pgadmin/static/js/helpers/ModalProvider.jsx
Normal file
92
web/pgadmin/static/js/helpers/ModalProvider.jsx
Normal file
@ -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,
|
||||
};
|
@ -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) {
|
||||
|
@ -75,6 +75,7 @@
|
||||
|
||||
{% block body %}{% endblock %}
|
||||
<div id="notifierContainer"></div>
|
||||
<div id="modalContainer"></div>
|
||||
<script type="application/javascript">
|
||||
{% block init_script %}{% endblock %}
|
||||
</script>
|
||||
|
@ -2907,7 +2907,7 @@ closest@^0.0.1:
|
||||
dependencies:
|
||||
matches-selector "0.0.1"
|
||||
|
||||
clsx@^1.0.2, clsx@^1.0.4, clsx@^1.1.0:
|
||||
clsx@^1.0.2, clsx@^1.0.4, clsx@^1.1.0, clsx@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
||||
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
||||
@ -7782,6 +7782,14 @@ react-dom@^17.0.1:
|
||||
object-assign "^4.1.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-draggable@^4.4.4:
|
||||
version "4.4.4"
|
||||
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.4.tgz#5b26d9996be63d32d285a426f41055de87e59b2f"
|
||||
integrity sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==
|
||||
dependencies:
|
||||
clsx "^1.1.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-input-autosize@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
|
||||
|
Loading…
Reference in New Issue
Block a user