Add support for nested-fieldset control.
parent
621426ac55
commit
48176ea986
|
@ -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 { 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]),
|
||||
|
|
|
@ -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 <></>;
|
||||
|
|
|
@ -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,
|
||||
};
|
Loading…
Reference in New Issue