Fixes for the preferences dialog
1) Add server mode validation in the binary path. 2) Updated preferences tree rendering to avoid using the ReactDOM render. 3) Updated CSS for keyboard shortcuts checkbox border makes it consistent with input box border. 4) Fixed jasmine test case and improved code coverage. 5) Fixed SonarQube issues. 6) Added validation to disable "Maximum column with" option if "Column sized by" option is set to "Column name" in Query Tool -> Result grid. 7) Updated documentation with the latest screenshots. 8) Correct typo in the documentation. Fixes #7261 refs #7149
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 216 KiB |
@@ -421,7 +421,7 @@ Query Tool window navigation:
|
||||
Use the fields on the *SQL formatting* panel to specify your preferences for
|
||||
reformatting of SQL.
|
||||
|
||||
* Use the *Command-first notation* option to specify whether to place commas
|
||||
* Use the *Comma-first notation* option to specify whether to place commas
|
||||
before or after column names.
|
||||
* Use the *Identifier case* option to specify whether to change identifiers
|
||||
(object names) into upper, lower, or capitalized case.
|
||||
|
||||
@@ -22,4 +22,5 @@ Bug fixes
|
||||
| `Issue #7059 <https://redmine.postgresql.org/issues/7059>`_ - Fixed an issue where the error is shown on logout when the authentication source is oauth2.
|
||||
| `Issue #7216 <https://redmine.postgresql.org/issues/7216>`_ - Ensure that the values of certain fields are prettified in the statistics tab for collection nodes.
|
||||
| `Issue #7238 <https://redmine.postgresql.org/issues/7238>`_ - Fixed an issue where foreign key is not removed even if the referred table is removed in ERD.
|
||||
| `Issue #7257 <https://redmine.postgresql.org/issues/7257>`_ - Support running the container under OpenShift with alternate UIDs.
|
||||
| `Issue #7257 <https://redmine.postgresql.org/issues/7257>`_ - Support running the container under OpenShift with alternate UIDs.
|
||||
| `Issue #7261 <https://redmine.postgresql.org/issues/7261>`_ - Correct typo in the documentation.
|
||||
|
||||
@@ -26,7 +26,7 @@ click on a result row to select the object in the
|
||||
:ref:`browser <tree_control>`. If the object is greyed out, this means that you
|
||||
have not enabled those object types in the :ref:`preferences <preferences>`,
|
||||
so you can't double click on it. You can click on the ellipsis appended to
|
||||
the function and procedure names to see there arguments.
|
||||
the function and procedure names to see their arguments.
|
||||
|
||||
|
||||
You can filter based on a particular object type by selecting one from the
|
||||
|
||||
@@ -13,6 +13,7 @@ import url_for from 'sources/url_for';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import getApiInstance from '../../../../../static/js/api_instance';
|
||||
import Notify from '../../../../../static/js/helpers/Notifier';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
|
||||
export function getBinaryPathSchema() {
|
||||
|
||||
@@ -49,6 +50,7 @@ export default class BinaryPathSchema extends BaseUISchema {
|
||||
{
|
||||
id: 'binaryPath', label: gettext('Binary Path'), cell: 'file', type: 'file',
|
||||
isvalidate: true, controlProps: { dialogType: 'select_folder', supportedTypes: ['*', 'sql', 'backup'], dialogTitle: 'Select folder' },
|
||||
hideBrowseButton: pgAdmin.server_mode == 'True',
|
||||
validate: (data) => {
|
||||
const api = getApiInstance();
|
||||
if (_.isNull(data) || data.trim() === '') {
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import _ from 'lodash';
|
||||
import url_for from 'sources/url_for';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { FileType } from 'react-aspen';
|
||||
import { Box } from '@material-ui/core';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -209,100 +210,8 @@ export default function PreferencesComponent({ ...props }) {
|
||||
'expanded': false,
|
||||
};
|
||||
|
||||
if (subNode.label == 'Nodes' && node.label == 'Browser') {
|
||||
//Add Note for Nodes
|
||||
preferencesData.push(
|
||||
{
|
||||
id: 'note_' + subNode.id,
|
||||
type: 'note', text: [gettext('This settings is to Show/Hide nodes in the browser tree.')].join(''),
|
||||
visible: false,
|
||||
'parentId': nodeData['id']
|
||||
},
|
||||
);
|
||||
}
|
||||
subNode.preferences.forEach((element) => {
|
||||
let addNote = false;
|
||||
let note = '';
|
||||
let type = getControlMappedForType(element.type);
|
||||
|
||||
if (type === 'file') {
|
||||
addNote = true;
|
||||
note = gettext('Enter the directory in which the psql, pg_dump, pg_dumpall, and pg_restore utilities can be found for the corresponding database server version. The default path will be used for server versions that do not have a path specified.');
|
||||
element.type = 'collection';
|
||||
element.schema = getBinaryPathSchema();
|
||||
element.canAdd = false;
|
||||
element.canDelete = false;
|
||||
element.canEdit = false;
|
||||
element.editable = false;
|
||||
element.disabled = true;
|
||||
preferencesValues[element.id] = JSON.parse(element.value);
|
||||
}
|
||||
else if (type == 'select') {
|
||||
if (element.control_props !== undefined) {
|
||||
element.controlProps = element.control_props;
|
||||
} else {
|
||||
element.controlProps = {};
|
||||
}
|
||||
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
|
||||
if (element.name == 'theme') {
|
||||
element.type = 'theme';
|
||||
|
||||
element.options.forEach((opt) => {
|
||||
if (opt.value == element.value) {
|
||||
opt.selected = true;
|
||||
} else {
|
||||
opt.selected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (type === 'keyboardShortcut') {
|
||||
element.type = 'keyboardShortcut';
|
||||
element.canAdd = false;
|
||||
element.canDelete = false;
|
||||
element.canEdit = false;
|
||||
element.editable = false;
|
||||
if (pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name)?.value) {
|
||||
let temp = pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name).value;
|
||||
preferencesValues[element.id] = temp;
|
||||
} else {
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
delete element.value;
|
||||
} else if (type === 'threshold') {
|
||||
element.type = 'threshold';
|
||||
|
||||
let _val = element.value.split('|');
|
||||
preferencesValues[element.id] = { 'warning': _val[0], 'alert': _val[1] };
|
||||
} else {
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
|
||||
delete element.value;
|
||||
element.visible = false;
|
||||
element.helpMessage = element?.help_str ? element.help_str : null;
|
||||
preferencesData.push(element);
|
||||
|
||||
if (addNote) {
|
||||
preferencesData.push(
|
||||
{
|
||||
id: 'note_' + element.id,
|
||||
type: 'note', text: [
|
||||
'<ul><li>',
|
||||
gettext(note),
|
||||
'</li></ul>',
|
||||
].join(''),
|
||||
visible: false,
|
||||
'parentId': nodeData['id']
|
||||
},
|
||||
);
|
||||
}
|
||||
element.parentId = nodeData['id'];
|
||||
});
|
||||
addNote(node, subNode, nodeData, preferencesData);
|
||||
setPreferences(node, subNode, nodeData, preferencesValues, preferencesData);
|
||||
tdata['childrenNodes'].push(nodeData);
|
||||
});
|
||||
|
||||
@@ -318,11 +227,128 @@ export default function PreferencesComponent({ ...props }) {
|
||||
Notify.alert(err);
|
||||
});
|
||||
}, []);
|
||||
function setPreferences(node, subNode, nodeData, preferencesValues, preferencesData) {
|
||||
subNode.preferences.forEach((element) => {
|
||||
let note = '';
|
||||
let type = getControlMappedForType(element.type);
|
||||
|
||||
if (type === 'file') {
|
||||
note = gettext('Enter the directory in which the psql, pg_dump, pg_dumpall, and pg_restore utilities can be found for the corresponding database server version. The default path will be used for server versions that do not have a path specified.');
|
||||
element.type = 'collection';
|
||||
element.schema = getBinaryPathSchema();
|
||||
element.canAdd = false;
|
||||
element.canDelete = false;
|
||||
element.canEdit = false;
|
||||
element.editable = false;
|
||||
element.disabled = true;
|
||||
preferencesValues[element.id] = JSON.parse(element.value);
|
||||
addNote(node, subNode, nodeData, preferencesData, note);
|
||||
}
|
||||
else if (type == 'select') {
|
||||
setControlProps(element);
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
|
||||
setThemesOptions(element);
|
||||
}
|
||||
else if (type === 'keyboardShortcut') {
|
||||
getKeyboardShortcuts(element, preferencesValues, node);
|
||||
} else if (type === 'threshold') {
|
||||
element.type = 'threshold';
|
||||
|
||||
let _val = element.value.split('|');
|
||||
preferencesValues[element.id] = { 'warning': _val[0], 'alert': _val[1] };
|
||||
} else if (subNode.label == 'Results grid' && node.label == 'Query Tool') {
|
||||
setResultsOptions(element, subNode, preferencesValues, type);
|
||||
} else {
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
|
||||
delete element.value;
|
||||
element.visible = false;
|
||||
element.helpMessage = element?.help_str ? element.help_str : null;
|
||||
preferencesData.push(element);
|
||||
element.parentId = nodeData['id'];
|
||||
});
|
||||
}
|
||||
|
||||
function setResultsOptions(element, subNode, preferencesValues, type) {
|
||||
if (element.name== 'column_data_max_width') {
|
||||
let size_control_id = null;
|
||||
subNode.preferences.forEach((_el) => {
|
||||
if(_el.name == 'column_data_auto_resize') {
|
||||
size_control_id = _el.id;
|
||||
}
|
||||
|
||||
});
|
||||
element.disabled = (state) => {
|
||||
return state[size_control_id] != 'by_data';
|
||||
};
|
||||
}
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
|
||||
function setThemesOptions(element) {
|
||||
if (element.name == 'theme') {
|
||||
element.type = 'theme';
|
||||
|
||||
element.options.forEach((opt) => {
|
||||
if (opt.value == element.value) {
|
||||
opt.selected = true;
|
||||
} else {
|
||||
opt.selected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function setControlProps(element) {
|
||||
if (element.control_props !== undefined) {
|
||||
element.controlProps = element.control_props;
|
||||
} else {
|
||||
element.controlProps = {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getKeyboardShortcuts(element, preferencesValues, node) {
|
||||
element.type = 'keyboardShortcut';
|
||||
element.canAdd = false;
|
||||
element.canDelete = false;
|
||||
element.canEdit = false;
|
||||
element.editable = false;
|
||||
if (pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name)?.value) {
|
||||
let temp = pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name).value;
|
||||
preferencesValues[element.id] = temp;
|
||||
} else {
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
}
|
||||
function addNote(node, subNode, nodeData, preferencesData, note = '') {
|
||||
// Check and add the note for the element.
|
||||
if (subNode.label == 'Nodes' && node.label == 'Browser') {
|
||||
note = [gettext('This settings is to Show/Hide nodes in the browser tree.')].join('');
|
||||
} else {
|
||||
note = [gettext(note)].join('');
|
||||
}
|
||||
|
||||
if (note && note.length > 0) {
|
||||
//Add Note for Nodes
|
||||
preferencesData.push(
|
||||
{
|
||||
id: _.uniqueId('note') + subNode.id,
|
||||
type: 'note', text: note,
|
||||
visible: false,
|
||||
'parentId': nodeData['id']
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
props.renderTree(prefTreeData);
|
||||
let initTreeTimeout = null;
|
||||
|
||||
// Listen selected preferences tree node event and show the appropriate components in right panel.
|
||||
pgAdmin.Browser.Events.on('preferences:tree:selected', (item) => {
|
||||
if (item.type == FileType.File) {
|
||||
@@ -330,12 +356,12 @@ export default function PreferencesComponent({ ...props }) {
|
||||
field.visible = field.parentId === item._metadata.data.id;
|
||||
});
|
||||
setLoadTree(Math.floor(Math.random() * 1000));
|
||||
initTreeTimeout = setTimeout(()=> {
|
||||
initTreeTimeout = setTimeout(() => {
|
||||
prefTreeInit.current = true;
|
||||
}, 10);
|
||||
}
|
||||
else {
|
||||
if(item.isExpanded && item._children && item._children.length > 0 && prefTreeInit.current) {
|
||||
if (item.isExpanded && item._children && item._children.length > 0 && prefTreeInit.current) {
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._children[0], true);
|
||||
}
|
||||
}
|
||||
@@ -343,29 +369,24 @@ export default function PreferencesComponent({ ...props }) {
|
||||
|
||||
// Listen open preferences tree node event to default select first child node on parent node selection.
|
||||
pgAdmin.Browser.Events.on('preferences:tree:opened', (item) => {
|
||||
if (item._fileName == 'Browser' && item.type == 2 && item.isExpanded && item._children && item._children.length > 0 && !prefTreeInit.current) {
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._children[0], false);
|
||||
}
|
||||
else if(prefTreeInit.current) {
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._children[0], true);
|
||||
}
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._children[0], true);
|
||||
});
|
||||
|
||||
// Listen added preferences tree node event to expand the newly added node on tree load.
|
||||
pgAdmin.Browser.Events.on('preferences:tree:added', (item) => {
|
||||
// Check the if newely added node is Directoy call toggle to expand the node.
|
||||
if (item.type == FileType.Directory) {
|
||||
if (item._parent._fileName == 'Browser' && item._parent.isExpanded && !prefTreeInit.current) {
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._parent._children[0], false);
|
||||
}
|
||||
else if (item.type == FileType.Directory) {
|
||||
// Check the if newely added node is Directoy and call toggle to expand the node.
|
||||
pgAdmin.Browser.ptree.tree.toggleDirectory(item);
|
||||
}
|
||||
});
|
||||
|
||||
/* Clear the initTreeTimeout timeout if unmounted */
|
||||
return ()=>{
|
||||
return () => {
|
||||
clearTimeout(initTreeTimeout);
|
||||
};
|
||||
}, [prefTreeData]);
|
||||
|
||||
|
||||
}, []);
|
||||
|
||||
function getControlMappedForType(type) {
|
||||
switch (type) {
|
||||
@@ -414,7 +435,7 @@ export default function PreferencesComponent({ ...props }) {
|
||||
}
|
||||
}
|
||||
|
||||
function getCollectionValue(_metadata, value, initValues) {
|
||||
function getCollectionValue(_metadata, value, initVals) {
|
||||
let val = value;
|
||||
if (typeof (value) == 'object') {
|
||||
if (_metadata[0].type == 'collection' && _metadata[0].schema) {
|
||||
@@ -424,14 +445,7 @@ export default function PreferencesComponent({ ...props }) {
|
||||
value.changed.forEach((chValue) => {
|
||||
pathVersions.push(chValue.version);
|
||||
});
|
||||
initValues[_metadata[0].id].forEach((initVal) => {
|
||||
if (pathVersions.includes(initVal.version)) {
|
||||
pathData.push(value.changed[pathVersions.indexOf(initVal.version)]);
|
||||
}
|
||||
else {
|
||||
pathData.push(initVal);
|
||||
}
|
||||
});
|
||||
getPathData(initVals, pathData, _metadata, value, pathVersions);
|
||||
val = JSON.stringify(pathData);
|
||||
} else {
|
||||
let key_val = {
|
||||
@@ -450,12 +464,23 @@ export default function PreferencesComponent({ ...props }) {
|
||||
return val;
|
||||
}
|
||||
|
||||
function savePreferences(data, initValues) {
|
||||
function getPathData(initVals, pathData, _metadata, value, pathVersions) {
|
||||
initVals[_metadata[0].id].forEach((initVal) => {
|
||||
if (pathVersions.includes(initVal.version)) {
|
||||
pathData.push(value.changed[pathVersions.indexOf(initVal.version)]);
|
||||
}
|
||||
else {
|
||||
pathData.push(initVal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function savePreferences(data, initVal) {
|
||||
let _data = [];
|
||||
for (const [key, value] of Object.entries(data.current)) {
|
||||
let _metadata = prefSchema.current.schemaFields.filter((el) => { return el.id == key; });
|
||||
if (_metadata.length > 0) {
|
||||
let val = getCollectionValue(_metadata, value, initValues);
|
||||
let val = getCollectionValue(_metadata, value, initVal);
|
||||
_data.push({
|
||||
'category_id': _metadata[0]['cid'],
|
||||
'id': parseInt(key),
|
||||
@@ -536,14 +561,14 @@ export default function PreferencesComponent({ ...props }) {
|
||||
location.reload();
|
||||
return true;
|
||||
},
|
||||
function () { props.closeModal(); /*props.panel.close()*/ },
|
||||
function () { props.closeModal();},
|
||||
gettext('Refresh'),
|
||||
gettext('Later')
|
||||
);
|
||||
}
|
||||
// Refresh preferences cache
|
||||
pgAdmin.Browser.cache_preferences(modulesChanged);
|
||||
props.closeModal(); /*props.panel.close()*/
|
||||
props.closeModal();
|
||||
}).catch((err) => {
|
||||
Notify.alert(err.response.data);
|
||||
});
|
||||
@@ -558,7 +583,12 @@ export default function PreferencesComponent({ ...props }) {
|
||||
<Box className={classes.root}>
|
||||
<Box className={clsx(classes.preferences)}>
|
||||
<Box className={clsx(classes.treeContainer)} >
|
||||
<Box className={clsx('aciTree', classes.tree)} id={'treeContainer'}></Box>
|
||||
|
||||
<Box className={clsx('aciTree', classes.tree)} id={'treeContainer'}>
|
||||
{
|
||||
useMemo(() => (prefTreeData && props.renderTree(prefTreeData)), [prefTreeData])
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className={clsx(classes.preferencesContainer)}>
|
||||
{
|
||||
@@ -576,7 +606,7 @@ export default function PreferencesComponent({ ...props }) {
|
||||
<PgIconButton data-test="dialog-help" onClick={onDialogHelp} icon={<HelpIcon />} title={gettext('Help for this dialog.')} />
|
||||
</Box>
|
||||
<Box className={classes.actionBtn} marginLeft="auto">
|
||||
<DefaultButton className={classes.buttonMargin} onClick={() => { props.closeModal(); /*props.panel.close()*/ }} startIcon={<CloseSharpIcon onClick={() => { props.closeModal(); /*props.panel.close()*/ }} />}>
|
||||
<DefaultButton className={classes.buttonMargin} onClick={() => { props.closeModal();}} startIcon={<CloseSharpIcon onClick={() => { props.closeModal();}} />}>
|
||||
{gettext('Cancel')}
|
||||
</DefaultButton>
|
||||
<PrimaryButton className={classes.buttonMargin} startIcon={<SaveSharpIcon />} disabled={disableSave} onClick={() => { savePreferences(prefChangedData, initValues); }}>
|
||||
@@ -584,8 +614,6 @@ export default function PreferencesComponent({ ...props }) {
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* </Box> */}
|
||||
|
||||
</Box >
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -7,62 +7,79 @@
|
||||
// //
|
||||
// //////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import * as React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Directory } from 'react-aspen';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Directory} from 'react-aspen';
|
||||
import { FileTreeX, TreeModelX } from 'pgadmin4-tree';
|
||||
import {Tree} from '../../../../static/js/tree/tree';
|
||||
import { Tree } from '../../../../static/js/tree/tree';
|
||||
import { ManagePreferenceTreeNodes } from '../../../../static/js/tree/preference_nodes';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
|
||||
var initPreferencesTree = async (pgBrowser, containerElement, data) => {
|
||||
|
||||
export default function PreferencesTree({ pgBrowser, data }) {
|
||||
const pTreeModelX = React.useRef();
|
||||
const onReadyRef = React.useRef();
|
||||
const [loaded, setLoaded] = React.useState(false);
|
||||
|
||||
const MOUNT_POINT = '/preferences';
|
||||
|
||||
// Setup host
|
||||
let ptree = new ManagePreferenceTreeNodes(data);
|
||||
React.useEffect(() => {
|
||||
setLoaded(false);
|
||||
|
||||
// Init Tree with the Tree Parent node '/browser'
|
||||
ptree.init(MOUNT_POINT);
|
||||
// Setup host
|
||||
let ptree = new ManagePreferenceTreeNodes(data);
|
||||
// Init Tree with the Tree Parent node '/browser'
|
||||
ptree.init(MOUNT_POINT);
|
||||
|
||||
const host = {
|
||||
pathStyle: 'unix',
|
||||
getItems: async (path) => {
|
||||
return ptree.readNode(path);
|
||||
},
|
||||
sortComparator: (a, b) => {
|
||||
// No nee to sort Query tool options.
|
||||
if (a._parent && a._parent._fileName == 'Query Tool') return 0;
|
||||
// Sort alphabetically
|
||||
if (a.constructor === b.constructor) {
|
||||
return pgAdmin.natural_sort(a.fileName, b.fileName);
|
||||
}
|
||||
let retval = 0;
|
||||
if (a.constructor === Directory) {
|
||||
retval = -1;
|
||||
} else if (b.constructor === Directory) {
|
||||
retval = 1;
|
||||
}
|
||||
return retval;
|
||||
},
|
||||
};
|
||||
const host = {
|
||||
pathStyle: 'unix',
|
||||
getItems: (path) => {
|
||||
return ptree.readNode(path);
|
||||
|
||||
},
|
||||
sortComparator: (a, b) => {
|
||||
// No nee to sort Query tool options.
|
||||
if (a._parent && a._parent._fileName == 'Query Tool') return 0;
|
||||
// Sort alphabetically
|
||||
if (a.constructor === b.constructor) {
|
||||
return pgAdmin.natural_sort(a.fileName, b.fileName);
|
||||
}
|
||||
let retval = 0;
|
||||
if (a.constructor === Directory) {
|
||||
retval = -1;
|
||||
} else if (b.constructor === Directory) {
|
||||
retval = 1;
|
||||
}
|
||||
return retval;
|
||||
},
|
||||
};
|
||||
|
||||
const pTreeModelX = new TreeModelX(host, MOUNT_POINT);
|
||||
pTreeModelX.current = new TreeModelX(host, MOUNT_POINT);
|
||||
onReadyRef.current = function onReady(handler) {
|
||||
// Initialize preferences Tree
|
||||
pgBrowser.ptree = new Tree(handler, ptree, pgBrowser, 'preferences');
|
||||
// Expand directoy on loading.
|
||||
pTreeModelX.current.root._children.forEach((_d)=> {
|
||||
_d.root.expandDirectory(_d);
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const itemHandle = function onReady(handler) {
|
||||
// Initialize preferences Tree
|
||||
pgBrowser.ptree = new Tree(handler, ptree, pgBrowser, 'preferences');
|
||||
return true;
|
||||
};
|
||||
pTreeModelX.current.root.ensureLoaded().then(() => {
|
||||
setLoaded(true);
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
await pTreeModelX.root.ensureLoaded();
|
||||
if (!loaded || _.isUndefined(pTreeModelX.current) || _.isUndefined(onReadyRef.current)) {
|
||||
return (gettext('Loading...'));
|
||||
}
|
||||
return (<FileTreeX model={pTreeModelX.current} height={'100%'} onReady={onReadyRef.current} />);
|
||||
}
|
||||
|
||||
// Render Browser Tree
|
||||
await render(
|
||||
<FileTreeX model={pTreeModelX} height={'100%'}
|
||||
onReady={itemHandle} />
|
||||
, containerElement);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
initPreferencesTree: initPreferencesTree,
|
||||
PreferencesTree.propTypes = {
|
||||
pgBrowser: PropTypes.any,
|
||||
data: PropTypes.array,
|
||||
ptree: PropTypes.any,
|
||||
};
|
||||
@@ -11,8 +11,7 @@ import React from 'react';
|
||||
import gettext from 'sources/gettext';
|
||||
import PreferencesComponent from './components/PreferencesComponent';
|
||||
import Notify from '../../../static/js/helpers/Notifier';
|
||||
// import PreferencesTree from './components/PreferencesTree';
|
||||
import { initPreferencesTree } from './components/PreferencesTree';
|
||||
import PreferencesTree from './components/PreferencesTree';
|
||||
|
||||
export default class Preferences {
|
||||
static instance;
|
||||
@@ -50,12 +49,15 @@ export default class Preferences {
|
||||
|
||||
// This is a callback function to show preferences.
|
||||
show() {
|
||||
|
||||
// Render Preferences component
|
||||
Notify.showModal(gettext('Preferences'), (closeModal) => {
|
||||
return <PreferencesComponent
|
||||
renderTree={(prefTreeData) => {
|
||||
initPreferencesTree(this.pgBrowser, document.getElementById('treeContainer'), prefTreeData);
|
||||
// Render preferences tree component
|
||||
return <PreferencesTree pgBrowser={this.pgBrowser} data={prefTreeData} />;
|
||||
}} closeModal={closeModal} />;
|
||||
}, { isFullScreen: false, isResizeable: true, showFullScreen: true, isFullWidth: true, dialogWidth: 900, dialogHeight: 550 });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import _ from 'lodash';
|
||||
import {
|
||||
FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, FormInputColor,
|
||||
FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL, InputSQL, FormNote, FormInputDateTimePicker, PlainString,
|
||||
InputSelect, InputText, InputCheckbox, InputDateTimePicker, InputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold, FormInputThemes, InputRadio
|
||||
InputSelect, InputText, InputCheckbox, InputDateTimePicker, InputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold, FormInputSelectThemes, InputRadio
|
||||
} from '../components/FormComponents';
|
||||
import Privilege from '../components/Privilege';
|
||||
import { evalFunc } from 'sources/utils';
|
||||
@@ -85,7 +85,7 @@ function MappedFormControlBase({ type, value, id, onChange, className, visible,
|
||||
case 'threshold':
|
||||
return <FormInputQueryThreshold name={name} value={value} onChange={onTextChange} {...props}/>;
|
||||
case 'theme':
|
||||
return <FormInputThemes name={name} value={value} onChange={onTextChange} {...props}/>;
|
||||
return <FormInputSelectThemes name={name} value={value} onChange={onTextChange} {...props}/>;
|
||||
default:
|
||||
return <PlainString value={value} {...props} />;
|
||||
}
|
||||
@@ -197,7 +197,7 @@ 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'
|
||||
'orientation', 'isvalidate', 'fields', 'radioType', 'hideBrowseButton'
|
||||
];
|
||||
|
||||
const ALLOWED_PROPS_FIELD_FORM = [
|
||||
@@ -205,7 +205,7 @@ const ALLOWED_PROPS_FIELD_FORM = [
|
||||
];
|
||||
|
||||
const ALLOWED_PROPS_FIELD_CELL = [
|
||||
'cell', 'onCellChange', 'row', 'reRenderRow', 'validate', 'disabled', 'readonly', 'radioType'
|
||||
'cell', 'onCellChange', 'row', 'reRenderRow', 'validate', 'disabled', 'readonly', 'radioType', 'hideBrowseButton'
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,6 @@ export const ConnectedIcon = ()=><ExternalIcon Icon={ConnectedSvg} style={{heigh
|
||||
export const DisonnectedIcon = ()=><ExternalIcon Icon={DisconnectedSvg} style={{height: '0.7em'}} />;
|
||||
export const RegexIcon = ()=><ExternalIcon Icon={RegexSvg} />;
|
||||
export const FormatCaseIcon = ()=><ExternalIcon Icon={FormatCaseSvg} />;
|
||||
export const ExpandDialog = ()=><ExternalIcon Icon={Expand} style={{height: '1em', width: '1em'}} />;
|
||||
export const MinimizeDialog = ()=><ExternalIcon Icon={Collapse} style={{height: 'auto'}} />;
|
||||
export const ExpandDialogIcon = ()=><ExternalIcon Icon={Expand} style={{height: 'auto', width: '1em'}} />;
|
||||
export const MinimizeDialogIcon = ()=><ExternalIcon Icon={Collapse} style={{height: 'auto'}} />;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ import { DefaultButton, PrimaryButton, PgIconButton } from './Buttons';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
import KeyboardShortcuts from './KeyboardShortcuts';
|
||||
import QueryThresholds from './QueryThresholds';
|
||||
import Themes from './Themes';
|
||||
import SelectThemes from './SelectThemes';
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
@@ -405,7 +405,7 @@ FormInputText.propTypes = {
|
||||
};
|
||||
|
||||
/* Using the existing file dialog functions using showFileDialog */
|
||||
export function InputFileSelect({ controlProps, onChange, disabled, readonly, isvalidate = false, validate, ...props }) {
|
||||
export function InputFileSelect({ controlProps, onChange, disabled, readonly, isvalidate = false, hideBrowseButton=false,validate, ...props }) {
|
||||
const inpRef = useRef();
|
||||
const onFileSelect = (value) => {
|
||||
onChange && onChange(decodeURI(value));
|
||||
@@ -414,8 +414,10 @@ export function InputFileSelect({ controlProps, onChange, disabled, readonly, is
|
||||
return (
|
||||
<InputText ref={inpRef} disabled={disabled} readonly={readonly} onChange={onChange} {...props} endAdornment={
|
||||
<>
|
||||
{!hideBrowseButton &&
|
||||
<IconButton onClick={() => showFileDialog(controlProps, onFileSelect)}
|
||||
disabled={disabled || readonly} aria-label={gettext('Select a file')}><FolderOpenRoundedIcon /></IconButton>
|
||||
}
|
||||
{isvalidate &&
|
||||
<PgIconButton title={gettext('Validate')} style={{ border: 'none' }} disabled={!props.value} onClick={() => { validate(props.value); }} icon={<AssignmentTurnedIn />}></PgIconButton>
|
||||
}
|
||||
@@ -430,7 +432,8 @@ InputFileSelect.propTypes = {
|
||||
readonly: PropTypes.bool,
|
||||
isvalidate: PropTypes.bool,
|
||||
validate: PropTypes.func,
|
||||
value: PropTypes.string
|
||||
value: PropTypes.string,
|
||||
hideBrowseButton: PropTypes.bool
|
||||
};
|
||||
|
||||
export function FormInputFileSelect({
|
||||
@@ -510,7 +513,6 @@ export function InputCheckbox({ cid, helpid, value, onChange, controlProps, read
|
||||
{...props} />
|
||||
}
|
||||
label={controlProps.label}
|
||||
labelPlacement={props?.labelPlacement ? props.labelPlacement : 'end'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -521,7 +523,6 @@ InputCheckbox.propTypes = {
|
||||
controlProps: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
readonly: PropTypes.bool,
|
||||
labelPlacement: PropTypes.string
|
||||
};
|
||||
|
||||
export function FormInputCheckbox({ hasError, required, label,
|
||||
@@ -1189,13 +1190,11 @@ const useStylesKeyboardShortcut = makeStyles(() => ({
|
||||
}
|
||||
}));
|
||||
|
||||
export function FormInputKeyboardShortcut({ hasError, label, className, helpMessage, testcid, onChange, ...props }) {
|
||||
const cid = _.uniqueId('c');
|
||||
const helpid = `h${cid}`;
|
||||
export function FormInputKeyboardShortcut({ hasError, label, className, helpMessage, onChange, ...props }) {
|
||||
const classes = useStylesKeyboardShortcut();
|
||||
return (
|
||||
<FormInput label={label} error={hasError} className={clsx(classes.customRow, className)} helpMessage={helpMessage} testcid={testcid}>
|
||||
<KeyboardShortcuts cid={cid} helpid={helpid} onChange={onChange} {...props} />
|
||||
<FormInput label={label} error={hasError} className={clsx(classes.customRow, className)} helpMessage={helpMessage}>
|
||||
<KeyboardShortcuts onChange={onChange} {...props} />
|
||||
</FormInput>
|
||||
|
||||
);
|
||||
@@ -1229,17 +1228,17 @@ FormInputQueryThreshold.propTypes = {
|
||||
};
|
||||
|
||||
|
||||
export function FormInputThemes({ hasError, label, className, helpMessage, testcid, onChange, ...props }) {
|
||||
export function FormInputSelectThemes({ hasError, label, className, helpMessage, testcid, onChange, ...props }) {
|
||||
const cid = _.uniqueId('c');
|
||||
const helpid = `h${cid}`;
|
||||
return (
|
||||
<FormInput label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<Themes cid={cid} helpid={helpid} onChange={onChange} {...props} />
|
||||
<SelectThemes cid={cid} helpid={helpid} onChange={onChange} {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
|
||||
FormInputThemes.propTypes = {
|
||||
FormInputSelectThemes.propTypes = {
|
||||
hasError: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
className: CustomPropTypes.className,
|
||||
|
||||
@@ -22,6 +22,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
inputCheckboxClass: {
|
||||
border: '1px solid',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
borderColor: theme.otherVars.inputBorderColor,
|
||||
padding: 3
|
||||
}
|
||||
}));
|
||||
@@ -36,6 +37,7 @@ export default function KeyboardShortcuts({ value, onChange, fields }) {
|
||||
const ctrlhelpid = `h${ctrlCid}`;
|
||||
const altCid = _.uniqueId('c');
|
||||
const althelpid = `h${altCid}`;
|
||||
const keyLabel = _.uniqueId('c');
|
||||
|
||||
const onKeyDown = (e) => {
|
||||
let newVal = { ...value };
|
||||
@@ -72,19 +74,20 @@ export default function KeyboardShortcuts({ value, onChange, fields }) {
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="center">
|
||||
alignItems="center"
|
||||
key={_.uniqueId('c')}
|
||||
>
|
||||
{fields.map(element => {
|
||||
let ctrlProps = {
|
||||
label: element.label
|
||||
};
|
||||
if (element.type == 'keyCode') {
|
||||
return <Grid item container lg={4} md={4} sm={4} xs={12}>
|
||||
return <Grid item container lg={4} md={4} sm={4} xs={12} key={_.uniqueId('c')}>
|
||||
<Grid item lg={4} md={4} sm={4} xs={12} className={classes.inputLabel}>
|
||||
<Typography>{element.label}</Typography>
|
||||
<Typography id={keyLabel}>{element.label}</Typography>
|
||||
</Grid>
|
||||
<Grid item lg={8} md={8} sm={8} xs={12}>
|
||||
<InputText cid={keyCid} helpid={keyhelpid} type='text' value={value?.key?.char} controlProps={
|
||||
<InputText id={keyCid} helpid={keyhelpid} type='text' value={value?.key?.char} controlProps={
|
||||
{
|
||||
onKeyDown: onKeyDown,
|
||||
}
|
||||
@@ -92,27 +95,27 @@ export default function KeyboardShortcuts({ value, onChange, fields }) {
|
||||
</Grid>
|
||||
</Grid>;
|
||||
} else if (element.name == 'shift') {
|
||||
return <Grid item lg={2} md={2} sm={2} xs={12} className={classes.inputLabel}>
|
||||
return <Grid item lg={2} md={2} sm={2} xs={12} className={classes.inputLabel} key={_.uniqueId('c')}>
|
||||
<Box className={classes.inputCheckboxClass}>
|
||||
<InputCheckbox cid={shiftCid} helpid={shifthelpid} value={value?.shift}
|
||||
<InputCheckbox id={shiftCid} helpid={shifthelpid} value={value?.shift}
|
||||
controlProps={ctrlProps}
|
||||
onChange={onShiftChange} labelPlacement="end" ></InputCheckbox>
|
||||
onChange={onShiftChange}></InputCheckbox>
|
||||
</Box>
|
||||
</Grid>;
|
||||
} else if (element.name == 'control') {
|
||||
return <Grid item lg={2} md={2} sm={2} xs={12} className={classes.inputLabel}>
|
||||
return <Grid item lg={2} md={2} sm={2} xs={12} className={classes.inputLabel} key={_.uniqueId('c')}>
|
||||
<Box className={classes.inputCheckboxClass}>
|
||||
<InputCheckbox cid={ctrlCid} helpid={ctrlhelpid} value={value?.ctrl}
|
||||
<InputCheckbox id={ctrlCid} helpid={ctrlhelpid} value={value?.ctrl}
|
||||
controlProps={ctrlProps}
|
||||
onChange={onCtrlChange} labelPlacement="end" ></InputCheckbox>
|
||||
onChange={onCtrlChange}></InputCheckbox>
|
||||
</Box>
|
||||
</Grid>;
|
||||
} else if (element.name == 'alt') {
|
||||
return <Grid item lg={3} md={3} sm={3} xs={12} className={classes.inputLabel}>
|
||||
return <Grid item lg={3} md={3} sm={3} xs={12} className={classes.inputLabel} key={_.uniqueId('c')}>
|
||||
<Box className={classes.inputCheckboxClass}>
|
||||
<InputCheckbox cid={altCid} helpid={althelpid} value={value?.alt}
|
||||
<InputCheckbox id={altCid} helpid={althelpid} value={value?.alt}
|
||||
controlProps={ctrlProps}
|
||||
onChange={onAltChange} labelPlacement="end" ></InputCheckbox>
|
||||
onChange={onAltChange}></InputCheckbox>
|
||||
</Box>
|
||||
</Grid>;
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
className={classes.fixedSizeList}
|
||||
height={height - 75}
|
||||
itemCount={rows.length}
|
||||
itemSize={35}
|
||||
itemSize={35}
|
||||
sorted={props?.sortOptions}
|
||||
>
|
||||
{RenderRow}
|
||||
|
||||
@@ -61,7 +61,6 @@ export default function QueryThresholds({ value, onChange }) {
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item lg={2} md={2} sm={2} xs={12}>
|
||||
|
||||
@@ -20,7 +20,7 @@ const useStyles = makeStyles(() => ({
|
||||
}
|
||||
}));
|
||||
|
||||
export default function Themes({onChange, ...props}) {
|
||||
export default function SelectThemes({onChange, ...props}) {
|
||||
const classes = useStyles();
|
||||
const [previewSrc, setPreviewSrc] = useState(null);
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function Themes({onChange, ...props}) {
|
||||
);
|
||||
}
|
||||
|
||||
Themes.propTypes = {
|
||||
SelectThemes.propTypes = {
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
controlProps: PropTypes.object,
|
||||
@@ -20,7 +20,7 @@ import Theme from '../Theme';
|
||||
import HTMLReactParser from 'html-react-parser';
|
||||
import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
|
||||
import { Rnd } from 'react-rnd';
|
||||
import { ExpandDialog, MinimizeDialog } from '../components/ExternalIcon';
|
||||
import { ExpandDialogIcon, MinimizeDialogIcon } from '../components/ExternalIcon';
|
||||
|
||||
const ModalContext = React.createContext({});
|
||||
|
||||
@@ -154,6 +154,15 @@ function PaperComponent(props) {
|
||||
let classes = dialogStyle();
|
||||
let [dialogPosition, setDialogPosition] = useState(null);
|
||||
let resizeable = props.isresizeable == 'true' ? true : false;
|
||||
|
||||
const setEnableResizing = () => {
|
||||
return props.isfullscreen == 'true' ? false : resizeable;
|
||||
};
|
||||
|
||||
const setConditionalPosition = () => {
|
||||
return props.isfullscreen == 'true' ? { x: 0, y: 0 } : dialogPosition && { x: dialogPosition.x, y: dialogPosition.y };
|
||||
};
|
||||
|
||||
return (
|
||||
props.isresizeable == 'true' ?
|
||||
<Rnd
|
||||
@@ -168,8 +177,8 @@ function PaperComponent(props) {
|
||||
{...(props.width && { minWidth: 500 })}
|
||||
{...(props.width && { minHeight: 190 })}
|
||||
bounds="window"
|
||||
enableResizing={props.isfullscreen == 'true' ? false : resizeable}
|
||||
position={props.isfullscreen == 'true' ? { x: 0, y: 0 } : dialogPosition && { x: dialogPosition.x, y: dialogPosition.y }}
|
||||
enableResizing={setEnableResizing()}
|
||||
position={setConditionalPosition()}
|
||||
onDragStop={(e, position) => {
|
||||
if (props.isfullscreen !== 'true') {
|
||||
setDialogPosition({
|
||||
@@ -241,11 +250,11 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, fullScr
|
||||
<Box className={classes.title} marginRight="0.25rem" >{title}</Box>
|
||||
{
|
||||
showFullScreen && !isfullScreen &&
|
||||
<Box marginLeft="auto"><PgIconButton title={gettext('Maximize')} icon={<ExpandDialog className={classes.icon} />} size="xs" noBorder onClick={() => { setIsFullScreen(!isfullScreen); }} /></Box>
|
||||
<Box marginLeft="auto"><PgIconButton title={gettext('Maximize')} icon={<ExpandDialogIcon className={classes.icon} />} size="xs" noBorder onClick={() => { setIsFullScreen(!isfullScreen); }} /></Box>
|
||||
}
|
||||
{
|
||||
showFullScreen && isfullScreen &&
|
||||
<Box marginLeft="auto"><PgIconButton title={gettext('Minimize')} icon={<MinimizeDialog className={classes.icon} />} size="xs" noBorder onClick={() => { setIsFullScreen(!isfullScreen); }} /></Box>
|
||||
<Box marginLeft="auto"><PgIconButton title={gettext('Minimize')} icon={<MinimizeDialogIcon className={classes.icon} />} size="xs" noBorder onClick={() => { setIsFullScreen(!isfullScreen); }} /></Box>
|
||||
}
|
||||
|
||||
<Box marginLeft="auto"><PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={closeModal} /></Box>
|
||||
|
||||
@@ -17,7 +17,7 @@ export class ManagePreferenceTreeNodes {
|
||||
constructor(data) {
|
||||
this.tree = {}
|
||||
this.tempTree = new TreeNode(undefined, {});
|
||||
this.treeData = data;
|
||||
this.treeData = data || [];
|
||||
}
|
||||
|
||||
public init = (_root: string) => new Promise((res, rej) => {
|
||||
@@ -50,7 +50,7 @@ export class ManagePreferenceTreeNodes {
|
||||
if (path === null || path === undefined || path.length === 0 || path == '/preferences') {
|
||||
return this.tempTree;
|
||||
}
|
||||
console.log('Path', path)
|
||||
|
||||
return findInTree(this.tempTree, path);
|
||||
}
|
||||
|
||||
@@ -109,13 +109,15 @@ export class ManagePreferenceTreeNodes {
|
||||
await fill(self.treeData);
|
||||
}
|
||||
|
||||
if (node?.children.length > 0) return res(node.children);
|
||||
else return res(null);
|
||||
|
||||
self.returnChildrens(node, res)
|
||||
}
|
||||
loadData();
|
||||
})
|
||||
|
||||
public returnChildrens = (node: any, res: any) =>{
|
||||
if (node?.children.length > 0) return res(node.children);
|
||||
else return res(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import * as React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { FileTreeX, TreeModelX } from 'pgadmin4-tree';
|
||||
import {Tree} from './tree';
|
||||
|
||||
import { IBasicFileSystemHost } from 'react-aspen';
|
||||
import { ManagePreferenceTreeNodes } from './preference_nodes';
|
||||
|
||||
var initPreferencesTree = async (pgBrowser, container, data) => {
|
||||
const MOUNT_POINT = '/preferences'
|
||||
|
||||
// Setup host
|
||||
let ptree = new ManagePreferenceTreeNodes(data);
|
||||
|
||||
// Init Tree with the Tree Parent node '/browser'
|
||||
ptree.init(MOUNT_POINT);
|
||||
const host: IBasicFileSystemHost = {
|
||||
pathStyle: 'unix',
|
||||
getItems: async (path) => {
|
||||
return ptree.readNode(path);
|
||||
},
|
||||
}
|
||||
|
||||
const pTreeModelX = new TreeModelX(host, MOUNT_POINT)
|
||||
|
||||
const itemHandle = function onReady(handler) {
|
||||
// Initialize pgBrowser Tree
|
||||
pgBrowser.ptree = new Tree(handler, ptree, pgBrowser, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
await pTreeModelX.root.ensureLoaded()
|
||||
|
||||
// Render Browser Tree
|
||||
await render(
|
||||
<FileTreeX model={pTreeModelX}
|
||||
onReady={itemHandle} />
|
||||
, container);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initPreferencesTree: initPreferencesTree,
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
OutlinedInput,
|
||||
} from '@material-ui/core';
|
||||
import KeyboardShortcuts from '../../../pgadmin/static/js/components/KeyboardShortcuts';
|
||||
import { InputCheckbox } from '../../../pgadmin/static/js/components/FormComponents';
|
||||
|
||||
/* MUI Components need to be wrapped in Theme for theme vars */
|
||||
describe('KeyboardShortcuts', () => {
|
||||
@@ -26,7 +27,8 @@ describe('KeyboardShortcuts', () => {
|
||||
'key': {
|
||||
'char': 'a',
|
||||
'key_code': 97
|
||||
}
|
||||
},
|
||||
'shift': false
|
||||
};
|
||||
let fields = [{
|
||||
type: 'keyCode',
|
||||
@@ -63,21 +65,17 @@ describe('KeyboardShortcuts', () => {
|
||||
|
||||
describe('KeyboardShortcuts', () => {
|
||||
let ThemedFormInputKeyboardShortcuts = withTheme(KeyboardShortcuts), ctrl;
|
||||
|
||||
let onChange = jasmine.createSpy('onChange');
|
||||
beforeEach(() => {
|
||||
ctrl = mount(
|
||||
<ThemedFormInputKeyboardShortcuts
|
||||
testcid="inpCid"
|
||||
helpMessage="some help message"
|
||||
/* InputText */
|
||||
readonly={false}
|
||||
disabled={false}
|
||||
maxlength={1}
|
||||
value={defult_value}
|
||||
fields={fields}
|
||||
controlProps={{
|
||||
extraprop: 'test'
|
||||
extraprop: 'test',
|
||||
keyDown: onChange
|
||||
}}
|
||||
onChange={onChange}
|
||||
/>);
|
||||
});
|
||||
|
||||
@@ -85,15 +83,29 @@ describe('KeyboardShortcuts', () => {
|
||||
expect(ctrl.find(OutlinedInput).prop('value')).toBe('a');
|
||||
});
|
||||
|
||||
it('Key change', () => {
|
||||
let onChange = () => {/*This is intentional (SonarQube)*/ };
|
||||
ctrl.setProps({
|
||||
controlProps: {
|
||||
onKeyDown: onChange
|
||||
}
|
||||
});
|
||||
it('Key change', (done) => {
|
||||
ctrl.find(OutlinedInput).at(0).find('input').simulate('keydown', { key: '', keyCode: 32});
|
||||
expect(onChange).toHaveBeenCalledWith({ ctrl: true, alt: true, key: { char: 'Space', key_code: 32 }, shift: false });
|
||||
done();
|
||||
});
|
||||
|
||||
expect(ctrl.find(OutlinedInput).prop('value')).toBe('a');
|
||||
it('Shift option', (done) => {
|
||||
ctrl.find(InputCheckbox).at(0).find('input').simulate('change', { target: { checked: true, name: 'shift' } });
|
||||
expect(onChange).toHaveBeenCalledWith({ ctrl: true, alt: true, key: { char: 'a', key_code: 97 }, shift: true });
|
||||
done();
|
||||
});
|
||||
|
||||
it('Ctrl option', (done) => {
|
||||
ctrl.find(InputCheckbox).at(1).find('input').simulate('change', { target: { checked: false, name: 'ctrl' } });
|
||||
expect(onChange).toHaveBeenCalledWith({ ctrl: false, alt: true, key: { char: 'a', key_code: 97 }, shift: false });
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it('Alt option', (done) => {
|
||||
ctrl.find(InputCheckbox).at(2).find('input').simulate('change', { target: { checked: false, name: 'alt' } });
|
||||
expect(onChange).toHaveBeenCalledWith({ ctrl: true, alt: false, key: { char: 'a', key_code: 97 }, shift: false });
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -12,10 +12,8 @@ import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { withTheme } from '../fake_theme';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import {
|
||||
OutlinedInput,
|
||||
} from '@material-ui/core';
|
||||
import QueryThresholds from '../../../pgadmin/static/js/components/QueryThresholds';
|
||||
import { InputText } from '../../../pgadmin/static/js/components/FormComponents';
|
||||
|
||||
/* MUI Components need to be wrapped in Theme for theme vars */
|
||||
describe('QueryThresholds', () => {
|
||||
@@ -41,45 +39,34 @@ describe('QueryThresholds', () => {
|
||||
|
||||
describe('QueryThresholds', () => {
|
||||
let ThemedFormInputQueryThresholds = withTheme(QueryThresholds), ctrl;
|
||||
|
||||
let onChange = jasmine.createSpy('onChange');
|
||||
beforeEach(() => {
|
||||
ctrl = mount(
|
||||
<ThemedFormInputQueryThresholds
|
||||
testcid="inpCid"
|
||||
testcid="QueryThresholdCid"
|
||||
helpMessage="some help message"
|
||||
/* InputText */
|
||||
readonly={false}
|
||||
disabled={false}
|
||||
maxlength={1}
|
||||
value={defult_value}
|
||||
controlProps={{
|
||||
extraprop: 'test'
|
||||
}}
|
||||
onChange={onChange}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('init Warning', () => {
|
||||
expect(ctrl.find(OutlinedInput).at(0).prop('value')).toBe(5);
|
||||
it('Warning', (done) => {
|
||||
ctrl.find(InputText).at(0).find('input').simulate('change', { warning: 5, alert: 6 });
|
||||
expect(ctrl.find(InputText).at(0).prop('value')).toBe(5);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({ warning: '5', alert: 6 });
|
||||
done();
|
||||
});
|
||||
|
||||
it('init Alert', () => {
|
||||
expect(ctrl.find(OutlinedInput).at(1).prop('value')).toBe(6);
|
||||
});
|
||||
it('Alert', (done) => {
|
||||
ctrl.find(InputText).at(1).find('input').simulate('change', { warning: 5, alert: 6 });
|
||||
expect(ctrl.find(InputText).at(1).prop('value')).toBe(6);
|
||||
|
||||
it('warning change', () => {
|
||||
let onChange = () => {/*This is intentional (SonarQube)*/ };
|
||||
ctrl.setProps({
|
||||
onChange: onChange
|
||||
});
|
||||
expect(ctrl.find(OutlinedInput).at(0).prop('value')).toBe(5);
|
||||
});
|
||||
|
||||
it('Alert change', () => {
|
||||
let onChange = () => {/*This is intentional (SonarQube)*/ };
|
||||
ctrl.setProps({
|
||||
onChange: onChange
|
||||
});
|
||||
expect(ctrl.find(OutlinedInput).at(1).prop('value')).toBe(6);
|
||||
expect(onChange).toHaveBeenCalledWith({ warning: 5, alert: '6' });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { withTheme } from '../fake_theme';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import Themes from '../../../pgadmin/static/js/components/Themes';
|
||||
import SelectThemes from '../../../pgadmin/static/js/components/SelectThemes';
|
||||
import { InputSelect } from '../../../pgadmin/static/js/components/FormComponents';
|
||||
|
||||
/* MUI Components need to be wrapped in Theme for theme vars */
|
||||
describe('Themes', () => {
|
||||
describe('SelectThemes', () => {
|
||||
let mount;
|
||||
let options = [{
|
||||
value: 'standard',
|
||||
@@ -51,13 +51,13 @@ describe('Themes', () => {
|
||||
jasmineEnzyme();
|
||||
});
|
||||
|
||||
describe('Themes', () => {
|
||||
let ThemedFormInputThemes = withTheme(Themes), ctrl, onChange = jasmine.createSpy('onChange');
|
||||
describe('Select Themes', () => {
|
||||
let ThemedFormInputThemes = withTheme(SelectThemes), ctrl, onChange = jasmine.createSpy('onChange');
|
||||
|
||||
beforeEach(() => {
|
||||
ctrl = mount(
|
||||
<ThemedFormInputThemes
|
||||
testcid="inpCid"
|
||||
testcid="SelectThemeCid"
|
||||
helpMessage="some help message"
|
||||
options={options}
|
||||
onChange={onChange}
|
||||