codekingpro/portable-devtools
114k
1/////////////////////////////////////////////////////////////2//3// pgAdmin 4 - PostgreSQL Tools4//5// Copyright (C) 2013 - 2024, The pgAdmin Development Team6// This software is released under the PostgreSQL Licence7//8//////////////////////////////////////////////////////////////9 10import { Box, Dialog, DialogContent, DialogTitle, Paper } from '@mui/material';11import { makeStyles } from '@mui/styles';12import React, { useState, useMemo } from 'react';13import clsx from 'clsx';14import { getEpoch } from 'sources/utils';15import { DefaultButton, PgIconButton, PrimaryButton } from '../components/Buttons';16import Draggable from 'react-draggable';17import CloseIcon from '@mui/icons-material/CloseRounded';18import CustomPropTypes from '../custom_prop_types';19import PropTypes from 'prop-types';20import gettext from 'sources/gettext';21import HTMLReactParser from 'html-react-parser';22import CheckRoundedIcon from '@mui/icons-material/CheckRounded';23import { Rnd } from 'react-rnd';24import { ExpandDialogIcon, MinimizeDialogIcon } from '../components/ExternalIcon';25 26export const ModalContext = React.createContext({});27const MIN_HEIGHT = 190;28const MIN_WIDTH = 500;29 30export function useModal() {31 return React.useContext(ModalContext);32}33const useAlertStyles = makeStyles((theme) => ({34 footer: {35 display: 'flex',36 justifyContent: 'flex-end',37 padding: '0.5rem',38 ...theme.mixins.panelBorder.top,39 },40 margin: {41 marginLeft: '0.25rem',42 }43}));44 45function AlertContent({ text, confirm, okLabel = gettext('OK'), cancelLabel = gettext('Cancel'), onOkClick, onCancelClick }) {46 const classes = useAlertStyles();47 return (48 <Box display="flex" flexDirection="column" height="100%">49 <Box flexGrow="1" p={2}>{typeof (text) == 'string' ? HTMLReactParser(text) : text}</Box>50 <Box className={classes.footer}>51 {confirm &&52 <DefaultButton startIcon={<CloseIcon />} onClick={onCancelClick} >{cancelLabel}</DefaultButton>53 }54 <PrimaryButton className={classes.margin} startIcon={<CheckRoundedIcon />} onClick={onOkClick} autoFocus={true} >{okLabel}</PrimaryButton>55 </Box>56 </Box>57 );58}59AlertContent.propTypes = {60 text: PropTypes.string,61 confirm: PropTypes.bool,62 onOkClick: PropTypes.func,63 onCancelClick: PropTypes.func,64 okLabel: PropTypes.string,65 cancelLabel: PropTypes.string,66};67 68function alert(title, text, onOkClick, okLabel = gettext('OK')) {69 // bind the modal provider before calling70 this.showModal(title, (closeModal) => {71 const onOkClickClose = () => {72 onOkClick?.();73 closeModal();74 };75 return (76 <AlertContent text={text} onOkClick={onOkClickClose} okLabel={okLabel} />77 );78 });79}80 81function confirm(title, text, onOkClick, onCancelClick, okLabel = gettext('Yes'), cancelLabel = gettext('No')) {82 // bind the modal provider before calling83 this.showModal(title, (closeModal) => {84 const onCancelClickClose = () => {85 onCancelClick?.();86 closeModal();87 };88 const onOkClickClose = () => {89 onOkClick?.();90 closeModal();91 };92 return (93 <AlertContent text={text} confirm onOkClick={onOkClickClose} onCancelClick={onCancelClickClose} okLabel={okLabel} cancelLabel={cancelLabel} />94 );95 });96}97 98export default function ModalProvider({ children }) {99 const [modals, setModals] = React.useState([]);100 101 const showModal = (title, content, modalOptions) => {102 let id = getEpoch().toString() + crypto.getRandomValues(new Uint8Array(4));103 setModals((prev) => [...prev, {104 id: id,105 title: title,106 content: content,107 ...modalOptions,108 }]);109 };110 const closeModal = (id) => {111 setModals((prev) => {112 return prev.filter((o) => o.id != id);113 });114 };115 116 const fullScreenModal = (fullScreen) => {117 setModals((prev) => [...prev, {118 fullScreen: fullScreen,119 }]);120 };121 122 const modalContextBase = {123 showModal: showModal,124 closeModal: closeModal,125 fullScreenModal: fullScreenModal126 };127 const modalContext = React.useMemo(() => ({128 ...modalContextBase,129 confirm: confirm.bind(modalContextBase),130 alert: alert.bind(modalContextBase)131 }), []);132 return (133 <ModalContext.Provider value={modalContext}>134 {children}135 {modals.map((modalOptions) => (136 <ModalContainer key={modalOptions.id} {...modalOptions} />137 ))}138 </ModalContext.Provider>139 );140}141 142ModalProvider.propTypes = {143 children: CustomPropTypes.children,144};145 146const dialogStyle = makeStyles((theme) => ({147 dialog: {148 display: 'flex',149 alignItems: 'center',150 justifyContent: 'center',151 border: '1px solid ' + theme.otherVars.inputBorderColor,152 borderRadius: theme.shape.borderRadius,153 },154 fullScreen: {155 transform: 'none !important'156 }157}));158 159 160function checkIsResizable(props) {161 return props.isresizeable == 'true';162}163 164function setEnableResizing(props, resizeable) {165 return props.isfullscreen == 'true' ? false : resizeable;166}167 168function PaperComponent({minHeight, minWidth, ...props}) {169 let classes = dialogStyle();170 let [dialogPosition, setDialogPosition] = useState(null);171 let resizeable = checkIsResizable(props);172 173 const setConditionalPosition = () => {174 return props.isfullscreen == 'true' ? { x: 0, y: 0 } : dialogPosition && { x: dialogPosition.x, y: dialogPosition.y };175 };176 177 const y_position = window.innerHeight*0.02; // 2% of total height178 const x_position = props.width ? (window.innerWidth/2) - (props.width/2) : (window.innerWidth/2) - (MIN_WIDTH/2);179 180 return (181 props.isresizeable == 'true' ?182 <Rnd183 size={props.isfullscreen == 'true' && { width: '100%', height: '100%' }}184 className={clsx(classes.dialog, props.isfullscreen == 'true' ? classes.fullScreen : '')}185 default={{186 x: x_position,187 y: y_position,188 ...(props.width && { width: props.width }),189 ...(props.height && { height: props.height }),190 }}191 minWidth = { minWidth || MIN_WIDTH }192 minHeight = { minHeight || MIN_HEIGHT }193 // {...(props.width && { minWidth: MIN_WIDTH })}194 // {...(props.height && { minHeight: MIN_HEIGHT })}195 bounds="window"196 enableResizing={setEnableResizing(props, resizeable)}197 position={setConditionalPosition()}198 onDragStop={(e, position) => {199 if (props.isfullscreen !== 'true') {200 setDialogPosition({201 ...position,202 });203 }204 }}205 onResize={(e, direction, ref, delta, position) => {206 setDialogPosition({207 ...position,208 });209 }}210 dragHandleClassName="modal-drag-area"211 >212 <Paper {...props} style={{ width: '100%', height: '100%', maxHeight: '100%', maxWidth: '100%' }} />213 </Rnd>214 :215 <Draggable cancel={'[class*="MuiDialogContent-root"]'}>216 <Paper {...props} style={{ minWidth: '600px' }} />217 </Draggable>218 );219}220 221PaperComponent.propTypes = {222 isfullscreen: PropTypes.string,223 isresizeable: PropTypes.string,224 width: PropTypes.number,225 height: PropTypes.number,226 minWidth: PropTypes.number,227 minHeight: PropTypes.number,228};229 230export const useModalStyles = makeStyles((theme) => ({231 container: {232 backgroundColor: theme.palette.background.default233 },234 235 titleBar: {236 display: 'flex',237 flexGrow: 1238 },239 title: {240 flexGrow: 1241 },242 icon: {243 fill: 'currentColor',244 width: '1em',245 height: '1em',246 display: 'inline-block',247 fontSize: '1.5rem',248 transition: 'none',249 flexShrink: 0,250 userSelect: 'none',251 },252 footer: {253 display: 'flex',254 justifyContent: 'flex-end',255 padding: '0.5rem',256 ...theme.mixins.panelBorder?.top,257 },258 margin: {259 marginLeft: '0.25rem',260 },261 iconButtonStyle: {262 marginLeft: 'auto',263 marginRight: '4px'264 },265}));266 267function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false, minHeight = MIN_HEIGHT, minWidth = MIN_WIDTH, showTitle=true }) {268 let useModalRef = useModal();269 const classes = useModalStyles();270 let closeModal = (_e, reason) => {271 if(reason == 'backdropClick' && showTitle) {272 return;273 }274 useModalRef.closeModal(id);275 if(reason == 'escapeKeyDown' || reason == undefined) {276 onClose?.();277 }278 };279 const [isFullScreen, setIsFullScreen] = useState(fullScreen);280 281 return (282 <Dialog283 open={true}284 onClose={closeModal}285 PaperComponent={PaperComponent}286 PaperProps={{ 'isfullscreen': isFullScreen.toString(), 'isresizeable': isResizeable.toString(), width: dialogWidth, height: dialogHeight, minHeight: minHeight, minWidth: minWidth }}287 fullScreen={isFullScreen}288 fullWidth={isFullWidth}289 disablePortal290 >291 { showTitle &&292 <DialogTitle className='modal-drag-area'>293 <Box className={classes.titleBar}>294 <Box className={classes.title} marginRight="0.25rem" >{title}</Box>295 {296 showFullScreen && !isFullScreen &&297 <Box className={classes.iconButtonStyle}><PgIconButton title={gettext('Maximize')} icon={<ExpandDialogIcon className={classes.icon} />} size="xs" noBorder onClick={() => { setIsFullScreen(!isFullScreen); }} /></Box>298 }299 {300 showFullScreen && isFullScreen &&301 <Box className={classes.iconButtonStyle}><PgIconButton title={gettext('Minimize')} icon={<MinimizeDialogIcon className={classes.icon} />} size="xs" noBorder onClick={() => { setIsFullScreen(!isFullScreen); }} /></Box>302 }303 304 <Box marginLeft="auto"><PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={closeModal} /></Box>305 </Box>306 </DialogTitle>307 }308 <DialogContent height="100%">309 {useMemo(()=>{ return content(closeModal); }, [])}310 </DialogContent>311 </Dialog>312 );313}314ModalContainer.propTypes = {315 id: PropTypes.string,316 title: CustomPropTypes.children,317 content: PropTypes.func,318 fullScreen: PropTypes.bool,319 isFullWidth: PropTypes.bool,320 showFullScreen: PropTypes.bool,321 isResizeable: PropTypes.bool,322 dialogHeight: PropTypes.number,323 dialogWidth: PropTypes.number,324 onClose: PropTypes.func,325 minWidth: PropTypes.number,326 minHeight: PropTypes.number,327 showTitle: PropTypes.bool,328};329 