/
Ao
/
MaxReport
Обзор
Документация
Войти
/
Ao
/
MaxReport
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
pgsql/pgAdmin 4/web/pgadmin/static/js/SchemaView/MappedControl.jsx
453 строки
14 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 { useCallback, useContext, useMemo, useState } from 'react'; import _ from 'lodash'; import PropTypes from 'prop-types'; import { FormButton, FormInputCheckbox, FormInputColor, FormInputDateTimePicker, FormInputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold, FormInputSQL, FormInputSelect, FormInputSelectThemes, FormInputSwitch, FormInputText, FormInputToggle, FormNote, InputCheckbox, InputDateTimePicker, InputFileSelect, InputRadio, InputSQL,InputSelect, InputSwitch, InputText, InputTree, PlainString, } from 'sources/components/FormComponents'; import { SelectRefresh } from 'sources/components/SelectRefresh'; import Privilege from 'sources/components/Privilege'; import { useIsMounted } from 'sources/custom_hooks'; import CustomPropTypes from 'sources/custom_prop_types'; import { evalFunc } from 'sources/utils'; import { SchemaStateContext } from './SchemaState'; import { isValueEqual } from './common'; import { useFieldOptions, useFieldValue, useFieldError, useSchemaStateSubscriber, } from './hooks'; import { listenDepChanges } from './utils'; import { InputColor } from '../components/FormComponents'; /* Control mapping for form view */ function MappedFormControlBase({ id, type, state, onChange, className, inputRef, visible, withContainer, controlGridBasis, noLabel, ...props }) { let name = id; const onTextChange = useCallback((e) => { let val = e; if(e?.target) { val = e.target.value; } onChange?.(val); }, []); const value = state; const onSqlChange = useCallback((changedValue) => { onChange?.(changedValue); }, []); const onTreeSelection = useCallback((selectedValues)=> { onChange?.(selectedValues); }, []); if (!visible) { return <></>; } if (name && _.isNumber(name)) { name = String(name); } /* The mapping uses Form* components as it comes with labels */ switch (type) { case 'int': return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='int' />; case 'numeric': return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='numeric' />; case 'tel': return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='tel' />; case 'text': return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} />; case 'multiline': return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} controlProps={{ multiline: true }} {...props} />; case 'password': return <FormInputText name={name} value={value} onChange={onTextChange} className={className} type='password' inputRef={inputRef} {...props} />; case 'select': return <FormInputSelect name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} />; case 'select-refresh': return <SelectRefresh name={name} value={value} onChange={onTextChange} className={className} {...props} />; case 'switch': return <FormInputSwitch name={name} value={value} className={className} onChange={(e) => onTextChange(e.target.checked, e.target.name)} withContainer={withContainer} controlGridBasis={controlGridBasis} {...props} />; case 'checkbox': return <FormInputCheckbox name={name} value={value} className={className} onChange={(e) => onTextChange(e.target.checked, e.target.name)} {...props} />; case 'toggle': return <FormInputToggle name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} />; case 'color': return <FormInputColor name={name} value={value} onChange={onTextChange} className={className} {...props} />; case 'file': return <FormInputFileSelect name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} />; case 'sql': return <FormInputSQL name={name} value={value} onChange={onSqlChange} className={className} noLabel={noLabel} inputRef={inputRef} {...props} />; case 'note': return <FormNote className={className} {...props} />; case 'datetimepicker': return <FormInputDateTimePicker name={name} value={value} onChange={onTextChange} className={className} {...props} />; case 'keyboardShortcut': return <FormInputKeyboardShortcut name={name} value={value} onChange={onTextChange} {...props} />; case 'threshold': return <FormInputQueryThreshold name={name} value={value} onChange={onTextChange} {...props} />; case 'theme': return <FormInputSelectThemes name={name} value={value} onChange={onTextChange} {...props} />; case 'button': return <FormButton name={name} value={value} className={className} onClick={props.onClick} {...props} />; case 'tree': return <InputTree name={name} treeData={props.treeData} onChange={onTreeSelection} {...props} />; default: return <PlainString value={value} {...props} />; } } MappedFormControlBase.propTypes = { type: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, ]).isRequired, state: PropTypes.any, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), onChange: PropTypes.func, className: PropTypes.oneOfType([ PropTypes.string, PropTypes.object, ]), visible: PropTypes.bool, inputRef: CustomPropTypes.ref, noLabel: PropTypes.bool, onClick: PropTypes.func, withContainer: PropTypes.bool, controlGridBasis: PropTypes.number, treeData: PropTypes.oneOfType([ PropTypes.array, PropTypes.instanceOf(Promise), PropTypes.func] ), }; /* Control mapping for grid cell view */ function MappedCellControlBase({ cell, value, id, optionsLoaded, onCellChange, visible, reRenderRow, inputRef, ...props }) { let name = id; const onTextChange = useCallback((e) => { let val = e; if (e?.target) { val = e.target.value; } onCellChange?.(val); }, []); const onRadioChange = useCallback((e) => { let val =e; if(e?.target) { val = e.target.checked; } onCellChange?.(val); }); const onSqlChange = useCallback((val) => { onCellChange?.(val); }, []); /* Some grid cells are based on options selected in other cells. * lets trigger a re-render for the row if optionsLoaded */ const optionsLoadedRerender = useCallback((res) => { /* optionsLoaded is called when select options are fetched */ optionsLoaded?.(res); reRenderRow?.(); }, []); if (!visible) { return <></>; } if (name && _.isNumber(name)) { name = String('name'); } /* The mapping does not need Form* components as labels are not needed for grid cells */ switch(cell) { case 'int': return <InputText name={name} value={value} onChange={onTextChange} ref={inputRef} {...props} type='int'/>; case 'numeric': return <InputText name={name} value={value} onChange={onTextChange} ref={inputRef} {...props} type='numeric'/>; case 'text': return <InputText name={name} value={value} onChange={onTextChange} ref={inputRef} {...props}/>; case 'password': return <InputText name={name} value={value} onChange={onTextChange} ref={inputRef} {...props} type='password'/>; case 'select': return <InputSelect name={name} value={value} onChange={onTextChange} optionsLoaded={optionsLoadedRerender} inputRef={inputRef} {...props}/>; case 'switch': return <InputSwitch name={name} value={value} onChange={(e)=>onTextChange(e.target.checked, e.target.name)} {...props} />; case 'checkbox': return <InputCheckbox name={name} value={value} onChange={(e)=>onTextChange(e.target.checked, e.target.name)} {...props} />; case 'privilege': return <Privilege name={name} value={value} onChange={onTextChange} {...props}/>; case 'datetimepicker': return <InputDateTimePicker name={name} value={value} onChange={onTextChange} {...props}/>; case 'sql': return <InputSQL name={name} value={value} onChange={onSqlChange} {...props} />; case 'color': return <InputColor name={name} value={value} onChange={onTextChange} {...props} />; case 'file': return <InputFileSelect name={name} value={value} onChange={onTextChange} inputRef={props.inputRef} {...props} />; case 'keyCode': return <InputText name={name} value={value} onChange={onTextChange} {...props} type='text' maxlength={1} />; case 'radio': return <InputRadio name={name} value={value} onChange={onRadioChange} disabled={props.disabled} {...props} ></InputRadio>; default: return <PlainString value={value} {...props} />; } } MappedCellControlBase.propTypes = { cell: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, ]).isRequired, value: PropTypes.any, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, onChange: PropTypes.func, reRenderRow: PropTypes.func, optionsLoaded: PropTypes.func, onCellChange: PropTypes.func, visible: PropTypes.bool, disabled: PropTypes.bool, inputRef: CustomPropTypes.ref, }; const ALLOWED_PROPS_FIELD_COMMON = [ 'mode', 'value', 'readonly', 'disabled', 'hasError', 'id', 'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef', 'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis', 'orientation', 'isvalidate', 'fields', 'radioType', 'hideBrowseButton', 'btnName', 'hidden', 'withContainer', 'controlGridBasis', 'hasCheckbox', 'treeData', 'labelTooltip' ]; const ALLOWED_PROPS_FIELD_FORM = [ 'type', 'onChange', 'state', 'noLabel', 'text','onClick' ]; const ALLOWED_PROPS_FIELD_CELL = [ 'cell', 'onCellChange', 'reRenderRow', 'validate', 'disabled', 'readonly', 'radioType', 'hideBrowseButton', 'hidden', 'row', ]; export const StaticMappedFormControl = ({accessPath, field, ...props}) => { const schemaState = useContext(SchemaStateContext); const state = schemaState.value(accessPath); const newProps = { ...props, state, noLabel: field.isFullTab, ...field, onChange: () => { /* Do nothing */ }, }; const visible = evalFunc(null, field.visible, state); if (visible === false) return <></>; return useMemo( () => <MappedFormControlBase { ..._.pick( newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_FORM) ) } />, [] ); }; StaticMappedFormControl.propTypes = { accessPath: PropTypes.array.isRequired, field: PropTypes.object, }; export const MappedFormControl = ({ accessPath, dataDispatch, field, onChange, ...props }) => { const checkIsMounted = useIsMounted(); const [key, setKey] = useState(0); const subscriberManager = useSchemaStateSubscriber(setKey); const schemaState = useContext(SchemaStateContext); const state = schemaState.data; const value = useFieldValue(accessPath, schemaState, subscriberManager); const options = useFieldOptions(accessPath, schemaState, subscriberManager); const {hasError} = useFieldError(accessPath, schemaState, subscriberManager); const avoidRenderingWhenNotMounted = (...args) => { if (checkIsMounted()) subscriberManager.current?.signal(...args); }; const origOnChange = onChange; onChange = (changedValue) => { if (!origOnChange || !checkIsMounted()) return; // We don't want the 'onChange' to be executed for the same value to avoid // rerendering of the control, top component may still be rerendered on the // change of the value. const currValue = schemaState.value(accessPath); if (!isValueEqual(changedValue, currValue)) origOnChange(changedValue); }; const depVals = listenDepChanges( accessPath, field, schemaState, avoidRenderingWhenNotMounted ); let newProps = { ...props, state: value, noLabel: field.isFullTab, ...field, onChange: onChange, dataDispatch: dataDispatch, ...options, hasError, }; // Pass current dependency values into controlProps so that // child components (e.g., SelectRefresh) can read the live // unsaved form values for dependent fields. if (depVals && field.controlProps?.refreshDeps) { const refreshDeps = field.controlProps.refreshDeps; const depKeys = Object.keys(refreshDeps); const currentDepValues = {}; depKeys.forEach((paramName, idx) => { currentDepValues[paramName] = depVals[idx]; }); newProps.controlProps = { ...newProps.controlProps, currentDepValues, }; } if (typeof (field.type) === 'function') { const typeProps = evalFunc(null, field.type, state); newProps = { ...newProps, ...typeProps, }; } let origOnClick = newProps.onClick; newProps.onClick = ()=>{ origOnClick?.(); }; // FIXME:: Get this list from the option registry. const memDeps = ['disabled', 'visible', 'readonly'].map( option => options[option] ); memDeps.push(value); memDeps.push(hasError); memDeps.push(key); memDeps.push(JSON.stringify(accessPath)); memDeps.push(depVals); // Filter out garbage props if any using ALLOWED_PROPS_FIELD. return useMemo( () => <MappedFormControlBase { ..._.pick( newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_FORM) ) } />, [...memDeps] ); }; MappedFormControl.propTypes = { accessPath: PropTypes.array.isRequired, field: PropTypes.object, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, }; export const MappedCellControl = (props) => { const newProps = _.pick( props, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_CELL) );; // Filter out garbage props if any using ALLOWED_PROPS_FIELD. return <MappedCellControlBase {...newProps}/>; };