mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
308 lines
9.8 KiB
JavaScript
308 lines
9.8 KiB
JavaScript
/////////////////////////////////////////////////////////////
|
|
//
|
|
// 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, 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';
|
|
import DataGridView from './DataGridView';
|
|
import { SCHEMA_STATE_ACTIONS } from '.';
|
|
import { InputSQL } from '../components/FormComponents';
|
|
import gettext from 'sources/gettext';
|
|
import { evalFunc } from 'sources/utils';
|
|
import CustomPropTypes from '../custom_prop_types';
|
|
import { useOnScreen } from '../custom_hooks';
|
|
import { DepListenerContext } from './DepListener';
|
|
|
|
const useStyles = makeStyles((theme)=>({
|
|
fullSpace: {
|
|
padding: 0,
|
|
height: '100%'
|
|
},
|
|
controlRow: {
|
|
paddingBottom: theme.spacing(1),
|
|
},
|
|
nestedTabPanel: {
|
|
backgroundColor: theme.otherVars.headerBg,
|
|
},
|
|
nestedControl: {
|
|
height: 'unset',
|
|
}
|
|
}));
|
|
|
|
/* Optional SQL tab */
|
|
function SQLTab({active, getSQLValue}) {
|
|
const [sql, setSql] = useState('Loading...');
|
|
useEffect(()=>{
|
|
let unmounted = false;
|
|
if(active) {
|
|
setSql('Loading...');
|
|
getSQLValue().then((value)=>{
|
|
if(!unmounted) {
|
|
setSql(value);
|
|
}
|
|
});
|
|
}
|
|
return ()=>{unmounted=true;};
|
|
}, [active]);
|
|
|
|
return <InputSQL
|
|
value={sql}
|
|
options={{
|
|
readOnly: true,
|
|
}}
|
|
isAsync={true}
|
|
/>;
|
|
}
|
|
|
|
SQLTab.propTypes = {
|
|
active: PropTypes.bool,
|
|
getSQLValue: PropTypes.func.isRequired,
|
|
};
|
|
|
|
/* 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}) {
|
|
let defaultTab = 'General';
|
|
let tabs = {};
|
|
let tabsClassname = {};
|
|
const [tabValue, setTabValue] = useState(0);
|
|
const classes = useStyles();
|
|
const firstElement = useRef();
|
|
const formRef = useRef();
|
|
const onScreenTracker = useRef(false);
|
|
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 */
|
|
if(onScreenTracker.current == false) {
|
|
/* Re-select the tab. If form is hidden then sometimes it is not selected */
|
|
setTabValue(tabValue);
|
|
onScreenTracker.current = true;
|
|
}
|
|
} else {
|
|
onScreenTracker.current = false;
|
|
}
|
|
|
|
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);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}, []);
|
|
|
|
/* 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);
|
|
}
|
|
if(modeSuppoted) {
|
|
let {visible, disabled, group, readonly, ...field} = f;
|
|
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 */
|
|
if(field.type === 'nested-tab') {
|
|
/* Pass on the top schema */
|
|
if(isNested) {
|
|
field.schema.top = schema.top;
|
|
} 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}/>
|
|
);
|
|
} else if(field.type === 'collection') {
|
|
/* If its a collection, let data grid view handle it */
|
|
let depsMap = [value[field.id]];
|
|
/* Pass on the top schema */
|
|
if(isNested) {
|
|
field.schema.top = schema.top;
|
|
} else {
|
|
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);
|
|
|
|
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)
|
|
);
|
|
} else if(field.type === 'group') {
|
|
groupLabels[field.id] = field.label;
|
|
if(!_visible) {
|
|
schema.filterGroups.push(field.label);
|
|
}
|
|
} else {
|
|
/* 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.
|
|
*/
|
|
tabs[group].push(
|
|
useMemo(()=><MappedFormControl
|
|
inputRef={(ele)=>{
|
|
if(firstEleRef && !firstEleRef.current) {
|
|
firstEleRef.current = ele;
|
|
}
|
|
}}
|
|
state={value}
|
|
key={field.id}
|
|
viewHelperProps={viewHelperProps}
|
|
name={field.id}
|
|
value={value[field.id]}
|
|
readonly={_readonly}
|
|
disabled={disabled}
|
|
visible={_visible}
|
|
{...field}
|
|
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={classes.controlRow}
|
|
/>, [
|
|
value[field.id],
|
|
_readonly,
|
|
disabled,
|
|
_visible,
|
|
hasError,
|
|
classes.controlRow,
|
|
...(evalFunc(null, field.deps) || []).map((dep)=>value[dep]),
|
|
])
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
/* Add the SQL tab if required */
|
|
let sqlTabActive = false;
|
|
let sqlTabName = gettext('SQL');
|
|
if(hasSQLTab) {
|
|
sqlTabActive = (Object.keys(tabs).length === tabValue);
|
|
/* Re-render and fetch the SQL tab when it is active */
|
|
tabs[sqlTabName] = [
|
|
useMemo(()=><SQLTab key="sqltab" active={sqlTabActive} getSQLValue={getSQLValue} />, [sqlTabActive]),
|
|
];
|
|
tabsClassname[sqlTabName] = classes.fullSpace;
|
|
}
|
|
|
|
useEffect(()=>{
|
|
firstElement.current && firstElement.current.focus();
|
|
}, []);
|
|
|
|
useEffect(()=>{
|
|
onTabChange && onTabChange(tabValue, Object.keys(tabs)[tabValue], sqlTabActive);
|
|
}, [tabValue]);
|
|
|
|
return (
|
|
<>
|
|
<Box height="100%" display="flex" flexDirection="column" className={className} ref={formRef}>
|
|
<Box>
|
|
<Tabs
|
|
value={tabValue}
|
|
onChange={(event, selTabValue) => {
|
|
setTabValue(selTabValue);
|
|
}}
|
|
// indicatorColor="primary"
|
|
variant="scrollable"
|
|
scrollButtons="auto"
|
|
action={(ref)=>ref && ref.updateIndicator()}
|
|
>
|
|
{Object.keys(tabs).map((tabName)=>{
|
|
return <Tab key={tabName} label={tabName} />;
|
|
})}
|
|
</Tabs>
|
|
</Box>
|
|
{Object.keys(tabs).map((tabName, i)=>{
|
|
return (
|
|
<TabPanel key={tabName} value={tabValue} index={i} classNameRoot={clsx(tabsClassname[tabName], isNested ? classes.nestedTabPanel : null)}
|
|
className={tabName != sqlTabName ? classes.nestedControl : null}>
|
|
{tabs[tabName]}
|
|
</TabPanel>
|
|
);
|
|
})}
|
|
</Box>
|
|
</>);
|
|
}
|
|
|
|
FormView.propTypes = {
|
|
value: PropTypes.any,
|
|
formErr: PropTypes.object,
|
|
schema: CustomPropTypes.schemaUI.isRequired,
|
|
viewHelperProps: PropTypes.object,
|
|
isNested: PropTypes.bool,
|
|
isDataGridForm: PropTypes.bool,
|
|
accessPath: PropTypes.array.isRequired,
|
|
dataDispatch: PropTypes.func,
|
|
hasSQLTab: PropTypes.bool,
|
|
getSQLValue: PropTypes.func,
|
|
onTabChange: PropTypes.func,
|
|
firstEleRef: CustomPropTypes.ref,
|
|
className: CustomPropTypes.className,
|
|
};
|