diff --git a/web/pgadmin/static/js/SchemaView/DataGridView.jsx b/web/pgadmin/static/js/SchemaView/DataGridView.jsx index 89f9b519f..d010cd5bf 100644 --- a/web/pgadmin/static/js/SchemaView/DataGridView.jsx +++ b/web/pgadmin/static/js/SchemaView/DataGridView.jsx @@ -9,7 +9,7 @@ /* 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 { makeStyles } from '@material-ui/core/styles'; import { PgIconButton } from '../components/Buttons'; @@ -92,6 +92,15 @@ const useStyles = makeStyles((theme)=>({ zIndex: 1, 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}) { @@ -129,38 +138,31 @@ DataTableHeader.propTypes = { headerGroups: PropTypes.array.isRequired, }; -function DataTableRow({row, totalRows, canExpand, isResizing, viewHelperProps, formErr, schema, dataDispatch, accessPath}) { +function DataTableRow({row, totalRows, isResizing}) { const classes = useStyles(); const [key, setKey] = useState(false); - // let key = useRef(true); /* Memoize the row to avoid unnecessary re-render. * If table data changes, then react-table re-renders the complete tables * 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]); - return ( - useMemo(()=> - <> -
- {row.cells.map((cell, ci) => { - return ( -
- {cell.render('Cell', { - reRenderRow: ()=>{setKey((currKey)=>!currKey);} - })} -
- ); - })} -
- { - canExpand && row.isExpanded && - + return useMemo(()=> +
+ {row.cells.map((cell, ci) => { + let classNames = [classes.tableCell]; + if(cell.column.id == 'btn-edit' && row.isExpanded) { + classNames.push(classes.expandedIconCell); } - - , depsMap) - ); + return ( +
+ {cell.render('Cell', { + reRenderRow: ()=>{setKey((currKey)=>!currKey);} + })} +
+ ); + })} +
, depsMap); } export default function DataGridView({ @@ -178,6 +180,8 @@ export default function DataGridView({ }); return res; }, []); + /* Using ref so that schema variable is not frozen in columns closure */ + const schemaRef = useRef(schema); let columns = useMemo( ()=>{ let cols = []; @@ -236,86 +240,96 @@ export default function DataGridView({ } cols = cols.concat( - schema.fields - .map((field)=>{ - let colInfo = { - Header: field.label, - accessor: field.id, - field: field, - resizable: true, - sortable: true, - ...(field.minWidth ? {minWidth: field.minWidth} : {}), - Cell: ({value, row, ...other}) => { - let {visible, disabled, readonly, ..._field} = field; + schemaRef.current.fields.filter((f)=>{ + return _.isArray(props.columns) ? props.columns.indexOf(f.id) > -1 : true; + }).sort((firstF, secondF)=>{ + if(_.isArray(props.columns)) { + return props.columns.indexOf(firstF.id) < props.columns.indexOf(secondF.id) ? -1 : 1; + } + return 0; + }).map((field)=>{ + let colInfo = { + Header: field.label, + 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 : - ((_.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, row.original || {}); - } + 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(schemaRef.current, readonly, row.original || {}); + } - let _visible = true; - if(visible) { - _visible = evalFunc(schema, visible, row.original || {}); - } - _visible = _visible && verInLimit; + visible = _.isUndefined(visible) ? true : visible; + let _visible = true; + if(visible) { + _visible = evalFunc(schemaRef.current, visible, row.original || {}); + } + _visible = _visible && verInLimit; - disabled = evalFunc(schema, disabled, row.original || {}); + editable = _.isUndefined(editable) ? true : editable; + editable = evalFunc(schemaRef.current, editable, row.original || {}); - return { - /* Get the changes on dependent fields as well. - * The return value of depChange function is merged and passed to state. - */ - const depChange = (state)=>{ - let rowdata = _.get(state, accessPath.concat(row.index)); - _field.depChange && _.merge(rowdata, _field.depChange(rowdata, _field.id) || {}); - (dependsOnField[_field.id] || []).forEach((d)=>{ - d = _.find(schema.fields, (f)=>f.id==d); - if(d.depChange) { - _.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 { + /* Get the changes on dependent fields as well. + * The return value of depChange function is merged and passed to state. + */ + const depChange = (state)=>{ + let rowdata = _.get(state, accessPath.concat(row.index)); + _field.depChange && _.merge(rowdata, _field.depChange(rowdata, _field.id) || {}); + (dependsOnField[_field.id] || []).forEach((d)=>{ + d = _.find(schemaRef.current.fields, (f)=>f.id==d); + if(d.depChange) { + _.merge(rowdata, d.depChange(rowdata, _field.id) || {}); + } }); - }} - reRenderRow={other.reRenderRow} - />; - }, - }; - colInfo.Cell.displayName = 'Cell', - colInfo.Cell.propTypes = { - row: PropTypes.object.isRequired, - value: PropTypes.any, - onCellChange: PropTypes.func, - }; - return colInfo; - }) + return state; + }; + dataDispatch({ + type: SCHEMA_STATE_ACTIONS.SET_VALUE, + path: accessPath.concat([row.index, _field.id]), + value: value, + depChange: depChange, + }); + }} + reRenderRow={other.reRenderRow} + />; + }, + }; + colInfo.Cell.displayName = 'Cell', + colInfo.Cell.propTypes = { + row: PropTypes.object.isRequired, + value: PropTypes.any, + onCellChange: PropTypes.func, + }; + return colInfo; + }) ); return cols; - },[]); + },[] + ); const onAddClick = useCallback(()=>{ let newRow = {}; columns.forEach((column)=>{ if(column.field) { - newRow[column.field.id] = schema.defaults[column.field.id]; + newRow[column.field.id] = schemaRef.current.defaults[column.field.id]; } }); dataDispatch({ @@ -327,6 +341,7 @@ export default function DataGridView({ const defaultColumn = useMemo(()=>({ minWidth: 175, + width: 0, })); let tablePlugins = [ @@ -371,9 +386,15 @@ export default function DataGridView({
{rows.map((row, i) => { prepareRow(row); - return ; + return + + {props.canEdit && row.isExpanded && + + } + })}
diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx index 74bf22dd6..e0b7031f3 100644 --- a/web/pgadmin/static/js/SchemaView/FormView.jsx +++ b/web/pgadmin/static/js/SchemaView/FormView.jsx @@ -11,6 +11,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Box, makeStyles, Tab, Tabs } from '@material-ui/core'; import _ from 'lodash'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; import { MappedFormControl } from './MappedControl'; import TabPanel from '../components/TabPanel'; @@ -29,6 +30,9 @@ const useStyles = makeStyles((theme)=>({ controlRow: { paddingBottom: theme.spacing(1), }, + nestedTabPanel: { + backgroundColor: theme.otherVars.headerBg, + }, })); /* Optional SQL tab */ @@ -62,7 +66,7 @@ SQLTab.propTypes = { /* The first component of schema view form */ 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 tabs = {}; let tabsClassname = {}; @@ -109,8 +113,8 @@ export default function FormView({ _readonly = evalFunc(schema, readonly, value); } + visible = _.isUndefined(visible) ? true : visible; let _visible = true; - if(visible) { _visible = evalFunc(schema, visible, value); } @@ -223,29 +227,31 @@ export default function FormView({ return ( <> - - { - setTabValue(selTabValue); - }} - // indicatorColor="primary" - variant="scrollable" - scrollButtons="auto" - action={(ref)=>ref && ref.updateIndicator()} - > - {Object.keys(tabs).map((tabName)=>{ - return ; - })} - + + + { + setTabValue(selTabValue); + }} + // indicatorColor="primary" + variant="scrollable" + scrollButtons="auto" + action={(ref)=>ref && ref.updateIndicator()} + > + {Object.keys(tabs).map((tabName)=>{ + return ; + })} + + + {Object.keys(tabs).map((tabName, i)=>{ + return ( + + {tabs[tabName]} + + ); + })} - {Object.keys(tabs).map((tabName, i)=>{ - return ( - - {tabs[tabName]} - - ); - })} ); } diff --git a/web/pgadmin/static/js/Theme/index.jsx b/web/pgadmin/static/js/Theme/index.jsx index 16748acb0..2177f3e41 100644 --- a/web/pgadmin/static/js/Theme/index.jsx +++ b/web/pgadmin/static/js/Theme/index.jsx @@ -247,6 +247,7 @@ function getFinalTheme(baseTheme) { MuiInputBase: { root: { backgroundColor: baseTheme.palette.background.default, + textOverflow: 'ellipsis', }, inputMultiline: { fontSize: baseTheme.typography.fontSize,