mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Fixes for expanded table row form control.
This commit is contained in:
parent
9d33b03be9
commit
6b9e8c3039
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
/* The DataGridView component is based on react-table component */
|
/* The DataGridView component is based on react-table component */
|
||||||
|
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { Box } from '@material-ui/core';
|
import { Box } from '@material-ui/core';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import { PgIconButton } from '../components/Buttons';
|
import { PgIconButton } from '../components/Buttons';
|
||||||
@ -92,6 +92,15 @@ const useStyles = makeStyles((theme)=>({
|
|||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
touchAction: 'none',
|
touchAction: 'none',
|
||||||
},
|
},
|
||||||
|
expandedForm: {
|
||||||
|
borderTopWidth: theme.spacing(0.5),
|
||||||
|
borderStyle: 'solid ',
|
||||||
|
borderColor: theme.palette.grey[400],
|
||||||
|
},
|
||||||
|
expandedIconCell: {
|
||||||
|
backgroundColor: theme.palette.grey[400],
|
||||||
|
borderBottom: 'none',
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function DataTableHeader({headerGroups}) {
|
function DataTableHeader({headerGroups}) {
|
||||||
@ -129,38 +138,31 @@ DataTableHeader.propTypes = {
|
|||||||
headerGroups: PropTypes.array.isRequired,
|
headerGroups: PropTypes.array.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function DataTableRow({row, totalRows, canExpand, isResizing, viewHelperProps, formErr, schema, dataDispatch, accessPath}) {
|
function DataTableRow({row, totalRows, isResizing}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [key, setKey] = useState(false);
|
const [key, setKey] = useState(false);
|
||||||
// let key = useRef(true);
|
|
||||||
/* Memoize the row to avoid unnecessary re-render.
|
/* Memoize the row to avoid unnecessary re-render.
|
||||||
* If table data changes, then react-table re-renders the complete tables
|
* If table data changes, then react-table re-renders the complete tables
|
||||||
* We can avoid re-render by if row data is not changed
|
* We can avoid re-render by if row data is not changed
|
||||||
*/
|
*/
|
||||||
let depsMap = _.values(row.original, Object.keys(row.original).filter((k)=>!k.startsWith('btn')));
|
let depsMap = _.values(row.values, Object.keys(row.values).filter((k)=>!k.startsWith('btn')));
|
||||||
depsMap = depsMap.concat([totalRows, row.isExpanded, key, isResizing]);
|
depsMap = depsMap.concat([totalRows, row.isExpanded, key, isResizing]);
|
||||||
return (
|
return useMemo(()=>
|
||||||
useMemo(()=>
|
<div {...row.getRowProps()} className="tr">
|
||||||
<>
|
{row.cells.map((cell, ci) => {
|
||||||
<div {...row.getRowProps()} className="tr">
|
let classNames = [classes.tableCell];
|
||||||
{row.cells.map((cell, ci) => {
|
if(cell.column.id == 'btn-edit' && row.isExpanded) {
|
||||||
return (
|
classNames.push(classes.expandedIconCell);
|
||||||
<div key={ci} {...cell.getCellProps()} className={classes.tableCell}>
|
|
||||||
{cell.render('Cell', {
|
|
||||||
reRenderRow: ()=>{setKey((currKey)=>!currKey);}
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
canExpand && row.isExpanded &&
|
|
||||||
<FormView key={row.index} value={row.original} viewHelperProps={viewHelperProps} formErr={formErr} dataDispatch={dataDispatch}
|
|
||||||
schema={schema} accessPath={accessPath} isNested={true}/>
|
|
||||||
}
|
}
|
||||||
</>
|
return (
|
||||||
, depsMap)
|
<div key={ci} {...cell.getCellProps()} className={clsx(classNames)}>
|
||||||
);
|
{cell.render('Cell', {
|
||||||
|
reRenderRow: ()=>{setKey((currKey)=>!currKey);}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>, depsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DataGridView({
|
export default function DataGridView({
|
||||||
@ -178,6 +180,8 @@ export default function DataGridView({
|
|||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
|
/* Using ref so that schema variable is not frozen in columns closure */
|
||||||
|
const schemaRef = useRef(schema);
|
||||||
let columns = useMemo(
|
let columns = useMemo(
|
||||||
()=>{
|
()=>{
|
||||||
let cols = [];
|
let cols = [];
|
||||||
@ -236,86 +240,96 @@ export default function DataGridView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
cols = cols.concat(
|
cols = cols.concat(
|
||||||
schema.fields
|
schemaRef.current.fields.filter((f)=>{
|
||||||
.map((field)=>{
|
return _.isArray(props.columns) ? props.columns.indexOf(f.id) > -1 : true;
|
||||||
let colInfo = {
|
}).sort((firstF, secondF)=>{
|
||||||
Header: field.label,
|
if(_.isArray(props.columns)) {
|
||||||
accessor: field.id,
|
return props.columns.indexOf(firstF.id) < props.columns.indexOf(secondF.id) ? -1 : 1;
|
||||||
field: field,
|
}
|
||||||
resizable: true,
|
return 0;
|
||||||
sortable: true,
|
}).map((field)=>{
|
||||||
...(field.minWidth ? {minWidth: field.minWidth} : {}),
|
let colInfo = {
|
||||||
Cell: ({value, row, ...other}) => {
|
Header: field.label,
|
||||||
let {visible, disabled, readonly, ..._field} = field;
|
accessor: field.id,
|
||||||
|
field: field,
|
||||||
|
resizable: true,
|
||||||
|
sortable: true,
|
||||||
|
...(field.minWidth ? {minWidth: field.minWidth} : {}),
|
||||||
|
...(field.width ? {width: field.width} : {}),
|
||||||
|
Cell: ({value, row, ...other}) => {
|
||||||
|
let {visible, editable, readonly, ..._field} = field;
|
||||||
|
|
||||||
let verInLimit = (_.isUndefined(viewHelperProps.serverInfo) ? true :
|
let verInLimit = (_.isUndefined(viewHelperProps.serverInfo) ? true :
|
||||||
((_.isUndefined(field.server_type) ? true :
|
((_.isUndefined(field.server_type) ? true :
|
||||||
(viewHelperProps.serverInfo.type in field.server_type)) &&
|
(viewHelperProps.serverInfo.type in field.server_type)) &&
|
||||||
(_.isUndefined(field.min_version) ? true :
|
(_.isUndefined(field.min_version) ? true :
|
||||||
(viewHelperProps.serverInfo.version >= field.min_version)) &&
|
(viewHelperProps.serverInfo.version >= field.min_version)) &&
|
||||||
(_.isUndefined(field.max_version) ? true :
|
(_.isUndefined(field.max_version) ? true :
|
||||||
(viewHelperProps.serverInfo.version <= field.max_version))));
|
(viewHelperProps.serverInfo.version <= field.max_version))));
|
||||||
let _readonly = viewHelperProps.inCatalog || (viewHelperProps.mode == 'properties');
|
let _readonly = viewHelperProps.inCatalog || (viewHelperProps.mode == 'properties');
|
||||||
if(!_readonly) {
|
if(!_readonly) {
|
||||||
_readonly = evalFunc(schema, readonly, row.original || {});
|
_readonly = evalFunc(schemaRef.current, readonly, row.original || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
let _visible = true;
|
visible = _.isUndefined(visible) ? true : visible;
|
||||||
if(visible) {
|
let _visible = true;
|
||||||
_visible = evalFunc(schema, visible, row.original || {});
|
if(visible) {
|
||||||
}
|
_visible = evalFunc(schemaRef.current, visible, row.original || {});
|
||||||
_visible = _visible && verInLimit;
|
}
|
||||||
|
_visible = _visible && verInLimit;
|
||||||
|
|
||||||
disabled = evalFunc(schema, disabled, row.original || {});
|
editable = _.isUndefined(editable) ? true : editable;
|
||||||
|
editable = evalFunc(schemaRef.current, editable, row.original || {});
|
||||||
|
|
||||||
return <MappedCellControl rowIndex={row.index} value={value}
|
return <MappedCellControl rowIndex={row.index} value={value}
|
||||||
row={row.original} {..._field}
|
row={row.original} {..._field}
|
||||||
readonly={_readonly}
|
readonly={_readonly}
|
||||||
disabled={disabled}
|
disabled={!editable}
|
||||||
visible={_visible}
|
visible={_visible}
|
||||||
onCellChange={(value)=>{
|
onCellChange={(value)=>{
|
||||||
/* Get the changes on dependent fields as well.
|
/* Get the changes on dependent fields as well.
|
||||||
* The return value of depChange function is merged and passed to state.
|
* The return value of depChange function is merged and passed to state.
|
||||||
*/
|
*/
|
||||||
const depChange = (state)=>{
|
const depChange = (state)=>{
|
||||||
let rowdata = _.get(state, accessPath.concat(row.index));
|
let rowdata = _.get(state, accessPath.concat(row.index));
|
||||||
_field.depChange && _.merge(rowdata, _field.depChange(rowdata, _field.id) || {});
|
_field.depChange && _.merge(rowdata, _field.depChange(rowdata, _field.id) || {});
|
||||||
(dependsOnField[_field.id] || []).forEach((d)=>{
|
(dependsOnField[_field.id] || []).forEach((d)=>{
|
||||||
d = _.find(schema.fields, (f)=>f.id==d);
|
d = _.find(schemaRef.current.fields, (f)=>f.id==d);
|
||||||
if(d.depChange) {
|
if(d.depChange) {
|
||||||
_.merge(rowdata, d.depChange(rowdata, _field.id) || {});
|
_.merge(rowdata, d.depChange(rowdata, _field.id) || {});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return state;
|
|
||||||
};
|
|
||||||
dataDispatch({
|
|
||||||
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
|
|
||||||
path: accessPath.concat([row.index, _field.id]),
|
|
||||||
value: value,
|
|
||||||
depChange: depChange,
|
|
||||||
});
|
});
|
||||||
}}
|
return state;
|
||||||
reRenderRow={other.reRenderRow}
|
};
|
||||||
/>;
|
dataDispatch({
|
||||||
},
|
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
|
||||||
};
|
path: accessPath.concat([row.index, _field.id]),
|
||||||
colInfo.Cell.displayName = 'Cell',
|
value: value,
|
||||||
colInfo.Cell.propTypes = {
|
depChange: depChange,
|
||||||
row: PropTypes.object.isRequired,
|
});
|
||||||
value: PropTypes.any,
|
}}
|
||||||
onCellChange: PropTypes.func,
|
reRenderRow={other.reRenderRow}
|
||||||
};
|
/>;
|
||||||
return colInfo;
|
},
|
||||||
})
|
};
|
||||||
|
colInfo.Cell.displayName = 'Cell',
|
||||||
|
colInfo.Cell.propTypes = {
|
||||||
|
row: PropTypes.object.isRequired,
|
||||||
|
value: PropTypes.any,
|
||||||
|
onCellChange: PropTypes.func,
|
||||||
|
};
|
||||||
|
return colInfo;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
return cols;
|
return cols;
|
||||||
},[]);
|
},[]
|
||||||
|
);
|
||||||
|
|
||||||
const onAddClick = useCallback(()=>{
|
const onAddClick = useCallback(()=>{
|
||||||
let newRow = {};
|
let newRow = {};
|
||||||
columns.forEach((column)=>{
|
columns.forEach((column)=>{
|
||||||
if(column.field) {
|
if(column.field) {
|
||||||
newRow[column.field.id] = schema.defaults[column.field.id];
|
newRow[column.field.id] = schemaRef.current.defaults[column.field.id];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dataDispatch({
|
dataDispatch({
|
||||||
@ -327,6 +341,7 @@ export default function DataGridView({
|
|||||||
|
|
||||||
const defaultColumn = useMemo(()=>({
|
const defaultColumn = useMemo(()=>({
|
||||||
minWidth: 175,
|
minWidth: 175,
|
||||||
|
width: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let tablePlugins = [
|
let tablePlugins = [
|
||||||
@ -371,9 +386,15 @@ export default function DataGridView({
|
|||||||
<div {...getTableBodyProps()}>
|
<div {...getTableBodyProps()}>
|
||||||
{rows.map((row, i) => {
|
{rows.map((row, i) => {
|
||||||
prepareRow(row);
|
prepareRow(row);
|
||||||
return <DataTableRow key={i} row={row} totalRows={rows.length} canExpand={props.canEdit}
|
return <React.Fragment key={i}>
|
||||||
value={value} viewHelperProps={viewHelperProps} formErr={formErr} isResizing={isResizing}
|
<DataTableRow row={row} totalRows={rows.length} canExpand={props.canEdit}
|
||||||
schema={schema} accessPath={accessPath.concat([row.index])} dataDispatch={dataDispatch} />;
|
value={value} viewHelperProps={viewHelperProps} formErr={formErr} isResizing={isResizing}
|
||||||
|
schema={schemaRef.current} accessPath={accessPath.concat([row.index])} dataDispatch={dataDispatch} />
|
||||||
|
{props.canEdit && row.isExpanded &&
|
||||||
|
<FormView value={row.original} viewHelperProps={viewHelperProps} formErr={formErr} dataDispatch={dataDispatch}
|
||||||
|
schema={schemaRef.current} accessPath={accessPath.concat([row.index])} isNested={true} className={classes.expandedForm}/>
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,6 +11,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { Box, makeStyles, Tab, Tabs } from '@material-ui/core';
|
import { Box, makeStyles, Tab, Tabs } from '@material-ui/core';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import { MappedFormControl } from './MappedControl';
|
import { MappedFormControl } from './MappedControl';
|
||||||
import TabPanel from '../components/TabPanel';
|
import TabPanel from '../components/TabPanel';
|
||||||
@ -29,6 +30,9 @@ const useStyles = makeStyles((theme)=>({
|
|||||||
controlRow: {
|
controlRow: {
|
||||||
paddingBottom: theme.spacing(1),
|
paddingBottom: theme.spacing(1),
|
||||||
},
|
},
|
||||||
|
nestedTabPanel: {
|
||||||
|
backgroundColor: theme.otherVars.headerBg,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/* Optional SQL tab */
|
/* Optional SQL tab */
|
||||||
@ -62,7 +66,7 @@ SQLTab.propTypes = {
|
|||||||
|
|
||||||
/* 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, getSQLValue, onTabChange, firstEleRef}) {
|
value, formErr, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab, getSQLValue, onTabChange, firstEleRef, className}) {
|
||||||
let defaultTab = 'General';
|
let defaultTab = 'General';
|
||||||
let tabs = {};
|
let tabs = {};
|
||||||
let tabsClassname = {};
|
let tabsClassname = {};
|
||||||
@ -109,8 +113,8 @@ export default function FormView({
|
|||||||
_readonly = evalFunc(schema, readonly, value);
|
_readonly = evalFunc(schema, readonly, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visible = _.isUndefined(visible) ? true : visible;
|
||||||
let _visible = true;
|
let _visible = true;
|
||||||
|
|
||||||
if(visible) {
|
if(visible) {
|
||||||
_visible = evalFunc(schema, visible, value);
|
_visible = evalFunc(schema, visible, value);
|
||||||
}
|
}
|
||||||
@ -223,29 +227,31 @@ export default function FormView({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box>
|
<Box height="100%" display="flex" flexDirection="column" className={className}>
|
||||||
<Tabs
|
<Box>
|
||||||
value={tabValue}
|
<Tabs
|
||||||
onChange={(event, selTabValue) => {
|
value={tabValue}
|
||||||
setTabValue(selTabValue);
|
onChange={(event, selTabValue) => {
|
||||||
}}
|
setTabValue(selTabValue);
|
||||||
// indicatorColor="primary"
|
}}
|
||||||
variant="scrollable"
|
// indicatorColor="primary"
|
||||||
scrollButtons="auto"
|
variant="scrollable"
|
||||||
action={(ref)=>ref && ref.updateIndicator()}
|
scrollButtons="auto"
|
||||||
>
|
action={(ref)=>ref && ref.updateIndicator()}
|
||||||
{Object.keys(tabs).map((tabName)=>{
|
>
|
||||||
return <Tab key={tabName} label={tabName} />;
|
{Object.keys(tabs).map((tabName)=>{
|
||||||
})}
|
return <Tab key={tabName} label={tabName} />;
|
||||||
</Tabs>
|
})}
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
{Object.keys(tabs).map((tabName, i)=>{
|
||||||
|
return (
|
||||||
|
<TabPanel key={tabName} value={tabValue} index={i} classNameRoot={clsx(tabsClassname[tabName], isNested ? classes.nestedTabPanel : null)}>
|
||||||
|
{tabs[tabName]}
|
||||||
|
</TabPanel>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
{Object.keys(tabs).map((tabName, i)=>{
|
|
||||||
return (
|
|
||||||
<TabPanel key={tabName} value={tabValue} index={i} classNameRoot={isNested ? classes.fullSpace : tabsClassname[tabName]}>
|
|
||||||
{tabs[tabName]}
|
|
||||||
</TabPanel>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,6 +247,7 @@ function getFinalTheme(baseTheme) {
|
|||||||
MuiInputBase: {
|
MuiInputBase: {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: baseTheme.palette.background.default,
|
backgroundColor: baseTheme.palette.background.default,
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
},
|
},
|
||||||
inputMultiline: {
|
inputMultiline: {
|
||||||
fontSize: baseTheme.typography.fontSize,
|
fontSize: baseTheme.typography.fontSize,
|
||||||
|
Loading…
Reference in New Issue
Block a user