Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
FormView.jsx496 linesDownload Raw Back to SchemaView
1/////////////////////////////////////////////////////////////2//3// pgAdmin 4 - PostgreSQL Tools4//5// Copyright (C) 2013 - 2024, The pgAdmin Development Team6// This software is released under the PostgreSQL Licence7//8//////////////////////////////////////////////////////////////9 10import React, { useContext, useEffect, useRef, useState } from 'react';11import { Box, Tab, Tabs, Grid } from '@mui/material';12import { makeStyles } from '@mui/styles';13import _ from 'lodash';14import PropTypes from 'prop-types';15import clsx from 'clsx';16 17import { MappedFormControl } from './MappedControl';18import TabPanel from '../components/TabPanel';19import DataGridView from './DataGridView';20import { SCHEMA_STATE_ACTIONS, StateUtilsContext } from '.';21import { FormNote, InputSQL } from '../components/FormComponents';22import gettext from 'sources/gettext';23import { evalFunc } from 'sources/utils';24import CustomPropTypes from '../custom_prop_types';25import { useOnScreen } from '../custom_hooks';26import { DepListenerContext } from './DepListener';27import FieldSetView from './FieldSetView';28 29const useStyles = makeStyles((theme)=>({30  fullSpace: {31    padding: 0,32    height: '100%',33    overflow: 'hidden',34  },35  controlRow: {36    marginBottom: theme.spacing(1),37  },38  nestedTabPanel: {39    backgroundColor: theme.otherVars.headerBg,40  },41  nestedControl: {42    height: 'unset',43  },44  fullControl: {45    display: 'flex',46    flexDirection: 'column'47  },48  errorMargin: {49    /* Error footer space */50    paddingBottom: '36px !important',51  },52  sqlTabInput: {53    border: 0,54  },55  nonTabPanel: {56    padding: 0,57    background: 'inherit',58  },59  nonTabPanelContent: {60    height: 'unset'61  }62}));63 64/* Optional SQL tab */65function SQLTab({active, getSQLValue}) {66  const classes = useStyles();67  const [sql, setSql] = useState('Loading...');68  useEffect(()=>{69    let unmounted = false;70    if(active) {71      setSql('Loading...');72      getSQLValue().then((value)=>{73        if(!unmounted) {74          setSql(value);75        }76      });77    }78    return ()=>{unmounted=true;};79  }, [active]);80 81  return <InputSQL82    value={sql}83    options={{84      readOnly: true,85    }}86    readonly={true}87    className={classes.sqlTabInput}88  />;89}90 91SQLTab.propTypes = {92  active: PropTypes.bool,93  getSQLValue: PropTypes.func.isRequired,94};95 96export function getFieldMetaData(field, schema, value, viewHelperProps, onlyModeCheck=false) {97  let retData = {98    readonly: false,99    disabled: false,100    visible: true,101    editable: true,102    canAdd: true,103    canEdit: false,104    canDelete: true,105    modeSupported: true,106    canAddRow: true,107  };108 109  if(field.mode) {110    retData.modeSupported = (field.mode.indexOf(viewHelperProps.mode) > -1);111  }112  if(!retData.modeSupported) {113    return retData;114  }115 116  if(onlyModeCheck) {117    return retData;118  }119 120  let {visible, disabled, readonly, editable} = field;121  let verInLimit;122 123  if (_.isUndefined(viewHelperProps.serverInfo)) {124    verInLimit= true;125  } else {126    verInLimit = ((_.isUndefined(field.server_type) ? true :127      (viewHelperProps.serverInfo.type in field.server_type)) &&128      (_.isUndefined(field.min_version) ? true :129        (viewHelperProps.serverInfo.version >= field.min_version)) &&130      (_.isUndefined(field.max_version) ? true :131        (viewHelperProps.serverInfo.version <= field.max_version)));132  }133 134  retData.readonly = viewHelperProps.inCatalog || (viewHelperProps.mode == 'properties');135  if(!retData.readonly) {136    retData.readonly = evalFunc(schema, readonly, value);137  }138 139  let _visible = verInLimit;140  _visible = _visible && evalFunc(schema, _.isUndefined(visible) ? true : visible, value);141  retData.visible = Boolean(_visible);142 143  retData.disabled = Boolean(evalFunc(schema, disabled, value));144 145  retData.editable = !(viewHelperProps.inCatalog || (viewHelperProps.mode == 'properties'));146  if(retData.editable) {147    retData.editable = evalFunc(schema, _.isUndefined(editable) ? true : editable, value);148  }149 150  let {canAdd, canEdit, canDelete, canReorder, canAddRow } = field;151  retData.canAdd = _.isUndefined(canAdd) ? retData.canAdd : evalFunc(schema, canAdd, value);152  retData.canAdd = !retData.disabled && retData.canAdd;153  retData.canEdit = _.isUndefined(canEdit) ? retData.canEdit : evalFunc(schema, canEdit, value);154  retData.canEdit = !retData.disabled && retData.canEdit;155  retData.canDelete = _.isUndefined(canDelete) ? retData.canDelete : evalFunc(schema, canDelete, value);156  retData.canDelete = !retData.disabled && retData.canDelete;157  retData.canReorder =_.isUndefined(canReorder) ? retData.canReorder : evalFunc(schema, canReorder, value);158  retData.canAddRow = _.isUndefined(canAddRow) ? retData.canAddRow : evalFunc(schema, canAddRow, value);159  return retData;160}161 162/* The first component of schema view form */163export default function FormView({164  value, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab,165  getSQLValue, onTabChange, firstEleRef, className, isDataGridForm=false, isTabView=true, visible}) {166  let defaultTab = 'General';167  let tabs = {};168  let tabsClassname = {};169  const [tabValue, setTabValue] = useState(0);170  const classes = useStyles();171  const firstEleID = useRef();172  const formRef = useRef();173  const onScreenTracker = useRef(false);174  const depListener = useContext(DepListenerContext);175  let groupLabels = {};176  const schemaRef = useRef(schema);177  const stateUtils = useContext(StateUtilsContext);178 179  let isOnScreen = useOnScreen(formRef);180 181  useEffect(()=>{182    if(isOnScreen) {183      /* Don't do it when the form is alredy visible */184      if(!onScreenTracker.current) {185        /* Re-select the tab. If form is hidden then sometimes it is not selected */186        setTabValue((prev)=>prev);187        onScreenTracker.current = true;188      }189    } else {190      onScreenTracker.current = false;191    }192  }, [isOnScreen]);193 194  useEffect(()=>{195    /* Calculate the fields which depends on the current field */196    if(!isDataGridForm) {197      schemaRef.current.fields.forEach((field)=>{198        /* Self change is also dep change */199        if(field.depChange || field.deferredDepChange) {200          depListener.addDepListener(accessPath.concat(field.id), accessPath.concat(field.id), field.depChange, field.deferredDepChange);201        }202        (evalFunc(null, field.deps) || []).forEach((dep)=>{203          // when dep is a string then prepend the complete accessPath204          let source = accessPath.concat(dep);205 206          // but when dep is an array, then the intention is to provide the exact accesspath207          if(_.isArray(dep)) {208            source = dep;209          }210          if(field.depChange || field.deferredDepChange) {211            depListener.addDepListener(source, accessPath.concat(field.id), field.depChange, field.deferredDepChange);212          }213          if(field.depChange || field.deferredDepChange) {214            depListener.addDepListener(source, accessPath.concat(field.id), field.depChange, field.deferredDepChange);215          }216        });217      });218      return ()=>{219        /* Cleanup the listeners when unmounting */220        depListener.removeDepListener(accessPath);221      };222    }223  }, []);224 225  /* Upon reset, set the tab to first */226  useEffect(()=>{227    setTabValue(0);228  }, [stateUtils.formResetKey]);229 230  let fullTabs = [];231  let inlineComponents = [];232  let inlineCompGroup = null;233 234  /* Prepare the array of components based on the types */235  for(const field of schemaRef.current.fields) {236    let {visible, disabled, readonly, canAdd, canEdit, canDelete, canReorder, canAddRow, modeSupported} =237      getFieldMetaData(field, schema, value, viewHelperProps);238 239    if(modeSupported) {240      let {group, CustomControl} = field;241      if(field.type === 'group') {242        groupLabels[field.id] = field.label;243        if(!visible) {244          schemaRef.current.filterGroups.push(field.label);245        }246        continue;247      }248      group = groupLabels[group] || group || defaultTab;249 250      if(!tabs[group]) tabs[group] = [];251 252      /* Lets choose the path based on type */253      if(field.type === 'nested-tab') {254        /* Pass on the top schema */255        if(isNested) {256          field.schema.top = schemaRef.current.top;257        } else {258          field.schema.top = schema;259        }260        tabs[group].push(261          <FormView key={`nested${tabs[group].length}`} value={value} viewHelperProps={viewHelperProps}262            schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} isDataGridForm={isDataGridForm}263            {...field} visible={visible}/>264        );265      } else if(field.type === 'nested-fieldset') {266        /* Pass on the top schema */267        if(isNested) {268          field.schema.top = schemaRef.current.top;269        } else {270          field.schema.top = schema;271        }272        tabs[group].push(273          <FieldSetView key={`nested${tabs[group].length}`} value={value} viewHelperProps={viewHelperProps}274            schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} isDataGridForm={isDataGridForm}275            controlClassName={classes.controlRow}276            {...field} visible={visible}/>277        );278      } else if(field.type === 'collection') {279        /* If its a collection, let data grid view handle it */280        /* Pass on the top schema */281        if(isNested) {282          field.schema.top = schemaRef.current.top;283        } else {284          field.schema.top = schemaRef.current;285        }286 287        if(!_.isUndefined(field.fixedRows)) {288          canAdd = false;289          canDelete = false;290        }291 292        const props = {293          key: field.id, value: value[field.id] || [], viewHelperProps: viewHelperProps,294          schema: field.schema, accessPath: accessPath.concat(field.id), dataDispatch: dataDispatch,295          containerClassName: classes.controlRow, ...field, canAdd: canAdd, canReorder: canReorder,296          canEdit: canEdit, canDelete: canDelete,297          visible: visible, canAddRow: canAddRow, onDelete: field.onDelete, canSearch: field.canSearch,298          expandEditOnAdd: field.expandEditOnAdd,299          fixedRows: (viewHelperProps.mode == 'create' ? field.fixedRows : undefined),300          addOnTop: Boolean(field.addOnTop)301        };302 303        if(CustomControl) {304          tabs[group].push(<CustomControl {...props}/>);305        } else {306          tabs[group].push(<DataGridView {...props}/>);307        }308      } else {309        /* Its a form control */310        const hasError = _.isEqual(accessPath.concat(field.id), stateUtils.formErr.name);311        /* When there is a change, the dependent values can change312         * lets pass the new changes to dependent and get the new values313         * from there as well.314         */315        if(field.isFullTab) {316          tabsClassname[group] = classes.fullSpace;317          fullTabs.push(group);318        }319 320        const id = field.id || `control${tabs[group].length}`;321        if(visible && !disabled && !firstEleID.current) {322          firstEleID.current = field.id;323        }324 325        let currentControl = <MappedFormControl326          inputRef={(ele)=>{327            if(firstEleRef && firstEleID.current === field.id) {328              if(typeof firstEleRef == 'function') {329                firstEleRef(ele);330              } else {331                firstEleRef.current = ele;332              }333            }334          }}335          state={value}336          key={id}337          viewHelperProps={viewHelperProps}338          name={id}339          value={value[id]}340          {...field}341          id={id}342          readonly={readonly}343          disabled={disabled}344          visible={visible}345          onChange={(changeValue)=>{346            /* Get the changes on dependent fields as well */347            dataDispatch({348              type: SCHEMA_STATE_ACTIONS.SET_VALUE,349              path: accessPath.concat(id),350              value: changeValue,351            });352          }}353          hasError={hasError}354          className={classes.controlRow}355          noLabel={field.isFullTab}356          memoDeps={[357            value[id],358            readonly,359            disabled,360            visible,361            hasError,362            classes.controlRow,363            ...(evalFunc(null, field.deps) || []).map((dep)=>value[dep]),364          ]}365        />;366 367        if(field.isFullTab && field.helpMessage) {368          currentControl = (<React.Fragment key={`coll-${field.id}`}>369            <FormNote key={`note-${field.id}`} text={field.helpMessage}/>370            {currentControl}371          </React.Fragment>);372        }373 374        if(field.inlineNext) {375          inlineComponents.push(React.cloneElement(currentControl, {376            withContainer: false, controlGridBasis: 3377          }));378          inlineCompGroup = group;379        } else if(inlineComponents?.length > 0) {380          inlineComponents.push(React.cloneElement(currentControl, {381            withContainer: false, controlGridBasis: 3382          }));383          tabs[group].push(384            <Grid container spacing={0} key={`ic-${inlineComponents[0].key}`} className={classes.controlRow} rowGap="8px">385              {inlineComponents}386            </Grid>387          );388          inlineComponents = [];389          inlineCompGroup = null;390        } else {391          tabs[group].push(currentControl);392        }393      }394    }395  }396 397  if(inlineComponents?.length > 0) {398    tabs[inlineCompGroup].push(399      <Grid container spacing={0} key={`ic-${inlineComponents[0].key}`} className={classes.controlRow} rowGap="8px">400        {inlineComponents}401      </Grid>402    );403  }404 405  let finalTabs = _.pickBy(tabs, (v, tabName)=>schemaRef.current.filterGroups.indexOf(tabName) <= -1);406 407  /* Add the SQL tab if required */408  let sqlTabActive = false;409  let sqlTabName = gettext('SQL');410  if(hasSQLTab) {411    sqlTabActive = (Object.keys(finalTabs).length === tabValue);412    /* Re-render and fetch the SQL tab when it is active */413    finalTabs[sqlTabName] = [414      <SQLTab key="sqltab" active={sqlTabActive} getSQLValue={getSQLValue} />,415    ];416    tabsClassname[sqlTabName] = classes.fullSpace;417    fullTabs.push(sqlTabName);418  }419 420  useEffect(()=>{421    onTabChange?.(tabValue, Object.keys(tabs)[tabValue], sqlTabActive);422  }, [tabValue]);423 424  /* check whether form is kept hidden by visible prop */425  if(!_.isUndefined(visible) && !visible) {426    return <></>;427  }428 429  if(isTabView) {430    return (431      <Box height="100%" display="flex" flexDirection="column" className={className} ref={formRef} data-test="form-view">432        <Box>433          <Tabs434            value={tabValue}435            onChange={(event, selTabValue) => {436              setTabValue(selTabValue);437            }}438            variant="scrollable"439            scrollButtons="auto"440            action={(ref)=>ref?.updateIndicator()}441          >442            {Object.keys(finalTabs).map((tabName)=>{443              return <Tab key={tabName} label={tabName} data-test={tabName}/>;444            })}445          </Tabs>446        </Box>447        {Object.keys(finalTabs).map((tabName, i)=>{448          let contentClassName = [stateUtils.formErr.message ? classes.errorMargin : null];449          if(fullTabs.indexOf(tabName) == -1) {450            contentClassName.push(classes.nestedControl);451          } else {452            contentClassName.push(classes.fullControl);453          }454          return (455            <TabPanel key={tabName} value={tabValue} index={i} classNameRoot={clsx(tabsClassname[tabName], isNested ? classes.nestedTabPanel : null)}456              className={clsx(contentClassName)} data-testid={tabName}>457              {finalTabs[tabName]}458            </TabPanel>459          );460        })}461      </Box>);462  } else {463    let contentClassName = [classes.nonTabPanelContent, stateUtils.formErr.message ? classes.errorMargin : null];464    return (465      <Box height="100%" display="flex" flexDirection="column" className={clsx(className)} ref={formRef} data-test="form-view">466        <TabPanel value={tabValue} index={0} classNameRoot={classes.nonTabPanel}467          className={clsx(contentClassName)}>468          {Object.keys(finalTabs).map((tabName)=>{469            return (470              <React.Fragment key={tabName}>{finalTabs[tabName]}</React.Fragment>471            );472          })}473        </TabPanel>474      </Box>);475  }476}477 478FormView.propTypes = {479  value: PropTypes.any,480  schema: CustomPropTypes.schemaUI.isRequired,481  viewHelperProps: PropTypes.object,482  isNested: PropTypes.bool,483  isDataGridForm: PropTypes.bool,484  isTabView: PropTypes.bool,485  visible: PropTypes.oneOfType([486    PropTypes.bool, PropTypes.func,487  ]),488  accessPath: PropTypes.array.isRequired,489  dataDispatch: PropTypes.func,490  hasSQLTab: PropTypes.bool,491  getSQLValue: PropTypes.func,492  onTabChange: PropTypes.func,493  firstEleRef: CustomPropTypes.ref,494  className: CustomPropTypes.className,495};496 
codekingpro/portable-devtools · Team Ai