Add support for nested-fieldset control.

pull/58/head
Aditya Toshniwal 2021-07-20 10:55:33 +05:30 committed by Akshay Joshi
parent 621426ac55
commit 48176ea986
4 changed files with 229 additions and 50 deletions

View 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,
};

View File

@ -23,6 +23,7 @@ import { evalFunc } from 'sources/utils';
import CustomPropTypes from '../custom_prop_types';
import { useOnScreen } from '../custom_hooks';
import { DepListenerContext } from './DepListener';
import FieldSetView from './FieldSetView';
const useStyles = makeStyles((theme)=>({
fullSpace: {
@ -70,6 +71,54 @@ SQLTab.propTypes = {
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 */
export default function FormView({
value, formErr, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab,
@ -85,8 +134,6 @@ export default function FormView({
const depListener = useContext(DepListenerContext);
let groupLabels = {};
schema = schema || {fields: []};
let isOnScreen = useOnScreen(formRef);
if(isOnScreen) {
/* 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 */
schema.fields.forEach((f)=>{
let modeSuppoted = true;
if(f.mode) {
modeSuppoted = (f.mode.indexOf(viewHelperProps.mode) > -1);
}
schema.fields.forEach((field)=>{
let {visible, disabled, readonly, canAdd, canEdit, canDelete, modeSuppoted} =
getFieldMetaData(field, schema, value, viewHelperProps);
if(modeSuppoted) {
let {visible, disabled, group, readonly, ...field} = f;
let {group} = field;
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] = [];
/* Lets choose the path based on type */
@ -161,10 +186,22 @@ export default function FormView({
} else {
field.schema.top = schema;
}
tabs[group].push(
<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') {
/* If its a collection, let data grid view handle it */
@ -176,20 +213,16 @@ export default function FormView({
field.schema.top = schema;
}
/* Eval the params based on state */
let {canAdd, canEdit, canDelete, ..._field} = field;
canAdd = evalFunc(schema, canAdd, value);
canEdit = evalFunc(schema, canAdd, value);
canDelete = evalFunc(schema, canAdd, value);
depsMap.push(canAdd, canEdit, canDelete);
tabs[group].push(
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}
canAdd={canAdd} canEdit={canEdit} canDelete={canDelete} {..._field}/>, depsMap)
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}
{...field} canAdd={canAdd} canEdit={canEdit} canDelete={canDelete}/>, depsMap)
);
} else if(field.type === 'group') {
groupLabels[field.id] = field.label;
if(!_visible) {
if(!visible) {
schema.filterGroups.push(field.label);
}
} else {
@ -211,10 +244,10 @@ export default function FormView({
viewHelperProps={viewHelperProps}
name={field.id}
value={value[field.id]}
readonly={_readonly}
disabled={disabled}
visible={_visible}
{...field}
readonly={readonly}
disabled={disabled}
visible={visible}
onChange={(value)=>{
/* Get the changes on dependent fields as well */
dataDispatch({
@ -227,9 +260,9 @@ export default function FormView({
className={classes.controlRow}
/>, [
value[field.id],
_readonly,
readonly,
disabled,
_visible,
visible,
hasError,
classes.controlRow,
...(evalFunc(null, field.deps) || []).map((dep)=>value[dep]),

View File

@ -26,11 +26,11 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
value = e.target.value;
}
onChange && onChange(value);
});
}, []);
const onSqlChange = useCallback((e, cm) => {
onChange && onChange(cm.getValue());
});
}, []);
const onIntChange = useCallback((e) => {
let value = e;
@ -41,7 +41,7 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
value = parseInt(value);
}
onChange && onChange(value);
});
}, []);
if(!visible) {
return <></>;
@ -107,7 +107,7 @@ function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, vi
}
onCellChange(value);
});
}, []);
/* Some grid cells are based on options selected in other cells.
* 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 && optionsLoaded(res);
reRenderRow && reRenderRow();
});
}, []);
if(!visible) {
return <></>;

View 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,
};