From 81f52a82c80885cb99da9369d376496bb5b4cfc4 Mon Sep 17 00:00:00 2001 From: Akshay Joshi Date: Thu, 28 Jul 2022 10:11:40 +0530 Subject: [PATCH] Port About dialog to React. Fixes #7567 --- docs/en_US/release_notes.rst | 1 + docs/en_US/release_notes_6_13.rst | 20 +++ web/pgadmin/about/__init__.py | 9 +- .../about/static/js/AboutComponent.jsx | 151 ++++++++++++++++++ web/pgadmin/about/static/js/about.js | 123 ++++++-------- web/pgadmin/about/templates/about/index.html | 47 ------ web/pgadmin/static/js/Theme/index.jsx | 2 + .../static/js/components/FormComponents.jsx | 6 +- .../static/js/helpers/ModalProvider.jsx | 16 +- 9 files changed, 242 insertions(+), 133 deletions(-) create mode 100644 docs/en_US/release_notes_6_13.rst create mode 100644 web/pgadmin/about/static/js/AboutComponent.jsx delete mode 100644 web/pgadmin/about/templates/about/index.html diff --git a/docs/en_US/release_notes.rst b/docs/en_US/release_notes.rst index 32204c1e8..907a7af26 100644 --- a/docs/en_US/release_notes.rst +++ b/docs/en_US/release_notes.rst @@ -11,6 +11,7 @@ notes for it. .. toctree:: :maxdepth: 1 + release_notes_6_13 release_notes_6_12 release_notes_6_11 release_notes_6_10 diff --git a/docs/en_US/release_notes_6_13.rst b/docs/en_US/release_notes_6_13.rst new file mode 100644 index 000000000..09b459bc8 --- /dev/null +++ b/docs/en_US/release_notes_6_13.rst @@ -0,0 +1,20 @@ +************ +Version 6.13 +************ + +Release date: 2022-08-25 + +This release contains a number of bug fixes and new features since the release of pgAdmin 4 v6.12. + +New features +************ + + +Housekeeping +************ + + | `Issue #7567 `_ - Port About dialog to React. + +Bug fixes +********* + diff --git a/web/pgadmin/about/__init__.py b/web/pgadmin/about/__init__.py index e520a1e80..f02c0cee5 100644 --- a/web/pgadmin/about/__init__.py +++ b/web/pgadmin/about/__init__.py @@ -9,12 +9,13 @@ """A blueprint module implementing the about box.""" -from flask import Response, render_template, url_for, request +from flask import Response, render_template, request from flask_babel import gettext from flask_security import current_user, login_required from pgadmin.utils import PgAdminModule from pgadmin.utils.menu import MenuItem from pgadmin.utils.constants import MIMETYPE_APP_JS +from pgadmin.utils.ajax import make_json_response import config import httpagentparser from pgadmin.model import User @@ -66,6 +67,7 @@ def index(): info['os_details'] = os_details info['config_db'] = config.SQLITE_PATH info['log_file'] = config.LOG_FILE + info['version'] = config.APP_VERSION if config.SERVER_MODE: info['app_mode'] = gettext('Server') @@ -98,8 +100,9 @@ def index(): info['settings'] = settings - return render_template( - MODULE_NAME + '/index.html', info=info, _=gettext + return make_json_response( + data=info, + status=200 ) diff --git a/web/pgadmin/about/static/js/AboutComponent.jsx b/web/pgadmin/about/static/js/AboutComponent.jsx new file mode 100644 index 000000000..724e3c5f7 --- /dev/null +++ b/web/pgadmin/about/static/js/AboutComponent.jsx @@ -0,0 +1,151 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import url_for from 'sources/url_for'; +import React, { useEffect, useState, useRef } from 'react'; +import { Box, Grid, InputLabel } from '@material-ui/core'; +import PropTypes from 'prop-types'; +import { DefaultButton } from '../../../static/js/components/Buttons'; +import { makeStyles } from '@material-ui/styles'; +import { InputText } from '../../../static/js/components/FormComponents'; +import getApiInstance from '../../../static/js/api_instance'; +import { copyToClipboard } from '../../../static/js/clipboard'; +import Notify from '../../../static/js/helpers/Notifier'; +import { useDelayedCaller } from '../../../static/js/custom_hooks'; + + +const useStyles = makeStyles((theme)=>({ + container: { + padding: '16px', + height: '100%', + display: 'flex', + flexDirection: 'column', + }, + copyBtn: { + marginRight: '1px', + float: 'right', + borderColor: theme.otherVars.borderColor, + fontSize: '13px', + }, +})); + +export default function AboutComponent() { + const classes = useStyles(); + const containerRef = useRef(); + const [aboutData, setAboutData] = useState([]); + const [copyText, setCopyText] = useState(gettext('Copy')); + const revertCopiedText = useDelayedCaller(()=>{ + setCopyText(gettext('Copy')); + }); + + useEffect(() => { + const about_url = url_for('about.index'); + const api = getApiInstance(); + + api.get(about_url).then((res)=>{ + setAboutData(res.data.data); + }).catch((err)=>{ + Notify.error(err); + }); + }, []); + + return ( + + + + {gettext('Version')} + + + {aboutData.version} + + + + + {gettext('Application Mode')} + + + {aboutData.app_mode} + + + + + {gettext('Current User')} + + + {aboutData.current_user} + + + { aboutData.nwjs && + + + {gettext('NW.js Version')} + + + {aboutData.nwjs} + + + } + + + {gettext('Browser')} + + + {aboutData.browser_details} + + + + + {gettext('Operating System')} + + + {aboutData.os_details} + + + + + {gettext('pgAdmin Database File')} + + + {aboutData.config_db} + + + + + {gettext('Log File')} + + + {aboutData.log_file} + + + { (aboutData.app_mode == 'Desktop' || (aboutData.app_mode == 'Server' && aboutData.admin)) && + <> + + + {gettext('Server Configuration')} + { + copyToClipboard(aboutData.settings); + setCopyText(gettext('Copied!')); + revertCopiedText(1500); + }}>{copyText} + + + + + + + } + + ); +} + +AboutComponent.propTypes = { + closeModal: PropTypes.func +}; \ No newline at end of file diff --git a/web/pgadmin/about/static/js/about.js b/web/pgadmin/about/static/js/about.js index 7c90618d3..0fc6814e9 100644 --- a/web/pgadmin/about/static/js/about.js +++ b/web/pgadmin/about/static/js/about.js @@ -7,85 +7,56 @@ // ////////////////////////////////////////////////////////////// -define( - ['jquery', 'alertify', 'sources/pgadmin', 'sources/gettext', - 'sources/url_for','sources/utils','pgadmin.user_management.current_user', - ], - function($, alertify, pgAdmin, gettext, url_for, commonUtils, current_user) { - pgAdmin = pgAdmin || window.pgAdmin || {}; +import React from 'react'; +import gettext from 'sources/gettext'; +import Notify from '../../../static/js/helpers/Notifier'; +import pgAdmin from 'sources/pgadmin'; +import pgBrowser from 'top/browser/static/js/browser'; +import AboutComponent from './AboutComponent'; +import current_user from 'pgadmin.user_management.current_user'; - /* Return back, this has been called more than once */ - if (pgAdmin.About) +class About { + static instance; + + static getInstance(...args) { + if (!About.instance) { + About.instance = new About(...args); + } + return About.instance; + } + + constructor(pgAdmin, pgBrowser) { + this.pgAdmin = pgAdmin; + this.pgBrowser = pgBrowser; + } + + init() { + if (this.initialized) return; + this.initialized = true; + } - pgAdmin.About = { - about_show: function() { - if (!alertify.aboutDialog) { - alertify.dialog('aboutDialog', function factory() { - return { - main: function(title, message) { - this.set('title', title); - this.message = message; - }, - setup: function() { - return { - buttons:[{ text: gettext('OK'), key: 27, - className: 'btn btn-primary fa fa-lg fa-check pg-alertify-button' }], - options: { - modal: false, - resizable: true, - maximizable: true, - pinnable: false, - closableByDimmer: false, - }, - }; - }, - build: function() { - alertify.pgDialogBuild.apply(this); - }, - hooks:{ - onshow:function(){ - var self = this; - var container = $(this.elements.footer).find('button:not([disabled])'); - commonUtils.findAndSetFocus(container); - $('#copy_textarea').on('click', function(){ - //Copy the server configuration details - let textarea = document.getElementById('about-textarea'); - textarea.select(); - document.execCommand('copy'); - $('#copy_textarea').text('Copied'); - }); + // This is a callback function to show about dialog. + about_show() { + let dlgHeight = 470, + dlgWidth = 750; - $(this.elements.resizeHandle).on('click', function(){ - // Set the height of the Textarea - var height = self.elements.dialog.scrollHeight - 300; - if (height < 0) - height = self.elements.dialog.scrollHeight - 150; - $('#about-textarea').css({'height':height}); - }); - }, - }, - prepare:function() { - this.setContent(this.message); - }, - }; - }); - } + if(!current_user.is_admin && pgAdmin.server_mode) { + dlgWidth = pgAdmin.Browser.stdW.md; + dlgHeight = 300; + } - $.get(url_for('about.index'), - function(data) { - if(!current_user.is_admin && pgAdmin.server_mode){ - alertify.aboutDialog( - gettext('About %s', pgAdmin.Browser.utils.app_name), data - ).resizeTo(pgAdmin.Browser.stdW.md, 300); - }else{ - alertify.aboutDialog( - gettext('About %s', pgAdmin.Browser.utils.app_name), data - ).resizeTo(750, 470); - } - }); - }, - }; + // Render About component + Notify.showModal(gettext('About %s', pgAdmin.Browser.utils.app_name), () => { + return ; + }, { isFullScreen: false, isResizeable: true, showFullScreen: true, + isFullWidth: true, dialogWidth: dlgWidth, dialogHeight: dlgHeight, minHeight: dlgHeight + }); + } +} - return pgAdmin.About; - }); +pgAdmin.About = About.getInstance(pgAdmin, pgBrowser); + +module.exports = { + About: About, +}; \ No newline at end of file diff --git a/web/pgadmin/about/templates/about/index.html b/web/pgadmin/about/templates/about/index.html deleted file mode 100644 index f63ab9675..000000000 --- a/web/pgadmin/about/templates/about/index.html +++ /dev/null @@ -1,47 +0,0 @@ -
-
-
{{ _('Version') }}
-
{{ config.APP_VERSION }}
-
-
-
{{ _('Application Mode') }}
-
{{ info.app_mode }}
-
-
-
{{ _('Current User') }}
-
{{ info.current_user }}
-
- {% if info.nwjs %} -
-
{{ _('NW.js Version') }}
-
{{ info.nwjs }}
-
- {% endif %} -
-
{{ _('Browser') }}
-
{{ info.browser_details }}
-
-
-
{{ _('Operating System') }}
-
{{ info.os_details }}
-
-
-
{{ _('pgAdmin Database File') }}
-
{{ info.config_db }}
-
-
-
{{ _('Log File') }}
-
{{ info.log_file }}
-
- {%if (info.app_mode == 'Desktop') or (info.app_mode == 'Server' and info.admin)%} -
-
{{ _('Server Configuration') }}
-
-
-
- - -
-
- {% endif %} -
diff --git a/web/pgadmin/static/js/Theme/index.jsx b/web/pgadmin/static/js/Theme/index.jsx index 4b966f881..5bc215c1b 100644 --- a/web/pgadmin/static/js/Theme/index.jsx +++ b/web/pgadmin/static/js/Theme/index.jsx @@ -122,6 +122,8 @@ basicSettings = createMuiTheme(basicSettings, { inputMultiline: { padding: basicSettings.spacing(0.75, 1.5), resize: 'vertical', + height: '100%', + boxSizing: 'border-box', }, adornedEnd: { paddingRight: basicSettings.spacing(0.75), diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx index 2dcd79d69..6b72869df 100644 --- a/web/pgadmin/static/js/components/FormComponents.jsx +++ b/web/pgadmin/static/js/components/FormComponents.jsx @@ -326,7 +326,7 @@ FormInputDateTimePicker.propTypes = { /* Use forwardRef to pass ref prop to OutlinedInput */ export const InputText = forwardRef(({ - cid, helpid, readonly, disabled, value, onChange, controlProps, type, size, ...props }, ref) => { + cid, helpid, readonly, disabled, value, onChange, controlProps, type, size, inputStyle, ...props }, ref) => { const maxlength = typeof(controlProps?.maxLength) != 'undefined' ? controlProps.maxLength : 255; @@ -370,7 +370,8 @@ export const InputText = forwardRef(({ id: cid, maxLength: controlProps?.multiline ? null : maxlength, 'aria-describedby': helpid, - ...(type ? { pattern: !_.isUndefined(controlProps) && !_.isUndefined(controlProps.pattern) ? controlProps.pattern : patterns[type] } : {}) + ...(type ? { pattern: !_.isUndefined(controlProps) && !_.isUndefined(controlProps.pattern) ? controlProps.pattern : patterns[type] } : {}), + style: inputStyle || {} }} readOnly={Boolean(readonly)} disabled={Boolean(disabled)} @@ -399,6 +400,7 @@ InputText.propTypes = { controlProps: PropTypes.object, type: PropTypes.string, size: PropTypes.string, + inputStyle: PropTypes.object }; export function FormInputText({ hasError, required, label, className, helpMessage, testcid, ...props }) { diff --git a/web/pgadmin/static/js/helpers/ModalProvider.jsx b/web/pgadmin/static/js/helpers/ModalProvider.jsx index 6c1f41b2d..50347cbf5 100644 --- a/web/pgadmin/static/js/helpers/ModalProvider.jsx +++ b/web/pgadmin/static/js/helpers/ModalProvider.jsx @@ -155,7 +155,7 @@ const dialogStyle = makeStyles((theme) => ({ } })); -function PaperComponent(props) { +function PaperComponent({minHeight, minWidth, ...props}) { let classes = dialogStyle(); let [dialogPosition, setDialogPosition] = useState(null); let resizeable = props.isresizeable == 'true' ? true : false; @@ -182,8 +182,10 @@ function PaperComponent(props) { ...(props.width && { width: props.width }), ...(props.height && { height: props.height }), }} - {...(props.width && { minWidth: MIN_WIDTH })} - {...(props.height && { minHeight: MIN_HEIGHT })} + minWidth = { minWidth || MIN_WIDTH } + minHeight = { minHeight || MIN_HEIGHT } + // {...(props.width && { minWidth: MIN_WIDTH })} + // {...(props.height && { minHeight: MIN_HEIGHT })} bounds="window" enableResizing={setEnableResizing()} position={setConditionalPosition()} @@ -215,6 +217,8 @@ PaperComponent.propTypes = { isresizeable: PropTypes.string, width: PropTypes.number, height: PropTypes.number, + minWidth: PropTypes.number, + minHeight: PropTypes.number, }; export const useModalStyles = makeStyles((theme) => ({ @@ -254,7 +258,7 @@ export const useModalStyles = makeStyles((theme) => ({ } })); -function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false }) { +function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false, minHeight = MIN_HEIGHT, minWidth = MIN_WIDTH }) { let useModalRef = useModal(); const classes = useModalStyles(); let closeModal = (_e, reason) => { @@ -270,7 +274,7 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose open={true} onClose={closeModal} PaperComponent={PaperComponent} - PaperProps={{ 'isfullscreen': isfullScreen.toString(), 'isresizeable': isResizeable.toString(), width: dialogWidth, height: dialogHeight }} + PaperProps={{ 'isfullscreen': isfullScreen.toString(), 'isresizeable': isResizeable.toString(), width: dialogWidth, height: dialogHeight, minHeight: minHeight, minWidth: minWidth }} fullScreen={isfullScreen} fullWidth={isFullWidth} disableBackdropClick @@ -309,4 +313,6 @@ ModalContainer.propTypes = { dialogHeight: PropTypes.number, dialogWidth: PropTypes.number, onClose: PropTypes.func, + minWidth: PropTypes.number, + minHeight: PropTypes.number, };