diff --git a/web/pgadmin/static/js/SchemaView/DataGridView.jsx b/web/pgadmin/static/js/SchemaView/DataGridView.jsx index 0438258c4..0c345db98 100644 --- a/web/pgadmin/static/js/SchemaView/DataGridView.jsx +++ b/web/pgadmin/static/js/SchemaView/DataGridView.jsx @@ -78,6 +78,8 @@ const useStyles = makeStyles((theme)=>({ ...theme.mixins.panelBorder.bottom, ...theme.mixins.panelBorder.right, position: 'relative', + overflow: 'hidden', + textOverflow: 'ellipsis', }, tableCellHeader: { fontWeight: theme.typography.fontWeightBold, @@ -159,6 +161,20 @@ function DataTableRow({row, totalRows, isResizing, schema, schemaRef, accessPath let retVal = []; /* Calculate the fields which depends on the current field deps has info on fields which the current field depends on. */ + schema.fields.forEach((field)=>{ + (evalFunc(null, field.deps) || []).forEach((dep)=>{ + let source = accessPath.concat(dep); + if(_.isArray(dep)) { + source = dep; + /* If its an array, then dep is from the top schema and external */ + retVal.push(source); + } + }); + }); + return retVal; + }, []); + + useEffect(()=>{ schema.fields.forEach((field)=>{ /* Self change is also dep change */ if(field.depChange) { @@ -168,13 +184,14 @@ function DataTableRow({row, totalRows, isResizing, schema, schemaRef, accessPath let source = accessPath.concat(dep); if(_.isArray(dep)) { source = dep; - /* If its an array, then dep is from the top schema */ - retVal.push(source); } depListener.addDepListener(source, accessPath.concat(field.id), field.depChange); }); }); - return retVal; + return ()=>{ + /* Cleanup the listeners when unmounting */ + depListener.removeDepListener(accessPath); + }; }, []); /* External deps values are from top schema sess data */ @@ -201,12 +218,28 @@ function DataTableRow({row, totalRows, isResizing, schema, schemaRef, accessPath , depsMap); } +export function DataGridHeader({label, canAdd, onAddClick}) { + const classes = useStyles(); + return ( + + {label} + + {canAdd && } className={classes.gridControlsButton} />} + + + ); +} +DataGridHeader.propTypes = { + label: PropTypes.string, + canAdd: PropTypes.bool, + onAddClick: PropTypes.func, +}; + export default function DataGridView({ value, viewHelperProps, formErr, schema, accessPath, dataDispatch, containerClassName, fixedRows, ...props}) { const classes = useStyles(); const stateUtils = useContext(StateUtilsContext); - const depListener = useContext(DepListenerContext); /* Using ref so that schema variable is not frozen in columns closure */ const schemaRef = useRef(schema); @@ -268,7 +301,6 @@ export default function DataGridView({ value: row.index, }); - depListener.removeDepListener(accessPath.concat(row.index)); }, ()=>{}, props.customDeleteTitle, props.customDeleteMsg); }} className={classes.gridRowButton} disabled={!canDeleteRow} /> ); @@ -349,22 +381,30 @@ export default function DataGridView({ }) ); return cols; - },[] + },[props.canEdit, props.canDelete] ); const onAddClick = useCallback(()=>{ + if(props.canAddRow) { + let state = schemaRef.current.top ? schemaRef.current.top.sessData : schemaRef.current.sessData; + let canAddRow = evalFunc(schemaRef.current, props.canAddRow, state || {}); + if(!canAddRow) { + return; + } + } + let newRow = schemaRef.current.getNewData(); dataDispatch({ type: SCHEMA_STATE_ACTIONS.ADD_ROW, path: accessPath, value: newRow, }); - }); + }, []); const defaultColumn = useMemo(()=>({ minWidth: 175, width: 0, - })); + }), []); let tablePlugins = [ useBlockLayout, @@ -393,6 +433,8 @@ export default function DataGridView({ useEffect(()=>{ let rowsPromise = fixedRows, umounted=false; + + /* If fixedRows is defined, fetch the details */ if(typeof rowsPromise === 'function') { rowsPromise = rowsPromise(); } @@ -417,12 +459,7 @@ export default function DataGridView({ return ( - {(props.label || props.canAdd) && - {props.label} - - {props.canAdd && } className={classes.gridControlsButton} />} - - } + {(props.label || props.canAdd) && }
@@ -460,6 +497,9 @@ DataGridView.propTypes = { canAdd: PropTypes.bool, canDelete: PropTypes.bool, visible: PropTypes.bool, + canAddRow: PropTypes.oneOfType([ + PropTypes.bool, PropTypes.func, + ]), canEditRow: PropTypes.oneOfType([ PropTypes.bool, PropTypes.func, ]), diff --git a/web/pgadmin/static/js/SchemaView/FieldSetView.jsx b/web/pgadmin/static/js/SchemaView/FieldSetView.jsx index 5953c397d..7596ec86b 100644 --- a/web/pgadmin/static/js/SchemaView/FieldSetView.jsx +++ b/web/pgadmin/static/js/SchemaView/FieldSetView.jsx @@ -25,7 +25,7 @@ export default function FieldSetView({ useEffect(()=>{ /* Calculate the fields which depends on the current field */ - if(!isDataGridForm) { + if(!isDataGridForm && depListener) { schema.fields.forEach((field)=>{ /* Self change is also dep change */ if(field.depChange || field.deferredDepChange) { diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx index b3d51ef08..c85eb4742 100644 --- a/web/pgadmin/static/js/SchemaView/FormView.jsx +++ b/web/pgadmin/static/js/SchemaView/FormView.jsx @@ -122,7 +122,7 @@ export function getFieldMetaData(field, schema, value, viewHelperProps) { /* The first component of schema view form */ export default function FormView({ value, formErr, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab, - getSQLValue, onTabChange, firstEleRef, className, isDataGridForm=false, visible}) { + getSQLValue, onTabChange, firstEleRef, className, isDataGridForm=false, isTabView=true, visible}) { let defaultTab = 'General'; let tabs = {}; let tabsClassname = {}; @@ -164,6 +164,10 @@ export default function FormView({ } }); }); + return ()=>{ + /* Cleanup the listeners when unmounting */ + depListener.removeDepListener(accessPath); + }; } }, []); @@ -175,7 +179,7 @@ export default function FormView({ getFieldMetaData(field, schema, value, viewHelperProps); if(modeSupported) { - let {group} = field; + let {group, CustomControl} = field; group = groupLabels[group] || group || defaultTab; if(!tabs[group]) tabs[group] = []; @@ -190,7 +194,7 @@ export default function FormView({ } tabs[group].push( ); } else if(field.type === 'nested-fieldset') { @@ -223,11 +227,18 @@ export default function FormView({ canDelete = false; } - tabs[group].push( - - ); + const props = { + 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, + visible: visible, + }; + + if(CustomControl) { + tabs[group].push(); + } else { + tabs[group].push(); + } } else if(field.type === 'group') { groupLabels[field.id] = field.label; if(!visible) { @@ -311,35 +322,48 @@ export default function FormView({ return <>; } - return ( - <> - - - { - setTabValue(selTabValue); - }} - // indicatorColor="primary" - variant="scrollable" - scrollButtons="auto" - action={(ref)=>ref && ref.updateIndicator()} - > - {Object.keys(tabs).map((tabName)=>{ - return ; - })} - + if(isTabView) { + 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]} - - ); - })} - - ); + ); + } else { + return ( + <> + + {Object.keys(tabs).map((tabName)=>{ + return ( + <>{tabs[tabName]} + ); + })} + + ); + } } FormView.propTypes = { diff --git a/web/pgadmin/static/js/SchemaView/base_schema.ui.js b/web/pgadmin/static/js/SchemaView/base_schema.ui.js index 4521b6329..ebff0853e 100644 --- a/web/pgadmin/static/js/SchemaView/base_schema.ui.js +++ b/web/pgadmin/static/js/SchemaView/base_schema.ui.js @@ -126,4 +126,9 @@ export default class BaseUISchema { }); return newRow; } + + /* Used in header schema */ + addDisabled() { + return false; + } } diff --git a/web/pgadmin/static/js/SchemaView/index.jsx b/web/pgadmin/static/js/SchemaView/index.jsx index f125998b1..8887d93c0 100644 --- a/web/pgadmin/static/js/SchemaView/index.jsx +++ b/web/pgadmin/static/js/SchemaView/index.jsx @@ -28,7 +28,7 @@ import { minMaxValidator, numberValidator, integerValidator, emptyValidator, che import { MappedFormControl } from './MappedControl'; import gettext from 'sources/gettext'; import BaseUISchema from 'sources/SchemaView/base_schema.ui'; -import FormView from './FormView'; +import FormView, { getFieldMetaData } from './FormView'; import { pgAlertify } from '../helpers/legacyConnector'; import { evalFunc } from 'sources/utils'; import PropTypes from 'prop-types'; @@ -113,9 +113,9 @@ const diffArrayOptions = { compareFunction: objectComparator, }; -function getChangedData(topSchema, mode, sessData, stringify=false) { +function getChangedData(topSchema, viewHelperProps, sessData, stringify=false) { let changedData = {}; - let isEdit = mode === 'edit'; + let isEdit = viewHelperProps.mode === 'edit'; /* The comparator and setter */ const attrChanged = (currPath, change, force=false)=>{ @@ -136,6 +136,10 @@ function getChangedData(topSchema, mode, sessData, stringify=false) { /* Will be called recursively as data can be nested */ const parseChanges = (schema, accessPath, changedData)=>{ schema.fields.forEach((field)=>{ + let {modeSupported} = getFieldMetaData(field, schema, {}, viewHelperProps); + if(!modeSupported) { + return; + } if(typeof(field.type) == 'string' && field.type.startsWith('nested-')) { /* its nested */ parseChanges(field.schema, accessPath, changedData); @@ -324,7 +328,6 @@ const sessDataReducer = (state, action)=>{ /* If there is any dep listeners get the changes */ data = getDepChange(action.path, data, state, action); deferredList = getDeferredDepChange(action.path, data, state, action); - // let deferredInfo = getDeferredDepChange(action.path, data, state, action); data.__deferred__ = deferredList || []; break; case SCHEMA_STATE_ACTIONS.ADD_ROW: @@ -387,7 +390,7 @@ function prepareData(val) { /* If its the dialog */ function SchemaDialogView({ - getInitData, viewHelperProps, schema={}, ...props}) { + getInitData, viewHelperProps, schema={}, showFooter=true, isTabView=true, ...props}) { const classes = useDialogStyles(); /* Some useful states */ const [dirty, setDirty] = useState(false); @@ -421,11 +424,12 @@ function SchemaDialogView({ if(!isNotValid) setFormErr({}); /* check if anything changed */ - let dataChanged = Object.keys(getChangedData(schema, viewHelperProps.mode, sessData)).length > 0; - setDirty(dataChanged); + let changedData = getChangedData(schema, viewHelperProps, sessData); + let isDataChanged = Object.keys(changedData).length > 0; + setDirty(isDataChanged); /* tell the callbacks the data has changed */ - props.onDataChange && props.onDataChange(dataChanged); + props.onDataChange && props.onDataChange(isDataChanged, changedData); }, [sessData]); useEffect(()=>{ @@ -518,7 +522,7 @@ function SchemaDialogView({ setSaving(true); setLoaderText('Saving...'); /* Get the changed data */ - let changeData = getChangedData(schema, viewHelperProps.mode, sessData); + let changeData = getChangedData(schema, viewHelperProps, sessData); /* Add the id when in edit mode */ if(viewHelperProps.mode !== 'edit') { @@ -577,7 +581,7 @@ function SchemaDialogView({ /* Called when SQL tab is active */ if(dirty) { if(!formErr.name) { - let changeData = getChangedData(schema, viewHelperProps.mode, sessData); + let changeData = getChangedData(schema, viewHelperProps, sessData); if(viewHelperProps.mode !== 'edit') { /* If new then merge the changed data with origData */ changeData = _.assign({}, schema.origData, changeData); @@ -626,11 +630,11 @@ function SchemaDialogView({ + hasSQLTab={props.hasSQL} getSQLValue={getSQLValue} firstEleRef={firstEleRef} isTabView={isTabView} /> - + {showFooter && {useMemo(()=> props.onHelp(true, isNew)} icon={} disabled={props.disableSqlHelp} className={classes.buttonMargin} title="SQL help for this object type."/> @@ -648,7 +652,7 @@ function SchemaDialogView({ {gettext('Save')} - + } @@ -671,10 +675,12 @@ SchemaDialogView.propTypes = { onHelp: PropTypes.func, onDataChange: PropTypes.func, confirmOnCloseReset: PropTypes.bool, + isTabView: PropTypes.bool, hasSQL: PropTypes.bool, getSQLValue: PropTypes.func, disableSqlHelp: PropTypes.bool, disableDialogHelp: PropTypes.bool, + showFooter: PropTypes.bool, }; const usePropsStyles = makeStyles((theme)=>({ diff --git a/web/pgadmin/static/js/helpers/DataGridViewWithHeaderForm.jsx b/web/pgadmin/static/js/helpers/DataGridViewWithHeaderForm.jsx new file mode 100644 index 000000000..547cb3f8b --- /dev/null +++ b/web/pgadmin/static/js/helpers/DataGridViewWithHeaderForm.jsx @@ -0,0 +1,92 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { Box, makeStyles } from '@material-ui/core'; +import DataGridView, { DataGridHeader } from '../SchemaView/DataGridView'; +import SchemaView, { SCHEMA_STATE_ACTIONS } from '../SchemaView'; +import { DefaultButton } from '../components/Buttons'; +import { evalFunc } from '../utils'; +import PropTypes from 'prop-types'; +import CustomPropTypes from '../custom_prop_types'; + +const useStyles = makeStyles((theme)=>({ + formBorder: { + ...theme.mixins.panelBorder, + borderBottom: 0, + }, + form: { + padding: '0.25rem', + }, + addBtn: { + marginLeft: 'auto', + } +})); + +export default function DataGridViewWithHeaderForm(props) { + let {containerClassName, headerSchema, headerVisible, ...otherProps} = props; + const classes = useStyles(); + const headerFormData = useRef({}); + const schemaRef = useRef(otherProps.schema); + const [isAddDisabled, setAddDisabled] = useState(true); + + const onAddClick = useCallback(()=>{ + if(otherProps.canAddRow) { + let state = schemaRef.current.top ? schemaRef.current.top.sessData : schemaRef.current.sessData; + let canAddRow = evalFunc(schemaRef.current, otherProps.canAddRow, state || {}); + if(!canAddRow) { + return; + } + } + + let newRow = headerSchema.getNewData(headerFormData.current); + otherProps.dataDispatch({ + type: SCHEMA_STATE_ACTIONS.ADD_ROW, + path: otherProps.accessPath, + value: newRow, + }); + }, []); + + useEffect(()=>{ + headerSchema.top = schemaRef.current.top; + }, []); + + let state = schemaRef.current.top ? schemaRef.current.top.origData : schemaRef.current.origData; + headerVisible = headerVisible && evalFunc(null, headerVisible, state); + return ( + + + + {headerVisible && + Promise.resolve({})} + schema={headerSchema} + viewHelperProps={props.viewHelperProps} + showFooter={false} + onDataChange={(isDataChanged, dataChanged)=>{ + headerFormData.current = dataChanged; + setAddDisabled(headerSchema.addDisabled(headerFormData.current)); + }} + hasSQL={false} + isTabView={false} + /> + + Add + + } + + + + ); +} + +DataGridViewWithHeaderForm.propTypes = { + label: PropTypes.string, + value: PropTypes.array, + viewHelperProps: PropTypes.object, + formErr: PropTypes.object, + headerSchema: CustomPropTypes.schemaUI.isRequired, + headerVisible: PropTypes.func, + schema: CustomPropTypes.schemaUI, + accessPath: PropTypes.array.isRequired, + dataDispatch: PropTypes.func.isRequired, + containerClassName: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), +};