mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Add support for nested-fieldset control.
This commit is contained in:
committed by
Akshay Joshi
parent
621426ac55
commit
48176ea986
111
web/pgadmin/static/js/SchemaView/FieldSetView.jsx
Normal file
111
web/pgadmin/static/js/SchemaView/FieldSetView.jsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// pgAdmin 4 - PostgreSQL Tools
|
||||||
|
//
|
||||||
|
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||||
|
// This software is released under the PostgreSQL Licence
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import React, { useContext, useEffect, useMemo } from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { MappedFormControl } from './MappedControl';
|
||||||
|
import { SCHEMA_STATE_ACTIONS } from '.';
|
||||||
|
import { evalFunc } from 'sources/utils';
|
||||||
|
import CustomPropTypes from '../custom_prop_types';
|
||||||
|
import { DepListenerContext } from './DepListener';
|
||||||
|
import { getFieldMetaData } from './FormView';
|
||||||
|
import FieldSet from '../components/FieldSet';
|
||||||
|
|
||||||
|
export default function FieldSetView({
|
||||||
|
value, formErr, schema={}, viewHelperProps, accessPath, dataDispatch, controlClassName, isDataGridForm=false, label}) {
|
||||||
|
const depListener = useContext(DepListenerContext);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
/* Calculate the fields which depends on the current field */
|
||||||
|
if(!isDataGridForm) {
|
||||||
|
schema.fields.forEach((field)=>{
|
||||||
|
/* Self change is also dep change */
|
||||||
|
if(field.depChange || field.deferredDepChange) {
|
||||||
|
depListener.addDepListener(accessPath.concat(field.id), accessPath.concat(field.id), field.depChange, field.deferredDepChange);
|
||||||
|
}
|
||||||
|
(evalFunc(null, field.deps) || []).forEach((dep)=>{
|
||||||
|
let source = accessPath.concat(dep);
|
||||||
|
if(_.isArray(dep)) {
|
||||||
|
source = dep;
|
||||||
|
}
|
||||||
|
if(field.depChange) {
|
||||||
|
depListener.addDepListener(source, accessPath.concat(field.id), field.depChange);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
let viewFields = [];
|
||||||
|
/* Prepare the array of components based on the types */
|
||||||
|
schema.fields.forEach((field)=>{
|
||||||
|
let {visible, disabled, readonly, modeSuppoted} =
|
||||||
|
getFieldMetaData(field, schema, value, viewHelperProps);
|
||||||
|
|
||||||
|
if(modeSuppoted) {
|
||||||
|
/* Its a form control */
|
||||||
|
const hasError = field.id == formErr.name;
|
||||||
|
/* When there is a change, the dependent values can change
|
||||||
|
* lets pass the new changes to dependent and get the new values
|
||||||
|
* from there as well.
|
||||||
|
*/
|
||||||
|
viewFields.push(
|
||||||
|
useMemo(()=><MappedFormControl
|
||||||
|
state={value}
|
||||||
|
key={field.id}
|
||||||
|
viewHelperProps={viewHelperProps}
|
||||||
|
name={field.id}
|
||||||
|
value={value[field.id]}
|
||||||
|
{...field}
|
||||||
|
readonly={readonly}
|
||||||
|
disabled={disabled}
|
||||||
|
visible={visible}
|
||||||
|
onChange={(value)=>{
|
||||||
|
/* Get the changes on dependent fields as well */
|
||||||
|
dataDispatch({
|
||||||
|
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
|
||||||
|
path: accessPath.concat(field.id),
|
||||||
|
value: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
hasError={hasError}
|
||||||
|
className={controlClassName}
|
||||||
|
/>, [
|
||||||
|
value[field.id],
|
||||||
|
readonly,
|
||||||
|
disabled,
|
||||||
|
visible,
|
||||||
|
hasError,
|
||||||
|
controlClassName,
|
||||||
|
...(evalFunc(null, field.deps) || []).map((dep)=>value[dep]),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FieldSet title={label} className={controlClassName}>
|
||||||
|
{viewFields}
|
||||||
|
</FieldSet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldSetView.propTypes = {
|
||||||
|
value: PropTypes.any,
|
||||||
|
formErr: PropTypes.object,
|
||||||
|
schema: CustomPropTypes.schemaUI.isRequired,
|
||||||
|
viewHelperProps: PropTypes.object,
|
||||||
|
isDataGridForm: PropTypes.bool,
|
||||||
|
accessPath: PropTypes.array.isRequired,
|
||||||
|
dataDispatch: PropTypes.func,
|
||||||
|
controlClassName: CustomPropTypes.className,
|
||||||
|
label: PropTypes.string,
|
||||||
|
};
|
||||||
@@ -23,6 +23,7 @@ import { evalFunc } from 'sources/utils';
|
|||||||
import CustomPropTypes from '../custom_prop_types';
|
import CustomPropTypes from '../custom_prop_types';
|
||||||
import { useOnScreen } from '../custom_hooks';
|
import { useOnScreen } from '../custom_hooks';
|
||||||
import { DepListenerContext } from './DepListener';
|
import { DepListenerContext } from './DepListener';
|
||||||
|
import FieldSetView from './FieldSetView';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme)=>({
|
const useStyles = makeStyles((theme)=>({
|
||||||
fullSpace: {
|
fullSpace: {
|
||||||
@@ -70,6 +71,54 @@ SQLTab.propTypes = {
|
|||||||
getSQLValue: PropTypes.func.isRequired,
|
getSQLValue: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getFieldMetaData(field, schema, value, viewHelperProps) {
|
||||||
|
let retData = {
|
||||||
|
readonly: false,
|
||||||
|
disabled: false,
|
||||||
|
visible: true,
|
||||||
|
canAdd: true,
|
||||||
|
canEdit: true,
|
||||||
|
canDelete: true,
|
||||||
|
modeSuppoted: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(field.mode) {
|
||||||
|
retData.modeSuppoted = (field.mode.indexOf(viewHelperProps.mode) > -1);
|
||||||
|
}
|
||||||
|
if(!retData.modeSuppoted) {
|
||||||
|
return retData;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {visible, disabled, readonly} = field;
|
||||||
|
|
||||||
|
let verInLimit = (_.isUndefined(viewHelperProps.serverInfo) ? true :
|
||||||
|
((_.isUndefined(field.server_type) ? true :
|
||||||
|
(viewHelperProps.serverInfo.type in field.server_type)) &&
|
||||||
|
(_.isUndefined(field.min_version) ? true :
|
||||||
|
(viewHelperProps.serverInfo.version >= field.min_version)) &&
|
||||||
|
(_.isUndefined(field.max_version) ? true :
|
||||||
|
(viewHelperProps.serverInfo.version <= field.max_version))));
|
||||||
|
|
||||||
|
let _readonly = viewHelperProps.inCatalog || (viewHelperProps.mode == 'properties');
|
||||||
|
if(!_readonly) {
|
||||||
|
_readonly = evalFunc(schema, readonly, value);
|
||||||
|
}
|
||||||
|
retData.readonly = _readonly;
|
||||||
|
|
||||||
|
let _visible = verInLimit;
|
||||||
|
_visible = _visible && evalFunc(schema, _.isUndefined(visible) ? true : visible, value);
|
||||||
|
retData.visible = Boolean(_visible);
|
||||||
|
|
||||||
|
retData.disabled = Boolean(evalFunc(schema, disabled, value));
|
||||||
|
|
||||||
|
let {canAdd, canEdit, canDelete } = field;
|
||||||
|
retData.canAdd = _.isUndefined(canAdd) ? true : evalFunc(schema, canAdd, value);
|
||||||
|
retData.canEdit = _.isUndefined(canEdit) ? true : evalFunc(schema, canEdit, value);
|
||||||
|
retData.canDelete = _.isUndefined(canDelete) ? true : evalFunc(schema, canDelete, value);
|
||||||
|
|
||||||
|
return retData;
|
||||||
|
}
|
||||||
|
|
||||||
/* The first component of schema view form */
|
/* The first component of schema view form */
|
||||||
export default function FormView({
|
export default function FormView({
|
||||||
value, formErr, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab,
|
value, formErr, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab,
|
||||||
@@ -85,8 +134,6 @@ export default function FormView({
|
|||||||
const depListener = useContext(DepListenerContext);
|
const depListener = useContext(DepListenerContext);
|
||||||
let groupLabels = {};
|
let groupLabels = {};
|
||||||
|
|
||||||
schema = schema || {fields: []};
|
|
||||||
|
|
||||||
let isOnScreen = useOnScreen(formRef);
|
let isOnScreen = useOnScreen(formRef);
|
||||||
if(isOnScreen) {
|
if(isOnScreen) {
|
||||||
/* Don't do it when the form is alredy visible */
|
/* Don't do it when the form is alredy visible */
|
||||||
@@ -121,36 +168,14 @@ export default function FormView({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/* Prepare the array of components based on the types */
|
/* Prepare the array of components based on the types */
|
||||||
schema.fields.forEach((f)=>{
|
schema.fields.forEach((field)=>{
|
||||||
let modeSuppoted = true;
|
let {visible, disabled, readonly, canAdd, canEdit, canDelete, modeSuppoted} =
|
||||||
if(f.mode) {
|
getFieldMetaData(field, schema, value, viewHelperProps);
|
||||||
modeSuppoted = (f.mode.indexOf(viewHelperProps.mode) > -1);
|
|
||||||
}
|
|
||||||
if(modeSuppoted) {
|
if(modeSuppoted) {
|
||||||
let {visible, disabled, group, readonly, ...field} = f;
|
let {group} = field;
|
||||||
group = groupLabels[group] || group || defaultTab;
|
group = groupLabels[group] || group || defaultTab;
|
||||||
|
|
||||||
let verInLimit = (_.isUndefined(viewHelperProps.serverInfo) ? true :
|
|
||||||
((_.isUndefined(field.server_type) ? true :
|
|
||||||
(viewHelperProps.serverInfo.type in field.server_type)) &&
|
|
||||||
(_.isUndefined(field.min_version) ? true :
|
|
||||||
(viewHelperProps.serverInfo.version >= field.min_version)) &&
|
|
||||||
(_.isUndefined(field.max_version) ? true :
|
|
||||||
(viewHelperProps.serverInfo.version <= field.max_version))));
|
|
||||||
|
|
||||||
let _readonly = viewHelperProps.inCatalog || (viewHelperProps.mode == 'properties');
|
|
||||||
if(!_readonly) {
|
|
||||||
_readonly = evalFunc(schema, readonly, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
visible = _.isUndefined(visible) ? true : visible;
|
|
||||||
let _visible = true;
|
|
||||||
_visible = evalFunc(schema, visible, value);
|
|
||||||
_visible = _visible && verInLimit;
|
|
||||||
|
|
||||||
disabled = evalFunc(schema, disabled, value);
|
|
||||||
|
|
||||||
|
|
||||||
if(!tabs[group]) tabs[group] = [];
|
if(!tabs[group]) tabs[group] = [];
|
||||||
|
|
||||||
/* Lets choose the path based on type */
|
/* Lets choose the path based on type */
|
||||||
@@ -161,10 +186,22 @@ export default function FormView({
|
|||||||
} else {
|
} else {
|
||||||
field.schema.top = schema;
|
field.schema.top = schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs[group].push(
|
tabs[group].push(
|
||||||
<FormView key={`nested${tabs[group].length}`} value={value} viewHelperProps={viewHelperProps} formErr={formErr}
|
<FormView key={`nested${tabs[group].length}`} value={value} viewHelperProps={viewHelperProps} formErr={formErr}
|
||||||
schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} {...field}/>
|
schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} isDataGridForm={isDataGridForm} {...field}/>
|
||||||
|
);
|
||||||
|
} else if(field.type === 'nested-fieldset') {
|
||||||
|
/* Pass on the top schema */
|
||||||
|
if(isNested) {
|
||||||
|
field.schema.top = schema.top;
|
||||||
|
} else {
|
||||||
|
field.schema.top = schema;
|
||||||
|
}
|
||||||
|
tabs[group].push(
|
||||||
|
<FieldSetView key={`nested${tabs[group].length}`} value={value} viewHelperProps={viewHelperProps} formErr={formErr}
|
||||||
|
schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} isDataGridForm={isDataGridForm}
|
||||||
|
controlClassName={classes.controlRow}
|
||||||
|
{...field} />
|
||||||
);
|
);
|
||||||
} else if(field.type === 'collection') {
|
} else if(field.type === 'collection') {
|
||||||
/* If its a collection, let data grid view handle it */
|
/* If its a collection, let data grid view handle it */
|
||||||
@@ -176,20 +213,16 @@ export default function FormView({
|
|||||||
field.schema.top = schema;
|
field.schema.top = schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Eval the params based on state */
|
depsMap.push(canAdd, canEdit, canDelete);
|
||||||
let {canAdd, canEdit, canDelete, ..._field} = field;
|
|
||||||
canAdd = evalFunc(schema, canAdd, value);
|
|
||||||
canEdit = evalFunc(schema, canAdd, value);
|
|
||||||
canDelete = evalFunc(schema, canAdd, value);
|
|
||||||
|
|
||||||
tabs[group].push(
|
tabs[group].push(
|
||||||
useMemo(()=><DataGridView key={_field.id} value={value[_field.id]} viewHelperProps={viewHelperProps} formErr={formErr}
|
useMemo(()=><DataGridView key={field.id} value={value[field.id]} viewHelperProps={viewHelperProps} formErr={formErr}
|
||||||
schema={_field.schema} accessPath={accessPath.concat(_field.id)} dataDispatch={dataDispatch} containerClassName={classes.controlRow}
|
schema={field.schema} accessPath={accessPath.concat(field.id)} dataDispatch={dataDispatch} containerClassName={classes.controlRow}
|
||||||
canAdd={canAdd} canEdit={canEdit} canDelete={canDelete} {..._field}/>, depsMap)
|
{...field} canAdd={canAdd} canEdit={canEdit} canDelete={canDelete}/>, depsMap)
|
||||||
);
|
);
|
||||||
} else if(field.type === 'group') {
|
} else if(field.type === 'group') {
|
||||||
groupLabels[field.id] = field.label;
|
groupLabels[field.id] = field.label;
|
||||||
if(!_visible) {
|
if(!visible) {
|
||||||
schema.filterGroups.push(field.label);
|
schema.filterGroups.push(field.label);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -211,10 +244,10 @@ export default function FormView({
|
|||||||
viewHelperProps={viewHelperProps}
|
viewHelperProps={viewHelperProps}
|
||||||
name={field.id}
|
name={field.id}
|
||||||
value={value[field.id]}
|
value={value[field.id]}
|
||||||
readonly={_readonly}
|
|
||||||
disabled={disabled}
|
|
||||||
visible={_visible}
|
|
||||||
{...field}
|
{...field}
|
||||||
|
readonly={readonly}
|
||||||
|
disabled={disabled}
|
||||||
|
visible={visible}
|
||||||
onChange={(value)=>{
|
onChange={(value)=>{
|
||||||
/* Get the changes on dependent fields as well */
|
/* Get the changes on dependent fields as well */
|
||||||
dataDispatch({
|
dataDispatch({
|
||||||
@@ -227,9 +260,9 @@ export default function FormView({
|
|||||||
className={classes.controlRow}
|
className={classes.controlRow}
|
||||||
/>, [
|
/>, [
|
||||||
value[field.id],
|
value[field.id],
|
||||||
_readonly,
|
readonly,
|
||||||
disabled,
|
disabled,
|
||||||
_visible,
|
visible,
|
||||||
hasError,
|
hasError,
|
||||||
classes.controlRow,
|
classes.controlRow,
|
||||||
...(evalFunc(null, field.deps) || []).map((dep)=>value[dep]),
|
...(evalFunc(null, field.deps) || []).map((dep)=>value[dep]),
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
|
|||||||
value = e.target.value;
|
value = e.target.value;
|
||||||
}
|
}
|
||||||
onChange && onChange(value);
|
onChange && onChange(value);
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
const onSqlChange = useCallback((e, cm) => {
|
const onSqlChange = useCallback((e, cm) => {
|
||||||
onChange && onChange(cm.getValue());
|
onChange && onChange(cm.getValue());
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
const onIntChange = useCallback((e) => {
|
const onIntChange = useCallback((e) => {
|
||||||
let value = e;
|
let value = e;
|
||||||
@@ -41,7 +41,7 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
|
|||||||
value = parseInt(value);
|
value = parseInt(value);
|
||||||
}
|
}
|
||||||
onChange && onChange(value);
|
onChange && onChange(value);
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
if(!visible) {
|
if(!visible) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@@ -107,7 +107,7 @@ function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, vi
|
|||||||
}
|
}
|
||||||
|
|
||||||
onCellChange(value);
|
onCellChange(value);
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
/* Some grid cells are based on options selected in other cells.
|
/* Some grid cells are based on options selected in other cells.
|
||||||
* lets trigger a re-render for the row if optionsLoaded
|
* lets trigger a re-render for the row if optionsLoaded
|
||||||
@@ -116,7 +116,7 @@ function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, vi
|
|||||||
/* optionsLoaded is called when select options are fetched */
|
/* optionsLoaded is called when select options are fetched */
|
||||||
optionsLoaded && optionsLoaded(res);
|
optionsLoaded && optionsLoaded(res);
|
||||||
reRenderRow && reRenderRow();
|
reRenderRow && reRenderRow();
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
if(!visible) {
|
if(!visible) {
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|||||||
35
web/pgadmin/static/js/components/FieldSet.jsx
Normal file
35
web/pgadmin/static/js/components/FieldSet.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
import React from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import CustomPropTypes from '../custom_prop_types';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme)=>({
|
||||||
|
fieldset: {
|
||||||
|
padding: theme.spacing(0.5),
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
backgroundColor: 'inherit',
|
||||||
|
border: '1px solid ' + theme.otherVars.borderColor,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
width: 'unset',
|
||||||
|
fontSize: 'inherit',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function FieldSet({title='', className, children}) {
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<fieldset className={clsx(classes.fieldset, className)}>
|
||||||
|
<legend className={classes.legend}>{title}</legend>
|
||||||
|
{children}
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldSet.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
className: CustomPropTypes.className,
|
||||||
|
children: CustomPropTypes.children,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user