mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
1) Port the file/storage manager to React. Fixes #7313
2) Allow users to delete files/folders from the storage manager. Fixes #4607 3) Allow users to search within the file/storage manager. Fixes #7389 4) Fixed an issue where new folders cannot be created in the save dialog. Fixes #7524
This commit is contained in:
committed by
Akshay Joshi
parent
4585597388
commit
4808df5e95
@@ -25,11 +25,11 @@ import _ from 'lodash';
|
||||
import gettext from 'sources/gettext';
|
||||
import { SCHEMA_STATE_ACTIONS, StateUtilsContext } from '.';
|
||||
import FormView, { getFieldMetaData } from './FormView';
|
||||
import { confirmDeleteRow } from '../helpers/legacyConnector';
|
||||
import CustomPropTypes from 'sources/custom_prop_types';
|
||||
import { evalFunc } from 'sources/utils';
|
||||
import { DepListenerContext } from './DepListener';
|
||||
import { useIsMounted } from '../custom_hooks';
|
||||
import Notify from '../helpers/Notifier';
|
||||
|
||||
const useStyles = makeStyles((theme)=>({
|
||||
grid: {
|
||||
@@ -303,15 +303,21 @@ export default function DataGridView({
|
||||
return (
|
||||
<PgIconButton data-test="delete-row" title={gettext('Delete row')} icon={<DeleteRoundedIcon fontSize="small" />}
|
||||
onClick={()=>{
|
||||
confirmDeleteRow(()=>{
|
||||
/* Get the changes on dependent fields as well */
|
||||
dataDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.DELETE_ROW,
|
||||
path: accessPath,
|
||||
value: row.index,
|
||||
});
|
||||
|
||||
}, ()=>{/*This is intentional (SonarQube)*/}, props.customDeleteTitle, props.customDeleteMsg);
|
||||
Notify.confirm(
|
||||
props.customDeleteTitle || gettext('Delete Row'),
|
||||
props.customDeleteMsg || gettext('Are you sure you wish to delete this row?'),
|
||||
function() {
|
||||
dataDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.DELETE_ROW,
|
||||
path: accessPath,
|
||||
value: row.index,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
function() {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}} className={classes.gridRowButton} disabled={!canDeleteRow} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,6 +125,9 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
},
|
||||
adornedEnd: {
|
||||
paddingRight: basicSettings.spacing(0.75),
|
||||
},
|
||||
marginDense: {
|
||||
height: '28px',
|
||||
}
|
||||
},
|
||||
MuiAccordion: {
|
||||
|
||||
@@ -2835,106 +2835,6 @@ define([
|
||||
].join('\n')),
|
||||
});
|
||||
|
||||
/*
|
||||
* Input File Control: This control is used with Storage Manager Dialog,
|
||||
* It allows user to perform following operations:
|
||||
* - Select File
|
||||
* - Select Folder
|
||||
* - Create File
|
||||
* - Opening Storage Manager Dialog itself.
|
||||
*/
|
||||
Backform.FileControl = Backform.InputControl.extend({
|
||||
defaults: {
|
||||
type: 'text',
|
||||
label: '',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
maxlength: 255,
|
||||
extraClasses: [],
|
||||
dialog_title: '',
|
||||
btn_primary: '',
|
||||
helpMessage: null,
|
||||
dialog_type: 'select_file',
|
||||
},
|
||||
initialize: function() {
|
||||
Backform.InputControl.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
template: _.template([
|
||||
'<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>',
|
||||
'<div class="<%=Backform.controlsClassName%>">',
|
||||
'<div class="input-group">',
|
||||
'<input type="<%=type%>" id="<%=cId%>" class="form-control <%=extraClasses.join(\' \')%>" name="<%=name%>" min="<%=min%>" max="<%=max%>"maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=readonly ? "readonly aria-readonly=true" : ""%> <%=required ? "required" : ""%> />',
|
||||
'<div class="input-group-append">',
|
||||
'<button class="btn btn-primary-icon fa fa-ellipsis-h select_item" <%=disabled ? "disabled" : ""%> <%=readonly ? "disabled" : ""%> aria-hidden="true" aria-label="' + gettext('Select file') + '" title="' + gettext('Select file') + '"></button>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'<% if (helpMessage && helpMessage.length) { %>',
|
||||
'<span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
||||
'<% } %>',
|
||||
'</div>',
|
||||
].join('\n')),
|
||||
events: function() {
|
||||
// Inherit all default events of InputControl
|
||||
return _.extend({}, Backform.InputControl.prototype.events, {
|
||||
'click .select_item': 'onSelect',
|
||||
});
|
||||
},
|
||||
onSelect: function() {
|
||||
var dialog_type = this.field.get('dialog_type'),
|
||||
supp_types = this.field.get('supp_types'),
|
||||
btn_primary = this.field.get('btn_primary'),
|
||||
dialog_title = this.field.get('dialog_title'),
|
||||
params = {
|
||||
supported_types: supp_types,
|
||||
dialog_type: dialog_type,
|
||||
dialog_title: dialog_title,
|
||||
btn_primary: btn_primary,
|
||||
};
|
||||
|
||||
pgAdmin.FileManager.init();
|
||||
pgAdmin.FileManager.show_dialog(params);
|
||||
// Listen click events of Storage Manager dialog buttons
|
||||
this.listen_file_dlg_events();
|
||||
},
|
||||
storage_dlg_hander: function(value) {
|
||||
var attrArr = this.field.get('name').split('.'),
|
||||
name = attrArr.shift();
|
||||
|
||||
this.remove_file_dlg_event_listeners();
|
||||
|
||||
// Set selected value into the model
|
||||
this.model.set(name, decodeURI(value));
|
||||
this.$el.find('input[type=text]').focus();
|
||||
},
|
||||
storage_close_dlg_hander: function() {
|
||||
this.remove_file_dlg_event_listeners();
|
||||
},
|
||||
listen_file_dlg_events: function() {
|
||||
pgAdmin.Browser.Events.on('pgadmin-storage:finish_btn:' + this.field.get('dialog_type'), this.storage_dlg_hander, this);
|
||||
pgAdmin.Browser.Events.on('pgadmin-storage:cancel_btn:' + this.field.get('dialog_type'), this.storage_close_dlg_hander, this);
|
||||
},
|
||||
remove_file_dlg_event_listeners: function() {
|
||||
pgAdmin.Browser.Events.off('pgadmin-storage:finish_btn:' + this.field.get('dialog_type'), this.storage_dlg_hander, this);
|
||||
pgAdmin.Browser.Events.off('pgadmin-storage:cancel_btn:' + this.field.get('dialog_type'), this.storage_close_dlg_hander, this);
|
||||
},
|
||||
clearInvalid: function() {
|
||||
Backform.InputControl.prototype.clearInvalid.apply(this, arguments);
|
||||
this.$el.removeClass('pgadmin-file-has-error');
|
||||
return this;
|
||||
},
|
||||
updateInvalid: function() {
|
||||
Backform.InputControl.prototype.updateInvalid.apply(this, arguments);
|
||||
// Introduce a new class to fix the error icon placement on the control
|
||||
this.$el.addClass('pgadmin-file-has-error');
|
||||
},
|
||||
disable_button: function() {
|
||||
this.$el.find('button.select_item').attr('disabled', 'disabled');
|
||||
},
|
||||
enable_button: function() {
|
||||
this.$el.find('button.select_item').removeAttr('disabled');
|
||||
},
|
||||
});
|
||||
|
||||
Backform.DatetimepickerControl =
|
||||
Backform.InputControl.extend({
|
||||
defaults: {
|
||||
|
||||
@@ -12,10 +12,10 @@ import Notify from '../../static/js/helpers/Notifier';
|
||||
define([
|
||||
'sources/gettext', 'underscore', 'jquery', 'backbone', 'backform', 'backgrid', 'alertify',
|
||||
'moment', 'bignumber', 'codemirror', 'sources/utils', 'sources/keyboard_shortcuts', 'sources/select2/configure_show_on_scroll',
|
||||
'sources/window', 'sources/url_for', 'bootstrap.datetimepicker', 'backgrid.filter', 'bootstrap.toggle',
|
||||
'sources/window', 'bootstrap.datetimepicker', 'backgrid.filter', 'bootstrap.toggle',
|
||||
], function(
|
||||
gettext, _, $, Backbone, Backform, Backgrid, Alertify, moment, BigNumber, CodeMirror,
|
||||
commonUtils, keyboardShortcuts, configure_show_on_scroll, pgWindow, url_for
|
||||
commonUtils, keyboardShortcuts, configure_show_on_scroll, pgWindow
|
||||
) {
|
||||
/*
|
||||
* Add mechanism in backgrid to render different types of cells in
|
||||
@@ -2314,125 +2314,5 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
Backgrid.Extension.SelectFileCell = Backgrid.Cell.extend({
|
||||
/** @property */
|
||||
className: 'file-cell',
|
||||
defaults: {
|
||||
supported_types: ['*'],
|
||||
dialog_type: 'select_file',
|
||||
dialog_title: gettext('Select file'),
|
||||
type: 'text',
|
||||
value: '',
|
||||
placeholder: gettext('Select file...'),
|
||||
disabled: false,
|
||||
browse_btn_label: gettext('Select file'),
|
||||
check_btn_label: gettext('Validate file'),
|
||||
browse_btn_visible: true,
|
||||
validate_btn_visible: true,
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
Backgrid.Cell.prototype.initialize.apply(this, arguments);
|
||||
this.data = _.extend(this.defaults, this.column.toJSON());
|
||||
},
|
||||
template: _.template([
|
||||
'<div class="input-group">',
|
||||
'<input type="<%=type%>" id="<%=cId%>" class="form-control" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> />',
|
||||
'<% if (browse_btn_visible) { %>',
|
||||
'<div class="input-group-append">',
|
||||
'<button class="btn btn-primary-icon fa fa-ellipsis-h select_item" <%=disabled ? "disabled" : ""%> aria-hidden="true" aria-label=<%=browse_btn_label%> title=<%=browse_btn_label%>></button>',
|
||||
'</div>',
|
||||
'<% } %>',
|
||||
'<% if (validate_btn_visible) { %>',
|
||||
'<div class="input-group-append">',
|
||||
'<button class="btn btn-primary-icon fa fa-clipboard-check validate_item" <%=disabled ? "disabled" : ""%> <%=(value=="" || value==null) ? "disabled" : ""%> aria-hidden="true" aria-label=<%=check_btn_label%> title=<%=check_btn_label%>></button>',
|
||||
'</div>',
|
||||
'<% } %>',
|
||||
'</div>',
|
||||
].join('\n')),
|
||||
events: {
|
||||
'change input': 'onChange',
|
||||
'click .select_item': 'onSelect',
|
||||
'click .validate_item': 'onValidate',
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.empty();
|
||||
this.data = _.extend(this.data, {value: this.model.get(this.column.get('name'))});
|
||||
// Adding unique id
|
||||
this.data['cId'] = _.uniqueId('pgC_');
|
||||
this.$el.append(this.template(this.data));
|
||||
|
||||
this.$input = this.$el.find('input');
|
||||
this.delegateEvents();
|
||||
|
||||
return this;
|
||||
},
|
||||
onChange: function() {
|
||||
var model = this.model,
|
||||
column = this.column,
|
||||
val = this.formatter.toRaw(this.$input.prop('value'), model);
|
||||
|
||||
model.set(column.get('name'), val);
|
||||
},
|
||||
onSelect: function() {
|
||||
let self = this;
|
||||
|
||||
var params = {
|
||||
supported_types: self.data.supported_types,
|
||||
dialog_type: self.data.dialog_type,
|
||||
dialog_title: self.data.dialog_title
|
||||
};
|
||||
|
||||
pgAdmin.FileManager.init();
|
||||
pgAdmin.FileManager.show_dialog(params);
|
||||
// Listen click events of Storage Manager dialog buttons
|
||||
this.listen_file_dlg_events();
|
||||
},
|
||||
storage_dlg_hander: function(value) {
|
||||
var attrArr = this.column.get('name').split('.'),
|
||||
name = attrArr.shift();
|
||||
|
||||
this.remove_file_dlg_event_listeners();
|
||||
|
||||
// Set selected value into the model
|
||||
this.model.set(name, decodeURI(value));
|
||||
},
|
||||
storage_close_dlg_hander: function() {
|
||||
this.remove_file_dlg_event_listeners();
|
||||
},
|
||||
listen_file_dlg_events: function() {
|
||||
pgAdmin.Browser.Events.on('pgadmin-storage:finish_btn:' + this.data.dialog_type, this.storage_dlg_hander, this);
|
||||
pgAdmin.Browser.Events.on('pgadmin-storage:cancel_btn:' + this.data.dialog_type, this.storage_close_dlg_hander, this);
|
||||
},
|
||||
remove_file_dlg_event_listeners: function() {
|
||||
pgAdmin.Browser.Events.off('pgadmin-storage:finish_btn:' + this.data.dialog_type, this.storage_dlg_hander, this);
|
||||
pgAdmin.Browser.Events.off('pgadmin-storage:cancel_btn:' + this.data.dialog_type, this.storage_close_dlg_hander, this);
|
||||
},
|
||||
onValidate: function() {
|
||||
var model = this.model,
|
||||
val = this.formatter.toRaw(this.$input.prop('value'), model);
|
||||
|
||||
if (_.isNull(val) || val.trim() === '') {
|
||||
Notify.alert(gettext('Validate Path'), gettext('Path should not be empty.'));
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: url_for('misc.validate_binary_path'),
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
'utility_path': val,
|
||||
}),
|
||||
})
|
||||
.done(function(res) {
|
||||
Notify.alert(gettext('Validate binary path'), gettext(res.data));
|
||||
})
|
||||
.fail(function(xhr, error) {
|
||||
Notify.pgNotifier(error, xhr, gettext('Failed to validate binary path.'));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return Backgrid;
|
||||
});
|
||||
|
||||
@@ -199,7 +199,7 @@ PgIconButton.propTypes = {
|
||||
export const PgButtonGroup = forwardRef(({children, ...props}, ref)=>{
|
||||
/* Tooltip does not work for disabled items */
|
||||
return (
|
||||
<ButtonGroup disableElevation innerRef={ref} {...props}>
|
||||
<ButtonGroup innerRef={ref} {...props}>
|
||||
{children}
|
||||
</ButtonGroup>
|
||||
);
|
||||
|
||||
@@ -35,13 +35,13 @@ import * as DateFns from 'date-fns';
|
||||
|
||||
import CodeMirror from './CodeMirror';
|
||||
import gettext from 'sources/gettext';
|
||||
import { showFileDialog } from '../helpers/legacyConnector';
|
||||
import _ from 'lodash';
|
||||
import { DefaultButton, PrimaryButton, PgIconButton } from './Buttons';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
import KeyboardShortcuts from './KeyboardShortcuts';
|
||||
import QueryThresholds from './QueryThresholds';
|
||||
import SelectThemes from './SelectThemes';
|
||||
import { showFileManager } from '../helpers/showFileManager';
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
@@ -326,11 +326,10 @@ FormInputDateTimePicker.propTypes = {
|
||||
|
||||
/* Use forwardRef to pass ref prop to OutlinedInput */
|
||||
export const InputText = forwardRef(({
|
||||
cid, helpid, readonly, disabled, value, onChange, controlProps, type, ...props }, ref) => {
|
||||
cid, helpid, readonly, disabled, value, onChange, controlProps, type, size, ...props }, ref) => {
|
||||
|
||||
const maxlength = typeof(controlProps?.maxLength) != 'undefined' ? controlProps.maxLength : 255;
|
||||
|
||||
const classes = useStyles();
|
||||
const patterns = {
|
||||
'numeric': '^-?[0-9]\\d*\\.?\\d*$',
|
||||
'int': '^-?[0-9]\\d*$',
|
||||
@@ -356,12 +355,17 @@ export const InputText = forwardRef(({
|
||||
finalValue = controlProps.formatter.fromRaw(finalValue);
|
||||
}
|
||||
|
||||
const filteredProps = _.pickBy(props, (_v, key)=>(
|
||||
/* When used in ButtonGroup, following props should be skipped */
|
||||
!['color', 'disableElevation', 'disableFocusRipple', 'disableRipple'].includes(key)
|
||||
));
|
||||
|
||||
return (
|
||||
<OutlinedInput
|
||||
ref={ref}
|
||||
color="primary"
|
||||
fullWidth
|
||||
className={classes.formInput}
|
||||
margin={size == 'small' ? 'dense' : 'none'}
|
||||
inputProps={{
|
||||
id: cid,
|
||||
maxLength: controlProps?.multiline ? null : maxlength,
|
||||
@@ -378,7 +382,7 @@ export const InputText = forwardRef(({
|
||||
...(controlProps?.onKeyDown && { onKeyDown: controlProps.onKeyDown })
|
||||
}
|
||||
{...controlProps}
|
||||
{...props}
|
||||
{...filteredProps}
|
||||
{...(['numeric', 'int'].indexOf(type) > -1 ? { type: 'tel' } : { type: type })}
|
||||
/>
|
||||
);
|
||||
@@ -394,6 +398,7 @@ InputText.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
controlProps: PropTypes.object,
|
||||
type: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
};
|
||||
|
||||
export function FormInputText({ hasError, required, label, className, helpMessage, testcid, ...props }) {
|
||||
@@ -412,7 +417,6 @@ FormInputText.propTypes = {
|
||||
testcid: PropTypes.string,
|
||||
};
|
||||
|
||||
/* Using the existing file dialog functions using showFileDialog */
|
||||
export function InputFileSelect({ controlProps, onChange, disabled, readonly, isvalidate = false, hideBrowseButton=false,validate, ...props }) {
|
||||
const inpRef = useRef();
|
||||
let textControlProps = {};
|
||||
@@ -420,15 +424,24 @@ export function InputFileSelect({ controlProps, onChange, disabled, readonly, is
|
||||
const {placeholder} = controlProps;
|
||||
textControlProps = {placeholder};
|
||||
}
|
||||
const onFileSelect = (value) => {
|
||||
onChange && onChange(decodeURI(value));
|
||||
inpRef.current.focus();
|
||||
const showFileDialog = ()=>{
|
||||
let params = {
|
||||
supported_types: controlProps.supportedTypes || [],
|
||||
dialog_type: controlProps.dialogType || 'select_file',
|
||||
dialog_title: controlProps.dialogTitle || '',
|
||||
btn_primary: controlProps.btnPrimary || '',
|
||||
};
|
||||
showFileManager(params, (fileName)=>{
|
||||
onChange && onChange(decodeURI(fileName));
|
||||
inpRef.current.focus();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<InputText ref={inpRef} disabled={disabled} readonly={readonly} onChange={onChange} controlProps={textControlProps} {...props} endAdornment={
|
||||
<>
|
||||
{!hideBrowseButton &&
|
||||
<IconButton onClick={() => showFileDialog(controlProps, onFileSelect)}
|
||||
<IconButton onClick={showFileDialog}
|
||||
disabled={disabled || readonly} aria-label={gettext('Select a file')}><FolderOpenRoundedIcon /></IconButton>
|
||||
}
|
||||
{isvalidate &&
|
||||
@@ -1184,6 +1197,9 @@ const useStylesFormFooter = makeStyles((theme) => ({
|
||||
message: {
|
||||
marginLeft: theme.spacing(0.5),
|
||||
},
|
||||
messageCenter: {
|
||||
margin: 'auto',
|
||||
},
|
||||
closeButton: {
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
@@ -1272,13 +1288,13 @@ FormInputSelectThemes.propTypes = {
|
||||
};
|
||||
|
||||
|
||||
export function NotifierMessage({ type = MESSAGE_TYPE.SUCCESS, message, closable = true, onClose = () => {/*This is intentional (SonarQube)*/ } }) {
|
||||
export function NotifierMessage({ type = MESSAGE_TYPE.SUCCESS, message, closable = true, showIcon=true, textCenter=false, onClose = () => {/*This is intentional (SonarQube)*/ } }) {
|
||||
const classes = useStylesFormFooter();
|
||||
|
||||
return (
|
||||
<Box className={clsx(classes.container, classes[`container${type}`])}>
|
||||
<FormIcon type={type} className={classes[`icon${type}`]} />
|
||||
<Box className={classes.message}>{HTMLReactParse(message || '')}</Box>
|
||||
{showIcon && <FormIcon type={type} className={classes[`icon${type}`]} />}
|
||||
<Box className={textCenter ? classes.messageCenter : classes.message}>{HTMLReactParse(message || '')}</Box>
|
||||
{closable && <IconButton className={clsx(classes.closeButton, classes[`icon${type}`])} onClick={onClose}>
|
||||
<FormIcon close={true} />
|
||||
</IconButton>}
|
||||
@@ -1290,6 +1306,8 @@ NotifierMessage.propTypes = {
|
||||
type: PropTypes.oneOf(Object.values(MESSAGE_TYPE)).isRequired,
|
||||
message: PropTypes.string,
|
||||
closable: PropTypes.bool,
|
||||
showIcon: PropTypes.bool,
|
||||
textCenter: PropTypes.bool,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
88
web/pgadmin/static/js/components/PgReactDataGrid.jsx
Normal file
88
web/pgadmin/static/js/components/PgReactDataGrid.jsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import ReactDataGrid from 'react-data-grid';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import clsx from 'clsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
|
||||
const useStyles = makeStyles((theme)=>({
|
||||
root: {
|
||||
height: '100%',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.otherVars.qtDatagridBg,
|
||||
fontSize: '12px',
|
||||
border: 'none',
|
||||
'--rdg-selection-color': theme.palette.primary.main,
|
||||
'& .rdg-cell': {
|
||||
...theme.mixins.panelBorder.right,
|
||||
...theme.mixins.panelBorder.bottom,
|
||||
fontWeight: 'abc',
|
||||
'&[aria-colindex="1"]': {
|
||||
padding: 0,
|
||||
},
|
||||
'&[aria-selected=true]:not([role="columnheader"])': {
|
||||
outlineWidth: '0px',
|
||||
outlineOffset: '0px',
|
||||
}
|
||||
},
|
||||
'& .rdg-header-row .rdg-cell': {
|
||||
padding: 0,
|
||||
},
|
||||
'& .rdg-header-row': {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'& .rdg-row': {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
'&[aria-selected=true]': {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
color: theme.otherVars.qtDatagridSelectFg,
|
||||
},
|
||||
}
|
||||
},
|
||||
cellSelection: {
|
||||
'& .rdg-cell': {
|
||||
'&[aria-selected=true]:not([role="columnheader"])': {
|
||||
outlineWidth: '1px',
|
||||
outlineOffset: '-1px',
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
color: theme.otherVars.qtDatagridSelectFg,
|
||||
}
|
||||
},
|
||||
},
|
||||
hasSelectColumn: {
|
||||
'& .rdg-cell': {
|
||||
'&[aria-selected=true][aria-colindex="1"]': {
|
||||
outlineWidth: '2px',
|
||||
outlineOffset: '-2px',
|
||||
backgroundColor: theme.otherVars.qtDatagridBg,
|
||||
color: theme.palette.text.primary,
|
||||
}
|
||||
},
|
||||
'& .rdg-row[aria-selected=true] .rdg-cell:nth-child(1)': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastText,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
export default function PgReactDataGrid({gridRef, className, hasSelectColumn=true, ...props}) {
|
||||
const classes = useStyles();
|
||||
let finalClassName = [classes.root];
|
||||
hasSelectColumn && finalClassName.push(classes.hasSelectColumn);
|
||||
props.enableCellSelect && finalClassName.push(classes.cellSelection);
|
||||
finalClassName.push(className);
|
||||
return <ReactDataGrid
|
||||
ref={gridRef}
|
||||
className={clsx(finalClassName)}
|
||||
{...props}
|
||||
/>;
|
||||
}
|
||||
|
||||
PgReactDataGrid.propTypes = {
|
||||
gridRef: CustomPropTypes.ref,
|
||||
className: CustomPropTypes.className,
|
||||
hasSelectColumn: PropTypes.bool,
|
||||
enableCellSelect: PropTypes.bool,
|
||||
};
|
||||
@@ -22,7 +22,7 @@ import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
|
||||
import { Rnd } from 'react-rnd';
|
||||
import { ExpandDialogIcon, MinimizeDialogIcon } from '../components/ExternalIcon';
|
||||
|
||||
const ModalContext = React.createContext({});
|
||||
export const ModalContext = React.createContext({});
|
||||
const MIN_HEIGHT = 190;
|
||||
const MIN_WIDTH = 500;
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
/* This file will have wrappers and connectors used by React components to
|
||||
* re-use any existing non-react components.
|
||||
* These functions may not be needed once all are migrated
|
||||
*/
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import Notify from './Notifier';
|
||||
|
||||
export function confirmDeleteRow(onOK, onCancel, title, message) {
|
||||
Notify.confirm(
|
||||
title || gettext('Delete Row'),
|
||||
message || gettext('Are you sure you wish to delete this row?'),
|
||||
function() {
|
||||
onOK();
|
||||
return true;
|
||||
},
|
||||
function() {
|
||||
onCancel();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* Used by file select component to re-use existing logic */
|
||||
export function showFileDialog(dialogParams, onFileSelect) {
|
||||
let params = {
|
||||
supported_types: dialogParams.supportedTypes || [],
|
||||
dialog_type: dialogParams.dialogType || 'select_file',
|
||||
dialog_title: dialogParams.dialogTitle || '',
|
||||
btn_primary: dialogParams.btnPrimary || '',
|
||||
};
|
||||
pgAdmin.FileManager.init();
|
||||
pgAdmin.FileManager.show_dialog(params);
|
||||
|
||||
const onFileSelectClose = (value)=>{
|
||||
removeListeners();
|
||||
onFileSelect(value);
|
||||
};
|
||||
const onDialogClose = ()=>removeListeners();
|
||||
pgAdmin.Browser.Events.on('pgadmin-storage:finish_btn:' + params.dialog_type, onFileSelectClose);
|
||||
pgAdmin.Browser.Events.on('pgadmin-storage:cancel_btn:' + params.dialog_type, onDialogClose);
|
||||
|
||||
const removeListeners = ()=>{
|
||||
pgAdmin.Browser.Events.off('pgadmin-storage:finish_btn:' + params.dialog_type, onFileSelectClose);
|
||||
pgAdmin.Browser.Events.off('pgadmin-storage:cancel_btn:' + params.dialog_type, onDialogClose);
|
||||
};
|
||||
}
|
||||
|
||||
export function onPgadminEvent(eventName, handler) {
|
||||
pgAdmin.Browser.Events.on(eventName, handler);
|
||||
}
|
||||
|
||||
export function offPgadminEvent(eventName, handler) {
|
||||
pgAdmin.Browser.Events.off(eventName, handler);
|
||||
}
|
||||
6
web/pgadmin/static/js/helpers/showFileManager.js
Normal file
6
web/pgadmin/static/js/helpers/showFileManager.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import 'pgadmin.tools.file_manager';
|
||||
|
||||
export function showFileManager(...args) {
|
||||
pgAdmin.Tools.FileManager.show(...args);
|
||||
}
|
||||
@@ -14,8 +14,6 @@ import pgAdmin from 'sources/pgadmin';
|
||||
import { FileType } from 'react-aspen';
|
||||
import { TreeNode } from './tree_nodes';
|
||||
|
||||
import { isValidData } from 'sources/utils';
|
||||
|
||||
function manageTreeEvents(event, eventName, item) {
|
||||
let d = item ? item._metadata.data : [];
|
||||
let node_metadata = item ? item._metadata : {};
|
||||
@@ -594,6 +592,6 @@ export function findInTree(rootNode, path) {
|
||||
})(rootNode);
|
||||
}
|
||||
|
||||
let isValidTreeNodeData = isValidData;
|
||||
let isValidTreeNodeData = (data) => (!_.isEmpty(data));
|
||||
|
||||
export { isValidTreeNodeData };
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import _ from 'underscore';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import gettext from 'sources/gettext';
|
||||
import 'wcdocker';
|
||||
@@ -115,14 +115,6 @@ export function findAndSetFocus(container) {
|
||||
}, 200);
|
||||
}
|
||||
|
||||
let isValidData = (data) => (!_.isUndefined(data) && !_.isNull(data));
|
||||
let isFunction = (fn) => (_.isFunction(fn));
|
||||
let isString = (str) => (_.isString(str));
|
||||
|
||||
export {
|
||||
isValidData, isFunction, isString,
|
||||
};
|
||||
|
||||
export function getEpoch(inp_date) {
|
||||
let date_obj = inp_date ? inp_date : new Date();
|
||||
return parseInt(date_obj.getTime()/1000);
|
||||
@@ -456,6 +448,10 @@ export function getBrowser() {
|
||||
tem=/\brv[ :]+(\d+)/g.exec(ua) || [];
|
||||
return {name:'IE', version:(tem[1]||'')};
|
||||
}
|
||||
if(ua.startsWith('Nwjs')) {
|
||||
let nwjs = ua.split('-')[0]?.split(':');
|
||||
return {name:nwjs[0], version: nwjs[1]};
|
||||
}
|
||||
|
||||
if(M[1]==='Chrome') {
|
||||
tem=ua.match(/\bOPR|Edge\/(\d+)/);
|
||||
@@ -480,3 +476,21 @@ export function checkTrojanSource(content, isPasteEvent) {
|
||||
Notify.alert(gettext('Trojan Source Warning'), msg);
|
||||
}
|
||||
}
|
||||
|
||||
export function downloadBlob(blob, fileName) {
|
||||
let urlCreator = window.URL || window.webkitURL,
|
||||
downloadUrl = urlCreator.createObjectURL(blob),
|
||||
link = document.createElement('a');
|
||||
|
||||
document.body.appendChild(link);
|
||||
|
||||
if (getBrowser() === 'IE' && window.navigator.msSaveBlob) {
|
||||
// IE10+ : (has Blob, but not a[download] or URL)
|
||||
window.navigator.msSaveBlob(blob, fileName);
|
||||
} else {
|
||||
link.setAttribute('href', downloadUrl);
|
||||
link.setAttribute('download', fileName);
|
||||
link.click();
|
||||
}
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user