mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Fixes the Variable Schema UI issues and InlineView bug reported in #7884
* Show the icon for the 'Reset' button. (Reference #7884) * Reload the server list after connecting to a server in the 'New connection' dialog (QueryTool). (Reference: #7884) * Pass the grid path during the bulk update (click on a radio action) * Don't assign the cell value to the 'rowValue' variable. * Don't rely on the 'optionsLoaded' for setting the variable types as it is loaded asynchronously, and variable types data may not be available while rendering the 'value' cell. (Fixes #7884) * Fixed a type while checking for the 'inline-group'. fixes (#7884) * 'vnameOptions' can be a Promise function too, hence - taken care accrodingly. * Introduced a parameter 'reloadOnDepChanges' in the BaseSchemaUI field to force reload the control on value change for one of the dependencies. * Reload on the components in case of dependent value changes. * Introduced 'useSchemaStateSubscriber', which generates a state subscriber mananager instance. It helps multiple subscribers in a single control as we could have multiple subscribe within a control. (For example - value, options, errors, etc). * Fixed all the issues reported (#7884)
This commit is contained in:
parent
98d703645c
commit
5e96f0fd61
@ -96,7 +96,8 @@ class ForeignKeyHeaderSchema extends BaseUISchema {
|
|||||||
return state._disable_references;
|
return state._disable_references;
|
||||||
}
|
}
|
||||||
},{
|
},{
|
||||||
id: 'referenced', label: gettext('Referencing'), editable: false, deps: ['references'],
|
id: 'referenced', label: gettext('Referencing'), editable: false,
|
||||||
|
deps: ['references'],
|
||||||
type: (state)=>{
|
type: (state)=>{
|
||||||
return {
|
return {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
@ -59,6 +59,8 @@ export default class VariableSchema extends BaseUISchema {
|
|||||||
this.varTypes = {};
|
this.varTypes = {};
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
this.allReadOnly = false;
|
this.allReadOnly = false;
|
||||||
|
|
||||||
|
setTimeout(() => this.setVarTypes(vnameOptions), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
setAllReadOnly(isReadOnly) {
|
setAllReadOnly(isReadOnly) {
|
||||||
@ -66,11 +68,19 @@ export default class VariableSchema extends BaseUISchema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setVarTypes(options) {
|
setVarTypes(options) {
|
||||||
options.forEach((option)=>{
|
let optPromise = options;
|
||||||
|
|
||||||
|
if (typeof options === 'function') {
|
||||||
|
optPromise = options();
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.resolve(optPromise).then((res) => {
|
||||||
|
res.forEach((option) => {
|
||||||
this.varTypes[option.value] = {
|
this.varTypes[option.value] = {
|
||||||
...option,
|
...option,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlaceHolderMsg(variable) {
|
getPlaceHolderMsg(variable) {
|
||||||
@ -145,13 +155,10 @@ export default class VariableSchema extends BaseUISchema {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'name', label: gettext('Name'), type:'text',
|
id: 'name', label: gettext('Name'), type:'text',
|
||||||
editable: function(state) {
|
editable: (state) => (obj.isNew(state) || !obj.allReadOnly),
|
||||||
return obj.isNew(state) || !obj.allReadOnly;
|
|
||||||
},
|
|
||||||
cell: () => ({
|
cell: () => ({
|
||||||
cell: 'select',
|
cell: 'select',
|
||||||
options: this.vnameOptions,
|
options: obj.vnameOptions,
|
||||||
optionsLoaded: (options)=>{obj.setVarTypes(options);},
|
|
||||||
controlProps: { allowClear: false },
|
controlProps: { allowClear: false },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -167,7 +174,7 @@ export default class VariableSchema extends BaseUISchema {
|
|||||||
deps: ['name'], editable: !obj.allReadOnly,
|
deps: ['name'], editable: !obj.allReadOnly,
|
||||||
depChange: (state, source) => {
|
depChange: (state, source) => {
|
||||||
if(source[source.length-1] == 'name') {
|
if(source[source.length-1] == 'name') {
|
||||||
let variable = this.varTypes[state.name];
|
let variable = obj.varTypes[state.name];
|
||||||
if(variable.vartype === 'bool'){
|
if(variable.vartype === 'bool'){
|
||||||
return {
|
return {
|
||||||
value: false,
|
value: false,
|
||||||
@ -179,18 +186,19 @@ export default class VariableSchema extends BaseUISchema {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
cell: (row) => {
|
cell: (row) => {
|
||||||
let variable = this.varTypes[row.name];
|
let variable = obj.varTypes[row.name];
|
||||||
return this.getValueFieldProps(variable);
|
return obj.getValueFieldProps(variable);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{id: 'database', label: gettext('Database'), type: 'text',
|
{
|
||||||
cell: ()=>({cell: 'select', options: this.databaseOptions }),
|
id: 'database', label: gettext('Database'), type: 'text',
|
||||||
|
cell: ()=>({cell: 'select', options: obj.databaseOptions }),
|
||||||
},
|
},
|
||||||
{id: 'role', label: gettext('Role'), type: 'text',
|
{
|
||||||
cell: ()=>({cell: 'select', options: this.roleOptions,
|
id: 'role', label: gettext('Role'), type: 'text',
|
||||||
controlProps: {
|
cell: () => ({
|
||||||
allowClear: false,
|
cell: 'select', options: obj.roleOptions,
|
||||||
}
|
controlProps: { allowClear: false },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -65,10 +65,16 @@ class ChangePasswordSchema extends BaseUISchema {
|
|||||||
|
|
||||||
export default function ChangePasswordContent({getInitData=() => { /*This is intentional (SonarQube)*/ },
|
export default function ChangePasswordContent({getInitData=() => { /*This is intentional (SonarQube)*/ },
|
||||||
onSave, onClose, hasCsrfToken=false, showUser=true}) {
|
onSave, onClose, hasCsrfToken=false, showUser=true}) {
|
||||||
|
const schema=React.useRef(null);
|
||||||
|
if (!schema.current)
|
||||||
|
schema.current = new ChangePasswordSchema(
|
||||||
|
'', false, hasCsrfToken, showUser
|
||||||
|
);
|
||||||
|
|
||||||
return <SchemaView
|
return <SchemaView
|
||||||
formType={'dialog'}
|
formType={'dialog'}
|
||||||
getInitData={getInitData}
|
getInitData={getInitData}
|
||||||
schema={new ChangePasswordSchema('', false, hasCsrfToken, showUser)}
|
schema={schema.current}
|
||||||
viewHelperProps={{
|
viewHelperProps={{
|
||||||
mode: 'create',
|
mode: 'create',
|
||||||
}}
|
}}
|
||||||
|
@ -33,7 +33,9 @@ import CustomPropTypes from 'sources/custom_prop_types';
|
|||||||
|
|
||||||
import { StyleDataGridBox } from '../StyledComponents';
|
import { StyleDataGridBox } from '../StyledComponents';
|
||||||
import { SchemaStateContext } from '../SchemaState';
|
import { SchemaStateContext } from '../SchemaState';
|
||||||
import { useFieldOptions, useFieldValue } from '../hooks';
|
import {
|
||||||
|
useFieldOptions, useFieldValue, useSchemaStateSubscriber,
|
||||||
|
} from '../hooks';
|
||||||
import { registerView } from '../registry';
|
import { registerView } from '../registry';
|
||||||
import { listenDepChanges } from '../utils';
|
import { listenDepChanges } from '../utils';
|
||||||
|
|
||||||
@ -49,20 +51,16 @@ export default function DataGridView({
|
|||||||
}) {
|
}) {
|
||||||
const pgAdmin = usePgAdmin();
|
const pgAdmin = usePgAdmin();
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
const subscriberManager = useSchemaStateSubscriber(setRefreshKey);
|
||||||
const schemaState = useContext(SchemaStateContext);
|
const schemaState = useContext(SchemaStateContext);
|
||||||
const options = useFieldOptions(
|
const options = useFieldOptions(accessPath, schemaState, subscriberManager);
|
||||||
accessPath, schemaState, refreshKey, setRefreshKey
|
|
||||||
);
|
|
||||||
const value = useFieldValue(accessPath, schemaState);
|
const value = useFieldValue(accessPath, schemaState);
|
||||||
const schema = field.schema;
|
const schema = field.schema;
|
||||||
const features = useRef();
|
const features = useRef();
|
||||||
|
|
||||||
// Update refresh key on changing the number of rows.
|
// Update refresh key on changing the number of rows.
|
||||||
useFieldValue(
|
useFieldValue(
|
||||||
[...accessPath, 'length'], schemaState, refreshKey,
|
[...accessPath, 'length'], schemaState, subscriberManager
|
||||||
(newKey) => {
|
|
||||||
setRefreshKey(newKey);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -16,7 +16,9 @@ import { evalFunc } from 'sources/utils';
|
|||||||
import { MappedCellControl } from '../MappedControl';
|
import { MappedCellControl } from '../MappedControl';
|
||||||
import { SCHEMA_STATE_ACTIONS, SchemaStateContext } from '../SchemaState';
|
import { SCHEMA_STATE_ACTIONS, SchemaStateContext } from '../SchemaState';
|
||||||
import { flatternObject } from '../common';
|
import { flatternObject } from '../common';
|
||||||
import { useFieldOptions, useFieldValue } from '../hooks';
|
import {
|
||||||
|
useFieldOptions, useFieldValue, useSchemaStateSubscriber
|
||||||
|
} from '../hooks';
|
||||||
import { listenDepChanges } from '../utils';
|
import { listenDepChanges } from '../utils';
|
||||||
|
|
||||||
import { DataGridContext, DataGridRowContext } from './context';
|
import { DataGridContext, DataGridRowContext } from './context';
|
||||||
@ -25,14 +27,17 @@ import { DataGridContext, DataGridRowContext } from './context';
|
|||||||
export function getMappedCell({field}) {
|
export function getMappedCell({field}) {
|
||||||
const Cell = ({reRenderRow, getValue}) => {
|
const Cell = ({reRenderRow, getValue}) => {
|
||||||
|
|
||||||
const [key, setKey] = useState(0);
|
const [, setKey] = useState(0);
|
||||||
|
const subscriberManager = useSchemaStateSubscriber(setKey);
|
||||||
const schemaState = useContext(SchemaStateContext);
|
const schemaState = useContext(SchemaStateContext);
|
||||||
const { dataDispatch, accessPath } = useContext(DataGridContext);
|
const { dataDispatch, accessPath } = useContext(DataGridContext);
|
||||||
const { rowAccessPath, row } = useContext(DataGridRowContext);
|
const { rowAccessPath, row } = useContext(DataGridRowContext);
|
||||||
const colAccessPath = schemaState.accessPath(rowAccessPath, field.id);
|
const colAccessPath = schemaState.accessPath(rowAccessPath, field.id);
|
||||||
|
|
||||||
let colOptions = useFieldOptions(colAccessPath, schemaState, key, setKey);
|
let colOptions = useFieldOptions(
|
||||||
let value = useFieldValue(colAccessPath, schemaState, key, setKey);
|
colAccessPath, schemaState, subscriberManager
|
||||||
|
);
|
||||||
|
let value = useFieldValue(colAccessPath, schemaState, subscriberManager);
|
||||||
let rowValue = useFieldValue(rowAccessPath, schemaState);
|
let rowValue = useFieldValue(rowAccessPath, schemaState);
|
||||||
|
|
||||||
listenDepChanges(colAccessPath, field, true, schemaState);
|
listenDepChanges(colAccessPath, field, true, schemaState);
|
||||||
|
@ -15,7 +15,9 @@ import CustomPropTypes from 'sources/custom_prop_types';
|
|||||||
|
|
||||||
import { FieldControl } from './FieldControl';
|
import { FieldControl } from './FieldControl';
|
||||||
import { SchemaStateContext } from './SchemaState';
|
import { SchemaStateContext } from './SchemaState';
|
||||||
import { useFieldSchema, useFieldValue } from './hooks';
|
import {
|
||||||
|
useFieldSchema, useFieldValue, useSchemaStateSubscriber,
|
||||||
|
} from './hooks';
|
||||||
import { registerView } from './registry';
|
import { registerView } from './registry';
|
||||||
import { createFieldControls, listenDepChanges } from './utils';
|
import { createFieldControls, listenDepChanges } from './utils';
|
||||||
|
|
||||||
@ -23,13 +25,15 @@ import { createFieldControls, listenDepChanges } from './utils';
|
|||||||
export default function FieldSetView({
|
export default function FieldSetView({
|
||||||
field, accessPath, dataDispatch, viewHelperProps, controlClassName,
|
field, accessPath, dataDispatch, viewHelperProps, controlClassName,
|
||||||
}) {
|
}) {
|
||||||
const [key, setRefreshKey] = useState(0);
|
const [, setKey] = useState(0);
|
||||||
|
const subscriberManager = useSchemaStateSubscriber(setKey);
|
||||||
const schema = field.schema;
|
const schema = field.schema;
|
||||||
const schemaState = useContext(SchemaStateContext);
|
const schemaState = useContext(SchemaStateContext);
|
||||||
const value = useFieldValue(accessPath, schemaState);
|
const value = useFieldValue(accessPath, schemaState);
|
||||||
const options = useFieldSchema(
|
const options = useFieldSchema(
|
||||||
field, accessPath, value, viewHelperProps, schemaState, key, setRefreshKey
|
field, accessPath, value, viewHelperProps, schemaState, subscriberManager
|
||||||
);
|
);
|
||||||
|
|
||||||
const label = field.label;
|
const label = field.label;
|
||||||
|
|
||||||
listenDepChanges(accessPath, field, options.visible, schemaState);
|
listenDepChanges(accessPath, field, options.visible, schemaState);
|
||||||
|
@ -27,7 +27,9 @@ import { FieldControl } from './FieldControl';
|
|||||||
import { SQLTab } from './SQLTab';
|
import { SQLTab } from './SQLTab';
|
||||||
import { FormContentBox } from './StyledComponents';
|
import { FormContentBox } from './StyledComponents';
|
||||||
import { SchemaStateContext } from './SchemaState';
|
import { SchemaStateContext } from './SchemaState';
|
||||||
import { useFieldSchema, useFieldValue } from './hooks';
|
import {
|
||||||
|
useFieldSchema, useFieldValue, useSchemaStateSubscriber,
|
||||||
|
} from './hooks';
|
||||||
import { registerView, View } from './registry';
|
import { registerView, View } from './registry';
|
||||||
import { createFieldControls, listenDepChanges } from './utils';
|
import { createFieldControls, listenDepChanges } from './utils';
|
||||||
|
|
||||||
@ -62,10 +64,11 @@ export default function FormView({
|
|||||||
showError=false, resetKey, focusOnFirstInput=false
|
showError=false, resetKey, focusOnFirstInput=false
|
||||||
}) {
|
}) {
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
|
const subscriberManager = useSchemaStateSubscriber(setKey);
|
||||||
const schemaState = useContext(SchemaStateContext);
|
const schemaState = useContext(SchemaStateContext);
|
||||||
const value = useFieldValue(accessPath, schemaState);
|
const value = useFieldValue(accessPath, schemaState);
|
||||||
const { visible } = useFieldSchema(
|
const { visible } = useFieldSchema(
|
||||||
field, accessPath, value, viewHelperProps, schemaState, key, setKey
|
field, accessPath, value, viewHelperProps, schemaState, subscriberManager
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tabValue, setTabValue] = useState(0);
|
const [tabValue, setTabValue] = useState(0);
|
||||||
@ -106,13 +109,12 @@ export default function FormView({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Refresh on message changes.
|
// Refresh on message changes.
|
||||||
return schemaState.subscribe(
|
return subscriberManager.current?.add(
|
||||||
['errors', 'message'],
|
schemaState, ['errors', 'message'], 'states',
|
||||||
(newState, prevState) => {
|
(newState, prevState) => {
|
||||||
if (_.isUndefined(newState) || _.isUndefined(prevState));
|
if (_.isUndefined(newState) || _.isUndefined(prevState))
|
||||||
setKey(Date.now());
|
subscriberManager.current?.signal();
|
||||||
},
|
}
|
||||||
'states'
|
|
||||||
);
|
);
|
||||||
}, [key]);
|
}, [key]);
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ import { evalFunc } from 'sources/utils';
|
|||||||
import { SchemaStateContext } from './SchemaState';
|
import { SchemaStateContext } from './SchemaState';
|
||||||
import { isValueEqual } from './common';
|
import { isValueEqual } from './common';
|
||||||
import {
|
import {
|
||||||
useFieldOptions, useFieldValue, useFieldError
|
useFieldOptions, useFieldValue, useFieldError, useSchemaStateSubscriber,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
import { listenDepChanges } from './utils';
|
import { listenDepChanges } from './utils';
|
||||||
|
|
||||||
@ -339,22 +339,15 @@ export const MappedFormControl = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const checkIsMounted = useIsMounted();
|
const checkIsMounted = useIsMounted();
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
|
const subscriberManager = useSchemaStateSubscriber(setKey);
|
||||||
const schemaState = useContext(SchemaStateContext);
|
const schemaState = useContext(SchemaStateContext);
|
||||||
const state = schemaState.data;
|
const state = schemaState.data;
|
||||||
const avoidRenderingWhenNotMounted = (newKey) => {
|
const value = useFieldValue(accessPath, schemaState, subscriberManager);
|
||||||
if (checkIsMounted()) {
|
const options = useFieldOptions(accessPath, schemaState, subscriberManager);
|
||||||
setKey(newKey);
|
const {hasError} = useFieldError(accessPath, schemaState, subscriberManager);
|
||||||
}
|
const avoidRenderingWhenNotMounted = (...args) => {
|
||||||
|
if (checkIsMounted()) subscriberManager.current?.signal(...args);
|
||||||
};
|
};
|
||||||
const value = useFieldValue(
|
|
||||||
accessPath, schemaState, key, avoidRenderingWhenNotMounted
|
|
||||||
);
|
|
||||||
const options = useFieldOptions(
|
|
||||||
accessPath, schemaState, key, avoidRenderingWhenNotMounted
|
|
||||||
);
|
|
||||||
const { hasError } = useFieldError(
|
|
||||||
accessPath, schemaState, key, avoidRenderingWhenNotMounted
|
|
||||||
);
|
|
||||||
|
|
||||||
const origOnChange = onChange;
|
const origOnChange = onChange;
|
||||||
|
|
||||||
@ -369,7 +362,10 @@ export const MappedFormControl = ({
|
|||||||
if (!isValueEqual(changedValue, currValue)) origOnChange(changedValue);
|
if (!isValueEqual(changedValue, currValue)) origOnChange(changedValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
listenDepChanges(accessPath, field, options.visible, schemaState);
|
const depVals = listenDepChanges(
|
||||||
|
accessPath, field, options.visible, schemaState, state,
|
||||||
|
avoidRenderingWhenNotMounted
|
||||||
|
);
|
||||||
|
|
||||||
let newProps = {
|
let newProps = {
|
||||||
...props,
|
...props,
|
||||||
@ -394,14 +390,17 @@ export const MappedFormControl = ({
|
|||||||
newProps.onClick = ()=>{
|
newProps.onClick = ()=>{
|
||||||
origOnClick?.();
|
origOnClick?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME:: Get this list from the option registry.
|
// FIXME:: Get this list from the option registry.
|
||||||
const memDeps = ['disabled', 'visible', 'readonly'].map(
|
const memDeps = ['disabled', 'visible', 'readonly'].map(
|
||||||
option => options[option]
|
option => options[option]
|
||||||
);
|
);
|
||||||
|
|
||||||
memDeps.push(value);
|
memDeps.push(value);
|
||||||
memDeps.push(hasError);
|
memDeps.push(hasError);
|
||||||
memDeps.push(key);
|
memDeps.push(key);
|
||||||
memDeps.push(JSON.stringify(accessPath));
|
memDeps.push(JSON.stringify(accessPath));
|
||||||
|
memDeps.push(depVals);
|
||||||
|
|
||||||
// Filter out garbage props if any using ALLOWED_PROPS_FIELD.
|
// Filter out garbage props if any using ALLOWED_PROPS_FIELD.
|
||||||
return useMemo(
|
return useMemo(
|
||||||
|
@ -12,6 +12,7 @@ import { useFieldOptions } from './useFieldOptions';
|
|||||||
import { useFieldValue } from './useFieldValue';
|
import { useFieldValue } from './useFieldValue';
|
||||||
import { useSchemaState } from './useSchemaState';
|
import { useSchemaState } from './useSchemaState';
|
||||||
import { useFieldSchema } from './useFieldSchema';
|
import { useFieldSchema } from './useFieldSchema';
|
||||||
|
import { useSchemaStateSubscriber } from './useSchemaStateSubscriber';
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -20,4 +21,5 @@ export {
|
|||||||
useFieldValue,
|
useFieldValue,
|
||||||
useFieldSchema,
|
useFieldSchema,
|
||||||
useSchemaState,
|
useSchemaState,
|
||||||
|
useSchemaStateSubscriber,
|
||||||
};
|
};
|
||||||
|
@ -9,26 +9,36 @@
|
|||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const isPathEqual = (path1, path2) => (
|
||||||
|
JSON.stringify(path1) === JSON.stringify(path2)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export const useFieldError = (path, schemaState, subscriberManager) => {
|
||||||
|
|
||||||
export const useFieldError = (
|
|
||||||
path, schemaState, key, setRefreshKey
|
|
||||||
) => {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!schemaState || !setRefreshKey) return;
|
if (!schemaState || !subscriberManager?.current) return;
|
||||||
|
|
||||||
const checkPathError = (newState, prevState) => {
|
const checkPathError = (newState, prevState) => {
|
||||||
if (prevState.name !== path && newState.name !== path) return;
|
|
||||||
// We don't need to redraw the control on message change.
|
// We don't need to redraw the control on message change.
|
||||||
if (prevState.name === newState.name) return;
|
if ((
|
||||||
|
!isPathEqual(prevState.name, path) &&
|
||||||
|
!isPathEqual(newState.name, path)
|
||||||
|
) || (
|
||||||
|
isPathEqual(prevState.name, newState.name) &&
|
||||||
|
prevState.message == newState.message
|
||||||
|
)) return;
|
||||||
|
|
||||||
setRefreshKey({id: Date.now()});
|
subscriberManager.current?.signal();
|
||||||
};
|
};
|
||||||
|
|
||||||
return schemaState.subscribe(['errors'], checkPathError, 'states');
|
return subscriberManager.current?.add(
|
||||||
}, [key, schemaState?._id]);
|
schemaState, ['errors'], 'states', checkPathError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const errors = schemaState?.errors || {};
|
const errors = schemaState?.errors || {};
|
||||||
const error = errors.name === path ? errors.message : null;
|
const error = isPathEqual(errors.name, path) ? errors.message : null;
|
||||||
|
|
||||||
return {hasError: !_.isNull(error), error};
|
return {hasError: !_.isNull(error), error};
|
||||||
};
|
};
|
||||||
|
@ -10,16 +10,13 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
|
||||||
export const useFieldOptions = (
|
export const useFieldOptions = (path, schemaState, subscriberManager) => {
|
||||||
path, schemaState, key, setRefreshKey
|
|
||||||
) => {
|
|
||||||
useEffect(() => {
|
|
||||||
if (!schemaState) return;
|
|
||||||
|
|
||||||
return schemaState.subscribe(
|
useEffect(() => {
|
||||||
path, () => setRefreshKey?.({id: Date.now()}), 'options'
|
if (!schemaState || !subscriberManager?.current) return;
|
||||||
);
|
|
||||||
}, [key, schemaState?._id]);
|
return subscriberManager.current?.add(schemaState, path, 'options');
|
||||||
|
});
|
||||||
|
|
||||||
return schemaState?.options(path) || {visible: true};
|
return schemaState?.options(path) || {visible: true};
|
||||||
};
|
};
|
||||||
|
@ -14,32 +14,32 @@ import { booleanEvaluator } from '../options';
|
|||||||
|
|
||||||
|
|
||||||
export const useFieldSchema = (
|
export const useFieldSchema = (
|
||||||
field, accessPath, value, viewHelperProps, schemaState, key, setRefreshKey
|
field, accessPath, value, viewHelperProps, schemaState, subscriberManager
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!schemaState || !field) return;
|
if (!schemaState || !field || !subscriberManager?.current) return;
|
||||||
|
|
||||||
// It already has 'id', 'options' is already evaluated.
|
// It already has 'id', 'options' is already evaluated.
|
||||||
if (field.id)
|
if (field.id)
|
||||||
return schemaState.subscribe(
|
return subscriberManager.current?.add(schemaState, accessPath, 'options');
|
||||||
accessPath, () => setRefreshKey?.({id: Date.now()}), 'options'
|
|
||||||
);
|
|
||||||
|
|
||||||
// There are no dependencies.
|
// There are no dependencies.
|
||||||
if (!_.isArray(field?.deps)) return;
|
if (!_.isArray(field?.deps)) return;
|
||||||
|
|
||||||
// Subscribe to all the dependents.
|
// Subscribe to all the dependents.
|
||||||
const unsubscribers = field.deps.map((dep) => (
|
const unsubscribers = field.deps.map((dep) => (
|
||||||
schemaState.subscribe(
|
subscriberManager.current?.add(
|
||||||
accessPath.concat(dep), () => setRefreshKey?.({id: Date.now()}),
|
schemaState, accessPath.concat(dep), 'value'
|
||||||
'value'
|
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribers.forEach(unsubscribe => unsubscribe());
|
unsubscribers.forEach(
|
||||||
|
unsubscribe => subscriberManager.current?.remove(unsubscribe)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, [key, schemaState?._id]);
|
});
|
||||||
|
|
||||||
if (!field) return { visible: true };
|
if (!field) return { visible: true };
|
||||||
if (field.id) return schemaState?.options(accessPath);
|
if (field.id) return schemaState?.options(accessPath);
|
||||||
|
@ -10,16 +10,13 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
|
||||||
export const useFieldValue = (
|
export const useFieldValue = (path, schemaState, subscriberManager) => {
|
||||||
path, schemaState, key, setRefreshKey
|
|
||||||
) => {
|
|
||||||
useEffect(() => {
|
|
||||||
if (!schemaState || !setRefreshKey) return;
|
|
||||||
|
|
||||||
return schemaState.subscribe(
|
useEffect(() => {
|
||||||
path, () => setRefreshKey({id: Date.now()}), 'value'
|
if (!schemaState || !subscriberManager?.current) return;
|
||||||
);
|
|
||||||
}, [key, schemaState?._id]);
|
return subscriberManager.current?.add(schemaState, path, 'value');
|
||||||
|
});
|
||||||
|
|
||||||
return schemaState?.value(path);
|
return schemaState?.value(path);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// pgAdmin 4 - PostgreSQL Tools
|
||||||
|
//
|
||||||
|
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
|
||||||
|
// This software is released under the PostgreSQL Licence
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/////////
|
||||||
|
//
|
||||||
|
// A class to handle the ScheamState subscription for a control to avoid
|
||||||
|
// rendering multiple times.
|
||||||
|
//
|
||||||
|
class SubscriberManager {
|
||||||
|
|
||||||
|
constructor(refreshKeyCallback) {
|
||||||
|
this.mounted = true;
|
||||||
|
this.callback = refreshKeyCallback;
|
||||||
|
this.unsubscribers = new Set();
|
||||||
|
this._id = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
add(schemaState, accessPath, kind, callback) {
|
||||||
|
if (!schemaState) return;
|
||||||
|
|
||||||
|
callback = callback || (() => this.signal());
|
||||||
|
|
||||||
|
return this._add(schemaState.subscribe(accessPath, callback, kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
_add(unsubscriber) {
|
||||||
|
if (!unsubscriber) return;
|
||||||
|
// Avoid reinsertion of same unsubscriber.
|
||||||
|
if (this.unsubscribers.has(unsubscriber)) return;
|
||||||
|
this.unsubscribers.add(unsubscriber);
|
||||||
|
|
||||||
|
return () => this.remove(unsubscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(unsubscriber) {
|
||||||
|
if (!unsubscriber) return;
|
||||||
|
if (!this.unsubscribers.has(unsubscriber)) return;
|
||||||
|
this.unsubscribers.delete(unsubscriber);
|
||||||
|
unsubscriber();
|
||||||
|
}
|
||||||
|
|
||||||
|
signal() {
|
||||||
|
// Do nothing - if already work is in progress.
|
||||||
|
if (!this.mounted) return;
|
||||||
|
this.mounted = false;
|
||||||
|
this.release();
|
||||||
|
this.callback(Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
release () {
|
||||||
|
const unsubscribers = this.unsubscribers;
|
||||||
|
this.unsubscribers = new Set();
|
||||||
|
this.mounted = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
Set.prototype.forEach.call(
|
||||||
|
unsubscribers, (unsubscriber) => unsubscriber()
|
||||||
|
);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mount() {
|
||||||
|
this.mounted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSchemaStateSubscriber(refreshKeyCallback) {
|
||||||
|
const subscriberManager = React.useRef(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!subscriberManager.current) return;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscriberManager.current?.release();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!subscriberManager.current)
|
||||||
|
subscriberManager.current = new SubscriberManager(refreshKeyCallback);
|
||||||
|
else
|
||||||
|
subscriberManager.current.mount();
|
||||||
|
|
||||||
|
return subscriberManager;
|
||||||
|
}
|
@ -17,7 +17,7 @@ import { View, hasView } from '../registry';
|
|||||||
import { StaticMappedFormControl, MappedFormControl } from '../MappedControl';
|
import { StaticMappedFormControl, MappedFormControl } from '../MappedControl';
|
||||||
|
|
||||||
|
|
||||||
const DEFAULT_TAB = 'general';
|
const DEFAULT_TAB = gettext('General');
|
||||||
|
|
||||||
export const createFieldControls = ({
|
export const createFieldControls = ({
|
||||||
schema, schemaState, accessPath, viewHelperProps, dataDispatch
|
schema, schemaState, accessPath, viewHelperProps, dataDispatch
|
||||||
@ -50,13 +50,13 @@ export const createFieldControls = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create default group - 'General'.
|
// Create default group - 'General'.
|
||||||
createGroup(DEFAULT_TAB, gettext('General'), true);
|
createGroup(DEFAULT_TAB, DEFAULT_TAB, true);
|
||||||
|
|
||||||
schema?.fields?.forEach((field) => {
|
schema?.fields?.forEach((field) => {
|
||||||
if (!isModeSupportedByField(field, viewHelperProps)) return;
|
if (!isModeSupportedByField(field, viewHelperProps)) return;
|
||||||
|
|
||||||
let inlineGroup = null;
|
let inlineGroup = null;
|
||||||
const inlineGroupId = field[inlineGroup];
|
const inlineGroupId = field['inlineGroup'];
|
||||||
|
|
||||||
if(field.type === 'group') {
|
if(field.type === 'group') {
|
||||||
|
|
||||||
|
@ -13,7 +13,16 @@ import _ from 'lodash';
|
|||||||
import { evalFunc } from 'sources/utils';
|
import { evalFunc } from 'sources/utils';
|
||||||
|
|
||||||
|
|
||||||
export const listenDepChanges = (accessPath, field, visible, schemaState) => {
|
export const listenDepChanges = (
|
||||||
|
accessPath, field, visible, schemaState, data, setRefreshKey
|
||||||
|
) => {
|
||||||
|
const deps = field?.deps ? (evalFunc(null, field.deps) || []) : null;
|
||||||
|
const parentPath = accessPath ? [...accessPath] : [];
|
||||||
|
|
||||||
|
// Remove the last element.
|
||||||
|
if (field?.id && field.id === parentPath[parentPath.length - 1]) {
|
||||||
|
parentPath.pop();
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visible || !schemaState || !field) return;
|
if (!visible || !schemaState || !field) return;
|
||||||
@ -26,15 +35,7 @@ export const listenDepChanges = (accessPath, field, visible, schemaState) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.deps) {
|
if (field.deps) {
|
||||||
const parentPath = [...accessPath];
|
deps.forEach((dep) => {
|
||||||
|
|
||||||
// Remove the last element.
|
|
||||||
if (field.id && field.id === parentPath[parentPath.length - 1]) {
|
|
||||||
parentPath.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
(evalFunc(null, field.deps) || []).forEach((dep) => {
|
|
||||||
|
|
||||||
// When dep is a string then prepend the complete accessPath,
|
// When dep is a string then prepend the complete accessPath,
|
||||||
// but - when dep is an array, then the intention is to provide
|
// but - when dep is an array, then the intention is to provide
|
||||||
// the exact accesspath.
|
// the exact accesspath.
|
||||||
@ -45,6 +46,11 @@ export const listenDepChanges = (accessPath, field, visible, schemaState) => {
|
|||||||
source, accessPath, field.depChange, field.deferredDepChange
|
source, accessPath, field.depChange, field.deferredDepChange
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (setRefreshKey)
|
||||||
|
schemaState.subscribe(
|
||||||
|
source, () => setRefreshKey(Date.now()), 'value'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,4 +60,7 @@ export const listenDepChanges = (accessPath, field, visible, schemaState) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
return deps?.map((dep) => schemaState.value(
|
||||||
|
_.isArray(dep) ? dep : parentPath.concat(dep)
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
@ -955,7 +955,6 @@ export const InputSelect = forwardRef(({
|
|||||||
return () => umounted = true;
|
return () => umounted = true;
|
||||||
}, [optionsReloadBasis]);
|
}, [optionsReloadBasis]);
|
||||||
|
|
||||||
|
|
||||||
/* Apply filter if any */
|
/* Apply filter if any */
|
||||||
const filteredOptions = (controlProps.filter?.(finalOptions)) || finalOptions;
|
const filteredOptions = (controlProps.filter?.(finalOptions)) || finalOptions;
|
||||||
const flatFiltered = flattenSelectOptions(filteredOptions);
|
const flatFiltered = flattenSelectOptions(filteredOptions);
|
||||||
|
@ -192,6 +192,11 @@ export default function MacrosDialog({onClose, onSave}) {
|
|||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const schema = React.useRef(null);
|
||||||
|
|
||||||
|
if (!schema.current)
|
||||||
|
schema.current = new MacrosSchema(keyOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledBox>
|
<StyledBox>
|
||||||
<SchemaView
|
<SchemaView
|
||||||
@ -202,7 +207,7 @@ export default function MacrosDialog({onClose, onSave}) {
|
|||||||
}
|
}
|
||||||
return Promise.resolve({macro: userMacrosData.filter((m)=>Boolean(m.name))});
|
return Promise.resolve({macro: userMacrosData.filter((m)=>Boolean(m.name))});
|
||||||
}}
|
}}
|
||||||
schema={new MacrosSchema(keyOptions)}
|
schema={schema.current}
|
||||||
viewHelperProps={{
|
viewHelperProps={{
|
||||||
mode: 'edit',
|
mode: 'edit',
|
||||||
}}
|
}}
|
||||||
|
@ -172,9 +172,10 @@ class NewConnectionSchema extends BaseUISchema {
|
|||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
id: 'user', label: gettext('User'), deps: ['sid', 'connected'],
|
id: 'user', label: gettext('User'), deps: ['sid', 'connected'],
|
||||||
noEmpty: true, controlProps: { allowClear: false },
|
noEmpty: true,
|
||||||
type: (state) => ({
|
type: (state) => ({
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
controlProps: { allowClear: false },
|
||||||
options: () => this.getOtherOptions(
|
options: () => this.getOtherOptions(
|
||||||
state.sid, 'get_new_connection_user'
|
state.sid, 'get_new_connection_user'
|
||||||
),
|
),
|
||||||
@ -184,6 +185,7 @@ class NewConnectionSchema extends BaseUISchema {
|
|||||||
id: 'role', label: gettext('Role'), deps: ['sid', 'connected'],
|
id: 'role', label: gettext('Role'), deps: ['sid', 'connected'],
|
||||||
type: (state) => ({
|
type: (state) => ({
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
controlProps: { allowClear: false },
|
||||||
options: () => this.getOtherOptions(
|
options: () => this.getOtherOptions(
|
||||||
state.sid, 'get_new_connection_role'
|
state.sid, 'get_new_connection_role'
|
||||||
),
|
),
|
||||||
|
@ -399,6 +399,11 @@ function UserManagementDialog({onClose}) {
|
|||||||
window.open(url_for('help.static', { 'filename': 'user_management.html' }), 'pgadmin_help');
|
window.open(url_for('help.static', { 'filename': 'user_management.html' }), 'pgadmin_help');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const schema = React.useRef(null);
|
||||||
|
|
||||||
|
if (!schema.current)
|
||||||
|
schema.current = new UserManagementSchema(authSourcesOptions, roleOptions);
|
||||||
|
|
||||||
return <StyledBox><SchemaView
|
return <StyledBox><SchemaView
|
||||||
formType={'dialog'}
|
formType={'dialog'}
|
||||||
getInitData={()=>{ return new Promise((resolve, reject)=>{
|
getInitData={()=>{ return new Promise((resolve, reject)=>{
|
||||||
@ -410,7 +415,7 @@ function UserManagementDialog({onClose}) {
|
|||||||
reject(err instanceof Error ? err : Error(gettext('Something went wrong')));
|
reject(err instanceof Error ? err : Error(gettext('Something went wrong')));
|
||||||
});
|
});
|
||||||
}); }}
|
}); }}
|
||||||
schema={new UserManagementSchema(authSourcesOptions, roleOptions)}
|
schema={schema.current}
|
||||||
viewHelperProps={{
|
viewHelperProps={{
|
||||||
mode: 'edit',
|
mode: 'edit',
|
||||||
}}
|
}}
|
||||||
|
Loading…
Reference in New Issue
Block a user