/
Ao
/
MaxReport
Обзор
Документация
Войти
/
Ao
/
MaxReport
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
pgsql/pgAdmin 4/web/pgadmin/static/js/SchemaView/FormView.jsx
317 строк
10 KB
AoAnima
first_commit
13 июл 2026, 17:47
13 июл 2026, 17:47
f1a670a
Код
Авторство
О чём код?
///////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2026, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { Box, Tab, Tabs } from '@mui/material'; import _ from 'lodash'; import PropTypes from 'prop-types'; import { FormFooterMessage, MESSAGE_TYPE, FormNote } from 'sources/components/FormComponents'; import TabPanel from 'sources/components/TabPanel'; import { useOnScreen } from 'sources/custom_hooks'; import CustomPropTypes from 'sources/custom_prop_types'; import gettext from 'sources/gettext'; import { FieldControl } from './FieldControl'; import { SQLTab } from './SQLTab'; import { FormContentBox } from './StyledComponents'; import { SchemaStateContext } from './SchemaState'; import { useFieldSchema, useFieldValue, useSchemaStateSubscriber, } from './hooks'; import { registerView, View } from './registry'; import { createFieldControls, listenDepChanges } from './utils'; import FormViewTab from './FormViewTab'; const ErrorMessageBox = () => { const [key, setKey] = useState(0); const schemaState = useContext(SchemaStateContext); const onErrClose = useCallback(() => { const err = { ...schemaState.errors, message: '' }; // Unset the error message, but not the name. schemaState.setError(err); }, [schemaState]); const errors = schemaState.errors; const message = errors?.message || ''; useEffect(() => { // Refresh on message changes. return schemaState.subscribe( ['errors', 'message'], () => setKey(Date.now()), 'states' ); }, [key]); return <FormFooterMessage type={MESSAGE_TYPE.ERROR} message={message} onClose={onErrClose} />; }; // The first component of schema view form. export default function FormView({ accessPath, schema=null, isNested=false, dataDispatch, className, hasSQLTab, getSQLValue, isTabView=true, viewHelperProps, field, showError=false, resetKey, focusOnFirstInput=false }) { const [key, setKey] = useState(0); const subscriberManager = useSchemaStateSubscriber(setKey); const schemaState = useContext(SchemaStateContext); const value = useFieldValue(accessPath, schemaState); const { visible } = useFieldSchema( field, accessPath, value, viewHelperProps, schemaState, subscriberManager ); const [tabValue, setTabValue] = useState(0); const formRef = useRef(); const onScreenTracker = useRef(false); let isOnScreen = useOnScreen(formRef); if (!schema) schema = field.schema; // Set focus on the first focusable element. useEffect(() => { if (!focusOnFirstInput) return; setTimeout(() => { const formEle = formRef.current; if (!formEle) return; const activeTabElement = formEle.querySelector( '[data-test="tabpanel"]:not([hidden])' ); if (!activeTabElement) return; // Find the first focusable input, which is either: // * An editable Input element. // * A select element, which is not disabled. // * An href element. // * Any element with 'tabindex', but - tabindex is not set to '-1'. const firstFocussableElement = activeTabElement.querySelector([ 'button:not([role="tab"])', '[href]', 'input:not(disabled)', 'select:not(disabled)', 'textarea', '[tabindex]:not([tabindex="-1"]):not([data-test="tabpanel"])', 'div[class="cm-content"]:not([aria-readonly="true"])', ].join(', ')); if (firstFocussableElement) firstFocussableElement.focus(); }, 200); }, [tabValue]); useEffect(() => { // Refresh on message changes. return subscriberManager.current?.add( schemaState, ['errors', 'message'], 'states', (newState, prevState) => { if (_.isUndefined(newState) || _.isUndefined(prevState)) subscriberManager.current?.signal(); } ); }, [key]); useEffect(() => { if (!visible) return; if(isOnScreen) { /* Don't do it when the form is alredy visible */ if(!onScreenTracker.current) { /* Re-select the tab. If form is hidden then sometimes it is not selected */ setTabValue((prev)=>prev); onScreenTracker.current = true; } } else { onScreenTracker.current = false; } }, [isOnScreen]); listenDepChanges( accessPath, field, schemaState, () => subscriberManager.current?.signal() ); // Upon reset, set the tab to first. useEffect(() => { if (!visible || !resetKey) return; if (resetKey) { setTabValue(0); } }, [resetKey]); const finalGroups = useMemo( () => createFieldControls({ schema, schemaState, accessPath, viewHelperProps, dataDispatch }), [schema, schemaState, accessPath, viewHelperProps, dataDispatch] ); // Check whether form is kept hidden by visible prop. if(!finalGroups || (!_.isUndefined(visible) && !visible)) { return <></>; } const isSingleCollection = () => { const DataGridView = View('DataGridView'); return ( finalGroups.length == 1 && finalGroups[0].controls.length == 1 && finalGroups[0].controls[0].control == DataGridView ); }; if(isTabView) { return ( <> <FormContentBox height="100%" display="flex" flexDirection="column" className={className} ref={formRef} data-test="form-view"> <Box> <Tabs value={tabValue} onChange={(ev, nextTabIndex) => { setTabValue(nextTabIndex); }} variant="scrollable" scrollButtons="auto" action={(ref) => ref?.updateIndicator()} >{ finalGroups.map((tabGroup, idx) => <FormViewTab key={tabGroup.id} tabGroup={tabGroup} idx={idx} tabValue={tabValue} /> ) }{hasSQLTab && <Tab key={'sql-tab'} label={gettext('SQL')} data-test={'SQL'} /> }</Tabs> </Box> { finalGroups.map((group, idx) => { let contentClassName = [ group.isFullTab ? 'FormView-fullControl' : 'FormView-nestedControl', schemaState.errors?.message ? 'FormView-errorMargin' : null ]; let id = group.id.replace(' ', ''); return ( <TabPanel key={id} value={tabValue} index={idx} classNameRoot={[ group.className, (isNested ? 'FormView-nestedTabPanel' : null) ].join(' ')} className={contentClassName.join(' ')} data-testid={group.id}> { group.isFullTab && group.field?.helpMessage ? <FormNote key={`note-${group.field.id}`} text={group.field.helpMessage}/> : <></> } { group.controls.map( (item, idx) => <FieldControl item={item} key={`${item.controlProps.id}-${idx}`} schemaId={schema._id} /> ) } </TabPanel> ); }) } { hasSQLTab && <TabPanel key={'sql-tab'} value={tabValue} index={finalGroups.length} classNameRoot={'FormView-fullSpace'} data-testid={'SQL'} > <SQLTab active={(Object.keys(finalGroups).length === tabValue)} getSQLValue={getSQLValue} /> </TabPanel> } </FormContentBox> { showError && <ErrorMessageBox /> } </> ); } else { let contentClassName = [ isSingleCollection() ? 'FormView-singleCollectionPanelContent' : 'FormView-nonTabPanelContent', (schemaState.errors?.message ? 'FormView-errorMargin' : null), (finalGroups.some((g)=>g.isFullTab) ? 'FormView-fullControl' : ''), ]; return ( <> <FormContentBox height="100%" display="flex" flexDirection="column" className={className} ref={formRef} data-test="form-view" > <TabPanel value={tabValue} index={0} classNameRoot={[ isSingleCollection() ? 'FormView-singleCollectionPanel' : 'FormView-nonTabPanel', className, (finalGroups.some((g)=>g.isFullTab) ? 'FormView-fullSpace' : ''), ].join(' ')} className={contentClassName.join(' ')}> { finalGroups.map((group, idx) => <React.Fragment key={`${group.id}-${idx}`}>{ group.controls.map( (item, idx) => <FieldControl item={item} key={`${item.controlProps.id}-${idx}`} schemaId={schema._id} /> ) }</React.Fragment> ) } { hasSQLTab && <SQLTab active={true} getSQLValue={getSQLValue} /> } </TabPanel> </FormContentBox> { showError && <ErrorMessageBox /> } </> ); } } FormView.propTypes = { schema: CustomPropTypes.schemaUI, viewHelperProps: PropTypes.object, isNested: PropTypes.bool, isTabView: PropTypes.bool, accessPath: PropTypes.array.isRequired, dataDispatch: PropTypes.func, hasSQLTab: PropTypes.bool, getSQLValue: PropTypes.func, className: CustomPropTypes.className, field: PropTypes.object, showError: PropTypes.bool, resetKey: PropTypes.number, focusOnFirstInput: PropTypes.bool, }; registerView(FormView, 'FormView');